Skip to content

Native media Equirect layers creation #31033

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -461,6 +461,7 @@
"webgpu_volume_perlin",
"webgpu_water",
"webgpu_xr_cubes",
"webgpu_xr_media_layer",
"webgpu_xr_native_layers"
],
"webaudio": [
Expand Down
Binary file added examples/screenshots/webgpu_xr_media_layer.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
167 changes: 167 additions & 0 deletions examples/webgpu_xr_media_layer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js vr - 360 stereo video</title>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please include the new example in files.json. Otherwise it won't appear on the homepage.

<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<link type="text/css" rel="stylesheet" href="main.css">
</head>
<body>
<div id="container"></div>

<div id="info">
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> vr - 360 stereo video native media layers<br />
stereoscopic panoramic render by <a href="http://pedrofe.com/rendering-for-oculus-rift-with-arnold/" target="_blank" rel="noopener">pedrofe</a>. scene from <a href="http://www.meryproject.com/" target="_blank" rel="noopener">mery project</a>.
</div>

<video id="video" muted loop crossOrigin="anonymous" playsinline style="display:none">
<source src="textures/MaryOculus.mp4">
</video>

<script type="importmap">
{
"imports": {
"three": "../build/three.webgpu.js",
"three/webgpu": "../build/three.webgpu.js",
"three/addons/": "./jsm/"
}
}
</script>

<script type="module">



import * as THREE from 'three';
import { XRButton } from 'three/addons/webxr/XRButton.js';

let camera, scene, renderer;

init();

function init() {


const container = document.getElementById( 'container' );
container.addEventListener( 'click', function () {

video.play();

} );

camera = new THREE.PerspectiveCamera( 70, window.innerWidth / window.innerHeight, 1, 2000 );
camera.layers.enable( 1 ); // render left view when no stereo available

// video

const video = document.getElementById( 'video' );
video.play();

const texture = new THREE.VideoTexture( video );
texture.colorSpace = THREE.SRGBColorSpace;

scene = new THREE.Scene();
scene.background = new THREE.Color( 0x101010 );

// left

const geometry1 = new THREE.SphereGeometry( 500, 60, 40 );
// invert the geometry on the x-axis so that all of the faces point inward
geometry1.scale( - 1, 1, 1 );

const uvs1 = geometry1.attributes.uv.array;


for ( let i = 0; i < uvs1.length; i += 2 ) {

uvs1[ i ] *= 0.5;

}

// right

const geometry2 = new THREE.SphereGeometry( 500, 60, 40 );
geometry2.scale( - 1, 1, 1 );

const uvs2 = geometry2.attributes.uv.array;


for ( let i = 0; i < uvs2.length; i += 2 ) {

uvs2[ i ] *= 0.5;
uvs2[ i ] += 0.5;

}

renderer = new THREE.WebGPURenderer( { antialias: true, forceWebGL: true, multiview: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( animate );
renderer.xr.enabled = true;
renderer.xr.setReferenceSpaceType( 'local' );

const mediaLayer = renderer.xr.createMediaLayer(texture, 'stereo-left-right', { x: 0, y: .28, z: 0, w: .96 });
//modify geometries for left and right
mediaLayer.children[0].geometry = geometry1;
mediaLayer.children[1].geometry = geometry2;

scene.add(mediaLayer);

renderer.xr.addEventListener("sessionstart", async () => {
const session = renderer.xr.getSession();

if (renderer.xr._supportsLayers) {
mediaLayer.children.forEach(mesh => mesh.layers.disableAll());
}

if (session.supportedFrameRates) {
console.log("supported framerates ", session.supportedFrameRates);
session.addEventListener('frameratechange', (event) => {
console.log("XRFrame rate is now " + session.frameRate)
});

session.updateTargetFrameRate(session.supportedFrameRates[session.supportedFrameRates.length - 1]).then((() => {

})).catch(console.warn)
}




});

renderer.xr.addEventListener("sessionend", async () => {
mediaLayer.children.forEach(mesh => mesh.layers.enableAll());
});

renderer.setClearColor(0x000, 1);
texture.anisotropy = renderer.backend.capabilities.getMaxAnisotropy();

container.appendChild( renderer.domElement );

document.body.appendChild( XRButton.createButton( renderer ) );

//

window.addEventListener( 'resize', onWindowResize );

}

function onWindowResize() {

camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();

renderer.setSize( window.innerWidth, window.innerHeight );

}

function animate() {

renderer.render( scene, camera );

}

</script>
</body>
</html>
134 changes: 131 additions & 3 deletions src/renderers/common/XRManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ 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 { Group } from '../../objects/Group.js';

const _cameraLPos = /*@__PURE__*/ new Vector3();
const _cameraRPos = /*@__PURE__*/ new Vector3();
Expand Down Expand Up @@ -375,6 +376,22 @@ class XRManager extends EventDispatcher {
*/
this._useMultiview = false;

/**
* Stores params and video elements for equirect layers.
*
* @private
* @type {Array<Object>}
*/
this._mediaLayers = [];

/**
* Stores the created equrect layers for updating render state.
*
* @private
* @type {Array<XREquirectLayer>}
*/
this._createdMediaLayers = [];

}

/**
Expand Down Expand Up @@ -596,6 +613,88 @@ class XRManager extends EventDispatcher {

}

/**
* 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='mono'] The layout to use either mono/stereo-left-right/stereo-top-bottom.
* @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.
* @returns {Group} Returns a group of a mono or stereo mesh
*/
createMediaLayer( texture, layout = 'mono', quaternion = {}, is180 = false, params = {} ) {

const createMaterial = ( texture ) => new MeshBasicMaterial( { map: texture } );
const createMesh = ( texture, eyeIndex = 1 ) => {

const geometry = new PlaneGeometry( 1, 1 ),
mesh = new Mesh( geometry, createMaterial( texture ) );

mesh.layers.set( eyeIndex );

return mesh;

};

const group = new Group();

switch ( layout ) {

case 'mono':
group.add( createMesh( texture ) );
break;
default:
[ 1, 2 ].forEach( eyeIndex => {

const mesh = createMesh( texture, eyeIndex );
mesh.rotation.y = - Math.PI / 2;
group.add( mesh );

} );
break;

}

if ( this._useLayers ) {

const angleFactor = is180 ? 1 : 2;

const layer = {
type: 'equirect',
texture: texture,
group: group,
quaternion: quaternion,
params: {
layout: layout,
centralHorizontalAngle: Math.PI * angleFactor,
...params
}

};

this._mediaLayers.push( layer );

if ( this._session !== null ) {

layer.xrlayer = this._createXRLayer( layer );

this._createdMediaLayers.push( layer.xrlayer );

const xrlayers = this._session.renderState.layers;
xrlayers.unshift( layer.xrlayer );

this._session.updateRenderState( { layers: xrlayers } );

}

}

return group;

}

createQuadLayer( width, height, translation, quaternion, pixelwidth, pixelheight, rendercall, attributes = [] ) {

const geometry = new PlaneGeometry( width, height );
Expand Down Expand Up @@ -657,7 +756,7 @@ class XRManager extends EventDispatcher {

const xrlayers = this._session.renderState.layers;
xrlayers.unshift( layer.xrlayer );
this._session.updateRenderState( { layers: xrlayers } );
this._session.updateRenderState( { layers: [ ...this._createdMediaLayers, ...xrlayers ] } );

} else {

Expand Down Expand Up @@ -731,7 +830,7 @@ class XRManager extends EventDispatcher {

const xrlayers = this._session.renderState.layers;
xrlayers.unshift( layer.xrlayer );
this._session.updateRenderState( { layers: xrlayers } );
this._session.updateRenderState( { layers: [ ...this._createdMediaLayers, ...xrlayers ] } );

} else {

Expand Down Expand Up @@ -918,9 +1017,23 @@ class XRManager extends EventDispatcher {

}

//Creates the equirect media layers on session creation
if ( this._mediaLayers.length ) {

this._createdMediaLayers = this._mediaLayers.map( layer => {

layer.xrlayer = this._createXRLayer( layer );
return layer.xrlayer;

} );

}

}

session.updateRenderState( { layers: layersArray } );
//console.log("created layers ", [ ...this._createdMediaLayers, ...layersArray ]);

session.updateRenderState( { layers: [ ...this._createdMediaLayers, ...layersArray ] } );

} else {

Expand Down Expand Up @@ -1408,6 +1521,21 @@ function createXRLayer( layer ) {
viewPixelHeight: layer.pixelheight
} );

} else if ( layer.type === 'equirect' ) {

const mediaBinding = new XRMediaBinding( this._session );

return mediaBinding.createEquirectLayer(
layer.texture.image,
{
space: this._referenceSpace,
transform: new XRRigidTransform(
{},
layer.quaternion
),
...layer.params
} );

} else {

return this._glBinding.createCylinderLayer( {
Expand Down
Loading