Fog on reflections in water.

I’m trying to add fog to water reflections. To do so I want to calculate world position of reflected objects and then distance to the main camera to calculate fog factor in water shader.

  1. Reflections camera renders to depth texture.
 m_ReflectionTexture = new RenderTexture(textureSize, textureSize, 0);
ReflectionTextureDepth = new RenderTexture(textureSize, textureSize, 24, RenderTextureFormat.Depth);
reflectionCamera.SetTargetBuffers(reflectionTex.colorBuffer, ReflectionTextureDepth.depthBuffer);
  1. Then send it to my shader. With reflection camera position to reconstract world position.
GetComponent<Renderer>().sharedMaterial.SetTexture("_ReflectionTex", reflectionTexture);
GetComponent<Renderer>().sharedMaterial.SetTexture("_ReflectionTexDepth", ReflectionTextureDepth);
GetComponent<Renderer>().sharedMaterial.SetVector("_ReflectionCameraPosition", reflectionCamera.transform.position);
  1. Sample depth, I think problem there because fog show on reflected geometry only if I set fog end close to 2 or smaller.
float rawZ = SAMPLE_DEPTH_TEXTURE_PROJ(_ReflectionTexDepth, UNITY_PROJ_COORD(i.ref));
float sceneZ = LinearEyeDepth(rawZ) ;
  1. Calculate world positions of reflected objects and distance from main camera. Am I correct that directionFromReflectionCamera is correct direction to the mountain?
float3 directionFromReflectionCamera = normalize(i.wpos - _ReflectionCameraPosition.xyz);
float3 reflectedWpos = (sceneZ * directionFromReflectionCamera) + _ReflectionCameraPosition.xyz;
float reflectedSceneDistance = length(reflectedWpos - _WorldSpaceCameraPos);
  1. Calculate fog factor and return resulting color.
//linear fog unity formula
float unityFogFactorReflection = (reflectedSceneDistance)* unity_FogParams.z + unity_FogParams.w;
loat3 reflectionColorWithFog = lerp(unity_FogColor.rgb, refl.rgb,saturate(unityFogFactorReflection));
return half4(reflectionColorWithFog.rgb, 1);

Fog start = 0, end = 1


Fog start = 0.35, end 0.5
We see that the fog shows on the top of the mountain cause the top is farther than the bottom.

Full shader code

Shader "FX/Water" {
    Properties{
    }
        Subshader{
            Tags { "WaterMode" = "Refractive" "RenderType" = "Opaque" }
            Pass {
        CGPROGRAM
        #pragma vertex vert
        #pragma fragment frag
        #pragma multi_compile_fog

        #include "UnityCG.cginc"
  
        sampler2D _ReflectionTex;
        sampler2D _ReflectionTexDepth;
        float4 _ReflectionCameraPosition;

        struct appdata {
            float4 vertex : POSITION;
        };

        struct v2f {
            float4 pos : SV_POSITION;
            float4 ref : TEXCOORD0;
            float3 wpos : TEXCOORD1;
        };
        v2f vert(appdata v)
        {
            v2f o;
            o.pos = UnityObjectToClipPos(v.vertex);
            o.ref = ComputeNonStereoScreenPos(o.pos);
            o.wpos = mul((float4x4)unity_ObjectToWorld, v.vertex);
            return o;
        }
        half4 frag(v2f i) : SV_Target
        {
            half4 refl = tex2Dproj(_ReflectionTex, UNITY_PROJ_COORD(i.ref));

            float rawZ = SAMPLE_DEPTH_TEXTURE_PROJ(_ReflectionTexDepth, UNITY_PROJ_COORD(i.ref));
            float sceneZ = LinearEyeDepth(rawZ) ; //return 1.0 / (_ZBufferParams.z * z + _ZBufferParams.w); where _ZBufferParams.z is (x/far) and _ZBufferParams.w is (y/far) they are same in reflectionCamera and mainCamera
            float3 directionFromReflectionCamera = normalize(i.wpos - _ReflectionCameraPosition.xyz);
            float3 reflectedWpos = (sceneZ * directionFromReflectionCamera) + _ReflectionCameraPosition.xyz;
            float reflectedSceneDistance = length(reflectedWpos - _WorldSpaceCameraPos);

            float unityFogFactorReflection = (reflectedSceneDistance)* unity_FogParams.z + unity_FogParams.w; //linear fog unity formula where  z = –1/(end-start) w = end/(end-start)
            float3 reflectionColorWithFog = lerp(unity_FogColor.rgb, refl.rgb,saturate(unityFogFactorReflection));

            //return half4(refl.rgb,1);//shows reflection texture
            //return half4(sceneZ.xxx, 1); //shows depth texture
            return half4(reflectionColorWithFog.rgb, 1);
        }
    ENDCG
        }
    }

}

