You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
434 lines
16 KiB
434 lines
16 KiB
#ifndef _UNIFIEDRAYTRACING_RAYQUERYSOFTWARE_HLSL_
|
|
#define _UNIFIEDRAYTRACING_RAYQUERYSOFTWARE_HLSL_
|
|
|
|
#include "Packages/com.unity.render-pipelines.core/Runtime/UnifiedRayTracing/Bindings.hlsl"
|
|
|
|
#pragma warning(disable : 4008) // fast_intersect_bbox is designed to handle inf and nans, so we disable the `floating point division by zero` warning
|
|
|
|
#ifndef UNIFIED_RT_LDS_STACK_SIZE
|
|
#define UNIFIED_RT_LDS_STACK_SIZE 8
|
|
#endif
|
|
|
|
#ifndef UNIFIED_RT_STACK_SIZE
|
|
#define UNIFIED_RT_STACK_SIZE 64
|
|
#endif
|
|
|
|
RWStructuredBuffer<uint> _UnifiedRT_Stack;
|
|
|
|
namespace UnifiedRT {
|
|
|
|
groupshared uint lds_stack[UNIFIED_RT_LDS_STACK_SIZE * (UNIFIED_RT_GROUP_SIZE_X*UNIFIED_RT_GROUP_SIZE_Y*UNIFIED_RT_GROUP_SIZE_Z)];
|
|
|
|
static const uint kTopLevelSentinel = 0xFFFFFFFE;
|
|
static const uint kCommittedNothing = 0;
|
|
static const uint kCommittedTriangleHit = 1;
|
|
|
|
static const uint kNonOpaqueInstanceBit = (1 << 29);
|
|
static const uint kCandidateHitCommittedBit = (1 << 28);
|
|
|
|
struct RayTraversalStack
|
|
{
|
|
void Init(uint globalThreadIndex, uint localThreadIndex)
|
|
{
|
|
sbegin = UNIFIED_RT_STACK_SIZE * globalThreadIndex;
|
|
sptr = sbegin;
|
|
lds_sbegin = localThreadIndex * UNIFIED_RT_LDS_STACK_SIZE;
|
|
lds_sptr = lds_sbegin;
|
|
lds_stack[lds_sptr++] = INVALID_NODE;
|
|
}
|
|
|
|
void Push(uint addr)
|
|
{
|
|
if (lds_sptr - lds_sbegin >= UNIFIED_RT_LDS_STACK_SIZE)
|
|
{
|
|
for (int i = 1; i < UNIFIED_RT_LDS_STACK_SIZE; ++i)
|
|
{
|
|
_UnifiedRT_Stack[sptr + i] = lds_stack[lds_sbegin + i];
|
|
}
|
|
|
|
sptr += UNIFIED_RT_LDS_STACK_SIZE;
|
|
lds_sptr = lds_sbegin + 1;
|
|
}
|
|
lds_stack[lds_sptr++] = addr;
|
|
}
|
|
|
|
uint Pop()
|
|
{
|
|
uint addr = lds_stack[--lds_sptr];
|
|
if (addr == INVALID_NODE && sptr > sbegin)
|
|
{
|
|
sptr -= UNIFIED_RT_LDS_STACK_SIZE;
|
|
for (int i = 1; i < UNIFIED_RT_LDS_STACK_SIZE; ++i)
|
|
{
|
|
lds_stack[lds_sbegin + i] = _UnifiedRT_Stack[sptr + i];
|
|
}
|
|
lds_sptr = lds_sbegin + UNIFIED_RT_LDS_STACK_SIZE;
|
|
addr = lds_stack[--lds_sptr];
|
|
}
|
|
return addr;
|
|
}
|
|
|
|
uint lds_sptr;
|
|
uint lds_sbegin;
|
|
uint sptr;
|
|
uint sbegin;
|
|
};
|
|
|
|
uint GetCullMode(uint rayFlags)
|
|
{
|
|
uint cullMode = 0;
|
|
cullMode |= (rayFlags & kRayFlagCullBackFacingTriangles) << 27;
|
|
|
|
if ((rayFlags & (kRayFlagCullFrontFacingTriangles | kRayFlagCullBackFacingTriangles)) == 0)
|
|
cullMode |= (1 << 30);
|
|
|
|
return cullMode;
|
|
}
|
|
|
|
struct ClosestHit
|
|
{
|
|
uint instanceIndex;
|
|
uint isFrontFace_primitiveIndex;
|
|
float2 uv;
|
|
};
|
|
|
|
struct CandidateHit
|
|
{
|
|
uint isFrontFace_primitiveIndex;
|
|
float2 uv;
|
|
float hitT;
|
|
};
|
|
|
|
struct CurrentInstance
|
|
{
|
|
uint cullMode_isNonOpaque_candidateCommitted;
|
|
uint bvhOffset;
|
|
int bvhLeavesOffset;
|
|
int vertexOffset;
|
|
};
|
|
|
|
CurrentInstance GetCurrentInstance(InstanceInfo instanceInfo, uint rayCullMode, bool transparentInstance)
|
|
{
|
|
CurrentInstance currentInstance;
|
|
currentInstance.bvhOffset = instanceInfo.blas_offset;
|
|
currentInstance.vertexOffset = instanceInfo.vertex_offset;
|
|
currentInstance.bvhLeavesOffset = instanceInfo.blas_leaves_offset;
|
|
|
|
uint instanceCullMode = (rayCullMode ^ instanceInfo.invert_triangle_culling) | instanceInfo.disable_triangle_culling;
|
|
|
|
currentInstance.cullMode_isNonOpaque_candidateCommitted = instanceCullMode;
|
|
if (transparentInstance)
|
|
currentInstance.cullMode_isNonOpaque_candidateCommitted |= kNonOpaqueInstanceBit;
|
|
|
|
return currentInstance;
|
|
}
|
|
|
|
bool IsInstanceNonOpaque(RayTracingAccelStruct accelStruct, uint instanceIndex, uint rayFlags)
|
|
{
|
|
bool isTransparent = !accelStruct.instance_infos[instanceIndex].is_opaque;
|
|
if (rayFlags & kRayFlagForceNonOpaque)
|
|
isTransparent = true;
|
|
if (rayFlags & kRayFlagForceOpaque)
|
|
isTransparent = false;
|
|
|
|
return isTransparent;
|
|
}
|
|
|
|
float4x3 ConvertToFloat4x3(Transform t)
|
|
{
|
|
float4x3 m;
|
|
m[0] = float3(t.row0.x, t.row1.x, t.row2.x);
|
|
m[1] = float3(t.row0.y, t.row1.y, t.row2.y);
|
|
m[2] = float3(t.row0.z, t.row1.z, t.row2.z);
|
|
m[3] = float3(t.row0.w, t.row1.w, t.row2.w);
|
|
return m;
|
|
}
|
|
|
|
float3x4 ConvertToFloat3x4(Transform t)
|
|
{
|
|
float3x4 m;
|
|
m[0] = float4(t.row0.x, t.row0.y, t.row0.z, t.row0.w);
|
|
m[1] = float4(t.row1.x, t.row1.y, t.row1.z, t.row1.w);
|
|
m[2] = float4(t.row2.x, t.row2.y, t.row2.z, t.row2.w);
|
|
return m;
|
|
}
|
|
|
|
bool IntersectLeafTriangle(
|
|
StructuredBuffer<uint> vertexBuffer, int vertexOffset, uint4 leafNode, uint triangleCullMode,
|
|
float3 rayDirection, float3 rayOrigin, float tmin, float tmax,
|
|
out CandidateHit hitInfo)
|
|
{
|
|
hitInfo = (CandidateHit)0;
|
|
uint3 triangleIndices = leafNode.xyz;
|
|
float3 v1 = FetchVertex(vertexBuffer, 3, vertexOffset, triangleIndices.x);
|
|
float3 v2 = FetchVertex(vertexBuffer, 3, vertexOffset, triangleIndices.y);
|
|
float3 v3 = FetchVertex(vertexBuffer, 3, vertexOffset, triangleIndices.z);
|
|
|
|
// Determine edge vectors for clockwise triangle vertices
|
|
const float3 e1 = v2 - v1;
|
|
const float3 e2 = v3 - v1;
|
|
|
|
const float3 s1 = cross(rayDirection, e2);
|
|
const float determinant = dot(s1, e1);
|
|
const float invd = rcp(determinant);
|
|
|
|
const float3 d = rayOrigin - v1;
|
|
const float u = dot(d, s1) * invd;
|
|
|
|
const uint detSignBit = asuint(determinant) & 0x80000000;
|
|
// Barycentric coordinate U is outside range or triangle front/backface culled
|
|
bool hit = false;
|
|
if (!((u < 0.f) || (u > 1.f) || determinant == 0.0f || detSignBit == triangleCullMode))
|
|
{
|
|
const float3 s2 = cross(d, e1);
|
|
const float v = dot(rayDirection, s2) * invd;
|
|
// Barycentric coordinate V is outside range
|
|
if (!((v < 0.f) || (u + v > 1.f)))
|
|
{
|
|
// Check parametric distance
|
|
const float t = dot(e2, s2) * invd;
|
|
if (!(t < tmin || t > tmax))
|
|
{
|
|
// Accept hit
|
|
hitInfo.isFrontFace_primitiveIndex = (detSignBit ^ 0x80000000) | leafNode.w;
|
|
hitInfo.uv = float2(u, v);
|
|
hitInfo.hitT = t;
|
|
hit = true;
|
|
}
|
|
}
|
|
}
|
|
return hit;
|
|
}
|
|
|
|
struct RayQuery
|
|
{
|
|
void Init(uint globalThreadIndex, uint localThreadIndex, RayTracingAccelStruct accelStruct_, uint rayFlags_, uint instanceMask_, Ray ray_)
|
|
{
|
|
accelStruct = accelStruct_;
|
|
rayCullMode_Mask = instanceMask_ & 0x000000FF;
|
|
rayCullMode_Mask |= GetCullMode(rayFlags_);
|
|
rayFlags = rayFlags_;
|
|
rayOriginInWorld = ray_.origin;
|
|
rayDirectionInWorld = ray_.direction;
|
|
tMin = ray_.tMin;
|
|
tMax = ray_.tMax;
|
|
rayOrigin = ray_.origin;
|
|
rayDirection = ray_.direction;
|
|
rayInvDir = 1.0 / ray_.direction;
|
|
|
|
stack.Init(globalThreadIndex, localThreadIndex);
|
|
|
|
candidateHit = (CandidateHit)0;
|
|
closestHit = (ClosestHit)0;
|
|
closestHit.instanceIndex = INVALID_NODE;
|
|
currentInstance = (CurrentInstance)0;
|
|
|
|
currentNodeIndex = accelStruct.bvh[0].parent; // get root node index from bvh header
|
|
currentInstanceIndex = INVALID_NODE;
|
|
currentLeafTriangleIndex = -1;
|
|
}
|
|
|
|
bool Proceed()
|
|
{
|
|
bool transparencyEnabled = !(rayFlags & (UnifiedRT::kRayFlagForceOpaque | UnifiedRT::kRayFlagCullNonOpaque));
|
|
|
|
if ((currentInstance.cullMode_isNonOpaque_candidateCommitted & kCandidateHitCommittedBit) && transparencyEnabled)
|
|
{
|
|
currentInstance.cullMode_isNonOpaque_candidateCommitted &= ~kCandidateHitCommittedBit;
|
|
|
|
_CommitCandidateHit();
|
|
|
|
if (rayFlags & kRayFlagAcceptFirstHitAndEndSearch)
|
|
{
|
|
Abort();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
currentLeafTriangleIndex++;
|
|
|
|
while (currentNodeIndex != INVALID_NODE)
|
|
{
|
|
bool isLeaf = IS_LEAF_NODE(currentNodeIndex);
|
|
bool skipPopStack = false;
|
|
|
|
// internal node (Bounding boxes)
|
|
if (!isLeaf)
|
|
{
|
|
BvhNode node;
|
|
if (currentInstanceIndex == INVALID_NODE)
|
|
node = accelStruct.bvh[1 + currentNodeIndex];
|
|
else
|
|
node = accelStruct.bottom_bvhs[currentInstance.bvhOffset + 1 + currentNodeIndex];
|
|
|
|
uint2 result = IntersectInternalNode(node, rayInvDir, rayOrigin, tMin, tMax);
|
|
if (result.y != INVALID_NODE)
|
|
{
|
|
stack.Push(result.y);
|
|
}
|
|
|
|
if (result.x != INVALID_NODE)
|
|
{
|
|
currentNodeIndex = result.x;
|
|
skipPopStack = true;
|
|
}
|
|
}
|
|
// top-level leaf: adjust ray respecively to transforms
|
|
else if (currentInstanceIndex == INVALID_NODE)
|
|
{
|
|
uint currentInstanceIndex_ = GET_LEAF_NODE_FIRST_PRIM(currentNodeIndex);
|
|
uint instanceMask = accelStruct.instance_infos[currentInstanceIndex_].instance_mask;
|
|
bool instanceIsTransparent = IsInstanceNonOpaque(accelStruct, currentInstanceIndex_, rayFlags);
|
|
|
|
bool instanceCulled = (instanceMask & rayCullMode_Mask & 0x000000FF) == 0 ||
|
|
(instanceIsTransparent && (rayFlags & kRayFlagCullNonOpaque)) ||
|
|
(!instanceIsTransparent && (rayFlags & kRayFlagCullOpaque));
|
|
|
|
if (!instanceCulled)
|
|
{
|
|
// push sentinel
|
|
stack.Push(kTopLevelSentinel);
|
|
|
|
currentInstanceIndex = currentInstanceIndex_;
|
|
currentInstance = GetCurrentInstance(accelStruct.instance_infos[currentInstanceIndex_], rayCullMode_Mask & 0xC0000000, instanceIsTransparent);
|
|
currentNodeIndex = accelStruct.bottom_bvhs[currentInstance.bvhOffset + 0].parent;
|
|
|
|
// transform ray into Bottom level space
|
|
Transform transform = accelStruct.instance_infos[currentInstanceIndex].world_to_local_transform;
|
|
rayOrigin = TransformPointT(rayOriginInWorld, transform);
|
|
rayDirection = TransformDirection(rayDirectionInWorld, transform);
|
|
rayInvDir = 1.0 / rayDirection;
|
|
|
|
skipPopStack = true;
|
|
}
|
|
}
|
|
// bottom-level leaf (triangles)
|
|
else
|
|
{
|
|
int firstTriangle = GET_LEAF_NODE_FIRST_PRIM(currentNodeIndex);
|
|
int nodeTriangleCount = GET_LEAF_NODE_PRIM_COUNT(currentNodeIndex);
|
|
|
|
while (currentLeafTriangleIndex < nodeTriangleCount)
|
|
{
|
|
uint4 leafNode = accelStruct.bottom_bvh_leaves[currentInstance.bvhLeavesOffset + (firstTriangle + currentLeafTriangleIndex)];
|
|
uint triangleCullMode = (currentInstance.cullMode_isNonOpaque_candidateCommitted & 0xC0000000);
|
|
bool nonOpaqueInstance = (currentInstance.cullMode_isNonOpaque_candidateCommitted & kNonOpaqueInstanceBit);
|
|
|
|
if (IntersectLeafTriangle(
|
|
accelStruct.vertexBuffer, currentInstance.vertexOffset, leafNode, triangleCullMode,
|
|
rayDirection, rayOrigin, tMin, tMax,
|
|
candidateHit))
|
|
{
|
|
if (nonOpaqueInstance && transparencyEnabled)
|
|
return true;
|
|
|
|
_CommitCandidateHit();
|
|
|
|
if (rayFlags & kRayFlagAcceptFirstHitAndEndSearch)
|
|
{
|
|
Abort();
|
|
return false;
|
|
}
|
|
}
|
|
|
|
currentLeafTriangleIndex++;
|
|
}
|
|
|
|
currentLeafTriangleIndex = 0;
|
|
}
|
|
|
|
if (skipPopStack)
|
|
continue;
|
|
|
|
currentNodeIndex = stack.Pop();
|
|
|
|
// check if need to go back to the top-level
|
|
if (currentNodeIndex == kTopLevelSentinel)
|
|
{
|
|
currentNodeIndex = stack.Pop();
|
|
currentInstanceIndex = INVALID_NODE;
|
|
|
|
// restore ray
|
|
rayOrigin = rayOriginInWorld;
|
|
rayDirection= rayDirectionInWorld;
|
|
rayInvDir = 1.0 / rayDirectionInWorld;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void Abort()
|
|
{
|
|
currentNodeIndex = INVALID_NODE;
|
|
}
|
|
|
|
void CommitNonOpaqueTriangleHit()
|
|
{
|
|
currentInstance.cullMode_isNonOpaque_candidateCommitted |= kCandidateHitCommittedBit;
|
|
}
|
|
|
|
void _CommitCandidateHit()
|
|
{
|
|
closestHit.instanceIndex = currentInstanceIndex;
|
|
closestHit.isFrontFace_primitiveIndex = candidateHit.isFrontFace_primitiveIndex;
|
|
closestHit.uv = candidateHit.uv;
|
|
tMax = candidateHit.hitT;
|
|
}
|
|
|
|
uint RayFlags() { return rayFlags; }
|
|
float3 WorldRayOrigin() { return rayOriginInWorld; }
|
|
float3 WorldRayDirection() { return rayDirectionInWorld; }
|
|
float RayTMin() { return tMin; }
|
|
|
|
float CandidateTriangleRayT() { return candidateHit.hitT; }
|
|
uint CandidateInstanceID() { return accelStruct.instance_infos[currentInstanceIndex].user_instance_id; }
|
|
uint CandidatePrimitiveIndex() { return candidateHit.isFrontFace_primitiveIndex & 0x7FFFFFFF; }
|
|
float2 CandidateTriangleBarycentrics() { return candidateHit.uv; }
|
|
bool CandidateTriangleFrontFace() { return candidateHit.isFrontFace_primitiveIndex & 0x80000000; }
|
|
float3 CandidateLocalRayOrigin() { return rayOrigin; }
|
|
float3 CandidateLocalRayDirection() { return rayDirection; }
|
|
float3x4 CandidateWorldToLocal3x4() { return ConvertToFloat3x4(accelStruct.instance_infos[currentInstanceIndex].world_to_local_transform); }
|
|
float4x3 CandidateWorldToLocal4x3() { return ConvertToFloat4x3(accelStruct.instance_infos[currentInstanceIndex].world_to_local_transform); }
|
|
float3x4 CandidateLocalToWorld3x4() { return ConvertToFloat3x4(accelStruct.instance_infos[currentInstanceIndex].local_to_world_transform); }
|
|
float4x3 CandidateLocalToWorld4x3() { return ConvertToFloat4x3(accelStruct.instance_infos[currentInstanceIndex].local_to_world_transform); }
|
|
|
|
uint CommittedStatus() { return closestHit.instanceIndex == -1 ? kCommittedNothing : kCommittedTriangleHit; }
|
|
float CommittedRayT() { return tMax; }
|
|
uint CommittedInstanceID() { return accelStruct.instance_infos[closestHit.instanceIndex].user_instance_id; }
|
|
uint CommittedPrimitiveIndex() { return closestHit.isFrontFace_primitiveIndex & 0x7FFFFFFF; }
|
|
float2 CommittedTriangleBarycentrics() { return closestHit.uv; }
|
|
bool CommittedTriangleFrontFace() { return closestHit.isFrontFace_primitiveIndex & 0x80000000; }
|
|
float3 CommittedLocalRayOrigin() { return TransformPointT(rayOriginInWorld, accelStruct.instance_infos[closestHit.instanceIndex].world_to_local_transform); }
|
|
float3 CommittedLocalRayDirection() { return TransformDirection(rayDirectionInWorld, accelStruct.instance_infos[closestHit.instanceIndex].world_to_local_transform); }
|
|
float3x4 CommittedWorldToLocal3x4() { return ConvertToFloat3x4(accelStruct.instance_infos[closestHit.instanceIndex].world_to_local_transform); }
|
|
float4x3 CommittedWorldToLocal4x3() { return ConvertToFloat4x3(accelStruct.instance_infos[closestHit.instanceIndex].world_to_local_transform); }
|
|
float3x4 CommittedLocalToWorld3x4() { return ConvertToFloat3x4(accelStruct.instance_infos[closestHit.instanceIndex].local_to_world_transform); }
|
|
float4x3 CommittedLocalToWorld4x3() { return ConvertToFloat4x3(accelStruct.instance_infos[closestHit.instanceIndex].local_to_world_transform); }
|
|
|
|
// read only data
|
|
uint rayFlags;
|
|
uint rayCullMode_Mask;
|
|
float3 rayOriginInWorld;
|
|
float3 rayDirectionInWorld;
|
|
float tMin;
|
|
RayTracingAccelStruct accelStruct;
|
|
|
|
// traversal state
|
|
float tMax;
|
|
float3 rayOrigin;
|
|
float3 rayDirection;
|
|
float3 rayInvDir;
|
|
ClosestHit closestHit;
|
|
CandidateHit candidateHit;
|
|
RayTraversalStack stack;
|
|
CurrentInstance currentInstance;
|
|
uint currentNodeIndex;
|
|
uint currentInstanceIndex;
|
|
int currentLeafTriangleIndex;
|
|
};
|
|
|
|
|
|
} // namespace UnifiedRT
|
|
|
|
#endif // _UNIFIEDRAYTRACING_RAYQUERYSOFTWARE_HLSL_
|