您最多选择25个主题
主题必须以中文或者字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符
1064 行
38 KiB
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
|