Skip to content

Instantly share code, notes, and snippets.

@pema99
Created May 4, 2025 20:22
Show Gist options
  • Save pema99/97070ad75ad010ee57780c9441654427 to your computer and use it in GitHub Desktop.
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.
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