diff --git a/examples/files.json b/examples/files.json index 85dc5dc3d49bd8..446287dc79cea9 100644 --- a/examples/files.json +++ b/examples/files.json @@ -501,7 +501,8 @@ "webgpu_water", "webgpu_xr_rollercoaster", "webgpu_xr_cubes", - "webgpu_xr_native_layers" + "webgpu_xr_native_layers", + "webgpu_xr_media_layer" ], "webaudio": [ "webaudio_orientation", diff --git a/examples/screenshots/webgpu_xr_media_layer.jpg b/examples/screenshots/webgpu_xr_media_layer.jpg new file mode 100644 index 00000000000000..001fe203e0b94e Binary files /dev/null and b/examples/screenshots/webgpu_xr_media_layer.jpg differ diff --git a/examples/webgpu_xr_media_layer.html b/examples/webgpu_xr_media_layer.html new file mode 100644 index 00000000000000..c9acc25f1d0393 --- /dev/null +++ b/examples/webgpu_xr_media_layer.html @@ -0,0 +1,345 @@ + + + + three.js vr - 360 stereo video + + + + + +
+
+ +
+
+ +
+ three.js vr - 360 stereo/mono video native media layers
+ stereoscopic panoramic render by pedrofe. scene from mery project.
+ Choose Layout: +
+ + + + + + + + diff --git a/src/renderers/common/XRLayerUtils.js b/src/renderers/common/XRLayerUtils.js new file mode 100644 index 00000000000000..5db1a0ac0e620b --- /dev/null +++ b/src/renderers/common/XRLayerUtils.js @@ -0,0 +1,262 @@ + +import { SphereGeometry } from '../../geometries/SphereGeometry.js'; + +import { PlaneGeometry } from '../../geometries/PlaneGeometry.js'; +import { MeshBasicMaterial } from '../../materials/MeshBasicMaterial.js'; +import { Mesh } from '../../objects/Mesh.js'; + +import { AddEquation, CustomBlending, FrontSide, ZeroFactor } from '../../constants.js'; + +/** + * Utility methods for native layer creation. + * Common methods for creating sphere and planes for stereo abd mono video textures. + */ + +//The UV mapping geometry transforms for stereo layouts. +const UVMapFactors = { + 'stereo-top-bottom': [ + { yMult: 0.5, yPhase: 0, xMult: 1.0, xPhase: 0 }, + { yMult: 0.5, yPhase: 0.5, xMult: 1.0, xPhase: 0 } + ], + 'stereo-left-right': [ + { yMult: 1.0, yPhase: 0, xMult: 0.5, xPhase: 0 }, + { yMult: 1.0, yPhase: 0, xMult: 0.5, xPhase: 0.5 } + ] +}; + +/** + * set the uv mapping for each eye in a stereo video. + * uv factors for stereo-left-right and stereo-top-bottom layouts is applied. + * @param {number} [eyeIndex = 1] The mesh layer eye index for stereo rendering. + * @param {Object} [uvFactors] The uv factories for a set stereo layout. + * @param {Geometry} geometry The geometry to transform. + */ +const setUVMapping = ( eyeIndex, uvFactors, geometry ) => { + + const eyeUvfactor = uvFactors[ eyeIndex - 1 ], + uvs = geometry.attributes.uv.array; + + for ( let i = 0; i < uvs.length; i += 2 ) { + + //x + uvs[ i ] *= eyeUvfactor.xMult; + uvs[ i ] += eyeUvfactor.xPhase; + //y + uvs[ i + 1 ] *= eyeUvfactor.yMult; + uvs[ i + 1 ] += eyeUvfactor.yPhase; + + } + +}; + +/** + * Positions the plane to the set translation and quaternion. + * @param {Planegoemetry} [plane] The plane to position. + * @param {Vector3} [translation] The position vector. + * @param {Quaternion} quaternion The quaternion to set. + */ +const positionQuad = ( plane, translation, quaternion ) => { + + plane.position.copy( translation ); + plane.quaternion.copy( quaternion ); + +}; + +/** + * Create a common material for mesh creation. + * @param {number} [side = Frontside] The side to set for the material. + * @return {MeshBasicMaterial} Return the mesh material. + */ +const createMaterial = ( side = FrontSide ) => new MeshBasicMaterial( { color: 0xffffff, side: side } ); + +/** + * Creates a mesh material providing a texture. + * @param {VideoTexture} texture The video texture + * @return {MeshBasicMaterial} The mesh material. + */ +const createMeshMaterial = ( texture ) => { + + const material = createMaterial(); + material.map = texture; + return material; + +}; + +/** + * Creates a full and half sphere geometry. + * @param {number} [radius = 500] The SphereGeometry radius. + * @param {number} [widthSegments = 60] The SphereGeometry width segments. + * @param {number} [heightSegments = 40] The SphereGeometry height segments. + * @param {boolean} [is180 = false] If it's a 180 video. + * @return {SphereGeometry} The full or half sphere geometry. + */ +const createSphereGeometry = ( radius, widthSegments, heightSegments, is180 = false ) => { + + let phiStart = 0, phiLength = Math.PI * 2; + + if ( is180 ) { + + phiStart = Math.PI / 2, phiLength = Math.PI; + + } + + const geometry = new SphereGeometry( radius, widthSegments, heightSegments, phiStart, phiLength ); + + geometry.scale( - 1, 1, 1 ); + return geometry; + +}; + +/** + * Creates a mesh. + * If uvFactors is given it will create stereo transformed meshes. + * @param {VideoTexture} texture The video texture + * @param {number} [eyeIndex = 0] The mesh layer eye index for stereo rendering. + * @param {Geometry} [geometry] Tje geometry. + * @param {MeshBasicMaterial} [material] The material. If not set creates a material with texture. + * @param {Object} [uvFactors] The uv factors for a set stereo layout. + * @return {Mesh} The created mono/stereo transformed mesh. + */ +const createMesh = ( texture, geometry, eyeIndex = 0, material, uvFactors = null ) => { + + //if has stereo layout set the uv mapping. + if ( uvFactors ) { + + setUVMapping( eyeIndex, uvFactors, geometry ); + + } + + const mesh = new Mesh( geometry, material || createMeshMaterial( texture ) ); + mesh.layers.set( eyeIndex ); + + return mesh; + +}; + +/** + * Creates a sphere mesh for mono/stereo 360/180 textures. + * @param {VideoTexture} texture The video texture + * @param {number} [eyeIndex = 0] The mesh layer eye index for stereo rendering. + * @param {number} [radius = 500] The SphereGeometry radius. + * @param {number} [widthSegments = 60] The SphereGeometry width segments. + * @param {number} [heightSegments = 40] The SphereGeometry height segments. + * @param {Object} [uvFactors] The uv factors for a set stereo layout. + * @param {boolean} [is180 = false] If it's a 180 video. + * @return {Mesh} The sphere mesh. + */ +const createSphereMesh = ( texture, eyeIndex = 0, radius, widthSegments, heightSegments, uvFactors = null, is180 = false, material = null ) => { + + const geometry = createSphereGeometry( radius, widthSegments, heightSegments, is180 ), + mesh = createMesh( texture, geometry, eyeIndex, material, uvFactors ); + + //only rotate for stereo layouts. + mesh.rotation.y = uvFactors ? - Math.PI / 2 : 0; + + return mesh; + +}; + +/** + * Creates a plane mesh for mono/stereo textures. + * @param {VideoTexture} texture The video texture + * @param {number} [eyeIndex = 0] The mesh layer eye index for stereo rendering. + * @param {number} [quadWidth = 0] The quad layer width in meter units. + * @param {number} [quadHeight = 0] The quad layer height in meter units. + * @param {Vector3} translation - The position/translation of the layer plane in world units. Native layer Z is 2 units more required than non layer planes. + * @param {Object} [quaternion={}] A transform quaternion param for the layer. + * @param {Object} [uvFactors] The uv factors for a set stereo layout. + * @return {Mesh} The plane mesh. + */ +const createPlaneMesh = ( texture, eyeIndex = 0, quadWidth = 1, quadHeight = 1, translation = {}, quaternion = {}, uvFactors = null, material = null ) => { + + const geometry = new PlaneGeometry( quadWidth, quadHeight ), + mesh = createMesh( texture, geometry, eyeIndex, material, uvFactors ); + + positionQuad( mesh, translation, quaternion ); + + return mesh; + +}; + + +/** + * Creates a blend layer material required for native layer rendering. + * @param {number} [side = Frontside] The side to set for the material. + * @return {MeshBasicMaterial} The blend layer material. + */ +const createBlendLayerMaterial = ( side = FrontSide ) => { + + const material = createMaterial( side ); + material.blending = CustomBlending; + material.blendEquation = AddEquation; + material.blendSrc = ZeroFactor; + material.blendDst = ZeroFactor; + return material; + +}; + +/** + * Toggle between the 2D non layer meshes and the blend layer meshes for native layer rendering. + * This is needed to disable video texture rendering from the 2D meshes. + * @param {Object} [layer] The layer data object. + * @param {boolean} [toggle] Toggle on/off. + */ +const toggleBlendLayerRender = ( layer, toggle ) => { + + layer.group.children.forEach( mesh => { + + if ( ! mesh.textureMaterial && toggle ) { + + //store the original material with the texture map + mesh.textureMaterial = mesh.material; + //swap the material with a cmmmon blend layer material + mesh.material = layer.blendMaterial; + + } else { + + //disable the blend layer material. + mesh.material = mesh.textureMaterial; + mesh.textureMaterial = null; + + } + + } ); + +}; + +// +/** + * update and replace the layer item at the specified index or prepend + * @param {number} [index] The layer index to insert or update. + * @param {Array} [layers] The medialayers. + * @param {Object} [layer] The layer object to insert or update. + */ +const updateOrPrepend = ( index, layers, layer ) => { + + if ( index > - 1 ) { + + layers.splice( index, 1, layer ); + + } else { + + layers.unshift( layer ); + + } + +}; + +export { + + UVMapFactors, + createMesh, + createMaterial, + createMeshMaterial, + createSphereGeometry, + createSphereMesh, + createPlaneMesh, + createBlendLayerMaterial, + toggleBlendLayerRender, + updateOrPrepend, + positionQuad + +}; diff --git a/src/renderers/common/XRManager.js b/src/renderers/common/XRManager.js index 13c50cfb9fe919..f66ab00a6abf77 100644 --- a/src/renderers/common/XRManager.js +++ b/src/renderers/common/XRManager.js @@ -7,15 +7,14 @@ import { Vector2 } from '../../math/Vector2.js'; import { Vector3 } from '../../math/Vector3.js'; import { Vector4 } from '../../math/Vector4.js'; import { WebXRController } from '../webxr/WebXRController.js'; -import { AddEquation, BackSide, CustomBlending, DepthFormat, DepthStencilFormat, FrontSide, RGBAFormat, UnsignedByteType, UnsignedInt248Type, UnsignedIntType, ZeroFactor } from '../../constants.js'; +import { BackSide, DepthFormat, DepthStencilFormat, RGBAFormat, UnsignedByteType, UnsignedInt248Type, UnsignedIntType } from '../../constants.js'; import { DepthTexture } from '../../textures/DepthTexture.js'; import { XRRenderTarget } from './XRRenderTarget.js'; import { CylinderGeometry } from '../../geometries/CylinderGeometry.js'; -import { PlaneGeometry } from '../../geometries/PlaneGeometry.js'; -import { MeshBasicMaterial } from '../../materials/MeshBasicMaterial.js'; -import { Mesh } from '../../objects/Mesh.js'; import { warn } from '../../utils.js'; import { renderOutput } from '../../nodes/display/RenderOutputNode.js'; +import { Group } from '../../objects/Group.js'; +import { createMesh, createSphereMesh, createPlaneMesh, createBlendLayerMaterial, toggleBlendLayerRender, UVMapFactors, updateOrPrepend, positionQuad } from './XRLayerUtils.js'; const _cameraLPos = /*@__PURE__*/ new Vector3(); const _cameraRPos = /*@__PURE__*/ new Vector3(); @@ -392,6 +391,22 @@ class XRManager extends EventDispatcher { */ this._useMultiview = false; + /** + * Stores params and video elements for equirect layers. + * + * @private + * @type {Array} + */ + this._mediaLayers = []; + + /** + * Stores the created equrect layers for updating render state. + * + * @private + * @type {Array} + */ + this._createdMediaLayers = []; + } /** @@ -634,6 +649,195 @@ class XRManager extends EventDispatcher { } + /** + * Sets up params for a Quad native video layer. + * Creates meshes for 2D layers in mono or stereo + * + * @param {VideoTexture} texture The video texture + * @param {('default'|'mono'|'stereo'|'stereo-left-right'|'stereo-top-bottom')} [layout='stereo'] The layout to use either mono/steree/stereo-left-right/stereo-top-bottom. The default layout is stereo which creates a stereo-top-bottom layout. + * @param {number} [width = 1] The width in meter units. + * @param {number} [height = 1] The quad layer height in meter units. + * @param {Vector3} translation - The position/translation of the layer plane in world units. Native layer Z is 2 units more required than non layer planes. + * @param {Object} [quaternion = {}] A transform quaternion param for the layer. + * @param {Object} [params = {}] Extra params for the layer to add but not needed. + * @param {number} [updateAtIndex = -1] If set update and replace the current layer in an XR session at the specified index. + * @returns {Group} Returns a group of a mono or stereo mesh + */ + createQuadMediaLayer( texture, layout = 'mono', width = 1, height = 1, translation, quaternion, params = {}, updateAtIndex = - 1 ) { + + return this.createMediaLayer( texture, layout, quaternion, false, params, 0, 0, 0, updateAtIndex, width, height, translation ); + + } + + /** + * Sets up params for an Equirect native video layer. + * Creates meshes for 2D layers in mono or stereo + * + * @param {VideoTexture} texture The video texture + * @param {('default'|'mono'|'stereo'|'stereo-left-right'|'stereo-top-bottom')} [layout='stereo'] The layout to use either mono/steree/stereo-left-right/stereo-top-bottom. The default layout is stereo which creates a stereo-top-bottom layout. + * @param {Object} [quaternion = {}] A transform quaternion param for the layer. + * @param {boolean} [is180 = false] If it's a 180 video. + * @param {Object} [params = {}] Extra params for the layer to add but not needed. + * @param {number} [radius = 500] The SphereGeometry radius. + * @param {number} [widthSegments = 60] The SphereGeometry width segments. + * @param {number} [heightSegments = 40] The SphereGeometry height segments. + * @param {number} [updateAtIndex = -1] If set update and replace the current layer in an XR session at the specified index. + * @param {number} [quadWidth = 0] The width in meter units. Tf the width is set it will enable quad media layer creation. + * @param {number} [quadHeight = 0] The quad layer height in meter units. + * @param {Vector3} translation - The position/translation of the layer plane in world units. Native layer Z is 2 units more required than non layer planes. + * @returns {Group} Returns a group of a mono or stereo mesh + */ + createMediaLayer( texture, layout = 'stereo', quaternion = {}, is180 = false, params = {}, radius = 500, widthSegments = 60, heightSegments = 40, updateAtIndex = - 1, quadWidth = 0, quadHeight = 0, translation = {} ) { + + const group = new Group(), + isQuad = !! quadWidth; + + /** + * Creates a sphere mesh for equirect layers. + * @param {VideoTexture} texture The video texture + * @param {number} [eyeIndex = 1] The mesh layer eye index for stereo rendering. + * @param {number} [radius = 500] The SphereGeometry radius. + * @param {number} [widthSegments = 60] The SphereGeometry width segments. + * @param {number} [heightSegments = 40] The SphereGeometry height segments. + * @param {Object} [uvFactors = null] If the UV factors is set this turns on stereo mesh transforms. + * @param {boolean} [is180 = false] If it's a 180 video. + * @returns { Mesh} The mono/stereo sphere mesh. + */ + const createMediaSphere = ( texture, eyeIndex = 1, radius, widthSegments, heightSegments, uvFactors = null, is180 = false, material = null ) => { + + const mesh = createSphereMesh( texture, eyeIndex, radius, widthSegments, heightSegments, uvFactors, is180, material ); + + return mesh; + + }; + + /** + * Creates a quad layer plane mesh. + * @param {VideoTexture} texture The video texture + * @param {number} [eyeIndex = 1] The mesh layer eye index for stereo rendering. + * @param {number} [quadWidth = 0] The quad layer width in meter units. + * @param {number} [quadHeight = 0] The quad layer height in meter units. + * @param {Vector3} translation - The position/translation of the layer plane in world units. Native layer Z is 2 units more required than non layer planes. + * @param {Object} [quaternion={}] A transform quaternion param for the layer. + * @param {Object} [uvFactors = null] If the UV factors is set this turns on stereo mesh transforms. + * @returns { Mesh} The mono/stereo plane quad mesh. + */ + const createMediaPlane = ( texture, eyeIndex = 0, quadWidth = 1, quadHeight = 1, translation = {}, quaternion = {}, uvFactors = null, material = null ) => { + + const mesh = createPlaneMesh( texture, eyeIndex, quadWidth, quadHeight, translation, quaternion, uvFactors, material ); + + return mesh; + + }; + + /** + * Creates a sphere mesh if quad layer widths isn't set. Or create a quad layer plane. + * @param {number} [eyeIndex = 1] The mesh layer eye index for stereo rendering. + * @param {Object} [uvFactors = null] If the UV factors is set this turns on stereo mesh transforms. + * @returns { Mesh} The mono/stereo plane quad or sphere mesh. + */ + const createMediaMesh = ( eyeIndex = 0, uvFactors = null, material = null ) => { + + if ( ! isQuad ) { + + return createMediaSphere( texture, eyeIndex, radius, widthSegments, heightSegments, uvFactors, is180, material ); + + } else { + + return createMediaPlane( texture, eyeIndex, quadWidth, quadHeight, translation, quaternion, uvFactors, material ); + + } + + }; + + let mesh; + + switch ( layout ) { + + case 'mono': + + mesh = createMediaMesh(); + group.add( mesh ); + + break; + case 'stereo': + default: + + if ( layout === 'stereo' ) layout = 'stereo-top-bottom'; + + //get the uv factors for the layout + const uvFactors = UVMapFactors[ layout ]; + + mesh = createMediaMesh( 1, uvFactors ); + group.add( mesh ); + + //for efficiency use the first material for the second eye material + mesh = createMediaMesh( 2, uvFactors, mesh.material ); + group.add( mesh ); + + break; + + } + + if ( this._supportsLayers ) { + + let centralHorizontalAngle = Math.PI * ( is180 ? 1 : 2 ), + mediaLayerMethod = 'createEquirectLayer'; + + //change the media layer creation method to quad if set. + if ( isQuad ) { + + //set the quad layer plane width and height. + //diovide the units of the quad native layer by half to match world units. + params.width = quadWidth / 2; + params.height = quadHeight / 2; + + centralHorizontalAngle = 0; + mediaLayerMethod = 'createQuadLayer'; + + } + + const layer = { + type: 'media', + texture: texture, + group: group, + blendMaterial: createBlendLayerMaterial(), + translation: translation, + quaternion: quaternion, + mediaLayerMethod: mediaLayerMethod, + params: { + layout: layout, + centralHorizontalAngle: centralHorizontalAngle, + ...params + } + + }; + + updateOrPrepend( updateAtIndex, this._mediaLayers, layer ); + + if ( this._session !== null ) { + + layer.xrlayer = this._createXRLayer( layer ); + + updateOrPrepend( updateAtIndex, this._createdMediaLayers, layer.xrlayer ); + + //enable current blend layer group + toggleBlendLayerRender( layer, true ); + + const xrlayers = [ ...this._session.renderState.layers ]; + + updateOrPrepend( updateAtIndex, xrlayers, layer.xrlayer ); + + this._session.updateRenderState( { layers: xrlayers } ); + + } + + } + + return group; + + } + /** * This method can be used in XR applications to create a quadratic layer that presents a separate * rendered scene. @@ -651,7 +855,6 @@ class XRManager extends EventDispatcher { */ createQuadLayer( width, height, translation, quaternion, pixelwidth, pixelheight, rendercall, attributes = {} ) { - const geometry = new PlaneGeometry( width, height ); const renderTarget = new XRRenderTarget( pixelwidth, pixelheight, @@ -677,13 +880,9 @@ class XRManager extends EventDispatcher { renderTarget._autoAllocateDepthBuffer = true; - const material = new MeshBasicMaterial( { color: 0xffffff, side: FrontSide } ); - material.map = renderTarget.texture; - material.map.offset.y = 1; - material.map.repeat.y = - 1; - const plane = new Mesh( geometry, material ); - plane.position.copy( translation ); - plane.quaternion.copy( quaternion ); + const plane = createPlaneMesh( renderTarget.texture, 0, width, height, translation, quaternion ); + plane.material.map.offset.y = 1; + plane.material.map.repeat.y = - 1; const layer = { type: 'quad', @@ -694,7 +893,7 @@ class XRManager extends EventDispatcher { pixelwidth: pixelwidth, pixelheight: pixelheight, plane: plane, - material: material, + material: plane.material, rendercall: rendercall, renderTarget: renderTarget }; @@ -702,17 +901,13 @@ class XRManager extends EventDispatcher { if ( this._session !== null ) { - layer.plane.material = new MeshBasicMaterial( { color: 0xffffff, side: FrontSide } ); - layer.plane.material.blending = CustomBlending; - layer.plane.material.blendEquation = AddEquation; - layer.plane.material.blendSrc = ZeroFactor; - layer.plane.material.blendDst = ZeroFactor; + layer.plane.material = createBlendLayerMaterial(); layer.xrlayer = this._createXRLayer( layer ); const xrlayers = this._session.renderState.layers; xrlayers.unshift( layer.xrlayer ); - this._session.updateRenderState( { layers: xrlayers } ); + this._session.updateRenderState( { layers: [ ...this._createdMediaLayers, ...xrlayers ] } ); } else { @@ -768,13 +963,11 @@ class XRManager extends EventDispatcher { renderTarget._autoAllocateDepthBuffer = true; - const material = new MeshBasicMaterial( { color: 0xffffff, side: BackSide } ); - material.map = renderTarget.texture; - material.map.offset.y = 1; - material.map.repeat.y = - 1; - const plane = new Mesh( geometry, material ); - plane.position.copy( translation ); - plane.quaternion.copy( quaternion ); + const plane = createMesh( renderTarget.texture, geometry ); + plane.material.side = BackSide; + plane.material.map.offset.y = 1; + plane.material.map.repeat.y = - 1; + positionQuad( plane, translation, quaternion ); const layer = { type: 'cylinder', @@ -786,7 +979,8 @@ class XRManager extends EventDispatcher { pixelwidth: pixelwidth, pixelheight: pixelheight, plane: plane, - material: material, + material: plane.material, + blendSide: BackSide, rendercall: rendercall, renderTarget: renderTarget }; @@ -794,17 +988,13 @@ class XRManager extends EventDispatcher { if ( this._session !== null ) { - layer.plane.material = new MeshBasicMaterial( { color: 0xffffff, side: BackSide } ); - layer.plane.material.blending = CustomBlending; - layer.plane.material.blendEquation = AddEquation; - layer.plane.material.blendSrc = ZeroFactor; - layer.plane.material.blendDst = ZeroFactor; + layer.plane.material = createBlendLayerMaterial( layer.blendSide ); layer.xrlayer = this._createXRLayer( layer ); const xrlayers = this._session.renderState.layers; xrlayers.unshift( layer.xrlayer ); - this._session.updateRenderState( { layers: xrlayers } ); + this._session.updateRenderState( { layers: [ ...this._createdMediaLayers, ...xrlayers ] } ); } else { @@ -1028,11 +1218,7 @@ class XRManager extends EventDispatcher { for ( const layer of this._layers ) { // change material so it "punches" out a hole to show the XR Layer. - layer.plane.material = new MeshBasicMaterial( { color: 0xffffff, side: layer.type === 'cylinder' ? BackSide : FrontSide } ); - layer.plane.material.blending = CustomBlending; - layer.plane.material.blendEquation = AddEquation; - layer.plane.material.blendSrc = ZeroFactor; - layer.plane.material.blendDst = ZeroFactor; + layer.plane.material = createBlendLayerMaterial( layer.blendSide ); layer.xrlayer = this._createXRLayer( layer ); @@ -1040,9 +1226,28 @@ class XRManager extends EventDispatcher { } + //Creates the equirect or quad media layers on session creation + if ( this._mediaLayers.length ) { + + this._createdMediaLayers = this._mediaLayers.map( layer => { + + layer.xrlayer = this._createXRLayer( layer ); + return layer.xrlayer; + + } ); + + //disable 2D media mesh layers to be replaced with native media layers + for ( const mediaLayer of this._mediaLayers ) { + + toggleBlendLayerRender( mediaLayer, true ); + + } + + } + } - session.updateRenderState( { layers: layersArray } ); + session.updateRenderState( { layers: [ ...this._createdMediaLayers, ...layersArray ] } ); } else { @@ -1432,6 +1637,13 @@ function onSessionEnd() { } + //reenable 2D media mesh layers on session end + for ( const mediaLayer of this._mediaLayers ) { + + toggleBlendLayerRender( mediaLayer, false ); + + } + } // @@ -1535,6 +1747,21 @@ function createXRLayer( layer ) { clearOnAccess: false } ); + } else if ( /media/.test( layer.type ) ) { + + const mediaBinding = new XRMediaBinding( this._session ); + + return mediaBinding[ layer.mediaLayerMethod ]( + layer.texture.image, + { + space: this._referenceSpace, + transform: new XRRigidTransform( + layer.translation, + layer.quaternion + ), + ...layer.params + } ); + } else { return this._glBinding.createCylinderLayer( {