1.將實物照常渲染到后臺緩沖區(qū)內(nèi)(不包括鏡子)。注意,此步驟不修改模 板緩沖區(qū)。
2.清理模板緩沖區(qū),將其整體置零。
將實物都繪制到后臺緩沖區(qū)中,并將模板緩沖區(qū)清理為0 (用淺灰色來表示)。
繪制在模板緩沖區(qū)中的黑色輪廊線條反映的是:后臺緩沖區(qū)與模板緩沖區(qū)中像素之間的對照關(guān)系,而并非模板緩沖區(qū)中所繪的實際數(shù)據(jù)。
3.僅將鏡面渲染到模板緩沖區(qū)中。若要禁止其他顏色數(shù)據(jù)寫入到后臺緩沖區(qū),可用下列設(shè)置所創(chuàng) 建的混合狀態(tài):
D3D12_RENDER_TARGET_BLEND_DESC::RenderTargetWriteMask = 0;
再通過以下配置來禁止向深度緩沖區(qū)的寫操作:
D3D12_DEPTH_STENCIL_DESC::DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO;
在向模板緩沖區(qū)渲染鏡面的時候,我將模板測試設(shè)置為每次都成功(D3D12_COMPARISON_ALWAYS), 并且在通過測試時用1 ( StencilRef模板參考值)來替換(通過D3D12_STENCIL_OP_REPLACE來 設(shè)置)模板緩沖區(qū)元素。如果深度測試失敗,則應(yīng)當(dāng)采用枚舉項D3D12_STENCIL_OP_KEEP,使模板緩沖區(qū)中的對應(yīng)像素保持不變。由于僅向模板緩沖區(qū)繪制了鏡面,因此在模板緩沖區(qū)內(nèi),除了鏡面可見部分的對應(yīng)像素為1,其他像素皆為0。圖下所示的即為更新后的模板緩沖區(qū)。換言之,我們其實就是在模板緩沖區(qū)中標(biāo)記了鏡面的可見像素而已。
圖把鏡面渲染到模板緩沖區(qū)中,其實就是在模板緩沖區(qū)中標(biāo)記出鏡面可視部分的對應(yīng)像素。
模板緩沖區(qū)中實心黑色區(qū)域的模板元素取值為1。但請注意,由于被立方體擋住部分的深度測試會失敗,所以在模板緩沖區(qū)中的這一范圍內(nèi),元素的取值并不為1
(立方體與黑色鏡面重合的部分,也就是立方體位于鏡面前方的這一部分)
保證先繪制實物,后將鏡面渲染至模板緩沖區(qū)的順序是很重要的。這樣一來,深度 測試的失敗會令鏡面的像素被實物的像素所遮擋,因而也就不必再對模板緩沖區(qū)進 行二次修改了。我們并不希望把模板緩沖區(qū)中鏡面被遮擋部分的值設(shè)為1,那樣將導(dǎo)致在實物位于鏡面前方的范圍內(nèi)也能顯示出鏡面內(nèi)容。
4.現(xiàn)在我們來將實物的鏡像渲染至后臺緩沖區(qū)及模板緩沖區(qū)中。前面曾提到,只有通過模板測 試的像素才能渲染至后臺緩沖區(qū)。對此,我們便將其設(shè)置為:僅當(dāng)模板緩沖區(qū)中的值為1時, 才能通過模板測試。這可以通過令StencilRef為1,且模板運算符為D3D12_COMPARISON_ FUNC_EQUAL來實現(xiàn)。如此一來,只有模板緩沖區(qū)中元素數(shù)值為1的實物鏡像部分才能得以 渲染。由于只有鏡面可見部分所對應(yīng)的模板緩沖區(qū)中元素數(shù)值為1,所以僅有這一范圍內(nèi)的實物鏡像才能被渲染出來。
5.最后,我們像往常那樣將鏡面渲染到后臺緩沖區(qū)中。但是,為了能“透過”鏡面觀察實物的 鏡像(它實際位于鏡子的背面。雖說展現(xiàn)的是鏡面內(nèi)的鏡像,但實際上是鏡面背后的反射實物 與鏡面透明混合所得到的效果),我們就需要運用透明混合技術(shù)來渲染鏡面。若非如此,則由于 實物鏡像的深度值小于鏡面的深度值,理所當(dāng)然地會致使實物鏡像被鏡子擋住。為此,我 們只需為鏡面定義一個新的材質(zhì)配置實例:將其漫反射alpha通道分量設(shè)為0.3,使鏡子的不透 明度達(dá)到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;
假設(shè)已經(jīng)將實物鏡像的像素置于后臺緩沖區(qū)內(nèi),那么,此時我們所看到的鏡像顏色30%來自鏡子 (源像素),70%出自實物鏡像(目標(biāo)像素)。
定義鏡像的深度/模板狀態(tài)
為了實現(xiàn)上述算法,我們要用到兩個PSO對象。第一個用于在繪制鏡面時標(biāo)記模板緩沖區(qū)內(nèi)鏡面部 分的像素,第二個則用于繪制鏡面可見部分(即不被前側(cè)實物所遮擋部分)內(nèi)的實物鏡像。
//
//
// PSO for marking stencil mirrors.禁止對渲染目標(biāo)的寫操作
//
CD3DX12_BLEND_DESC mirrorBlendState(D3D12_DEFAULT);
mirrorBlendState.RenderTarget[0].RenderTargetWriteMask = 0; //禁止對渲染目標(biāo)的寫操作
D3D12_DEPTH_STENCIL_DESC mirrorDSS;
mirrorDSS.DepthEnable = true;
mirrorDSS.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO;//禁止對渲染目標(biāo)的寫操作
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;
//我們不渲染背面朝向的多邊形,因而對這些參數(shù)血置并不關(guān)心
// 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.用于渲染模板緩沖區(qū)中反射鏡像的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]);
//將模板緩沖區(qū)中可見的鏡面像素標(biāo)記為1
Mark the visible mirror pixels in the stencil buffer with the value 1
mCommandList->OMSetStencilRef(1);
mCommandList->SetPipelineState(mPSOs["markStencilMirrors"].Get());
DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::Mirrors]);
//只繪制鏡子范圍內(nèi)的鏡像(即僅繪制模板緩沖區(qū)中標(biāo)記為1的像素)
//注意,我們必須使用兩個單獨的渲染過程常量緩沖區(qū)(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]);
//恢復(fù)主渲染過程常量數(shù)據(jù)以及模板參考值
// 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]);
關(guān)于以上代碼還有一點需要注意,即在繪制RenderLayer: :Reflected層的時候如何來修改其渲染過程常量緩沖區(qū)。這是因為在繪制物體鏡像的同時,還涉及場景中光照的鏡像(即,物體的鏡像也 要有與之對應(yīng)的光照)。光源本存于渲染過程常量緩沖區(qū)中,因此我們可以再額外創(chuàng)建一個渲染過程常量 緩沖區(qū),用以存儲場景中光照的鏡像。該常量緩沖區(qū)的設(shè)置方法如下:
PassConstants mMainPassCB;
PassConstants mReflectedPassCB;
void StencilApp::UpdateReflectedPassCB(const GameTimer& gt)
{
mReflectedPassCB = mMainPassCB;
XMVECTOR mirrorPlane = XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f); // xy plane
XMMATRIX 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);
}
// 將光照鏡像的渲染過程常量數(shù)據(jù)存于渲染過程常量緩沖區(qū)中索引1的位置
auto currPassCB = mCurrFrameResource->PassCB.get();
currPassCB->CopyData(1, mReflectedPassCB);
}
繞序與鏡像
當(dāng)一個三角形被反射到某個平面上時(也就是此三角形在這一平面上的鏡像),其繞序(winding order ) 并不會發(fā)生改變,正因如此,其平面法線的方向同樣保持不變。所以,實際物體的外向法線在鏡像中則變?yōu)?了內(nèi)向法線。此時,為了糾正這一點,我們會告知Direct3D將逆時針繞序的三角形看作是正面 朝向,而將順時針繞序的三角形看作背面朝向。這實際上 是對法線的方向也進行了 “反射”,以此使鏡像成為外向朝向。我們可以通過設(shè)置下列PSO光柵化屬性來改 變繞序的約定:文章來源:http://www.zghlxwxcb.cn/news/detail-420766.html
drawReflectionsPsoDesc.RasterizerState.FrontCounterClockwise = true;
文章來源地址http://www.zghlxwxcb.cn/news/detail-420766.html
到了這里,關(guān)于Direct3D 12——模板——平面鏡效果的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!