First Person Rendering

Hello everyone, I’m new here in the forum, and new to shaders too

I was reading a post about Custom Projection Matrix: First Person Rendering in Unity 5 (writeup) - Unity Engine - Unity Discussions and I thought it’s a great solution for a big problem for everyone trying to develop an FPS on Unity.

This tutorial was written for an older version of Unity (5.4), and I tried to redo it on Unity 5.6, but I’m having several problems that I can not understand why and how to solve :frowning:

Firstly I edited UnityShaderUtilities.cginc and I added it in my shaders folder

#ifndef UNITY_SHADER_UTILITIES_INCLUDED
#define UNITY_SHADER_UTILITIES_INCLUDED

#include "UnityShaderVariables.cginc"

float4x4 _CustomProjMatrix;
#if defined(FIRST_PERSON)

    // Tranforms position from object to homogenous space
    inline float4 UnityObjectToClipPos(in float3 pos)
    {
        float4x4 mvp = mul(mul(_CustomProjMatrix, UNITY_MATRIX_V ), unity_ObjectToWorld);
        float4 ret = mul(mvp, float4(pos, 1.0));
    
        //hack to fix vertically flipped vertices post-projection
        ret.y *= -1;
    
        //hack to offset depth value to avoid scene geometry intersection
        //todo: check if it works in OpenGL?
        ret.z *= 0.5;
    
        return ret;
    }

#else

    // Tranforms position from object to homogenous space
    inline float4 UnityObjectToClipPos(in float3 pos)
    {
        // More efficient than computing M*VP matrix product
        return mul(UNITY_MATRIX_VP, mul(unity_ObjectToWorld, float4(pos, 1.0)));
    }

#endif

inline float4 UnityObjectToClipPos(float4 pos) // overload for float4; avoids "implicit truncation" warning for existing shaders
{
    return UnityObjectToClipPos(pos.xyz);
}

#endif

And then I added this to the example shader:

#pragma multi_compile __ FIRST_PERSON

Then I followed the steps to define the custom projection matrix

using UnityEngine;

public class SetWeaponProjectionMatrix : MonoBehaviour
{
    public Camera sourceCam;
    
    void LateUpdate ()
    {
        Shader.SetGlobalMatrix("_CustomProjMatrix", sourceCam.projectionMatrix);
    }
}

And then for each mesh that uses my custom shader, I’ve added the following script to enable the shader keyword FIRST_PERSON

using UnityEngine;

public class EnableFirstPersonShaderVariant : MonoBehaviour
{
    void Awake ()
    {
        GetComponent<Renderer>().material.EnableKeyword("FIRST_PERSON");
    }
}

Following these steps, I should get the same result as in the original post video, but instead I get those results:

The shader is rendering the inside of the weapon, but from the tests I’ve done, it seems to be some problem with the projection matrix (when I remove the keyword “FIRST_PERSON” from the shader, it works normally).

I’d appreciate your help! Thank’s for your time!

Unity uses an inverted projection Z when rendering with DirectX or on consoles.

I think you might be able to resolve this with:

float4x4 customProjMatrix = _CustomProjMatrix;
#ifdef UNITY_REVERSE_Z
customProjMatrix._m22 = -customProjMatrix._m22 - 1.0;
customProjMatrix._m32 = -customProjMatrix._m32; // might be _m23
#endif
float4x4 mvp = mul(mul(customProjMatrix, UNITY_MATRIX_V ), unity_ObjectToWorld);

or if that doesn’t work:

#ifdef UNITY_REVERSE_Z
ret.z = 1 - ret.z; // maybe ret.w - ret.z? I can’t remember
#endif
return ret;

1 Like

I made the changes but the problem persists

You might also try:

Shader.SetGlobalMatrix(“_CustomProjMatrix”, GL.GetGPUProjectionMatrix(sourceCam.projectionMatrix, false));

1 Like

It worked perfectly, thank you very much. Now I am facing another problem:

Well, I’m using the post-processing stack, I’m having terrible results when I use TAA and/or Motion Blur
(both effects use Motion Vectors).

I don’t understand what’s going on, but the effects that use Motion Vectors are applying massive amounts of blur on the weapons that use the custom shader.

In my custom shader (standard shader), I just added the Keyword “#pragma multi_compile __ FIRST_PERSON” to use the custom projection matrix, but apparently, these image effects use the UnityObjectToClipPos and I can’t change it :frowning:

To try to solve the problem, I added the following pass in my shader

Pass
        {
            CGINCLUDE
            struct MotionVertexInput
            {
                float4 vertex : POSITION;
                float3 oldPos : NORMAL;
            };
            struct v2f_motion_vectors
            {
                float4 pos : SV_POSITION;
            };
            v2f_motion_vectors vert_motion_vectors(MotionVertexInput v)
            {
                v2f_motion_vectors o;
                o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
                return o;
            }
            float4 frag_motion_vectors(v2f_motion_vectors i) : SV_Target
            {
                return float4(1.0, 0.0, 0.0, 1.0);
            }
            ENDCG
            Tags{ "LightMode" = "MotionVectors" }
            Name "MOTIONVECTORS"
            ZTest LEqual
            Cull Off
            ZWrite Off
            CGPROGRAM
            #pragma vertex vert_motion_vectors
            #pragma fragment frag_motion_vectors
            ENDCG
        }

But unfortunately the shader did not work, is there anything I need to change or add to correct the problem with motion vectors?

edit: When I disable FIRST_PERSON, the image effects work perfectly, so I conclude that they don’t use the same weapon projection matrix, if so, it might not using that function to calculate the old position. So it would constantly see it as movement in z.

edit1: To solve the problem with the motion vectors, just copy the internal-MotionVectors shader to the weapons shader (to calculate the MotionVectors with the same projection matrix using the custom UnityObjectToClipPos method)

Unfortunately I’ve not played with the motion vector stuff at all, so I’m not entirely sure how to fix that. The original post you linked to has some stuff on motion vectors, I’m pretty sure you just need to change lastProjMatrix in the C# code to use the same GetGPUProjectionMatrix change I recommended. You might also try return float4(0.0, 0.0, 0.0, 0.0); instead of return float4(1.0, 0.0, 0.0, 1.0);, or if you could just disable motion vectors on the renderer component with setting Motion Vectors to Force No Motion?

@bgolus This solves the problem, but causes the gun to cross the wall again. Would you have any other suggestions?