222 lines
7.2 KiB
Plaintext
222 lines
7.2 KiB
Plaintext
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Lighting/ScreenSpaceLighting/GTAOCommon.hlsl"
|
|
#include "Packages/com.unity.render-pipelines.high-definition/Runtime/Material/NormalBuffer.hlsl"
|
|
|
|
//#pragma enable_d3d11_debug_symbols
|
|
|
|
#pragma kernel GTAOMain
|
|
|
|
#pragma multi_compile HALF_RES FULL_RES
|
|
#pragma multi_compile _ TEMPORAL
|
|
|
|
// --------------------------------------------
|
|
// Options
|
|
// --------------------------------------------
|
|
|
|
// Integral type
|
|
#define INTEGRAL_UNIFORM 0
|
|
#define INTEGRAL_COSINE 1
|
|
#define INTEGRAL_TYPE INTEGRAL_COSINE
|
|
|
|
// Noise options
|
|
#define TEMPORAL_ROTATION defined(TEMPORAL)
|
|
#define ENABLE_TEMPORAL_OFFSET defined(TEMPORAL)
|
|
|
|
// Normal fetching options
|
|
#define NORMAL_FROM_GBUFFER 1
|
|
#define NORMAL_COMPUTATION NORMAL_FROM_GBUFFER
|
|
|
|
// --------------------------------------------
|
|
// Integration functions
|
|
// --------------------------------------------
|
|
|
|
float IntegrateArcUniform(float horizon1, float horizon2)
|
|
{
|
|
return (1.0f - cos(horizon1) + (1.0 - cos(horizon2)));
|
|
}
|
|
|
|
float IntegrateArcCosWeighted(float horzion1, float horizon2, float n, float cosN)
|
|
{
|
|
float h1 = horzion1 * 2.0;
|
|
float h2 = horizon2 * 2.0;
|
|
float sinN = sin(n);
|
|
return 0.25 * ((-cos(h1 - n) + cosN + h1 * sinN) + (-cos(h2 - n) + cosN + h2 * sinN));
|
|
}
|
|
|
|
float UpdateHorizon(float maxH, float candidateH, float distSq)
|
|
{
|
|
float falloff = saturate((1.0 - (distSq * _AOInvRadiusSq)));
|
|
|
|
return (candidateH > maxH) ? lerp(maxH, candidateH, falloff) : lerp(maxH, candidateH, 0.03f); // TODO: Thickness heuristic here.
|
|
}
|
|
|
|
// --------------------------------------------
|
|
// Direction functions
|
|
// --------------------------------------------
|
|
|
|
float2 GetDirection(uint2 positionSS, int offset)
|
|
{
|
|
float noise = InterleavedGradientNoise(positionSS.xy, 0);
|
|
float rotations[] = { 60.0, 300.0, 180.0, 240.0, 120.0, 0.0 };
|
|
|
|
#if TEMPORAL_ROTATION
|
|
float rotation = (rotations[_AOTemporalRotationIdx] / 360.0);
|
|
#else
|
|
float rotation = (rotations[offset] / 360.0);
|
|
#endif
|
|
|
|
noise += rotation;
|
|
noise *= PI;
|
|
|
|
return float2(cos(noise), sin(noise));
|
|
}
|
|
|
|
// --------------------------------------------
|
|
// Get sample start offset
|
|
// --------------------------------------------
|
|
|
|
float GetOffset(uint2 positionSS)
|
|
{
|
|
// Spatial offset
|
|
float offset = 0.25 * ((positionSS.y - positionSS.x) & 0x3);
|
|
|
|
// Temporal offset
|
|
#if ENABLE_TEMPORAL_OFFSET
|
|
float offsets[] = { 0.0, 0.5, 0.25, 0.75 };
|
|
offset += offsets[_AOTemporalOffsetIdx];
|
|
#endif
|
|
return frac(offset);
|
|
}
|
|
|
|
// --------------------------------------------
|
|
// Input generation functions
|
|
// --------------------------------------------
|
|
|
|
float3 GetPositionVS(float2 positionSS, float depth)
|
|
{
|
|
float linearDepth = LinearEyeDepth(depth, _ZBufferParams);
|
|
return float3((positionSS * _AODepthToViewParams.xy - _AODepthToViewParams.zw) * linearDepth, linearDepth);
|
|
}
|
|
|
|
float3 GetPositionSampleVS(float2 positionSS, out float depth)
|
|
{
|
|
float linearDepth = LinearEyeDepth(depth, _ZBufferParams);
|
|
return float3((positionSS * _AODepthToViewParams.xy - _AODepthToViewParams.zw) * linearDepth, linearDepth);
|
|
}
|
|
|
|
float3 GetNormalVS(float4 normalBufferData)
|
|
{
|
|
NormalData normalData;
|
|
DecodeFromNormalBuffer(normalBufferData, normalData);
|
|
float3 normalVS = normalize(mul((float3x3)UNITY_MATRIX_V, normalData.normalWS));
|
|
return float3(normalVS.xy, -normalVS.z);
|
|
}
|
|
|
|
// --------------------------------------------
|
|
// Kernel
|
|
// --------------------------------------------
|
|
|
|
float HorizonLoop(float3 positionVS, float3 V, float2 rayStart, float2 rayDir, float rayOffset, float rayStep, int mipModifier)
|
|
{
|
|
float maxHorizon = -1.0f; // cos(pi)
|
|
float t = rayOffset * rayStep + rayStep;
|
|
|
|
const uint startWithLowerRes = min(max(0, _AOStepCount / 2 - 2), 3);
|
|
for (uint i = 0; i < _AOStepCount; i++)
|
|
{
|
|
float2 samplePos = max(2, min(rayStart + t * rayDir, _AOBufferSize.xy - 2));
|
|
|
|
// Find horizons at these steps:
|
|
float sampleDepth = GetDepthSample(samplePos, i > startWithLowerRes);
|
|
float3 samplePosVS = GetPositionVS(samplePos.xy, sampleDepth);
|
|
|
|
float3 deltaPos = samplePosVS - positionVS;
|
|
float deltaLenSq = dot(deltaPos, deltaPos);
|
|
|
|
float currHorizon = dot(deltaPos, V) * rsqrt(deltaLenSq);
|
|
maxHorizon = UpdateHorizon(maxHorizon, currHorizon, deltaLenSq);
|
|
|
|
t += rayStep;
|
|
}
|
|
|
|
return maxHorizon;
|
|
}
|
|
|
|
RW_TEXTURE2D_X(float, _AOPackedData);
|
|
|
|
[numthreads(8,8,1)]
|
|
void GTAOMain(uint3 dispatchThreadId : SV_DispatchThreadID)
|
|
{
|
|
UNITY_XR_ASSIGN_VIEW_INDEX(dispatchThreadId.z);
|
|
|
|
// Read buffers as early as possible.
|
|
float currDepth = GetDepthForCentral(dispatchThreadId.xy);
|
|
float3 positionVS = GetPositionVS(dispatchThreadId.xy, currDepth);
|
|
|
|
#if HALF_RES
|
|
float4 normalBufferData = LOAD_TEXTURE2D_X(_NormalBufferTexture, dispatchThreadId.xy * 2);
|
|
#else
|
|
float4 normalBufferData = LOAD_TEXTURE2D_X(_NormalBufferTexture, dispatchThreadId.xy);
|
|
#endif
|
|
|
|
float offset = GetOffset(dispatchThreadId.xy);
|
|
float2 rayStart = dispatchThreadId.xy;
|
|
float integral = 0;
|
|
|
|
#ifdef TEMPORAL
|
|
const int dirCount = 1;
|
|
#else
|
|
const int dirCount = _AODirectionCount;
|
|
#endif
|
|
|
|
float3 V = normalize(-positionVS);
|
|
float fovCorrectedradiusSS = clamp(_AORadius * _AOFOVCorrection * rcp(positionVS.z), _AOStepCount, _AOMaxRadiusInPixels);
|
|
float step = max(1, fovCorrectedradiusSS * _AOInvStepCountPlusOne);
|
|
|
|
// Note: We unroll here for Metal to work around a Metal shader compiler crash
|
|
#if defined(TEMPORAL) || defined(SHADER_API_METAL)
|
|
[unroll]
|
|
#endif
|
|
for (int i = 0; i < dirCount; ++i)
|
|
{
|
|
float2 dir = GetDirection(dispatchThreadId.xy, i);
|
|
|
|
// NOTE: Work around a shader compilation bug, where removing the tiny epsilon causes
|
|
// incorrect output on some drivers. The epsilon should be small enough to not affect
|
|
// the output.
|
|
float2 negDir = -dir + 1e-30;
|
|
|
|
// Find horizons
|
|
float2 maxHorizons;
|
|
maxHorizons.x = HorizonLoop(positionVS, V, rayStart, dir, offset, step, 0);
|
|
maxHorizons.y = HorizonLoop(positionVS, V, rayStart, negDir, offset, step, 0);
|
|
|
|
// We now can transform normal data into normal in view space (latency from read should have been hidden as much as possible)
|
|
float3 normalVS = GetNormalVS(normalBufferData);
|
|
|
|
// Integrate horizons
|
|
float3 sliceN = normalize(cross(float3(dir.xy, 0.0f), V.xyz));
|
|
float3 projN = normalVS - sliceN * dot(normalVS, sliceN);
|
|
float projNLen = length(projN);
|
|
float cosN = dot(projN / projNLen, V);
|
|
|
|
float3 T = cross(V, sliceN);
|
|
float N = -sign(dot(projN, T)) * GTAOFastAcos(cosN);
|
|
|
|
// Now we find the actual horizon angles
|
|
maxHorizons.x = -GTAOFastAcos(maxHorizons.x);
|
|
maxHorizons.y = GTAOFastAcos(maxHorizons.y);
|
|
maxHorizons.x = N + max(maxHorizons.x - N, -HALF_PI);
|
|
maxHorizons.y = N + min(maxHorizons.y - N, HALF_PI);
|
|
integral += AnyIsNaN(maxHorizons) ? 1 : IntegrateArcCosWeighted(maxHorizons.x, maxHorizons.y, N, cosN);
|
|
}
|
|
|
|
integral /= dirCount;
|
|
|
|
if (currDepth == UNITY_RAW_FAR_CLIP_VALUE || integral < -1e-2f)
|
|
{
|
|
integral = 1;
|
|
}
|
|
|
|
_AOPackedData[COORD_TEXTURE2D_X(dispatchThreadId.xy)] = PackAOOutput(saturate(integral), currDepth);
|
|
}
|