Преглед изворни кода

Merge pull request #5933 from TrevorDev/multiviewRTT

create multiview rtt
David Catuhe пре 6 година
родитељ
комит
717c034c62

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

@@ -27,6 +27,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))
 - 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)
@@ -120,7 +121,6 @@
 - Inspector light gizmo ([TrevorDev](https://github.com/TrevorDev))
 - Added option `multiMultiMaterials` to mesh.mergeMeshes ([danjpar](https://github.com/danjpar))
 - Expose fallback camera distortion metrics option in vrExperienceHelper ([TrevorDev](https://github.com/TrevorDev))
-- Added setColor method to boundingBoxGizmo ([TrevorDev](https://github.com/TrevorDev))
 - Added OnAfterEnteringVRObservable to webVRHelper ([TrevorDev](https://github.com/TrevorDev))
 - Added Support for Side By Side and Top/Bottom VR videos in the [video dome](https://doc.babylonjs.com/how_to/360videodome#video-types) ([Sebavan](https://github.com/Sebavan))
 - Added UnitTests for BaseCameraPointersInput and ArcRotateCameraPointersInput. ([mrdunk](https://github.com))

+ 15 - 1
src/Cameras/RigModes/vrRigMode.ts

@@ -1,7 +1,8 @@
 import { Camera } from "../camera";
 import { Matrix, Viewport } from "../../Maths/math";
-import { VRDistortionCorrectionPostProcess } from "../../PostProcesses/vrDistortionCorrectionPostProcess";
+import { VRDistortionCorrectionPostProcess, VRMultiviewToSingleview } from "../../PostProcesses/vrDistortionCorrectionPostProcess";
 import { VRCameraMetrics } from "../VR/vrCameraMetrics";
+import { Logger } from '../../Misc/logger';
 
 Camera._setVRRigMode = function(camera: Camera, rigParams: any) {
     var metrics = rigParams.vrCameraMetrics || VRCameraMetrics.GetDefault();
@@ -20,6 +21,19 @@ Camera._setVRRigMode = function(camera: Camera, rigParams: any) {
     camera._rigCameras[1]._cameraRigParams.vrPreViewMatrix = metrics.rightPreViewMatrix;
     camera._rigCameras[1].getProjectionMatrix = camera._rigCameras[1]._getVRProjectionMatrix;
 
+    // For multiview on a webVR camera
+    // First multiview will be rendered to camera._multiviewTexture
+    // Then this postprocess will run on each eye to copy the right texture to each eye
+    if (metrics.multiviewEnabled) {
+        if (!camera.getScene().getEngine().getCaps().multiview) {
+            Logger.Warn("Multiview is not supported, falling back to standard rendering");
+            metrics.multiviewEnabled = false;
+        }else {
+            camera._useMultiviewToSingleView = true;
+            camera._rigPostProcess = new VRMultiviewToSingleview("VRMultiviewToSingleview", camera, 1.0);
+        }
+    }
+
     if (metrics.compensateDistortion) {
         camera._rigCameras[0]._rigPostProcess = new VRDistortionCorrectionPostProcess("VR_Distort_Compensation_Left", camera._rigCameras[0], false, metrics);
         camera._rigCameras[1]._rigPostProcess = new VRDistortionCorrectionPostProcess("VR_Distort_Compensation_Right", camera._rigCameras[1], true, metrics);

+ 0 - 5
src/Cameras/RigModes/webVRRigMode.ts

@@ -29,10 +29,5 @@ Camera._setWebVRRigMode = function(camera: Camera, rigParams: any) {
         camera._rigCameras[1].getProjectionMatrix = camera._getWebVRProjectionMatrix;
         camera._rigCameras[1].parent = camera;
         camera._rigCameras[1]._getViewMatrix = camera._getWebVRViewMatrix;
-
-        if (Camera.UseAlternateWebVRRendering) {
-            camera._rigCameras[1]._skipRendering = true;
-            camera._rigCameras[0]._alternateCamera = camera._rigCameras[1];
-        }
     }
 };

+ 5 - 0
src/Cameras/VR/vrCameraMetrics.ts

@@ -60,6 +60,11 @@ export class VRCameraMetrics {
     public compensateDistortion = true;
 
     /**
+     * Defines if multiview should be enabled when rendering (Default: false)
+     */
+    public multiviewEnabled = false;
+
+    /**
      * Gets the rendering aspect ratio based on the provided resolutions.
      */
     public get aspectRatio(): number {

+ 6 - 0
src/Cameras/VR/vrExperienceHelper.ts

@@ -707,6 +707,12 @@ export class VRExperienceHelper {
 
         // Create VR cameras
         if (webVROptions.createFallbackVRDeviceOrientationFreeCamera) {
+            if (webVROptions.useMultiview) {
+                if (!webVROptions.vrDeviceOrientationCameraMetrics) {
+                    webVROptions.vrDeviceOrientationCameraMetrics = VRCameraMetrics.GetDefault();
+                }
+                webVROptions.vrDeviceOrientationCameraMetrics.multiviewEnabled = true;
+            }
             this._vrDeviceOrientationCamera = new VRDeviceOrientationFreeCamera("VRDeviceOrientationVRHelper", this._position, this._scene, true, webVROptions.vrDeviceOrientationCameraMetrics);
             this._vrDeviceOrientationCamera.angularSensibility = Number.MAX_VALUE;
         }

+ 16 - 0
src/Cameras/VR/webVRCamera.ts

@@ -13,6 +13,8 @@ import { Node } from "../../node";
 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';
 
 // Side effect import to define the stereoscopic mode.
 import "../RigModes/webVRRigMode";
@@ -140,6 +142,10 @@ export interface WebVROptions {
      */
     defaultHeight?: number;
 
+    /**
+     * If multiview should be used if availible (default: false)
+     */
+    useMultiview?: boolean;
 }
 
 /**
@@ -277,6 +283,16 @@ export class WebVRFreeCamera extends FreeCamera implements PoseControlled {
             this._frameData = new VRFrameData();
         }
 
+        if (webVROptions.useMultiview) {
+            if (!this.getScene().getEngine().getCaps().multiview) {
+                Logger.Warn("Multiview is not supported, falling back to standard rendering");
+                this._useMultiviewToSingleView = false;
+            }else {
+                this._useMultiviewToSingleView = true;
+                this._rigPostProcess = new VRMultiviewToSingleview("VRMultiviewToSingleview", this, 1.0);
+            }
+        }
+
         /**
          * The idea behind the following lines:
          * objects that have the camera as parent should actually have the rig cameras as a parent.

+ 27 - 8
src/Cameras/camera.ts

@@ -13,6 +13,7 @@ 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;
@@ -93,12 +94,6 @@ export class Camera extends Node {
     public static ForceAttachControlToAlwaysPreventDefault = false;
 
     /**
-     * @hidden
-     * Might be removed once multiview will be a thing
-     */
-    public static UseAlternateWebVRRendering = false;
-
-    /**
      * Define the input manager associated with the camera.
      */
     public inputs: CameraInputsManager<Camera>;
@@ -245,6 +240,32 @@ 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>();
@@ -271,8 +292,6 @@ export class Camera extends Node {
     protected _webvrViewMatrix = Matrix.Identity();
     /** @hidden */
     public _skipRendering = false;
-    /** @hidden */
-    public _alternateCamera: Camera;
 
     /** @hidden */
     public _projectionMatrix = new Matrix();

+ 60 - 3
src/Engines/engine.ts

@@ -174,6 +174,8 @@ export class EngineCapabilities {
     public timerQuery: EXT_disjoint_timer_query;
     /** Defines if timestamp can be used with timer query */
     public canUseTimestampForTimerQuery: boolean;
+    /** Defines if multiview is supported (https://www.khronos.org/registry/webgl/extensions/WEBGL_multiview/) */
+    public multiview: any;
     /** Function used to let the system compiles shaders in background */
     public parallelShaderCompile: {
         COMPLETION_STATUS_KHR: number;
@@ -1453,6 +1455,7 @@ export class Engine {
 
         this._caps.textureLOD = (this._webGLVersion > 1 || this._gl.getExtension('EXT_shader_texture_lod')) ? true : false;
 
+        this._caps.multiview = this._gl.getExtension('WEBGL_multiview');
         // Draw buffers
         if (this._webGLVersion > 1) {
             this._caps.drawBuffersExtension = true;
@@ -5599,6 +5602,52 @@ 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
+     */
+    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
@@ -6458,7 +6507,12 @@ export class Engine {
 
             this._activateCurrentTexture();
 
-            this._gl.bindTexture(target, texture ? texture._webGLTexture : null);
+            if (texture && texture.isMultiview) {
+                this._gl.bindTexture(target, texture ? texture._colorTextureArray : null);
+            }else {
+                this._gl.bindTexture(target, texture ? texture._webGLTexture : null);
+            }
+
             this._boundTexturesCache[this._activeChannel] = texture;
 
             if (texture) {
@@ -6649,8 +6703,11 @@ export class Engine {
         }
 
         this._activeChannel = channel;
-
-        if (internalTexture && internalTexture.is3D) {
+        if (internalTexture && internalTexture.isMultiview) {
+            if (needToBind) {
+                this._bindTextureDirectly(this._gl.TEXTURE_2D_ARRAY, internalTexture, isPartOfTextureArray);
+            }
+        }else if (internalTexture && internalTexture.is3D) {
             if (needToBind) {
                 this._bindTextureDirectly(this._gl.TEXTURE_3D, internalTexture, isPartOfTextureArray);
             }

+ 14 - 0
src/Materials/Background/backgroundMaterial.ts

@@ -119,6 +119,7 @@ class BackgroundMaterialDefines extends MaterialDefines implements IImageProcess
     public SAMPLER3DBGRMAP = false;
     public IMAGEPROCESSINGPOSTPROCESS = false;
     public EXPOSURE = false;
+    public MULTIVIEW = false;
 
     // Reflection.
     public REFLECTION = false;
@@ -675,6 +676,15 @@ export class BackgroundMaterial extends PushMaterial {
         MaterialHelper.PrepareDefinesForLights(scene, mesh, defines, false, this._maxSimultaneousLights);
         defines._needNormals = true;
 
+        // Multiview
+        if (scene.activeCamera) {
+            var previousMultiview = defines.MULTIVIEW;
+            defines.MULTIVIEW = (scene.activeCamera.outputRenderTarget !== null && scene.activeCamera.outputRenderTarget.getViewCount() > 1);
+            if (defines.MULTIVIEW != previousMultiview) {
+                defines.markAsUnprocessed();
+            }
+        }
+
         // Textures
         if (defines._areTexturesDirty) {
             defines._needUVs = false;
@@ -838,6 +848,10 @@ export class BackgroundMaterial extends PushMaterial {
                 fallbacks.addFallback(1, "POINTSIZE");
             }
 
+            if (defines.MULTIVIEW) {
+                fallbacks.addFallback(0, "MULTIVIEW");
+            }
+
             MaterialHelper.HandleFallbacksForShadows(defines, fallbacks, this._maxSimultaneousLights);
 
             if (defines.NUM_BONE_INFLUENCERS > 0) {

+ 14 - 0
src/Materials/PBR/pbrBaseMaterial.ts

@@ -169,6 +169,7 @@ class PBRMaterialDefines extends MaterialDefines
     public SAMPLER3DBGRMAP = false;
     public IMAGEPROCESSINGPOSTPROCESS = false;
     public EXPOSURE = false;
+    public MULTIVIEW = false;
 
     public USEPHYSICALLIGHTFALLOFF = false;
     public USEGLTFLIGHTFALLOFF = false;
@@ -1141,6 +1142,10 @@ export abstract class PBRBaseMaterial extends PushMaterial {
             fallbacks.addFallback(fallbackRank++, "MORPHTARGETS");
         }
 
+        if (defines.MULTIVIEW) {
+            fallbacks.addFallback(0, "MULTIVIEW");
+        }
+
         //Attributes
         var attribs = [VertexBuffer.PositionKind];
 
@@ -1236,6 +1241,15 @@ export abstract class PBRBaseMaterial extends PushMaterial {
         MaterialHelper.PrepareDefinesForLights(scene, mesh, defines, true, this._maxSimultaneousLights, this._disableLighting);
         defines._needNormals = true;
 
+        // Multiview
+        if (scene.activeCamera) {
+            var previousMultiview = defines.MULTIVIEW;
+            defines.MULTIVIEW = (scene.activeCamera.outputRenderTarget !== null && scene.activeCamera.outputRenderTarget.getViewCount() > 1);
+            if (defines.MULTIVIEW != previousMultiview) {
+                defines.markAsUnprocessed();
+            }
+        }
+
         // Textures
         defines.METALLICWORKFLOW = this.isMetallicWorkflow();
         if (defines._areTexturesDirty) {

+ 10 - 0
src/Materials/Textures/internalTexture.ts

@@ -88,6 +88,10 @@ export class InternalTexture implements IInternalTextureTracker {
      */
     public is3D: boolean;
     /**
+     * Defines if the texture contains multiview data
+     */
+    public isMultiview: boolean;
+    /**
      * Gets the URL used to load this texture
      */
     public url: string;
@@ -217,6 +221,12 @@ export class InternalTexture implements IInternalTextureTracker {
     /** @hidden */
     public _lodGenerationOffset: number = 0;
 
+    // Multiview
+    /** @hidden */
+    public _colorTextureArray: Nullable<WebGLTexture>;
+    /** @hidden */
+    public _depthStencilTextureArray:  Nullable<WebGLTexture>;
+
     // The following three fields helps sharing generated fixed LODs for texture filtering
     // In environment not supporting the textureLOD extension like EDGE. They are for internal use only.
     // They are at the level of the gl texture to benefit from the cache.

+ 56 - 3
src/Materials/Textures/renderTargetTexture.ts

@@ -727,6 +727,18 @@ export class RenderTargetTexture extends Texture {
         return Math.min(Tools.FloorPOT(renderDimension), curved);
     }
 
+    public _bindFrameBuffer(faceIndex: number = 0) {
+        var scene = this.getScene();
+        if (!scene) {
+            return;
+        }
+
+        var engine = scene.getEngine();
+        if (this._texture) {
+            engine.bindFramebuffer(this._texture, this.isCube ? faceIndex : undefined, undefined, undefined, this.ignoreCameraViewport, this.depthStencilTexture ? this.depthStencilTexture : undefined);
+        }
+    }
+
     protected unbindFrameBuffer(engine: Engine, faceIndex: number): void {
         if (!this._texture) {
             return;
@@ -754,9 +766,7 @@ export class RenderTargetTexture extends Texture {
             this._postProcessManager._prepareFrame(this._texture, this._postProcesses);
         }
         else if (!useCameraPostProcess || !scene.postProcessManager._prepareFrame(this._texture)) {
-            if (this._texture) {
-                engine.bindFramebuffer(this._texture, this.isCube ? faceIndex : undefined, undefined, undefined, this.ignoreCameraViewport, this.depthStencilTexture ? this.depthStencilTexture : undefined);
-            }
+            this._bindFrameBuffer(faceIndex);
         }
 
         this.onBeforeRenderObservable.notifyObservers(faceIndex);
@@ -975,8 +985,51 @@ export class RenderTargetTexture extends Texture {
             this._renderingManager.freeRenderingGroups();
         }
     }
+
+    /**
+     * Gets the number of views the corresponding to the texture (eg. a MultiviewRenderTarget will have > 1)
+     */
+    public getViewCount() {
+        return 1;
+    }
 }
 
 Texture._CreateRenderTargetTexture = (name: string, renderTargetSize: number, scene: Scene, generateMipMaps: boolean) => {
     return new RenderTargetTexture(name, renderTargetSize, scene, generateMipMaps);
 };
+
+/**
+ * Renders to multiple views with a single draw call
+ * @see https://www.khronos.org/registry/webgl/extensions/WEBGL_multiview/
+ */
+export class MultiviewRenderTarget extends RenderTargetTexture {
+    /**
+     * Creates a multiview render target
+     * @param scene scene used with the render target
+     * @param size the size of the render target (used for each view)
+     */
+    constructor(public scene: Scene, size: number | { width: number, height: number } | { ratio: number } = 512) {
+        super("multiview rtt", size, scene, false, true, InternalTexture.DATASOURCE_UNKNOWN, false, undefined, false, false, true, undefined, true);
+        var internalTexture = scene.getEngine().createMultiviewRenderTargetTexture(this.getRenderWidth(), this.getRenderHeight());
+        internalTexture.isMultiview = true;
+        this._texture = internalTexture;
+    }
+
+    /**
+     * @hidden
+     * @param faceIndex the face index, if its a cube texture
+     */
+    public _bindFrameBuffer(faceIndex: number = 0) {
+        if (!this._texture) {
+            return;
+        }
+        this.scene.getEngine().bindMultiviewFramebuffer(this._texture);
+    }
+
+    /**
+     * Gets the number of views the corresponding to the texture (eg. a MultiviewRenderTarget will have > 1)
+     */
+    public getViewCount() {
+        return 2;
+    }
+}

+ 7 - 1
src/Materials/effect.ts

@@ -631,7 +631,7 @@ export class Effect {
         // #extension GL_EXT_shader_texture_lod : enable
         // #extension GL_EXT_frag_depth : enable
         // #extension GL_EXT_draw_buffers : require
-        var regex = /#extension.+(GL_OES_standard_derivatives|GL_EXT_shader_texture_lod|GL_EXT_frag_depth|GL_EXT_draw_buffers).+(enable|require)/g;
+        var regex = /#extension.+(GL_OVR_multiview|GL_OES_standard_derivatives|GL_EXT_shader_texture_lod|GL_EXT_frag_depth|GL_EXT_draw_buffers).+(enable|require)/g;
         var result = preparedSourceCode.replace(regex, "");
 
         // Migrate to GLSL v300
@@ -650,6 +650,12 @@ export class Effect {
             result = result.replace(/void\s+?main\s*\(/g, (hasDrawBuffersExtension ? "" : "out vec4 glFragColor;\n") + "void main(");
         }
 
+        // Add multiview setup to top of file when defined
+        var hasMultiviewExtension = this.defines.indexOf("#define MULTIVIEW\n") !== -1;
+        if (hasMultiviewExtension && !isFragment) {
+            result = "#extension GL_OVR_multiview : require\nlayout (num_views = 2) in;\n" + result;
+        }
+
         callback(result);
     }
 

+ 7 - 1
src/Materials/materialHelper.ts

@@ -13,6 +13,7 @@ import { Light } from "../Lights/light";
 import { UniformBuffer } from "./uniformBuffer";
 import { Effect, EffectFallbacks, EffectCreationOptions } from "./effect";
 import { BaseTexture } from "../Materials/Textures/baseTexture";
+import { WebVRFreeCamera } from '../Cameras/VR/webVRCamera';
 
 /**
  * "Static Class" containing the most commonly used helper while dealing with material for
@@ -34,7 +35,12 @@ export class MaterialHelper {
             effect.setVector3("vEyePosition", scene._forcedViewPosition);
             return;
         }
-        effect.setVector3("vEyePosition", scene._mirroredCameraPosition ? scene._mirroredCameraPosition : scene.activeCamera!.globalPosition);
+        var globalPosition = scene.activeCamera!.globalPosition;
+        if (!globalPosition) {
+            // Use WebVRFreecamera's device position as global position is not it's actual position in babylon space
+            globalPosition = (scene.activeCamera! as WebVRFreeCamera).devicePosition;
+        }
+        effect.setVector3("vEyePosition", scene._mirroredCameraPosition ? scene._mirroredCameraPosition : globalPosition);
     }
 
     /**

+ 12 - 0
src/Materials/standardMaterial.ts

@@ -124,6 +124,7 @@ export class StandardMaterialDefines extends MaterialDefines implements IImagePr
     public SAMPLER3DGREENDEPTH = false;
     public SAMPLER3DBGRMAP = false;
     public IMAGEPROCESSINGPOSTPROCESS = false;
+    public MULTIVIEW = false;
     /**
      * If the reflection texture on this material is in linear color space
      * @hidden
@@ -792,6 +793,13 @@ export class StandardMaterial extends PushMaterial {
 
         // Lights
         defines._needNormals = MaterialHelper.PrepareDefinesForLights(scene, mesh, defines, true, this._maxSimultaneousLights, this._disableLighting);
+        if (scene.activeCamera) {
+            var previousMultiview = defines.MULTIVIEW;
+            defines.MULTIVIEW = (scene.activeCamera.outputRenderTarget !== null && scene.activeCamera.outputRenderTarget.getViewCount() > 1);
+            if (defines.MULTIVIEW != previousMultiview) {
+                defines.markAsUnprocessed();
+            }
+        }
 
         // Textures
         if (defines._areTexturesDirty) {
@@ -1080,6 +1088,10 @@ export class StandardMaterial extends PushMaterial {
                 fallbacks.addFallback(4, "FRESNEL");
             }
 
+            if (defines.MULTIVIEW) {
+                fallbacks.addFallback(0, "MULTIVIEW");
+            }
+
             //Attributes
             var attribs = [VertexBuffer.PositionKind];
 

+ 1 - 18
src/Meshes/mesh.ts

@@ -1296,7 +1296,7 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
     }
 
     /** @hidden */
-    public _draw(subMesh: SubMesh, fillMode: number, instancesCount?: number, alternate = false): Mesh {
+    public _draw(subMesh: SubMesh, fillMode: number, instancesCount?: number): Mesh {
         if (!this._geometry || !this._geometry.getVertexBuffers() || (!this._unIndexed && !this._geometry.getIndexBuffer())) {
             return this;
         }
@@ -1318,23 +1318,6 @@ export class Mesh extends AbstractMesh implements IGetSetVerticesData {
             engine.drawElementsType(fillMode, subMesh.indexStart, subMesh.indexCount, instancesCount);
         }
 
-        if (scene._isAlternateRenderingEnabled && !alternate) {
-            let effect = subMesh.effect || this._effectiveMaterial.getEffect();
-            if (!effect || !scene.activeCamera) {
-                return this;
-            }
-            scene._switchToAlternateCameraConfiguration(true);
-            this._effectiveMaterial.bindView(effect);
-            this._effectiveMaterial.bindViewProjection(effect);
-
-            engine.setViewport(scene.activeCamera._alternateCamera.viewport);
-            this._draw(subMesh, fillMode, instancesCount, true);
-            engine.setViewport(scene.activeCamera.viewport);
-
-            scene._switchToAlternateCameraConfiguration(false);
-            this._effectiveMaterial.bindView(effect);
-            this._effectiveMaterial.bindViewProjection(effect);
-        }
         return this;
     }
 

+ 27 - 0
src/PostProcesses/vrDistortionCorrectionPostProcess.ts

@@ -53,3 +53,30 @@ export class VRDistortionCorrectionPostProcess extends PostProcess {
         });
     }
 }
+
+/**
+ * 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);
+        });
+    }
+}

+ 3 - 0
src/Shaders/ShadersInclude/backgroundUboDeclaration.fx

@@ -26,5 +26,8 @@ uniform Material
 
 uniform Scene {
 	mat4 viewProjection;
+#ifdef MULTIVIEW
+	mat4 viewProjectionR;
+#endif 
 	mat4 view;
 };

+ 4 - 1
src/Shaders/ShadersInclude/defaultUboDeclaration.fx

@@ -39,6 +39,9 @@ uniform Material
 };
 
 uniform Scene {
-	mat4 viewProjection;
+    mat4 viewProjection;
+#ifdef MULTIVIEW
+	mat4 viewProjectionR;
+#endif 
 	mat4 view;
 };

+ 3 - 0
src/Shaders/ShadersInclude/pbrUboDeclaration.fx

@@ -61,5 +61,8 @@ uniform Material
 
 uniform Scene {
     mat4 viewProjection;
+#ifdef MULTIVIEW
+	mat4 viewProjectionR;
+#endif 
     mat4 view;
 };

+ 8 - 0
src/Shaders/background.vertex.fx

@@ -63,7 +63,15 @@ void main(void) {
 #include<instancesVertex>
 #include<bonesVertex>
 
+#ifdef MULTIVIEW
+	if (gl_ViewID_OVR == 0u) {
+		gl_Position = viewProjection * finalWorld * vec4(position, 1.0);
+	} else {
+		gl_Position = viewProjectionR * finalWorld * vec4(position, 1.0);
+	}
+#else
 	gl_Position = viewProjection * finalWorld * vec4(position, 1.0);
+#endif
 
 	vec4 worldPos = finalWorld * vec4(position, 1.0);
 	vPositionW = vec3(worldPos);

+ 9 - 0
src/Shaders/default.vertex.fx

@@ -123,7 +123,16 @@ void main(void) {
 #include<instancesVertex>
 #include<bonesVertex>
 
+#ifdef MULTIVIEW
+	if (gl_ViewID_OVR == 0u) {
+		gl_Position = viewProjection * finalWorld * vec4(positionUpdated, 1.0);
+	} else {
+		gl_Position = viewProjectionR * finalWorld * vec4(positionUpdated, 1.0);
+	}
+#else
 	gl_Position = viewProjection * finalWorld * vec4(positionUpdated, 1.0);
+#endif
+	
 
 	vec4 worldPos = finalWorld * vec4(positionUpdated, 1.0);
 	vPositionW = vec3(worldPos);

+ 13 - 4
src/Shaders/pbr.vertex.fx

@@ -148,10 +148,19 @@ void main(void) {
 #include<instancesVertex>
 #include<bonesVertex>
 
-    gl_Position = viewProjection * finalWorld * vec4(positionUpdated, 1.0);
-    #if DEBUGMODE > 0
-        vClipSpacePosition = gl_Position;
-    #endif
+#ifdef MULTIVIEW
+	if (gl_ViewID_OVR == 0u) {
+		gl_Position = viewProjection * finalWorld * vec4(positionUpdated, 1.0);
+	} else {
+		gl_Position = viewProjectionR * finalWorld * vec4(positionUpdated, 1.0);
+	}
+#else
+	gl_Position = viewProjection * finalWorld * vec4(positionUpdated, 1.0);
+#endif
+
+#if DEBUGMODE > 0
+    vClipSpacePosition = gl_Position;
+#endif
 
     vec4 worldPos = finalWorld * vec4(positionUpdated, 1.0);
     vPositionW = vec3(worldPos);

+ 12 - 0
src/Shaders/vrMultiviewToSingleview.fragment.fx

@@ -0,0 +1,12 @@
+#ifdef GL_ES
+	precision mediump sampler2DArray;
+#endif
+
+varying vec2 vUV;
+uniform sampler2DArray multiviewSampler;
+uniform int imageIndex;
+
+void main(void)
+{
+	gl_FragColor = texture(multiviewSampler, vec3(vUV, imageIndex));
+}

+ 93 - 100
src/scene.ts

@@ -1078,8 +1078,6 @@ export class Scene extends AbstractScene implements IAnimatable {
 
     private _viewUpdateFlag = -1;
     private _projectionUpdateFlag = -1;
-    private _alternateViewUpdateFlag = -1;
-    private _alternateProjectionUpdateFlag = -1;
 
     /** @hidden */
     public _toBeDisposed = new Array<Nullable<IDisposable>>(256);
@@ -1108,25 +1106,16 @@ export class Scene extends AbstractScene implements IAnimatable {
     public _activeAnimatables = new Array<Animatable>();
 
     private _transformMatrix = Matrix.Zero();
+    private _transformMatrixR = Matrix.Zero();
     private _sceneUbo: UniformBuffer;
-    private _alternateSceneUbo: UniformBuffer;
+    private _multiviewSceneUbo: UniformBuffer;
 
     private _viewMatrix: Matrix;
     private _projectionMatrix: Matrix;
-    private _alternateViewMatrix: Matrix;
-    private _alternateProjectionMatrix: Matrix;
-    private _alternateTransformMatrix: Matrix;
-    private _useAlternateCameraConfiguration = false;
-    private _alternateRendering = false;
     private _wheelEventName = "";
     /** @hidden */
     public _forcedViewPosition: Nullable<Vector3>;
 
-    /** @hidden */
-    public get _isAlternateRenderingEnabled(): boolean {
-        return this._alternateRendering;
-    }
-
     private _frustumPlanes: Plane[];
     /**
      * Gets the list of frustum planes (built from the active camera)
@@ -1602,10 +1591,11 @@ export class Scene extends AbstractScene implements IAnimatable {
         this._sceneUbo.addUniform("view", 16);
     }
 
-    private _createAlternateUbo(): void {
-        this._alternateSceneUbo = new UniformBuffer(this._engine, undefined, true);
-        this._alternateSceneUbo.addUniform("viewProjection", 16);
-        this._alternateSceneUbo.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
@@ -2526,17 +2516,13 @@ export class Scene extends AbstractScene implements IAnimatable {
     }
 
     // Matrix
-    /** @hidden */
-    public _switchToAlternateCameraConfiguration(active: boolean): void {
-        this._useAlternateCameraConfiguration = active;
-    }
 
     /**
      * Gets the current view matrix
      * @returns a Matrix
      */
     public getViewMatrix(): Matrix {
-        return this._useAlternateCameraConfiguration ? this._alternateViewMatrix : this._viewMatrix;
+        return this._viewMatrix;
     }
 
     /**
@@ -2544,7 +2530,7 @@ export class Scene extends AbstractScene implements IAnimatable {
      * @returns a Matrix
      */
     public getProjectionMatrix(): Matrix {
-        return this._useAlternateCameraConfiguration ? this._alternateProjectionMatrix : this._projectionMatrix;
+        return this._projectionMatrix;
     }
 
     /**
@@ -2552,25 +2538,30 @@ export class Scene extends AbstractScene implements IAnimatable {
      * @returns a Matrix made of View * Projection
      */
     public getTransformMatrix(): Matrix {
-        return this._useAlternateCameraConfiguration ? this._alternateTransformMatrix : this._transformMatrix;
+        return this._transformMatrix;
     }
 
     /**
      * Sets the current transform matrix
-     * @param view defines the View matrix to use
-     * @param projection defines the Projection matrix to use
+     * @param viewL defines the View matrix to use
+     * @param projectionL defines the Projection matrix to use
+     * @param viewR defines the right View matrix to use (if provided)
+     * @param projectionR defines the right Projection matrix to use (if provided)
      */
-    public setTransformMatrix(view: Matrix, projection: Matrix): void {
-        if (this._viewUpdateFlag === view.updateFlag && this._projectionUpdateFlag === projection.updateFlag) {
+    public setTransformMatrix(viewL: Matrix, projectionL: Matrix, viewR?: Matrix, projectionR?: Matrix): void {
+        if (this._viewUpdateFlag === viewL.updateFlag && this._projectionUpdateFlag === projectionL.updateFlag) {
             return;
         }
 
-        this._viewUpdateFlag = view.updateFlag;
-        this._projectionUpdateFlag = projection.updateFlag;
-        this._viewMatrix = view;
-        this._projectionMatrix = projection;
+        this._viewUpdateFlag = viewL.updateFlag;
+        this._projectionUpdateFlag = projectionL.updateFlag;
+        this._viewMatrix = viewL;
+        this._projectionMatrix = projectionL;
 
         this._viewMatrix.multiplyToRef(this._projectionMatrix, this._transformMatrix);
+        if (viewR && projectionR) {
+            viewR.multiplyToRef(projectionR, this._transformMatrixR);
+        }
 
         // Update frustum
         if (!this._frustumPlanes) {
@@ -2579,53 +2570,29 @@ export class Scene extends AbstractScene implements IAnimatable {
             Frustum.GetPlanesToRef(this._transformMatrix, this._frustumPlanes);
         }
 
-        if (this.activeCamera && this.activeCamera._alternateCamera) {
-            let otherCamera = this.activeCamera._alternateCamera;
-            otherCamera.getViewMatrix().multiplyToRef(otherCamera.getProjectionMatrix(), Tmp.Matrix[0]);
+        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._sceneUbo.useUbo) {
+        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();
+        }else if (this._sceneUbo.useUbo) {
             this._sceneUbo.updateMatrix("viewProjection", this._transformMatrix);
             this._sceneUbo.updateMatrix("view", this._viewMatrix);
             this._sceneUbo.update();
         }
     }
 
-    /** @hidden */
-    public _setAlternateTransformMatrix(view: Matrix, projection: Matrix): void {
-        if (this._alternateViewUpdateFlag === view.updateFlag && this._alternateProjectionUpdateFlag === projection.updateFlag) {
-            return;
-        }
-
-        this._alternateViewUpdateFlag = view.updateFlag;
-        this._alternateProjectionUpdateFlag = projection.updateFlag;
-        this._alternateViewMatrix = view;
-        this._alternateProjectionMatrix = projection;
-
-        if (!this._alternateTransformMatrix) {
-            this._alternateTransformMatrix = Matrix.Zero();
-        }
-
-        this._alternateViewMatrix.multiplyToRef(this._alternateProjectionMatrix, this._alternateTransformMatrix);
-
-        if (!this._alternateSceneUbo) {
-            this._createAlternateUbo();
-        }
-
-        if (this._alternateSceneUbo.useUbo) {
-            this._alternateSceneUbo.updateMatrix("viewProjection", this._alternateTransformMatrix);
-            this._alternateSceneUbo.updateMatrix("view", this._alternateViewMatrix);
-            this._alternateSceneUbo.update();
-        }
-    }
-
     /**
      * Gets the uniform buffer used to store scene data
      * @returns a UniformBuffer
      */
     public getSceneUniformBuffer(): UniformBuffer {
-        return this._useAlternateCameraConfiguration ? this._alternateSceneUbo : this._sceneUbo;
+        return this._multiviewSceneUbo ? this._multiviewSceneUbo : this._sceneUbo;
     }
 
     /**
@@ -4042,12 +4009,24 @@ export class Scene extends AbstractScene implements IAnimatable {
         this.setTransformMatrix(this.activeCamera.getViewMatrix(), this.activeCamera.getProjectionMatrix(force));
     }
 
-    /**
-     * Defines an alternate camera (used mostly in VR-like scenario where two cameras can render the same scene from a slightly different point of view)
-     * @param alternateCamera defines the camera to use
-     */
-    public updateAlternateTransformMatrix(alternateCamera: Camera): void {
-        this._setAlternateTransformMatrix(alternateCamera.getViewMatrix(), alternateCamera.getProjectionMatrix());
+    private _bindFrameBuffer() {
+        if (this.activeCamera && this.activeCamera._multiviewTexture) {
+            this.activeCamera._multiviewTexture._bindFrameBuffer();
+        } else if (this.activeCamera && this.activeCamera.outputRenderTarget) {
+            var useMultiview = this.getEngine().getCaps().multiview && this.activeCamera.outputRenderTarget && this.activeCamera.outputRenderTarget.getViewCount() > 1;
+            if (useMultiview) {
+                this.activeCamera.outputRenderTarget._bindFrameBuffer();
+            }else {
+                var internalTexture = this.activeCamera.outputRenderTarget.getInternalTexture();
+                if (internalTexture) {
+                    this.getEngine().bindFramebuffer(internalTexture);
+                } else {
+                    Logger.Error("Camera contains invalid customDefaultRenderTarget");
+                }
+            }
+        } else {
+            this.getEngine().restoreDefaultFramebuffer(); // Restore back buffer if needed
+        }
     }
     /** @hidden */
     public _allowPostProcessClearColor = true;
@@ -4071,11 +4050,12 @@ export class Scene extends AbstractScene implements IAnimatable {
         // Camera
         this.resetCachedMaterial();
         this._renderId++;
-        this.updateTransformMatrix();
 
-        if (camera._alternateCamera) {
-            this.updateAlternateTransformMatrix(camera._alternateCamera);
-            this._alternateRendering = true;
+        var useMultiview = this.getEngine().getCaps().multiview && camera.outputRenderTarget && camera.outputRenderTarget.getViewCount() > 1;
+        if (useMultiview) {
+            this.setTransformMatrix(camera._rigCameras[0].getViewMatrix(), camera._rigCameras[0].getProjectionMatrix(), camera._rigCameras[1].getViewMatrix(), camera._rigCameras[1].getProjectionMatrix());
+        }else {
+            this.updateTransformMatrix();
         }
 
         this.onBeforeCameraRenderObservable.notifyObservers(this.activeCamera);
@@ -4130,22 +4110,14 @@ export class Scene extends AbstractScene implements IAnimatable {
 
             this._intermediateRendering = false;
 
-            if (this.activeCamera.outputRenderTarget) {
-                var internalTexture = this.activeCamera.outputRenderTarget.getInternalTexture();
-                if (internalTexture) {
-                    engine.bindFramebuffer(internalTexture);
-                } else {
-                    Logger.Error("Camera contains invalid customDefaultRenderTarget");
-                }
-            } else {
-                engine.restoreDefaultFramebuffer(); // Restore back buffer if needed
-            }
+            // Restore framebuffer after rendering to targets
+            this._bindFrameBuffer();
         }
 
         this.onAfterRenderTargetsRenderObservable.notifyObservers(this);
 
         // Prepare Frame
-        if (this.postProcessManager) {
+        if (this.postProcessManager && !camera._multiviewTexture) {
             this.postProcessManager._prepareFrame();
         }
 
@@ -4165,27 +4137,51 @@ export class Scene extends AbstractScene implements IAnimatable {
         }
 
         // Finalize frame
-        if (this.postProcessManager) {
+        if (this.postProcessManager  && !camera._multiviewTexture) {
             this.postProcessManager._finalizeFrame(camera.isIntermediate);
         }
 
         // Reset some special arrays
         this._renderTargets.reset();
 
-        this._alternateRendering = false;
-
         this.onAfterCameraRenderObservable.notifyObservers(this.activeCamera);
     }
 
     private _processSubCameras(camera: Camera): void {
-        if (camera.cameraRigMode === Camera.RIG_MODE_NONE) {
+        if (camera.cameraRigMode === Camera.RIG_MODE_NONE || (camera.outputRenderTarget && camera.outputRenderTarget.getViewCount() > 1 && this.getEngine().getCaps().multiview)) {
             this._renderForCamera(camera);
             return;
         }
 
-        // rig cameras
-        for (var index = 0; index < camera._rigCameras.length; index++) {
-            this._renderForCamera(camera._rigCameras[index], camera);
+        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(this.getEngine().getRenderWidth(true) / 2, 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);
+                }
+            }
+        } else {
+            // rig cameras
+            for (var index = 0; index < camera._rigCameras.length; index++) {
+                this._renderForCamera(camera._rigCameras[index], camera);
+            }
         }
 
         // Use _activeCamera instead of activeCamera to avoid onActiveCameraChanged
@@ -4396,12 +4392,9 @@ export class Scene extends AbstractScene implements IAnimatable {
         }
 
         // Restore back buffer
-        if (this.customRenderTargets.length > 0) {
-            engine.restoreDefaultFramebuffer();
-        }
-
-        this.onAfterRenderTargetsRenderObservable.notifyObservers(this);
         this.activeCamera = currentActiveCamera;
+        this._bindFrameBuffer();
+        this.onAfterRenderTargetsRenderObservable.notifyObservers(this);
 
         for (let step of this._beforeClearStage) {
             step.action();
@@ -4664,8 +4657,8 @@ export class Scene extends AbstractScene implements IAnimatable {
         // Release UBO
         this._sceneUbo.dispose();
 
-        if (this._alternateSceneUbo) {
-            this._alternateSceneUbo.dispose();
+        if (this._multiviewSceneUbo) {
+            this._multiviewSceneUbo.dispose();
         }
 
         // Post-processes