[DirectX12学习笔记] Cube Mapping

本文介绍了DirectX12中Cube Mapping的概念及其在环境映射、天空球和动态反射的应用。通过立方体贴图,可以实现反射效果,包括天空球反射的Demo和动态cubemap采样技术,展示了如何创建和渲染带有动态反射的物体。
摘要由CSDN通过智能技术生成
  • 注意!本文是在下几年前入门期间所写(young and naive),其中许多表述可能不正确,为防止误导,请各位读者仔细鉴别。

Cube Mapping


Cube Mapping简介

Cube Map是用6张贴图存一个正方体上的贴图,存的时候是跟坐标轴对齐的,index的0~5分别对应+X,-X,+Y,-Y,+Z,-Z,然后采样不再用uv,而使用一个三维矢量v,v的长度无所谓,v无限延长的射线与正方体的交点就是采样位置,注意v如果要采样的话需要变换到和cube map同一个空间里来,因为cube map是和坐标轴对齐的,要得到正确采样结果的话v也要在这个坐标空间下才行。

Cube Map的采样一般是用6个横竖FOV都是90度的摄像头。

cube map经常用来实现环境贴图。环境贴图我们希望是离我们无限远的,一个很简单的做法就是以摄像头为中心创建一个球,然后这个球总是跟着摄像头一起动,也就是说摄像头无论怎么动背景都不会动,这也就形成了无穷远的错觉。

接下来讨论一下Cube Map怎么样实现反射环境的效果。首先如下图所示
在这里插入图片描述
E是我们眼睛的位置,I是入射光,n是法线,可以看出我们应该用矢量r=reflect(-v,n)来采样。代码如下

const float shininess = 1.0f - roughness;
// Add in specular reflections.
float3 r = reflect(-toEyeW, pin.NormalW);
float4 reflectionColor = gCubeMap.Sample(gsamLinearWrap, r);
float3 fresnelFactor = SchlickFresnel(fresnelR0, pin.NormalW, r);
litColor.rgb += shininess * fresnelFactor * reflectionColor.rgb;

然而这种方法并不适合于比较大的平面,对曲面状的物体而言这种方法一般不会穿帮,但是对一个很大的平面来说,这种方法没有把位置考虑进去,就会出现问题,如下图所示。
在这里插入图片描述
图中两个位置的采样结果是一样的,这是一种错误,因此real-time rendering这本书的第三版提出了一种解决方法。如下图
在这里插入图片描述
这种方法是把反射点的位置也考虑进去了,最后用的采样矢量是p+t0r,代码如下

float3 BoxCubeMapLookup(float3 rayOrigin, float3 unitRayDir,
	float3 boxCenter, float3 boxExtents)
{
   
// Based on slab method as described in Real-Time Rendering
// 16.7.1 (3rd edition).
// Make relative to the box center.
float3 p = rayOrigin - boxCenter;
// The ith slab ray/plane intersection formulas for AABB are:
//
// t1 = (-dot(n_i, p) + h_i)/dot(n_i, d) = (-p_i + h_i)/d_i
// t2 = (-dot(n_i, p) - h_i)/dot(n_i, d) = (-p_i - h_i)/d_i
// Vectorize and do ray/plane formulas for every slab together.
float3 t1 = (-p+boxExtents)/unitRayDir;
float3 t2 = (-p-boxExtents)/unitRayDir;
// Find max for each coordinate. Because we assume the ray is inside
// the box, we only want the max intersection parameter.
float3 tmax = max(t1, t2);
// Take minimum of all the tmax components:
float t = min(min(tmax.x, tmax.y), tmax.z);
// This is relative to the box center so it can be used as a
// cube map lookup vector.
return p + t*unitRayDir;
}

这里t1和t2都是float3,共包含6个浮点数,对应的是6个t,分别是aabb的六个面和直线交点的t,然后把t1和t2的三个分量分别取max,使用因为反射的射线的t都是大于0的,小于0的那三个是和反射方向相反的方向,应该直接舍弃掉,然后剩下的这三个t里面t最小的就是和aabb的交点,其他两个都是扩展平面上的。


天空球与环境反射demo

接下来用上面提到的内容实现一个天空球和一些反射天空的小球,并列出关键部分的代码。

首先Cube Map的载入方法和普通贴图是一样的,dds支持cube map,所以和以前一样读取即可,这里不再列出代码。

然后创建Root Signature的时候单独用一个SRV来存天空球的cube map

