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的交点,其他两个都是扩展平面上的。
接下来用上面提到的内容实现一个天空球和一些反射天空的小球,并列出关键部分的代码。
首先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["shapeGeo"].get();
skyRitem->PrimitiveType = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
skyRitem->IndexCount = skyRitem->Geo->DrawArgs["sphere"].IndexCount;
skyRitem->StartIndexLocation = skyRitem->Geo->DrawArgs["sphere"].StartIndexLocation;
skyRitem->BaseVertexLocation = skyRitem->Geo->DrawArgs["sphere"].BaseVertexLocation;
mRitemLayer[(int)RenderLayer::Sky].push_back(skyRitem.get());
mAllRitems.push_back(std::move(skyRitem));
然后因为天空的渲染方法和普通的完全不同,所以我们用另外一个shader,也就是还要用另一个pso来渲染天空。然后深度检测的LESS要改成LESS_EQUAL,因为我们在shader里要把天空球上的点投射到远平面上,也就是令z=1,如果是小于而不是小于等于的话,天空球会无法通过深度检测。然后背面cull要关掉,或者把三角形顺时针改成逆时针也行。
//
// PSO for sky.
//
D3D12_GRAPHICS_PIPELINE_STATE_DESC skyPsoDesc = opaquePsoDesc;
// The camera is inside the sky sphere, so just turn off culling.
skyPsoDesc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
// Make sure the depth function is LESS_EQUAL and not just LESS.
// Otherwise, the normalized depth values at z = 1 (NDC) will
// fail the depth test if the depth buffer was cleared to 1.
skyPsoDesc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL;
skyPsoDesc.pRootSignature = mRootSignature.Get();
skyPsoDesc.VS =
{
reinterpret_cast<BYTE*>(mShaders["skyVS"]->GetBufferPointer()),
mShaders["skyVS"]->GetBufferSize()
};
skyPsoDesc.PS =
{
reinterpret_cast<BYTE*>(mShaders["skyPS"]->GetBufferPointer()),
mShaders["skyPS"]->GetBufferSize()
};
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&skyPsoDesc, IID_PPV_ARGS(&mPSOs["sky"])));
然后是draw部分,先渲染不透明部分再渲染天空球
// Bind the sky cube map. For our demos, we just use one "world" cube map representing the environment
// from far away, so all objects will use the same cube map and we only need to set it once per-frame.
// If we wanted to use "local" cube maps, we would have to change them per-object, or dynamically
// index into an array of cube maps.
CD3DX12_GPU_DESCRIPTOR_HANDLE skyTexDescriptor(mSrvDescriptorHeap->GetGPUDescriptorHandleForHeapStart());
skyTexDescriptor.Offset(mSkyTexHeapIndex, mCbvSrvDescriptorSize);
mCommandList->SetGraphicsRootDescriptorTable(3, skyTexDescriptor);
// Bind all the textures used in this scene. Observe
// that we only have to specify the first descriptor in the table.
// The root signature knows how many descriptors are expected in the table.
mCommandList->SetGraphicsRootDescriptorTable(4, mSrvDescriptorHeap->GetGPUDescriptorHandleForHeapStart());
DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::Opaque]);
mCommandList->SetPipelineState(mPSOs["sky"].Get());
DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::Sky]);
最后是shader部分,shader有两个,一个渲染天空球一个渲染不透明物体的,两者的共同部分(头部的声明)放在common.hlsl里,不透明物体的和原来的很像,就改了一点点。首先看common.hlsl,里面主要多了个cubemap的声明,要用TextureCube来声明,如下
TextureCube gCubeMap : register(t0);
接下来是default.hlsl,和之前稍微有点不同,加入了反射部分,这部分是从cubemap上采样,再乘以菲涅尔系数和粗糙度系数得到的。因此材质里面把菲涅尔系数设置的很大然后设置得很光滑,这个反射就会很明显。
// 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;
然后是天空球的shader,内容很简单,VS投影到原平面上,PS采样cubemap即可。
VertexOut VS(VertexIn vin)
{
VertexOut vout;
// Use local vertex position as cubemap lookup vector.
vout.PosL = vin.PosL;
// Transform to world space.
float4 posW = mul(float4(vin.PosL, 1.0f), gWorld);
// Always center sky about camera.
posW.xyz += gEyePosW;
// Set z = w so that z/w = 1 (i.e., skydome always on far plane).
vout.PosH = mul(posW, gViewProj).xyww;
return vout;
}
float4 PS(VertexOut pin) : SV_Target
{
return gCubeMap.Sample(gsamLinearWrap, pin.PosL);
}
最终得到的渲染结果如图,可以看到地板和球上的反射结果根据摄像头位置的不同而产生变化。
在这个demo里面我们做这么一件事,用6个摄像头渲染一个物体周围的所有物体(除了这个物体本身),渲染结果写入到6个render target里,这6张贴图构成一个cubemap,再作为输入,输入到shader中用来渲染这个物体本身,这样我们就可以实现一个带有动态反射效果的物体。
这里直接列出关键部分代码。
首先我们封装一个CubeRenderTarget类简化操作,这个类里面存一个viewport,scissor rect,和6个cpu的rtv,一个cpu的srv和一个gpu的srv等。
class CubeRenderTarget
{
public:
CubeRenderTarget(ID3D12Device* device,
UINT width, UINT height,
DXGI_FORMAT format);
CubeRenderTarget(const CubeRenderTarget& rhs)=delete;
CubeRenderTarget& operator=(const CubeRenderTarget& rhs)=delete;
~CubeRenderTarget()=default;
ID3D12Resource* Resource();
CD3DX12_GPU_DESCRIPTOR_HANDLE Srv();
CD3DX12_CPU_DESCRIPTOR_HANDLE Rtv(int faceIndex);
D3D12_VIEWPORT Viewport()const;
D3D12_RECT ScissorRect()const;
void BuildDescriptors(
CD3DX12_CPU_DESCRIPTOR_HANDLE hCpuSrv,
CD3DX12_GPU_DESCRIPTOR_HANDLE hGpuSrv,
CD3DX12_CPU_DESCRIPTOR_HANDLE hCpuRtv[6]);
void OnResize(UINT newWidth, UINT newHeight);
private:
void BuildDescriptors();
void BuildResource();
private:
ID3D12Device* md3dDevice = nullptr;
D3D12_VIEWPORT mViewport;
D3D12_RECT mScissorRect;
UINT mWidth = 0;
UINT mHeight = 0;
DXGI_FORMAT mFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
CD3DX12_CPU_DESCRIPTOR_HANDLE mhCpuSrv;
CD3DX12_GPU_DESCRIPTOR_HANDLE mhGpuSrv;
CD3DX12_CPU_DESCRIPTOR_HANDLE mhCpuRtv[6];
Microsoft::WRL::ComPtr<ID3D12Resource> mCubeMap = nullptr;
};
然后这个类里面包含了创建resource的方法,我们创建6个rtv的resource,数组大小为6,设置allow render target这个flag,如下
void CubeRenderTarget::BuildResource()
{
// Note, compressed formats cannot be used for UAV. We get error like:
// ERROR: ID3D11Device::CreateTexture2D: The format (0x4d, BC3_UNORM)
// cannot be bound as an UnorderedAccessView, or cast to a format that
// could be bound as an UnorderedAccessView. Therefore this format
// does not support D3D11_BIND_UNORDERED_ACCESS.
D3D12_RESOURCE_DESC texDesc;
ZeroMemory(&texDesc, sizeof(D3D12_RESOURCE_DESC));
texDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
texDesc.Alignment = 0;
texDesc.Width = mWidth;
texDesc.Height = mHeight;
texDesc.DepthOrArraySize = 6;
texDesc.MipLevels = 1;
texDesc.Format = mFormat;
texDesc.SampleDesc.Count = 1;
texDesc.SampleDesc.Quality = 0;
texDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
texDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
ThrowIfFailed(md3dDevice->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&texDesc,
D3D12_RESOURCE_STATE_GENERIC_READ,
nullptr,
IID_PPV_ARGS(&mCubeMap)));
}
最后这个资源创建在mCubemap位置,这个resource之后还可以当作输入,用了allow render target这个flag,而不需要allow shader resource,因为默认就可以用作shader resource,其他的才要allow。
现在我们有了rt的resource,还需要6个rtv,这里我们override了D3Dapp的CreateRtvAndDsvDescriptorHeaps这个方法,把rtv的数量改成了SwapChainBufferCount+6,然后DSV多要了一个(因为是依次渲染6个,所以其实1个dsv就行了)。
void DynamicCubeMapApp::CreateRtvAndDsvDescriptorHeaps()
{
// Add +6 RTV for cube render target.
D3D12_DESCRIPTOR_HEAP_DESC rtvHeapDesc;
rtvHeapDesc.NumDescriptors = SwapChainBufferCount + 6;
rtvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
rtvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
rtvHeapDesc.NodeMask = 0;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(
&rtvHeapDesc, IID_PPV_ARGS(mRtvHeap.GetAddressOf())));
// Add +1 DSV for cube render target.
D3D12_DESCRIPTOR_HEAP_DESC dsvHeapDesc;
dsvHeapDesc.NumDescriptors = 2;
dsvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
dsvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_NONE;
dsvHeapDesc.NodeMask = 0;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(
&dsvHeapDesc, IID_PPV_ARGS(mDsvHeap.GetAddressOf())));
mCubeDSV = CD3DX12_CPU_DESCRIPTOR_HANDLE(
mDsvHeap->GetCPUDescriptorHandleForHeapStart(),
1,
mDsvDescriptorSize);
}
现在堆建好了,我们要把多出来的6个rtv和1的dsv也建好放进去,用CubeRenderTarget的BuildDescriptors这个方法。注意rtv要六个,但是srv只要一个就可以了,因为我们把格式设置成TEXTURECUBE就声明的是cubemap类型了,也就是包含了六张。代码如下
auto srvCpuStart = mSrvDescriptorHeap->GetCPUDescriptorHandleForHeapStart();
auto srvGpuStart = mSrvDescriptorHeap->GetGPUDescriptorHandleForHeapStart();
auto rtvCpuStart = mRtvHeap->GetCPUDescriptorHandleForHeapStart();
// Cubemap RTV goes after the swap chain descriptors.
int rtvOffset = SwapChainBufferCount;
CD3DX12_CPU_DESCRIPTOR_HANDLE cubeRtvHandles[6];
for(int i = 0; i < 6; ++i)
cubeRtvHandles[i] = CD3DX12_CPU_DESCRIPTOR_HANDLE(rtvCpuStart, rtvOffset + i, mRtvDescriptorSize);
// Dynamic cubemap SRV is after the sky SRV.
mDynamicCubeMap->BuildDescriptors(
CD3DX12_CPU_DESCRIPTOR_HANDLE(srvCpuStart, mDynamicTexHeapIndex, mCbvSrvUavDescriptorSize),
CD3DX12_GPU_DESCRIPTOR_HANDLE(srvGpuStart, mDynamicTexHeapIndex, mCbvSrvUavDescriptorSize),
cubeRtvHandles);
void CubeRenderTarget::BuildDescriptors(CD3DX12_CPU_DESCRIPTOR_HANDLE hCpuSrv,
CD3DX12_GPU_DESCRIPTOR_HANDLE hGpuSrv,
CD3DX12_CPU_DESCRIPTOR_HANDLE hCpuRtv[6])
{
// Save references to the descriptors.
mhCpuSrv = hCpuSrv;
mhGpuSrv = hGpuSrv;
for(int i = 0; i < 6; ++i)
mhCpuRtv[i] = hCpuRtv[i];
// Create the descriptors
BuildDescriptors();
}
void CubeRenderTarget::BuildDescriptors()
{
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Format = mFormat;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURECUBE;
srvDesc.TextureCube.MostDetailedMip = 0;
srvDesc.TextureCube.MipLevels = 1;
srvDesc.TextureCube.ResourceMinLODClamp = 0.0f;
// Create SRV to the entire cubemap resource.
md3dDevice->CreateShaderResourceView(mCubeMap.Get(), &srvDesc, mhCpuSrv);
// Create RTV to each cube face.
for(int i = 0; i < 6; ++i)
{
D3D12_RENDER_TARGET_VIEW_DESC rtvDesc;
rtvDesc.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2DARRAY;
rtvDesc.Format = mFormat;
rtvDesc.Texture2DArray.MipSlice = 0;
rtvDesc.Texture2DArray.PlaneSlice = 0;
// Render target to ith element.
rtvDesc.Texture2DArray.FirstArraySlice = i;
// Only view one element of the array.
rtvDesc.Texture2DArray.ArraySize = 1;
// Create RTV to ith cubemap face.
md3dDevice->CreateRenderTargetView(mCubeMap.Get(), &rtvDesc, mhCpuRtv[i]);
}
}
然后因为我们创建了新的render target,分辨率和back buffer不一样,所以viewport和scissor rectangle也要重新创建,这个部分写在CubeRenderTarget类的构造方法里面了。
CubeRenderTarget::CubeRenderTarget(ID3D12Device* device,
UINT width, UINT height,
DXGI_FORMAT format)
{
md3dDevice = device;
mWidth = width;
mHeight = height;
mFormat = format;
mViewport = { 0.0f, 0.0f, (float)width, (float)height, 0.0f, 1.0f };
mScissorRect = { 0, 0, (int)width, (int)height };
BuildResource();
}
接下来看主程序部分。
初始化的时候我们要先创建好6个camera用来渲染6个render target,这里的输入参数是摄像头的位置,摄像头朝向是和世界坐标空间里的坐标轴对齐的。
void DynamicCubeMapApp::BuildCubeFaceCamera(float x, float y, float z)
{
// Generate the cube map about the given position.
XMFLOAT3 center(x, y, z);
XMFLOAT3 worldUp(0.0f, 1.0f, 0.0f);
// Look along each coordinate axis.
XMFLOAT3 targets[6] =
{
XMFLOAT3(x + 1.0f, y, z), // +X
XMFLOAT3(x - 1.0f, y, z), // -X
XMFLOAT3(x, y + 1.0f, z), // +Y
XMFLOAT3(x, y - 1.0f, z), // -Y
XMFLOAT3(x, y, z + 1.0f), // +Z
XMFLOAT3(x, y, z - 1.0f) // -Z
};
// Use world up vector (0,1,0) for all directions except +Y/-Y. In these cases, we
// are looking down +Y or -Y, so we need a different "up" vector.
XMFLOAT3 ups[6] =
{
XMFLOAT3(0.0f, 1.0f, 0.0f), // +X
XMFLOAT3(0.0f, 1.0f, 0.0f), // -X
XMFLOAT3(0.0f, 0.0f, -1.0f), // +Y
XMFLOAT3(0.0f, 0.0f, +1.0f), // -Y
XMFLOAT3(0.0f, 1.0f, 0.0f), // +Z
XMFLOAT3(0.0f, 1.0f, 0.0f) // -Z
};
for(int i = 0; i < 6; ++i)
{
mCubeMapCamera[i].LookAt(center, targets[i], ups[i]);
mCubeMapCamera[i].SetLens(0.5f*XM_PI, 1.0f, 0.1f, 1000.0f);
mCubeMapCamera[i].UpdateViewMatrix();
}
}
然后创建descriptor heap的时候要把多出来的一个srv创建好,顺便把rtv也创建好(这部分代码在上面贴过一次了),代码如下
void DynamicCubeMapApp::BuildDescriptorHeaps()
{
//
// Create the SRV heap.
//
D3D12_DESCRIPTOR_HEAP_DESC srvHeapDesc = {};
srvHeapDesc.NumDescriptors = 6;
srvHeapDesc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
srvHeapDesc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
ThrowIfFailed(md3dDevice->CreateDescriptorHeap(&srvHeapDesc, IID_PPV_ARGS(&mSrvDescriptorHeap)));
//
// Fill out the heap with actual descriptors.
//
CD3DX12_CPU_DESCRIPTOR_HANDLE hDescriptor(mSrvDescriptorHeap->GetCPUDescriptorHandleForHeapStart());
auto bricksTex = mTextures["bricksDiffuseMap"]->Resource;
auto tileTex = mTextures["tileDiffuseMap"]->Resource;
auto whiteTex = mTextures["defaultDiffuseMap"]->Resource;
auto skyTex = mTextures["skyCubeMap"]->Resource;
D3D12_SHADER_RESOURCE_VIEW_DESC srvDesc = {};
srvDesc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
srvDesc.Format = bricksTex->GetDesc().Format;
srvDesc.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D;
srvDesc.Texture2D.MostDetailedMip = 0;
srvDesc.Texture2D.MipLevels = bricksTex->GetDesc().MipLevels;
srvDesc.Texture2D.ResourceMinLODClamp = 0.0f;
md3dDevice->CreateShaderResourceView(bricksTex.Get(), &srvDesc, hDescriptor);
// next descriptor
hDescriptor.Offset(1, mCbvSrvUavDescriptorSize);
srvDesc.Format = tileTex->GetDesc().Format;
srvDesc.Texture2D.MipLevels = tileTex->GetDesc().MipLevels;
md3dDevice->CreateShaderResourceView(tileTex.Get(), &srvDesc, hDescriptor);
// next descriptor
hDescriptor.Offset(1, mCbvSrvUavDescriptorSize);
srvDesc.Format = whiteTex->GetDesc().Format;
srvDesc.Texture2D.MipLevels = whiteTex->GetDesc().MipLevels;
md3dDevice->CreateShaderResourceView(whiteTex.Get(), &srvDesc, hDescriptor);
// next descriptor
hDescriptor.Offset(1, mCbvSrvUavDescriptorSize);
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;
mDynamicTexHeapIndex = mSkyTexHeapIndex+1;
auto srvCpuStart = mSrvDescriptorHeap->GetCPUDescriptorHandleForHeapStart();
auto srvGpuStart = mSrvDescriptorHeap->GetGPUDescriptorHandleForHeapStart();
auto rtvCpuStart = mRtvHeap->GetCPUDescriptorHandleForHeapStart();
// Cubemap RTV goes after the swap chain descriptors.
int rtvOffset = SwapChainBufferCount;
CD3DX12_CPU_DESCRIPTOR_HANDLE cubeRtvHandles[6];
for(int i = 0; i < 6; ++i)
cubeRtvHandles[i] = CD3DX12_CPU_DESCRIPTOR_HANDLE(rtvCpuStart, rtvOffset + i, mRtvDescriptorSize);
// Dynamic cubemap SRV is after the sky SRV.
mDynamicCubeMap->BuildDescriptors(
CD3DX12_CPU_DESCRIPTOR_HANDLE(srvCpuStart, mDynamicTexHeapIndex, mCbvSrvUavDescriptorSize),
CD3DX12_GPU_DESCRIPTOR_HANDLE(srvGpuStart, mDynamicTexHeapIndex, mCbvSrvUavDescriptorSize),
cubeRtvHandles);
}
接下来还要创建cube的dsv,创建一个就行
void DynamicCubeMapApp::BuildCubeDepthStencil()
{
// Create the depth/stencil buffer and view.
D3D12_RESOURCE_DESC depthStencilDesc;
depthStencilDesc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
depthStencilDesc.Alignment = 0;
depthStencilDesc.Width = CubeMapSize;
depthStencilDesc.Height = CubeMapSize;
depthStencilDesc.DepthOrArraySize = 1;
depthStencilDesc.MipLevels = 1;
depthStencilDesc.Format = mDepthStencilFormat;
depthStencilDesc.SampleDesc.Count = 1;
depthStencilDesc.SampleDesc.Quality = 0;
depthStencilDesc.Layout = D3D12_TEXTURE_LAYOUT_UNKNOWN;
depthStencilDesc.Flags = D3D12_RESOURCE_FLAG_ALLOW_DEPTH_STENCIL;
D3D12_CLEAR_VALUE optClear;
optClear.Format = mDepthStencilFormat;
optClear.DepthStencil.Depth = 1.0f;
optClear.DepthStencil.Stencil = 0;
ThrowIfFailed(md3dDevice->CreateCommittedResource(
&CD3DX12_HEAP_PROPERTIES(D3D12_HEAP_TYPE_DEFAULT),
D3D12_HEAP_FLAG_NONE,
&depthStencilDesc,
D3D12_RESOURCE_STATE_COMMON,
&optClear,
IID_PPV_ARGS(mCubeDepthStencilBuffer.GetAddressOf())));
// Create descriptor to mip level 0 of entire resource using the format of the resource.
md3dDevice->CreateDepthStencilView(mCubeDepthStencilBuffer.Get(), nullptr, mCubeDSV);
// Transition the resource from its initial state to be used as a depth buffer.
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(mCubeDepthStencilBuffer.Get(),
D3D12_RESOURCE_STATE_COMMON, D3D12_RESOURCE_STATE_DEPTH_WRITE));
}
创建render item的时候多创建一个用来动态反射的物体,单独占一个层,即OpaqueDynamicReflectors层:
auto globeRitem = std::make_unique<RenderItem>();
XMStoreFloat4x4(&globeRitem->World, XMMatrixScaling(2.0f, 2.0f, 2.0f)*XMMatrixTranslation(0.0f, 2.0f, 0.0f));
XMStoreFloat4x4(&globeRitem->TexTransform, XMMatrixScaling(1.0f, 1.0f, 1.0f));
globeRitem->ObjCBIndex = 3;
globeRitem->Mat = mMaterials["mirror0"].get();
globeRitem->Geo = mGeometries["shapeGeo"].get();
globeRitem->PrimitiveType = D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
globeRitem->IndexCount = globeRitem->Geo->DrawArgs["sphere"].IndexCount;
globeRitem->StartIndexLocation = globeRitem->Geo->DrawArgs["sphere"].StartIndexLocation;
globeRitem->BaseVertexLocation = globeRitem->Geo->DrawArgs["sphere"].BaseVertexLocation;
mRitemLayer[(int)RenderLayer::OpaqueDynamicReflectors].push_back(globeRitem.get());
mAllRitems.push_back(std::move(globeRitem));
这个OpaqueDynamicReflectors层用的是Opaque的PSO。
接下来就是要渲染了,我们先渲染cubemap的6个render target。
void DynamicCubeMapApp::DrawSceneToCubeMap()
{
mCommandList->RSSetViewports(1, &mDynamicCubeMap->Viewport());
mCommandList->RSSetScissorRects(1, &mDynamicCubeMap->ScissorRect());
// Change to RENDER_TARGET.
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(mDynamicCubeMap->Resource(),
D3D12_RESOURCE_STATE_GENERIC_READ, D3D12_RESOURCE_STATE_RENDER_TARGET));
UINT passCBByteSize = d3dUtil::CalcConstantBufferByteSize(sizeof(PassConstants));
// For each cube map face.
for(int i = 0; i < 6; ++i)
{
// Clear the back buffer and depth buffer.
mCommandList->ClearRenderTargetView(mDynamicCubeMap->Rtv(i), Colors::LightSteelBlue, 0, nullptr);
mCommandList->ClearDepthStencilView(mCubeDSV, D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);
// Specify the buffers we are going to render to.
mCommandList->OMSetRenderTargets(1, &mDynamicCubeMap->Rtv(i), true, &mCubeDSV);
// Bind the pass constant buffer for this cube map face so we use
// the right view/proj matrix for this cube face.
auto passCB = mCurrFrameResource->PassCB->Resource();
D3D12_GPU_VIRTUAL_ADDRESS passCBAddress = passCB->GetGPUVirtualAddress() + (1+i)*passCBByteSize;
mCommandList->SetGraphicsRootConstantBufferView(1, passCBAddress);
DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::Opaque]);
mCommandList->SetPipelineState(mPSOs["sky"].Get());
DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::Sky]);
mCommandList->SetPipelineState(mPSOs["opaque"].Get());
}
// Change back to GENERIC_READ so we can read the texture in a shader.
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(mDynamicCubeMap->Resource(),
D3D12_RESOURCE_STATE_RENDER_TARGET, D3D12_RESOURCE_STATE_GENERIC_READ));
}
注意这里返回的Rtv()用的是cpu的地址,传入和清空rtv都是用cpu的地址,这点和srv不同,我们不需要rtv的gpu地址。而Srv()返回的是gpu地址,因为我们用的时候(也就是传入的时候)需要的是gpu地址。
然后是draw部分,渲染完6个render target之后我们用渲染出来的结果渲染OpaqueDynamicReflectors层,然后再是opaque和sky这两个层。注意这里用mDynamicCubeMap->Srv()代替dynamicTexDescriptor来传入也是一样的。
DrawSceneToCubeMap();
mCommandList->RSSetViewports(1, &mScreenViewport);
mCommandList->RSSetScissorRects(1, &mScissorRect);
// Indicate a state transition on the resource usage.
mCommandList->ResourceBarrier(1, &CD3DX12_RESOURCE_BARRIER::Transition(CurrentBackBuffer(),
D3D12_RESOURCE_STATE_PRESENT, D3D12_RESOURCE_STATE_RENDER_TARGET));
// Clear the back buffer and depth buffer.
mCommandList->ClearRenderTargetView(CurrentBackBufferView(), Colors::LightSteelBlue, 0, nullptr);
mCommandList->ClearDepthStencilView(DepthStencilView(), D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f, 0, 0, nullptr);
// Specify the buffers we are going to render to.
mCommandList->OMSetRenderTargets(1, &CurrentBackBufferView(), true, &DepthStencilView());
auto passCB = mCurrFrameResource->PassCB->Resource();
mCommandList->SetGraphicsRootConstantBufferView(1, passCB->GetGPUVirtualAddress());
// Use the dynamic cube map for the dynamic reflectors layer.
CD3DX12_GPU_DESCRIPTOR_HANDLE dynamicTexDescriptor(mSrvDescriptorHeap->GetGPUDescriptorHandleForHeapStart());
dynamicTexDescriptor.Offset(mSkyTexHeapIndex + 1, mCbvSrvUavDescriptorSize);
mCommandList->SetGraphicsRootDescriptorTable(3, dynamicTexDescriptor);
DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::OpaqueDynamicReflectors]);
// Use the static "background" cube map for the other objects (including the sky)
mCommandList->SetGraphicsRootDescriptorTable(3, skyTexDescriptor);
DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::Opaque]);
mCommandList->SetPipelineState(mPSOs["sky"].Get());
DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::Sky]);
shader和上一个demo用的shader一模一样,所以不再列出代码了。
最后渲染结果如图所示,可以看到镜面的球里反射出了正在移动的骷髅头。