Möller-Trumbore算法射线追踪三角形单维度相交像素缺失问题
排查Möller-Trumbore三角形相交检测的异常渲染问题
从你描述的症状(部分三角形像素漏渲染、仅特定维度出现、与相机/射线方向强相关)以及提供的代码来看,问题大概率出在射线方向的处理、浮点精度阈值或者背面/正面的判断逻辑上。下面是具体的排查步骤和修复建议:
1. 优先检查射线方向的归一化
你提到更换射线计算方式后缺失像素减少,这是一个关键线索——未归一化的射线方向会彻底打乱Möller-Trumbore算法的精度:
- 算法中的叉积
pvec = vec_cross(dir, v2)和后续的点积计算,结果会随dir的长度缩放,导致s.delta的阈值判断(0.00001)在不同尺度下失效。比如dir过长时,原本应该被判定为平行的情况没触发;过短时又会误判平行,跳过相交检测。 - 未归一化的
dir还会影响t1(射线到三角形的距离)的计算精度,导致原本应该更近的三角形被错误判定为更远,显示后方物体。
修复建议:在调用这个相交检测函数之前,强制归一化射线方向:
// 假设vec_normalize是你的向量归一化函数 dir = vec_normalize(dir);
2. 调整浮点精度阈值的合理性
你的代码中用了固定的0.00001作为阈值,但这个值可能和你的场景尺度不匹配,同时没有考虑浮点计算的误差:
fabs(s.delta) < 0.00001:如果场景单位很小(比如厘米级),这个阈值太大,会误判很多非平行的三角形为平行;如果场景很大,阈值又可能太小,导致精度问题。s.a < 0 || s.a > 1和s.b < 0 || s.a + s.b > 1:浮点计算的误差可能导致合法的a/b值略小于0或略大于1,被错误过滤,比如a为-1e-8时,本该被保留却被返回了。
修复建议:定义一个可配置的全局epsilon,并且给判断加上误差容忍:
#define RAYTRACER_EPSILON 1e-6 // 替换原有的阈值判断 if (fabs(s.delta) < RAYTRACER_EPSILON) return; // ... if (s.a < -RAYTRACER_EPSILON || s.a > 1.0 + RAYTRACER_EPSILON) return; // ... if (s.b < -RAYTRACER_EPSILON || (s.a + s.b) > 1.0 + RAYTRACER_EPSILON) return; // ... if (s.t1 < RAYTRACER_EPSILON) return; // 避免射线原点在三角形表面的自相交
3. 检查背面剔除的逻辑一致性
你提到转动相机看三角形背面时,问题出在另一侧,这说明三角形的正面定义和射线方向的匹配度有问题:
- Möller-Trumbore算法中,
s.delta的符号对应射线入射的面:如果你的三角形顶点是按逆时针顺序定义的(面向相机为正面),那么s.delta > 0表示射线从正面入射,s.delta < 0表示从背面入射。 - 你的代码目前只跳过了平行的情况(
fabs(delta) < 阈值),没有处理背面的情况。如果你的渲染管线默认只渲染正面,或者射线方向的定义和顶点顺序相反,就会导致某些角度下的相交被错误处理。
修复建议:
- 如果需要双面渲染:保持现有逻辑,但确保所有三角形的顶点顺序一致。
- 如果只需要正面渲染:在判断平行后,加上背面过滤:
if (s.delta < RAYTRACER_EPSILON) return; // 跳过背面入射的情况
4. 验证射线原点的计算正确性
问题与相机位置相关,还要确认射线原点ori的计算是否正确:
- 检查相机的世界坐标转换是否正确,有没有把局部坐标和世界坐标搞混。
- 确保
ori是相机的实际3D位置,而不是屏幕空间的2D坐标(很多初学者会在这里出错)。
调试技巧
如果以上修复还没解决问题,可以用以下方法定位具体错误:
- 打印出现问题的像素对应的
dir、ori和三角形顶点,手动计算Möller-Trumbore的每一步,看哪个判断条件错误地触发了返回。 - 暂时注释掉所有的阈值判断(除了
s.t1 < 0),看看三角形是否能完整渲染——如果能,说明是阈值的问题;如果还是有缺失,说明是射线dir/ori的计算错误。
内容的提问来源于stack exchange,提问作者UnDesSix




