|
|
|
|
|
|
#endif |
|
|
|
}; |
|
|
|
|
|
|
|
bool ScreenSpaceRaymarch( |
|
|
|
ScreenSpaceRaymarchInput input, |
|
|
|
out ScreenSpaceRayHit hit) |
|
|
|
void CalculateRayTXS(ScreenSpaceRaymarchInput input, out float3 positionTXS, out float3 rayTXS) |
|
|
|
// dirVS must be normalized |
|
|
|
float3 positionVS = input.startPositionVS; |
|
|
|
float3 rayEndVS = input.startPositionVS + input.dirVS * 10; |
|
|
|
const float2 CROSS_OFFSET = float2(2, 2); |
|
|
|
const int MAX_ITERATIONS = 32; |
|
|
|
float4 positionCS = ComputeClipSpacePosition(positionVS, input.projectionMatrix); |
|
|
|
float4 rayEndCS = ComputeClipSpacePosition(rayEndVS, input.projectionMatrix); |
|
|
|
float4 startPositionCS = mul(input.projectionMatrix, float4(input.startPositionVS, 1)); |
|
|
|
float4 dirCS = mul(input.projectionMatrix, float4(input.dirVS, 1)); |
|
|
|
#if UNITY_UV_STARTS_AT_TOP |
|
|
|
// Our clip space is correct, but the NDC is flipped. |
|
|
|
// Conceptually, it should be (positionNDC.y = 1.0 - positionNDC.y), but this is more efficient. |
|
|
|
startPositionCS.y = -startPositionCS.y; |
|
|
|
dirCS.y = -dirCS.y; |
|
|
|
#endif |
|
|
|
float2 positionNDC = ComputeNormalizedDeviceCoordinates(positionVS, input.projectionMatrix); |
|
|
|
float2 rayEndNDC = ComputeNormalizedDeviceCoordinates(rayEndVS, input.projectionMatrix); |
|
|
|
float2 startPositionNDC = (startPositionCS.xy * rcp(startPositionCS.w)) * 0.5 + 0.5; |
|
|
|
// store linear depth in z |
|
|
|
float l = length(dirCS.xy); |
|
|
|
float3 dirNDC = dirCS.xyz / l; |
|
|
|
float3 rayStartTXS = float3( |
|
|
|
positionNDC.xy * input.bufferSize, |
|
|
|
1.0 / positionCS.w); // Screen space depth interpolate properly in 1/z |
|
|
|
float2 invDirNDC = float2(1, 1) / dirNDC.xy; |
|
|
|
int2 cellPlanes = sign(dirNDC.xy); |
|
|
|
float2 crossOffset = CROSS_OFFSET * cellPlanes; |
|
|
|
cellPlanes = clamp(cellPlanes, 0, 1); |
|
|
|
float3 rayEndTXS = float3( |
|
|
|
rayEndNDC.xy * input.bufferSize, |
|
|
|
1.0 / rayEndCS.w); // Screen space depth interpolate properly in 1/z |
|
|
|
uint2 startPositionTXS = uint2(startPositionNDC * input.bufferSize); |
|
|
|
positionTXS = rayStartTXS; |
|
|
|
rayTXS = rayEndTXS - rayStartTXS; |
|
|
|
} |
|
|
|
ZERO_INITIALIZE(ScreenSpaceRayHit, hit); |
|
|
|
bool IsPositionAboveDepth(float rayDepth, float invLinearDepth) |
|
|
|
{ |
|
|
|
// as depth is inverted, we must invert the check as well |
|
|
|
// rayZ > HiZ <=> 1/rayZ < 1/HiZ |
|
|
|
return rayDepth > invLinearDepth; |
|
|
|
} |
|
|
|
int currentLevel = input.minLevel; |
|
|
|
uint2 cellCount = input.bufferSize >> currentLevel; |
|
|
|
uint2 cellSize = uint2(1, 1) << currentLevel; |
|
|
|
bool ScreenSpaceRaymarch( |
|
|
|
ScreenSpaceRaymarchInput input, |
|
|
|
out ScreenSpaceRayHit hit) |
|
|
|
{ |
|
|
|
const float2 CROSS_OFFSET = float2(2, 2); |
|
|
|
const int MAX_ITERATIONS = 32; |
|
|
|
// store linear depth in z |
|
|
|
float3 positionTXS = float3(float2(startPositionTXS), input.startLinearDepth); |
|
|
|
int iteration = 0; |
|
|
|
ZERO_INITIALIZE(ScreenSpaceRayHit, hit); |
|
|
|
bool hitSuccessful = true; |
|
|
|
// Caclulate TXS ray |
|
|
|
float3 startPositionTXS; |
|
|
|
float3 rayTXS; |
|
|
|
CalculateRayTXS(input, startPositionTXS, rayTXS); |
|
|
|
int maxUsedLevel = currentLevel; |
|
|
|
int maxUsedLevel = input.minLevel; |
|
|
|
debug.startPositionSSX = startPositionTXS.x; |
|
|
|
debug.startPositionSSY = startPositionTXS.y; |
|
|
|
debug.startLinearDepth = input.startLinearDepth; |
|
|
|
debug.startPositionSSX = uint(startPositionTXS.x); |
|
|
|
debug.startPositionSSY = uint(startPositionTXS.y); |
|
|
|
debug.startLinearDepth = 1 / startPositionTXS.z; |
|
|
|
while (currentLevel >= input.minLevel) |
|
|
|
bool hitSuccessful = true; |
|
|
|
int iteration = 0; |
|
|
|
if (!any(rayTXS.xy)) |
|
|
|
if (iteration >= MAX_ITERATIONS) |
|
|
|
hit.distance = 1 / startPositionTXS.z; |
|
|
|
hit.linearDepth = 1 / startPositionTXS.z; |
|
|
|
hit.positionSS = uint2(startPositionTXS.xy); |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
float2 invRayTXS = float2(1, 1) / rayTXS.xy; |
|
|
|
|
|
|
|
// Calculate planes to intersect for each cell |
|
|
|
int2 cellPlanes = sign(rayTXS.xy); |
|
|
|
float2 crossOffset = CROSS_OFFSET * cellPlanes; |
|
|
|
cellPlanes = clamp(cellPlanes, 0, 1); |
|
|
|
|
|
|
|
// Initialize loop |
|
|
|
int currentLevel = input.minLevel; |
|
|
|
uint2 cellCount = input.bufferSize >> currentLevel; |
|
|
|
uint2 cellSize = uint2(1, 1) << currentLevel; |
|
|
|
|
|
|
|
float3 positionTXS = startPositionTXS; |
|
|
|
|
|
|
|
while (currentLevel >= input.minLevel) |
|
|
|
hitSuccessful = false; |
|
|
|
break; |
|
|
|
} |
|
|
|
if (iteration >= MAX_ITERATIONS) |
|
|
|
{ |
|
|
|
hitSuccessful = false; |
|
|
|
break; |
|
|
|
} |
|
|
|
if (_DebugStep == iteration) |
|
|
|
{ |
|
|
|
debug.cellSizeW = cellSize.x; |
|
|
|
debug.cellSizeH = cellSize.y; |
|
|
|
debug.positionTXS = positionTXS; |
|
|
|
} |
|
|
|
if (_DebugStep == iteration) |
|
|
|
{ |
|
|
|
debug.cellSizeW = cellSize.x; |
|
|
|
debug.cellSizeH = cellSize.y; |
|
|
|
debug.positionTXS = positionTXS; |
|
|
|
debug.hitLinearDepth = 1 / positionTXS.z; |
|
|
|
debug.hitPositionSS = uint2(positionTXS.xy); |
|
|
|
} |
|
|
|
// 1. Calculate hit in this HiZ cell |
|
|
|
int2 cellId = int2(positionTXS.xy) / cellCount; |
|
|
|
// 1. Calculate hit in this HiZ cell |
|
|
|
int2 cellId = int2(positionTXS.xy) / cellSize; |
|
|
|
// Planes to check |
|
|
|
int2 planes = (cellId + cellPlanes) * cellSize; |
|
|
|
// Hit distance to each planes |
|
|
|
float2 distanceToCellAxes = float2(planes - positionTXS.xy) * invDirNDC; // (distance to x axis, distance to y axis) |
|
|
|
|
|
|
|
float distanceToCell = min(distanceToCellAxes.x, distanceToCellAxes.y); |
|
|
|
float3 testHitPositionTXS = positionTXS + dirNDC * distanceToCell; |
|
|
|
// Planes to check |
|
|
|
int2 planes = (cellId + cellPlanes) * cellSize; |
|
|
|
// Hit distance to each planes |
|
|
|
float2 distanceToCellAxes = float2(planes - positionTXS.xy) * invRayTXS; // (distance to x axis, distance to y axis) |
|
|
|
float distanceToCell = min(distanceToCellAxes.x, distanceToCellAxes.y); |
|
|
|
// Interpolate screen space to get next test point |
|
|
|
float3 testHitPositionTXS = positionTXS + rayTXS * distanceToCell; |
|
|
|
// Offset the proper axis to enforce cell crossing |
|
|
|
// https://gamedev.autodesk.com/blogs/1/post/5866685274515295601 |
|
|
|
testHitPositionTXS.xy += (distanceToCellAxes.x < distanceToCellAxes.y) |
|
|
|
? float2(crossOffset.x, 0) |
|
|
|
: float2(0, crossOffset.y); |
|
|
|
// Offset the proper axis to enforce cell crossing |
|
|
|
// https://gamedev.autodesk.com/blogs/1/post/5866685274515295601 |
|
|
|
testHitPositionTXS.xy += (distanceToCellAxes.x < distanceToCellAxes.y) |
|
|
|
? float2(crossOffset.x, 0) |
|
|
|
: float2(0, crossOffset.y); |
|
|
|
if (any(testHitPositionTXS.xy > input.bufferSize) |
|
|
|
|| any(testHitPositionTXS.xy < 0)) |
|
|
|
{ |
|
|
|
hitSuccessful = false; |
|
|
|
break; |
|
|
|
} |
|
|
|
// Check if we are out of the buffer |
|
|
|
if (any(testHitPositionTXS.xy > input.bufferSize) |
|
|
|
|| any(testHitPositionTXS.xy < 0)) |
|
|
|
{ |
|
|
|
hitSuccessful = false; |
|
|
|
break; |
|
|
|
} |
|
|
|
// 2. Sample the HiZ cell |
|
|
|
float pyramidDepth = LOAD_TEXTURE2D_LOD(_PyramidDepthTexture, int2(testHitPositionTXS.xy) >> currentLevel, currentLevel).r; |
|
|
|
float hiZLinearDepth = LinearEyeDepth(pyramidDepth, _ZBufferParams); |
|
|
|
// 2. Sample the HiZ cell |
|
|
|
float pyramidDepth = LOAD_TEXTURE2D_LOD(_PyramidDepthTexture, int2(testHitPositionTXS.xy) >> currentLevel, currentLevel).r; |
|
|
|
float hiZLinearDepth = LinearEyeDepth(pyramidDepth, _ZBufferParams); |
|
|
|
float invHiZLinearDepth = 1 / hiZLinearDepth; |
|
|
|
if (hiZLinearDepth < testHitPositionTXS.z) |
|
|
|
{ |
|
|
|
currentLevel = min(input.maxLevel, currentLevel + 1); |
|
|
|
if (IsPositionAboveDepth(testHitPositionTXS.z, invHiZLinearDepth)) |
|
|
|
{ |
|
|
|
currentLevel = min(input.maxLevel, currentLevel + 1); |
|
|
|
maxUsedLevel = max(maxUsedLevel, currentLevel); |
|
|
|
maxUsedLevel = max(maxUsedLevel, currentLevel); |
|
|
|
positionTXS = testHitPositionTXS; |
|
|
|
hit.distance += distanceToCell; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
float rayOffsetLength = (hiZLinearDepth - positionTXS.z) / dirNDC.z; |
|
|
|
positionTXS += dirNDC * rayOffsetLength; |
|
|
|
hit.distance += rayOffsetLength; |
|
|
|
--currentLevel; |
|
|
|
} |
|
|
|
|
|
|
|
cellCount = input.bufferSize >> currentLevel; |
|
|
|
cellSize = uint2(1, 1) << currentLevel; |
|
|
|
positionTXS = testHitPositionTXS; |
|
|
|
hit.distance += distanceToCell; |
|
|
|
} |
|
|
|
else |
|
|
|
{ |
|
|
|
float rayOffsetLength = (invHiZLinearDepth - positionTXS.z) / rayTXS.z; |
|
|
|
positionTXS += rayTXS * rayOffsetLength; |
|
|
|
hit.distance += rayOffsetLength; |
|
|
|
--currentLevel; |
|
|
|
} |
|
|
|
++iteration; |
|
|
|
} |
|
|
|
cellCount = input.bufferSize >> currentLevel; |
|
|
|
cellSize = uint2(1, 1) << currentLevel; |
|
|
|
hit.linearDepth = positionTXS.z; |
|
|
|
hit.positionSS = float2(positionTXS.xy) / float2(input.bufferSize); |
|
|
|
++iteration; |
|
|
|
} |
|
|
|
hit.linearDepth = 1 / positionTXS.z; |
|
|
|
hit.positionSS = float2(positionTXS.xy) / float2(input.bufferSize); |
|
|
|
} |
|
|
|
|
|
|
|
debug.hitDistance = hit.distance; |
|
|
|
|
|
|
|
if (input.writeStepDebug) |
|
|
|
{ |
|
|
|
|
|
|
switch (_DebugLightingSubMode) |
|
|
|
{ |
|
|
|
case DEBUGSCREENSPACETRACING_POSITION_NDC: |
|
|
|
hit.debugOutput = float3(startPositionNDC.xy, 0); |
|
|
|
hit.debugOutput = float3(float2(startPositionTXS.xy) / input.bufferSize, 0); |
|
|
|
hit.debugOutput = float3(dirNDC.xy * 0.5 + 0.5, dirNDC.z); |
|
|
|
hit.debugOutput = float3(rayTXS.xy * 0.5 + 0.5, frac(0.1 / rayTXS.z)); |
|
|
|
break; |
|
|
|
case DEBUGSCREENSPACETRACING_HIT_DISTANCE: |
|
|
|
hit.debugOutput = frac(hit.distance * 0.1); |
|
|
|