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 封盖渲染。

image-20260817160520945

posted @ 2026-08-17 15:55  王小于的啦  阅读(7)  评论(0)    收藏  举报