> 文章列表 > Direct3D 12——模板——平面镜效果

Direct3D 12——模板——平面镜效果

Direct3D 12——模板——平面镜效果

1.将实物照常渲染到后台缓冲区内(不包括镜子)。注意,此步骤不修改模 板缓冲区。

2.清理模板缓冲区,将其整体置零。
Direct3D 12——模板——平面镜效果
将实物都绘制到后台缓冲区中,并将模板缓冲区清理为0 (用浅灰色来表示)。
绘制在模板缓冲区中的黑色轮廊线条反映的是:后台缓冲区与模板缓冲区中像素之间的对照关系,而并非模板缓冲区中所绘的实际数据。

3.仅将镜面渲染到模板缓冲区中。若要禁止其他颜色数据写入到后台缓冲区,可用下列设置所创 建的混合状态:
D3D12_RENDER_TARGET_BLEND_DESC::RenderTargetWriteMask = 0;
再通过以下配置来禁止向深度缓冲区的写操作:
D3D12_DEPTH_STENCIL_DESC::DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO;

在向模板缓冲区渲染镜面的时候,我将模板测试设置为每次都成功(D3D12_COMPARISON_ALWAYS), 并且在通过测试时用1 ( StencilRef模板参考值)来替换(通过D3D12_STENCIL_OP_REPLACE来 设置)模板缓冲区元素。如果深度测试失败,则应当采用枚举项D3D12_STENCIL_OP_KEEP,使模板缓冲区中的对应像素保持不变。由于仅向模板缓冲区绘制了镜面,因此在模板缓冲区内,除了镜面可见部分的对应像素为1,其他像素皆为0。图下所示的即为更新后的模板缓冲区。换言之,我们其实就是在模板缓冲区中标记了镜面的可见像素而已。
Direct3D 12——模板——平面镜效果
图把镜面渲染到模板缓冲区中,其实就是在模板缓冲区中标记出镜面可视部分的对应像素。
模板缓冲区中实心黑色区域的模板元素取值为1。但请注意,由于被立方体挡住部分的深度测试会失败,所以在模板缓冲区中的这一范围内,元素的取值并不为1
(立方体与黑色镜面重合的部分,也就是立方体位于镜面前方的这一部分)

保证先绘制实物,后将镜面渲染至模板缓冲区的顺序是很重要的。这样一来,深度 测试的失败会令镜面的像素被实物的像素所遮挡,因而也就不必再对模板缓冲区进 行二次修改了。我们并不希望把模板缓冲区中镜面被遮挡部分的值设为1,那样将导致在实物位于镜面前方的范围内也能显示出镜面内容。

4.现在我们来将实物的镜像渲染至后台缓冲区及模板缓冲区中。前面曾提到,只有通过模板测 试的像素才能渲染至后台缓冲区。对此,我们便将其设置为:仅当模板缓冲区中的值为1时, 才能通过模板测试。这可以通过令StencilRef为1,且模板运算符为D3D12_COMPARISON_ FUNC_EQUAL来实现。如此一来,只有模板缓冲区中元素数值为1的实物镜像部分才能得以 渲染。由于只有镜面可见部分所对应的模板缓冲区中元素数值为1,所以仅有这一范围内的实物镜像才能被渲染出来。

5.最后,我们像往常那样将镜面渲染到后台缓冲区中。但是,为了能“透过”镜面观察实物的 镜像(它实际位于镜子的背面。虽说展现的是镜面内的镜像,但实际上是镜面背后的反射实物 与镜面透明混合所得到的效果),我们就需要运用透明混合技术来渲染镜面。若非如此,则由于 实物镜像的深度值小于镜面的深度值,理所当然地会致使实物镜像被镜子挡住。为此,我 们只需为镜面定义一个新的材质配置实例:将其漫反射alpha通道分量设为0.3,使镜子的不透 明度达到30%,

	auto icemirror = std::make_unique<Material>();icemirror->Name = "icemirror";icemirror->MatCBIndex = 2;icemirror->DiffuseSrvHeapIndex = 2;icemirror->DiffuseAlbedo = XMFLOAT4(1.0f, 1.0f, 1.0f, 0.3f);icemirror->FresnelR0 = XMFLOAT3(0.1f, 0.1f, 0.1f);icemirror->Roughness = 0.5f;

