setProjectionFromUnion method
Assumes 2 cameras that are parallel and share an X-axis, and that the cameras' projection and world matrices have already been set. And that near and far planes are identical for both cameras. Visualization of this technique: https://computergraphics.stackexchange.com/a/4765
Implementation
void setProjectionFromUnion(Camera camera, PerspectiveCamera cameraL, PerspectiveCamera cameraR ) {
cameraLPos.setFromMatrixPosition( cameraL.matrixWorld );
cameraRPos.setFromMatrixPosition( cameraR.matrixWorld );
final ipd = cameraLPos.distanceTo( cameraRPos );
final projL = cameraL.projectionMatrix.storage;
final projR = cameraR.projectionMatrix.storage;
// VR systems will have identical far and near planes, and
// most likely identical top and bottom frustum extents.
// Use the left camera for these values.
final near = projL[ 14 ] / ( projL[ 10 ] - 1 );
final far = projL[ 14 ] / ( projL[ 10 ] + 1 );
final topFov = ( projL[ 9 ] + 1 ) / projL[ 5 ];
final bottomFov = ( projL[ 9 ] - 1 ) / projL[ 5 ];
final leftFov = ( projL[ 8 ] - 1 ) / projL[ 0 ];
final rightFov = ( projR[ 8 ] + 1 ) / projR[ 0 ];
final left = near * leftFov;
final right = near * rightFov;
// Calculate the camera's position offset from the
// left camera. xOffset should be roughly half `ipd`.
final zOffset = ipd / ( - leftFov + rightFov );
final xOffset = zOffset * - leftFov;
// TODO: Better way to apply this offset?
cameraL.matrixWorld.decompose( camera.position, camera.quaternion, camera.scale );
camera.translateX( xOffset );
camera.translateZ( zOffset );
camera.matrixWorld.compose( camera.position, camera.quaternion, camera.scale );
camera.matrixWorldInverse.setFrom( camera.matrixWorld ).invert();
// Find the union of the frustum values of the cameras and scale
// the values so that the near plane's position does not change in world space,
// although must now be relative to the union camera.
final near2 = near + zOffset;
final far2 = far + zOffset;
final left2 = left - xOffset;
final right2 = right + ( ipd - xOffset );
final top2 = topFov * far / far2 * near2;
final bottom2 = bottomFov * far / far2 * near2;
camera.projectionMatrix.makePerspective( left2, right2, top2, bottom2, near2, far2 );
}