十二、D3D12学习笔记——模板缓冲

一、什么叫模板

模板就好像一张面具,戴在脸上,你可以说想让别人看你面具,或者说看不被面具修饰的部分,总而言之就是将一个可观察的面加以划分,分为哪些可见,哪些不可见,用D3D12的原话表述:使用模板来组织特定的像素片段渲染到后台缓冲。

模板缓冲是一个大小与RTV,和深度缓冲一致的资源,因此可以将像素一一对应起来。我们不妨这样想:RTV相当于我们的脸,而模板缓冲相当于一张面膜,面膜完美的覆盖了表面的肌肤,但是还是留有呼吸的口子。如果此时,你打翻了一盒墨水,那么不被面膜覆盖的地方将立马变色(因为面膜覆盖的表面深度小于脸部,所以模板与深度缓冲是配合使用的,所以我们一般也称之为DSV),如此一来,你可以说面膜阻止了墨水对你脸部的染色,或者说你允许墨水滋润你部分肌肤。不同角度的说法,但是总之,他就是只对一个主体的部门进行阻碍或者通行。

二、模板的使用

1.每帧重置

因为我们在初始化阶段就创建了DSV了,并且在每帧的开始重置DSV:

mCommandList->ClearDepthStencilView(DepthStencilView(),//DSV 
D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, //与深度一起的清理重置
1.0f,//depth
0,//模板初始值
0,//Rect区域数目
nullptr);//区域

2.PSO配置

然后,我们就要在PSO的模板属性中进行配置,以让流水线按照我们设置的状态渲染场景Item,这都是硬件自己做的,我们要做的事就是理清绘制逻辑,填写参数,如下:

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"])));

三、镜面效果

绘制逻辑:

1).先确定哪个几何体模型需要镜像,然后先绘制本体;

2).用镜子范围几何体作为RenderItem,禁用RTV(不真实渲染镜子)和深度缓冲的写入(为以后绘制在镜子后边的物体做准备),允许深度测试(剔除挡在镜子前边的几何体),仅修改模板缓冲,设置此时进行模板测试为从初始值0替换为标记值(mCommandList->OMSetStencilRef(1)),对于通过模板测试的部分设置为系统设置值进行标记(此时原始全为0的模板缓冲就标记了部分系统设置值,给出了一个我们可见的镜子范围);

 3).将需要镜像的几何体使用坐标变换到镜子内侧(镜像),进行绘制,由于我们之前禁用了深度写入,所以这个地方不会被视为被镜子挡住的,并设置此时的模板测试为等于标记值1,那么也就是只有镜子范围内可见区域的像素才会被后续像素处理,否则抛弃对该像素的处理。所谓的像素处理就是进入像素着色器生成镜像;

4).最后我们绘制镜子,将镜子作为透明体来绘制,则可以把镜子后边的dst混合到镜子表面上,并且深度缓冲开启情况下自动丢弃了原始对镜像几何体的绘制。

代码解析

//绘制不透明物,待镜像的几何体

ThrowIfFailed(mCommandList->Reset(cmdListAlloc.Get(), mPSOs["opaque"].Get()));

.....

//清理DSV
mCommandList->ClearDepthStencilView(DepthStencilView(),//DSV 
D3D12_CLEAR_FLAG_DEPTH | D3D12_CLEAR_FLAG_STENCIL, 1.0f,0,0,nullptr);

......

DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::Opaque]);

//标记模板

mCommandList->SetPipelineState(mPSOs["markStencilMirrors"].Get());
DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::Mirrors]);

对于mPSOs["markStencilMirrors"]设置如下:

//获得我们可见的镜子像素范围
    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;
    //使用设置的模板参考值进行替换,相当于从整个DSV初值中用0,1标记镜子范围
    mirrorDSS.FrontFace.StencilPassOp = D3D12_STENCIL_OP_REPLACE;
    //每次都成功,保证完全记录镜子的模板像素范围,让所有通过测试的都变为设置模板值
    mirrorDSS.FrontFace.StencilFunc = D3D12_COMPARISON_FUNC_ALWAYS;
    // 背面,一般剔除背面,所以不关心
    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"])));

//绘制镜子后边的镜像

  

mCommandList->SetGraphicsRootConstantBufferView(2, passCB->GetGPUVirtualAddress() + 1 * passCBByteSize);
mCommandList->SetPipelineState(mPSOs["drawStencilReflections"].Get());
DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::Reflected]);

对于mPSOs["drawStencilReflections"]设置如下:

