Custom camera matrices break deferred lights/shadows.

I’m using a modified version of Unity’s water.cs script to simulate a portal effect. Part of said effect requires me to use a custom projection matrix for the camera before rendering to a render texture, which works fine in Forward rendering, but in deferred rendering the texture doesn’t show lights or shadows.

Now this seems to be a common problem with Unity’s camera matrices, somebody addresses it fairly well here. However the fix they suggest does not work in my case. It seems using a custom projection matrix breaks shadows and lighting in deferred rendering for some reason. I’m not entirely sure how I could recalculate my custom matrix to include shadows and lighting, or if I could find some work around. Here’s the bit of code I’m using to calculate the matrix:

(This is a snippet from the OnWillRenderObject function)

Vector4 	reflectionPlaneUp 	= new Vector4 (normal.x, normal.y, normal.z, - m_ClipPlaneOffset);
        Vector4 	reflectionPlaneRt	= new Vector4 (right.x,  right.y,  right.z,  0);
           
        Matrix4x4 	reflectionUp		= Matrix4x4.zero;
        Matrix4x4 	reflectionRt		= Matrix4x4.zero;
        Matrix4x4 	reflectionSum		= Matrix4x4.zero;          
        Quaternion 	rotate 				= transform.rotation * Quaternion.Inverse(SecondPortal.transform.rotation);
        
        CalculateReflectionMatrix(ref reflectionUp, reflectionPlaneUp);
        CalculateReflectionMatrix(ref reflectionRt, reflectionPlaneRt); 
        
        //Step1 Move to BEGIN OF COORDINATES  
        reflectionSum = Matrix4x4.TRS(transform.position, Quaternion.identity, new Vector3(1,1,1)); 
        //Step2 reflect from Normal and Right vectors
        reflectionSum *= reflectionUp * reflectionRt;  
        //Step3 Rotate Camera on Difference Quaternion between 2 portals
        reflectionSum *= Matrix4x4.TRS(new Vector3(0,0,0), rotate, new Vector3(1,1,1));
        //Step4 Move to Other portal position         
        reflectionSum *= Matrix4x4.TRS(-SecondPortal.transform.position, Quaternion.identity, new Vector3(1,1,1));
    
        //Apply all transformations on Portal camera	
       	reflectionCamera.worldToCameraMatrix = cam.worldToCameraMatrix * reflectionSum;

            // Setup oblique projection matrix so that near plane is our reflection
            // plane. This way we clip everything below/above it for free.

            Vector4 clipPlane = CameraSpacePlane( reflectionCamera, SecondPortal.transform.position,             SecondPortal.transform.up, 1.0f );
            Matrix4x4 projection = cam.projectionMatrix;
            CalculateObliqueMatrix (ref projection, clipPlane);
            reflectionCamera.projectionMatrix = projection;

And these are the functions referenced above:

// Given position/normal of the plane, calculates plane in camera space.
    private Vector4 CameraSpacePlane (Camera cam, Vector3 pos, Vector3 normal, float sideSign)
    {
        Vector3 offsetPos = pos + normal * m_ClipPlaneOffset;
        Matrix4x4 m = cam.worldToCameraMatrix;
        Vector3 cpos = m.MultiplyPoint( offsetPos );
        Vector3 cnormal = m.MultiplyVector( normal ).normalized * sideSign;
        return new Vector4( cnormal.x, cnormal.y, cnormal.z, -Vector3.Dot(cpos,cnormal) );
    }

    // Adjusts the given projection matrix so that near plane is the given clipPlane
    // clipPlane is given in camera space. See article in Game Programming Gems 5 and
    // http://aras-p.info/texts/obliqueortho.html
    private static void CalculateObliqueMatrix (ref Matrix4x4 projection, Vector4 clipPlane)
    {
        Vector4 q = projection.inverse * new Vector4(
            sgn(clipPlane.x),
            sgn(clipPlane.y),
            1.0f,
            1.0f
        );
        Vector4 c = clipPlane * (2.0F / (Vector4.Dot (clipPlane, q)));
        // third row = clip plane - fourth row
        projection[2] = c.x - projection[3];
        projection[6] = c.y - projection[7];
        projection[10] = c.z - projection[11];
        projection[14] = c.w - projection[15];
    }

    // Calculates reflection matrix around the given plane
    private static void CalculateReflectionMatrix (ref Matrix4x4 reflectionMat, Vector4 plane)
    {
        reflectionMat.m00 = (1F - 2F*plane[0]*plane[0]);
        reflectionMat.m01 = (    -2F*plane[0]*plane[1]);
        reflectionMat.m02 = (    -2F*plane[0]*plane[2]);
        reflectionMat.m03 = (    -2F*plane[3]*plane[0]);

        reflectionMat.m10 = (    -2F*plane[1]*plane[0]);
        reflectionMat.m11 = (1F - 2F*plane[1]*plane[1]);
        reflectionMat.m12 = (    -2F*plane[1]*plane[2]);
        reflectionMat.m13 = (    -2F*plane[3]*plane[1]);

        reflectionMat.m20 = (    -2F*plane[2]*plane[0]);
        reflectionMat.m21 = (    -2F*plane[2]*plane[1]);
        reflectionMat.m22 = (1F - 2F*plane[2]*plane[2]);
        reflectionMat.m23 = (    -2F*plane[3]*plane[2]);

        reflectionMat.m30 = 0F;
        reflectionMat.m31 = 0F;
        reflectionMat.m32 = 0F;
        reflectionMat.m33 = 1F;
    }

Does anyone know how I could write the shadow/lighting in to the matrix myself, or another solution to be able to use a custom camera matrix in deferred rendering?

I’m still looking in to this, there are tons of posts all over the forums about this issue but none of them have any replies.

Turns out this is a bug with Unity, after some research I found out it was in their Deferred Lighting → RenderingToTexture pipeline. Basically what happens is they are cutting it to only show the first pass.

I’m going to file a pug report with Unity and hope it gets fixed, in the mean while I’m just changing my portal camera to forward rendering. It still doesn’t render shadows, but at least it renders lights now.