Search Unity

Smooth transition between perspective and orthographic modes

Discussion in 'Scripting' started by mlapin, Oct 25, 2009.

  1. mlapin

    mlapin

    Joined:
    Nov 25, 2008
    Posts:
    18
    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!!
     
    rich-gg likes this.
  2. tomvds

    tomvds

    Joined:
    Oct 10, 2008
    Posts:
    1,028
    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:
    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. [RequireComponent (typeof(Camera))]
    5. public class MatrixBlender : MonoBehaviour
    6. {
    7.     public static Matrix4x4 MatrixLerp(Matrix4x4 from, Matrix4x4 to, float time)
    8.     {
    9.         Matrix4x4 ret = new Matrix4x4();
    10.         for (int i = 0; i < 16; i++)
    11.             ret[i] = Mathf.Lerp(src[i], dest[i], time);
    12.         return ret;
    13.     }
    14.  
    15.     private IEnumerator LerpFromTo(Matrix4x4 src, Matrix4x4 dest, float duration)
    16.     {
    17.         float startTime = Time.time;
    18.         while (Time.time - startTime < duration)
    19.         {
    20.             camera.projectionMatrix = MatrixLerp(src, dest, (Time.time - startTime) / duration);
    21.             yield return 1;
    22.         }
    23.         camera.projectionMatrix = dest;
    24.     }
    25.  
    26.     public Coroutine BlendToMatrix(Matrix4x4 targetMatrix, float duration)
    27.     {
    28.         StopAllCoroutines();
    29.         return StartCoroutine(LerpFromTo(camera.projectionMatrix, targetMatrix, duration));
    30.     }
    31. }
    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:

    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. [RequireComponent (typeof(MatrixBlender))]
    5. public class PerspectiveSwitcher : MonoBehaviour
    6. {
    7.     private Matrix4x4   ortho,
    8.                         perspective;
    9.     public float        fov     = 60f,
    10.                         near    = .3f,
    11.                         far     = 1000f,
    12.                         orthographicSize = 50f;
    13.     private float       aspect;
    14.     private MatrixBlender blender;
    15.     private bool        orthoOn;
    16.  
    17.     void Start()
    18.     {
    19.         aspect = (float) Screen.width / (float) Screen.height;
    20.         ortho = Matrix4x4.Ortho(-orthographicSize * aspect, orthographicSize * aspect, -orthographicSize, orthographicSize, near, far);
    21.         perspective = Matrix4x4.Perspective(fov, aspect, near, far);
    22.         camera.projectionMatrix = ortho;
    23.         orthoOn = true;
    24.         blender = (MatrixBlender) GetComponent(typeof(MatrixBlender));
    25.     }
    26.  
    27.     void Update()
    28.     {
    29.         if (Input.GetKeyDown(KeyCode.Space))
    30.         {
    31.             orthoOn = !orthoOn;
    32.             if (orthoOn)
    33.                 blender.BlendToMatrix(ortho, 1f);
    34.             else
    35.                 blender.BlendToMatrix(perspective, 1f);
    36.         }
    37.     }
    38. }
    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.
     
  3. mlapin

    mlapin

    Joined:
    Nov 25, 2008
    Posts:
    18
    Thank you very much for sharing this!

    I will soon give it a try!!

    Best regards
     
  4. mlapin

    mlapin

    Joined:
    Nov 25, 2008
    Posts:
    18
    Graceful, thanks again! Led me exactly to where I wanted!

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

    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. [RequireComponent (typeof(Camera))]
    5. public class MatrixBlender : MonoBehaviour
    6. {
    7.     public static Matrix4x4 MatrixLerp(Matrix4x4 from, Matrix4x4 to, float time)
    8.     {
    9.         Matrix4x4 ret = new Matrix4x4();
    10.         for (int i = 0; i < 16; i++)
    11.             ret[i] = Mathf.Lerp(from[i], to[i], time);
    12.         return ret;
    13.     }
    14.  
    15.     private IEnumerator LerpFromTo(Matrix4x4 src, Matrix4x4 dest, float duration)
    16.     {
    17.         float startTime = Time.time;
    18.         while (Time.time - startTime < duration)
    19.         {
    20.             camera.projectionMatrix = MatrixLerp(src, dest, (Time.time - startTime) / duration);
    21.             yield return 1;
    22.         }
    23.         camera.projectionMatrix = dest;
    24.     }
    25.  
    26.     public Coroutine BlendToMatrix(Matrix4x4 targetMatrix, float duration)
    27.     {
    28.         StopAllCoroutines();
    29.         return StartCoroutine(LerpFromTo(camera.projectionMatrix, targetMatrix, duration));
    30.     }
    31. }
    32.  
     
    zignuz, Atmey, nicobocoloco and 3 others like this.
  5. petey

    petey

    Joined:
    May 20, 2009
    Posts:
    1,824
    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
     
  6. MatMontre

    MatMontre

    Joined:
    Jun 29, 2011
    Posts:
    21
    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.
    :confused:

    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:

    Code (csharp):
    1. (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

    Code (csharp):
    1.  
    2. @script RequireComponent (typeof(Camera))
    3.  
    4.  
    5.  
    6. private var ortho: Matrix4x4;
    7. private var perspective: Matrix4x4;
    8.            
    9. public var fov: float   = 60f;
    10. public var near: float  = .3f;
    11. public var far: float   = 1000f;
    12. public var orthographicSize: float = 50f;
    13.  
    14. private var aspect: float;
    15. private var orthoOn: boolean;
    16.  
    17. function Awake ()
    18. {
    19.     aspect = (Screen.width+0.0) / (Screen.height+0.0);
    20.  
    21.     perspective = camera.projectionMatrix;
    22.  
    23.     ortho = Matrix4x4.Ortho(-orthographicSize * aspect, orthographicSize * aspect, -orthographicSize, orthographicSize, near, far);
    24.     orthoOn = false;
    25. }
    26.  
    27. function Update ()
    28. {
    29.     if ( Input.GetKeyDown(KeyCode.P) )
    30.     {
    31.         orthoOn = !orthoOn;
    32.         if (orthoOn)
    33.             BlendToMatrix(ortho, 1f);
    34.         else
    35.             BlendToMatrix(perspective, 1f);
    36.     }
    37. }
    38.  
    39.  
    40.  
    41. static function MatrixLerp (from: Matrix4x4, to: Matrix4x4, time: float) : Matrix4x4
    42. {
    43.     var ret: Matrix4x4 = new Matrix4x4();
    44.     var i: int;
    45.     for (i = 0; i < 16; i++)
    46.         ret[i] = Mathf.Lerp(from[i], to[i], time);
    47.     return ret;
    48. }
    49.  
    50. private function LerpFromTo (src: Matrix4x4, dest: Matrix4x4, duration: float) : IEnumerator
    51. {
    52.     var startTime: float = Time.time;
    53.     while (Time.time - startTime < duration)
    54.     {
    55.         camera.projectionMatrix = MatrixLerp(src, dest, (Time.time - startTime) / duration);
    56.         yield;
    57.     }
    58.     camera.projectionMatrix = dest;
    59. }
    60.  
    61. public function BlendToMatrix (targetMatrix: Matrix4x4, duration: float) : Coroutine
    62. {
    63.     StopAllCoroutines();
    64.     return StartCoroutine(LerpFromTo(camera.projectionMatrix, targetMatrix, duration));
    65. }
    66.  
    67.  
     
    StarNazi, Atmey and GarthSmith like this.
  7. petey

    petey

    Joined:
    May 20, 2009
    Posts:
    1,824
    Hey Thanks MatMontre! Cool!

    That's got me so many times! :)
     
  8. DFP

    DFP

    Joined:
    Nov 22, 2012
    Posts:
    12
    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?
     
  9. katoun

    katoun

    Joined:
    Dec 26, 2012
    Posts:
    91
    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)
     
  10. mooddoofus

    mooddoofus

    Joined:
    Sep 21, 2015
    Posts:
    1
    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.
     
  11. SunnyChow

    SunnyChow

    Joined:
    Jun 6, 2013
    Posts:
    360
    use a tweeter plugin?
     
  12. Zardify

    Zardify

    Joined:
    Jul 15, 2015
    Posts:
    20
    Thank you for your work @tomvds !
     
  13. PhenixII7

    PhenixII7

    Joined:
    Apr 4, 2016
    Posts:
    1
    Awesome, exactly what I need for my platformer, thanks a lot :)
     
  14. uchodhry

    uchodhry

    Joined:
    Nov 22, 2012
    Posts:
    7
    g
    exactly what i was looking for thanks a lot :)
     
  15. CarterG81

    CarterG81

    Joined:
    Jul 25, 2013
    Posts:
    1,773
    Very Nifty, Thank you!
     
  16. BillyMFT

    BillyMFT

    Joined:
    Mar 14, 2013
    Posts:
    178
    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.
     
  17. BillyMFT

    BillyMFT

    Joined:
    Mar 14, 2013
    Posts:
    178
    Sorry, should have search for 2 more minutes before posting.

    This looks like the answer: ResetProjectionMatrix
     
    Bip901 likes this.
  18. Deleted User

    Deleted User

    Guest

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

    RLasne

    Joined:
    Oct 14, 2015
    Posts:
    3
    I have two answers for you :
    1. You can replace the Lerp function by a SmoothStep, it takes the same variables but is smoother towards the ends of the animation
    2. 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
    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. [RequireComponent(typeof(Camera))]
    5. public class MatrixBlender : MonoBehaviour {
    6.     Camera m_camera;
    7.     private void Start() {
    8.         m_camera = GetComponent<Camera>();
    9.     }
    10.  
    11.     public static Matrix4x4 MatrixLerp(Matrix4x4 from, Matrix4x4 to, float time) {
    12.         Matrix4x4 ret = new Matrix4x4();
    13.         for (int i = 0; i < 16; i++)
    14.             ret[i] = Mathf.Lerp(from[i], to[i], time);
    15.         return ret;
    16.     }
    17.  
    18.     private IEnumerator LerpFromTo(Matrix4x4 src, Matrix4x4 dest, float duration, float ease, bool reverse) {
    19.         float startTime = Time.time;
    20.         while (Time.time - startTime < duration) {
    21.             float step;
    22.             if (reverse)step = 1-Mathf.Pow(1-(Time.time - startTime) / duration, ease);
    23.             else step = Mathf.Pow((Time.time - startTime) / duration, ease);
    24.             m_camera.projectionMatrix = MatrixLerp(src, dest, step);
    25.             yield return 1;
    26.         }
    27.         m_camera.projectionMatrix = dest;
    28.     }
    29.  
    30.     public Coroutine BlendToMatrix(Matrix4x4 targetMatrix, float duration, float ease, bool reverse) {
    31.         StopAllCoroutines();
    32.         return StartCoroutine(LerpFromTo(m_camera.projectionMatrix, targetMatrix, duration, ease, reverse));
    33.     }
    34. }
    My new PerspectiveSwitcher
    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. [RequireComponent(typeof(MatrixBlender))]
    5. public class PerspectiveSwitcher : MonoBehaviour {
    6.     private Matrix4x4 ortho,
    7.                         perspective;
    8.     public float fov = 60f,
    9.                         near = .3f,
    10.                         far = 1000f,
    11.                         orthographicSize = 50f;
    12.     private float aspect;
    13.     private MatrixBlender blender;
    14.     private bool orthoOn;
    15.     Camera m_camera;
    16.  
    17.     void Start() {
    18.         aspect = (float)Screen.width / (float)Screen.height;
    19.         ortho = Matrix4x4.Ortho(-orthographicSize * aspect, orthographicSize * aspect, -orthographicSize, orthographicSize, near, far);
    20.         perspective = Matrix4x4.Perspective(fov, aspect, near, far);
    21.         m_camera = GetComponent<Camera>();
    22.         m_camera.projectionMatrix = ortho;
    23.         orthoOn = true;
    24.         blender = (MatrixBlender)GetComponent(typeof(MatrixBlender));
    25.     }
    26.  
    27.     void Update() {
    28.         if (Input.GetKeyDown(KeyCode.Space)) {
    29.             orthoOn = !orthoOn;
    30.             if (orthoOn)
    31.                 blender.BlendToMatrix(ortho, 1f, 8,true);
    32.             else
    33.                 blender.BlendToMatrix(perspective, 1f, 8,false);
    34.         }
    35.     }
    36. }
     
  20. Xx_Alanox_xX

    Xx_Alanox_xX

    Joined:
    Feb 4, 2017
    Posts:
    6

    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...
     
  21. RLasne

    RLasne

    Joined:
    Oct 14, 2015
    Posts:
    3
    Could it be the same issue that BillyMFT faced earlier ?

    and

    If this is the case, you can try to call this function after each transition (I tried for a bit and didn't get very good results but with a little more time this should be the solution)
     
  22. drudiverse

    drudiverse

    Joined:
    May 16, 2013
    Posts:
    218
    Nice coding there. quote: Hey all, any wisdom on smoothing out the lerping?? or even guidance on what to tweak would be much appreciated!
    Yes, the Lerp all go from 0-1. The abruptness is logarytmic, or exponential... So, you can use log, pow, functions on the time value which is switching the lerp, you can do time*time to make an exponential 0-1 time graph... which is what I did and it works first time for smoothing the ortho-to-perspective, and i don't require the other cam switch so i didn't research, it's either exponent or log of the current linear time values in the lerps, which are all 0-1 so they are easy to adjust, i will use time*time*time for a steeper exponential, pow it as many powers as you wish, and use 0.1 powers if you have to on the perspective to ortho switch.
     
  23. bntr

    bntr

    Joined:
    Jul 12, 2016
    Posts:
    3
  24. Noxury

    Noxury

    Joined:
    Mar 8, 2015
    Posts:
    22
    It works, but if the viewport rect is not (1, 1) it looks squeezed. How do I alter the projection matrix to align with the viewport rect?
     
  25. EDDDDDDD

    EDDDDDDD

    Joined:
    Sep 30, 2017
    Posts:
    3
    Get thanks!
     
  26. ThatMellon

    ThatMellon

    Joined:
    Sep 23, 2021
    Posts:
    8
    Amazing! Thanks for this!!
     
  27. ThatMellon

    ThatMellon

    Joined:
    Sep 23, 2021
    Posts:
    8
    How did you implement this? I'm having trouble setting it up haha!

    Edit: Resolved this!

    I already had a few virtual cameras set up to change upon collision. I just made another collision object with cam.ResetProjectionMatrix(); Where "cam" is the camera I want to change orthographic views (I have a collision that swaps between perspectives and a collision to reset it immediately after).

    Thanks to everyone for the awesome input!
     
    Last edited: Nov 10, 2021
  28. XJonOneX

    XJonOneX

    Joined:
    Dec 19, 2017
    Posts:
    113
    This solution is the best out of all the available options. It does exactly what I need it to do: Lerp between ortho and perspective.

    The free asset one made shadows screwy.