Created
May 4, 2025 19:38
-
-
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.
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)) = 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