WebGL readPixels总是返回0,0,0,0。

23

我正在尝试在WebGL中进行拾取操作。我已经渲染了两个形状,并在每个形状上分别映射了不同的纹理。我正在尝试获取特定坐标下的像素。以下是示例。

var pixelValues = new Uint8Array(4);
gl.readPixels(10, 35, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixelValues);
console.log(pixelValues);

但是 pixelValues 总是包含 [0,0,0,0]。我做错了什么?我需要做一些与帧缓冲有关的事情吗?

3个回答

21
您不需要使用“preserveDrawingBuffer:true”来调用“readPixels”。您需要的是在退出当前事件之前调用“readPixels”。
规范说明,如果您调用任何影响画布的函数(如gl.clear、gl.drawXXX),则浏览器将在下一个复合操作后清除画布。该复合操作发生的时间由浏览器决定。它可能在处理几个鼠标事件、键盘事件或单击事件后发生。顺序是未定义的。已定义的是它不会在当前事件退出之前执行。
render
read

const gl = document.querySelector("canvas").getContext("webgl");

render();
read();  // read in same event

function render() {
  gl.clearColor(.25, .5, .75, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);
}

function read() {
  const pixel = new Uint8Array(4);
  gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
  log(pixel);
}

function log(...args) {
  const elem = document.createElement("pre");
  elem.textContent = [...args].join(' ');
  document.body.appendChild(elem);
}
<canvas></canvas>

工作原理是

render
setTimeout(read, 1000);  // some other event

无法工作。

const gl = document.querySelector("canvas").getContext("webgl");

render();
setTimeout(read, 1000);  // read in other event

function render() {
  gl.clearColor(.25, .5, .75, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);
}

function read() {
  const pixel = new Uint8Array(4);
  gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
  log(pixel);
}

function log(...args) {
  const elem = document.createElement("pre");
  elem.textContent = [...args].join(' ');
  document.body.appendChild(elem);
}
<canvas></canvas>

请注意,由于这是复合操作(浏览器实际上将画布与其他HTML元素一起绘制在页面上),因此如果画布不在页面上,则不会进行复合操作,也不会被清除。
换句话说,之前无法工作的情况在这里可以工作。

// create an offscreen canvas. Because it's offscreen it won't be composited
// and therefore will not be cleared.
const gl = document.createElement("canvas").getContext("webgl");

render();
setTimeout(read, 1000);  // read in other event

function render() {
  gl.clearColor(.25, .5, .75, 1);
  gl.clear(gl.COLOR_BUFFER_BIT);
}

function read() {
  const pixel = new Uint8Array(4);
  gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixel);
  log(pixel);
}

function log(...args) {
  const elem = document.createElement("pre");
  elem.textContent = [...args].join(' ');
  document.body.appendChild(elem);
}

现在,如果您想在其他事件中调用readPixels,比如当用户单击某个元素时,那么您至少有两个选项。
  1. Set preserveDrawingBuffer: true

  2. Render again in your event

    screenshotElement.addEventListener('click', event => {
      render();  
      gl.readPixels(...);
    });
    

如果我们使用不带preserveDrawingBuffer: true的上下文进行渲染,然后通过设置preserveDrawingBuffer: true并获取像素从同一画布中获取另一个上下文,那么后者的readPixels是否能够正常工作?第二个上下文的新配置是否会影响第一个上下文?我期望它们是相同的对象。这样对吗? - hackjutsu
1
你不能从同一画布获取具有不同属性的第二个上下文。第二次调用getContext将返回与第一次调用相同的上下文,并且设置将保持与第一次调用时相同。 - gman
对我来说还是零。c = $0.getContext("webgl2", {preserveDrawingBuffer: true}); bitmapData = new Uint8Array($0.width * $0.height * 4); c.readPixels(0, 0, $0.width, $0.height, c.RGBA, c.UNSIGNED_BYTE, bitmapData); bitmapData.reduce((a,b)=>a+b) 返回0。 - Nakilon
1
你没有画任何东西。它默认为零:$0 = document.createElement('canvas'); c = $0.getContext("webgl2", {preserveDrawingBuffer: true}); c.clearColor(1,0,0,1); c.clear(c.COLOR_BUFFER_BIT); bitmapData = new Uint8Array($0.width * $0.height * 4); c.readPixels(0, 0, $0.width, $0.height, c.RGBA, c.UNSIGNED_BYTE, bitmapData); bitmapData.reduce((a,b)=>a+b) 返回 22950000。 - gman

