前言
Unity关于模板缓冲区和模板测试与dx12类似,但也有不同,本文将基于dx12 来总结Unity中的模板缓冲区及模板测试的含义
模板缓冲区
什么是模板缓冲区?
- 我们知道深度缓冲区存储每个像素的深度值z,而模板缓冲区是一个额外的buffer,它的分辨率大小和深度缓冲区相同,但模板缓冲区的一个像素点占8bit,且格式为UINT(格式有两种,都为UINT,但一种仅仅8位bit;另一种虽然占32位,但有用的只有前8位,剩下的24位仅用于填充),而这8bit的作用之一是控制颜色缓冲区和z缓冲区的渲染,比如在一个像素的模板缓冲区中存放1,表示该像素对应的空间点处于阴影体中
注意:模板缓冲区需要搭配深度缓冲区一起工作,所以接下来会经常看到深度缓冲区的身影
为什么需要模板缓冲区?
- 模板缓冲区主要用于实现特效、草这样的面片
- 举个例子,若在模板缓冲区中绘制了一个空心矩形,模板缓冲中的值最开始时会被默认为0,之后在模板缓冲区中使用1填充了一个空心矩形,场景中的片段将会只在片段的模板值为1的时候会被渲染
如何使用模板缓冲区?
-
在DX12中,需要填写模板缓冲区的描述符(D3D12_DEPTH_STENCIL_DESC)并将该描述符填入渲染管线对象(D3D12_GRAPHICS_PIPELINE_STATE_DESC)
-
但在Unity中,为了提升开发者的开发效率,对模板缓冲区的使用方式进行了封装
-
使用框架如下
声明 Stencil,然后在关键字后填写需要的内容即可Stencil { Ref <ref> ReadMask <readMask> WriteMask <writeMask> Comp <comparisonOperation> Pass <passOperation> Fail <failOperation> ZFail <zFailOperation> CompBack <comparisonOperationBack> PassBack <passOperationBack> FailBack <failOperationBack> ZFailBack <zFailOperationBack> CompFront <comparisonOperationFront> PassFront <passOperationFront> FailFront <failOperationFront> ZFailFront <zFailOperationFront> }
关于这些关键词的含义在下面的模板测试会讲到
模板测试
定义
允许开发者在渲染片元时将模板缓冲区中的值设定为一个特定的值,通过在渲染时修改模板缓冲的内容,写入模板缓冲。在接下来的渲染迭代中,我们可以读取这些值,来决定丢弃/保留某个片段
执行阶段
输出合并阶段。当片元着色器处理完一个片段之后,模板测试(Stencil Test)会开始执行
大致流程
- 启用模板缓冲的写入
- 渲染物体,更新模板缓冲区
-
禁用模板缓冲的写入
-
渲染(其它)物体,这次根据模板缓冲的内容丢弃/保留特定的片段
丢弃/保留特定的片段的计算原理
if( StencilRef & StencilReadMask \unlhd Value & StencilReadMask )
accept pixel
else
reject pixel
- StencilRef (对应Unity的Ref):模板参考值。UINT,范围[0, 255],默认0
-
StencilReadMask(对应Unity的ReadMask):在模板测试时使用掩码值。UINT,范围[0, 255],默认255(0xff)
-
\unlhd(对应Unity的Comp):比较函数,用于比较左右两边的值,若为true,则通过模板测试;否则,失败
-
Comp
- 在DX12中,通过使用enum D3D12_COMPARISON_FUNC中的特定值来确定比较函数
//以下比较运算符返回true则通过测试 typedef enum D3D12_COMPARISON_FUNC { D3D12_COMPARISON_FUNC_NEVER = 1, //只返回false.永远不通过深度测试 D3D12_COMPARISON_FUNC_LESS = 2, //"<".片段深度值小于缓冲区的深度值 D3D12_COMPARISON_FUNC_EQUAL = 3, //"==".片段深度值等于缓冲区的深度值 D3D12_COMPARISON_FUNC_LESS_EQUAL = 4, //"≤".片段深度值小于等于缓冲区的深度值 D3D12_COMPARISON_FUNC_GREATER = 5, //">".片段深度值大于缓冲区的深度值 D3D12_COMPARISON_FUNC_NOT_EQUAL = 6, //"!=".片段深度值不等于缓冲区的深度值 D3D12_COMPARISON_FUNC_GREATER_EQUAL = 7, //"≥".片段深度值大于等于缓冲区的深度值 D3D12_COMPARISON_FUNC_ALWAYS = 8 //只返回true.永远通过深度测试 } ;
- 在Unity中,对比较函数进行了简化更加直观
enum CompareFunction { Never = 1, Less = 2, Equal = 3, LEqual = 4, Greater = 5, NotEqual = 6, GEqual = 7, Always = 8 };
模板测试后的逻辑
不管该片元的模板测试是否通过,都需要由开发者来决定其去留
- 在DX12中,填写描述符D3D12_DEPTH_STENCILOP_DESC来决定
typedef struct D3D12_DEPTH_STENCILOP_DESC { D3D12_STENCIL_OP StencilFailOp; //描述当片元在模板测试失败时,应如何更新模板缓冲区 D3D12_STENCIL_OP StencilDepthFailOp; //描述当片元通过模板测试,但在深度测试失败时,应如何更新模板缓冲区 D3D12_STENCIL_OP StencilPassOp; //描述当片元通过模板测试、深度测试时,应如何更新模板缓冲区 D3D12_COMPARISON_FUNC StencilFunc; //模板测试中所用的比较函数 } D3D12_DEPTH_STENCILOP_DESC; //指定在深度/模板测试期间被执行的模板运算符 typedef enum D3D12_STENCIL_OP { D3D12_STENCIL_OP_KEEP = 1, // 不修改模板缓冲区 D3D12_STENCIL_OP_ZERO = 2, // 将模板缓冲区中的元素置为0 D3D12_STENCIL_OP_REPLACE = 3, // 将模板缓冲区中的元素替换为用于模板测试的模板参考值(StencilRef).只有当深度/模板缓冲区状态块绑定至管线时,才能设定该值 D3D12_STENCIL_OP_INCR_SAT = 4,// 对模板缓冲区中的元素进行递增.若超出范围会进行钳制 D3D12_STENCIL_OP_DECR_SAT = 5,// 对模板缓冲区中的元素进行递减.若超出范围会进行钳制 D3D12_STENCIL_OP_INVERT = 6, // 对模板缓冲区中的元素按二进制位进行反转 D3D12_STENCIL_OP_INCR = 7, // 对模板缓冲区中的元素进行递增.若超出范围会回到最小值0 D3D12_STENCIL_OP_DECR = 8 // 对模板缓冲区中的元素进行递增.若超出范围会回到最大值255 } ;
- 在Unity中,对其进行了简化
enum StencilOp { Keep = 0, // 不修改模板缓冲区 Zero = 1, // 将模板缓冲区中的元素置为0 Replace = 2, // 将模板缓冲区中的元素替换为模板参考值(Ref) IncrSat = 3, // 对模板缓冲区中的元素进行递增.若超出范围会clamp DecrSat = 4, // 对模板缓冲区中的元素进行递减.若超出范围会进行clamp Invert = 5, // 对模板缓冲区中的元素按二进制位进行反转 IncrWrap = 6, // 对模板缓冲区中的元素进行递增.若超出范围会回到最小值0 DecrWrap = 7 // 对模板缓冲区中的元素进行递增.若超出范围会回到最大值255 }
定义深度/模板状态
最后,需要定义深度/模板缓冲区状态:包括是否启用深度/模板测试,比较函数,是否进行深度/模板写入,对模型的正面和背面做什么模板运算
- 在DX12中,需要填写描述符D3D12_DEPTH_STENCIL_DESC
typedef struct D3D12_DEPTH_STENCIL_DESC { BOOL DepthEnable; // 是否开启深度测试,TRUE则开启.默认TRUE D3D12_DEPTH_WRITE_MASK DepthWriteMask; // 是否禁止深度写入,若为D3D12_DEPTH_WRITE_MASK_ZERO则不可进行深度写入,若为D3D12_DEPTH_WRITE_MASK_ALL则可以.默认D3D12_DEPTH_WRITE_MASK_ALL D3D12_COMPARISON_FUNC DepthFunc; // 指定比较函数 BOOL StencilEnable; // 是否开启模板测试,若为true则开启.默认FALSE UINT8 StencilReadMask; // 掩码值,用于模板测试 UINT8 StencilWriteMask; // 掩码值,用于屏蔽对应位的写入操作 D3D12_DEPTH_STENCILOP_DESC FrontFace; //指示根据测试和深度测试的结果,对正面朝向的三角形进行指定的模板运算 D3D12_DEPTH_STENCILOP_DESC BackFace; //指示根据测试和深度测试的结果,对背面朝向的三角形进行指定的模板运算 } D3D12_DEPTH_STENCIL_DESC; //是否禁止深度写入 typedef enum D3D12_DEPTH_WRITE_MASK { D3D12_DEPTH_WRITE_MASK_ZERO = 0, D3D12_DEPTH_WRITE_MASK_ALL = 1 } ; //用于模板测试的掩码值的默认值——不会屏蔽任何一位模板值 #define D3D12_DEFAULT_STENCIL_READ_MASK (0xff) //写掩码值的默认值——不会屏蔽任何一位模板值 #define D3D12_DEFAULT_STENCIL_WRITE_MASK (0xff)
- 在Unity中,对于深度测试/写入和模板测试/写入的处理是分开的,因此这里只提及模板
Stencil { Pass <passOperation> // 通过模板、深度测试后,如何更新模板缓冲区(双面) Fail <failOperation> // 未通过模板测试后,如何更新模板缓冲区(双面) ZFail <zFailOperation> // 通过模板测试,但未通过深度测试,如何更新模板缓冲区(双面) CompBack <comparisonOperationBack> // 为模型的背面定义的比较函数 PassBack <passOperationBack> // 通过模板、深度测试后,如何更新模板缓冲区(背面) FailBack <failOperationBack> // 未通过模板测试后,如何更新模板缓冲区(背面) ZFailBack <zFailOperationBack> // 通过模板测试,但未通过深度测试,如何更新模板缓冲区(背面) CompFront <comparisonOperationFront> // 为模型的正面定义的比较函数 PassFront <passOperationFront> // 通过模板、深度测试后,如何更新模板缓冲区(正面) FailFront <failOperationFront> // 未通过模板测试后,如何更新模板缓冲区(正面) ZFailFront <zFailOperationFront> // 通过模板测试,但未通过深度测试,如何更新模板缓冲区(正面) }
总结
- 不难看出,模板与深度相差无几,只是模板测试的运算逻辑更加复杂,但本质和深度缓冲区一样,都是进行测试留下开发者想要的部分
-
最后,总结一下Unity中Stencil的使用方法和各参数的含义
Stencil { Ref <ref> // 参考值,比较函数比较的对象.UINT,范围[0, 255],默认0 ReadMask <readMask> // 在模板测试时,使用此值作为遮罩.UINT,范围[0, 255],默认255 WriteMask <writeMask> // 在写入模板缓冲区,使用此值作为遮罩.UINT,范围[0, 255],默认255 Comp <comparisonOperation> // 比较函数 Pass <passOperation> // 通过模板、深度测试后,如何更新模板缓冲区(双面) Fail <failOperation> // 未通过模板测试后,如何更新模板缓冲区(双面) ZFail <zFailOperation> // 通过模板测试,但未通过深度测试,如何更新模板缓冲区(双面) CompBack <comparisonOperationBack> // 为模型的背面定义的比较函数 PassBack <passOperationBack> // 通过模板、深度测试后,如何更新模板缓冲区(背面) FailBack <failOperationBack> // 未通过模板测试后,如何更新模板缓冲区(背面) ZFailBack <zFailOperationBack> // 通过模板测试,但未通过深度测试,如何更新模板缓冲区(背面) CompFront <comparisonOperationFront>// 为模型的正面定义的比较函数 PassFront <passOperationFront> // 通过模板、深度测试后,如何更新模板缓冲区(正面) FailFront <failOperationFront> // 未通过模板测试后,如何更新模板缓冲区(正面) ZFailFront <zFailOperationFront> // 通过模板测试,但未通过深度测试,如何更新模板缓冲区(正面) }
reference
Directx12 3D 游戏开发实战
https://github.com/QianMo/Game-Programmer-Study-Notes
Comments | NOTHING