【D3D11游戏编程】学习笔记十八:模板缓冲区的使用、镜子的实现--模板缓冲实现原理和机制

更新时间:2023-07-12 20:11:40 阅读: 评论:0

【D3D11游戏编程】学习笔记⼗⼋:模板缓冲区的使⽤、镜⼦的实现--模板缓冲实现原理和机制
(注:【D3D11游戏编程】学习笔记系列由CSDN作者BonChoix所写,转载请注明出处:,谢谢~)
模板缓冲区(Stencil Buffer)是⼀个与后缓冲区(Back Buffer)尺⼨⼀样的离屏缓冲区(Off-Screen Buffer),主要⽤于实现⼀些特效。模板缓冲区中的每⼀个像素Pi,j,与后缓冲区中的像素Pi,j是⼀⼀对应的。在功能上,与深度缓冲区类似,都是⽤来控制⼀个⽚段能否通过3D 渲染管线相应的阶段,以被进⼀步处理。不同之处在于,模板缓冲区与深度缓冲区⽤于控制⽚段是否通过所使⽤的判断依据不⼀样。对于深度缓冲区,它通过⽐较每个⽚段与当前缓冲区中对应像素处的深度值来判断,如果⼩于该深度值则通过,否则丢弃该⽚段;模板缓冲区使⽤其他的判断依据,我们稍后会详细介绍。
1. 模板缓冲区相关数据格式
实际上,模板缓冲区与深度缓冲区是共⽤⼀个“物理缓冲区”的,即真正存在的只有⼀个缓冲区,该缓冲区中任⼀像素处保存了两种信息:深度值与模板值。⽐如⼀个像素占⽤4个字节(32位),那么深度值可能占⽤前⾯⼏位,模板值占⽤后⾯⼏位。在D3D11中针对该缓冲区,定义了如下⼏种数据格式:
DXGI_FORMAT_D32_FLOAT_S8X24_UINT:该格式中,每个像素为8字节(64位),其中深度值占32位,为float型。模板值为8位,为位于[0,255]中的整型,后⾯24位⽆任何⽤途,纯对齐⽤;
DXGI_FORMAT_D24_UNORM_S8_UINT:该格式中,每个像素为4字节(32位),其中深度值占24位,并映射到[0,1]之间。模板值为8位,为位于[0,255]中的整型;
在⼤多数情况下,我们使⽤第⼆种格式,在我们前⾯所有的⽰例程序中,使⽤的正是这种格式。在初始化D3D时,我们需要在创建深度/模板缓冲区时为它指定相应的格式,对应代码如下(对应 dsDesc.Format部分):
2. 模板测试判断依据
正如深度缓冲区使⽤⽚段的深度值作为判断该⽚段是否通过的依据,模板缓冲区也有它独特的判断依据,如以下公式所⽰:
该判断主要包含两部分:COMPARISON左边部分和右边部分。
StencilRef为程序员设定的⼀个参考值,StencilReadMask为模板值读取掩码,与参考值“按位与”作为式⼦中的左边的结果;⼀般情况下,我们设定该掩码为0xFF,即接位与的结果就是模板参考值本⾝;
Value为模板缓冲区中对应位置处的当前值,同样与掩码按位与后作为右边的结果值;
昆山亭林公园
式⼦中左、右两部分的结果通过中间的⽐较操作“COMPARISON”来决定决断的结果,在D3D11中,⽐较操作定义为如下枚举类型:
守护神兽
通过名字即很容易想到其意义,我们忽略前缀:
NEVER:判断操作永远失败,即⽚段全部不通过模板测试;
LESS:该判断为“<"操作,即左边<;右边时测试通过;
EQUAL:“=”操作,即当左、右两边相等时测试通过;
LESS_EQUAL:“<="操作,当左边<=右边时测试通过;
GREATER:">"操作,当左边>右边时测试通过;
NOT_EQUAL:"!="操作,当左、右两边不相等时测试通过;
GREATER_EQUAL:">="操作,当左边>=右边时测试通过;
ALWAYS:永远通过,即不管左右两边的值,恒通过。
这⾥的枚举类型同样适应于深度缓冲区中⽐较操作的设定。
举个例⼦说明下模板测试的过程:
⽐如我们设定模板参考值为1,掩码值为0xffffffff,针对某个⽚段,如果模板缓冲区中对应的当前模板值为0,则按上述公式,
左边 = 1 & 0xFF = 1;
右边 = 0 & 0xFF = 0;
1. 如果⽐较操作我们设定为ALWAYS,则该⽚段的模板测试通过(不管左右两边什么值);
2. 如果⽐较操作我们设定为LESS,则由于1 < 0是错误的,因此该⽚段的模板测试失败,⽚段被丢弃;
3. 如果⽐较操作我们设定为GREATER,由于1 > 0正确,因此模板测试成功,⽚段通过。
其他⽐较操作依次类推,很简单。
3. 模板缓冲区的更新
1994年属在上⼀步骤中的模板测试之后,不管⽚段是否通过测试,都要对模板缓冲区进⾏相应的更新。⾄于怎么更新,取决于程序员的设定。
D3D11中针对模板缓冲区的更新操作定义了如下枚举类型:
同样,我们忽略前缀:
KEEP:保持当前值不变,⽐如测试前模板值为0,则继续为0不变;
ZERO:把模板缓冲区对应位置的模板值设为0;
REPLACE:"replace"即替换的意思,即使⽤模板参考值替换模板缓冲区中对应的当前值;
INCR_SAT:"INCR"即increa,⾃增的意思,"SAT"为saturate,⽤于限制⾃增的范围。即把当前的模板值加1。如果值超过了
255(因为我们的模板缓冲区为8位,因此255即为最⼤值),则保持在255。
DECR_SAT:同上,DECR为"decrea",⾃减的意思,即把当前值⾃减1,如果值低于0,则保持在0;
INVERT:把当前模板值按位取反。⽐如对于0xffffffff,更新后的结果为0x00000000;
INCR:同上⾯的INCR⼀样,也是把当前模板值⾃增1,但如果值超过255,则返回到0,之后继续⾃增;
DECR:同上⾯的DECR⼀样,也是把当前模板值⾃减1,但如果值低于0,则为255,之后继续⾃减。
4. D3D11中针对模板缓冲区的操作
之前我们使⽤过混合,在使⽤混合,我们⾸先要创建⼀个BlendState,然后通过SetBlendState来使⽤它。同样,这⾥我们要使⽤模板缓冲区,也是⾸先创建相应的DepthStencilState,然后SetDepthStencilState。在D3D11中对应的函数为:
第⼀个参数为对应的深度/模板缓冲区状态描述,我们应该已经很习惯这种步骤了。⽆论是创建纹理、
缓冲区、还是各种渲染状态,第⼀步都是通过给出其描述开始;
第⼆个参数为我们要创建的状态接⼝的地址。
状态描述结果定义如下:
该结构中前⼏个⽤于描述深度缓冲区,后⾯⽤于描述模板缓冲区。(毕竟该两个缓冲区位于⼀起嘛~)
DepthEnable:是否使⽤深度缓冲区,显然⼤多数数情况下为true;
DepthWriteMask:深度值写⼊掩码值,⼤多数情况下我们把整个深度值完整写⼊,因此掩码值为
知你D3D11_DEPTH_WRITE_MASK_ALL;
DepthFunc:深度判断值,即本⽂第⼆部分中提到的⽐较函数,⼤多数情况下我们使⽤LESS,即更⼩的深度(更靠前)通过测试;
StencilEnable:是否使⽤模板缓冲区,我们就是要开启模板缓冲区,因此为true;
StencilReadMask:模板值读取掩码,⼤多数情况下我们使⽤0xff;
StencilWriteMask:模板值写⼊掩码,同样为0xff;
FrontFace:针对渲染物体的正⾯,所使⽤的模板更新操作;
BackFace:针对渲染物体的背⾯,所使⽤的模板更新操作。
这⾥的FrontFace和BackFace对应的结果定义如下:
民国大总统这⾥前三个操作就是本⽂第三部分所提到的模板更新操作。
StencilFailOp:模板测试失败后的操作,⽐如我们想设置为如果失败,则保持不变,则为KEEP;
StencilDepthFailOp:深度测试失败后的操作;
StencilPassOp:模板测试通过后的操作,⽐如我们在通过测试后更新为参考值,则为REPLACE;
StencilFunc:⽐较操作,即本⽂第⼆部分中提到的⽐较操作:LESS、GREATER、ALWAYS等等。
⼀般情况下,我们只渲染物体的正⾯,背⾯是剔除的,因此在上⾯的结构中,对于BackFace的设置是⽆关紧要的。
到现在为此,你可能会有疑问:在模板测试中使⽤的模板参考值怎么设定?没错,上⾯我们只是说是程序员设定的,但到⽬前为⽌还没提到如何设定这个值。其实该模板参考值正是通过SetDepthStencilState函数设定的。该函数原型如下:
第⼀个参数就是我们刚刚创建的深度/模板状态接⼝;
第⼆个参数即指定模板参考值,为UINT类型。
好了,有关模板缓冲区的使⽤就这些,下⾯来个⼩结:阅读理解专项训练
1. 使⽤模板缓冲区时最重要的两个值:缓冲区中的当前值value,模板参考值ref;
2. 模板测试的本质即对该两个值使⽤特定的⽐较操作:NEVER, ALWAYS, LESS, EQUAL, GREATER等等;
3. 模板测试后要对模板缓冲区进⾏相应的更新,更新操作包括:KEEP, REPLACE, INCR_SAT, INCR, DECR, DECR_SAT 等等;
4. 模板测试后针对不同结果可以使⽤不同的更新操作,包括测试成功操作(StencilPassOp),测试失败操作(StencilFailOp),深度测试失败操作(DepthFailOp).
校园暴力演讲稿
有关模板缓冲区的使⽤,理论知识就是这些。但是学习模板缓冲区,最好的⽅法就是研究实际的例⼦。下⾯我们就通过⼀个平⾯镜⼦反射的例⼦来进⼀步掌握模板缓冲区的使⽤。
5. 实际例⼦:平⾯镜的实现          5.1 要解决的两个关键问题
要实现平⾯镜反射效果,主要有两⼤关键问题要解决。
⾸先是平⾯镜反射的变换操作。对于⼀个要绘制的物体,如何得到它在镜⼦中的影⼦的表⽰。在3D 中,任何变换都是通过矩阵来实现的。这⾥也⼀样,这时我们就需要⼀个反射变换矩阵。反射变换矩阵可以惟⼀地通过⼀个平⾯给确定,该平⾯就是反射平⾯。在3D 中,平⾯的数学表⽰为⼀个4维的向量[Nx, Ny, Nz, d]。此外,XNAMath 也提供了相应的函数来等到反射变换矩阵:
催化裂解
这⾥⾯惟⼀的参数即我们的反射平⾯。
由于篇幅限制,在本⽂中暂时不对任意平⾯的3D 数学表⽰进⾏解释,后⾯的⽰例程序中我们对于使⽤的平⾯直接给出其表⽰形式。如果⼤家对3D 平⾯的数学表⽰有困惑,可以参考专门的3D 数学⽅⾯的书籍。也欢迎向我提出,如有必要我后⾯会专门写篇⽂章来解释3D 平⾯表⽰的推导。
第⼆个问题,也是核⼼问题,即:当我们的观察点在空间中任意移动时,在移动到特定范围之外时,我们将看不到镜⼦中的物体。我想这种情况在现实当中很容易想到吧。下⾯我通过⼏张图来说明下这种情景(这些图来⾃本⽂对应的⽰例程序截图,⼤家可以⾃由下载参考源代码):上⾯这张图是正常情况下我们在镜⼦中看到物体的情形。
当我们在空间移动时,由于镜⼦尺⼨的限制,整个物体(或部分)可能会移动到镜⼦范围之外。正如以上图⽚所⽰,这时我们应该只能看到⼀部分物体了。这种情况也正是我们最终期待看到的情况。 但这就涉及到⼀个问题:如何让程序知道哪些部分位于镜⼦当中,以正确绘制它;⽽哪些部分位于镜⼦之外,从⽽不绘制呢?
这时就是“模板缓冲区”⼤显⾝⼿的时候啦!在继续介绍之前,我们先看⼀下如果没有模板缓冲区,将是什么样的情况:针对上幅图中的视⾓,以下是不使⽤模板缓冲区时的渲染结果:物体竟然显⽰到镜⼦外⾯去啦!这显然是不允许的!还好有模板缓冲区的存在~
下⾯我们来详细地看下如何作⽤模板缓冲区来实现第⼆张图中的效果。
5.2 绘制过程
在这个场景中,主要有如下⼏部分:墙⾯、地⾯、箱⼦、镜⼦、镜中的箱⼦。
1. 我们按正常情况绘制墙⾯、地⾯和箱⼦。这些物体的绘制与模板缓冲区⽆任何关系。
2. 第⼆步我们就要针对镜⼦区域,使⽤模板缓冲区来进⾏限制了。
为了告诉计算机,哪些区域是镜⼦所在区域,哪些区域是镜⼦之外的部分,我们需要使⽤模板缓冲区来标记给它看。因此在这⼀步当中,我们只针对模板缓冲区进⾏操作,⽽不更改场景中的颜⾊值。
为了实现这种效果,我们⾸先要禁⽌颜⾊的写⼊。在之前⽂章介绍“混合”的使⽤时,提到过禁⽌颜⾊写⼊的实现,即通过把
D3D11_RENDER_TARGET_BLEND_DESC 中对应的RenderTargetWriteMask 设置为0即可。如有疑问,可以参考。 相应的状态我已经在源代码框架中的RenderStates 中定义好了,即NoColorWrite 状态,详细情况请参考源代码。
其次就是模板缓冲区的操作了。还记得不,在每开始绘制⼀帧时,我们要做的第⼀件事就是清屏,包括后缓冲区以及深度/模板缓冲区。清屏时我们指定了默认的模板值,⽐如0,也是我们之前⼀直做的。如下语句,最后⼀个参数就是清屏时设定的模板值:
因此在经过第⼀步绘制后,模板缓冲区中所有的模板值依然为0,因为我们没打开模板操作。为了惟⼀地标记镜⼦所在区域,我们需要修改该区域对应的模板值,以区别于其他区域的模板值。⽐如把该区域的模板值设为参考值。
要修改模板缓冲区,我们就需要调⽤绘制函数。因此我们只需要更新镜⼦所在区域的模板值,因此这⼀步中我们只绘制镜⼦,由于禁⽌颜⾊写⼊,因此场景并不会被改变,仅仅是⽤于修改模板缓冲区。
为了把镜⼦所在区域全部更新,我们使⽤的模板⽐较函数
为ALWAYS要修改模板缓冲区,我们就需要调⽤绘制函数。因此我们只需要更新镜⼦所在区域的模板值所有(改区域所有的点都能通过测试被替换为参考值),即只要绘制该区域,总能够通过测试⽽修改模板值。对于模板更新操作,正如刚才所⾔,我们使⽤REPLACE操作,把对应区域设为参考值,即针对StencilPass的。因为现在模板测试总是通过,因此StencilFail的操作就⽆所谓的。对于DepthFail后的更新操作,我们设为KEEP,即即使模板测试通过,但深度测试不通过的(这就是这⾥为什么需要使⽤深度测试的原因),依然不改变对应的模板值(设想下,如果箱⼦挡住了⼀部分镜⼦,对于这⼀部分,即使位于镜⼦范围之内,我们也看不到镜⼦的物体吧。这就是深度测试失败导致的。因此这时我们就不需要修改对应的模板值了。当然,就算修改了,后⾯真正绘制镜⼦物体时,还会因为深度测试失败⽽丢弃)。
为了形象地表⽰这两步中后缓冲区和模板缓冲区相应的改变,请看以下⼏张图:
这张图是第⼀步绘制完墙⾯、地⾯、箱⼦后对应的后缓冲区和模板缓冲区。我们重点关注模板缓冲区,图中⼀致地为灰⾊,代表默认的0.
这张图为第⼆步把镜⼦区域模板值修改为参考值后的情形。显然,后缓冲区没有任何改变。⽽模板缓冲区,我们看到镜⼦区域的改变,⽽其他地⽅保持不变。这样我们就惟⼀地镜⼦区域标记出来了。
3. 绘制镜中的物体
现在,我们开始绘制箱⼦经反射后在镜⼦中的部分。这⾥有两⼤事情要做:
1. ⽣成相应的变换矩阵,注意光源的⽅向也要经过镜⼦的反射
在该程序中,我们的镜⼦所在平⾯为与z轴垂直且位于z = 5的平⾯,正⾯指向z轴负⽅向。这⾥我直接给出其数学表⽰,为【0,0,-1, 5】。通过它⽣成反射矩阵,应⽤于箱⼦,并且相应地修改光源的⽅向,代码如下:
还有⼀点要注意,原物体中规定顶点以顺时针为正⾯,但经过镜⾯反射后,对应的正⾯会变成逆时针⽅向,因此我们还需要相应地设置渲染状态,规定逆时针为正⾯。否则镜⼦中的物体会被作为背⾯⽽剔除掉。
第⼆件⼤事为设置相应的模板缓冲区状态。
为了使⽤之前标记好的区域,我们规定,只要当对应的模板值为参考值时才通过模板测试,否则失败。即这时的模板⽐较函数为EQUAL。显然,只有镜⼦中区域模板值才为参考值,其他区域都不会通过的。因此保证了物体只能被绘制在镜⼦范围之内。这个对应的模板状态在源代码框架中RenderStates中也定义好了,为DrawReflectionDSS。
现在设置好渲染状态就可以绘制镜⼦中的箱⼦了,渲染状态有如下两个关键点:

本文发布于:2023-07-12 20:11:40,感谢您对本站的认可!

本文链接:https://www.wtabcd.cn/fanwen/fan/89/1078871.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:模板   缓冲区   深度
相关文章
留言与评论(共有 0 条评论)
   
验证码:
推荐文章
排行榜
Copyright ©2019-2022 Comsenz Inc.Powered by © 专利检索| 网站地图