Browse Source

multiview component

Trevor Baron 6 years ago
parent
commit
7911814c41

+ 1 - 1
dist/preview release/what's new.md

@@ -29,7 +29,7 @@
   - InvalidateRect added to AdvancedDynamicTexture to improve perf for heavily populated GUIs, works with shadows ([TrevorDev](https://github.com/TrevorDev)) **** NEED DEMO or DOC LINK)
 - Migrated the code to modules and deploy [ES6 npm packages](https://doc.babylonjs.com/features/es6_support) ([Sebavan](https://github.com/Sebavan))
 - Added `TrailMesh` class. Credit to furcatomasz ([danjpar](https://github.com/danjpar)) **** NEED DEMO or DOC LINK)
-- Support rendering to a Multiview outputRenderTargetTexture to improve performance for XR scenarios ([TrevorDev](https://github.com/TrevorDev))
+- Support rendering to a Multiview outputRenderTargetTexture with multiview engine component to improve performance for XR scenarios ([TrevorDev](https://github.com/TrevorDev))
 - PBR:
   - Added Inspector Debug Mode ([Sebavan](https://github.com/Sebavan)) **** NEED DEMO or DOC LINK)
   - Added Smith Height Correlated Visibility term to PBR ([Sebavan](https://github.com/Sebavan)) **** NEED DEMO or DOC LINK)

+ 3 - 2
src/Cameras/RigModes/vrRigMode.ts

@@ -1,6 +1,7 @@
 import { Camera } from "../camera";
 import { Matrix, Viewport } from "../../Maths/math";
-import { VRDistortionCorrectionPostProcess, VRMultiviewToSingleview } from "../../PostProcesses/vrDistortionCorrectionPostProcess";
+import { VRDistortionCorrectionPostProcess } from "../../PostProcesses/vrDistortionCorrectionPostProcess";
+import { VRMultiviewToSingleviewPostProcess } from '../../PostProcesses/vrMultiviewToSingleviewPostProcess';
 import { VRCameraMetrics } from "../VR/vrCameraMetrics";
 import { Logger } from '../../Misc/logger';
 
@@ -30,7 +31,7 @@ Camera._setVRRigMode = function(camera: Camera, rigParams: any) {
             metrics.multiviewEnabled = false;
         }else {
             camera._useMultiviewToSingleView = true;
-            camera._rigPostProcess = new VRMultiviewToSingleview("VRMultiviewToSingleview", camera, metrics.postProcessScaleFactor);
+            camera._rigPostProcess = new VRMultiviewToSingleviewPostProcess("VRMultiviewToSingleview", camera, metrics.postProcessScaleFactor);
         }
     }
 

+ 2 - 2
src/Cameras/VR/webVRCamera.ts

@@ -14,7 +14,7 @@ import { AbstractMesh } from "../../Meshes/abstractMesh";
 import { Ray } from "../../Culling/ray";
 import { HemisphericLight } from "../../Lights/hemisphericLight";
 import { Logger } from '../../Misc/logger';
-import { VRMultiviewToSingleview } from '../../PostProcesses/vrDistortionCorrectionPostProcess';
+import { VRMultiviewToSingleviewPostProcess } from '../../PostProcesses/vrMultiviewToSingleviewPostProcess';
 
 // Side effect import to define the stereoscopic mode.
 import "../RigModes/webVRRigMode";
@@ -289,7 +289,7 @@ export class WebVRFreeCamera extends FreeCamera implements PoseControlled {
                 this._useMultiviewToSingleView = false;
             }else {
                 this._useMultiviewToSingleView = true;
-                this._rigPostProcess = new VRMultiviewToSingleview("VRMultiviewToSingleview", this, 1.0);
+                this._rigPostProcess = new VRMultiviewToSingleviewPostProcess("VRMultiviewToSingleview", this, 1.0);
             }
         }
 

+ 0 - 27
src/Cameras/camera.ts

@@ -13,7 +13,6 @@ import { ICullable } from "../Culling/boundingInfo";
 import { Logger } from "../Misc/logger";
 import { _TypeStore } from '../Misc/typeStore';
 import { _DevTools } from '../Misc/devTools';
-import { MultiviewRenderTarget } from '../Materials/Textures/renderTargetTexture';
 
 declare type PostProcess = import("../PostProcesses/postProcess").PostProcess;
 declare type RenderTargetTexture = import("../Materials/Textures/renderTargetTexture").RenderTargetTexture;
@@ -240,32 +239,6 @@ export class Camera extends Node {
     public outputRenderTarget: Nullable<RenderTargetTexture> = null;
 
     /**
-     * @hidden
-     * For cameras that cannot use multiview images to display directly. (e.g. webVR camera will render to multiview texture, then copy to each eye texture and go from there)
-     */
-    public _useMultiviewToSingleView = false;
-    /**
-     * @hidden
-     * For cameras that cannot use multiview images to display directly. (e.g. webVR camera will render to multiview texture, then copy to each eye texture and go from there)
-     */
-    public _multiviewTexture: Nullable<RenderTargetTexture> = null;
-
-    /**
-     * @hidden
-     * ensures the multiview texture of the camera exists and has the specified width/height
-     * @param width height to set on the multiview texture
-     * @param height width to set on the multiview texture
-     */
-    public _resizeOrCreateMultiviewTexture(width: number, height: number) {
-        if (!this._multiviewTexture) {
-            this._multiviewTexture = new MultiviewRenderTarget(this.getScene(), {width: width, height: height});
-        }else if (this._multiviewTexture.getRenderWidth() != width || this._multiviewTexture.getRenderHeight() != height) {
-            this._multiviewTexture.dispose();
-            this._multiviewTexture = new MultiviewRenderTarget(this.getScene(), {width: width, height: height});
-        }
-    }
-
-    /**
      * Observable triggered when the camera view matrix has changed.
      */
     public onViewMatrixChangedObservable = new Observable<Camera>();

+ 168 - 0
src/Engines/Extensions/engine.multiview.ts

@@ -0,0 +1,168 @@
+import { Camera } from "../../Cameras/camera";
+import { Engine } from "../../Engines/engine";
+import { Scene } from "../../scene";
+import { InternalTexture } from '../../Materials/Textures/internalTexture';
+import { Nullable } from '../../types';
+import { RenderTargetTexture, MultiviewRenderTarget } from '../../Materials/Textures/renderTargetTexture';
+import { Matrix, Tmp, Frustum } from '../../Maths/math';
+import { UniformBuffer } from '../../Materials/uniformBuffer';
+
+declare module "../../Engines/engine" {
+    export interface Engine {
+        /**
+         * Creates a new multiview render target
+         * @param width defines the width of the texture
+         * @param height defines the height of the texture
+         * @returns the created multiview texture
+         */
+        createMultiviewRenderTargetTexture(width: number, height: number): InternalTexture;
+
+        /**
+         * Binds a multiview framebuffer to be drawn to
+         * @param multiviewTexture texture to bind
+         */
+        bindMultiviewFramebuffer(multiviewTexture: InternalTexture): void;
+    }
+}
+
+Engine.prototype.createMultiviewRenderTargetTexture = function(width: number, height: number) {
+    var gl = this._gl;
+
+    if (!this.getCaps().multiview) {
+        throw "Multiview is not supported";
+    }
+
+    var internalTexture = new InternalTexture(this, InternalTexture.DATASOURCE_UNKNOWN, true);
+    internalTexture.width = width;
+    internalTexture.height = height;
+    internalTexture._framebuffer = gl.createFramebuffer();
+
+    internalTexture._colorTextureArray = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_2D_ARRAY, internalTexture._colorTextureArray);
+    (gl as any).texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.RGBA8, width, height, 2);
+
+    internalTexture._depthStencilTextureArray = gl.createTexture();
+    gl.bindTexture(gl.TEXTURE_2D_ARRAY, internalTexture._depthStencilTextureArray);
+    (gl as any).texStorage3D(gl.TEXTURE_2D_ARRAY, 1, (gl as any).DEPTH32F_STENCIL8, width, height, 2);
+    internalTexture.isReady = true;
+    return internalTexture;
+};
+
+Engine.prototype.bindMultiviewFramebuffer = function(multiviewTexture: InternalTexture) {
+    var gl: any = this._gl;
+    var ext = this.getCaps().multiview;
+
+    this.bindFramebuffer(multiviewTexture, undefined, undefined, undefined, true);
+    gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, multiviewTexture._framebuffer);
+    if (multiviewTexture._colorTextureArray && multiviewTexture._depthStencilTextureArray) {
+        ext.framebufferTextureMultiviewWEBGL(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, multiviewTexture._colorTextureArray, 0, 0, 2);
+        ext.framebufferTextureMultiviewWEBGL(gl.DRAW_FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, multiviewTexture._depthStencilTextureArray, 0, 0, 2);
+    } else {
+        throw "Invalid multiview frame buffer";
+    }
+};
+
+declare module "../../Cameras/camera" {
+    export interface Camera {
+        /**
+         * @hidden
+         * For cameras that cannot use multiview images to display directly. (e.g. webVR camera will render to multiview texture, then copy to each eye texture and go from there)
+         */
+        _useMultiviewToSingleView: boolean;
+        /**
+         * @hidden
+         * For cameras that cannot use multiview images to display directly. (e.g. webVR camera will render to multiview texture, then copy to each eye texture and go from there)
+         */
+        _multiviewTexture: Nullable<RenderTargetTexture>;
+
+        /**
+         * @hidden
+         * ensures the multiview texture of the camera exists and has the specified width/height
+         * @param width height to set on the multiview texture
+         * @param height width to set on the multiview texture
+         */
+        _resizeOrCreateMultiviewTexture(width: number, height: number): void;
+    }
+}
+
+Camera.prototype._useMultiviewToSingleView = false;
+
+Camera.prototype._multiviewTexture = null;
+
+Camera.prototype._resizeOrCreateMultiviewTexture = function(width: number, height: number) {
+    if (!this._multiviewTexture) {
+        this._multiviewTexture = new MultiviewRenderTarget(this.getScene(), {width: width, height: height});
+    }else if (this._multiviewTexture.getRenderWidth() != width || this._multiviewTexture.getRenderHeight() != height) {
+        this._multiviewTexture.dispose();
+        this._multiviewTexture = new MultiviewRenderTarget(this.getScene(), {width: width, height: height});
+    }
+};
+
+declare module "../../scene" {
+    export interface Scene {
+        /** @hidden */
+        _transformMatrixR: Matrix;
+        /** @hidden */
+        _multiviewSceneUbo: Nullable<UniformBuffer>;
+        /** @hidden */
+        _createMultiviewUbo(): void;
+        /** @hidden */
+        _updateMultiviewUbo(viewR?: Matrix, projectionR?: Matrix): void;
+        /** @hidden */
+        _renderMultiviewToSingleView(camera: Camera): void;
+    }
+}
+
+Scene.prototype._transformMatrixR = Matrix.Zero();
+Scene.prototype._multiviewSceneUbo = null;
+Scene.prototype._createMultiviewUbo = function() {
+    this._multiviewSceneUbo = new UniformBuffer(this._engine, undefined, true);
+    this._multiviewSceneUbo.addUniform("viewProjection", 16);
+    this._multiviewSceneUbo.addUniform("viewProjectionR", 16);
+    this._multiviewSceneUbo.addUniform("view", 16);
+};
+Scene.prototype._updateMultiviewUbo = function(viewR?: Matrix, projectionR?: Matrix) {
+    if (viewR && projectionR) {
+        viewR.multiplyToRef(projectionR, this._transformMatrixR);
+    }
+
+    if (viewR && projectionR) {
+        viewR.multiplyToRef(projectionR, Tmp.Matrix[0]);
+        Frustum.GetRightPlaneToRef(Tmp.Matrix[0], this._frustumPlanes[3]); // Replace right plane by second camera right plane
+    }
+
+    if (this._multiviewSceneUbo) {
+        this._multiviewSceneUbo.updateMatrix("viewProjection", this._transformMatrix);
+        this._multiviewSceneUbo.updateMatrix("viewProjectionR", this._transformMatrixR);
+        this._multiviewSceneUbo.updateMatrix("view", this._viewMatrix);
+        this._multiviewSceneUbo.update();
+    }
+};
+Scene.prototype._renderMultiviewToSingleView = function(camera: Camera) {
+    // Multiview is only able to be displayed directly for API's such as webXR
+    // This displays a multiview image by rendering to the multiview image and then
+    // copying the result into the sub cameras instead of rendering them and proceeding as normal from there
+
+    // Render to a multiview texture
+    camera._resizeOrCreateMultiviewTexture(
+        (camera._rigPostProcess && camera._rigPostProcess && camera._rigPostProcess.width > 0) ? camera._rigPostProcess.width / 2 : this.getEngine().getRenderWidth(true) / 2,
+        (camera._rigPostProcess && camera._rigPostProcess && camera._rigPostProcess.height > 0) ? camera._rigPostProcess.height : this.getEngine().getRenderHeight(true)
+    );
+    if (!this._multiviewSceneUbo) {
+        this._createMultiviewUbo();
+    }
+    camera.outputRenderTarget = camera._multiviewTexture;
+    this._renderForCamera(camera);
+    camera.outputRenderTarget = null;
+
+    // Consume the multiview texture through a shader for each eye
+    for (var index = 0; index < camera._rigCameras.length; index++) {
+        var engine = this._engine;
+        this._activeCamera = camera._rigCameras[index];
+        engine.setViewport(this._activeCamera.viewport);
+        if (this.postProcessManager) {
+            this.postProcessManager._prepareFrame();
+            this.postProcessManager._finalizeFrame(this._activeCamera.isIntermediate);
+        }
+    }
+};

+ 0 - 47
src/Engines/engine.ts

@@ -5604,53 +5604,6 @@ export class Engine {
     }
 
     /**
-     * Creates a new multiview render target
-     * @param width defines the width of the texture
-     * @param height defines the height of the texture
-     * @returns the created multiview texture
-     */
-    public createMultiviewRenderTargetTexture(width: number, height: number) {
-        var gl = this._gl;
-
-        if (!this.getCaps().multiview) {
-            throw "Multiview is not supported";
-        }
-
-        var internalTexture = new InternalTexture(this, InternalTexture.DATASOURCE_UNKNOWN, true);
-        internalTexture.width = width;
-        internalTexture.height = height;
-        internalTexture._framebuffer = gl.createFramebuffer();
-
-        internalTexture._colorTextureArray = gl.createTexture();
-        gl.bindTexture(gl.TEXTURE_2D_ARRAY, internalTexture._colorTextureArray);
-        (gl as any).texStorage3D(gl.TEXTURE_2D_ARRAY, 1, gl.RGBA8, width, height, 2);
-
-        internalTexture._depthStencilTextureArray = gl.createTexture();
-        gl.bindTexture(gl.TEXTURE_2D_ARRAY, internalTexture._depthStencilTextureArray);
-        (gl as any).texStorage3D(gl.TEXTURE_2D_ARRAY, 1, (gl as any).DEPTH32F_STENCIL8, width, height, 2);
-        internalTexture.isReady = true;
-        return internalTexture;
-    }
-
-    /**
-     * Binds a multiview framebuffer to be drawn to
-     * @param multiviewTexture texture to bind
-     */
-    public bindMultiviewFramebuffer(multiviewTexture: InternalTexture) {
-        var gl: any = this._gl;
-        var ext = this.getCaps().multiview;
-
-        this.bindFramebuffer(multiviewTexture, undefined, undefined, undefined, true);
-        gl.bindFramebuffer(gl.DRAW_FRAMEBUFFER, multiviewTexture._framebuffer);
-        if (multiviewTexture._colorTextureArray && multiviewTexture._depthStencilTextureArray) {
-            ext.framebufferTextureMultiviewWEBGL(gl.DRAW_FRAMEBUFFER, gl.COLOR_ATTACHMENT0, multiviewTexture._colorTextureArray, 0, 0, 2);
-            ext.framebufferTextureMultiviewWEBGL(gl.DRAW_FRAMEBUFFER, gl.DEPTH_STENCIL_ATTACHMENT, multiviewTexture._depthStencilTextureArray, 0, 0, 2);
-        } else {
-            throw "Invalid multiview frame buffer";
-        }
-    }
-
-    /**
      * Creates a new render target cube texture
      * @param size defines the size of the texture
      * @param options defines the options used to create the texture

+ 2 - 1
src/PostProcesses/index.ts

@@ -27,4 +27,5 @@ export * from "./sharpenPostProcess";
 export * from "./stereoscopicInterlacePostProcess";
 export * from "./tonemapPostProcess";
 export * from "./volumetricLightScatteringPostProcess";
-export * from "./vrDistortionCorrectionPostProcess";
+export * from "./vrDistortionCorrectionPostProcess";
+export * from "./vrMultiviewToSingleviewPostProcess";

+ 1 - 29
src/PostProcesses/vrDistortionCorrectionPostProcess.ts

@@ -6,7 +6,6 @@ import { Texture } from "../Materials/Textures/texture";
 import { PostProcess } from "./postProcess";
 
 import "../Shaders/vrDistortionCorrection.fragment";
-import "../Shaders/vrMultiviewToSingleview.fragment";
 
 /**
  * VRDistortionCorrectionPostProcess used for mobile VR
@@ -53,31 +52,4 @@ export class VRDistortionCorrectionPostProcess extends PostProcess {
             effect.setFloat4("HmdWarpParam", this._distortionFactors[0], this._distortionFactors[1], this._distortionFactors[2], this._distortionFactors[3]);
         });
     }
-}
-
-/**
- * VRMultiviewToSingleview used to convert multiview texture arrays to standard textures for scenarios such as webVR
- * This will not be used for webXR as it supports displaying texture arrays directly
- */
-export class VRMultiviewToSingleview extends PostProcess {
-    /**
-     * Initializes a VRMultiviewToSingleview
-     * @param name name of the post process
-     * @param camera camera to be applied to
-     * @param scaleFactor scaling factor to the size of the output texture
-     */
-    constructor(name: string, camera: Camera, scaleFactor: number) {
-        super(name, "vrMultiviewToSingleview", ["imageIndex"], ["multiviewSampler"], scaleFactor, camera, Texture.BILINEAR_SAMPLINGMODE);
-
-        this.onSizeChangedObservable.add(() => {
-        });
-        this.onApplyObservable.add((effect: Effect) => {
-            if (camera._scene.activeCamera && camera._scene.activeCamera.isLeftCamera) {
-                effect.setInt("imageIndex", 0);
-            }else {
-                effect.setInt("imageIndex", 1);
-            }
-            effect.setTexture("multiviewSampler", camera._multiviewTexture);
-        });
-    }
-}
+}

+ 34 - 0
src/PostProcesses/vrMultiviewToSingleviewPostProcess.ts

@@ -0,0 +1,34 @@
+import { Camera } from "../Cameras/camera";
+import { Effect } from "../Materials/effect";
+import { Texture } from "../Materials/Textures/texture";
+import { PostProcess } from "./postProcess";
+
+import "../Shaders/vrMultiviewToSingleview.fragment";
+import "../Engines/Extensions/engine.multiview";
+
+/**
+ * VRMultiviewToSingleview used to convert multiview texture arrays to standard textures for scenarios such as webVR
+ * This will not be used for webXR as it supports displaying texture arrays directly
+ */
+export class VRMultiviewToSingleviewPostProcess extends PostProcess {
+    /**
+     * Initializes a VRMultiviewToSingleview
+     * @param name name of the post process
+     * @param camera camera to be applied to
+     * @param scaleFactor scaling factor to the size of the output texture
+     */
+    constructor(name: string, camera: Camera, scaleFactor: number) {
+        super(name, "vrMultiviewToSingleview", ["imageIndex"], ["multiviewSampler"], scaleFactor, camera, Texture.BILINEAR_SAMPLINGMODE);
+
+        this.onSizeChangedObservable.add(() => {
+        });
+        this.onApplyObservable.add((effect: Effect) => {
+            if (camera._scene.activeCamera && camera._scene.activeCamera.isLeftCamera) {
+                effect.setInt("imageIndex", 0);
+            }else {
+                effect.setInt("imageIndex", 1);
+            }
+            effect.setTexture("multiviewSampler", camera._multiviewTexture);
+        });
+    }
+}

+ 15 - 54
src/scene.ts

@@ -5,7 +5,7 @@ import { Observable, Observer } from "./Misc/observable";
 import { SmartArrayNoDuplicate, SmartArray, ISmartArrayLike } from "./Misc/smartArray";
 import { StringDictionary } from "./Misc/stringDictionary";
 import { Tags } from "./Misc/tags";
-import { Color4, Color3, Plane, Vector2, Vector3, Matrix, Tmp, Frustum } from "./Maths/math";
+import { Color4, Color3, Plane, Vector2, Vector3, Matrix, Frustum } from "./Maths/math";
 import { Geometry } from "./Meshes/geometry";
 import { TransformNode } from "./Meshes/transformNode";
 import { SubMesh } from "./Meshes/subMesh";
@@ -870,7 +870,8 @@ export class Scene extends AbstractScene implements IAnimatable {
     /** All of the active cameras added to this scene. */
     public activeCameras = new Array<Camera>();
 
-    private _activeCamera: Nullable<Camera>;
+    /** @hidden */
+    public _activeCamera: Nullable<Camera>;
     /** Gets or sets the current active camera */
     public get activeCamera(): Nullable<Camera> {
         return this._activeCamera;
@@ -1046,7 +1047,8 @@ export class Scene extends AbstractScene implements IAnimatable {
     public proceduralTexturesEnabled = true;
 
     // Private
-    private _engine: Engine;
+    /** @hidden */
+    public _engine: Engine;
 
     // Performance counters
     private _totalVertices = new PerfCounter();
@@ -1112,18 +1114,19 @@ export class Scene extends AbstractScene implements IAnimatable {
     /** @hidden */
     public _activeAnimatables = new Array<Animatable>();
 
-    private _transformMatrix = Matrix.Zero();
-    private _transformMatrixR = Matrix.Zero();
+    /** @hidden */
+    public _transformMatrix = Matrix.Zero();
     private _sceneUbo: UniformBuffer;
-    private _multiviewSceneUbo: UniformBuffer;
 
-    private _viewMatrix: Matrix;
+    /** @hidden */
+    public _viewMatrix: Matrix;
     private _projectionMatrix: Matrix;
     private _wheelEventName = "";
     /** @hidden */
     public _forcedViewPosition: Nullable<Vector3>;
 
-    private _frustumPlanes: Plane[];
+    /** @hidden */
+    public _frustumPlanes: Plane[];
     /**
      * Gets the list of frustum planes (built from the active camera)
      */
@@ -1606,13 +1609,6 @@ export class Scene extends AbstractScene implements IAnimatable {
         this._sceneUbo.addUniform("view", 16);
     }
 
-    private _createMultiviewUbo(): void {
-        this._multiviewSceneUbo = new UniformBuffer(this._engine, undefined, true);
-        this._multiviewSceneUbo.addUniform("viewProjection", 16);
-        this._multiviewSceneUbo.addUniform("viewProjectionR", 16);
-        this._multiviewSceneUbo.addUniform("view", 16);
-    }
-
     // Pointers handling
     private _setRayOnPointerInfo(pointerInfo: PointerInfo) {
         if (pointerInfo.pickInfo && !pointerInfo.pickInfo._pickingUnavailable) {
@@ -2574,9 +2570,6 @@ export class Scene extends AbstractScene implements IAnimatable {
         this._projectionMatrix = projectionL;
 
         this._viewMatrix.multiplyToRef(this._projectionMatrix, this._transformMatrix);
-        if (viewR && projectionR) {
-            viewR.multiplyToRef(projectionR, this._transformMatrixR);
-        }
 
         // Update frustum
         if (!this._frustumPlanes) {
@@ -2585,16 +2578,8 @@ export class Scene extends AbstractScene implements IAnimatable {
             Frustum.GetPlanesToRef(this._transformMatrix, this._frustumPlanes);
         }
 
-        if (viewR && projectionR) {
-            viewR.multiplyToRef(projectionR, Tmp.Matrix[0]);
-            Frustum.GetRightPlaneToRef(Tmp.Matrix[0], this._frustumPlanes[3]); // Replace right plane by second camera right plane
-        }
-
         if (this._multiviewSceneUbo && this._multiviewSceneUbo.useUbo) {
-            this._multiviewSceneUbo.updateMatrix("viewProjection", this._transformMatrix);
-            this._multiviewSceneUbo.updateMatrix("viewProjectionR", this._transformMatrixR);
-            this._multiviewSceneUbo.updateMatrix("view", this._viewMatrix);
-            this._multiviewSceneUbo.update();
+            this._updateMultiviewUbo(viewR, projectionR);
         } else if (this._sceneUbo.useUbo) {
             this._sceneUbo.updateMatrix("viewProjection", this._transformMatrix);
             this._sceneUbo.updateMatrix("view", this._viewMatrix);
@@ -4045,7 +4030,8 @@ export class Scene extends AbstractScene implements IAnimatable {
     }
     /** @hidden */
     public _allowPostProcessClearColor = true;
-    private _renderForCamera(camera: Camera, rigParent?: Camera): void {
+    /** @hidden */
+    public _renderForCamera(camera: Camera, rigParent?: Camera): void {
         if (camera && camera._skipRendering) {
             return;
         }
@@ -4169,32 +4155,7 @@ export class Scene extends AbstractScene implements IAnimatable {
         }
 
         if (camera._useMultiviewToSingleView) {
-            // Multiview is only able to be displayed directly for API's such as webXR
-            // This displays a multiview image by rendering to the multiview image and then
-            // copying the result into the sub cameras instead of rendering them and proceeding as normal from there
-
-            // Render to a multiview texture
-            camera._resizeOrCreateMultiviewTexture(
-                (camera._rigPostProcess && camera._rigPostProcess && camera._rigPostProcess.width > 0) ? camera._rigPostProcess.width / 2 : this.getEngine().getRenderWidth(true) / 2,
-                (camera._rigPostProcess && camera._rigPostProcess && camera._rigPostProcess.height > 0) ? camera._rigPostProcess.height : this.getEngine().getRenderHeight(true)
-            );
-            if (!this._multiviewSceneUbo) {
-                this._createMultiviewUbo();
-            }
-            camera.outputRenderTarget = camera._multiviewTexture;
-            this._renderForCamera(camera);
-            camera.outputRenderTarget = null;
-
-            // Consume the multiview texture through a shader for each eye
-            for (var index = 0; index < camera._rigCameras.length; index++) {
-                var engine = this._engine;
-                this._activeCamera = camera._rigCameras[index];
-                engine.setViewport(this._activeCamera.viewport);
-                if (this.postProcessManager) {
-                    this.postProcessManager._prepareFrame();
-                    this.postProcessManager._finalizeFrame(this._activeCamera.isIntermediate);
-                }
-            }
+            this._renderMultiviewToSingleView(camera);
         } else {
             // rig cameras
             for (var index = 0; index < camera._rigCameras.length; index++) {