299 lines
11 KiB
HLSL

#ifndef RGI_RAYCAST
#define RGI_RAYCAST
// Copyright 2022-2026 Kronnect - All Rights Reserved.
#include "RadiantGI_Probing.hlsl"
TEXTURE2D_X(_RadiantPrevResolve);
TEXTURE2D_X(_RadiantRSMBuffer);
TEXTURECUBE(_Probe1Cube);
TEXTURECUBE(_Probe2Cube);
SAMPLER(sampler_LinearRepeat_Cube1);
SAMPLER(sampler_LinearRepeat_Cube2);
half3 _ProbeData;
half4 _Probe1HDR, _Probe2HDR;
float4 _Probe1BoxMin, _Probe1BoxMax, _Probe1ProbePosition;
float4 _Probe2BoxMin, _Probe2BoxMax, _Probe2ProbePosition;
half3 _FallbackDefaultAmbient;
struct VaryingsRaycast {
float4 positionCS : SV_POSITION;
float4 uv : TEXCOORD0;
UNITY_VERTEX_INPUT_INSTANCE_ID
UNITY_VERTEX_OUTPUT_STEREO
};
VaryingsRaycast VertRaycast(AttributesFS input) {
VaryingsRaycast output;
UNITY_SETUP_INSTANCE_ID(input);
UNITY_TRANSFER_INSTANCE_ID(input, output);
UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(output);
output.positionCS = float4(input.positionHCS.xyz, 1.0);
#if UNITY_UV_STARTS_AT_TOP
output.positionCS.y *= -1;
#endif
output.uv.xy = input.uv;
float4 projPos = output.positionCS * 0.5;
projPos.xy = projPos.xy + projPos.w;
output.uv.zw = projPos.xy;
return output;
}
float k0, q0;
float4x4 proj;
void PrepareRay(float2 uv, float3 rayStart) {
proj = unity_CameraProjection;
#if _ORTHO_SUPPORT
float4 p0 = float4(uv, rayStart.z, 1.0);
k0 = 1.0;
q0 = rayStart.z;
#else
proj._13 *= -1; // fix for lens shift
proj._23 *= -1;
float4 sposStart = mul(proj, float4(rayStart, 1.0));
k0 = rcp(sposStart.w);
q0 = rayStart.z * k0;
#endif
}
half4 Raycast(float2 uv, float3 rayStart, float3 rayDir, float jitterOffset) {
float rayLength = RAY_MAX_LENGTH;
float3 rayEnd = rayStart + rayDir * rayLength;
if (rayEnd.z < 1) {
rayLength = abs ((1 - rayStart.z) / rayDir.z);
rayEnd = rayStart + rayDir * rayLength;
}
float4 sposEnd = mul(proj, float4(rayEnd, 1.0));
#if _ORTHO_SUPPORT
float2 uv1 = (sposEnd.xy + 1.0) * 0.5;
float4 p0 = float4(uv, rayStart.z, 1.0);
float4 p1 = float4(uv1, rayEnd.z, 1.0);
#else
float k1 = rcp(sposEnd.w);
float q1 = rayEnd.z * k1;
float2 uv1 = sposEnd.xy * rcp(rayEnd.z) * 0.5 + 0.5;
float4 p0 = float4(uv, q0, k0);
float4 p1 = float4(uv1, q1, k1);
#endif
float2 duv = uv1 - uv;
float2 duvPixel = abs(duv * _DownscaledDepthRT_TexelSize.zw);
float pixelDistance = max(duvPixel.x, duvPixel.y);
pixelDistance = max(1, pixelDistance);
int sampleCount = (int)min(pixelDistance, RAY_MAX_SAMPLES);
float rcpSampleCount = rcp((float)sampleCount);
float rcpPixelDist = rcp(pixelDistance);
float4 p = p0;
float pz;
float jitter = jitterOffset * rcpSampleCount;
bool hit = false;
for (int k = 1; k <= sampleCount && !hit; k++) {
float progress = k * rcpSampleCount;
float t = progress * progress;
t = max(t, k * rcpPixelDist);
t = t + jitter;
p = lerp(p0, p1, t);
if (any(p.xy < 0 | p.xy >= 1)) return 0;
float sceneDepth = GetLinearEyeDownscaledDepth(p.xy);
#if _ORTHO_SUPPORT
pz = p.z;
#else
pz = p.z * rcp(p.w);
#endif
float depthDiff = pz - sceneDepth;
hit = depthDiff > 0.02 && depthDiff < THICKNESS;
}
if (hit) {
float zdist = rayLength * (pz - rayStart.z) / (0.0001 + rayEnd.z - rayStart.z);
// quadratic distance attenuation
half distSqr = zdist * zdist;
half distAtten = rcp(1.0 + distSqr);
// indirect term - apply motion compensation when extra bounce is enabled
float2 hitSampleUV = p.xy;
#if _ONE_EXTRA_BOUNCE
float2 hitVelocity = GetVelocity(p.xy);
float2 prevUV = p.xy - hitVelocity;
hitSampleUV = all(prevUV >= 0.0) && all(prevUV < 1.0) ? prevUV : p.xy;
#endif
half3 indirect = SAMPLE_TEXTURE2D_X_LOD(_MainTex, sampler_PointClamp, hitSampleUV, 0).rgb; // point clamp to avoid color bleed
indirect = clamp(indirect, 0, 32); // keep source data under reasonable range and avoid NaN
half invDistSqrWeight = lerp(1.0, distAtten, INDIRECT_DISTANCE_ATTENUATION);
indirect *= invDistSqrWeight;
return half4(indirect, 1.0);
}
return 0; // miss
}
float3 GetTangent(float3 v) {
return abs(v.x) > abs(v.z) ? float3(-v.y, v.x, 0.0) : float3(0.0, -v.z, v.y);
}
// Cosine-weighted hemisphere sample using precomputed TBN and sphere sin/cos
float3 GetJitteredNormal(float u, float s, float c, float3 norm, float3 tangent, float3 bitangent) {
float omu = 1.0f - u;
float z = omu * rsqrt(max(omu, 1e-8));
float r = u * rsqrt(max(u, 1e-8));
return tangent * (c * r) + bitangent * (s * r) + norm * z;
}
float AnimateNoise(float3 magic, float2 pixCoord, int frameCount) {
float2 frameMagicScale = float2(2.083f, 4.867f);
pixCoord += frameCount * frameMagicScale;
return frac(magic.z * 52 * frac(dot(pixCoord, magic.xy)));
}
half4 FragRaycast (VaryingsRaycast input) : SV_Target {
UNITY_SETUP_INSTANCE_ID(input);
UNITY_SETUP_STEREO_EYE_INDEX_POST_VERTEX(input);
float2 uv = UnityStereoTransformScreenSpaceTex(input.uv.xy);
//float rawDepth = SAMPLE_TEXTURE2D_X(_CameraDepthTexture, sampler_PointClamp, input.uv.xy).r;
float rawDepth = GetDownscaledRawDepth(input.uv.xy); // faster
// exclude skybox
if (IsSkyBox(rawDepth)) return 0;
float3 rayStart = GetViewSpacePosition(input.uv.zw, rawDepth);
float2 pos = uv * SOURCE_SIZE;
float3 normalWS = GetWorldNormal((uint2)pos);
float3 normalVS = mul((float3x3)_WorldToViewDir, normalWS);
normalVS.z *= -1.0;
// Precompute TBN in view space
float3 tangentVS = normalize(GetTangent(normalVS));
float3 bitangentVS = cross(tangentVS, normalVS);
half4 indirect = 0;
// More uniform noise
float2 noisePos = pos / DOWNSAMPLING;
float3 noises = SAMPLE_TEXTURE2D_LOD(_NoiseTex, sampler_PointRepeat, noisePos * _NoiseTex_TexelSize.xy, 0).xyz;
noises.z = AnimateNoise(noises, noisePos, FRAME_NUMBER);
float jitterOffset = noises.y * JITTER_AMOUNT;
PrepareRay(uv, rayStart);
// Reuse sincos across rays
float sSC, cSC;
sincos(2.0f * PI * noises.z, sSC, cSC);
float3 wpos = GetWorldPosition(uv, rawDepth);
half totalProbeWeight = 0;
float goldenRatioAcum = GOLDEN_RATIO_ACUM;
#if _USES_MULTIPLE_RAYS
for (int k=0; k<RAY_COUNT; k++) {
float u = frac(noises.x + goldenRatioAcum);
float3 rayDirVS = GetJitteredNormal(u, sSC, cSC, normalVS, tangentVS, bitangentVS);
half4 indirectSample = Raycast(uv, rayStart, rayDirVS, jitterOffset);
if (indirectSample.w == 0) { // add probe contribution
half probeWeight = 0;
float3 rayDirWS = mul((float3x3)UNITY_MATRIX_I_V, rayDirVS);
#if _FALLBACK_PROBE_ATLAS && USE_CLUSTER_LIGHT_LOOP
indirectSample.rgb = SampleReflectionProbesWithWeight(rayDirWS, wpos, uv, probeWeight) * _ProbeData.z;
probeWeight *= _ProbeData.z;
#elif (_FALLBACK_1_PROBE || _FALLBACK_2_PROBES)
indirectSample.rgb = SampleProbe(_Probe1Cube, sampler_LinearRepeat_Cube1, _Probe1HDR, rayDirWS, wpos, _Probe1ProbePosition, _Probe1BoxMin, _Probe1BoxMax) * _ProbeData.x;
probeWeight = _ProbeData.x;
#if _FALLBACK_2_PROBES
indirectSample.rgb += SampleProbe(_Probe2Cube, sampler_LinearRepeat_Cube2, _Probe2HDR, rayDirWS, wpos, _Probe2ProbePosition, _Probe2BoxMin, _Probe2BoxMax) * _ProbeData.y;
probeWeight += _ProbeData.y;
#endif
#endif
totalProbeWeight += probeWeight;
}
goldenRatioAcum += 0.618033989f; // GOLDEN_RATIO;
indirect.rgb += indirectSample.rgb;
indirect.w += indirectSample.w;
}
// Average results across multiple rays
float rcpRaysCount = rcp((float)RAY_COUNT);
indirect.rgb *= rcpRaysCount;
totalProbeWeight *= rcpRaysCount;
#else
float u = frac(noises.x + goldenRatioAcum);
float3 rayDirVS = GetJitteredNormal(u, sSC, cSC, normalVS, tangentVS, bitangentVS);
indirect = Raycast(uv, rayStart, rayDirVS, jitterOffset);
if (indirect.w == 0) {
float3 rayDirWS = mul((float3x3)UNITY_MATRIX_I_V, rayDirVS);
#if _FALLBACK_PROBE_ATLAS && USE_CLUSTER_LIGHT_LOOP
indirect.rgb = SampleReflectionProbesWithWeight(rayDirWS, wpos, uv, totalProbeWeight) * _ProbeData.z;
totalProbeWeight *= _ProbeData.z;
#elif (_FALLBACK_1_PROBE || _FALLBACK_2_PROBES)
indirect.rgb = SampleProbe(_Probe1Cube, sampler_LinearRepeat_Cube1, _Probe1HDR, rayDirWS, wpos, _Probe1ProbePosition, _Probe1BoxMin, _Probe1BoxMax) * _ProbeData.x;
totalProbeWeight = _ProbeData.x;
#if _FALLBACK_2_PROBES
indirect.rgb += SampleProbe(_Probe2Cube, sampler_LinearRepeat_Cube2, _Probe2HDR, rayDirWS, wpos, _Probe2ProbePosition, _Probe2BoxMin, _Probe2BoxMax) * _ProbeData.y;
totalProbeWeight += _ProbeData.y;
#endif
#endif
}
#endif
// Fill remaining weight with APV/Sky + RSM + Feedback from previous frames + minimum ambient
half remainingWeight = saturate(1.0 - totalProbeWeight);
UNITY_BRANCH
if (remainingWeight > 0.01) {
half3 remainingLight = SampleAmbientLighting(wpos, normalWS, uv * SOURCE_SIZE) * FALLBACK_APV_SKY;
#if _FALLBACK_RSM
half3 rsmLight = SAMPLE_TEXTURE2D_X_LOD(_RadiantRSMBuffer, sampler_LinearClamp, uv, 0).rgb;
remainingLight += rsmLight;
#endif
#if _REUSE_RAYS
float2 velocity = GetVelocity(uv);
float2 prevUV = uv - velocity;
if (all(prevUV >= 0.0 && prevUV <= 1.0)) {
half4 prevResolve = SAMPLE_TEXTURE2D_X_LOD(_RadiantPrevResolve, sampler_PointClamp, prevUV, 0);
float currEyeDepth = RawToLinearEyeDepth(rawDepth);
float prevEyeDepth = prevResolve.w;
float depthThreshold = max(0.5, currEyeDepth * 0.05);
if (prevEyeDepth > 0 && abs(currEyeDepth - prevEyeDepth) < depthThreshold) {
half wMotion = GetMotionAtten(velocity);
half w = saturate(RAY_REUSE_INTENSITY * wMotion);
remainingLight = lerp(remainingLight, prevResolve.rgb, w);
}
}
#endif
remainingLight = max(remainingLight, _FallbackDefaultAmbient);
indirect.rgb += remainingLight * remainingWeight;
}
half luma = GetLuma(indirect.rgb);
half lumaClamped = saturate(INDIRECT_MAX_BRIGHTNESS * rcp(luma + 0.001h));
indirect.rgb *= lumaClamped;
indirect.rgb *= (half)(luma > LUMA_THRESHOLD);
half eyeDepth = RawToLinearEyeDepth(rawDepth);
indirect.w = eyeDepth;
return indirect;
}
#endif // RGI_RAYCAST