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  王小于的啦  阅读(28)  评论(0)    收藏  举报