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