Skip to content

Instantly share code, notes, and snippets.

@Strilanc
Created March 1, 2020 23:03
Show Gist options
  • Save Strilanc/4130fbc7013e019dc363b777c83e9423 to your computer and use it in GitHub Desktop.
Save Strilanc/4130fbc7013e019dc363b777c83e9423 to your computer and use it in GitHub Desktop.
The bare minimum needed to render to an immersive headset.
<!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>
@Strilanc
Copy link
Author

Strilanc commented Mar 1, 2020

Works in Chrome with an Index after enabling "OpenVR hardware support" and disabling "XR device sandboxing" in chrome://flags .

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment