微信公众号搜"智元新知"关注
微信扫一扫可直接关注哦!

在 Chrome Android 上使用 readRenderTargetPixels() 的巨大延迟,带有第二个深度摄像头 - 完整演示

如何解决在 Chrome Android 上使用 readRenderTargetPixels() 的巨大延迟,带有第二个深度摄像头 - 完整演示

这显然是对 WEBGL-Gurus 的呼吁:

我使用第二个深度相机渲染到 2x2 像素分辨率,但我只读取了一个像素。

我试过了:

    Three.js 中的
  • renderer.readrendertargetPixels()(我认为它与纯 gl.readPixels 中的 WEBGL 相同)。

  • getimageData() 渲染到 Canvas 后。

两者似乎都非常慢:在我的情况下,它们加起来长达 25 毫秒,即使对于 2x2 像素的渲染目标也是如此。此处还提到了不合理的低效率,尤其是 gl.readpixelswhat is the correct way to use gl.readPixels?

所以,我正在寻找一种解决方案 - 任何解决方案,可以有效地读取该单像素,并将其放入 JS 数组或对象中。谢谢。

更新:

-我更新了标题和正文,使其更加切题。

-我创建了一个 JSfiddle 来演示 readrendertargetPixels()(相当于 gl.readpixels())延迟。 我试图让它尽可能简单,但不是......更简单:)

注意事项/使用方法

  1. 此演示可在正常模式和 WebXR 模式下运行。 你不必进入 VR 就能看到巨大的延迟。 JSfiddle一个 BUG,它不会在移动设备上显示 VRButton。 为了在移动设备上进入 VR,您需要将代码复制到 index.html 并通过 WIFI 运行安全的本地服务器 (HTTPS) 或使用不会使 VRButton ...不可见的 JSfiddle-alternative -I测试了很多,我找不到一个有效的(!)

  2. 这个演示显示了过去 10 帧的渲染统计数据(最小和最大毫秒渲染时间),为了您的方便,屏幕上有普通和 WebXR 模式,所以很容易看到它也可以在移动设备上使用。

  3. 一个主摄像头和一个深度摄像头。深度摄像头的分辨率为 2x2 像素,FOV 为 0.015 度。它指向一个旋转的立方体,它测量深度并在立方体的表面上绘制一条射线和一个点。我什至优化了代码以消除 JS 中代价高昂的解码数学。

  4. 我主要对移动设备感兴趣,它的延迟要高得多,为了方便起见,我提供了二维码。 因此,要在手机上进行测试,请扫描下面的#1 QR 码,等待一分钟稳定,然后扫描第二个QR 码进行比较。要查看/编辑 JSfiddle 中的代码,请从 url 中删除“show”并重新加载页面

  5. 请在 Chrome 浏览器上测试演示。如果您在 PC 上运行演示,在判断延迟之前等待一分钟也很重要。我注意到 PC 上的 Firefox 的延迟远低于 PC 上的 Chrome 并且稳定得多,但是我感兴趣的功能在 FF 上不受支持,所以我只对 Chrome 感兴趣。在我的 PC 上,Chrome 以大约 5 毫秒的渲染时间开始(与没有该功能的 1-2 毫秒相比,这仍然是巨大的),然后过了一会儿它会翻倍和翻三倍。在移动设备上,它几乎总是很高,在 15 到 30 毫秒之间(强大的移动设备)。

readrendertargetPixels() 开启:

readRenderTargetPixels() is ON

https://jsfiddle.net/dlllb/h3ywjeud/show

readrendertargetPixels() 关闭

enter image description here

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>

enter image description here

enter image description here

更新 #2

Andre van Kammen 的 mod:在移动设备(小米红米 Note 9S)中仍然非常高的延迟

使用网络摄像头拍摄的视频: https://www.bitchute.com/video/5WJxdo649KiF/

enter image description here

有一篇关于像素缓冲对象 (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中的代码:

https://github.com/mrdoob/three.js/blob/65e6b4835ae52ea6136392b12ee7114bccefc35a/src/renderers/WebGLRenderer.js

您可以看到它正在使用 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 举报,一经查实,本站将立刻删除。