And on next stage, I want to consider distance to water and show fog on the water.(current unity default behavior) So if I calculate distance to current water fragment.

float reflectionSceneDistance = length(i.wpos - _WorldSpaceCameraPos);
float unityFogFactorReflection = ( reflectionSceneDistance)* unity_FogParams.z + unity_FogParams.w;

But if I want to consider fog on reflected mountain.

float reflectedSceneDistance = length(reflectedWpos - _WorldSpaceCameraPos);
float reflectionSceneDistance = length(i.wpos - _WorldSpaceCameraPos);
float unityFogFactorReflection = ( reflectionSceneDistance - reflectedSceneDistance)* unity_FogParams.z + unity_FogParams.w;

Now it looks like this.

3277167–253405–StandartWaterFog.zip (248 KB)

I rewrote shader to display sceneZ calculated on the mountain.

half4 frag(v2f i) : SV_Target
    {
        half4 refl = tex2Dproj(_ReflectionTex, UNITY_PROJ_COORD(i.ref));
        float rawZ = SAMPLE_DEPTH_TEXTURE_PROJ(_ReflectionTexDepth, UNITY_PROJ_COORD(i.ref));
        float sceneZ =  LinearEyeDepth(rawZ);
        if (sceneZ > 30)
        {
            return half4(0, 0, 1, 1);
        }
        else if (sceneZ > 1)
        {
            return half4(1, 0, 0, 1);
        }
        else if (sceneZ > 0)
        {
            return half4(0, 1, 0, 1);
        }
        return half4(0, 0, 0, 1);
     }

And this is how it looks. All mountain has 1 > sceneZ >0. Only some outlining pixels of the mountain are red sceneZ >1. So LinearEyeDepth somehow doesn’t return world z value?

Or my depth texture is somehow rendered wrong?

Approximate distance from reflection camera to the bottom of the mountain is 9.3. To the top of the mountain 11.4

In frame debugger, I noted that _ZBufferParams values are strange.
Main camera far plane = 100, near plane = 0.3

Description from docs: Z-buffer x is (1-far/near), y is (far/near), z is (x/far) and w is (y/far).
From docs: x = 1-100/0.3 = -332.333; y = 100/0.3 = 333.333; z = -332.333/100 = -3.323; w = 333.333 / 100 = 3.333;
From debugger: x =3.3*10^2 = 330; y = 1; z =3.3; w=0.01;
I don’t understand why in debbugger and in the docs those values aren’t equal.

I tried to linearize z-buffer without using LinearEyeDepth. And use explict far and near plane values.

half4 refl = tex2Dproj(_ReflectionTex, UNITY_PROJ_COORD(i.ref));
float rawZ = SAMPLE_DEPTH_TEXTURE_PROJ(_ReflectionTexDepth, UNITY_PROJ_COORD(i.ref));
float far = 100;
float near = 0.3;

float sceneZ = 1.0/((1-far/near)/far*rawZ + far/near/far); // LinearEyeDepth formula : 1.0 / (_ZBufferParams.z * z + _ZBufferParams.w);

        if (sceneZ > 9)
        {
            return half4(1, 1, 0, 1);//yellow
        }
        if (sceneZ > 2)
        {
            return half4(0, 0, 1, 1);//blue
        }
        else if (sceneZ > 0.3)
        {
            return half4(1, 0, 0, 1);//red
        }
        else if (sceneZ > 0)
        {
            return half4(0, 1, 0, 1);//green
        }
     
        return half4(0, 0, 0, 1);

It looks like this. All mountain 0.3<sceneZ<2

Maybe someone knows what is wrong with my code? Thanks

And if I use LinearEyeDepth then the picture is the same. Maybe debugger shows wrong z-buffer value when really they are correct. And my problem seems to be in rawZ or RenderTextures?

Sorry I didn’t read all of your question, just title and pictures. But maybe we have same problem, and here’s a solution I’ve found:

I try this in frag:

fixed4 ApplyFog (fixed4 color, float3 wPos) {
    #if FOG_LINEAR
        float viewDistance = length(_WorldSpaceCameraPos - wPos);
        UNITY_CALC_FOG_FACTOR_RAW(viewDistance);
        color.rgb = lerp(unity_FogColor.rgb, color.rgb, saturate(unityFogFactor));
    #endif
    return color;
}

You can keep lerp in frag only and move others into vert function. Nice result for me :smile:.

Hi fUring, yeah I’ve seen this post and figure out that oblique camera projection is the problem, I don’t remember if I’ve tried using world space fog calculation or why I’ve decided against it.

My solution - not to use oblique camera projection, then fog on all objects works fine and only objects that can move through the water plane need to be clipped in a shader by world space Y.