This is my implementation of Alan Zucconi’s tutorial on Volumetric Rendering.

This is a vertex and fragment shader.
An important observation I made is the order of declaring functions is important. A function must be declared before it can be called by another function.
I expanded on the tutorial by declaring Properties, making the center of the sphere and its size available from Unity’s inspector. This way I could play with values without updating the shader each time.
|
Properties { _Center ("Center", Vector) = (0, 0, 0, 0) _Radius ("Radius", Float) = 0.5 } |
In the vertex function I get the world position of the vertex for use in the fragment function.
|
v2f vert (appdata_base input) { v2f output; output.vertex = UnityObjectToClipPos(input.vertex); output.wPos = mul(unity_ObjectToWorld, input.vertex).xyz; return output; } |
The fragment function is where most of the work in this shader happens.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
fixed4 frag (v2f input) : SV_Target { float3 worldPos = input.wPos; float3 viewDir = normalize(input.wPos - _WorldSpaceCameraPos); fixed4 color; if (raymarchHit(worldPos, viewDir)) { color = fixed4(1,0,0,1); } else { color = fixed4(1,1,1,1); } return color; } |
Full listing:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
|
Shader "Custom/VolumetricSphere" { Properties { _Center ("Center", Vector) = (0, 0, 0, 0) _Radius ("Radius", Float) = 0.5 } SubShader { Tags { "RenderType"="Opaque" } Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag #include "UnityCG.cginc" float3 _Center; float _Radius; #define STEPS 64 #define STEP_SIZE 0.01 struct v2f { float4 vertex : SV_POSITION; float3 wPos : TEXCOORD1; }; bool sphereHit(float3 pos) { return distance(pos, _Center) < _Radius; } bool raymarchHit(float3 worldPos, float3 viewDir) { for (int i = 0; i < STEPS; i++) { if (sphereHit(worldPos)) { return true; } worldPos += viewDir * STEP_SIZE; } return false; } v2f vert (appdata_base input) { v2f output; output.vertex = UnityObjectToClipPos(input.vertex); output.wPos = mul(unity_ObjectToWorld, input.vertex).xyz; return output; } fixed4 frag (v2f input) : SV_Target { float3 worldPos = input.wPos; float3 viewDir = normalize(input.wPos - _WorldSpaceCameraPos); fixed4 color; if (raymarchHit(worldPos, viewDir)) { color = fixed4(1,0,0,1); } else { color = fixed4(1,1,1,1); } return color; } ENDCG } } } |