如何解决在 Chrome Android 上使用 readRenderTargetPixels() 的巨大延迟,带有第二个深度摄像头 - 完整演示
这显然是对 WEBGL-Gurus 的呼吁:
我使用第二个深度相机渲染到 2x2 像素分辨率,但我只读取了一个像素。
我试过了:
-
Three.js 中的
-
renderer.readrendertargetPixels()
(我认为它与纯gl.readPixels
中的WEBGL
相同)。 -
getimageData()
渲染到Canvas
后。
两者似乎都非常慢:在我的情况下,它们加起来长达 25 毫秒,即使对于 2x2 像素的渲染目标也是如此。此处还提到了不合理的低效率,尤其是 gl.readpixels
:what is the correct way to use gl.readPixels?
所以,我正在寻找一种解决方案 - 任何解决方案,可以有效地读取该单像素,并将其放入 JS 数组或对象中。谢谢。
更新:
-我更新了标题和正文,使其更加切题。
-我创建了一个 JSfiddle 来演示 readrendertargetPixels()(相当于 gl.readpixels())延迟。 我试图让它尽可能简单,但不是......更简单:)
注意事项/使用方法:
-
此演示可在正常模式和 WebXR 模式下运行。 你不必进入 VR 就能看到巨大的延迟。 JSfiddle 有一个 BUG,它不会在移动设备上显示 VRButton。 为了在移动设备上进入 VR,您需要将代码复制到 index.html 并通过 WIFI 运行安全的本地服务器 (HTTPS) 或使用不会使 VRButton ...不可见的 JSfiddle-alternative -I测试了很多,我找不到一个有效的(!)
-
这个演示显示了过去 10 帧的渲染统计数据(最小和最大毫秒渲染时间),为了您的方便,屏幕上有普通和 WebXR 模式,所以很容易看到它也可以在移动设备上使用。
-
有一个主摄像头和一个深度摄像头。深度摄像头的分辨率为 2x2 像素,FOV 为 0.015 度。它指向一个旋转的立方体,它测量深度并在立方体的表面上绘制一条射线和一个点。我什至优化了代码以消除 JS 中代价高昂的解码数学。
-
我主要对移动设备感兴趣,它的延迟要高得多,为了方便起见,我提供了二维码。 因此,要在手机上进行测试,请扫描下面的#1 QR 码,等待一分钟稳定,然后扫描第二个QR 码进行比较。要查看/编辑 JSfiddle 中的代码,请从 url 中删除“show”并重新加载页面。
-
请在 Chrome 浏览器上测试演示。如果您在 PC 上运行演示,在判断延迟之前等待一分钟也很重要。我注意到 PC 上的 Firefox 的延迟远低于 PC 上的 Chrome 并且稳定得多,但是我感兴趣的功能在 FF 上不受支持,所以我只对 Chrome 感兴趣。在我的 PC 上,Chrome 以大约 5 毫秒的渲染时间开始(与没有该功能的 1-2 毫秒相比,这仍然是巨大的),然后过了一会儿它会翻倍和翻三倍。在移动设备上,它几乎总是很高,在 15 到 30 毫秒之间(强大的移动设备)。
readrendertargetPixels() 开启:
https://jsfiddle.net/dlllb/h3ywjeud/show
readrendertargetPixels() 关闭:
https://jsfiddle.net/dlllb/mbk4Lfy1/show
<!DOCTYPE html>
<html lang="en" >
<head>
<title>READPIXELS LATENCY</title>
<Meta charset="UTF-8">
<Meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,user-scalable=no,viewport-fit=cover" />
<Meta name="mobile-web-app-capable" content="yes">
<Meta name="HandheldFriendly" content="true" />
<style>
html{height: 100%}
body {text-align:center; background-color: #000000; height: 100%; margin: 0px; overflow: hidden}
#info { position: absolute; top: 0px; width: 800px; margin: 10px; text-align: left; z-index: 90; display:none}
</style>
</head>
<body>
<div id="container"></div>
<script type="module" defer>
import * as THREE from "https://threejs.org/build/three.module.js";
import { VRButton } from "https://threejs.org/examples/jsm/webxr/VRButton.js";
var win = {
x: window.innerWidth,y: window.innerHeight
}
// multiply deg with deg2rad to find radians (rad = deg * Pi/180)
const deg2rad = 0.017453292519943295769236907685;
var tmp;
var startms = 0;
var endms = 0;
var deltams = 0;
var fcounter = 0;
var fbuffer = [0,0];
var maxms = 0;
var minms = 1000;
let avframes = 10;
//_________________________SCENE___________________________________________
var scene = new THREE.Scene();
scene.background = new THREE.Color( "#346189" );
//___________________xrRig __xrCam________________________________________
var xrRig = new THREE.Object3D();
scene.add(xrRig);
var fov = 50;
var aspect = win.x/win.y;
var cnear = 10;
var cfar = 4000;
var xrCam = new THREE.PerspectiveCamera( fov,aspect,cnear,cfar );
xrRig.add(xrCam);
xrRig.position.set(0,20,125);
//___________________ depthRig ____ depthCam ____________________________
var depthRig = new THREE.Object3D();
scene.add(depthRig);
var dres = 2;
var lfov = 0.015625;
var laspect = 1;
var lnear = 1;
var lfar = 2000;
var depthCam = new THREE.PerspectiveCamera( lfov,laspect,lnear,lfar );
depthRig.add(depthCam);
depthRig.position.set(40,50);
depthRig.rotateOnAxis(new THREE.Vector3(0,1,0),40 * deg2rad)
// show camera cone (depth won't work)
// const helper = new THREE.CameraHelper( depthCam );
// scene.add( helper );
//_________________________________________________________________
var depthTarget = new THREE.WebGLrendertarget( dres,dres );
depthTarget.texture.format = THREE.RGBAFormat;
// depthTarget.texture.minFilter = THREE.NearestFilter;
// depthTarget.texture.magFilter = THREE.NearestFilter;
// depthTarget.texture.generateMipmaps = false;
// depthTarget.stencilBuffer = false;
// depthTarget.depthBuffer = true;
// depthTarget.depthTexture = new THREE.DepthTexture();
// depthTarget.depthTexture.format = THREE.DepthFormat;
// depthTarget.depthTexture.type = THREE.UnsignedShortType;
var depthMaterial = new THREE.MeshDepthMaterial({depthPacking: THREE.RGBADepthPacking});
var pb = new Uint8Array(4);
var onpos = new THREE.Vector3();
//_________________________________________________________________
const Dlight = new THREE.DirectionalLight( 0xffffff,1);
Dlight.position.set( 0,1000,1000 );
scene.add( Dlight );
//_________________________________________________________________
// *** Webglrenderer XR ***
var renderer = new THREE.Webglrenderer({ antialias: true,precision:'highp'});
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( win.x,win.y );
renderer.autoClear = false;
renderer.xr.enabled = true;
var cont = document.getElementById( 'container' );
cont.appendChild( renderer.domElement );
document.body.appendChild( VRButton.createButton( renderer ) );
//____________ LASER RAY VARS _________________________________
var startray = new THREE.Vector3();
var endray = new THREE.Vector3();
var raygeom = new THREE.BufferGeometry();
var points = [startray,endray];
raygeom.setFromPoints( points );
var rayline = new THREE.Line( raygeom,new THREE.MeshBasicMaterial({color: 0xff0000}) );
scene.add(rayline);
var marker = new THREE.Mesh(new THREE.SphereGeometry(0.8),new THREE.MeshBasicMaterial({color: 0xff0000}));
scene.add(marker);
//____________ CUBE _________________________________
var cubeGeometry = new THREE.BoxGeometry(40,40,40);
var material = new THREE.MeshStandardMaterial({color: "#eabf11"});
var cube = new THREE.Mesh(cubeGeometry,material);
scene.add(cube);
cube.position.set(0,0);
// ______________________VERTICAL_PLANE____________________________
var ccw = 500;
var cch = 150;
var vplane = new THREE.PlaneBufferGeometry( ccw,cch );
var vmap = new THREE.MeshBasicMaterial();
var m = new THREE.Mesh( vplane,vmap );
m.visible = false;
m.position.set(0,150,-1000);
scene.add( m );
//_________ CANVAS _______________
var canvas = document.createElement("canvas");
var ctx = canvas.getContext("2d");
ctx.canvas.width = ccw;
ctx.canvas.height = cch;
ctx.font = "60px Arial";
ctx.textBaseline = "top";
var img,tex;
function drawtext(t1,t2){
ctx.clearRect(0,ccw,cch);
ctx.fillStyle = "#346189";
ctx.fillRect(0,cch);
ctx.fillStyle = "#ffffff";
ctx.fillText(t1,100,10,ccw);
ctx.fillText(t2,80,ccw);
img = ctx.getimageData(0,cch);
tex = new THREE.Texture(img);
tex.needsUpdate = true;
m.material.map = tex;
m.material.needsUpdate = true;
tex.dispose();
m.visible = true;
}
//_________________________________
window.addEventListener('resize',onResize,false);
renderer.setAnimationLoop( render );
//_________handle_window_resizing___________________________
function onResize(){
if (renderer.xr.isPresenting) return;
win.x = window.innerWidth;
win.y = window.innerHeight;
xrCam.aspect = win.x / win.y;
xrCam.updateProjectionMatrix();
renderer.setSize( win.x,win.y );
}
// ____________________render_frame______________________________
function render() {
startms = Date.Now();
renderer.clear();
cube.rotation.y += 0.01;
renderer.xr.enabled = false;
//---------------------- RENDER RGBA-Depth to depthTarget--------------------------
renderer.setClearColor("#ffffff",1);
rayline.visible = false;
marker.visible = false;
renderer.setrendertarget(depthTarget);
scene.overrideMaterial = depthMaterial;
renderer.render(scene,depthCam);
// ******* COMMENT-OUT THE FOLLOWING LINE TO COMPARE ******
renderer.readrendertargetPixels(depthTarget,dres/2,pb);
var dp = pb[0]*0.0000000002328306436538696
+ pb[1]*0.00000005960464477539063
+ pb[2]*0.0000152587890625
+ pb[3]*0.00390625;
var viewZ = (lnear * lfar) / ((lfar - lnear) * dp - lfar);
var midZ = viewZ;
if (viewZ < -lfar) {
midZ = -lfar;
}
onpos.set(0,0.5).applyMatrix4(depthCam.projectionMatrixInverse);
onpos.multiplyScalar(midZ / onpos.z);
onpos.applyMatrix4(depthCam.matrixWorld);
startray = new THREE.Vector3();
depthCam.getWorldPosition(startray);
raygeom.attributes.position.setXYZ(0,startray.x,startray.y,startray.z);
raygeom.attributes.position.setXYZ(1,onpos.x,onpos.y,onpos.z);
raygeom.attributes.position.needsUpdate = true;
//-------------------- RENDER norMAL SCENE ------------------------
renderer.setClearColor("#346189",1);
renderer.xr.enabled = true;
rayline.visible = true;
marker.visible = true;
marker.position.copy(onpos);
scene.overrideMaterial = null;
renderer.setrendertarget(null);
renderer.render( scene,xrCam );
//------- delta time statistics for the last 10 frames -----------
endms = Date.Now();
deltams = endms - startms;
for (let f=avframes; f>0; f--){
tmp = fbuffer[f];
minms = Math.min(tmp,minms);
maxms = Math.max(tmp,maxms);
fbuffer[f+1]=tmp;
}
fbuffer[1] = deltams;
fcounter++;
if (fcounter === avframes){
fcounter = 0;
drawtext("max-ms:"+maxms,"min-ms:"+minms);
minms = 1000;
maxms = 0;
}
}//end render() _______________________________________________________________________________
</script>
</body>
</html>
更新 #2
Andre van Kammen 的 mod:在移动设备(小米红米 Note 9S)中仍然非常高的延迟。
使用网络摄像头拍摄的视频: https://www.bitchute.com/video/5WJxdo649KiF/
有一篇关于像素缓冲对象 (PBO) 的广泛文章: http://www.songho.ca/opengl/gl_pbo.html 它看起来非常有前途,它是关于 GPU 数据的异步读取,不会使 GPU 停顿......直到我尝试了按下空格键时关闭和打开的演示,并且得到 零 (0) 差异! strong> 在我的电脑上:http://www.songho.ca/opengl/files/pboUnpack.zip
因此,显然,在 gl.readpixels 推出 30 年后,技术未能提供一种可靠且有效的方式来读取该死的像素......这是非常可耻的,我正在设计硬件,多年来我学到的一件事是,电子领域的每个问题都有解决方案,这同样适用于软件和所有其他领域。显然,对于行业的某些部分来说,这是草率第一,而不是性能第一。请证明我错了。
解决方法
如果看一下readRenderTargetPixels中的代码:
您可以看到它正在使用 readPixels 进行读取并对其进行了一些检查。 ReadPixels 总是很慢,因为它强制同步,这意味着 GPU 必须在读取像素并将其返回之前完成所有工作,像素数量并不重要,直到达到兆字节。
25ms 是一个很长的时间,在读取像素之前你在做什么?
更新: 我已经运行了你的小提琴,它使用 readPixels 运行得相当慢(在 1-7ms 和 0-2ms 上),我在 chrome 的性能选项卡中检查了它,35.2% 的时间用于 readPixels,23.4% 的时间用于 checkFrameBufferStatus。这确实很多,但也有大约一半的时间,这可能来自等待场景渲染。
如果你删除这条线,一切都会异步运行,你就不会测量显卡上的等待时间。我将读取移动到 settimeout 以显示差异:
https://jsfiddle.net/rdvz15fx/4/
console.log('Why do i have to add code to a fiddle that is the code!')
它运行得更快一些,因为它可以异步读取,所以它不必等待。但这也意味着数据落后了一帧。
由于您使用它来更改相机位置,因此最好在顶点着色器中处理它,该着色器读取附加到您渲染的帧缓冲区的纹理。这样你就不必使用 readPixels 并将其保存在视频卡中。我不知道如何在 Threejs 中做到这一点。
这里有一段代码,用于在执行 readpixels 之前检查 GPU 是否准备好,将其放在 draw 之后
this.webGLSync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE,0);
然后你可以检查样品是否准备好了
checkSamplesReady() {
return this.gl.clientWaitSync(this.webGLSync,0) === this.gl.CONDITION_SATISFIED;
}
很遗憾,您仍然需要进行投票,但遗憾的是没有回调或事件。
版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。