//渲染实物的镜像
    D3D12_DEPTH_STENCIL_DESC reflectionsDSS;
    reflectionsDSS.DepthEnable = true;
    //开启深度写入,不禁用写入RTV,渲染像素
    reflectionsDSS.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL;
    reflectionsDSS.DepthFunc = D3D12_COMPARISON_FUNC_LESS;
    reflectionsDSS.StencilEnable = true;
    reflectionsDSS.StencilReadMask = 0xff;
    reflectionsDSS.StencilWriteMask = 0xff;

    //为0失败的继续保持
    reflectionsDSS.FrontFace.StencilFailOp = D3D12_STENCIL_OP_KEEP;
    reflectionsDSS.FrontFace.StencilDepthFailOp = D3D12_STENCIL_OP_KEEP;
    reflectionsDSS.FrontFace.StencilPassOp = D3D12_STENCIL_OP_KEEP;
    //设置为相等,此时系统设置就是1,这就与模板测试通过替换标记的1对上了,因此只会在镜子的可见范围上进行绘制
    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"])));

要注意一下FrontCounterClockwise ,因为顶点按照索引组织的顺序没改变,所以法线方向不变,但是镜像实际关于镜子对称了,所以进行设置。

//绘制镜子

就是一个Blend操作,注意要换回此时的常量缓冲,因为这不是绘制镜像了

mCommandList->SetGraphicsRootConstantBufferView(2, passCB->GetGPUVirtualAddress());

    // Draw mirror with transparency so reflection blends through.
    mCommandList->SetPipelineState(mPSOs["transparent"].Get());
    DrawRenderItems(mCommandList.Get(), mRitemLayer[(int)RenderLayer::Transparent]);

对于mPSOs["transparent"]设置如下:

D3D12_GRAPHICS_PIPELINE_STATE_DESC transparentPsoDesc = opaquePsoDesc;
    //镜子的混合,按道理是放在镜像效果的最后的
    D3D12_RENDER_TARGET_BLEND_DESC transparencyBlendDesc;
    transparencyBlendDesc.BlendEnable = true;
    transparencyBlendDesc.LogicOpEnable = false;

//根据Alpha绘制
    transparencyBlendDesc.SrcBlend = D3D12_BLEND_SRC_ALPHA;
    transparencyBlendDesc.DestBlend = D3D12_BLEND_INV_SRC_ALPHA;
    transparencyBlendDesc.BlendOp = D3D12_BLEND_OP_ADD;
    transparencyBlendDesc.SrcBlendAlpha = D3D12_BLEND_ONE;
    transparencyBlendDesc.DestBlendAlpha = D3D12_BLEND_ZERO;
    transparencyBlendDesc.BlendOpAlpha = D3D12_BLEND_OP_ADD;
    transparencyBlendDesc.LogicOp = D3D12_LOGIC_OP_NOOP;
    transparencyBlendDesc.RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;

    transparentPsoDesc.BlendState.RenderTarget[0] = transparencyBlendDesc;
    ThrowIfFailed(md3dDevice->CreateGraphicsPipelineState(&transparentPsoDesc, IID_PPV_ARGS(&mPSOs["transparent"])));

以上各步骤就已经实现了镜子效果的绘制,这里边我们主要关注模板的使用,两个阶段:

1.用模板标记镜子的可见范围;

2.根据与可见范围模板值相等来填充像素着色结果。

四、阴影效果

实现阴影的途径是多样的:

1.将阴影看做是一种几何体,只不过它没有厚度,然后就给一种很暗的材质即可绘制;

2.使用Shadow Map这样的阴影映射纹理,即以光源作为视点渲染一张深度图,并以摄像机视点渲染深度图,两张深度图对同一像素点深度值进行对比,如果能够被摄像机看到,但是不能被光源看到(摄像机深度值更深),那就是阴影(局部光照模型);

3.使用Ray Trace,观察点与光源之间有遮挡则视为阴影。

对于第一条,当做几何体来做,实现容易很多,毕竟绘制并渲染几何体使用我们对于PSO的设置即可,即便是考虑阴影不是全黑,要和背景混合也只需要Blend就能实现。然而问题也就出在这里了。什么问题呢?

试想一下,如果我们计算出某个像素是多个几何顶点的投影点,那么就会进行多次混合(不是全黑效果),就会导致这个像素越来越暗,这叫做“双重混合”,我觉得叫多重混合比较好,毕竟可能不止一次。那么要防止发生双重混合,就要保证只渲染一次,或者说多余一次的渲染被我们抛弃。OK提到抛弃,那就应该轮到模板上线,基本原理如下:
1.清空模板缓冲为0(如果本身就是0的可以不做了),并设置系统值为0:

mCommandList->OMSetStencilRef(0);

2.设置PSO的模板检测为相等时通过,通过后增加模板值(0++);并渲染该像素;

3.当如果再次操作到该像素,此时为1了,不能与模板值0相等,因此失败,抛弃像素,否则进行第2步。

对应的PSO中模板设置如下:

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"])));

通过这种占位操作就实现阴影的单次绘制。这种绘制阴影的方式理论上可行,实际上存在很多问题:

1.如何得到阴影几何体?这种计算本身就是逐顶点进行的,是消耗性能的,如果实在平面还好说,如果是曲面还要逐个曲面相交计算;

2.要解决Z-fight问题,所以会有适当地偏移,这是不准确的;

3.阴影边界太过明显,做出的是硬阴影。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值