Copied from https://jakobknudsen.wordpress.com/2013/08/06/altitude-fog/ for archival purpose.
When you submerge an object in water you expect it to become more and more obscured as it sinks further into the depths. As far as I can see, this is usually accomplished in Unity3D with a fullscreen image-effect like the global fog standard asset. But that only works in the pro-version of Unity, and I want something that works in the free version. This post shows my solution.
In Unity3D, you can implement your own custom fog on a material by writing your own final color function in a custom shader. The final color function is applied after any lightning has been applied, so we wont have lightsources brightening up objects that are supposed to be hidden. The incredibly useful Surface Shader Examples page in the Unity documentation includes an example titled “Custom Fog with Final Color Modifier”, where a custom finalcolor function is used to apply fog based on the vertical position of the pixel in screenspace. This is nearly what I want, but I want the fog to be only dependent on the y-axis-position in world space. Another example in there, titled “Slices via World Space Position”, uses the world coordinates of the rendered pixel to slice the object vertically. From this example we can see how to use the world space position of the pixel we are rendering.
The shader below utilises principles from both the examples. The final color is faded into the fog color using the world coordinates of the rendered pixel, providing a fog-like effect based on the vertical position of the object rather than the distance between the object and the camera.
Shader "Custom/AltitudeFog"
{
Properties
{
_MainTex ("Base (RGB)", 2D) = "white" {}
_FogColor ("Fog Color", Color) = (0.5, 0.5, 0.5, 1)
_FogMaxHeight ("Fog Max Height", Float) = 0.0
_FogMinHeight ("Fog Min Height", Float) = -1.0
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 200
Cull Back
ZWrite On
CGPROGRAM
#pragma surface surf Lambert finalcolor:finalcolor vertex:vert
sampler2D _MainTex;
float4 _FogColor;
float _FogMaxHeight;
float _FogMinHeight;
struct Input
{
float2 uv_MainTex;
float4 pos;
};
void vert (inout appdata_full v, out Input o)
{
float4 hpos = mul (UNITY_MATRIX_MVP, v.vertex);
o.pos = mul(_Object2World, v.vertex);
o.uv_MainTex = v.texcoord.xy;
}
void surf (Input IN, inout SurfaceOutput o)
{
float4 c = tex2D (_MainTex, IN.uv_MainTex);
o.Albedo = c.rgb;
}
void finalcolor (Input IN, SurfaceOutput o, inout fixed4 color)
{
#ifndef UNITY_PASS_FORWARDADD
float lerpValue = clamp((IN.pos.y - _FogMinHeight) / (_FogMaxHeight - _FogMinHeight), 0, 1);
color.rgb = lerp (_FogColor.rgb, color.rgb, lerpValue);
#endif
}
ENDCG
}
FallBack "Diffuse"
}
This shader, used on the material of any partially submerged objects, applies a linear depth fog using the lerp function, but you could replace it with any function you want. A straightforward change is to use an exponential fog by changing the calculations in the finalcolor function, but for now, I’m quite happy with linear depth fog.