假设已经将实物镜像的像素置于后台缓冲区内,那么,此时我们所看到的镜像颜色30%来自镜子 (源像素),70%出自实物镜像(目标像素)。

定义镜像的深度/模板状态

为了实现上述算法,我们要用到两个PSO对象。第一个用于在绘制镜面时标记模板缓冲区内镜面部 分的像素,第二个则用于绘制镜面可见部分(即不被前侧实物所遮挡部分)内的实物镜像。

	////// PSO for marking stencil mirrors.禁止对渲染目标的写操作//CD3DX12_BLEND_DESC mirrorBlendState(D3D12_DEFAULT);mirrorBlendState.RenderTarget[0].RenderTargetWriteMask = 0; //禁止对渲染目标的写操作D3D12_DEPTH_STENCIL_DESC mirrorDSS;mirrorDSS.DepthEnable = true;mirrorDSS.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO;//禁止对渲染目标的写操作mirrorDSS.DepthFunc = D3D12_COMPARISON_FUNC_LESS;mirrorDSS.StencilEnable = true;mirrorDSS.StencilReadMask = 0xff;mirrorDSS.StencilWriteMask = 0xff;mirrorDSS.FrontFace.StencilFailOp = D3D12_STENCIL_OP_KEEP;mirrorDSS.FrontFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP;mirrorDSS.FrontFace.StencilPassOp = D3D12_STENCIL_OP_REPLACE;mirrorDSS.FrontFace.StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS;//我们不渲染背面朝向的多边形,因而对这些参数血置并不关心// We are not rendering backfacing polygons, so these settings do not matter.]mirrorDSS.BackFace.StencilFailOp = D3D12_STENCIL_OP_KEEP;mirrorDSS.BackFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP;mirrorDSS.BackFace.StencilPassOp = D3D12_STENCIL_OP_REPLACE;mirrorDSS.BackFace.StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS;D3D12_GRAPHICS_PIPELINE_STATE_DESC markMirrorsPsoDesc = opaquePsoDesc;markMirrorsPsoDesc.BlendState = mirrorBlendState;markMirrorsPsoDesc.DepthStencilState = mirrorDSS;ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&markMirrorsPsoDesc, IID_PPV_ARGS(&mPSOs["markStencilMirrors"])));//// PSO for stencil reflections.用于渲染模板缓冲区中反射镜像的PSO//D3D12_DEPTH_STENCIL_DESC reflectionsDSS;reflectionsDSS.DepthEnable = true;reflectionsDSS.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL;reflectionsDSS.DepthFunc = D3D12_COMPARISON_FUNC_LESS;reflectionsDSS.StencilEnable = true;reflectionsDSS.StencilReadMask = 0xff;reflectionsDSS.StencilWriteMask = 0xff;reflectionsDSS.FrontFace.StencilFailOp = D3D12_STENCIL_OP_KEEP;reflectionsDSS.FrontFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP;reflectionsDSS.FrontFace.StencilPassOp = D3D12_STENCIL_OP_KEEP;reflectionsDSS.FrontFace.StencilFunc = D3D12_COMPARISON_FUNC_EQUAL;// We are not rendering backfacing polygons, so these settings do not matter.reflectionsDSS.BackFace.StencilFailOp = D3D12_STENCIL_OP_KEEP;reflectionsDSS.BackFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP;reflectionsDSS.BackFace.StencilPassOp = D3D12_STENCIL_OP_KEEP;reflectionsDSS.BackFace.StencilFunc = D3D12_COMPARISON_FUNC_EQUAL;D3D12_GRAPHICS_PIPELINE_STATE_DESC drawReflectionsPsoDesc = opaquePsoDesc;drawReflectionsPsoDesc.DepthStencilState = reflectionsDSS;drawReflectionsPsoDesc.RasterizerState.CullMode = D3D12_CULL_MODE_BACK;drawReflectionsPsoDesc.RasterizerState.FrontCounterClockwise = true;ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&drawReflectionsPsoDesc, IID_PPV_ARGS(&mPSOs["drawStencilReflections"])));

绘制场景

	//绘制不透明的物体// Draw opaque items--floors, walls, skull.auto passCB = mCurrFrameResource->PassCB->Resource();mCommandList->SetGraphicsRootConstantBufferView(2, passCB->GetGPUVirtualAddress());DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::Opaque]);//将模板缓冲区中可见的镜面像素标记为1 Mark the visible mirror pixels in the stencil buffer with the value 1mCommandList->OMSetStencilRef(1);mCommandList->SetPipelineState(mPSOs["markStencilMirrors"].Get());DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::Mirrors]);//只绘制镜子范围内的镜像(即仅绘制模板缓冲区中标记为1的像素)//注意,我们必须使用两个单独的渲染过程常量缓冲区(per-pass constant buffer)来完成此工作,//一个存储物体镜像,另一个保存光照镜像// Draw the reflection into the mirror only (only for pixels where the stencil buffer is 1).// Note that we must supply a different per-pass constant buffer--one with the lights reflected.mCommandList->SetGraphicsRootConstantBufferView(2, passCB->GetGPUVirtualAddress() + 1 * passCBByteSize);mCommandList->SetPipelineState(mPSOs["drawStencilReflections"].Get());DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::Reflected]);//恢复主渲染过程常量数据以及模板参考值 // Restore main pass constants and stencil ref.mCommandList->SetGraphicsRootConstantBufferView(2, passCB->GetGPUVirtualAddress());mCommandList->OMSetStencilRef(0);//绘制透明的镜面,使镜像可以与之混合// Draw mirror with transparency so reflection blends through.mCommandList->SetPipelineState(mPSOs["transparent"].Get());DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::Transparent]);

关于以上代码还有一点需要注意,即在绘制RenderLayer: :Reflected层的时候如何来修改其渲染过程常量缓冲区。这是因为在绘制物体镜像的同时,还涉及场景中光照的镜像(即,物体的镜像也 要有与之对应的光照)。光源本存于渲染过程常量缓冲区中,因此我们可以再额外创建一个渲染过程常量 缓冲区,用以存储场景中光照的镜像。该常量缓冲区的设置方法如下:

  PassConstants mMainPassCB;PassConstants mReflectedPassCB;
void StencilApp::UpdateReflectedPassCB(const GameTimer& gt)
{mReflectedPassCB = mMainPassCB;XMVECTOR mirrorPlane = XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f); // xy planeXMMATRIX R = XMMatrixReflect(mirrorPlane);// 光照镜像for(int i = 0; i < 3; ++i){XMVECTOR lightDir = XMLoadFloat3(&mMainPassCB.Lights[i].Direction);XMVECTOR reflectedLightDir = XMVector3TransformNormal(lightDir, R);XMStoreFloat3(&mReflectedPassCB.Lights[i].Direction, reflectedLightDir);}// 将光照镜像的渲染过程常量数据存于渲染过程常量缓冲区中索引1的位置auto currPassCB = mCurrFrameResource->PassCB.get();currPassCB->CopyData(1, mReflectedPassCB);
}

绕序与镜像

当一个三角形被反射到某个平面上时(也就是此三角形在这一平面上的镜像),其绕序(winding order ) 并不会发生改变,正因如此,其平面法线的方向同样保持不变。所以,实际物体的外向法线在镜像中则变为 了内向法线。此时,为了纠正这一点,我们会告知Direct3D将逆时针绕序的三角形看作是正面 朝向,而将顺时针绕序的三角形看作背面朝向。这实际上 是对法线的方向也进行了 “反射”,以此使镜像成为外向朝向。我们可以通过设置下列PSO光栅化属性来改 变绕序的约定:

drawReflectionsPsoDesc.RasterizerState.FrontCounterClockwise = true;

Direct3D 12——模板——平面镜效果

多边形的法线不会随着反射操作而调转过来,这使得镜像的法线都变为内向朝向