模板缓冲区(Stencil Buffer)详解

1. 是什么?

模板缓冲区是 GPU 中一个与深度缓冲区大小相同、像素一一对应的整数缓冲区。每个像素通常占用 8 位(取值范围 0~255),也可以配置为 1、2 或 4 位。

它与深度缓冲区经常共享同一块显存(例如 24 位深度 + 8 位模板 = 32 位),所以常被合称为“深度模板缓冲区”。

核心功能:按照每个像素的“模板值”有条件地拒绝或接受后续绘制。你可以把它想象成一张数字化的模板纸:先在某些像素上盖个章(写入非 0 值),然后在画其他东西时,只允许在盖过特定章的像素上绘制。


2. 工作原理(两个阶段)

模板缓冲区的使用分为两个阶段:

阶段一:写入模板值

  • 绘制某些物体时,不改变颜色和深度,只修改模板缓冲区。

  • 例如:绘制一个圆形区域,把模板值设为 1。

阶段二:测试模板值

  • 绘制其他物体时,设置一个测试条件,例如:

    • Equal(等于)—— 仅当当前模板值 == 参考值才绘制

    • NotEqual(不等于)

    • Less(小于)

    • Greater(大于)

    • Always(总是通过)

    • Never(总是不通过)

  • 测试失败的像素直接被丢弃,不会更新颜色和深度。

关键点:每个像素的模板值可以单独控制,且测试和写入可以同时发生。


3. 常用操作符

在图形 API(如 OpenGL、DirectX、Vulkan)或引擎(如 Unreal)中,你可以为模板测试配置三个操作:

操作 含义
Keep 保持当前模板值不变
Zero 将模板值清零
Replace 用参考值替换当前模板值
Increment 加 1(饱和到最大值)
Decrement 减 1(饱和到最小值)
Invert 按位取反

这些操作可以分别在三种情况下执行:

  • 模板测试失败时

  • 模板测试通过但深度测试失败时

  • 模板测试和深度测试都通过时


4. 经典应用举例

例子 1:镜子 / 平面反射

需求:只在镜面区域内显示反射的图像。

步骤

  1. 写入模板:绘制镜面形状(例如一个矩形),设置模板操作:通过时替换为 1。此时镜面区域所有像素的模板值 = 1,其他区域 = 0。

  2. 绘制反射内容:开启模板测试,条件设为 Equal 1。只会在模板值为 1 的像素上绘制反射场景,其他像素被丢弃。

  3. 绘制镜子表面:最后正常绘制镜面材质(半透明或带高光),覆盖在反射图像上。

效果:只有镜子区域显示反射,不会“漏”到墙壁上。


例子 2:物体轮廓描边(非后处理方式)

需求:给一个角色绘制彩色轮廓(比如红色外发光)。

步骤

  1. 写入模板:正常绘制角色,同时把角色占据的像素的模板值设为 1。

  2. 放大绘制轮廓:稍微放大角色模型,关闭深度写入,开启模板测试条件为 NotEqual 1。此时,放大后的模型只在角色外部的像素上绘制(因为内部像素模板值为 1,不满足 NotEqual 1)。

  3. 轮廓颜色:用纯色(红色)绘制放大后的模型,就得到了一个围绕角色的红边。

注意:这种是几何放大法,不如后处理描边平滑,但适合风格化游戏。


例子 3:体积光 / 聚光灯遮罩

需求:让聚光灯只照亮某个形状内的物体(比如一个窗户形状)。

步骤

  1. 写入模板:绘制窗户的几何体,设置模板为 1。

  2. 绘制光照:在渲染光照时,模板测试条件设为 Equal 1,光线只出现在窗户形状内。

  3. 结果:光就像透过窗户打进来一样。


例子 4:Stencil Shadow Volumes(传统阴影体)

需求:生成硬阴影。

步骤

  1. 从光源方向计算物体的阴影轮廓,拉伸成一个无限长的“阴影体”。

  2. 第一次绘制阴影体:正面朝向相机时模板值 +1,背面朝向相机时模板值 -1。

  3. 最后,模板值为 0 的像素在阴影外,非 0 的像素在阴影内。

  4. 将阴影内的像素变暗。

现在多使用 Shadow Map,但 Stencil Shadow Volumes 仍是学习渲染原理的经典案例。


5. 虚幻引擎中的 Custom Stencil

在虚幻引擎里,引擎自身会用模板缓冲区做某些内部处理(例如遮挡标记、景分离等)。为了避免冲突,UE 额外提供了一个 Custom Stencil 通道,专门留给开发者自由使用。

  • Set Custom Depth Stencil Value 节点就是往这个自定义通道里写入一个值(1~255)。

  • 后处理材质中通过 SceneTexture:CustomStencil 节点读取这个值。

典型用法(Lyra 项目的描边):

  1. 给红队角色所有网格体设置 Custom Stencil = 1,蓝队 = 2。

  2. 后处理材质读取每个像素的 Custom Stencil 值。

  3. 如果当前像素值为 0(背景),但相邻像素值为 1 或 2,就把该像素染成红或蓝——这就是轮廓描边的本质。


6. 总结一句话

模板缓冲区就是一张像素级的“标签贴纸”,你可以先在某些地方贴上数字标签,然后在画别的东西时,只允许在贴了特定标签的地方下笔。它是实现镜子、轮廓描边、体积光、阴影体等大量特效的基础工具。