From 7fc448a8a946e1f099796278dd8b93f2bf477d39 Mon Sep 17 00:00:00 2001 From: Stefan Werner Date: Thu, 5 Mar 2026 09:25:03 +0100 Subject: [PATCH] Fixed robust node intersection for parallel rays: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem The original code computed tNear and tFar slab distances in a single expression: (bound - org) * rdir. When a ray direction component is exactly 0.0f, rdir becomes inf, and (bound - org) * inf can produce NaN (specifically when bound == org, yielding 0 * inf = NaN). This caused rays parallel to a slab to either incorrectly miss or hit BVH nodes. What changed Separated loads from arithmetic — The AABB bound values (lowerX/Y/Z, upperX/Y/Z) are now loaded into named variables first, so they can be reused for the parallel-ray check. Initial slab distances computed normally — tNearX0..tFarZ0 are computed as before, but stored as intermediate values. Parallel-ray detection — Three boolean masks (parX, parY, parZ) check if each ray direction component is exactly zero. Outside-slab detection — For each parallel axis, outX/Y/Z checks whether the ray origin lies outside the bounding box on that axis. A ray parallel to a slab and outside it can never intersect. Infinity substitution via select — For parallel axes, tNear is forced to -inf and tFar to +inf, effectively making that slab a no-op in the max/min reduction (the slab is "infinitely wide"). This avoids NaN propagation. Final mask includes outX|outY|outZ — Even though the slab distances are now clean, nodes are explicitly rejected if the ray is parallel to and outside the box on any axis: (tNear <= tFar) & !(outX | outY | outZ). In summary This is a correctness fix for the robust BVH node intersection when rays are axis-aligned (direction component = 0). It prevents NaN results from 0 * inf and properly handles the case where a parallel ray is outside the bounding box slab. --- kernels/bvh/node_intersector1.h | 39 +++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 7 deletions(-) diff --git a/kernels/bvh/node_intersector1.h b/kernels/bvh/node_intersector1.h index 17641fa888..80749892ac 100644 --- a/kernels/bvh/node_intersector1.h +++ b/kernels/bvh/node_intersector1.h @@ -539,15 +539,40 @@ namespace embree template __forceinline size_t intersectNodeRobust(const typename BVHN::AABBNode* node, const TravRay& ray, vfloat& dist) { - const vfloat tNearX = (vfloat::load((float*)((const char*)&node->lower_x+ray.nearX)) - ray.org.x) * ray.rdir_near.x; - const vfloat tNearY = (vfloat::load((float*)((const char*)&node->lower_x+ray.nearY)) - ray.org.y) * ray.rdir_near.y; - const vfloat tNearZ = (vfloat::load((float*)((const char*)&node->lower_x+ray.nearZ)) - ray.org.z) * ray.rdir_near.z; - const vfloat tFarX = (vfloat::load((float*)((const char*)&node->lower_x+ray.farX )) - ray.org.x) * ray.rdir_far.x; - const vfloat tFarY = (vfloat::load((float*)((const char*)&node->lower_x+ray.farY )) - ray.org.y) * ray.rdir_far.y; - const vfloat tFarZ = (vfloat::load((float*)((const char*)&node->lower_x+ray.farZ )) - ray.org.z) * ray.rdir_far.z; + const vfloat lowerX = vfloat::load((float*)((const char*)&node->lower_x+ray.nearX)); + const vfloat lowerY = vfloat::load((float*)((const char*)&node->lower_x+ray.nearY)); + const vfloat lowerZ = vfloat::load((float*)((const char*)&node->lower_x+ray.nearZ)); + const vfloat upperX = vfloat::load((float*)((const char*)&node->lower_x+ray.farX )); + const vfloat upperY = vfloat::load((float*)((const char*)&node->lower_x+ray.farY )); + const vfloat upperZ = vfloat::load((float*)((const char*)&node->lower_x+ray.farZ )); + + const vfloat tNearX0 = (lowerX - ray.org.x) * ray.rdir_near.x; + const vfloat tNearY0 = (lowerY - ray.org.y) * ray.rdir_near.y; + const vfloat tNearZ0 = (lowerZ - ray.org.z) * ray.rdir_near.z; + const vfloat tFarX0 = (upperX - ray.org.x) * ray.rdir_far.x; + const vfloat tFarY0 = (upperY - ray.org.y) * ray.rdir_far.y; + const vfloat tFarZ0 = (upperZ - ray.org.z) * ray.rdir_far.z; + + const vbool parX = ray.dir.x == vfloat(0.0f); + const vbool parY = ray.dir.y == vfloat(0.0f); + const vbool parZ = ray.dir.z == vfloat(0.0f); + + const vbool outX = parX & ((ray.org.x < lowerX) | (ray.org.x > upperX)); + const vbool outY = parY & ((ray.org.y < lowerY) | (ray.org.y > upperY)); + const vbool outZ = parZ & ((ray.org.z < lowerZ) | (ray.org.z > upperZ)); + + const vfloat pinf = std::numeric_limits::infinity(); + const vfloat ninf = -pinf; + const vfloat tNearX = select(parX, ninf, tNearX0); + const vfloat tNearY = select(parY, ninf, tNearY0); + const vfloat tNearZ = select(parZ, ninf, tNearZ0); + const vfloat tFarX = select(parX, pinf, tFarX0); + const vfloat tFarY = select(parY, pinf, tFarY0); + const vfloat tFarZ = select(parZ, pinf, tFarZ0); + const vfloat tNear = max(tNearX,tNearY,tNearZ,ray.tnear); const vfloat tFar = min(tFarX ,tFarY ,tFarZ ,ray.tfar); - const vbool vmask = tNear <= tFar; + const vbool vmask = (tNear <= tFar) & !(outX | outY | outZ); const size_t mask = movemask(vmask); dist = tNear; return mask;