在基于k-d树加速结构的光线跟踪器的开发过程中,我遇到了一个问题。有时在光线/三角形交叉测试期间(使用Möller-Trumbore算法),两个相邻的三角形射线都会错过它们。在跨视轴的慢动作期间,它导致暗三角形之间的亮点闪烁(或反之亦然,对于任何对比度对)。特别是对于高多边形模型。
假设,光线/三角交叉算法是整个光线跟踪器的最热点(这个断言是硬全面全面基准测试的结果)。 Möller-Trumbore是最快的GPU算法(BTW我使用GPU,而不是CPU)。由于SAH(表面区域启发式),整个帧时间的大约一半被光线/三角交叉测试消耗。
为了避免闪烁,我只是在对于中心坐标计算和比较步骤的光线/三角形交叉测试期间使用略微加宽的三角形。这些是在运行时执行的。
要正确加宽三角形,请执行以下操作:每个计算u
,v
或w
乘以三角形对应高度的长度,并与-EPSILON
进行比较或1.0 + EPSILON
以物理单位测量(如果高度以米为单位,则为0.0001米)。
要计算三角形的所有三个高度,我需要计算它的平方,即交叉积的长度:对于ABC三角形,AB = B - A
,AC = C -
A
,length(cross(AB, AC))
和每个的长度它的一面:length(AB)
,length(AC)
,BC = AC - AB
,length(BC)
。其中length(vec)
为sqrt(dot(vec, vec))
的地方(提到估算计算的复杂程度)。当然可以很容易地避免sqrt
的计算。但是这个扩展步骤仍占用整个帧时间的大约10%。所以在纠正和运行速度之间需要权衡。
现在我记得,光栅化器根本没有EPSILON
这样的参数。它的正确性不依赖于舍入误差问题。
如何安排硬件光栅化器?为什么它总是给出正确的结果?
我可以猜想,在相邻三角形的遍历过程中,光栅化器以统一的方式进行计算,误差变得片面,从而在两侧相互补偿。
代码示例(HLSL),其中牺牲了相关性以支持运行时速度:
bool KdTriIntersectCheck(TKdTree kdTree,
in TRay ray, in float tMin, in float tMax,
inout THit hit, in uint face)
{ // Möller–Trumbore
float3 A = KdGetVertex(kdTree, hit.triangleIndex, 0);
float3 AB = KdGetVertex(kdTree, hit.triangleIndex, 1) - A;
float3 AC = KdGetVertex(kdTree, hit.triangleIndex, 2) - A;
float3 P = cross(ray.direction, AC);
float denominator = dot(AB, P);
if (denominator <= 0.0) {
return false;
}
float3 Q = ray.source - A;
hit.uv.x = dot(Q, P);
if ((hit.uv.x < 0.0) || (hit.uv.x > denominator)) {
return false;
}
float3 R = cross(Q, AB);
hit.uv.y = dot(ray.direction, R);
if ((hit.uv.y < 0.0) || (hit.uv.x + hit.uv.y > denominator)) {
return false;
}
hit.uv /= denominator;
hit.distance = dot(AC, R) / denominator;
hit.isFront = true;
return (hit.distance >= tMin - EPSILON) && (hit.distance <= tMax + EPSILON);
}