6
votes

I'm developing a game that consists of 2 stages, one of these has an orthographic projection, and the other stage has a perspective projection.

Currently when we go between modes we fade to black, and then come back in the new camera mode.

How would I go about smoothly transitioning between the two?

3
I am just guessing, but if P1(x) is the coordinates in one projection and P2(x) in the other, then you could get a smooth transition f P1(x) + (1-f) P2(x) by smoothly changing f from 0 to 1463035818_is_not_a_number
This question is interesting and clear, but as written it makes it sound like you are asking for someone to do the work for you. You should add more details, like the matrix for the ortho and the matrix for the perspective transformation, etc. As the other comment mentions, if your ortho projection is a 4x4 matrix and the perspective matrix is too, you could try a linear combination of the two.Jared Updike

3 Answers

5
votes

There are probably a handful of ways of accomplishing this, the two I found that seemed like they would work the best were:

  1. Lerping all the matrix elements from one matrix to the other. Apparently this works pretty well all things considered. I don't believe this transition will appear linear, though. You could try to give it an easing function instead of doing the interpolation linearly

  2. A dolly zoom on the perspective matrix going to/from a near 0 field of view. You would pop from the orthographic matrix to the near 0 perspective matrix and lerp the fov out to your target, and probably be heavily tweaking the near/far planes as you go. In reverse you would lerp to 0 and then pop to the orthographic matrix. The idea behind this being that things appear flatter with a lower fov and that a fov of 0 is essentially an orthographic projection. This is more complex but can also be tweaked a whole lot more.

2
votes

If you have access to a programmable pipeline (a.k.a. shaders), you can do the transition in the vertex shader. I have found that this works very well and does not introduce artifacts. Here's a GLSL code snippet:

#version 150

uniform mat4 uModelMatrix;
uniform mat4 uViewMatrix;
uniform mat4 uProjectionMatrix;

uniform float uNearClipPlane = 1.0;
uniform vec2  uPerspToOrtho = vec2( 0.0 );

in vec4 inPosition;

void main( void )
{    
    // Calculate view space position.
    vec4 view = uViewMatrix * uModelMatrix * inPosition;

    // Scale x&y to 'undo' perspective projection.
    view.x = mix( view.x, view.x * ( -view.z / uNearClipPlane ), uPerspToOrtho.x );
    view.y = mix( view.y, view.y * ( -view.z / uNearClipPlane ), uPerspToOrtho.y );

    // Output clip space coordinate.
    gl_Position = uProjectionMatrix * view;
}

In the code, uPerspToOrtho is a vec2 (e.g. a float2) that contains a value in the range [0..1]. When set to 0, your coordinates will use perspective projection (assuming your projection matrix is a perspective one). When set to 1, your coordinates will behave as if projected by an orthographic projection matrix. You can do this separately for the X- and Y-axes.

'uNearClipPlane' is the near plane distance, which is the value you used to create the perspective projection matrix.

When converting this to HLSL, you may need to use view.z instead of -view.z, but I could be wrong.

I hope you find this useful.

Edit: instead of passing in the near clip plane distance, you can also extract it from the projection matrix. For OpenGL, this is how:

float zNear = 2.0 * uProjectionMatrix[3][2] / ( 2.0 * uProjectionMatrix[2][2] - 2.0 );

Edit 2: you can optimize the code by doing the scaling on x and y at the same time:

view.xy = mix( view.xy, view.xy * ( -view.z / uNearClipPlane ), uPerspToOrtho.xy );

To get rid of the division, you could multiply by the inverse near plane distance:

uniform float uInvNearClipPlane; // = 1.0 / zNear
1
votes

I managed to do this without the explicit use of matrices. I used Java so the syntax is different but comparable. One of the things I used was this mix() function. It returns value1 when factor is 1 and value2 when factor is 0, and has a linear transition for every value in between.

private double mix(double value1, double value2, double factor)
{
    return (value1 * factor) + (value2 * (1 - factor));
}

When I call this function, I use value1 for perspective and value2 for orthographic, like so:mix(focalLength/voxel.z, orthoZoom, factor)

When determining your focal length and orthographic zoom factor, it is helpful to know that anything at distance focalLength/orthoZoom away from the camera will project to the same point throughout the transition.

Hope this helps. You can download my program to see how it looks at https://github.com/npetrangelo/3rd-Dimension/releases.