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 7 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.
185 changes: 185 additions & 0 deletions examples/webgpu_xr_media_layer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
<!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;

}



const material1 = new THREE.MeshBasicMaterial( { map: texture } );

const mesh1 = new THREE.Mesh( geometry1, material1 );
mesh1.rotation.y = - Math.PI / 2;
mesh1.layers.set( 1 ); // display in left eye only
scene.add( mesh1 );

// 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;

}

const material2 = new THREE.MeshBasicMaterial( { map: texture } );

const mesh2 = new THREE.Mesh( geometry2, material2 );
mesh2.rotation.y = - Math.PI / 2;
mesh2.layers.set( 2 ); // display in right eye only
scene.add( mesh2 );


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' );

//quest specific
//renderer.xr.setFramebufferScaleFactor( 1.5 );
//renderer.xr.setFoveation(1);

renderer.xr.createMediaLayer(video, 'stereo-left-right', { x: 0, y: .28, z: 0, w: .96 });

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

if (renderer.xr._supportsLayers) {
mesh1.layers.disableAll();
mesh2.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 () => {
mesh1.layers.enableAll();
mesh2.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>
78 changes: 75 additions & 3 deletions src/renderers/common/XRManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,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 +612,40 @@ class XRManager extends EventDispatcher {

}

/**
* Sets up params for an equirect native video layer.
*
* @param {HTMLVideoElement} video The video element.
* @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} [transform={}] A transform 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.
*/
createMediaLayer( video, layout = 'mono', transform = {}, is180 = false, params = {} ) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think this code implements emulation for 2D or browser that don't support xr layers. It will also not dynamically add them while in VR.
Please look at the flow of the quad and cylinder layer.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It does as normal with the meshes added to the scene, just when going to XR the mesh layers need to be disabled, if it doesn't there is a crash in the Oculus browser. I can't get my head around those methods and there is no texture render required.

There is specific logic for the stereo and mono video that shouldn't be in the manager perhaps. There is multiple meshes for the stereo version.

So even if it was created in the manager and added to the scene the meshes need to be disabled.

The native layer only gets setup if layers are supported. Works with the Rift in XR that doesn't support layers it gets rendered in the projection layer.

https://github.com/danrossi/three.js/blob/equirect-layers/examples/webgpu_xr_media_layer.html#L129

https://electroteque.org/dev/threejs/examples/webgpu_xr_media_layer.html

I could automatically disable/enable the mesh layers perhaps. That part I find is little clunky. There is nothing for it to do in the renderLayers method unless it's non layers and not in XR.

If you have another idea it would be good.

Copy link
Contributor

Choose a reason for hiding this comment

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

The code that creates the video layer in xr should also the emulation in 2D mode. Right now you create a video texture in your sample code and disable it on session start. That logic needs to go to xrmanager.
This code also doesn't work if you add a media layer while in VR.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

So create the single or multiple mesh in the method. Return and add the geometries before adding to the scene. the issue is while in XR. Is a special render target needed like the others where its then only rendering when not in XR ? so a different render method is needed to the renderLayers method. To prevent that layer disable hack needed. I'm assuming that is the requirement if it actually works like that.


if ( this._useLayers ) {

const angleFactor = is180 ? 1 : 2;

this._mediaLayers.push( {

video: video,
params: {
layout: layout,
centralHorizontalAngle: Math.PI * angleFactor,
transform: new XRRigidTransform(
{},
transform
),
...params
}

} );

}

}

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

const geometry = new PlaneGeometry( width, height );
Expand Down Expand Up @@ -657,7 +707,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 +781,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 +968,31 @@ class XRManager extends EventDispatcher {

}

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

const mediaBinding = new XRMediaBinding( session );

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

return mediaBinding.createEquirectLayer(
layer.video,

{
space: this._referenceSpace,
...layer.params
}

);

} );

}


}

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

} else {

Expand Down
Loading