1
\$\begingroup\$

Im writing a shader that uses Unity's _CameraDepthNormalsTexture to get both the view normal and depth values. I return a color from my fragment shader equal to the normal values sampled.

I am having problems converting the view normal into a world normal so that when my camera tilts around, the fragment colors shouldnt change.

How can turn my view normal-> world normal in my fragment shader? Relevant code is marked with TODO below:

Shader "Custom/DepthNormal"{
    Properties {
       _MainTex ("", 2D) = "white" {}
       _HighlightDirection ("Highlight Direction", Vector) = (1, 0,0)    
    }

    SubShader {
        Tags { "RenderType"="Opaque" }
        Pass{
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            #include "UnityCG.cginc"

            sampler2D _CameraDepthNormalsTexture;
            sampler2D _CameraDepthTexture;


            struct vertexOutput {
                float4 position_in_clip_space : SV_POSITION;
                float2 depth : TEXCOORD0;
                float4 scrPos : TEXCOORD1;
                float4 position_in_world_space : TEXCOORD2;   
            };

            vertexOutput vert(appdata_full v){
                vertexOutput output;
                output.position_in_clip_space = UnityObjectToClipPos(v.vertex);
                //use the helper function instead of MATRIX_MVP as per documentation
                //mul(UNITY_MATRIX_MVP,float4(v.vertex,1.0));

                output.position_in_world_space = mul(unity_ObjectToWorld,v.vertex);
                //recall computeScreenPos does not divide by w, but because this is not an image effect,
                //diving by w here will WARP things unless we are completely parallel to viewing camera

                output.scrPos = ComputeScreenPos(output.position_in_clip_space);

                return output;
            }

            float4 frag(vertexOutput input) : COLOR 
            {                   
                float3 normalValues;
                float depthValue;
                //extract depth value and normal values

                //recall computeScreenPos does not divide by w, so if you want proper screenUV for use in tex2D divide
                //xy by w
                //https://forum.unity3d.com/threads/what-does-the-function-computescreenpos-in-unitycg-cginc-do.294470/

                float2 screenUV = input.scrPos.xy / input.scrPos.w;
                //DecodeDepthNormal(tex2Dproj(_CameraDepthNormalsTexture, input.scrPos), depthValue, normalValues);
                //Alternatively:
                    DecodeDepthNormal(tex2D(_CameraDepthNormalsTexture,screenUV), depthValue, normalValues);

                 //TODO: CONVERT TO WORLD NORMAL?               
                 float4 worldNormalValues  = mul(unity_ObjectToWorld,mul(UNITY_MATRIX_IT_MV, float4(normalValues,1.))); //THIS ISNT RIGHT :(

                float d = pow(depthValue,2);
                return float4(normalValues.x,normalValues.y,normalValues.z,d);   
            }

         ENDCG
        }

    }
fallback "Diffuse"
}
\$\endgroup\$

1 Answer 1

1
\$\begingroup\$

If all we're transforming is normals (directions, not positions), then we can take a little shortcut:

float4x4 viewTranspose = transpose(UNITY_MATRIX_V);
float3 worldNormal = mul(viewTranspose, float4(viewNormal.xyz, 0)).xyz;

In this case we're only using the upper-left 3x3 block of the view matrix, which should generally be a pure rotation matrix. A nice property of rotation matrices is that transposing them produces their inverse cheaply. This undoes the camera's orientation, rotating the normal back into worldspace.

It does not correctly invert the translation component, but since we're zeroing-out and discarding the w, this won't impact the normal directions.


If you need to do this for points with translation, then we'll need a proper inverse which is not as convenient to compute in the shader. The simplest thing is to capture the matrix we want on the C# side and upload it as a uniform that all shaders can reference, similar to the built-in matrices Unity offers.

We can attach a script like this to the camera to do that work automatically, just before the camera renders the scene:

[RequireComponent(typeof(Camera))]
public class ViewMatrixInverter {

    int variableID;

    void Start() {
        variableID = Shader.PropertyToID("UNITY_MATRIX_IV");
    }

    void OnPreRender() {
        Shader.SetGlobalMatrix(variableID, transform.localToWorldMatrix);
    }
}

Then in the shader we can read this matrix by declaring a uniform:

float4x4 UNITY_MATRIX_IV;

And inside the fragment program we can use this matrix to transform viewspace normals OR points:

float3 worldNormal = mul(UNITY_MATRIX_IV, float4(viewNormal.xyz, 0)).xyz;

float3 worldPoint = mul(UNITY_MATRIX_IV, float3(viewPoint.xyz, 1)).xyz;
\$\endgroup\$
0

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .