webgl readpixels is always returning 0,0,0,0
I am trying to do picking in WebGl. I have two shapes rendered along with different texture mapped on each. I am trying to grab pixel on certain co-ordinates. Here is the example.
var pixelValues = new Uint8Array(4);
gl.readPixels(10, 35, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, pixelValues);
console.log(pixelValues);
But pixelValues always contain [0,0,0,0].开发者_如何学Go What am I doing wrong? Do I need to do something related to framebuffer?
You don't need preserveDrawingBuffer: true
to call readPixels
. What you need is to call readPixels
before exiting the current event.
The spec says if you call any function that affects the canvas (gl.clear, gl.drawXXX) then the browser will clear the canvas after the next composite operation. When that composite operation happens is up to the browser. It could be after it processes several mouse events or keyboard events or click events. The order is undefined. What is defined is that it won't do it until the current event exits so
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>
works where as
render
setTimeout(read, 1000); // some other event
does not work
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>
Note that since it's the composite operation (the browser actually drawing the canvas on the page with the rest of the HTML) that triggers the clear, if the canvas is not on the page then it's not composited and won't be cleared.
In other words the case that didn't work above does work here
// 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);
}
Now, if you want to call readPixels
in some other event, like when the user clicks an element, then you have at least 2 options
Set
preserveDrawingBuffer: true
Render again in your event
screenshotElement.addEventListener('click', event => { render(); gl.readPixels(...); });
According to WebGL specs, you need to call getContext
setting the preserveDrawingBuffer
flag, like:
var ctx = canvas.getContext("webgl", {preserveDrawingBuffer: true});
if you plan to read the pixels after exiting the event where the GL context is rendered. This prevents the drawing buffer (color, depth, stencil) from being cleared after they are draw to screen. Keep in mind that settings this may cause a performance penalty.
Alternatively, you can read the pixels before they are presented, which should also work.
I have two shapes rendered along with different texture mapped on each.
gl.readPixels(10, 35, 1, 1, ...);
I'm sure you thought gl.readPixels
starts reading pixels from the top left corner, but it doesn't. gl.readPixels
starts reading them from the lower left corner.
From the WebGLRenderingContext.readPixels() documentation:
Parameters
x
A GLint specifying the first horizontal pixel that is read from the lower left corner of a rectangular block of pixels.
y
A GLint specifying the first vertical pixel that is read from the lower left corner of a rectangular block of pixels.
<!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>
精彩评论