Created
March 1, 2020 23:03
-
-
Save Strilanc/4130fbc7013e019dc363b777c83e9423 to your computer and use it in GitHub Desktop.
The bare minimum needed to render to an immersive headset.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html lang="en"> | |
<head> | |
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> | |
<title>WebXR Testing</title> | |
</head> | |
<body> | |
<input id="button_enter" type="button" value="Loading..." disabled/> | |
<script> | |
const GL = WebGLRenderingContext; | |
const GL_ATTRIBUTE_POSITION = 1; | |
// Initialize WebGLv2. | |
let webglCanvas = document.createElement('canvas'); | |
let gl = webglCanvas.getContext('webgl2', {xrCompatible: true, alpha: false}); | |
gl.enable(gl.DEPTH_TEST); | |
// Create shaders that apply the motion tracked perspective transformations. | |
let program = gl.createProgram(); | |
let vertShader = gl.createShader(gl.VERTEX_SHADER); | |
gl.attachShader(program, vertShader); | |
gl.shaderSource(vertShader, ` | |
attribute vec3 POSITION; | |
uniform mat4 PROJECTION_MATRIX; | |
uniform mat4 VIEW_MATRIX; | |
void main() { | |
gl_Position = PROJECTION_MATRIX * VIEW_MATRIX * vec4(POSITION, 1.0); | |
} | |
`); | |
gl.compileShader(vertShader); | |
let fragShader = gl.createShader(gl.FRAGMENT_SHADER); | |
gl.attachShader(program, fragShader); | |
gl.shaderSource(fragShader, ` | |
precision highp float; | |
void main() { | |
gl_FragColor = vec4(1.0, 1.0, 1.0, 0.0); | |
} | |
`); | |
gl.compileShader(fragShader); | |
gl.bindAttribLocation(program, GL_ATTRIBUTE_POSITION, 'POSITION'); | |
gl.linkProgram(program); | |
gl.useProgram(program); | |
let projectionMatrixUniform = gl.getUniformLocation(program, 'PROJECTION_MATRIX'); | |
let viewMatrixUniform = gl.getUniformLocation(program, 'VIEW_MATRIX'); | |
// Prepare some triangles to draw (they form a four sided pyramid). | |
let indices = new Uint16Array([ | |
0, 1, 3, | |
1, 2, 3, | |
0, 1, 4, | |
1, 2, 4, | |
2, 3, 4, | |
3, 0, 4, | |
]); | |
let coords = new Float32Array([ | |
-1, 0, -1, | |
1, 0, -1, | |
1, 0, 1, | |
-1, 0, 1, | |
0, 1, 0, | |
]); | |
let elementBuf = gl.createBuffer(); | |
gl.bindBuffer(GL.ARRAY_BUFFER, elementBuf); | |
gl.bufferData(GL.ARRAY_BUFFER, coords, GL.STATIC_DRAW); | |
let elementArrayBuf = gl.createBuffer(); | |
gl.bindBuffer(GL.ELEMENT_ARRAY_BUFFER, elementArrayBuf); | |
gl.bufferData(GL.ELEMENT_ARRAY_BUFFER, indices, GL.STATIC_DRAW); | |
gl.enableVertexAttribArray(GL_ATTRIBUTE_POSITION); | |
gl.vertexAttribPointer(GL_ATTRIBUTE_POSITION, 3, GL.FLOAT, false, 12, 0); | |
let _drawElementsCount = indices.length; | |
// Use a button to enter/exit the VR session. | |
let xrButton = document.getElementById('button_enter'); | |
let xrReferenceSpace = null; | |
let xrSession = null; | |
xrButton.onclick = async () => { | |
if (xrSession !== null) { | |
// End VR Session. | |
xrSession.end(); | |
xrSession = null; | |
xrButton.disabled = false; | |
xrButton.value = 'Enter VR'; | |
return; | |
} | |
// Create VR session. | |
xrButton.value = 'Starting...'; | |
xrButton.disabled = true; | |
let session = await navigator.xr.requestSession('immersive-vr'); | |
session.updateRenderState({baseLayer: new XRWebGLLayer(session, gl)}); | |
xrReferenceSpace = await session.requestReferenceSpace('local'); | |
xrButton.value = 'Exit VR'; | |
xrButton.disabled = false; | |
xrSession = session; | |
// Start drawing. | |
session.requestAnimationFrame(xrDrawFrame); | |
}; | |
// Redraw the pyramid on each frame for each eye. | |
function xrDrawFrame(time, frame) { | |
frame.session.requestAnimationFrame(xrDrawFrame); | |
let pose = frame.getViewerPose(xrReferenceSpace); | |
if (!pose) { | |
return; // Motion tracking data not available right now. | |
} | |
let glLayer = frame.session.renderState.baseLayer; | |
gl.bindFramebuffer(gl.FRAMEBUFFER, glLayer.framebuffer); | |
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); | |
for (let view of pose.views) { | |
let viewport = glLayer.getViewport(view); | |
gl.viewport(viewport.x, viewport.y, viewport.width, viewport.height); | |
gl.uniformMatrix4fv(projectionMatrixUniform, false, view.projectionMatrix); | |
gl.uniformMatrix4fv(viewMatrixUniform, false, view.transform.inverse.matrix); | |
gl.drawElements(gl.TRIANGLES, _drawElementsCount, gl.UNSIGNED_SHORT, 0); | |
} | |
} | |
// Only enable entering VR if immersive-vr is supported. | |
if (navigator.xr) { | |
navigator.xr.isSessionSupported('immersive-vr').then(supported => { | |
if (supported) { | |
xrButton.disabled = false; | |
xrButton.value = 'Enter VR'; | |
} else { | |
xrButton.value = 'immersive-vr session not supported'; | |
} | |
}); | |
} else { | |
xrButton.value = 'navigator.xr is not present'; | |
} | |
</script> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Works in Chrome with an Index after enabling "OpenVR hardware support" and disabling "XR device sandboxing" in chrome://flags .