Skip to content

Instantly share code, notes, and snippets.

@pema99
Created May 4, 2025 19:38
Show Gist options
  • Save pema99/5fc65a98495a1a7207cc0e2ce0e5b078 to your computer and use it in GitHub Desktop.
Save pema99/5fc65a98495a1a7207cc0e2ce0e5b078 to your computer and use it in GitHub Desktop.
A bad implementation of screenspace reflection showing the absolute bare minimum of how to raymarch in screenspace.
Shader "pema99/SSR"
{
Properties
{
_MaxDistance("Max Distance", Range(0, 100)) = 100
_Steps("Steps", Range(1, 1000)) = 1
_Thickness("Thickness", Range(0, 1)) = 0.1
_EdgeFade("Edge Fade", Range(0, 1)) = 0.1
}
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 _Steps;
float _Thickness;
float _EdgeFade;
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;
// March depth buffer
float4 reflectedColor = 0;
float2 rayPosSS = 0;
for (int step = 0; step < _Steps; step++)
{
rayPosSS = lerp(startPosSS, endPosSS, step / _Steps);
// 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.
float rayDepth = -((startPosVS.z * endPosVS.z) / lerp(endPosVS.z, startPosVS.z, step / _Steps));
// 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 && abs(deltaDepth) < _Thickness)
{
reflectedColor = _GrabTexture.Load(int3(rayPosSS, 0));
break;
}
}
// 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