16

根据WebGL规范,您需要调用getContext并设置preserveDrawingBuffer标志,例如:

var ctx = canvas.getContext("webgl", {preserveDrawingBuffer: true});

如果你计划在GL上下文渲染事件退出后读取像素。这会防止绘图缓冲区(颜色,深度,模板)在它们绘制到屏幕后被清除。请注意,设置此选项可能会导致性能损失。

或者,你可以在呈现像素之前读取它们,这也应该可以工作。


2
这是错误的 :( - gman
它是不完整的,我同意。已修复以包括替代方案。 - fserb

-1
我有两个形状,每个形状上都贴有不同的纹理。
gl.readPixels(10, 35, 1, 1, ...);
我相信你认为 gl.readPixels 从左上角开始读取像素,但实际上它并不是这样。gl.readPixels 是从下左角开始读取像素的。

enter image description here

来自WebGLRenderingContext.readPixels()文档:

参数

x

一个GLint,指定从像素块的左下角读取的第一个水平像素。

y

一个GLint,指定从像素矩形块的左下角读取的第一个垂直像素。

enter image description here

<!DOCTYPE html>

<body>

    <head>
        <title>Pick object by click. WebGL, JavaScript</title>

        <script src="https://cdn.jsdelivr.net/npm/gl-matrix@3.4.3/gl-matrix-min.js"></script>

        <style>
            #renderCanvas {
                position: absolute;
            }

            #outputEN {
                position: absolute;
                top: 210px;
                left: 20px;
            }

            #outputRU {
                position: absolute;
                top: 235px;
                left: 20px;
            }

            #outputCH {
                position: absolute;
                top: 260px;
                left: 20px;
            }

            #outputPinyin {
                position: absolute;
                top: 285px;
                left: 20px;
            }
        </style>
    </head>

    <body>
        <div>
            <canvas id="renderCanvas" width="300" height="300"></canvas>
            <span id="outputEN">Click on any object or outside</span>
            <span id="outputRU">Кликните на любой объект или мимо</span>
            <span id="outputCH">单击任何对象或外部</span>
            <span id="outputPinyin">Dān jí rènhé duìxiàng huò wàibù</span>
        </div>

        <script id="vertexShader" type="x-shader/x-vertex">
            attribute vec2 aPosition;
            uniform mat4 uMvpMatrix;

            void main() {
                gl_Position = uMvpMatrix * vec4(aPosition, 0.0, 1.0);
            }
        </script>

        <script id="fragmentShader" type="x-shader/x-fragment">
            precision mediump float;
            uniform vec3 uColor;
            uniform bool uClick;
            uniform vec3 uPickColor;

            void main() {
                if (!uClick) {
                    gl_FragColor = vec4(uColor, 1.0);
                } else {
                    gl_FragColor = vec4(uPickColor, 1.0);
                }
            }
        </script>

        <script>
            const gl = document.getElementById("renderCanvas").getContext("webgl");

            const outputEN = document.getElementById("outputEN");
            const outputRU = document.getElementById("outputRU");

            const vShader = gl.createShader(gl.VERTEX_SHADER);
            const vSrc = document.getElementById("vertexShader").firstChild.textContent;
            gl.shaderSource(vShader, vSrc);
            gl.compileShader(vShader);
            let ok = gl.getShaderParameter(vShader, gl.COMPILE_STATUS);
            if (!ok) {
                console.log("vert: " + gl.getShaderInfoLog(vShader));
            };

            const fShader = gl.createShader(gl.FRAGMENT_SHADER);
            const fSrc = document.getElementById("fragmentShader").firstChild.textContent;
            gl.shaderSource(fShader, fSrc);
            gl.compileShader(fShader);
            ok = gl.getShaderParameter(fShader, gl.COMPILE_STATUS);
            if (!ok) {
                console.log("frag: " + gl.getShaderInfoLog(fShader));
            };

            const program = gl.createProgram();
            gl.attachShader(program, vShader);
            gl.attachShader(program, fShader);
            gl.linkProgram(program);
            ok = gl.getProgramParameter(program, gl.LINK_STATUS);
            if (!ok) {
                console.log("link: " + gl.getProgramInfoLog(program));
            };
            gl.useProgram(program);

            const vertPositions = [
                -0.5, -0.5,
                -0.5, 0.5,
                0.5, -0.5,
                0.5, 0.5
            ];
            const vertPosBuffer = gl.createBuffer();
            gl.bindBuffer(gl.ARRAY_BUFFER, vertPosBuffer);
            gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertPositions), gl.STATIC_DRAW);
            const aPositionLocation = gl.getAttribLocation(program, "aPosition");
            gl.vertexAttribPointer(aPositionLocation, 2, gl.FLOAT, false, 0, 0);
            gl.enableVertexAttribArray(aPositionLocation);

            const modelMatrix = glMatrix.mat4.create();
            const mvpMatrix = glMatrix.mat4.create();

            const projMatrix = glMatrix.mat4.create();
            glMatrix.mat4.ortho(projMatrix, -0.5, 2.5, 2.5, -0.5, 10, -10);

            const viewMatrix = glMatrix.mat4.create();
            glMatrix.mat4.lookAt(viewMatrix, [0, 0, 10], [0, 0, 0], [0, 1, 0]);

            const projViewMatrix = glMatrix.mat4.create();
            glMatrix.mat4.mul(projViewMatrix, projMatrix, viewMatrix);

            const uMvpMatrixLocation = gl.getUniformLocation(program, "uMvpMatrix");
            const uColorLocation = gl.getUniformLocation(program, "uColor");
            const uClickLocation = gl.getUniformLocation(program, "uClick");
            const uPickColorLocation = gl.getUniformLocation(program, "uPickColor");

            gl.uniform1i(uClickLocation, 0);

            const firstObj = {
                pos: glMatrix.vec3.fromValues(0, 0, 0),
                scale: glMatrix.vec3.fromValues(0.7, 0.7, 1),
                color: glMatrix.vec3.fromValues(0.50, 0.84, 0.22)
            };

            const secondObj = {
                pos: glMatrix.vec3.fromValues(1, 0, 0),
                scale: glMatrix.vec3.fromValues(0.7, 0.7, 1),
                color: glMatrix.vec3.fromValues(0.07, 0.59, 0.09)
            };

            const thirdObj = {
                pos: glMatrix.vec3.fromValues(2, 0, 0),
                scale: glMatrix.vec3.fromValues(0.7, 0.7, 1),
                color: glMatrix.vec3.fromValues(0.12, 0.88, 0.48)
            };

            const fourthObj = {
                pos: glMatrix.vec3.fromValues(0, 1, 0),
                scale: glMatrix.vec3.fromValues(0.7, 0.7, 1),
                color: glMatrix.vec3.fromValues(0.65, 0.37, 0.07)
            };

            const pickColors = {
                first: glMatrix.vec3.fromValues(255, 0, 0),
                second: glMatrix.vec3.fromValues(0, 255, 0),
                third: glMatrix.vec3.fromValues(0, 0, 255),
                fourth: glMatrix.vec3.fromValues(255, 255, 0)
            };

            gl.canvas.onmousedown = (e) => {
                // Get coordinates of mouse pick
                const rect = gl.canvas.getBoundingClientRect();
                const mouseX = e.clientX - rect.left;
                const mouseY = e.clientY - rect.top;
                const pickX = mouseX;
                const pickY = rect.bottom - rect.top - mouseY - 1;
                // console.log("mouse pick coords:", pickX, pickY);

                // Set the click flag and color id
                gl.uniform1i(uClickLocation, 1);
                gl.clearColor(0, 0, 0, 1);
                gl.clear(gl.COLOR_BUFFER_BIT);

                // Draw objects for picking
                gl.uniform3fv(uPickColorLocation, pickColors.first);
                glMatrix.mat4.fromTranslation(modelMatrix, firstObj.pos);
                glMatrix.mat4.scale(modelMatrix, modelMatrix, firstObj.scale);
                glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
                gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
                gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

                gl.uniform3fv(uPickColorLocation, pickColors.second);
                glMatrix.mat4.fromTranslation(modelMatrix, secondObj.pos);
                glMatrix.mat4.scale(modelMatrix, modelMatrix, secondObj.scale);
                glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
                gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
                gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

                gl.uniform3fv(uPickColorLocation, pickColors.third);
                glMatrix.mat4.fromTranslation(modelMatrix, thirdObj.pos);
                glMatrix.mat4.scale(modelMatrix, modelMatrix, thirdObj.scale);
                glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
                gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
                gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

                gl.uniform3fv(uPickColorLocation, pickColors.fourth);
                glMatrix.mat4.fromTranslation(modelMatrix, fourthObj.pos);
                glMatrix.mat4.scale(modelMatrix, modelMatrix, fourthObj.scale);
                glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
                gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
                gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

                const pixels = new Uint8Array(4);
                gl.readPixels(pickX, pickY, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
                // console.log("pick color:", pixels[0], pixels[1], pixels[2], pixels[3]);
                const pickResult = glMatrix.vec3.fromValues(pixels[0], pixels[1],
                    pixels[2]);

                let messageEN = "";
                let messageRU = "";
                let messageCH = "";
                let messagePinyin = "";
                if (glMatrix.vec3.exactEquals(pickResult, pickColors.first)) {
                    messageEN = "First object";
                    messageRU = "Первый объект";
                    messageCH = "第一个对象";
                    messagePinyin = "Dì yī gè duìxiàng";
                } else if (glMatrix.vec3.exactEquals(pickResult, pickColors.second)) {
                    messageEN = "Second object";
                    messageRU = "Второй объект";
                    messageCH = "第二个对象";
                    messagePinyin = "Dì èr gè duìxiàng";
                } else if (glMatrix.vec3.exactEquals(pickResult, pickColors.third)) {
                    messageEN = "Third object";
                    messageRU = "Третий объект";
                    messageCH = "第三个对象";
                    messagePinyin = "Dì sān gè duìxiàng";
                } else if (glMatrix.vec3.exactEquals(pickResult, pickColors.fourth)) {
                    messageEN = "Fourth object";
                    messageRU = "Четвёртый объект";
                    messageCH = "第四个对象";
                    messagePinyin = "Dì sì gè duìxiàng";
                } else {
                    messageEN = "You didn't click on the objects";
                    messageRU = "Вы не кликнули по объектам";
                    messageCH = "你没有点击对象";
                    messagePinyin = "Nǐ méiyǒu diǎnjī duìxiàng";
                }
                console.log(messageEN);
                outputEN.innerText = messageEN;
                outputRU.innerText = messageRU;
                outputCH.innerText = messageCH;
                outputPinyin.innerText = messagePinyin;

                gl.uniform1i(uClickLocation, 0);
                draw();
            };

            function draw() {
                gl.clearColor(0.9, 0.9, 0.95, 1);
                gl.clear(gl.COLOR_BUFFER_BIT);

                glMatrix.mat4.fromTranslation(modelMatrix, firstObj.pos);
                glMatrix.mat4.scale(modelMatrix, modelMatrix, firstObj.scale);
                glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
                gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
                gl.uniform3fv(uColorLocation, firstObj.color);
                gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

                glMatrix.mat4.fromTranslation(modelMatrix, secondObj.pos);
                glMatrix.mat4.scale(modelMatrix, modelMatrix, secondObj.scale);
                glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
                gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
                gl.uniform3fv(uColorLocation, secondObj.color);
                gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

                glMatrix.mat4.fromTranslation(modelMatrix, thirdObj.pos);
                glMatrix.mat4.scale(modelMatrix, modelMatrix, thirdObj.scale);
                glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
                gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
                gl.uniform3fv(uColorLocation, thirdObj.color);
                gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);

                glMatrix.mat4.fromTranslation(modelMatrix, fourthObj.pos);
                glMatrix.mat4.scale(modelMatrix, modelMatrix, fourthObj.scale);
                glMatrix.mat4.mul(mvpMatrix, projViewMatrix, modelMatrix);
                gl.uniformMatrix4fv(uMvpMatrixLocation, false, mvpMatrix);
                gl.uniform3fv(uColorLocation, fourthObj.color);
                gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
            }

            draw();
        </script>
    </body>

</body>


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接