Created
May 4, 2025 20:22
-
-
Save pema99/97070ad75ad010ee57780c9441654427 to your computer and use it in GitHub Desktop.
Mediocre SSR implementation, demonstrating coarse tracing followed by binary search to refine the position.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Shader "pema99/SSR" | |
{ | |
Properties | |
{ | |
_MaxDistance("Max Distance", Range(0, 100)) = 10 | |
_Sparsity("Ray Sparsity", Range(0, 1)) = 1.0 | |
_Thickness("Thickness", Range(0, 1)) = 0.1 | |
_EdgeFade("Edge Fade", Range(0, 1)) = 0.1 | |
_Steps("Refinement Steps", Range(1, 20)) = 5 | |
} | |
SubShader | |
{ | |
Tags { "Queue" = "Transparent" } | |
GrabPass { "_GrabTexture" } | |
Pass | |
{ | |
CGPROGRAM | |
#pragma vertex vert | |
#pragma fragment frag | |
#include "UnityCG.cginc" | |
struct appdata | |
{ | |
float4 vertexOS : POSITION; | |
float3 normalOS : NORMAL; | |
}; | |
struct v2f | |
{ | |
float4 vertexCS : SV_POSITION; | |
float4 posCS : TEXCOORD0; | |
float3 posVS : TEXCOORD1; | |
float3 normalVS : NORMAL; | |
}; | |
v2f vert (appdata v) | |
{ | |
v2f o; | |
o.vertexCS = UnityObjectToClipPos(v.vertexOS); | |
o.posCS = o.vertexCS; | |
o.posVS = UnityObjectToViewPos(v.vertexOS); | |
o.normalVS = mul(UNITY_MATRIX_V, mul(unity_ObjectToWorld, float4(v.normalOS, 0.0))).xyz; | |
return o; | |
} | |
float _MaxDistance; | |
float _Sparsity; | |
float _Thickness; | |
float _EdgeFade; | |
float _Steps; | |
SamplerState sampler_CameraDepthTexture; | |
Texture2D _CameraDepthTexture; | |
float4 _GrabTexture_TexelSize; | |
Texture2D _GrabTexture; | |
float4 frag (v2f i) : SV_Target | |
{ | |
uint2 screenResolution = _GrabTexture_TexelSize.zw; | |
// Calculate ray in view-space | |
float4 startPosVS = float4(i.posVS, 1); | |
float3 rayDirVS = normalize(reflect(normalize(i.posVS), normalize(i.normalVS))); | |
float4 endPosVS = float4(startPosVS + rayDirVS * _MaxDistance, 1); | |
// Convert to screen space | |
float4 startPosUV = mul(UNITY_MATRIX_P, startPosVS); | |
startPosUV /= startPosUV.w; | |
startPosUV = startPosUV * 0.5 + 0.5; | |
startPosUV.y = 1 - startPosUV.y; | |
float2 startPosSS = startPosUV.xy * screenResolution; | |
float4 endPosUV = mul(UNITY_MATRIX_P, endPosVS); | |
endPosUV /= endPosUV.w; | |
endPosUV = endPosUV * 0.5 + 0.5; | |
endPosUV.y = 1 - endPosUV.y; | |
float2 endPosSS = endPosUV.xy * screenResolution; | |
float deltaX = endPosSS.x - startPosSS.x; | |
float deltaY = endPosSS.y - startPosSS.y; | |
float useX = abs(deltaX) >= abs(deltaY) ? 1.0 : 0.0; | |
float delta = lerp(abs(deltaY), abs(deltaX), useX) * clamp(_Sparsity, 0.0, 1.0); | |
float2 rayStep = float2(deltaX, deltaY) / max(delta, 0.001); | |
// March depth buffer | |
float2 rayPosSS = startPosSS; | |
float t0 = 0, t1 = 0; | |
bool hitCoarse = false; | |
for (int step = 0; step < delta; step++) | |
{ | |
rayPosSS += rayStep; | |
// We went off screen, fall back to cubemap | |
if (rayPosSS.x < 0 || rayPosSS.x >= screenResolution.x || rayPosSS.y < 0 || rayPosSS.y >= screenResolution.y) | |
{ | |
break; | |
} | |
// Get depth of current point on ray using perspective-correct interpolation | |
// (https://www.comp.nus.edu.sg/~lowkl/publications/lowk_persp_interp_techrep.pdf) | |
// Also, viewspace Z is negated in Unity. | |
t1 = lerp((rayPosSS.y - startPosSS.y) / deltaY, (rayPosSS.x - startPosSS.x) / deltaX, useX); | |
float rayDepth = -((startPosVS.z * endPosVS.z) / lerp(endPosVS.z, startPosVS.z, t1)); | |
// Get scene depth at current point on ray | |
float sceneDepth = LinearEyeDepth(_CameraDepthTexture.Load(int3(rayPosSS, 0)).r); | |
// Compare depths, if the ray depth is behind the scene depth, we hit something | |
float deltaDepth = rayDepth - sceneDepth; | |
if (deltaDepth > 0 && deltaDepth < _Thickness) | |
{ | |
hitCoarse = true; | |
break; | |
} | |
else | |
{ | |
t0 = t1; | |
} | |
} | |
// Refine hit position | |
bool hitFine = false; | |
float lastGoodT = t1; | |
if (hitCoarse) | |
{ | |
for (int step = 0; step < _Steps; step++) | |
{ | |
float t = t0 + (t1 - t0) * 0.5; | |
rayPosSS = lerp(startPosSS, endPosSS, t); | |
float rayDepth = -((startPosVS.z * endPosVS.z) / lerp(endPosVS.z, startPosVS.z, t1)); | |
float sceneDepth = LinearEyeDepth(_CameraDepthTexture.Load(int3(rayPosSS, 0)).r); | |
float deltaDepth = rayDepth - sceneDepth; | |
if (deltaDepth > 0 && deltaDepth < _Thickness) | |
{ | |
hitFine = true; | |
lastGoodT = t; | |
t1 = t0 + (t1 - t0) * 0.5; | |
} | |
else | |
{ | |
t0 = t0 + (t1 - t0) * 0.5; | |
} | |
} | |
} | |
float4 reflectedColor = 0; | |
if (hitFine) | |
{ | |
rayPosSS = lerp(startPosSS, endPosSS, lastGoodT); | |
reflectedColor = _GrabTexture.Load(int3(rayPosSS, 0)); | |
} | |
// Ignore SSR color if we didn't hit anything | |
float fade = reflectedColor.a; | |
// Reflected ray pointing to camera, attenuate | |
fade *= (1.0 - max(dot(-normalize(i.posVS), rayDirVS), 0)); | |
// Near edge of screen, attenuate | |
float2 rayPosUV = rayPosSS / screenResolution; | |
float xfade = smoothstep(0, _EdgeFade, rayPosUV.x)*smoothstep(1, 1-_EdgeFade, rayPosUV.x); | |
float yfade = smoothstep(0, _EdgeFade, rayPosUV.y)*smoothstep(1, 1-_EdgeFade, rayPosUV.y); | |
fade *= xfade * yfade; | |
// Fade towards fallback cubemap | |
float4 skyData = UNITY_SAMPLE_TEXCUBE(unity_SpecCube0, mul(unity_CameraToWorld, rayDirVS)); | |
float3 fallbackColor = DecodeHDR(skyData, unity_SpecCube0_HDR); | |
return float4(lerp(fallbackColor, reflectedColor, fade), 1); | |
} | |
ENDCG | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment