Camera settings for smooth conversion from perspective to parallel

I believe this is strictly a math problem, and my math skills are lacking.

I want to switch a perspective camera pointing at a scene (with its specific frustum and zoom settings) to a parallel camera. Easy enough: just call “setParallelProjection(true)”. Except that now my scene is completely out of whack: it’s zoomed way out to the point of invisibility.

Through trial and error, I realized that the new parallel frustum settings need to be updated, as the old “trapezoidal” ones for the perspective camera no longer apply to the “box” frustum of the parallel camera. Also, the zoom value doesn’t influence the scene, which makes sense given that “zoom” doesn’t mean anything with a parallel camera. However, judicious updates to the parallel frustum settings seems to mimic a perspective zoom - smaller frustum, closer “zoom”, larger frustum, farther away “zoom”.

What I’m looking for here is how to programmatically convert [perspective frustum + zoom] values into [parallel frustum] values so that the rendered image occupies the same general space in the ViewPort.

Sorry if this has been answered elsewhere or if what I’m requesting makes no sense. Thanks!

1 Like

To transition between perspective and parallel without changing the apparent size of objects, you have to pick objects at a particular distance from the camera. If you have objects both 10 world units from the camera and 1000 world units from the camera, “something has to give”.

OK, let’s say I’ve identified an object at a particular distance. Given this, is there a straightforward algorithm/formula for how to make that object appear the same size after switching the camera from perspective to parallel mode?

1 Like

Yes. Here’s how it works in Maud:

        boolean parallel = options.isParallelProjection();
        if (parallel) {
            float halfHeight = CameraOptions.getFrustumYHalfTangent() * range;
            float halfWidth = aspectRatio * halfHeight;
            camera.setFrustumBottom(-halfHeight);
            camera.setFrustumFar(far);
            camera.setFrustumLeft(-halfWidth);
            camera.setFrustumNear(near);
            camera.setFrustumRight(halfWidth);
            camera.setFrustumTop(halfHeight);
            camera.setParallelProjection(true);
        } else {
            float yDegrees = CameraOptions.getFrustumYDegrees();
            camera.setFrustumPerspective(yDegrees, aspectRatio, near, far);
        }

and the relevant code from CameraOptions:

    /**
     * Read the vertical angle for camera frusta.
     *
     * @return angle (in degrees of arc, >0, <180)
     */
    public static float getFrustumYDegrees() {
        assert frustumYDegrees > 0f : frustumYDegrees;
        assert frustumYDegrees < 180f : frustumYDegrees;
        return frustumYDegrees;
    }
[...]
    /**
     * Read the vertical half-tangent for camera frusta.
     *
     * @return tangent of 1/2 the vertical angle (&gt;0)
     */
    public static float getFrustumYHalfTangent() {
        float yRadians = MyMath.toRadians(frustumYDegrees);
        float tangent = FastMath.tan(yRadians / 2f);

        assert tangent > 0f : tangent;
        return tangent;
    }
  • frustumYDegrees is a parameter maintained by the application.
  • range is the distance to the focus object, in world units.
  • aspectRatio is calculated by the viewAspectRatio() method:
    /**
     * Determine the aspect ratio of the specified camera's viewport.
     *
     * @param camera the Camera to analyze (not null, unaffected)
     * @return width divided by height (&gt;0)
     */
    public static float viewAspectRatio(Camera camera) {
        /*
         * Note: camera.getHeight() returns the height of the display,
         * not the height of the viewport!  The display and the viewport
         * often have the same aspect ratio, but not always.
         */
        float bottom = camera.getViewPortBottom();
        assert bottom >= 0f : bottom;
        assert bottom <= 1f : bottom;

        float top = camera.getViewPortTop();
        assert top >= 0f : top;
        assert top <= 1f : top;

        float yFraction = top - bottom;
        assert yFraction > 0f : yFraction;

        float height = camera.getHeight() * yFraction;
        assert height > 0f : height;

        float left = camera.getViewPortLeft();
        assert left >= 0f : left;
        assert left <= 1f : left;

        float right = camera.getViewPortRight();
        assert right >= 0f : right;
        assert right <= 1f : right;

        float xFraction = right - left;
        assert xFraction > 0f : xFraction;

        float width = camera.getWidth() * xFraction;
        assert width > 0f : width;

        float result = width / height;

        assert result > 0f : result;
        return result;
    }

Did you get the transition to work the way you wanted?