如何 mirror/clone WebXR 'immersive-xr' HMD 视图到浏览器

How to mirror/clone a WebXR 'immersive-xr' HMD view to the browser

如何使用相同的 WebGL canvas 从浏览器中的 VIVE 或 Oculus 等 HMD 镜像或克隆 WebXR 'immersive-xr' 视图?

关于将像素复制到 texture2D,然后将其作为渲染纹理应用,或者使用调整后的 viewTransform 完全重新绘制整个场景的讨论很多。如果您正在渲染不同的视图,例如远程摄像机或第三人称旁观者视图,这些效果很好,但是如果只想在桌面上镜像当前的 HMD 视图,那么两者都是资源浪费。

下面是我自己回答的,因为当我 运行 对此没有可靠的答案时,我想为未来的开发者节省时间。 (特别是如果他们不是都精通 WebGl2WebXR

请注意,我没有为 'reasons' 使用此项目的任何现有框架。如果你是,它应该不会有太大变化,你只需要在你的库渲染管道中的适当位置执行这些步骤。

事实证明,答案非常简单,几乎没有达到我的 fps。

  1. 将 canvas 附加到 DOM 并将其设置为您想要的大小。 (我的是流动的,所以 CSS 宽度是父容器的 100%,高度是 auto)
  2. 初始化 glContext 时,请务必指定抗锯齿为 false。如果您的观众和 HMD 视图是不同的分辨率,这一点很重要。{xrCompatible: true, webgl2: true, antialias: false}
  3. 创建将用于存储渲染的 HMD 视图的 frameBuffer。 spectateBuffer
  4. 像往常一样在 xrSession.requestAnimationFrame(OnXRFrame); 回调中绘制 immersive-xr
  5. 就在退出您的 OnXRFrame 方法之前,执行一个调用以绘制旁观者视图。我个人使用了一个 bool showCanvas 来允许我根据需要打开和关闭旁观者镜子:
//a quick reference I like to use for enums and types
const GL = WebGL2RenderingContext;

//Create a buffer for my spectate view so that I can just re-use it at will.
let spectateBuffer = _glContext.createFramebuffer();

//Called each frame, as per usual
function OnXRFrame(timestamp, xrFrame){
    //Bind my spectate framebuffer to the webGL2 readbuffer
    _glContext.bindFramebuffer(GL.READ_FRAMEBUFFER, spectateBuffer);

    //...Get my pose, update my scene objects
    //...Oh my, a bunch of stuff happens here
    //...finally gl.drawElements(GL.TRIANGLES...

    //render spectator canvas
    if(showCanvas){
        DrawSpectator();
    }

    //Request next animation callback
    xrFrame.session.requestAnimationFrame(OnXRFrame);
}

//A tad more verbose that needed to illustrate what's going on.
//You don't need to declare the src and dest x/y's as their own variables
function DrawSpectator(){
    //Set the DRAW_FRAMEBUFER to null, this tells the renderer to draw to the canvas.
    _glContext.bindFramebuffer(GL.DRAW_FRAMEBUFFER, null);

    //Store last HMD canvas view size (Mine was 0.89:1 aspect, 2296x2552)
    let bufferWidth = _glContext.canvas.width;
    let bufferHeight = _glContext.canvas.height;

    //Set canvas view size for the spectator view (Mine was 2:1 aspect, 1280x640)
    _glContext.canvas.width = _glContext.canvas.clientWidth;
    _glContext.canvas.height = _glContext.canvas.clientWidth / 2;

    //Define the bounds of the source buffer you want to use
    let srcX0 = 0;
    let srcY0 = bufferHeight * 0.25;    //I crop off the bottom 25% of the HMD's view
    let srcX1 = bufferWidth;
    let srcY1 = bufferHeight - (bufferHeight * 0.25);   //I crop off the top 25% of the HMD's view

    //Define the bounds of the output buffer
    let dstY0 = 0;
    let dstX0 = 0;
    let dstY1 = _glContext.canvas.height;
    let dstX1 = _glContext.canvas.width;

    //Blit the source buffer to the output buffer
    _glContext.blitFramebuffer(
        srcX0, srcY0, srcX1, srcY1,
        dstX0, dstY0, dstX1, dstY1,
        GL.COLOR_BUFFER_BIT, GL.NEAREST);
}

注意:我只将我的一个 HMD 眼睛视图显示为观察者视图,要同时显示这两个视图,您需要为每只眼睛存储一个观察者帧缓冲区并将它们一起 blit并排。

我希望这能为未来的 google 员工减轻一些痛苦。