您最多选择25个主题 主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
 
 
 
 

1064 行
38 KiB

#ifndef UNITY_SCREEN_SPACE_TRACING_INCLUDED
#define UNITY_SCREEN_SPACE_TRACING_INCLUDED
// How this file works:
// This file is separated in two sections: 1. Library, 2. Constant Buffer Specific Signatures
//
// 1. Library
// This section contains all function and structures for the Screen Space Tracing.
//
// 2. Constant Buffer Specific Signatures
// This section defines signatures that will use specifics constant buffers.
// Thus you can use the Screen Space Tracing library with different settings.
// It can be usefull to use it for both reflection and refraction but with different settings' sets.
//
//
// To use this file:
// 1. Define the macro SSRTID
// 2. Include the file
// 3. Undef the macro SSRTID
//
//
// Example for reflection:
// #define SSRTID Reflection
// #include "ScreenSpaceTracing.hlsl"
// #undef SSRTID
//
// Use library here, like ScreenSpaceProxyRaycastReflection(...)
// #################################################
// Notes
// #################################################
// Some banding issues can occurs when raymarching the depth buffer.
//
// This can be hidden by offsetting the ray origin with a jitter.
// Combined with a temporal filtering, the banding artifact will be smoothed.
// This will trade banding for noise.
//
// This happens when we raymarch with a ray direction that is quite different from the view vector.
// Exemple when raymarching with a direction perpendicular to the view vector:
//
// Depth buffer far
// |
// v
// near
//
// --------
// hit ==>xx
// xx
//
// fail ===>
// xx
// hit ===>xx
//
// xx
// #################################################
// Screen Space Tracing Library
// #################################################
// -------------------------------------------------
// Algorithm uniform parameters
// -------------------------------------------------
const float DepthPlaneBias = 1E-5;
// -------------------------------------------------
// Output
// -------------------------------------------------
struct ScreenSpaceRayHit
{
uint2 positionSS; // Position of the hit point (SS)
float2 positionNDC; // Position of the hit point (NDC)
float linearDepth; // Linear depth of the hit point
#ifdef DEBUG_DISPLAY
float3 debugOutput;
#endif
};
struct ScreenSpaceRaymarchInput
{
float3 rayOriginWS; // Ray origin (WS)
float3 rayDirWS; // Ray direction (WS)
#ifdef DEBUG_DISPLAY
bool debug;
#endif
};
struct ScreenSpaceProxyRaycastInput
{
float3 rayOriginWS; // Ray origin (WS)
float3 rayDirWS; // Ray direction (WS)
EnvLightData proxyData; // Proxy to use for raycasting
#ifdef DEBUG_DISPLAY
bool debug;
#endif
};
// -------------------------------------------------
// Utilities
// -------------------------------------------------
// Calculate the ray origin and direction in SS
void CalculateRaySS(
float3 rayOriginWS, // Ray origin (World Space)
float3 rayDirWS, // Ray direction (World Space)
uint2 bufferSize, // Texture size of screen buffers
out float3 positionSS, // (x, y, 1/linearDepth)
out float3 raySS, // (dx, dy, d(1/linearDepth))
out float rayEndDepth // Linear depth of the end point used to calculate raySS
)
{
const float kNearClipPlane = -0.01;
const float kMaxRayTraceDistance = 1000;
float3 rayOriginVS = mul(GetWorldToViewMatrix(), float4(rayOriginWS, 1.0)).xyz;
float3 rayDirVS = mul((float3x3)GetWorldToViewMatrix(), rayDirWS);
// Clip ray to near plane to avoid raymarching behind camera
float rayLength = ((rayOriginVS.z + rayDirVS.z * kMaxRayTraceDistance) > kNearClipPlane)
? ((kNearClipPlane - rayOriginVS.z) / rayDirVS.z)
: kMaxRayTraceDistance;
float3 positionWS = rayOriginWS;
float3 rayEndWS = rayOriginWS + rayDirWS * rayLength;
float4 positionCS = ComputeClipSpacePosition(positionWS, GetWorldToHClipMatrix());
float4 rayEndCS = ComputeClipSpacePosition(rayEndWS, GetWorldToHClipMatrix());
float2 positionNDC = ComputeNormalizedDeviceCoordinates(positionWS, GetWorldToHClipMatrix());
float2 rayEndNDC = ComputeNormalizedDeviceCoordinates(rayEndWS, GetWorldToHClipMatrix());
rayEndDepth = rayEndCS.w;
float3 rayStartSS = float3(
positionNDC.xy * bufferSize,
1.0 / positionCS.w); // Screen space depth interpolate properly in 1/z
float3 rayEndSS = float3(
rayEndNDC.xy * bufferSize,
1.0 / rayEndDepth); // Screen space depth interpolate properly in 1/z
positionSS = rayStartSS;
raySS = rayEndSS - rayStartSS;
}
// Sample the Depth buffer at a specific mip and linear depth
float2 LoadDepth(float2 positionSS, int level)
{
float2 pyramidDepth = LOAD_TEXTURE2D_LOD(_DepthPyramidTexture, int2(positionSS.xy) >> level, level).rg;
float2 linearDepth = float2(LinearEyeDepth(pyramidDepth.r, _ZBufferParams), LinearEyeDepth(pyramidDepth.g, _ZBufferParams));
return linearDepth;
}
// Sample the Depth buffer at a specific mip and return 1/linear depth
float2 LoadInvDepth(float2 positionSS, int level)
{
float2 linearDepth = LoadDepth(positionSS, level);
float2 invLinearDepth = 1 / linearDepth;
return invLinearDepth;
}
bool CellAreEquals(int2 cellA, int2 cellB)
{
return cellA.x == cellB.x && cellA.y == cellB.y;
}
// Calculate intersection between the ray and the depth plane
// positionSS.z is 1/depth
// raySS.z is 1/depth
float3 IntersectDepthPlane(float3 positionSS, float3 raySS, float invDepth)
{
// The depth of the intersection with the depth plane is: positionSS.z + raySS.z * t = invDepth
float t = (invDepth - positionSS.z) / raySS.z;
// (t<0) When the ray is going away from the depth plane,
// put the intersection away.
// Instead the intersection with the next tile will be used.
// (t>=0) Add a small distance to go through the depth plane.
t = t >= 0.0f ? (t + DepthPlaneBias) : 1E5;
// Return the point on the ray
return positionSS + raySS * t;
}
float2 CalculateDistanceToCellPlanes(
float3 positionSS, // Ray Origin (Screen Space, 1/LinearDepth)
float2 invRaySS, // 1/RayDirection
int2 cellId, // (Row, Colum) of the cell
uint2 cellSize, // Size of the cell in pixel
int2 cellPlanes // Planes to intersect (one of (0,0), (1, 0), (0, 1), (1, 1))
)
{
// Planes to check
int2 planes = (cellId + cellPlanes) * cellSize;
// Hit distance to each planes
float2 distanceToCellAxes = float2(planes - positionSS.xy) * invRaySS; // (distance to x axis, distance to y axis)
return distanceToCellAxes;
}
// Calculate intersection between a ray and a cell
float3 IntersectCellPlanes(
float3 positionSS, // Ray Origin (Screen Space, 1/LinearDepth)
float3 raySS, // Ray Direction (Screen Space, 1/LinearDepth)
float2 invRaySS, // 1/RayDirection
int2 cellId, // (Row, Colum) of the cell
uint2 cellSize, // Size of the cell in pixel
int2 cellPlanes, // Planes to intersect (one of (0,0), (1, 0), (0, 1), (1, 1))
float2 crossOffset // Offset to use to ensure cell boundary crossing
)
{
float2 distanceToCellAxes = CalculateDistanceToCellPlanes(
positionSS,
invRaySS,
cellId,
cellSize,
cellPlanes
);
float t = min(distanceToCellAxes.x, distanceToCellAxes.y)
// Offset to ensure cell crossing
// This assume that length(raySS.xy) == 1;
+ 0.1;
// Interpolate screen space to get next test point
float3 testHitPositionSS = positionSS + raySS * t;
return testHitPositionSS;
}
float CalculateHitWeight(
ScreenSpaceRayHit hit,
float2 startPositionSS,
float minLinearDepth,
float settingsDepthBufferThickness,
float settingsRayMaxScreenDistance,
float settingsRayBlendScreenDistance
)
{
// Blend when the hit is near the thickness of the object
//float thicknessWeight = clamp(1 - (hit.linearDepth - minLinearDepth) / settingsDepthBufferThickness, 0, 1);
// Blend when the ray when the raymarched distance is too long
float2 screenDistanceNDC = abs(hit.positionSS.xy - startPositionSS) * _ScreenSize.zw;
float2 screenDistanceWeights = clamp((settingsRayMaxScreenDistance - screenDistanceNDC) / settingsRayBlendScreenDistance, 0, 1);
float screenDistanceWeight = min(screenDistanceWeights.x, screenDistanceWeights.y);
// return thicknessWeight * screenDistanceWeight;
return screenDistanceWeight;
}
#ifdef DEBUG_DISPLAY
// -------------------------------------------------
// Debug Utilities
// -------------------------------------------------
void DebugComputeCommonOutput(
float3 rayDirWS,
bool hitSuccessful,
int tracingModel,
inout ScreenSpaceRayHit hit
)
{
switch (_DebugLightingSubMode)
{
case DEBUGSCREENSPACETRACING_RAY_DIR_WS:
hit.debugOutput = rayDirWS * 0.5 + 0.5;
break;
case DEBUGSCREENSPACETRACING_HIT_DEPTH:
hit.debugOutput = frac(hit.linearDepth * 0.1);
break;
case DEBUGSCREENSPACETRACING_HIT_SUCCESS:
hit.debugOutput = GetIndexColor(hitSuccessful ? 1 : 2);
break;
case DEBUGSCREENSPACETRACING_TRACING_MODEL:
hit.debugOutput = GetIndexColor(tracingModel);
break;
}
}
#endif
float SampleBayer4(uint2 positionSS)
{
const float4x4 Bayer4 = float4x4(0, 8, 2, 10,
12, 4, 14, 6,
3, 11, 1, 9,
15, 7, 13, 5) / 16;
return Bayer4[positionSS.x % 4][positionSS.y % 4];
}
// -------------------------------------------------
// Algorithms
// -------------------------------------------------
// -------------------------------------------------
// Algorithm: Linear Raymarching
// -------------------------------------------------
// Based on Digital Differential Analyzer and Morgan McGuire's Screen Space Ray Tracing (http://casual-effects.blogspot.fr/2014/08/screen-space-ray-tracing.html)
//
// Linear raymarching algorithm with precomputed properties
// -------------------------------------------------
bool ScreenSpaceLinearRaymarch(
ScreenSpaceRaymarchInput input,
// Settings
int settingRayLevel, // Mip level to use to ray march depth buffer
uint settingsRayMaxIterations, // Maximum number of iterations (= max number of depth samples)
float settingsDepthBufferThickness, // Bias to use when trying to detect whenever we raymarch behind a surface
float settingsRayMaxScreenDistance, // Maximum screen distance raymarched
float settingsRayBlendScreenDistance, // Distance to blend before maximum screen distance is reached
int settingsDebuggedAlgorithm, // currently debugged algorithm (see PROJECTIONMODEL defines)
// Precomputed properties
float3 startPositionSS, // Start position in Screen Space (x in pixel, y in pixel, z = 1/linearDepth)
float3 raySS, // Ray direction in Screen Space (dx in pixel, dy in pixel, z = 1/endPointLinearDepth - 1/startPointLinearDepth)
float rayEndDepth, // Linear depth of the end point used to calculate raySS.
uint2 bufferSize, // Texture size of screen buffers
// Out
out ScreenSpaceRayHit hit,
out float hitWeight,
out uint iteration
)
{
ZERO_INITIALIZE(ScreenSpaceRayHit, hit);
bool hitSuccessful = false;
iteration = 0u;
hitWeight = 0;
int mipLevel = min(max(settingRayLevel, 0), int(_DepthPyramidScale.z));
uint maxIterations = settingsRayMaxIterations;
float3 positionSS = startPositionSS;
raySS /= max(abs(raySS.x), abs(raySS.y));
raySS *= 1 << mipLevel;
#ifdef DEBUG_DISPLAY
float3 debugIterationPositionSS = positionSS;
uint debugIteration = iteration;
float debugIterationLinearDepthBufferMin = 0;
float debugIterationLinearDepthBufferMinThickness = 0;
float debugIterationLinearDepthBufferMax = 0;
#endif
float2 invLinearDepth = float2(0.0, 0.0);
float minLinearDepth = 0;
float minLinearDepthWithThickness = 0;
float positionLinearDepth = 0;
for (iteration = 0u; iteration < maxIterations; ++iteration)
{
positionSS += raySS;
// Sampled as 1/Z so it interpolate properly in screen space.
invLinearDepth = LoadInvDepth(positionSS.xy, mipLevel);
minLinearDepth = 1 / invLinearDepth.r;
minLinearDepthWithThickness = minLinearDepth + settingsDepthBufferThickness;
positionLinearDepth = 1 / positionSS.z;
bool isAboveDepth = positionLinearDepth < minLinearDepth;
bool isAboveThickness = positionLinearDepth < minLinearDepthWithThickness;
bool isBehindDepth = !isAboveThickness;
bool intersectWithDepth = !isAboveDepth && isAboveThickness;
#ifdef DEBUG_DISPLAY
// Fetch post iteration debug values
if (input.debug && _DebugStep >= int(iteration))
{
debugIterationPositionSS = positionSS;
debugIterationLinearDepthBufferMin = minLinearDepth;
debugIterationLinearDepthBufferMinThickness = minLinearDepthWithThickness;
debugIterationLinearDepthBufferMax = 1 / invLinearDepth.g;
debugIteration = iteration;
}
#endif
if (intersectWithDepth)
{
hitSuccessful = true;
break;
}
// Check if we are out of the buffer
if (any(int2(positionSS.xy) > int2(bufferSize))
|| any(positionSS.xy < 0)
)
{
hitSuccessful = false;
break;
}
}
if (iteration >= maxIterations)
hitSuccessful = false;
hit.linearDepth = 1 / positionSS.z;
hit.positionNDC = float2(positionSS.xy) / float2(bufferSize);
hit.positionSS = uint2(positionSS.xy);
// Detect when we go behind an object given a thickness
hitWeight = CalculateHitWeight(
hit,
startPositionSS.xy,
invLinearDepth.r,
settingsDepthBufferThickness,
settingsRayMaxScreenDistance,
settingsRayBlendScreenDistance
);
if (hitWeight <= 0)
hitSuccessful = false;
#ifdef DEBUG_DISPLAY
DebugComputeCommonOutput(input.rayDirWS, hitSuccessful, PROJECTIONMODEL_LINEAR, hit);
switch (_DebugLightingSubMode)
{
case DEBUGSCREENSPACETRACING_LINEAR_POSITION_NDC:
hit.debugOutput = float3(float2(startPositionSS.xy) * _ScreenSize.zw, 0);
break;
case DEBUGSCREENSPACETRACING_LINEAR_ITERATION_COUNT:
hit.debugOutput = float(iteration) / float(settingsRayMaxIterations);
break;
case DEBUGSCREENSPACETRACING_LINEAR_RAY_DIR_NDC:
hit.debugOutput = float3(raySS.xy * 0.5 + 0.5, frac(0.1 / raySS.z));
break;
case DEBUGSCREENSPACETRACING_LINEAR_HIT_WEIGHT:
hit.debugOutput = float3(hitWeight, hitWeight, hitWeight);
break;
}
if (input.debug
&& _DebugScreenSpaceTracingData[0].tracingModel == -1
&& settingsDebuggedAlgorithm == PROJECTIONMODEL_LINEAR
)
{
// Build debug structure
ScreenSpaceTracingDebug debug;
ZERO_INITIALIZE(ScreenSpaceTracingDebug, debug);
debug.tracingModel = PROJECTIONMODEL_LINEAR;
debug.loopStartPositionSSX = uint(startPositionSS.x);
debug.loopStartPositionSSY = uint(startPositionSS.y);
debug.loopStartLinearDepth = 1 / startPositionSS.z;
debug.loopRayDirectionSS = raySS;
debug.loopIterationMax = iteration;
debug.iterationPositionSS = debugIterationPositionSS;
debug.iterationMipLevel = mipLevel;
debug.iteration = debugIteration;
debug.iterationLinearDepthBufferMin = debugIterationLinearDepthBufferMin;
debug.iterationLinearDepthBufferMinThickness = debugIterationLinearDepthBufferMinThickness;
debug.iterationLinearDepthBufferMax = debugIterationLinearDepthBufferMax;
debug.endHitSuccess = hitSuccessful;
debug.endLinearDepth = hit.linearDepth;
debug.endPositionSSX = hit.positionSS.x;
debug.endPositionSSY = hit.positionSS.y;
debug.iterationCellSizeW = 1 << mipLevel;
debug.iterationCellSizeH = 1 << mipLevel;
debug.endHitWeight = hitWeight;
_DebugScreenSpaceTracingData[0] = debug;
}
#endif
return hitSuccessful;
}
// -------------------------------------------------
// Algorithm: Scene Proxy Raycasting
// -------------------------------------------------
// We perform a raycast against a proxy volume that approximate the current scene.
// Is is a simple shape (Sphere, Box).
// -------------------------------------------------
bool ScreenSpaceProxyRaycast(
ScreenSpaceProxyRaycastInput input,
// Settings
int settingsDebuggedAlgorithm, // currently debugged algorithm (see PROJECTIONMODEL defines)
// Out
out ScreenSpaceRayHit hit
)
{
// Initialize loop
ZERO_INITIALIZE(ScreenSpaceRayHit, hit);
float3x3 worldToPS = WorldToProxySpace(input.proxyData);
float3 rayOriginPS = WorldToProxyPosition(input.proxyData, worldToPS, input.rayOriginWS);
float3 rayDirPS = mul(input.rayDirWS, worldToPS);
float projectionDistance = 0.0;
switch(input.proxyData.influenceShapeType)
{
case ENVSHAPETYPE_SPHERE:
case ENVSHAPETYPE_SKY:
{
projectionDistance = IntersectSphereProxy(input.proxyData, rayDirPS, rayOriginPS);
break;
}
case ENVSHAPETYPE_BOX:
projectionDistance = IntersectBoxProxy(input.proxyData, rayDirPS, rayOriginPS);
break;
}
float3 hitPositionWS = input.rayOriginWS + input.rayDirWS * projectionDistance;
float4 hitPositionCS = ComputeClipSpacePosition(hitPositionWS, GetWorldToHClipMatrix());
float4 rayOriginCS = ComputeClipSpacePosition(input.rayOriginWS, GetWorldToHClipMatrix());
float2 hitPositionNDC = ComputeNormalizedDeviceCoordinates(hitPositionWS, GetWorldToHClipMatrix());
uint2 hitPositionSS = uint2(hitPositionNDC *_ScreenSize.xy);
float hitLinearDepth = hitPositionCS.w;
hit.positionNDC = hitPositionNDC;
hit.positionSS = hitPositionSS;
hit.linearDepth = hitLinearDepth;
bool hitSuccessful = hitLinearDepth > 0; // Negative means that the hit is behind the camera
#ifdef DEBUG_DISPLAY
DebugComputeCommonOutput(input.rayDirWS, hitSuccessful, PROJECTIONMODEL_PROXY, hit);
if (input.debug
&& _DebugScreenSpaceTracingData[0].tracingModel == -1
&& settingsDebuggedAlgorithm == PROJECTIONMODEL_PROXY
)
{
ScreenSpaceTracingDebug debug;
ZERO_INITIALIZE(ScreenSpaceTracingDebug, debug);
float2 rayOriginNDC = ComputeNormalizedDeviceCoordinates(input.rayOriginWS, GetWorldToHClipMatrix());
uint2 rayOriginSS = uint2(rayOriginNDC * _ScreenSize.xy);
debug.tracingModel = PROJECTIONMODEL_PROXY;
debug.loopStartPositionSSX = rayOriginSS.x;
debug.loopStartPositionSSY = rayOriginSS.y;
debug.loopStartLinearDepth = rayOriginCS.w;
debug.endHitSuccess = hitSuccessful;
debug.endLinearDepth = hitLinearDepth;
debug.endPositionSSX = hitPositionSS.x;
debug.endPositionSSY = hitPositionSS.y;
debug.proxyShapeType = input.proxyData.influenceShapeType;
debug.projectionDistance = projectionDistance;
_DebugScreenSpaceTracingData[0] = debug;
}
#endif
return hitSuccessful;
}
// -------------------------------------------------
// Algorithm: Linear Raymarching And Scene Proxy Raycasting
// -------------------------------------------------
// Perform a linear raymarching for close hit detection and fallback on proxy raycasting
// -------------------------------------------------
bool ScreenSpaceLinearProxyRaycast(
ScreenSpaceProxyRaycastInput input,
// Settings (linear)
int settingRayLevel, // Mip level to use to ray march depth buffer
uint settingsRayMaxIterations, // Maximum number of iterations (= max number of depth samples)
float settingsDepthBufferThickness, // Bias to use when trying to detect whenever we raymarch behind a surface
float settingsRayMaxScreenDistance, // Maximum screen distance raymarched
float settingsRayBlendScreenDistance, // Distance to blend before maximum screen distance is reached
// Settings (common)
int settingsDebuggedAlgorithm, // currently debugged algorithm (see PROJECTIONMODEL defines)
// Out
out ScreenSpaceRayHit hit
)
{
// Perform linear raymarch
ScreenSpaceRaymarchInput inputLinear;
inputLinear.rayOriginWS = input.rayOriginWS;
inputLinear.rayDirWS = input.rayDirWS;
#ifdef DEBUG_DISPLAY
inputLinear.debug = input.debug;
#endif
uint2 bufferSize = uint2(_DepthPyramidSize.xy);
// Compute properties for linear raymarch
float3 startPositionSS;
float3 raySS;
float rayEndDepth;
CalculateRaySS(
input.rayOriginWS,
input.rayDirWS,
bufferSize,
startPositionSS,
raySS,
rayEndDepth
);
uint iteration;
float hitWeight;
bool hitSuccessful = ScreenSpaceLinearRaymarch(
inputLinear,
// Settings
settingRayLevel,
settingsRayMaxIterations,
settingsDepthBufferThickness,
settingsRayMaxScreenDistance,
settingsRayBlendScreenDistance,
settingsDebuggedAlgorithm,
// Precomputed properties
startPositionSS,
raySS,
rayEndDepth,
bufferSize,
// Out
hit,
hitWeight,
iteration
);
if (!hitSuccessful)
{
hitSuccessful = ScreenSpaceProxyRaycast(
input,
// Settings
settingsDebuggedAlgorithm,
// Out
hit
);
}
return hitSuccessful;
}
// -------------------------------------------------
// Algorithm: HiZ raymarching
// -------------------------------------------------
// Based on Yasin Uludag, 2014. "Hi-Z Screen-Space Cone-Traced Reflections", GPU Pro5: Advanced Rendering Techniques
//
// NB: We perform first a linear raymarch to handle close hits, then we perform the actual HiZ raymarching.
// We do this for two reasons:
// - It is cheaper in case of close hit than starting with HiZ
// - It will start the HiZ algorithm with an offset, preventing false positive hit at ray origin.
// -------------------------------------------------
bool ScreenSpaceHiZRaymarch(
ScreenSpaceRaymarchInput input,
// Settings
uint settingsRayMinLevel, // Minimum mip level to use for ray marching the depth buffer in HiZ
uint settingsRayMaxLevel, // Maximum mip level to use for ray marching the depth buffer in HiZ
uint settingsRayMaxIterations, // Maximum number of iteration for the HiZ raymarching (= number of depth sample for HiZ)
float settingsDepthBufferThickness, // Bias to use when trying to detect whenever we raymarch behind a surface
float settingsRayMaxScreenDistance, // Maximum screen distance raymarched
float settingsRayBlendScreenDistance, // Distance to blend before maximum screen distance is reached
bool settingsRayMarchBehindObjects, // Whether to raymarch behind objects
int settingsDebuggedAlgorithm, // currently debugged algorithm (see PROJECTIONMODEL defines)
// out
out ScreenSpaceRayHit hit,
out float hitWeight
)
{
const float2 CROSS_OFFSET = float2(1, 1);
// Initialize loop
ZERO_INITIALIZE(ScreenSpaceRayHit, hit);
hitWeight = 0;
bool hitSuccessful = false;
uint iteration = 0u;
int minMipLevel = max(settingsRayMinLevel, 0u);
int maxMipLevel = min(settingsRayMaxLevel, uint(_DepthPyramidScale.z));
uint2 bufferSize = uint2(_DepthPyramidSize.xy);
uint maxIterations = settingsRayMaxIterations;
float3 startPositionSS;
float3 raySS;
float rayEndDepth;
CalculateRaySS(
input.rayOriginWS,
input.rayDirWS,
bufferSize,
startPositionSS,
raySS,
rayEndDepth
);
#ifdef DEBUG_DISPLAY
// Initialize debug variables
int debugLoopMipMaxUsedLevel = minMipLevel;
int debugIterationMipLevel = minMipLevel;
uint2 debugIterationCellSize = uint2(0u, 0u);
float3 debugIterationPositionSS = float3(0, 0, 0);
uint debugIteration = 0u;
uint debugIterationIntersectionKind = 0u;
float debugIterationLinearDepthBufferMin = 0;
float debugIterationLinearDepthBufferMinThickness = 0;
float debugIterationLinearDepthBufferMax = 0;
#endif
iteration = 0u;
int intersectionKind = 0;
float raySSLength = length(raySS.xy);
raySS /= raySSLength;
// Initialize raymarching
float2 invRaySS = float2(1, 1) / raySS.xy;
// Calculate planes to intersect for each cell
int2 cellPlanes = sign(raySS.xy);
float2 crossOffset = CROSS_OFFSET * cellPlanes;
cellPlanes = clamp(cellPlanes, 0, 1);
int currentLevel = minMipLevel;
uint2 cellCount = bufferSize >> currentLevel;
uint2 cellSize = uint2(1, 1) << currentLevel;
float3 positionSS = startPositionSS;
float2 invLinearDepth = float2(0.0, 0.0);
float positionLinearDepth = 0;
float minLinearDepth = 0;
float minLinearDepthWithThickness = 0;
// Intersect with first cell and add an offsot to avoid HiZ raymarching to stuck at the origin
{
const float epsilon = 1E-3;
const float minTraversal = 2 << currentLevel;
float2 distanceToCellAxes = CalculateDistanceToCellPlanes(
positionSS,
invRaySS,
int2(positionSS.xy) / cellSize,
cellSize,
cellPlanes
);
float t = min(distanceToCellAxes.x * minTraversal + epsilon, distanceToCellAxes.y * minTraversal + epsilon);
positionSS = positionSS + raySS * t;
}
bool isBehindDepth = false;
while (currentLevel >= minMipLevel)
{
hitSuccessful = true;
if (iteration >= maxIterations)
{
hitSuccessful = false;
break;
}
cellCount = bufferSize >> currentLevel;
cellSize = uint2(1, 1) << currentLevel;
#ifdef DEBUG_DISPLAY
// Fetch pre iteration debug values
if (input.debug && _DebugStep >= int(iteration))
debugIterationMipLevel = currentLevel;
#endif
// Go down in HiZ levels by default
int mipLevelDelta = -1;
// Sampled as 1/Z so it interpolate properly in screen space.
invLinearDepth = LoadInvDepth(positionSS.xy, currentLevel);
positionLinearDepth = 1 / positionSS.z;
minLinearDepth = 1 / invLinearDepth.r;
minLinearDepthWithThickness = minLinearDepth + settingsDepthBufferThickness;
bool isAboveDepth = positionLinearDepth < minLinearDepth;
bool isAboveThickness = positionLinearDepth < minLinearDepthWithThickness;
isBehindDepth = !isAboveThickness;
bool intersectWithDepth = minLinearDepth >= positionLinearDepth && isAboveThickness;
intersectionKind = HIZINTERSECTIONKIND_NONE;
// Nominal case, we raymarch in front of the depth buffer and accelerate with HiZ
if (isAboveDepth)
{
float3 candidatePositionSS = IntersectDepthPlane(positionSS, raySS, invLinearDepth.r);
intersectionKind = HIZINTERSECTIONKIND_DEPTH;
const int2 cellId = int2(positionSS.xy) / cellSize;
const int2 candidateCellId = int2(candidatePositionSS.xy) / cellSize;
// If we crossed the current cell
if (!CellAreEquals(cellId, candidateCellId))
{
candidatePositionSS = IntersectCellPlanes(
positionSS,
raySS,
invRaySS,
cellId,
cellSize,
cellPlanes,
crossOffset
);
intersectionKind = HIZINTERSECTIONKIND_CELL;
// Go up a level to go faster
mipLevelDelta = 1;
}
positionSS = candidatePositionSS;
}
// Raymarching behind object in depth buffer, this case degenerate into a linear search
else if (settingsRayMarchBehindObjects && isBehindDepth && currentLevel <= (minMipLevel + 1))
{
const int2 cellId = int2(positionSS.xy) / cellSize;
positionSS = IntersectCellPlanes(
positionSS,
raySS,
invRaySS,
cellId,
cellSize,
cellPlanes,
crossOffset
);
intersectionKind = HIZINTERSECTIONKIND_CELL;
mipLevelDelta = 1;
}
currentLevel = min(currentLevel + mipLevelDelta, maxMipLevel);
float4 distancesToBorders = float4(positionSS.xy, bufferSize - positionSS.xy);
float distanceToBorders = min(min(distancesToBorders.x, distancesToBorders.y), min(distancesToBorders.z, distancesToBorders.w));
int minLevelForBorders = int(log2(distanceToBorders));
currentLevel = min(currentLevel, minLevelForBorders);
#ifdef DEBUG_DISPLAY
// Fetch post iteration debug values
if (input.debug && _DebugStep >= int(iteration))
{
debugLoopMipMaxUsedLevel = max(debugLoopMipMaxUsedLevel, currentLevel);
debugIterationPositionSS = positionSS;
debugIterationLinearDepthBufferMin = 1 / invLinearDepth.r;
debugIterationLinearDepthBufferMinThickness = 1 / invLinearDepth.r + settingsDepthBufferThickness;
debugIterationLinearDepthBufferMax = 1 / invLinearDepth.g;
debugIteration = iteration;
debugIterationIntersectionKind = intersectionKind;
debugIterationCellSize = cellSize;
}
#endif
// Check if we are out of the buffer
if (any(int2(positionSS.xy) > int2(bufferSize))
|| any(positionSS.xy < 0))
{
hitSuccessful = false;
break;
}
++iteration;
}
hit.linearDepth = positionLinearDepth;
hit.positionSS = uint2(positionSS.xy);
hit.positionNDC = float2(hit.positionSS) / float2(bufferSize);
// Detect when we go behind an object given a thickness
hitWeight = CalculateHitWeight(
hit,
startPositionSS.xy,
minLinearDepth,
settingsDepthBufferThickness,
settingsRayMaxScreenDistance,
settingsRayBlendScreenDistance
);
if (hitWeight <= 0 || isBehindDepth)
hitSuccessful = false;
#ifdef DEBUG_DISPLAY
DebugComputeCommonOutput(input.rayDirWS, hitSuccessful, PROJECTIONMODEL_HI_Z, hit);
switch (_DebugLightingSubMode)
{
case DEBUGSCREENSPACETRACING_HI_ZPOSITION_NDC:
hit.debugOutput = float3(float2(startPositionSS.xy) * _ScreenSize.zw, 0);
break;
case DEBUGSCREENSPACETRACING_HI_ZITERATION_COUNT:
hit.debugOutput = float(iteration) / float(settingsRayMaxIterations);
break;
case DEBUGSCREENSPACETRACING_HI_ZRAY_DIR_NDC:
hit.debugOutput = float3(raySS.xy * 0.5 + 0.5, frac(0.1 / raySS.z));
break;
case DEBUGSCREENSPACETRACING_HI_ZMAX_USED_MIP_LEVEL:
hit.debugOutput = float(debugLoopMipMaxUsedLevel) / float(maxMipLevel);
break;
case DEBUGSCREENSPACETRACING_HI_ZINTERSECTION_KIND:
hit.debugOutput = GetIndexColor(intersectionKind);
break;
case DEBUGSCREENSPACETRACING_HI_ZHIT_WEIGHT:
hit.debugOutput = float3(hitWeight, hitWeight, hitWeight);
break;
}
if (input.debug
&& _DebugScreenSpaceTracingData[0].tracingModel == -1
&& settingsDebuggedAlgorithm == PROJECTIONMODEL_HI_Z
)
{
// Build debug structure
ScreenSpaceTracingDebug debug;
ZERO_INITIALIZE(ScreenSpaceTracingDebug, debug);
debug.tracingModel = PROJECTIONMODEL_HI_Z;
debug.loopStartPositionSSX = uint(startPositionSS.x);
debug.loopStartPositionSSY = uint(startPositionSS.y);
debug.loopStartLinearDepth = 1 / startPositionSS.z;
debug.loopRayDirectionSS = raySS;
debug.loopMipLevelMax = debugLoopMipMaxUsedLevel;
debug.loopIterationMax = iteration;
debug.iterationPositionSS = debugIterationPositionSS;
debug.iterationMipLevel = debugIterationMipLevel;
debug.iteration = debugIteration;
debug.iterationLinearDepthBufferMin = debugIterationLinearDepthBufferMin;
debug.iterationLinearDepthBufferMinThickness = debugIterationLinearDepthBufferMinThickness;
debug.iterationLinearDepthBufferMax = debugIterationLinearDepthBufferMax;
debug.iterationIntersectionKind = debugIterationIntersectionKind;
debug.iterationCellSizeW = debugIterationCellSize.x;
debug.iterationCellSizeH = debugIterationCellSize.y;
debug.endHitSuccess = hitSuccessful;
debug.endLinearDepth = hit.linearDepth;
debug.endPositionSSX = hit.positionSS.x;
debug.endPositionSSY = hit.positionSS.y;
debug.endHitWeight = hitWeight;
_DebugScreenSpaceTracingData[0] = debug;
}
#endif
return hitSuccessful;
}
#endif
// #################################################
// Screen Space Tracing CB Specific Signatures
// #################################################
#ifdef SSRTID
// -------------------------------------------------
// Macros
// -------------------------------------------------
#define SSRT_SETTING(name, SSRTID) _SS ## SSRTID ## name
// -------------------------------------------------
// Constant buffers
// -------------------------------------------------
CBUFFER_START(MERGE_NAME(UnityScreenSpaceRaymarching, SSRTID))
int SSRT_SETTING(RayLevel, SSRTID);
int SSRT_SETTING(RayMinLevel, SSRTID);
int SSRT_SETTING(RayMaxLevel, SSRTID);
int SSRT_SETTING(RayMaxIterations, SSRTID);
float SSRT_SETTING(DepthBufferThickness, SSRTID);
float SSRT_SETTING(RayMaxScreenDistance, SSRTID);
float SSRT_SETTING(RayBlendScreenDistance, SSRTID);
int SSRT_SETTING(RayMarchBehindObjects, SSRTID);
#ifdef DEBUG_DISPLAY
int SSRT_SETTING(DebuggedAlgorithm, SSRTID);
#endif
CBUFFER_END
// -------------------------------------------------
// Algorithm: Linear Raymarching
// -------------------------------------------------
bool MERGE_NAME(ScreenSpaceLinearRaymarch, SSRTID)(
ScreenSpaceRaymarchInput input,
out ScreenSpaceRayHit hit,
out float hitWeight
)
{
uint2 bufferSize = uint2(_DepthPyramidSize.xy);
float3 startPositionSS;
float3 raySS;
float rayEndDepth;
CalculateRaySS(
input.rayOriginWS,
input.rayDirWS,
bufferSize,
startPositionSS,
raySS,
rayEndDepth
);
uint iteration;
return ScreenSpaceLinearRaymarch(
input,
// settings
SSRT_SETTING(RayLevel, SSRTID),
SSRT_SETTING(RayMaxIterations, SSRTID),
max(0.01, SSRT_SETTING(DepthBufferThickness, SSRTID)),
SSRT_SETTING(RayMaxScreenDistance, SSRTID),
SSRT_SETTING(RayBlendScreenDistance, SSRTID),
#ifdef DEBUG_DISPLAY
SSRT_SETTING(DebuggedAlgorithm, SSRTID),
#else
PROJECTIONMODEL_NONE,
#endif
// precomputed properties
startPositionSS,
raySS,
rayEndDepth,
bufferSize,
// out
hit,
hitWeight,
iteration
);
}
// -------------------------------------------------
// Algorithm: Scene Proxy Raycasting
// -------------------------------------------------
bool MERGE_NAME(ScreenSpaceProxyRaycast, SSRTID)(
ScreenSpaceProxyRaycastInput input,
out ScreenSpaceRayHit hit
)
{
#ifdef DEBUG_DISPLAY
int debuggedAlgorithm = int(SSRT_SETTING(DebuggedAlgorithm, SSRTID));
#else
int debuggedAlgorithm = int(PROJECTIONMODEL_NONE);
#endif
return ScreenSpaceProxyRaycast(
input,
// Settings
debuggedAlgorithm,
// Out
hit
);
}
// -------------------------------------------------
// Algorithm: HiZ raymarching
// -------------------------------------------------
bool MERGE_NAME(ScreenSpaceHiZRaymarch, SSRTID)(
ScreenSpaceRaymarchInput input,
out ScreenSpaceRayHit hit,
out float hitWeight
)
{
return ScreenSpaceHiZRaymarch(
input,
// Settings
SSRT_SETTING(RayMinLevel, SSRTID),
SSRT_SETTING(RayMaxLevel, SSRTID),
SSRT_SETTING(RayMaxIterations, SSRTID),
max(0.01, SSRT_SETTING(DepthBufferThickness, SSRTID)),
SSRT_SETTING(RayMaxScreenDistance, SSRTID),
SSRT_SETTING(RayBlendScreenDistance, SSRTID),
SSRT_SETTING(RayMarchBehindObjects, SSRTID) == 1,
#ifdef DEBUG_DISPLAY
SSRT_SETTING(DebuggedAlgorithm, SSRTID),
#else
PROJECTIONMODEL_NONE,
#endif
// out
hit,
hitWeight
);
}
// -------------------------------------------------
// Cleaning
// -------------------------------------------------
#undef SSRT_SETTING
#endif