Created
March 30, 2026 10:33
-
-
Save cnlohr/fa55a462476b0e1a2b2cad1d469eae62 to your computer and use it in GitHub Desktop.
Fixed-step object tracer in Unity Shader code
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 "cnlohr/StepShader" | |
| { | |
| Properties | |
| { | |
| _MainTex ("Texture", 2D) = "white" {} | |
| _TANoiseTex ("TANoise", 2D) = "white" {} | |
| } | |
| SubShader | |
| { | |
| Tags { "RenderType"="Opaque" } | |
| Pass | |
| { | |
| CGPROGRAM | |
| #pragma vertex vert | |
| #pragma fragment frag | |
| #pragma target 5.0 | |
| #pragma multi_compile_fog | |
| #pragma enable_d3d11_debug_symbols | |
| //#pragma skip_optimizations opengl | |
| //#pragma skip_optimizations d3d11 | |
| #include "UnityCG.cginc" | |
| #include "/Assets/cnlohr/Shaders/tanoise/tanoise.cginc" | |
| #include "/Assets/cnlohr/Shaders/hashwithoutsine/hashwithoutsine.cginc" | |
| //#define glsl_mod(x,y) (((x)-(y)*floor((x)/(y)))) | |
| struct appdata | |
| { | |
| float4 vertex : POSITION; | |
| float3 normal : NORMAL; | |
| float2 uv : TEXCOORD0; | |
| }; | |
| struct v2f | |
| { | |
| float4 test : TEST; | |
| float3 rayOrigin : RAY_ORIGIN; | |
| float3 rayDir : RAY_DIR; | |
| float3 worldPos : WORLD_POS; | |
| float3 worldNormal : WORLD_NORMAL; | |
| float2 uv : TEXCOORD0; | |
| UNITY_FOG_COORDS(1) | |
| float4 vertex : SV_POSITION; | |
| }; | |
| sampler2D _MainTex; | |
| float4 _MainTex_ST; | |
| v2f vert (appdata v) | |
| { | |
| v2f o; | |
| o.vertex = UnityObjectToClipPos(v.vertex); | |
| o.uv = TRANSFORM_TEX(v.uv, _MainTex); | |
| UNITY_TRANSFER_FOG(o,o.vertex); | |
| // https://github.com/cnlohr/shadertrixx?tab=readme-ov-file#raycasting-with-orthographic-and-normal-cameras | |
| // I saw these ortho shadow substitutions in a few places, but bgolus explains them | |
| // https://bgolus.medium.com/rendering-a-sphere-on-a-quad-13c92025570c | |
| float howOrtho = UNITY_MATRIX_P._m33; // instead of unity_OrthoParams.w | |
| float3 worldSpaceCameraPos = UNITY_MATRIX_I_V._m03_m13_m23; // instead of _WorldSpaceCameraPos | |
| float3 worldPos = mul(unity_ObjectToWorld, v.vertex); | |
| float3 cameraToVertex = worldPos - worldSpaceCameraPos; | |
| float3 orthoFwd = -UNITY_MATRIX_I_V._m02_m12_m22; // often seen: -UNITY_MATRIX_V[2].xyz; | |
| float3 orthoRayDir = orthoFwd * dot(cameraToVertex, orthoFwd); | |
| // start from the camera plane (can also just start from o.vertex if your scene is contained within the geometry) | |
| float3 orthoCameraPos = worldPos - orthoRayDir; | |
| o.rayOrigin = lerp(worldSpaceCameraPos, orthoCameraPos, howOrtho ); | |
| o.rayDir = normalize( lerp( cameraToVertex, orthoRayDir, howOrtho ) ); | |
| o.worldPos = worldPos; | |
| o.test = howOrtho; | |
| o.worldNormal = mul(unity_ObjectToWorld, v.normal); | |
| return o; | |
| } | |
| float4 SphereFunction ( float3 pos ) | |
| { | |
| return float4( 1.0.xxx, 1.0-saturate( length( pos.xyz ) ) ); | |
| } | |
| float4 MultiSphereFunction ( float3 pos ) | |
| { | |
| return float4( 1.0.xxx, 1.0-saturate( length( glsl_mod(pos.xyz,1.0)-0.5 ) ) ); | |
| } | |
| float4 ColorMultiSphereFunction ( float3 pos ) | |
| { | |
| return float4( chash33(floor(pos.xyz)), 1.0-saturate( length( glsl_mod(pos.xyz,1.0)-0.5 ) ) ); | |
| } | |
| float4 ColorMovingMultiSphereFunction ( float3 pos ) | |
| { | |
| float3 sphid = floor(pos.xyz); | |
| float4 spho = tanoise4_hq( float4( sphid, glsl_mod( _Time.y, 1000 ) ) ); | |
| return float4( chash33(sphid), 1.0-saturate( length( glsl_mod(pos.xyz,1.0)-0.5 - (spho.xyz-0.5)*0.5 ) ) ); | |
| } | |
| float4 VoroiSphereSlave( int3 sphid, float3 relPlace ) | |
| { | |
| float4 spho = tanoise4_hq( float4( sphid, glsl_mod( _Time.y*0.1, 1000 ) ) ); | |
| float4 ret = float4( chash33(sphid), 1.0-saturate(2.0*length( relPlace.xyz+(spho.xyz-0.5) ) ) ); | |
| //float4 ret = float4( sphid&1, saturate(1.0-(2.0*length(relPlace)))); | |
| ret.xyz *= ret.w; | |
| return ret; | |
| } | |
| float4 MetaBall( int mb, float3 pos ) | |
| { | |
| float4 spho = tanoise2( float2( mb*3.0, glsl_mod( _Time.y, 1000 ) ) ); | |
| float4 ret = float4( chash31(mb), 1.0-saturate(2.0*length( pos.xyz + spho.xyz ) ) ); | |
| ret.xyz *= ret.w; | |
| return ret; | |
| } | |
| float4 ThreeBalls ( float3 pos ) | |
| { | |
| // At each point, look for 8 closest spheres and create a metasurface. | |
| pos /= 3.0; | |
| pos -= 0.5; | |
| float4 sum = MetaBall( 1, pos ); | |
| sum += MetaBall( 2, pos ); | |
| sum += MetaBall( 3, pos ); | |
| sum.a += 0.7; | |
| return sum; | |
| } | |
| float sdTorus( float3 p, float2 t ) | |
| { | |
| float2 q = float2(length(p.xz)-t.x,p.y); | |
| return length(q)-t.y; | |
| } | |
| float4 Torus( float3 pos ) | |
| { | |
| // At each point, look for 8 closest spheres and create a metasurface. | |
| //pos /= 3.0; | |
| float4 ret = float4( 1.0.xxx, 1.0-sdTorus( pos, float2( 1.0, -0.50 ) ) ); | |
| ret.rgb *= ret.a; | |
| ret.a += 0.7; | |
| return ret; | |
| } | |
| float4 frag (v2f i) : SV_Target | |
| { | |
| float4 col = 0.; | |
| int step; | |
| float3 rayDir = normalize( i.worldPos - i.rayOrigin ); | |
| //normalize( i.rayDir ); | |
| const float maxDepth = 15.0; | |
| const int maxSteps = 100; | |
| float distancePerStep = maxDepth / maxSteps; | |
| float stepSpeedFromGlancingAngle = -1.0 / dot( i.worldNormal, rayDir ); | |
| float3 rayDirStep = rayDir * distancePerStep | |
| ; | |
| // * stepSpeedFromGlancingAngle; | |
| float3 pos = i.worldPos; | |
| // Check world position | |
| //return float4( glsl_mod( pos, 1.0 ), 1.0 ); | |
| //return float4( rayDir, 1.0 ); | |
| float opacity = 0.0; | |
| float distanceRemain = 1.0; | |
| float nearDerate = 0.0; | |
| for( step = 0; step < maxSteps; step++ ) | |
| { | |
| //float4 value = float4( 1.0, 1.0, 1.0, tanoise4_1d( float4( pos*1.0, _Time.y*0.1) ) ); | |
| //float4 value = tanoise4_hq( float4( pos*1.0, glsl_mod( _Time.y*0.5, 1000.0) ) ); | |
| //float4 value = SphereFunction( pos ); | |
| //float4 value = MultiSphereFunction( pos ); | |
| //float4 value = ColorMultiSphereFunction( pos ); | |
| //float4 value = ColorMovingMultiSphereFunction( pos ); | |
| //float4 value = VoronoiSpheres( pos ); | |
| //float4 value = ThreeBalls( pos ); | |
| float4 value = Torus( pos ); | |
| float nearDen = saturate( ( value.a - 0.7 ) * 5.0 ); | |
| // If very close to the glass, derate. | |
| nearDen *= saturate( nearDerate ); | |
| nearDerate += 4.0 / maxSteps; | |
| pos += rayDirStep; | |
| col += (nearDen) * saturate(1.0 - opacity ) * distanceRemain * float4( value.rgb, 1.0 ); | |
| opacity += nearDen*0.5; | |
| distanceRemain -= 1.0/maxSteps; | |
| if( opacity > 1.2 ) break; | |
| } | |
| col = col; | |
| UNITY_APPLY_FOG(i.fogCoord, col); | |
| return col; | |
| } | |
| ENDCG | |
| } | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment