Odd-Even Stencil 剖切封盖技术
Odd-Even Stencil 剖切封盖技术
一、技术背景
根据公开资料,Odd-Even Stencil Cap(也称 stencil-based capping of clipped solids)最初的目的不是做通用 CSG,而是解决实时三维可视化中“裁剪实体后截面没有封闭面”的问题。OpenGL 早期提供用户裁剪面(user clip plane)可以快速切掉模型一侧,但裁剪后的实体会暴露内部空洞,因此需要额外生成一个位于裁剪平面的 cap 面;经典做法利用 stencil buffer 统计视线穿过封闭实体表面的次数,通过 front/back face 的 stencil increment/decrement 或 GL_INVERT 获得截面区域的 inside/outside 状态,再利用 stencil 限制 cap polygon 只绘制在实体内部,从而实现无需 CPU 几何切割的实时剖切封盖。该技术主要用于 CAD、科学可视化、医学可视化等需要实时观察实体内部结构的场景。OpenGL SIGGRAPH 99 示例《Capping Clipped Solids with the Stencil Buffer》明确描述了这一用途:裁剪实体后,将 cap polygon 嵌入 clipping plane,并利用 stencil buffer 将其裁剪到 solid interior;《Advanced Graphics Programming Using OpenGL》也将其作为 clipped solid capping 的经典方法。
经典 Odd-Even Stencil Cap 的主要优点是几何正确性高、无需修改原始模型数据、GPU 实现简单稳定:它利用封闭网格的拓扑性质,通过射线穿越表面的奇偶关系判断截面内部区域,因此对于凹模型、复杂实体以及具有较高 depth complexity 的封闭模型仍然可以正确生成 cap;同时 cap 不依赖屏幕空间阈值,而是由真实几何深度和 stencil 状态约束,因此边界精确。其缺点也比较明显:首先要求输入模型满足封闭 solid 条件,存在孔洞、非流形边、错误 winding 时容易失败;其次每个 clipping plane 通常需要独立建立 stencil 状态,因此多剖切面、多对象、多视图情况下 pass 数量增加;另外 stencil buffer 本身只保存离散 inside/outside 信息,无法表达模型之间的重叠深度、干涉强度等连续信息。OpenGL 资料也特别指出,该方法依赖正确构造的 solid,并要求面方向满足内部朝向约定。
随着 GPU 渲染技术发展,实时 CAD 和可视化系统逐渐出现了多种替代或增强方案。第一类是几何级切割方案,即 CPU 或 GPU 直接计算 mesh 与 clipping plane 的交线,生成新的截面边界并三角化 cap,例如 CAD 内核中的 B-rep section、boolean intersection、mesh slicing,这类方法生成真实几何,适合工程分析、测量和制造,但计算成本更高。第二类是GPU screen-space 方法,例如基于 depth buffer、mask texture、signed accumulation、ray-based reconstruction 的方案,它们避免真实 mesh 修改,通过额外 framebuffer 存储裁剪结果,再在后处理中重建 cap,优势是支持大规模装配和动态交互;代价是结果依赖屏幕分辨率,需要处理深度一致性和非封闭模型问题。第三类是现代 CAD 软件内部的混合方案,商业软件通常不会公开内部实现,但公开功能可以确认它们已经支持多剖切面、装配剖视、实时 section view 等能力,例如 Siemens NX 提供 Assembly Clip Section 和多 section plane 操作,Solid Edge API 也提供多个 section plane 的实时 clipping 支持,这类系统通常结合 GPU clipping、缓存几何、LOD 和专用显示管线实现,而不局限于传统单 stencil buffer 方法。
从技术演进角度看,Odd-Even Stencil Cap 可以看作第一代 GPU 实时实体剖切封盖方案:它解决了“如何快速显示截面”的问题,但它本质是二值拓扑判断;现代方案则更多转向“几何精确性”和“视觉交互效率”的折中,例如真实 B-rep section 用于工程计算,GPU mask/depth 方法用于大规模装配浏览,而 RGBA32F signed mask 方案属于后者,它将传统 stencil 的 1bit parity 信息扩展为连续 signed field,因此能够进一步支持多模型叠加、干涉区域检测和强度可视化,这是传统 Odd-Even stencil cap 本身无法提供的能力。
二、OpenGL 模板缓冲API介绍
2.1 glStencilFunc
void glStencilFunc( GLenum func,
GLint ref,
GLuint mask);
该函数用于设置 stencil test 的比较函数、参考值以及比较掩码。它决定一个 fragment 是否能够通过 stencil test,实际 stencil 修改行为由 glStencilOp 决定。
参数:
a. GLenum func 指定 stencil test 的比较函数
| func | 判断条件 | 说明 |
|---|---|---|
GL_NEVER |
永远失败 | fragment 永远不通过 stencil test |
GL_LESS |
(ref & mask) < (stencil & mask) |
参考值小于 stencil 值时通过 |
GL_LEQUAL |
(ref & mask) <= (stencil & mask) |
参考值小于等于 stencil 值时通过 |
GL_GREATER |
(ref & mask) > (stencil & mask) |
参考值大于 stencil 值时通过 |
GL_GEQUAL |
(ref & mask) >= (stencil & mask) |
参考值大于等于 stencil 值时通过 |
GL_EQUAL |
(ref & mask) == (stencil & mask) |
两者相等时通过 |
GL_NOTEQUAL |
(ref & mask) != (stencil & mask) |
两者不等时通过 |
GL_ALWAYS |
永远成功 | 不进行限制 |
b. GLint ref Stencil 比较使用的参考值
(ref & mask)会与(stencilBufferValue & mask)进行比较。
c. GLuint mask 指Stencil value mask,用于屏蔽参与比较的 bit
2.2 glStencilOp
void glStencilOp(
GLenum sfail,
GLenum dpfail,
GLenum dppass
);
该函数用于指定 stencil test 和 depth test 不同结果情况下,如何更新 stencil buffer 的值。
参数:
a. GLenum sfail 用于Stencil test 失败时执行的 stencil 操作
b. GLenum dpfail 用于Stencil test 通过,但是 depth test 失败时执行的 stencil 操作
c. GLenum dppass 用于Stencil test 和 depth test 都通过时执行的 stencil 操作
| 枚举值 | 操作名称 | 说明 |
|---|---|---|
GL_KEEP |
保持 | stencil 值保持不变,不修改 buffer |
GL_ZERO |
清零 | 将 stencil 值设置为 0 |
GL_REPLACE |
替换 | 使用 glStencilFunc() 中设置的 reference value 替换当前 stencil 值 |
GL_INCR |
饱和递增 | stencil 加 1,达到最大值后保持最大值 |
GL_INCR_WRAP |
循环递增 | stencil 加 1,超过最大值后回绕到 0 |
GL_DECR |
饱和递减 | stencil 减 1,减到 0 后保持 |
GL_DECR_WRAP |
循环递减 | stencil 减 1,低于 0 后回绕到最大值 |
GL_INVERT |
位翻转 | 对 stencil 所有 bit 进行 XOR 翻转 |
2.3 glStencilMask
void glStencilMask(GLuint mask);
该函数用于控制 stencil buffer 中哪些 bit 可以被写入。它不会影响 stencil test 的比较,也不会改变当前 stencil 值,只决定后续 glStencilOp() 操作修改 stencil buffer 时哪些位生效。
参数:
a. GLuint mask 指定 stencil buffer 的写入掩码(write mask)
只有 mask 中对应 bit 为 1 的位置,才允许被 glStencilOp() 修改
常用mask设置:
| 设置 | 含义 | 用途 |
|---|---|---|
glStencilMask(0xFF) |
全部 bit 可写 | 默认 stencil 写入 |
glStencilMask(0x00) |
禁止写入任何 bit | 只测试 stencil,不修改 |
glStencilMask(0x01) |
只写最低 bit | Odd-Even parity |
glStencilMask(0x0F) |
写低 4 bit | 多状态 stencil 管理 |
三、Odd-Even Stencil 剖切封盖
封盖面(Cap)渲染采用 OpenGL 模板缓冲区(Stencil Buffer)+ 奇偶性判断 + 屏幕空间 Quad 的方式实现。
模板缓冲区是与颜色、深度缓冲区对应的逐像素整数缓冲区,用于记录每个像素的状态。
屏幕空间 Quad的位置计算逻辑如下。首先根据当前视图的 inverseViewProjection 矩阵,将 NDC 空间 [-1,1]^3 的视锥体 8 个角点反变换到世界空间,并计算截面平面与视锥体 12 条边的交点,再根据平面法向建立正交的 axisU/axisV 局部坐标系,将交点投影到 UV 平面并计算 minU/maxU、minV/maxV,从而得到能够覆盖当前截面的最小包围四边形,并通过 u_capCenter、u_capAxisU、u_capAxisV、u_capHalfExtent 等 Uniform 传递给 Cap Quad Shader。因此,实际绘制时并不需要生成真实的截面多边形,而是渲染一个覆盖截面区域的屏幕空间 Quad,再由 Shader 根据当前像素位置以及 u_capPlaneIndex、u_capPlaneEpsilon 等参数判断该像素是否位于截面平面及其有效范围内。
在具体的模板处理阶段,首先通过 glClearStencil/glClear 将模板缓冲区清零,关闭颜色和深度写入,并使用 glStencilFunc(GL_ALWAYS, 0, 0x01) 和 glStencilMask(0x01) 只针对模板值的最低 1 位进行操作,再通过 glStencilOp(GL_INVERT, GL_INVERT, GL_INVERT) 对几何体覆盖区域执行按位翻转。所谓奇偶性,就是根据一个像素被几何体覆盖的次数判断其为奇数次还是偶数次:例如初始值为 0,第一次覆盖 0 → 1,第二次覆盖 1 → 0,第三次覆盖 0 → 1,因此最低位为 1 表示奇数次覆盖,即截面有效区域。随后使用 glStencilFunc(GL_EQUAL, 0x01, 0x01),仅允许模板最低位为 1 的像素通过模板测试,并绘制前面计算得到的 Cap Quad,使屏幕空间四边形只在实际截面区域产生颜色,从而完成封盖。
对于 X/Y/Z 三个截面平面,采用逐平面独立处理的方式:循环 planeIndex = 0~2,每个平面首先调用 SetupCciCapPlaneUniforms() 计算自身的中心点、U/V 轴和包围范围,然后通过 ClearStencilMasked(0, 0xFF) 将模板缓冲区重新清零,再执行“几何体模板标记 → Cap Quad 封盖绘制”的流程。因此三个平面之间不共享模板状态,每个平面的模板值均从 0 开始并在完成封盖后清零;但三个 Cap Quad 最终都绘制到同一个颜色缓冲区中,因此多个封盖面可以通过 Blending 叠加显示。如果开启干涉检测,则封盖阶段通过 glStencilOp(..., GL_INCR) 将通过模板测试的区域从 1 → 2,再利用模板测试识别多个截面产生的重叠区域并绘制干涉颜色。
整体上,该方法将视锥体与截面的几何计算、屏幕空间 Quad 的范围裁剪、Stencil 奇偶性判断以及多平面独立渲染结合起来,在不显式生成截面轮廓和进行 CPU 三角剖分的情况下完成高效的 CCI 封盖渲染。


浙公网安备 33010602011771号