Maths behind Global Fog Shader ?

Hello everyone !

I had to play with post-process effects with Unity3D, and I finally ended with looking at the “Global Fog” Shader script.
But there are things I’m a bit confused with, mostly mathematics.

Everything I said from this point is what I think have understood from the code, so feel free to correct me if I’m wrong :slight_smile:

Here, in the OnRenderImage function, a 4x4 Matrix is created, where each rows contains the direction of each “edge” of the far plane.

function OnRenderImage (source : RenderTexture, destination : RenderTexture) {	
		if(CheckResources()==false) {
			Graphics.Blit (source, destination);
			return;
		}
			
		CAMERA_NEAR = camera.nearClipPlane;
		CAMERA_FAR = camera.farClipPlane;
		CAMERA_FOV = camera.fieldOfView;
		CAMERA_ASPECT_RATIO = camera.aspect;
	
		var frustumCorners : Matrix4x4 = Matrix4x4.identity;		
		var vec : Vector4;
		var corner : Vector3;
	
		var fovWHalf : float = CAMERA_FOV * 0.5f;
		
		var toRight : Vector3 = camera.transform.right * CAMERA_NEAR * Mathf.Tan (fovWHalf * Mathf.Deg2Rad) * CAMERA_ASPECT_RATIO;
		var toTop : Vector3 = camera.transform.up * CAMERA_NEAR * Mathf.Tan (fovWHalf * Mathf.Deg2Rad);
	
		var topLeft : Vector3 = (camera.transform.forward * CAMERA_NEAR - toRight + toTop);
		var CAMERA_SCALE : float = topLeft.magnitude * CAMERA_FAR/CAMERA_NEAR;	
			
		topLeft.Normalize();
		topLeft *= CAMERA_SCALE;
	
		var topRight : Vector3 = (camera.transform.forward * CAMERA_NEAR + toRight + toTop);
		topRight.Normalize();
		topRight *= CAMERA_SCALE;
		
		var bottomRight : Vector3 = (camera.transform.forward * CAMERA_NEAR + toRight - toTop);
		bottomRight.Normalize();
		bottomRight *= CAMERA_SCALE;
		
		var bottomLeft : Vector3 = (camera.transform.forward * CAMERA_NEAR - toRight - toTop);
		bottomLeft.Normalize();
		bottomLeft *= CAMERA_SCALE;
				
		frustumCorners.SetRow (0, topLeft); 
		frustumCorners.SetRow (1, topRight);		
		frustumCorners.SetRow (2, bottomRight);
		frustumCorners.SetRow (3, bottomLeft);		
								
		fogMaterial.SetMatrix ("_FrustumCornersWS", frustumCorners);
		fogMaterial.SetVector ("_CameraWS", camera.transform.position);
		fogMaterial.SetVector ("_StartDistance", Vector4 (1.0f / startDistance, (CAMERA_SCALE-startDistance)));
		fogMaterial.SetVector ("_Y", Vector4 (height, 1.0f / heightScale));
		
		fogMaterial.SetFloat ("_GlobalDensity", globalDensity * 0.01f);
		fogMaterial.SetColor ("_FogColor", globalFogColor);
		
		CustomGraphicsBlit (source, destination, fogMaterial, fogMode);
	}

Ok, so no we blit your source on a quad. The MultiTexCoord2 isn’t a problem, but I don’t really understand the point of making the quad starting in Z=3, and then each vertex make if closer from the Z=0. I Believe this is related to the vertex part of the shader ?

static function CustomGraphicsBlit (source : RenderTexture, dest : RenderTexture, fxMaterial : Material, passNr : int) {
		RenderTexture.active = dest;
		       
		fxMaterial.SetTexture ("_MainTex", source);	        
	        	        
		GL.PushMatrix ();
		GL.LoadOrtho ();	
	    	
		fxMaterial.SetPass (passNr);	
		
	    GL.Begin (GL.QUADS);
							
		GL.MultiTexCoord2 (0, 0.0f, 0.0f); 
		GL.Vertex3 (0.0f, 0.0f, 3.0f); // BL
		
		GL.MultiTexCoord2 (0, 1.0f, 0.0f); 
		GL.Vertex3 (1.0f, 0.0f, 2.0f); // BR
		
		GL.MultiTexCoord2 (0, 1.0f, 1.0f); 
		GL.Vertex3 (1.0f, 1.0f, 1.0f); // TR
		
		GL.MultiTexCoord2 (0, 0.0f, 1.0f); 
		GL.Vertex3 (0.0f, 1.0f, 0.0); // TL
		
		GL.End ();
	    GL.PopMatrix ();
	}

And now the shader: There’s no problem with the fragment, it’s mostly the vertex: there’s interpolation, And it’s that point I don’t understand:

uniform float4x4 _FrustumCornersWS;
	uniform float4 _CameraWS;
	 
	struct v2f {
		float4 pos : POSITION;
		float2 uv : TEXCOORD0;
		float2 uv_depth : TEXCOORD1;
		float4 interpolatedRay : TEXCOORD2;
	};
	
	v2f vert( appdata_img v )
	{
		v2f o;
		half index = v.vertex.z;
		v.vertex.z = 0.1;
		o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
		o.uv = v.texcoord.xy;
		o.uv_depth = v.texcoord.xy;
		
		#if UNITY_UV_STARTS_AT_TOP
		if (_MainTex_TexelSize.y < 0)
			o.uv.y = 1-o.uv.y;
		#endif				
		
		o.interpolatedRay = _FrustumCornersWS[(int)index];
		o.interpolatedRay.w = index;
		
		return o;
	}

...
half4 fragAbsoluteYAndDistance (v2f i) : COLOR
	{
		float dpth = Linear01Depth(UNITY_SAMPLE_DEPTH(tex2D(_CameraDepthTexture,i.uv_depth)));
		float4 wsDir = dpth * i.interpolatedRay;
		float4 wsPos = _CameraWS + wsDir;
		return lerp(...);
	}

If someone could explain me how this interpolation works ? And what is the meaning of interpolatedRay ?

o.interpolatedRay = _FrustumCornersWS[(int)index];
        o.interpolatedRay.w = index;

Many thanks for those who’ll help understanding this :slight_smile:

Edit: Yeah, I took me nearly one year to decide myself to ask “how it works”: http://forum.unity3d.com/threads/169841-Weird-world-coordinate-for-post-process-pixel

The Z values of 0 to 3 for the vertices are all changed to 0.1 at line 15 of the shader, so the original values are are never actually used as a position. Instead, they’re a means of identifying each vertex without resorting to conditionals in the shader or additional vertex attributes.

interpolatedRay is then set to the appropriate value, using the 0 to 3 index to look up the correct vector from the _FrustumCornersWS matrix - which is never actually used as a matrix, it’s just a convenient way to pass an array of four vectors to the shader.

I’m not sure why interpolatedRay.w is set to the index value. As far as I can tell that’s never used again. Anyway, intepolatedRay.xyz now contains the world space direction from the camera to each of the frustum corners.

All the values from v2f are interpolated across the quad during rasterization, after which the fragment shader multiplies intepolatedRay with the linear depth at each pixel and then adds the world space position of the camera, resulting in the world space position of the fragment.

Hopefully that all makes at least a little sense and I haven’t just complicated things further.

Thanks you very much for your explanations !
I was somewhat quite far from the correct answer, and I must admit I never thought of using the Z or the matrix as something else from what they’re supposed to be :slight_smile:

It’s now much clearer for me !
I hope this thread will be able to help some more people also wondering how it works :slight_smile:

It helped me :slight_smile: