Smooth transition between perspective and orthographic modes

Hi!

I would like to ask if any kind soul knows an easy way to achieve a smooth transition effect between camera perspective and orthographic modes, similar to that found in the Editor (when you click the upper right gizmo alternating 'persp' and other ortho modes).

Thanks a lot!!

1 Like

I really liked this question, so I made a quick test to test if linear matrix interpolation would do the trick. It seems to work.

Add the following script to your camera:

using UnityEngine;
using System.Collections;

[RequireComponent (typeof(Camera))]
public class MatrixBlender : MonoBehaviour
{
    public static Matrix4x4 MatrixLerp(Matrix4x4 from, Matrix4x4 to, float time)
    {
        Matrix4x4 ret = new Matrix4x4();
        for (int i = 0; i < 16; i++)
            ret[i] = Mathf.Lerp(src[i], dest[i], time);
        return ret;
    }

    private IEnumerator LerpFromTo(Matrix4x4 src, Matrix4x4 dest, float duration)
    {
        float startTime = Time.time;
        while (Time.time - startTime < duration)
        {
            camera.projectionMatrix = MatrixLerp(src, dest, (Time.time - startTime) / duration);
            yield return 1;
        }
        camera.projectionMatrix = dest;
    }

    public Coroutine BlendToMatrix(Matrix4x4 targetMatrix, float duration)
    {
        StopAllCoroutines();
        return StartCoroutine(LerpFromTo(camera.projectionMatrix, targetMatrix, duration));
    }
}

To use it, get a link to this script and call the BlendToMatrix function with the new matrix for your camera. The blender script will then make the smooth transition between the current matrix of its camera and the targetMatrix.

You can obtain this target matrix in a script by calling Matrix4x4.ProjectionMatrix(...), or Matrix4x4.Ortho(...). An example script that switches between an ortho and a perspective matrix (when the user presses space) is this:

using UnityEngine;
using System.Collections;

[RequireComponent (typeof(MatrixBlender))]
public class PerspectiveSwitcher : MonoBehaviour
{
    private Matrix4x4   ortho,
                        perspective;
    public float        fov     = 60f,
                        near    = .3f,
                        far     = 1000f,
                        orthographicSize = 50f;
    private float       aspect;
    private MatrixBlender blender;
    private bool        orthoOn;

    void Start()
    {
        aspect = (float) Screen.width / (float) Screen.height;
        ortho = Matrix4x4.Ortho(-orthographicSize * aspect, orthographicSize * aspect, -orthographicSize, orthographicSize, near, far);
        perspective = Matrix4x4.Perspective(fov, aspect, near, far);
        camera.projectionMatrix = ortho;
        orthoOn = true;
        blender = (MatrixBlender) GetComponent(typeof(MatrixBlender));
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            orthoOn = !orthoOn;
            if (orthoOn)
                blender.BlendToMatrix(ortho, 1f);
            else
                blender.BlendToMatrix(perspective, 1f);
        }
    }
}

I don't know if this linear lerp is the same as the one in the editor, as I simply don't have the latest Unity version here to compare ;).

If you use javascript you should put these C# scripts in the Standard Assets/ folder, or translate them to JS.

I tested all the code, but I couldn't copy paste, so the scripts may contain typo's.

19 Likes

Thank you very much for sharing this!

I will soon give it a try!!

Best regards

Graceful, thanks again! Led me exactly to where I wanted!

Just one typo =), here is the corrected version for MatrixBlender.cs:

using UnityEngine; 
using System.Collections; 

[RequireComponent (typeof(Camera))] 
public class MatrixBlender : MonoBehaviour 
{ 
    public static Matrix4x4 MatrixLerp(Matrix4x4 from, Matrix4x4 to, float time) 
    { 
        Matrix4x4 ret = new Matrix4x4(); 
        for (int i = 0; i < 16; i++) 
            ret[i] = Mathf.Lerp(from[i], to[i], time); 
        return ret; 
    } 

    private IEnumerator LerpFromTo(Matrix4x4 src, Matrix4x4 dest, float duration) 
    { 
        float startTime = Time.time; 
        while (Time.time - startTime < duration) 
        { 
            camera.projectionMatrix = MatrixLerp(src, dest, (Time.time - startTime) / duration); 
            yield return 1; 
        } 
        camera.projectionMatrix = dest; 
    } 

    public Coroutine BlendToMatrix(Matrix4x4 targetMatrix, float duration) 
    { 
        StopAllCoroutines(); 
        return StartCoroutine(LerpFromTo(camera.projectionMatrix, targetMatrix, duration)); 
    } 
}
6 Likes

Hi there,

I was wondering how to do this and I stumbled over this thread.
It looks like just the thing I'm trying to do but I'm not sure how to use this script :)

What should I put in this function?
BlendToMatrix(Matrix4x4 targetMatrix, float duration)

Also, would it be possible to keep the camera angle when you switch from one to the other when using this?

Thanks
Pete

After seriously struggling to convert your work in Javascript I finally made it!

For those interested there was one tiny little bit that was driving me insane: the aspect float was ALWAYS at 1.
So each time I would switch to orthographic the aspect ratio would change from 16:10 to 1:1.
:face_with_spiral_eyes:

The reason was actually too simple: in Javascript int / int = int... there is no automatic casting of types.
I found a simple workaround to cast in Javascript:

(Screen.width+0.0) / (Screen.height+0.0);

I also simplified it in one single script.

Here is my first gift for the Unity community :p

@script RequireComponent (typeof(Camera))



private var ortho: Matrix4x4;
private var perspective: Matrix4x4;

public var fov: float   = 60f;
public var near: float  = .3f;
public var far: float   = 1000f;
public var orthographicSize: float = 50f;

private var aspect: float;
private var orthoOn: boolean;

function Awake ()
{
    aspect = (Screen.width+0.0) / (Screen.height+0.0);

    perspective = camera.projectionMatrix;

    ortho = Matrix4x4.Ortho(-orthographicSize * aspect, orthographicSize * aspect, -orthographicSize, orthographicSize, near, far); 
    orthoOn = false;
}

function Update ()
{
    if ( Input.GetKeyDown(KeyCode.P) )
    {
        orthoOn = !orthoOn;
        if (orthoOn)
            BlendToMatrix(ortho, 1f);
        else
            BlendToMatrix(perspective, 1f);
    }
}



static function MatrixLerp (from: Matrix4x4, to: Matrix4x4, time: float) : Matrix4x4 
{
    var ret: Matrix4x4 = new Matrix4x4(); 
    var i: int;
    for (i = 0; i < 16; i++) 
        ret[i] = Mathf.Lerp(from[i], to[i], time); 
    return ret; 
} 

private function LerpFromTo (src: Matrix4x4, dest: Matrix4x4, duration: float) : IEnumerator 
{
    var startTime: float = Time.time; 
    while (Time.time - startTime < duration) 
    { 
        camera.projectionMatrix = MatrixLerp(src, dest, (Time.time - startTime) / duration); 
        yield;
    }
    camera.projectionMatrix = dest; 
} 

public function BlendToMatrix (targetMatrix: Matrix4x4, duration: float) : Coroutine
{ 
    StopAllCoroutines(); 
    return StartCoroutine(LerpFromTo(camera.projectionMatrix, targetMatrix, duration)); 
}
3 Likes

Hey Thanks MatMontre! Cool!

[quote]
The reason was actually too simple: in Javascript int / int = int... there is no automatic casting of types.
[/quote]
That's got me so many times! :)

I started using this recently and I noticed that it really affects the size of particles. In the Orthographic view, the particles are super small. Any reason why this happens or how to work around it?

The camera has a Projection property. The code from above dose not change that, just the projection matrix. That might be the problem as I have seen the MatrixBlender affects the LOD mechanism (changing the Projection property from the editor made that work properly)

This script has been super useful! Quick question; what's the best way to even out the lerp? The transitions are kinda uneven when switching back and forth.

[quote=“mooddoofus”, post:10, topic: 400151]
This script has been super useful! Quick question; what’s the best way to even out the lerp? The transitions are kinda uneven when switching back and forth.
[/quote]
use a tweeter plugin?

Thank you for your work @tomvds !

Awesome, exactly what I need for my platformer, thanks a lot :)

g
[quote=“tomvds”, post:2, topic: 400151]
I really liked this question, so I made a quick test to test if linear matrix interpolation would do the trick. It seems to work.

Add the following script to your camera:

using UnityEngine;
using System.Collections;

[RequireComponent (typeof(Camera))]
public class MatrixBlender : MonoBehaviour
{
    public static Matrix4x4 MatrixLerp(Matrix4x4 from, Matrix4x4 to, float time)
    {
        Matrix4x4 ret = new Matrix4x4();
        for (int i = 0; i < 16; i++)
            ret[i] = Mathf.Lerp(src[i], dest[i], time);
        return ret;
    }

    private IEnumerator LerpFromTo(Matrix4x4 src, Matrix4x4 dest, float duration)
    {
        float startTime = Time.time;
        while (Time.time - startTime < duration)
        {
            camera.projectionMatrix = MatrixLerp(src, dest, (Time.time - startTime) / duration);
            yield return 1;
        }
        camera.projectionMatrix = dest;
    }

