[DirectX12学习笔记] 模板缓冲

本文介绍了使用DirectX12通过模板缓冲实现镜子和平面阴影的技术。首先,讨论了Depth/Stencil State在清除缓冲区和设置模板操作中的作用。接着,详细说明了如何利用模板缓冲实现镜子效果,通过两次渲染和模板测试来反射场景内容。最后,阐述了平面阴影的计算方法和避免双重阴影的解决方案,并提供了关键代码实现。
摘要由CSDN通过智能技术生成
  • 注意!本文是在下几年前入门期间所写(young and naive),其中许多表述可能不正确,为防止误导,请各位读者仔细鉴别。

实现镜子与平面阴影


Depth/Stencil State

每帧渲染开始前我们都应该清除depth和stencil buffer到我们指定的值,例如

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

第二个参数是我们要清除的buffer,第三个参数是我们depth buffer清除到的值,第四个则是stencil buffer要清除到的值,第五个参数是pRects数组的长度,第六个参数pRects数组是一系列矩形区域,传入这些矩形区域可以指定要清空的区域,如果传入nullptr则是清空整个depth/stencil buffer。

if( StencilRef & StencilReadMask  ⊴  Value &StencilReadMask)
    accept pixel
else
    reject pixel

Value是现在存储的值,StencilRef是我们要传入的值,这两个值读取的时候都要通过一个StencilReadMask,然后中间的运算符号也有我们规定,有以下取值

typedef enum D3D11_COMPARISON_FUNC {
    D3D11_COMPARISON_NEVER = 1,
D3D11_COMPARISON_LESS = 2, 
D3D11_COMPARISON_EQUAL = 3, 
D3D11_COMPARISON_LESS_EQUAL = 4, 
D3D11_COMPARISON_GREATER = 5, 
D3D11_COMPARISON_NOT_EQUAL = 6, 
D3D11_COMPARISON_GREATER_EQUAL = 7, 
D3D11_COMPARISON_ALWAYS = 8 
} D3D11_COMPARISON_FUNC;

在创建PSO的时候会要填一个D3D12_DEPTH_STENCIL_DESC,这里面包含的内容如下

typedef struct D3D12_DEPTH_STENCIL_DESC {
   
  BOOL                       DepthEnable;
  D3D12_DEPTH_WRITE_MASK     DepthWriteMask;
  D3D12_COMPARISON_FUNC      DepthFunc;
  BOOL                       StencilEnable;
  UINT8                      StencilReadMask;
  UINT8                      StencilWriteMask;
  D3D12_DEPTH_STENCILOP_DESC FrontFace;
  D3D12_DEPTH_STENCILOP_DESC BackFace;
} D3D12_DEPTH_STENCIL_DESC;

第一个参数是启用深度缓冲,第二个是写入掩码,第三个是深度比较的函数,第四个是是否启用模板缓冲,第五个和第六个是读取和写入的掩码,第七个和第八个是正面朝向的面和反面朝向的面的Stencil Op Desc,可能的取值如下

typedef enum D3D12_STENCIL_OP {
   
  D3D12_STENCIL_OP_KEEP,
  D3D12_STENCIL_OP_ZERO,
  D3D12_STENCIL_OP_REPLACE,
  D3D12_STENCIL_OP_INCR_SAT,
  D3D12_STENCIL_OP_DECR_SAT,
  D3D12_STENCIL_OP_INVERT,
  D3D12_STENCIL_OP_INCR,
  D3D12_STENCIL_OP_DECR
} ;

如果要传入StencilRef的值的话,可以这样

mCommandList->OMSetStencilRef(1);

实现一个镜子

虚幻里的镜子是专门给镜子创建一个摄像机来实现,这里利用stencil buffer来实现另一种镜子,就是把镜子里要反射的东西全部根据镜子对称一次(包括光源),第一次渲染只写入到stencil buffer,先清零stencil buffer,把屏幕上没被挡住的区域(即通过深度测试的部分)的stencil buffer值设置为1,第二次渲染的时候把要反射的物体正常渲染,不过要打开stencil test,把stencil buffer里为1的部分渲染出来即可,在这之后,再把镜子以透明度方式渲染出来,盖在反射出来的图像上,就看起来像一面镜子了,具体代码和阴影部分一并写在下方。

实现平面阴影

这是一种最简单的阴影算法,给定一个平面和光的位置,让阴影只投射在这个平面上,首先考虑平行光,给定平行光方向和一个点的位置,可以确定一条线,给定一个平面的位置,就可以求出线和平面的交点,假如要被投影的点是p,入射方向是L,面的法线是n,面的偏移常量是d,(n,d)可以表示一个面,然后交点s可以这样求
在这里插入图片描述注意这样乘完之后w坐标不是1而是n·L,接下来可以不用处理了交给后面硬件完成的齐次除法就可以了,但是有个问题是,n·L一般是负的,所以后面硬件裁切的时候会被裁掉,所以我们这里要用-n·L代替n·L,因为-L和L求得的直线都是同一条,所以交点也不会变。
这个矩阵叫做shadow matrix,点光源的矩阵如下
在这里插入图片描述注意最后一列和平行光的不一样。
以上两个矩阵可以统一的表示为
在这里插入图片描述当Lw为0时表示平行光,Lw为1时表示点光源。
获取Shadow Matrix的方法如下

inline XMMATRIX XM_CALLCONV XMMatrixShadow(
	FXMVECTOR ShadowPlane,
	FXMVECTOR LightPosition);

直接利用shadow matrix来变换算出阴影顶点位置的话,会有个问题,那就是如果一个曲面是闭合的,那么这个曲面一定会有多个三角面被投影到平面上的同一个位置,这样就会出现两倍的阴影,会变得很黑,称为阴影的double blending,要正常渲染平面阴影,可以使用stencil buffer,首先清零stencil buffer,然后设置stencil buffer成只接受0,然后如果pass了stencil test,就设置这一位为1,这样的话如果有一个像素算了两次阴影,第二次不会被渲染出来。

代码实现

这里只列出关键部分的代码。
首先阴影是要一个材质的,可以设置为全黑然后透明度0.5。

	auto shadowMat = std::make_unique<Material>();
	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;

然后场景中的骷髅头要三个render item,一个是本身,一个是镜子里的,另一个是阴影(材质不一样、cb不一样、render layer也不一样)。

	auto skullRitem = std::make_unique<RenderItem>();
	skullRitem->World = MathHelper::Identity4x4();
	skullRitem->TexTransform = MathHelper::Identity4x4();
	skullRitem->ObjCBI
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值