Runtime perspective to orthographic camera

Hi!
I need to achieve at runtime the scaling/translation the editor camera performs when switching from/to perspective/orthographic camera, e.g. the fact that when switching camera mode, the camera kind of “stays in place”. I don’t need it to be smoothly animated like in editor, I just need the starting/ending translation/orthographic size values.
Achieving this at runtime needs calculations I couldn’t find searching the docs and the web.
Any ideas? :slight_smile:

I have always just swapped to another camera that was orthographic

1 Like

Doing a right transition is not that difficult but also quite math heavy. The SceneView source code is available online (just for reference, watch the license), however most parameters in the editor are handled through “animated variables” (AnimFloat, AnimBool, …). Those are essentially responsible for the “transion”. Though one important point about the SceneView camera is how it’s position is actually handled.

The SceneView camera isn’t really moved around directly. Instead the SceneView always has a pivot point in front of the camera which essentially works like the focus point. It’s the point the camera will orbit around and it’s the point that is set when you “LookAt” the sceneview to a target. The camera has a certain “size” which is used to calculate the distance from the pivot based on the current fov. When the transition from perspective to orthographic mode is done, the fov is lerped from the normal fov towards 0. That has the effect that the camera will move backwards (and theoretically infinitely far at a fov of 0). Since the pivot stays the same and the desired size stays the same the area around the pivot would appear about the same size.

When the fov is below a certain threshold (kOrthoThresholdAngle which is 3 degree) it actually switches to orthographic.

Since most parameters are actually “lerped” individually it’s a bit difficult to make sense of what actually happens when you switch mode.

2 Likes

You’d have to compose a projection;
not sure how, but you’d might see the difference if you decompose a cameras projection when on both settings.
Unity - Scripting API: Matrix4x4.decomposeProjection (unity3d.com)

In theory a Perspective camera visualizing from a single point outwards could include 4 points of the same position, leading out a distance to the projection square. Just like a orthographic has 4 points at camera, and 4 points at distance, making the cuboid cubicle view. The morph between the two is likely to require a custom matrix definition, where you would compose a warpable orthographic camera, whose closest camera points can be squeezed into a perspective.

9010060--1241947--Yes.gif
that’s really what’s happening here ^ so, just needs to warp all points into the perfect square that the otho is. Its just the perspective square has no distance here. Probably requires 2 cams. One preset to ortho and then this one removing its clipping plane getting the flat angle.

1 Like

I just had a bit of time and quickly made an example script.

using System.Collections;
using UnityEngine;

public class CamOrthoTransition : MonoBehaviour
{
    public Camera cam;
    private float fov = 90f;
    private float dist = 10f;
    private bool m_Ortho;
    private bool m_Animating = false;

    public string distText = "10";
    public Vector3 pos;
    public float size;
    public Transform[] objs;

    private void OnGUI()
    {
        GUI.enabled = !m_Animating;
        distText = GUILayout.TextField(distText);
        if (float.TryParse(distText, out float d))
            dist = d;
        if (!m_Ortho && GUILayout.Button("Orthographic") )
        {
            dist = float.Parse(distText);
            StartCoroutine(Perspective2Ortho());
        }
        if (m_Ortho && GUILayout.Button("Perspective"))
        {
            dist = float.Parse(distText);
            StartCoroutine(Ortho2Perspective());
        }
        for(int i = 0; i < objs.Length; i++)
            GUILayout.Label("O"+i+":" + cam.transform.InverseTransformPoint(objs[i].position).z);
    }
    private void OnDrawGizmos()
    {
        if (!m_Animating)
        {
            m_Ortho = cam.orthographic;
            if (cam.orthographic)
            {
                size = cam.orthographicSize;
            }
            else
            {
                if (float.TryParse(distText, out float d))
                    dist = d;
                size = dist * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);
                fov = cam.fieldOfView;
                var trans = cam.transform;
                pos = trans.position + trans.forward * dist;
            }
        }
        float a = cam.aspect;
        Gizmos.color = Color.green;
        var u = cam.transform.up * size;
        var r = cam.transform.right * size*a;
        Gizmos.DrawLine(pos + u + r, pos - u + r);
        Gizmos.DrawLine(pos + u - r, pos - u - r);
        Gizmos.DrawLine(pos + u + r, pos + u - r);
        Gizmos.DrawLine(pos - u + r, pos - u - r);
        Gizmos.DrawLine(pos + u + r, pos - u - r);
        Gizmos.DrawLine(pos - u + r, pos - u + r);
    }
    IEnumerator Perspective2Ortho()
    {
        m_Animating = true;
        var trans = cam.transform;
        fov = cam.fieldOfView;
        pos = trans.position + trans.forward * dist;
        size = dist * Mathf.Tan(fov * 0.5f * Mathf.Deg2Rad);

        for (float t = 0; t < 1.1f; t+=Time.deltaTime*1f)
        {
            float f = Mathf.Lerp(fov, 0, t);
            float d = size / Mathf.Tan(f * 0.5f * Mathf.Deg2Rad);
            cam.fieldOfView = f;
            trans.position = pos - trans.forward * d;
            if (f < 1f || d > 700)
                break;
            yield return null;
        }
        cam.orthographic = true;
        cam.orthographicSize = size;
        cam.fieldOfView = fov;
        m_Ortho = true;
        m_Animating = false;
    }
    IEnumerator Ortho2Perspective()
    {
        m_Animating = true;
        var trans = cam.transform;
        size = cam.orthographicSize;
        cam.orthographic = false;
        for (float t = 0; t < 1.1f; t += Time.deltaTime * 1f)
        {
            float f = Mathf.Lerp(1f, fov, t);
            float d = size / Mathf.Tan(f * 0.5f * Mathf.Deg2Rad);
            cam.fieldOfView = f;
            trans.position = pos - trans.forward * d;
            yield return null;
        }
        m_Ortho = false;
        m_Animating = false;
    }
}

It has a lot of “noise” and extra code like the gizmo to show the “depth-plane” that will keep its size as well as the GUI controls

Here’s the result with various depth plane distances. They have been choosen to be the center of one of the objects in the scene. So you can see the effect it has when you transition. Also when you look at the frustum of the camera, you will notice that the worldspace size at this plane will stay constant.
Examples

9010657--1242052--Persp2Ortho_01.gif

9010657--1242055--Persp2Ortho_02.gif

9010657--1242058--Persp2Ortho_03.gif

9010657--1242061--Persp2Ortho_04.gif

Note that at the moment the camera stays all the way in the distance when it switches to orthographic. That’s the main reason why the shadows disappear. Though shadows don’t look great in orthographic anyways ^^. Of course in orthographic mode you can move your camera closer or further away and the view would not change, only the clipping planes would make a difference.

If you want to cut your scene at the original near plane, you would need to adjust the nearplane as you move the camera back so it stays at the same position. When the transition is done and we switch to orthographic we can move the camera back so it’s again closer to the scene and adjust its nearplane so it also is located at the same original position in space. Though in my example that means the large “floor” cube would be cut off.

As I mentioned in my first post, you have to choose which plane that is parallel to the camera plane should stay fixed. Of course this plane is the only thing that stays fixed. Any other depth gets distorted. There’s no way around that since a perspective projection always distorts the image. This effect gets stronger the closer you get to the edges. An orthographic projection has no distortion and looks uniformly the same everywhere.

5 Likes

9010750--1242085--ortho_perspective.gif

Bunny seems to have cracked the nut, I was about to give it a run myself >.< I don’t want to read bunnies solution but i suspect a small position change is required!

1 Like

Wow thank you guys for your detailed answers, you rock!
Actually, maybe I should have given more details about my use case, which may have made things easier: What I’m trying to achieve is exclusively done from a top down view.
Anyway, what you provided will be useful, and I’m really grateful.