如何 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 视图,那么两者都是资源浪费。
下面是我自己回答的,因为当我 运行 对此没有可靠的答案时,我想为未来的开发者节省时间。 (特别是如果他们不是都精通 WebGl2
和 WebXR
)
请注意,我没有为 'reasons' 使用此项目的任何现有框架。如果你是,它应该不会有太大变化,你只需要在你的库渲染管道中的适当位置执行这些步骤。
事实证明,答案非常简单,几乎没有达到我的 fps。
- 将 canvas 附加到 DOM 并将其设置为您想要的大小。 (我的是流动的,所以 CSS 宽度是父容器的 100%,高度是 auto)
- 初始化 glContext 时,请务必指定抗锯齿为 false。如果您的观众和 HMD 视图是不同的分辨率,这一点很重要。
{xrCompatible: true, webgl2: true, antialias: false}
- 创建将用于存储渲染的 HMD 视图的 frameBuffer。
spectateBuffer
- 像往常一样在
xrSession.requestAnimationFrame(OnXRFrame);
回调中绘制 immersive-xr
层
- 就在退出您的
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 员工减轻一些痛苦。
如何使用相同的 WebGL canvas
从浏览器中的 VIVE 或 Oculus 等 HMD 镜像或克隆 WebXR 'immersive-xr'
视图?
关于将像素复制到 texture2D,然后将其作为渲染纹理应用,或者使用调整后的 viewTransform
完全重新绘制整个场景的讨论很多。如果您正在渲染不同的视图,例如远程摄像机或第三人称旁观者视图,这些效果很好,但是如果只想在桌面上镜像当前的 HMD 视图,那么两者都是资源浪费。
下面是我自己回答的,因为当我 运行 对此没有可靠的答案时,我想为未来的开发者节省时间。 (特别是如果他们不是都精通 WebGl2
和 WebXR
)
请注意,我没有为 'reasons' 使用此项目的任何现有框架。如果你是,它应该不会有太大变化,你只需要在你的库渲染管道中的适当位置执行这些步骤。
事实证明,答案非常简单,几乎没有达到我的 fps。
- 将 canvas 附加到 DOM 并将其设置为您想要的大小。 (我的是流动的,所以 CSS 宽度是父容器的 100%,高度是 auto)
- 初始化 glContext 时,请务必指定抗锯齿为 false。如果您的观众和 HMD 视图是不同的分辨率,这一点很重要。
{xrCompatible: true, webgl2: true, antialias: false}
- 创建将用于存储渲染的 HMD 视图的 frameBuffer。
spectateBuffer
- 像往常一样在
xrSession.requestAnimationFrame(OnXRFrame);
回调中绘制immersive-xr
层 - 就在退出您的
OnXRFrame
方法之前,执行一个调用以绘制旁观者视图。我个人使用了一个 boolshowCanvas
来允许我根据需要打开和关闭旁观者镜子:
//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 员工减轻一些痛苦。