-
-
Notifications
You must be signed in to change notification settings - Fork 35.7k
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
base: dev
Are you sure you want to change the base?
Changes from 7 commits
0a90352
9059ba2
90b50c2
8ff4d70
24e131f
c18d146
6f0fedf
92341fe
531ce09
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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> | ||
<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> |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 = []; | ||
|
||
} | ||
|
||
/** | ||
|
@@ -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 = {} ) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 If you have another idea it would be good. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||
|
||
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 ); | ||
|
@@ -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 { | ||
|
||
|
@@ -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 { | ||
|
||
|
@@ -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 { | ||
|
||
|
There was a problem hiding this comment.
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.