void CubeMapApp::BuildRootSignature()
{
   
	CD3DX12_DESCRIPTOR_RANGE texTable0;
	texTable0.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 1, 0, 0);

	CD3DX12_DESCRIPTOR_RANGE texTable1;
	texTable1.Init(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 5, 1, 0);

    // Root parameter can be a table, root descriptor or root constants.
    CD3DX12_ROOT_PARAMETER slotRootParameter[5];

	// Perfomance TIP: Order from most frequent to least frequent.
    slotRootParameter[0].InitAsConstantBufferView(0);
    slotRootParameter[1].InitAsConstantBufferView(1);
    slotRootParameter[2].InitAsShaderResourceView(0, 1);
	slotRootParameter[3].InitAsDescriptorTable(1, &texTable0, D3D12_SHADER_VISIBILITY_PIXEL);
	slotRootParameter[4].InitAsDescriptorTable(1, &texTable1, D3D12_SHADER_VISIBILITY_PIXEL);


	auto staticSamplers = GetStaticSamplers();

    // A root signature is an array of root parameters.
	CD3DX12_ROOT_SIGNATURE_DESC rootSigDesc(5, slotRootParameter,
		(UINT)staticSamplers.size(), staticSamplers.data(),
		D3D12_ROOT_SIGNATURE_FLAG_ALLOW_INPUT_ASSEMBLER_INPUT_LAYOUT);

    // create a root signature with a single slot which points to a descriptor range consisting of a single constant buffer
    ComPtr<ID3DBlob> serializedRootSig = nullptr;
    ComPtr<ID3DBlob> errorBlob = nullptr;
    HRESULT hr = D3D12SerializeRootSignature(&rootSigDesc, D3D_ROOT_SIGNATURE_VERSION_1,
        serializedRootSig.GetAddressOf(), errorBlob.GetAddressOf());

    if(errorBlob != nullptr)
    {
   
        ::OutputDebugStringA((char*)errorBlob->GetBufferPointer());
    }
    ThrowIfFailed(hr);

    ThrowIfFailed(md3dDevice->CreateRootSignature(
		0,
        serializedRootSig->GetBufferPointer(),
        serializedRootSig->GetBufferSize(),
        IID_PPV_ARGS(mRootSignature.GetAddressOf())));
}

创建descriptor heap的时候,天空球贴图的srv要设置格式成D3D12_SRV_DIMENSION_TEXTURECUBE

void CubeMapApp::BuildDescriptorHeaps()
{
   

	···
	
	// next descriptor
	hDescriptor.Offset(1, mCbvSrvDescriptorSize);

	srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE;
	srvDesc.TextureCube.MostDetailedMip = 0;
	srvDesc.TextureCube.MipLevels = skyTex->GetDesc().MipLevels;
	srvDesc.TextureCube.ResourceMinLODClamp = 0.0f;
	srvDesc.Format = skyTex->GetDesc().Format;
	md3dDevice->CreateShaderResourceView(skyTex.Get(), &srvDesc, hDescriptor);
	
	mSkyTexHeapIndex = 3;
}

创建材质的时候天空球和反射很强的材质如下,反射强就用一个很大的菲涅尔系数就可以了,具体见后面的shader代码

    auto sky = std::make_unique<Material>();
    sky->Name = "sky";
    sky->MatCBIndex = 4;
    sky->DiffuseSrvHeapIndex = 3;
    sky->DiffuseAlbedo = XMFLOAT4(1.0f, 1.0f, 1.0f, 1.0f);
    sky->FresnelR0 = XMFLOAT3(0.1f, 0.1f, 0.1f);
    sky->Roughness = 1.0f;

	auto mirror0 = std::make_unique<Material>();
    mirror0->Name = "mirror0";
    mirror0->MatCBIndex = 2;
    mirror0->DiffuseSrvHeapIndex = 2;
    mirror0->DiffuseAlbedo = XMFLOAT4(0.0f, 0.0f, 0.1f, 1.0f);
    mirror0->FresnelR0 = XMFLOAT3(0.98f, 0.97f, 0.95f);
    mirror0->Roughness = 0.1f;

创建render item的时候天空单独作为一个层,然后建立一个很大的球体

	auto skyRitem = std::make_unique<RenderItem>();
	XMStoreFloat4x4(&skyRitem->World, XMMatrixScaling(5000.0f, 5000.0f, 5000.0f));
	skyRitem->TexTransform = MathHelper::Identity4x4();
	skyRitem->ObjCBIndex = 0;
	skyRitem->Mat = mMaterials["sky"].get();
	skyRitem->Geo = mGeometries[
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值