7
\$\begingroup\$

I am working on a 3d scene editor and would like to show the scene in orthographic projection. My current problem is that I am not sure how to calculate the orthographic projection matrix such that the objects do not appear at a completely different size on the screen.

I currently have the following 2 functions for calculating the camera projection matrix.

Matrix4.createPerspective = function(fov, aspect, near, far, result) {
    if (typeof result === 'undefined') {
        result = new Matrix4();
    }

    var ymax = near * Math.tan(fov * Math.PI / 360);
    var ymin = - ymax;
    var xmin = ymin * aspect;
    var xmax = ymax * aspect;

    Matrix4.createFrustum(xmin, xmax, ymin, ymax, near, far, /*out*/ result);

    return result;
};

Matrix4.createOrthographic = function(left, right, top, bottom, near, far, result) {
    if (typeof result === 'undefined') {
        result = new Matrix4();
    }

    var re = result.elements;
    var w = right - left;
    var h = top - bottom;
    var p = far - near;

    var x = ( right + left ) / w;
    var y = ( top + bottom ) / h;
    var z = ( far + near ) / p;

    re[0] =2/w;  re[4] = 0;   re[8]  = 0;   re[12] =-x;
    re[1] = 0;   re[5] =2/h;  re[9]  = 0;   re[13] =-y;
    re[2] = 0;   re[6] = 0;   re[10] =-2/p; re[14] =-z;
    re[3] = 0;   re[7] = 0;   re[11] = 0;   re[15] = 1;

    return result;
};

And this is how they are being used:

    updateProjectionMatrix: function(entity) {
        var camera = entity.getComponent('Camera');
        if (camera.isDirty()) {
            camera.aspect = camera.width / camera.height;
            if (camera.isOrthographic()){
                Matrix4.createOrthographic(
                    0, 
                    camera.width, 
                    0, 
                    camera.height, 
                    camera.near, 
                    camera.far, 
            /*out*/ camera._projectionMatrix);
            } else {
                Matrix4.createPerspective(
                    camera.fov,
                    camera.aspect,
                    camera.near,
                    camera.far,
            /*out*/ camera._projectionMatrix);
            }
        }
    },

Here is an example of what I am trying to accomplish inside Blender (F5 toggles between Ortho and Perspective). Note that both cubes appear to be about the same size on the viewports:

Blender Example

\$\endgroup\$
  • \$\begingroup\$ Pretty sure all you need to do is set your focal distance to be constant. This would be the distance from vanishing point to the projection plane. \$\endgroup\$ – RandyGaul Dec 21 '13 at 0:41
  • \$\begingroup\$ @RandyGaul I'm sure you mean this anyway, but syncing both distances instead of fixing them is sufficient. \$\endgroup\$ – danijar Dec 21 '13 at 6:23
4
\$\begingroup\$

First of all, your createPerspective function doesn't look quite right - compare the formulas used for gluPerspective.

If you want to use the same view matrix for both cameras, then when you set up the orthographic matrix, instead of using 0, camera.width, 0, camera.height you probably want -0.5*camera.width, 0.5*camera.width, -0.5*camera.height, 0.5*camera.height. This will keep the view centered around the camera position, instead of putting lower-left corner of the view at the camera position.

You may also need to use different near/far values for the ortho camera, depending on whether you want the ortho view to show geometry behind the camera (which would require a negative near plane value).

Then, what you need to do next is calculate camera.width and camera.height based on the currently selected/focused object when you switch to orthographic mode. You want those values to represent the width and height of the frustum at the depth (distance along camera space z-axis) of the selected object.

If you use a gluPerspective-style perspective matrix, then the [0, 0] and [1, 1] components of the matrix represent 2/width and 2/height respectively, at a depth of 1.0 units from the camera. The width and height of the frustum scale linearly with depth, so all you would need to do is multiply them by the depth of the focused object:

objectDepth = dot(objectPos - cameraPos, cameraViewVector);
camera.width = (2.0 / perspectiveMatrix[0]) * objectDepth;
camera.height = (2.0 / perspectiveMatrix[5]) * objectDepth;

Of course, you can also recompute the perspective matrix components using the FOV and aspect ratio if necessary.

\$\endgroup\$
  • \$\begingroup\$ Once you have the objectDepth, you can use the following to calculate the ortho width and height: var ymax = Math.tan(camera.fov * Math.PI / 360); var xmax = ymax * camera.aspect; var width = objectDepth * xmax; var height = objectDepth * ymax; \$\endgroup\$ – zfedoran Jan 31 '14 at 16:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.