    public Coroutine BlendToMatrix(Matrix4x4 targetMatrix, float duration)
    {
        StopAllCoroutines();
        return StartCoroutine(LerpFromTo(camera.projectionMatrix, targetMatrix, duration));
    }
}

To use it, get a link to this script and call the BlendToMatrix function with the new matrix for your camera. The blender script will then make the smooth transition between the current matrix of its camera and the targetMatrix.

You can obtain this target matrix in a script by calling Matrix4x4.ProjectionMatrix(…), or Matrix4x4.Ortho(…). An example script that switches between an ortho and a perspective matrix (when the user presses space) is this:

using UnityEngine;
using System.Collections;

[RequireComponent (typeof(MatrixBlender))]
public class PerspectiveSwitcher : MonoBehaviour
{
    private Matrix4x4   ortho,
                        perspective;
    public float        fov     = 60f,
                        near    = .3f,
                        far     = 1000f,
                        orthographicSize = 50f;
    private float       aspect;
    private MatrixBlender blender;
    private bool        orthoOn;

    void Start()
    {
        aspect = (float) Screen.width / (float) Screen.height;
        ortho = Matrix4x4.Ortho(-orthographicSize * aspect, orthographicSize * aspect, -orthographicSize, orthographicSize, near, far);
        perspective = Matrix4x4.Perspective(fov, aspect, near, far);
        camera.projectionMatrix = ortho;
        orthoOn = true;
        blender = (MatrixBlender) GetComponent(typeof(MatrixBlender));
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            orthoOn = !orthoOn;
            if (orthoOn)
                blender.BlendToMatrix(ortho, 1f);
            else
                blender.BlendToMatrix(perspective, 1f);
        }
    }
}

I don’t know if this linear lerp is the same as the one in the editor, as I simply don’t have the latest Unity version here to compare ;).

If you use javascript you should put these C# scripts in the Standard Assets/ folder, or translate them to JS.

I tested all the code, but I couldn’t copy paste, so the scripts may contain typo’s.
[/quote]
exactly what i was looking for thanks a lot :slight_smile:

Very Nifty, Thank you!

this is cool, however I have found that after you have called BlendToMatrix on a camera, trying to set the camera field of view via the normal method doesn't do anything. Any idea how to fix this? Thanks.

Sorry, should have search for 2 more minutes before posting.

This looks like the answer: ResetProjectionMatrix

1 Like

Hey all, any wisdom on smoothing out the lerping?? or even guidance on what to tweak would be much appreciated!

[quote]
Hey all, any wisdom on smoothing out the lerping?? or even guidance on what to tweak would be much appreciated!
[/quote]

I have two answers for you :

  • You can replace the Lerp function by a SmoothStep, it takes the same variables but is smoother towards the ends of the animation
  • If this is not enough you can take a look at this thread that has taken a very different approach to this question

EDIT : I actually got back to it today and found an even smoother solution that kind of combines the two answers. Instead of the SmoothStep i used the Mathf.Pow to change the behavior of the Lerp. I also had to manually change the Lerp behavior depending on whether you're going from Persp to Ortho or from Ortho to Persp. These are the resulting files :

My new MatrixBlender

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(Camera))]
public class MatrixBlender : MonoBehaviour {
    Camera m_camera;
    private void Start() {
        m_camera = GetComponent<Camera>();
    }

    public static Matrix4x4 MatrixLerp(Matrix4x4 from, Matrix4x4 to, float time) {
        Matrix4x4 ret = new Matrix4x4();
        for (int i = 0; i < 16; i++)
            ret[i] = Mathf.Lerp(from[i], to[i], time);
        return ret;
    }

    private IEnumerator LerpFromTo(Matrix4x4 src, Matrix4x4 dest, float duration, float ease, bool reverse) {
        float startTime = Time.time;
        while (Time.time - startTime < duration) {
            float step;
            if (reverse)step = 1-Mathf.Pow(1-(Time.time - startTime) / duration, ease);
            else step = Mathf.Pow((Time.time - startTime) / duration, ease);
            m_camera.projectionMatrix = MatrixLerp(src, dest, step);
            yield return 1;
        }
        m_camera.projectionMatrix = dest;
    }

    public Coroutine BlendToMatrix(Matrix4x4 targetMatrix, float duration, float ease, bool reverse) {
        StopAllCoroutines();
        return StartCoroutine(LerpFromTo(m_camera.projectionMatrix, targetMatrix, duration, ease, reverse));
    }
}

My new PerspectiveSwitcher

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(MatrixBlender))]
public class PerspectiveSwitcher : MonoBehaviour {
    private Matrix4x4 ortho,
                        perspective;
    public float fov = 60f,
                        near = .3f,
                        far = 1000f,
                        orthographicSize = 50f;
    private float aspect;
    private MatrixBlender blender;
    private bool orthoOn;
    Camera m_camera;

    void Start() {
        aspect = (float)Screen.width / (float)Screen.height;
        ortho = Matrix4x4.Ortho(-orthographicSize * aspect, orthographicSize * aspect, -orthographicSize, orthographicSize, near, far);
        perspective = Matrix4x4.Perspective(fov, aspect, near, far);
        m_camera = GetComponent<Camera>();
        m_camera.projectionMatrix = ortho;
        orthoOn = true;
        blender = (MatrixBlender)GetComponent(typeof(MatrixBlender));
    }

    void Update() {
        if (Input.GetKeyDown(KeyCode.Space)) {
            orthoOn = !orthoOn;
            if (orthoOn)
                blender.BlendToMatrix(ortho, 1f, 8,true);
            else
                blender.BlendToMatrix(perspective, 1f, 8,false);
        }
    }
}
5 Likes

[quote=“RLasne”, post:19, topic: 400151]
I have two answers for you :

  • You can replace the Lerp function by a SmoothStep, it takes the same variables but is smoother towards the ends of the animation
  • If this is not enough you can take a look at this thread that has taken a very different approach to this question

EDIT : I actually got back to it today and found an even smoother solution that kind of combines the two answers. Instead of the SmoothStep i used the Mathf.Pow to change the behavior of the Lerp. I also had to manually change the Lerp behavior depending on whether you’re going from Persp to Ortho or from Ortho to Persp. These are the resulting files :

My new MatrixBlender

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(Camera))]
public class MatrixBlender : MonoBehaviour {
    Camera m_camera;
    private void Start() {
        m_camera = GetComponent<Camera>();
    }

    public static Matrix4x4 MatrixLerp(Matrix4x4 from, Matrix4x4 to, float time) {
        Matrix4x4 ret = new Matrix4x4();
        for (int i = 0; i < 16; i++)
            ret[i] = Mathf.Lerp(from[i], to[i], time);
        return ret;
    }

    private IEnumerator LerpFromTo(Matrix4x4 src, Matrix4x4 dest, float duration, float ease, bool reverse) {
        float startTime = Time.time;
        while (Time.time - startTime < duration) {
            float step;
            if (reverse)step = 1-Mathf.Pow(1-(Time.time - startTime) / duration, ease);
            else step = Mathf.Pow((Time.time - startTime) / duration, ease);
            m_camera.projectionMatrix = MatrixLerp(src, dest, step);
            yield return 1;
        }
        m_camera.projectionMatrix = dest;
    }

    public Coroutine BlendToMatrix(Matrix4x4 targetMatrix, float duration, float ease, bool reverse) {
        StopAllCoroutines();
        return StartCoroutine(LerpFromTo(m_camera.projectionMatrix, targetMatrix, duration, ease, reverse));
    }
}

My new PerspectiveSwitcher

using UnityEngine;
using System.Collections;

[RequireComponent(typeof(MatrixBlender))]
public class PerspectiveSwitcher : MonoBehaviour {
    private Matrix4x4 ortho,
                        perspective;
    public float fov = 60f,
                        near = .3f,
                        far = 1000f,
                        orthographicSize = 50f;
    private float aspect;
    private MatrixBlender blender;
    private bool orthoOn;
    Camera m_camera;

    void Start() {
        aspect = (float)Screen.width / (float)Screen.height;
        ortho = Matrix4x4.Ortho(-orthographicSize * aspect, orthographicSize * aspect, -orthographicSize, orthographicSize, near, far);
        perspective = Matrix4x4.Perspective(fov, aspect, near, far);
        m_camera = GetComponent<Camera>();
        m_camera.projectionMatrix = ortho;
        orthoOn = true;
        blender = (MatrixBlender)GetComponent(typeof(MatrixBlender));
    }

    void Update() {
        if (Input.GetKeyDown(KeyCode.Space)) {
            orthoOn = !orthoOn;
            if (orthoOn)
                blender.BlendToMatrix(ortho, 1f, 8,true);
            else
                blender.BlendToMatrix(perspective, 1f, 8,false);
        }
    }
}

[/quote]

There is one problem with this, i found out that when you try ( from orthographic mode ) to change the size of view, it doesn’t actually affect the size…