fluent animation from orthographic to perspective projection

Unity Editor has this neat little feature to switch between orthographic and perspective projection with a fluent, natural animation (by clicking on the axis of the viewport cube).

How can I implement this effect in a custom application?

2 Answers

2

Even though the question has been asked before and given an answer, I thought I’d post a solution anyways (apologies if that’s against guidelines, I saw nothing against it in the FAQ).

using UnityEngine;

[RequireComponent(typeof(Camera))]
public class CameraProjectionChange : MonoBehaviour
{
    public float ProjectionChangeTime = 0.5f;
    public bool ChangeProjection = false;

    private bool _changing = false;
    private float _currentT = 0.0f;

    private void Update()
    {
        if(_changing)
        {
            ChangeProjection = false;
        }
        else if(ChangeProjection)
        {
            _changing = true;
            _currentT = 0.0f;
        }
    }

    private void LateUpdate()
    {
        if(!_changing)
        {
            return;
        }

        var currentlyOrthographic = camera.orthographic;
        Matrix4x4 orthoMat, persMat;
        if(currentlyOrthographic)
        {
            orthoMat = camera.projectionMatrix;

            camera.orthographic = false;
            camera.ResetProjectionMatrix();
            persMat = camera.projectionMatrix;
        }
        else
        {
            persMat = camera.projectionMatrix;

            camera.orthographic = true;
            camera.ResetProjectionMatrix();
            orthoMat = camera.projectionMatrix;
        }
        camera.orthographic = currentlyOrthographic;

        _currentT += (Time.deltaTime / ProjectionChangeTime);
        if(_currentT < 1.0f)
        {
            if(currentlyOrthographic)
            {
                camera.projectionMatrix = MatrixLerp(orthoMat, persMat, _currentT * _currentT);
            }
            else
            {
                camera.projectionMatrix = MatrixLerp(persMat, orthoMat, Mathf.Sqrt(_currentT));
            }
        }
        else
        {
            _changing = false;
            camera.orthographic = !currentlyOrthographic;
            camera.ResetProjectionMatrix();
        }
    }

    private Matrix4x4 MatrixLerp(Matrix4x4 from, Matrix4x4 to, float t)
    {
        t = Mathf.Clamp(t, 0.0f, 1.0f);
        var newMatrix = new Matrix4x4();
        newMatrix.SetRow(0, Vector4.Lerp(from.GetRow(0), to.GetRow(0), t));
        newMatrix.SetRow(1, Vector4.Lerp(from.GetRow(1), to.GetRow(1), t));
        newMatrix.SetRow(2, Vector4.Lerp(from.GetRow(2), to.GetRow(2), t));
        newMatrix.SetRow(3, Vector4.Lerp(from.GetRow(3), to.GetRow(3), t));
        return newMatrix;
    }
}

Surprisingly, linear interpolation between matrices seems to do the trick.

Thanks for posting this solution! Sorry to necro, but how does the ProjectionChangeTime float work? It doesn't seem to correspond directly to seconds. For example, this is what I get when I set ProjectionChangeTime to 2, yet it looks like the transition takes maybe 0.5 seconds: https://gfycat.com/HospitableDisastrousAardwolf

This question has already been answered, here: