https://github.com/jiabaodan/Direct12BookReadingNotes
模板缓冲(stencil buffer)状态是通过配置一个D3D12_DEPTH_STENCIL_DESC实例,并且赋值到PSO中的D3D12_GRAPHICS_PIPELINE_STATE_DESC::DepthStencilState
回顾深度/模板缓冲是一个纹理,所以它必须使用特定的格式,它们可以使用的格式如下:
在我们的D3DAPP框架中,当我们创建了深度缓冲后,我们指定:
DXGI_FORMAT mDepthStencilFormat = DXGI_FORMAT_D24_UNORM_S8_UINT;
depthStencilDesc.Format = mDepthStencilFormat;
并且模板缓冲在每帧开始的时候必须要重置,这个由下面的函数来实现:
void ID3D12GraphicsCommandList::ClearDepthStencilView(
D3D12_CPU_DESCRIPTOR_HANDLE DepthStencilView,
D3D12_CLEAR_FLAGS ClearFlags,
FLOAT Depth,
UINT8 Stencil,
UINT NumRects,
const D3D12_RECT *pRects);
我们已经在我们的Demo中每帧都调用一次:
mCommandList->ClearDepthStencilView(DepthStencilView(),
D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL,
1.0f, 0, 0, nullptr);
作为预先的状态,我们可以使用模板缓冲来阻止渲染到后置缓冲的特定部分:
if( StencilRef & StencilReadMask CF Value & StencilReadMask )
accept pixel
else
reject pixel
模板测试是在像素光栅化后进行的(在输出合并阶段),如果模板测试是开启的,那么会进行2个操作:
StencilReadMask对于LHS和RHS是一样的。模板测试通过程序选择的比较函数来比较LHS和RHS,然后返回True或者False。如果返回为false,就不会将像素写入后置缓冲中。(也不会写入深度缓冲中)。
比较函数在D3D12_COMPARISON_FUNC枚举类型中:
typedef enum D3D12_COMPARISON_FUNC
{
D3D12_COMPARISON_NEVER = 1,
D3D12_COMPARISON_LESS = 2,
D3D12_COMPARISON_EQUAL = 3,
D3D12_COMPARISON_LESS_EQUAL = 4,
D3D12_COMPARISON_GREATER = 5,
D3D12_COMPARISON_NOT_EQUAL = 6,
D3D12_COMPARISON_GREATER_EQUAL = 7,
D3D12_COMPARISON_ALWAYS = 8,
} D3D12_COMPARISON_FUNC;
深度/模板状态通过赋值一个D3D12_DEPTH_STENCIL_DESC结构对象来定义:
typedef struct D3D12_DEPTH_STENCIL_DESC {
BOOL DepthEnable; // Default True
// Default: D3D11_DEPTH_WRITE_MASK_ALL
D3D12_DEPTH_WRITE_MASK DepthWriteMask;
// Default: D3D11_COMPARISON_LESS
D3D12_COMPARISON_FUNC DepthFunc;
BOOL StencilEnable; // Default: False
UINT8 StencilReadMask; // Default: 0xff
UINT8 StencilWriteMask; // Default: 0xff
D3D12_DEPTH_STENCILOP_DESC FrontFace;
D3D12_DEPTH_STENCILOP_DESC BackFace;
} D3D12_DEPTH_STENCIL_DESC;
if( StencilRef & StencilReadMask CF Value & StencilReadMask )
accept pixel
else
reject pixel
默认值不遮挡任何位:
#define D3D12_DEFAULT_STENCIL_READ_MASK ( 0xff )
#define D3D12_DEFAULT_STENCIL_WRITE_MASK ( 0xff )
typedef struct D3D12_DEPTH_STENCILOP_DESC {
D3D12_STENCIL_OP StencilFailOp; // Default: D3D12_STENCIL_OP_KEEP
D3D12_STENCIL_OP StencilDepthFailOp; // Default: D3D12_STENCIL_OP_KEEP
D3D12_STENCIL_OP StencilPassOp; // Default: D3D12_STENCIL_OP_KEEP
D3D12_COMPARISON_FUNC StencilFunc; // Default: D3D12_COMPARISON_ALWAYS
} D3D12_DEPTH_STENCILOP_DESC;
typedef
enum D3D12_STENCIL_OP
{
D3D12_STENCIL_OP_KEEP = 1,
D3D12_STENCIL_OP_ZERO = 2,
D3D12_STENCIL_OP_REPLACE = 3,
D3D12_STENCIL_OP_INCR_SAT = 4,
D3D12_STENCIL_OP_DECR_SAT = 5,
D3D12_STENCIL_OP_INVERT = 6,
D3D12_STENCIL_OP_INCR = 7,
D3D12_STENCIL_OP_DECR = 8
} D3D12_STENCIL_OP;
在模板缓冲中,前向面和后向面的设置是可以不同的;如果我们设置了背面裁切,那后向面的设置就是无效的。
当我们填充好D3D12_DEPTH_STENCIL_DESC应用来描述我们的深度/模板状态后,我们可以把它指定到PSO中的D3D12_GRAPHICS_PIPELINE_STATE_DESC::DepthStencilState。
模板引用值可以通过方法ID3D12GraphicsCommandList::OMSetStencilRef来设置(一个单独的无符号整形):
mCommandList->OMSetStencilRef(1);
当我们绘制反射的时候,我们将灯光也反射过去,否则光照会不准确。
如下图所示,被反射的物体就是场景中的另一个物体,如果它没有被任何遮挡,那么可以直接看到它;我们通过模板测试让它只显示在镜子上。主要步骤如下:
D3D12_RENDER_TARGET_BLEND_DESC::RenderTargetWriteMask = 0;
然后禁用写到深度缓冲:
D3D12_DEPTH_STENCIL_DESC::DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ZERO;
当我们渲染镜子到模板缓冲后,我们设置模板测试为总是成功(D3D12_COMPARISON_ALWAYS)然后指定模板缓冲如果测试通过,替换(D3D12_STENCIL_OP_REPLACE)为值1(StencilRef)。如果深度测试失败,我们指定D3D12_STENCIL_OP_KEEP,这样模板缓冲在深度测试失败的时候就不会被改变。这个时候模板缓冲中除了可见的镜子部分以外,其他部分都是不可见的(值为0),如下图:
4. 现在我们渲染反射的骷髅头到后置缓冲和模板缓冲中,但是当且仅当模板测试通过后才渲染到后置缓冲中。我们使用只有模板测试值为1时,模板测试才成功,可以设置StencilRef为1,然后模板测试运算为D3D12_COMPARISON_EQUAL。
5. 最后我们正常渲染镜子到后置缓冲中,为了不遮挡到反射的骷髅,我们需要为镜子添加透明混合。为了实现这个方案,我们为镜子添加一个新的材质,将透明度设置为30%,然后用上章中提到的透明混合状态类渲染:
auto icemirror = std::make_unique();
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;
使用下面的混合公式:
为了实现之前描述的算法,我们需要2个PSO,一个用来绘制镜子到模板缓冲中;另一个用来只在标记了镜面的地方绘制反射的骷髅:
//
// PSO for marking stencil mirrors.
//
// Turn off render target writes.
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.
//
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]);
// 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]);
// 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一层的时候,我们如何改变per-pass的常量缓冲,因为灯光也是需要反射过去的。我们另外创建了一个per-pass的常量缓冲,来保存反射的灯光,这个常量缓冲设置代码如下:
PassConstants StencilApp::mMainPassCB;
PassConstants StencilApp::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);
// Reflect the lighting.
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);
}
// Reflected pass stored in index 1
auto currPassCB = mCurrFrameResource->PassCB.get();
currPassCB->CopyData(1, mReflectedPassCB);
}
当三角形反射过平面后,它的缠绕顺序并没有反向,所以它的法向量并没有反向。导致向外的面变成了向里的面。为了修正这个问题,我们让D3D使用逆时针防线来缠绕表示三角形的正面,顺时针表示背面。可以通过对PSO属性设置来实现:
drawReflectionsPsoDesc.RasterizerState.FrontCounterClockwise = true;
为了实现平面阴影,我们需要先找到在阴影在几何算法上是如何投射到平面上的,这个可以通过3D数学简单的得到。然后我们渲染描述阴影的三角形,使用黑色,50%半透明的材质。这种渲染阴影的方法可以介绍一种技术叫做“双混合”(“double blending”),我们利用模板缓冲来防止双混合的出现。
如上图所示,给出一个具有方向L的平行光,光线经过点P(r(t) = p + tL),射线r(t)和阴影平面(n, d)的交点是s。所以这组通过射线经过物体每个顶点射向阴影平面的交点就可以组成阴影。对于点P,阴影投射公式:(更多射线和平面相交的内容可以查看本书的附录C)
上述公式可以写成下面矩阵:
我们管这个4x4矩阵叫方向阴影矩阵并通过Sdir来表示。为了证明他们是相等的,我们可以执行乘法运算:
我们使用阴影矩阵的时候会合并到我们的世界矩阵中。执行完世界转换后,几何体还没有变成阴影,因为还没有执行透视除法。
(还没有完结,公式看得头大,以后再写 ^ @ ^)
使用其次坐标系,可以创建一个通用的可以应用于点光源和平行光的阴影矩阵。
那么阴影矩阵就可以写为:
DirectX数学库中提供了下面的函数来通过给出的平面和一个描述光方向向量(w = 0平行光;w = 1点光源)来创建阴影矩阵:
inline XMMATRIX XM_CALLCONV XMMatrixShadow(
FXMVECTOR ShadowPlane,
FXMVECTOR LightPosition);
如果要进一步阅读平面阴影,可以查看[Blinn96] 和 [Möller02]。
当我们把几何体压平到平面上的时候,可以会有多个三角形重叠到一起。这时候我们使用透明混合渲染阴影,就会出现有些地方被透明计算了多次,导致出现渲染错误。
我们可以通过模板缓冲来解决这个问题:
定义阴影材质为50%透明的黑色:
auto shadowMat = std::make_unique();
shadowMat->Name = "shadowMat";
shadowMat->MatCBIndex = 4;
shadowMat->DiffuseSrvHeapIndex = 3;
shadowMat->DiffuseAlbedo = XMFLOAT4(0.0f, 0.0f, 0.0f, 0.5f);
shadowMat->FresnelR0 = XMFLOAT3(0.001f, 0.001f, 0.001f);
shadowMat->Roughness = 0.0f;
为了防止双混合,我们新添加一个PSO:
// We are going to draw shadows with transparency, so base it off
// the transparency description.
D3D12_DEPTH_STENCIL_DESC shadowDSS;
shadowDSS.DepthEnable = true;
shadowDSS.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL;
shadowDSS.DepthFunc = D3D12_COMPARISON_FUNC_LESS;
shadowDSS.StencilEnable = true;
shadowDSS.StencilReadMask = 0xff;
shadowDSS.StencilWriteMask = 0xff;
shadowDSS.FrontFace.StencilFailOp = D3D12_STENCIL_OP_KEEP;
shadowDSS.FrontFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP;
shadowDSS.FrontFace.StencilPassOp = D3D12_STENCIL_OP_INCR;
shadowDSS.FrontFace.StencilFunc = D3D12_COMPARISON_FUNC_EQUAL;
// We are not rendering backfacing polygons, so these settings do not
// matter.
shadowDSS.BackFace.StencilFailOp = D3D12_STENCIL_OP_KEEP;
shadowDSS.BackFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP;
shadowDSS.BackFace.StencilPassOp = D3D12_STENCIL_OP_INCR;
shadowDSS.BackFace.StencilFunc = D3D12_COMPARISON_FUNC_EQUAL;
D3D12_GRAPHICS_PIPELINE_STATE_DESC shadowPsoDesc = transparentPsoDesc;
shadowPsoDesc.DepthStencilState = shadowDSS;
ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(
&shadowPsoDesc,
IID_PPV_ARGS(&mPSOs["shadow"])));
然后我们使用阴影PSO并设置StencilRef为0来绘制骷髅阴影:
// Draw shadows
mCommandList->OMSetStencilRef(0);
mCommandList->SetPipelineState(mPSOs["shadow"].Get());
DrawRenderItems(mCommandList.Get(),
mRitemLayer[(int)RenderLayer::Shadow]);
骷髅阴影矩阵计算如下:
// Update shadow world matrix.
XMVECTOR shadowPlane = XMVectorSet(0.0f, 1.0f, 0.0f, 0.0f); // xz plane
XMVECTOR toMainLight = -XMLoadFloat3(&mMainPassCB.Lights[0].Direction);
XMMATRIX S = XMMatrixShadow(shadowPlane, toMainLight);
XMMATRIX shadowOffsetY = XMMatrixTranslation(0.0f, 0.001f, 0.0f);
XMStoreFloat4x4(&mShadowedSkullRitem->World, skullWorld * S * shadowOffsetY);
我们阴影网格在Y轴上网上偏移了一点点,这是为了防止当骷髅和底面有交叉的时候出现深度冲突(z-fighting)。如果网格和地面交叉了,由于深度缓冲精度的限制,我们会看到一些闪烁的像素点。
if( StencilRef & StencilReadMask ST Value & StencilReadMask )
accept pixel
else
reject pixel
3. 修改本章Demo,达到下面的效果:
关闭drawStencilReflections PSO模板测试即可:
//reflectionsDSS.StencilEnable = true;
reflectionsDSS.StencilEnable = false;
4. 修改本章Demo,达到下面的效果:
修改shadow PSO,关闭模板测试即可
//shadowDSS.StencilEnable = true;
shadowDSS.StencilEnable = false;
5. 修改本章Demo,根据下面的方式。首先使用下面的设置绘制墙面
depthStencilDesc.DepthEnable = false;
depthStencilDesc.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL;
depthStencilDesc.DepthFunc = D3D12_COMPARISON_LESS;
然后使用下面的设置绘制骷髅:
depthStencilDesc.DepthEnable = true;
depthStencilDesc.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL;
depthStencilDesc.DepthFunc = D3D12_COMPARISON_LESS;
可以遮挡住骷髅吗?如果对墙面再次替换到下面的设置呢?
depthStencilDesc.DepthEnable = true;
depthStencilDesc.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL;
depthStencilDesc.DepthFunc = D3D12_COMPARISON_LESS;
修改完前两个设置后:
骷髅没有被遮挡,因为墙面的Z值没有写入Z缓冲;对墙面替换设置后就可以准确显示,因为Z值准确写入了Z缓冲中。
6. 如果不反向Demo中三角形缠绕顺序,被反射的骷髅会准确显示吗?
无法准确显示,会只显示背面,通过修改drawStencilReflections PSO设置:
//drawReflectionsPsoDesc.RasterizerState.FrontCounterClockwise = true;
drawReflectionsPsoDesc.RasterizerState.FrontCounterClockwise = false;
8. 深度复杂度是指多个像素片元通过深度测试在同一个像素上竞争写入后置缓冲。比如一个像素可能会被另一个距离相机更近的像素覆盖。下图中P的深度复杂度是3:
显卡可能会每帧在同一个点上绘制多次,这种覆盖会影响到性能,因为它在重复绘制看不到的点。所以测量深度复杂度对优化分析就很有用。
我们可以通过下面的方法测量深度复杂度:渲染场景,并且使用模板测试作为计数器;刚开始将模板缓冲初始化为0,然后每次像素绘制,我们增加对应的模板缓冲中的值(D3D12_STENCIL_OP_INCR),模板测试公式使用D3D12_COMPARISON_ALWAYS。所以如果绘制完成后,深度缓冲中的值为5,则这个像素点被绘制了5次。值得注意的是,在计算深度复杂度的时候,你只需要绘制到模板缓冲中。
为了能够图形化的显示深度复杂度,可以根据下面的步骤:
1、对每一个深度复杂度k关联一个颜色Ck,比如蓝色是复杂度1,绿色是复杂度2,红色是复杂度3等。(对于一个非常复杂的场景,可以对多个相连的复杂度关联同一个颜色);
2、设置模板缓冲比较公式D3D12_STENCIL_OP_KEEP,表示我们不再修改它了。
3、对每一个深度复杂度等级k:
a、设置模板比较函数为D3D12_COMPARISON_EQUAL并且设置模板引用值为k;
b、绘制Ck的方块覆盖到整个投射窗口。
根据上面的步骤,我们为每一个像素基于深度复杂度绘制了颜色,我们就可以很简单的分析深度复杂度。作为这个的练习,为Demo Blending绘制深度复杂度。下面是一个屏幕截图的例子:
深度测试时在输出合并阶段执行的(像素着色器之后)。所以即使像素后续会被放弃,但是还是会进行很耗时的像素着色器计算。但是现代的显卡提供了一个叫早期Z测试(early z-test)的技术,可以让Z测试在像素着色器之前进行。为了能够得到这个技术带来的重要的好处,你需要在绘制没有混合的物体的时候,进行从前往后的顺序进行绘制,这样最近的物体会先绘制,然后被遮挡的物体会被这个early z-test技术剔除,它会对具有高深度复杂度的场景带来大量的性能提升。对于early z-test技术我们无法使用D3D API进行控制,它完全是由显卡驱动判定控制的。比如如果你的像素着色器中修改了像素的Z值,那么early z-test将无法进行。
我们刚才提到在像素着色器中修改Z值,也就是说像素着色器不仅仅只能输出颜色,还可以输出整个结构:
struct PixelOut
{
float4 color : SV_Target;
float depth : SV_Depth;
};
PixelOut PS(VertexOut pin)
{
PixelOut pout;
// … usual pixel work
pout.Color = float4(litColor, alpha);
// set pixel depth in normalized [0, 1] range
pout.depth = pin.PosH.z - 0.05f;
return pout;
}
SV_Position的Z元素就是没有修改的Z值,SV_Depth代表修改后输出的Z值。
这道题暂时没有写,因为觉得第九题的方案更好(主要不知道如何在Shader中访问模板缓冲的数据 ^ @ ^)
9. 另一个实现图形化深度复杂度的方法是使用叠加混合。首先将后置缓冲初始化为黑色,然后将混合因子都设置为D3D12_BLEND_ONE,然后混合公式设置为D3D12_BLEND_OP_ADD,然后我们对物体使用(0.05, 0.05, 0.05)来绘制。最终绘制出的结果就是越亮的地方,深度复杂度越高。使用上一章的Blending Demo实现它:
根据提示修改,代码在https://github.com/jiabaodan/Direct12BookReadingNotes的Chapter11_Exercises_DepthComBlending工程
10. 如何计算出通过深度测试的像素的个数?如何计算没有通过深度测试像素的个数?
使用模板缓冲???
11. 修改本章Demo,增加反射地面:
添加一个反射的地面的RenderItem即可:
// 反射的地面
auto reflectedFloorRitem = std::make_unique();
*reflectedFloorRitem = *floorRitem;
reflectedFloorRitem->ObjCBIndex = 6;
XMVECTOR mirrorPlane = XMVectorSet(0.0f, 0.0f, 1.0f, 0.0f); // xy plane
XMMATRIX R = XMMatrixReflect(mirrorPlane);
XMStoreFloat4x4(&reflectedFloorRitem->World, XMMatrixIdentity() * R);
mRitemLayer[(int)RenderLayer::Reflected].push_back(reflectedFloorRitem.get());
12. 修改本章Demo,不要在Y方向偏移骷髅的阴影,观察深度冲突(z-fighting):
修改下面一行代码即可:
XMStoreFloat4x4(&mShadowedSkullRitem->World, skullWorld * S/* * shadowOffsetY*/);