Unable to read pixels from a grayscale heightmap with WebGL? - webgl

I'm trying to read the pixels of greyscale heightmap in order to store the height values later in a mesh, but whatever I do, I constantly read the same values rgba(0, 0, 0, 255).
Note: Color normal images are perfectly read.
The image used:
Code I've written:
let canvas = document.querySelector("canvas");
let gl = canvas.getContext("webgl");
gl.canvas.width = canvas.getBoundingClientRect().width;
gl.canvas.height = canvas.getBoundingClientRect().height;
let vertexShaderSource = `
attribute vec4 a_position;
varying vec2 v_texturePos;
void main() {
gl_Position = vec4(a_position.xy, 0, 1.0);
v_texturePos = (a_position.xy+1.0)/2.0;
}
`;
let fragmentShaderSource = `
precision mediump float;
uniform sampler2D u_heightmap;
varying vec2 v_texturePos;
void main() {
gl_FragColor = texture2D(u_heightmap, v_texturePos);
}
`;
function createShader(gl, type, source) {
let shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
let success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if(success)
return shader;
console.log(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}
function createProgram(gl, vertexShader, fragmentShader) {
let program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
let success = gl.getProgramParameter(program, gl.LINK_STATUS);
if(success)
return program;
console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
let mesh = [
-1, -1, 0,
-1, 1, 0,
1, 1, 0,
1, 1, 0,
1, -1, 0,
-1, -1, 0
];
function drawScene(gl) {
gl.clearColor(0, 0, 0, 0);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(mesh), gl.STATIC_DRAW);
gl.vertexAttribPointer(attribPositionLoc, 3, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.TRIANGLES, 0, 6);
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE) {
let pixels = new Uint8Array(gl.canvas.width*gl.canvas.height*4/625);
for(let g = 0; g < gl.canvas.width; g += 25) {
for(let h = 0; h < gl.canvas.height; h += 25) {
gl.readPixels(g, h, gl.canvas.width/25, gl.canvas.height/25, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
}
}
console.log(pixels);
}
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.bindTexture(gl.TEXTURE_2D, frameTexture);
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
let attribPositionLoc;
let frameBuffer;
let texture, frameTexture;
function resize(gl) {
let realToCSSPixels = window.devicePixelRatio;
let displayWidth = Math.floor(gl.canvas.clientWidth * realToCSSPixels);
let displayHeight = Math.floor(gl.canvas.clientHeight * realToCSSPixels);
if (gl.canvas.width !== displayWidth ||
gl.canvas.height !== displayHeight) {
gl.canvas.width = displayWidth;
gl.canvas.height = displayHeight;
}
}
let img = document.createElement("img");
img.crossOrigin = "null";
img.src = "http://localhost:8000/heightmap?filename=terrain.jpg";
img.addEventListener("load", startWebGL.bind(this, gl));
function startWebGL(gl) {
resize(gl);
let vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
let fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
let program = createProgram(gl, vertexShader, fragmentShader);
gl.useProgram(program);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LESS);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
frameBuffer = gl.createFramebuffer();
frameTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, frameTexture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.canvas.width, gl.canvas.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, frameTexture, 0);
attribPositionLoc = gl.getAttribLocation(program, "a_position");
let positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.enableVertexAttribArray(attribPositionLoc);
drawScene(gl);
}
<canvas></canvas>
What am I doing wrong and how can I fix it? Any ideas?

It's not at all clear what this code is trying to do
let pixels = new Uint8Array(gl.canvas.width*gl.canvas.height*4/625);
for(let g = 0; g < gl.canvas.width; g += 25) {
for(let h = 0; h < gl.canvas.height; h += 25) {
gl.readPixels(g, h, gl.canvas.width/25, gl.canvas.height/25, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
}
}
console.log(pixels);
What does dividing by 625 do? On top of that you only print the last result. If you want read the entire canvas it's just
let pixels = new Uint8Array(gl.canvas.width*gl.canvas.height*4);
gl.readPixels(g, h, gl.canvas.width, gl.canvas.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
In any case if I change the URL for the image to something that can be loaded while on stack overflow I see the expected values. Looking at your image since you're only reading at 25x25 area and you're only printing the last 25x25 area since your console.log is outside the loop I'm guessing your reading a black corner of the image.
Also since you're stepping by 25, if your canvas is not a multiple of 25 then you'll read off the edge, past the end of the canvas. Reading off the edge always produces 0,0,0,0.
let canvas = document.querySelector("canvas");
let gl = canvas.getContext("webgl");
gl.canvas.width = canvas.getBoundingClientRect().width;
gl.canvas.height = canvas.getBoundingClientRect().height;
let vertexShaderSource = `
attribute vec4 a_position;
varying vec2 v_texturePos;
void main() {
gl_Position = vec4(a_position.xy, 0, 1.0);
v_texturePos = (a_position.xy+1.0)/2.0;
}
`;
let fragmentShaderSource = `
precision mediump float;
uniform sampler2D u_heightmap;
varying vec2 v_texturePos;
void main() {
gl_FragColor = texture2D(u_heightmap, v_texturePos);
}
`;
function createShader(gl, type, source) {
let shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
let success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if(success)
return shader;
console.log(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
}
function createProgram(gl, vertexShader, fragmentShader) {
let program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
let success = gl.getProgramParameter(program, gl.LINK_STATUS);
if(success)
return program;
console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
}
let mesh = [
-1, -1, 0,
-1, 1, 0,
1, 1, 0,
1, 1, 0,
1, -1, 0,
-1, -1, 0
];
function drawScene(gl) {
gl.clearColor(0, 0, 0, 0);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(mesh), gl.STATIC_DRAW);
gl.vertexAttribPointer(attribPositionLoc, 3, gl.FLOAT, false, 0, 0);
gl.drawArrays(gl.TRIANGLES, 0, 6);
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) === gl.FRAMEBUFFER_COMPLETE) {
let pixels = new Uint8Array(gl.canvas.width*gl.canvas.height*4/625);
for(let g = 0; g < gl.canvas.width; g += 25) {
for(let h = 0; h < gl.canvas.height; h += 25) {
gl.readPixels(g, h, gl.canvas.width/25, gl.canvas.height/25, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
}
}
console.log(pixels);
}
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.bindTexture(gl.TEXTURE_2D, frameTexture);
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
let attribPositionLoc;
let frameBuffer;
let texture, frameTexture;
function resize(gl) {
let realToCSSPixels = window.devicePixelRatio;
let displayWidth = Math.floor(gl.canvas.clientWidth * realToCSSPixels);
let displayHeight = Math.floor(gl.canvas.clientHeight * realToCSSPixels);
if (gl.canvas.width !== displayWidth ||
gl.canvas.height !== displayHeight) {
gl.canvas.width = displayWidth;
gl.canvas.height = displayHeight;
}
}
let img = document.createElement("img");
img.crossOrigin = "null";
// img.src = "http://localhost:8000/heightmap?filename=terrain.jpg";
img.src = "https://i.imgur.com/ZKMnXce.png";
img.addEventListener("load", startWebGL.bind(this, gl));
function startWebGL(gl) {
resize(gl);
let vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
let fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
let program = createProgram(gl, vertexShader, fragmentShader);
gl.useProgram(program);
gl.enable(gl.DEPTH_TEST);
gl.depthFunc(gl.LESS);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
frameBuffer = gl.createFramebuffer();
frameTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, frameTexture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.canvas.width, gl.canvas.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
gl.bindFramebuffer(gl.FRAMEBUFFER, frameBuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, frameTexture, 0);
attribPositionLoc = gl.getAttribLocation(program, "a_position");
let positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.enableVertexAttribArray(attribPositionLoc);
drawScene(gl);
}
<canvas></canvas>

Related

what is the use and role of texSubImage2D?

After a lot of searching, I managed to get the texSubImage2D function to work. Simply what I haven't found is: what is this function for. In the example below I made a nice effect. In short, I know how to make it work but I am still completely unaware of the role of its parameters. And where to find these explanations?
I'm not looking for the syntax,
the example I give shows that I have (it seems to me) understood it well.
https://registry.khronos.org/webgl/specs/latest/1.0/#5.14.8
What I don't understand at all is the semantics...
Anyway, if someone could answer with examples so that I can understand.
"use strict";
let canvas = document.getElementById("canvas");
let gl = canvas.getContext("webgl");
gl.canvas.width = 30;
gl.canvas.height = 30;
let vertex = `
attribute vec2 a_position;
attribute vec2 a_texCoord;
uniform vec2 u_resolution;
varying vec2 v_texCoord;
void main() {
vec2 zeroToOne = a_position / u_resolution;
vec2 zeroToTwo = zeroToOne * 2.0;
vec2 clipSpace = zeroToTwo - 1.0;
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
v_texCoord = a_texCoord;
}
`;
let fragment = `
precision mediump float;
uniform sampler2D u_image;
varying vec2 v_texCoord;
void main() {
gl_FragColor = texture2D(u_image, v_texCoord);
gl_FragColor.rgb *= gl_FragColor.a;
}
`;
let shader = gl.createProgram();
const vertexShader = gl.createShader(gl.VERTEX_SHADER);
const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(vertexShader, vertex);
gl.shaderSource(fragmentShader, fragment);
gl.compileShader(vertexShader);
gl.compileShader(fragmentShader);
gl.attachShader(shader, vertexShader);
gl.attachShader(shader, fragmentShader);
gl.linkProgram(shader);
let image_RGBA = new Image();
image_RGBA.src = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAeBAMAAADJHrORAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAYUExURdUAAKPTdgCN09Aq0w4A09PS0dOoXwD//56WZMcAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAA8SURBVCjPYyAIBJEBNr4SAmDnG8MALr4LBODmh4IAPn5aWhp+fjkBPgH9BOwn4H4C/icQfgTCHx9gYAAArEg8b+0tf+EAAAAASUVORK5CYII=";
image_RGBA.onload = function() {
go(image_RGBA);
};
function go(image) {
let width = image.width;
let height = image.height;
let positionLocation = gl.getAttribLocation(shader, "a_position");
let texcoordLocation = gl.getAttribLocation(shader, "a_texCoord");
let positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0, 0,
width, 0,
0, height,
0, height,
width, 0,
width, height
]), gl.STATIC_DRAW);
let texcoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
0.0, 1.0,
1.0, 0.0,
1.0, 1.0,
]), gl.STATIC_DRAW);
let texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
const canvas2D = document.getElementById('canvas2D');
canvas2D.width = 30;
canvas2D.height = 30;
const ctx = canvas2D.getContext('2d');
ctx.drawImage(image, 0, 0);
var imgData = ctx.getImageData(0, 0, width, height).data;
var ArrayBufferView = new Uint8Array(imgData.buffer);
gl.texImage2D(
gl.TEXTURE_2D,
0,
gl.RGBA,
30,
30,
0,
gl.RGBA,
gl.UNSIGNED_BYTE,
ArrayBufferView
);
gl.texSubImage2D(
gl.TEXTURE_2D,
0,
0,
0,
29,
29,
gl.RGBA,
gl.UNSIGNED_BYTE,
ArrayBufferView
);
let resolutionLocation = gl.getUniformLocation(shader, "u_resolution");
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clearColor(100 / 255, 200 / 255, 150 / 255, 1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(shader);
gl.enableVertexAttribArray(positionLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
let size = 2;
let type = gl.FLOAT;
let normalize = false;
let stride = 0;
let offset = 0;
gl.vertexAttribPointer(positionLocation, size, type, normalize, stride, offset);
gl.enableVertexAttribArray(texcoordLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
size = 2;
type = gl.FLOAT;
normalize = false;
stride = 0;
offset = 0;
gl.vertexAttribPointer(texcoordLocation, size, type, normalize, stride, offset);
gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height);
gl.enable(gl.BLEND);
gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
#canvas {
width: 150px;
height: 150px;
image-rendering: pixelated;
}
#canvas2D {
width: 150px;
height: 150px;
image-rendering: pixelated;
}
<canvas id="canvas2D"></canvas><canvas id="canvas"></canvas>

WebGL FBO (real-time drawing)

I'm trying to translate this example from Three.js - https://codepen.io/tutsplus/pen/PZmpEM
to pure WebGL. I experimented a lot with the code, I think there is an error in texture baking, but my attempts are unsuccessful, if not difficult, please!
WebGL example
let a_Position, u_Mouse, u_Sampler, u_Resolution;
const position = {
screenRect: null,
xyz: [0.0, 0.0, 0.0],
mouseDown: false,
};
function main() {
const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl');
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
const program = webglUtils.createProgramFromScripts(gl, ["2d-vertex-shader", "2d-fragment-shader"]);
gl.useProgram(program);
const tick = function() {
render(gl, canvas, fbo, plane);
window.requestAnimationFrame(tick, canvas);
};
a_Position = gl.getAttribLocation(program, 'a_position');
u_Mouse = gl.getUniformLocation(program, 'u_mouse');
u_Resolution = gl.getUniformLocation(program, 'u_resolution');
u_Sampler = gl.getUniformLocation(program, 'u_sampler');
const fbo = [initFramebufferObject(gl), initFramebufferObject(gl)];
const plane = initVertexBuffersForPlane(gl);
tick();
}
let src = 0, dst = 1, t;
function render(gl, canvas, fbo, plane) {
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo[dst]);
gl.viewport(0, 0, 1, 1);
drawTexture(gl, gl.program, plane, fbo[src].texture);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, canvas.width, canvas.height);
drawTexture(gl, gl.program, plane, fbo[dst].texture);
t = src;
src = dst;
dst = t;
}
function drawTexture(gl, program, o, texture) {
gl.uniform3f(u_Mouse, ...position.xyz);
gl.uniform2f(u_Resolution, canvas.width, canvas.height);
initAttributeVariable(gl, a_Position, o.vertexBuffer);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, o.indexBuffer);
gl.drawElements(gl.TRIANGLES, o.numIndices, o.indexBuffer.type, 0);
}
function initAttributeVariable(gl, a_attribute, buffer) {
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.vertexAttribPointer(a_attribute, buffer.num, buffer.type, false, 0, 0);
gl.enableVertexAttribArray(a_attribute);
}
function initFramebufferObject(gl) {
const framebuffer = gl.createFramebuffer(), texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 0, 0, 255]));
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
framebuffer.texture = texture;
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.bindTexture(gl.TEXTURE_2D, null);
return framebuffer;
}
function initVertexBuffersForPlane(gl) {
const vertices = new Float32Array([1.0, 1.0, 0.0, -1.0, 1.0, 0.0, -1.0,-1.0, 0.0, 1.0,-1.0, 0.0]);
const texCoords = new Float32Array([1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0]);
const indices = new Uint8Array([0, 1, 2, 0, 2, 3]);
const o = {};
o.vertexBuffer = initArrayBufferForLaterUse(gl, vertices, 3, gl.FLOAT);
o.indexBuffer = initElementArrayBufferForLaterUse(gl, indices, gl.UNSIGNED_BYTE);
o.numIndices = indices.length;
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
return o;
}
function initArrayBufferForLaterUse(gl, data, num, type) {
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
buffer.num = num;
buffer.type = type;
return buffer;
}
function initElementArrayBufferForLaterUse(gl, data, type) {
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data, gl.STATIC_DRAW);
buffer.type = type;
return buffer;
}
function mouseHandlers() {
function getPosition(e) {
const x = e.clientX, y = window.innerHeight - e.clientY, z = 0.05;
position.xyz = [x, y, z];
}
function getRect() {
position.screnRect = canvas.getBoundingClientRect();
}
function mouseDown(e) {
position.mouseDown = true;
getPosition(e);
}
function move(e) {
if (position.mouseDown) getPosition(e);
else return;
}
function up() {
position.mouseDown = false;
}
getRect();
canvas.addEventListener('mousedown', mouseDown);
canvas.addEventListener('mousemove', move);
canvas.addEventListener('mouseup', up);
}
mouseHandlers();
main();
body {
margin: 0;
}
canvas {
width: 100vw;
height: 100vh;
display: block;
}
<canvas id="canvas"></canvas>
<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec4 a_position;
void main() {
gl_Position = a_position;
}
</script>
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
uniform sampler2D u_sampler;
uniform vec2 u_resolution;
uniform vec3 u_mouse;
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution;
gl_FragColor = texture2D(u_sampler, uv);
float dist = distance(u_mouse.xy, gl_FragCoord.xy);
gl_FragColor.rgb += u_mouse.z * max(15.0-dist,0.0);
//gl_FragColor.gb += 0.01; /* testing FBO */
}
</script>
<script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
So I have a result after moving the mouse but something is wrong:
It should be:
There is an obvious mistake when you create the texture objects for the frambuffer.
If you do not generate mipmaps (by gl.generateMipmap), then it is important to set gl.TEXTURE_MIN_FILTER. Since the default filter is gl.NEAREST_MIPMAP_LINEAR the texture would be mipmap incomplete, if you don't change the minifying function to gl.NEAREST or gl.LINEAR:
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
See further OpenGL ES 2.0 Full Specification - 3.7.10 Texture Completeness.
I recommend to check the completeness of the framebuffer:
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
// [...]
}
The size of the frame buffer textures has to be a power of 2 (WebGL 1.0). Create framebuffers with a fixed size (e.g. 1024x1024):
framebuffer.size = [1024, 1024];
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, ...framebuffer.size, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(tblack));
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
framebuffer.texture = texture;
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
alert("incomplete frambuffer");
}
Ensure that the uniforms are set correctly. Set the resolution (u_resolution) dependent on the size of the framebuffer. The position of the mouse (u_mouse) has to be relative to the size of the framebuffer:
function drawTexture(gl, program, o, texture, resolution) {
const mx = position.xyz[0] * resolution[0] / canvas.width;
const my = position.xyz[1] * resolution[1] / canvas.height;
gl.uniform3f(u_Mouse, mx, my, position.xyz[2]);
gl.uniform2f(u_Resolution, resolution[0], resolution[1]);
initAttributeVariable(gl, a_Position, o.vertexBuffer);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, o.indexBuffer);
gl.drawElements(gl.TRIANGLES, o.numIndices, o.indexBuffer.type, 0);
}
Set the viewport rectangle when you switch the current frame buffer
let src = 0, dst = 1, t;
function render(gl, canvas, fbo, plane) {
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo[dst]);
gl.viewport(0, 0, ...fbo[dst].size);
drawTexture(gl, gl.program, plane, fbo[src].texture, fbo[src].size);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, canvas.width, canvas.height);
drawTexture(gl, gl.program, plane, fbo[dst].texture, [canvas.width, canvas.height]);
t = src;
src = dst;
dst = t;
}
See the example, where the computation of the distance to the mouse is scaled, by the ration of the canvas resoultuion and the framebuffer:
let a_Position, u_Mouse, u_Sampler;
const position = {
screenRect: null,
xyz: [0.0, 0.0, 0.0],
mouseDown: false,
};
function main() {
const canvas = document.getElementById('canvas');
const gl = canvas.getContext('webgl');
canvas.width = canvas.clientWidth;
canvas.height = canvas.clientHeight;
const program = webglUtils.createProgramFromScripts(gl, ["2d-vertex-shader", "2d-fragment-shader"]);
gl.useProgram(program);
const tick = function() {
render(gl, canvas, fbo, plane);
window.requestAnimationFrame(tick, canvas);
};
a_Position = gl.getAttribLocation(program, 'a_position');
u_Mouse = gl.getUniformLocation(program, 'u_mouse');
u_Sampler = gl.getUniformLocation(program, 'u_sampler');
u_Resolution = gl.getUniformLocation(program, 'u_resolution');
u_CanvasSize = gl.getUniformLocation(program, 'u_canvasSize');
const fbo = [initFramebufferObject(gl), initFramebufferObject(gl)];
const plane = initVertexBuffersForPlane(gl);
tick();
}
let src = 0, dst = 1, t;
function render(gl, canvas, fbo, plane) {
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo[dst]);
gl.viewport(0, 0, ...fbo[dst].size);
drawTexture(gl, gl.program, plane, fbo[src].texture, fbo[src].size);
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, canvas.width, canvas.height);
drawTexture(gl, gl.program, plane, fbo[dst].texture, [canvas.width, canvas.height]);
t = src;
src = dst;
dst = t;
}
function drawTexture(gl, program, o, texture, resolution) {
const mx = position.xyz[0] * resolution[0] / canvas.width;
const my = position.xyz[1] * resolution[1] / canvas.height;
gl.uniform3f(u_Mouse, mx, my, position.xyz[2]);
gl.uniform2f(u_Resolution, resolution[0], resolution[1]);
gl.uniform2f(u_CanvasSize, canvas.width, canvas.height);
initAttributeVariable(gl, a_Position, o.vertexBuffer);
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, o.indexBuffer);
gl.drawElements(gl.TRIANGLES, o.numIndices, o.indexBuffer.type, 0);
}
function initAttributeVariable(gl, a_attribute, buffer) {
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.vertexAttribPointer(a_attribute, buffer.num, buffer.type, false, 0, 0);
gl.enableVertexAttribArray(a_attribute);
}
function initFramebufferObject(gl) {
let framebuffer = gl.createFramebuffer(), texture = gl.createTexture();
framebuffer.size = [1024, 1024];
let tblack = []
for (let i= 0; i < framebuffer.size[0]*framebuffer.size[1]; i ++) tblack.push(0, 0, 0, 255);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, ...framebuffer.size, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array(tblack));
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
framebuffer.texture = texture;
gl.bindFramebuffer(gl.FRAMEBUFFER, framebuffer);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture, 0);
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) != gl.FRAMEBUFFER_COMPLETE) {
alert("incomplete frambuffer");
}
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.bindTexture(gl.TEXTURE_2D, null);
return framebuffer;
}
function initVertexBuffersForPlane(gl) {
const vertices = new Float32Array([1.0, 1.0, 0.0, -1.0, 1.0, 0.0, -1.0,-1.0, 0.0, 1.0,-1.0, 0.0]);
const texCoords = new Float32Array([1.0, 1.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0]);
const indices = new Uint8Array([0, 1, 2, 0, 2, 3]);
const o = {};
o.vertexBuffer = initArrayBufferForLaterUse(gl, vertices, 3, gl.FLOAT);
o.indexBuffer = initElementArrayBufferForLaterUse(gl, indices, gl.UNSIGNED_BYTE);
o.numIndices = indices.length;
gl.bindBuffer(gl.ARRAY_BUFFER, null);
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
return o;
}
function initArrayBufferForLaterUse(gl, data, num, type) {
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.bufferData(gl.ARRAY_BUFFER, data, gl.STATIC_DRAW);
buffer.num = num;
buffer.type = type;
return buffer;
}
function initElementArrayBufferForLaterUse(gl, data, type) {
const buffer = gl.createBuffer();
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, buffer);
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data, gl.STATIC_DRAW);
buffer.type = type;
return buffer;
}
function mouseHandlers() {
function getPosition(e) {
const x = e.clientX, y = window.innerHeight - e.clientY, z = 0.05;
position.xyz = [x, y, z];
}
function getRect() {
position.screnRect = canvas.getBoundingClientRect();
}
function mouseDown(e) {
position.mouseDown = true;
getPosition(e);
}
function move(e) {
if (position.mouseDown) getPosition(e);
else return;
}
function up() {
position.mouseDown = false;
}
getRect();
canvas.addEventListener('mousedown', mouseDown);
canvas.addEventListener('mousemove', move);
canvas.addEventListener('mouseup', up);
}
mouseHandlers();
main();
<style>
body { margin: 0; }
canvas { width: 100vw; height: 100vh; display: block; }
</style>
<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec4 a_position;
void main() {
gl_Position = a_position;
}
</script>
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
uniform sampler2D u_sampler;
uniform vec2 u_resolution;
uniform vec2 u_canvasSize;
uniform vec3 u_mouse;
void main() {
vec2 uv = gl_FragCoord.xy / u_resolution.xy;
vec4 texColor = texture2D(u_sampler, uv);
vec2 scale = u_canvasSize / u_resolution;
float dist = distance(u_mouse.xy * scale, gl_FragCoord.xy * scale);
float intensity = u_mouse.z * max(15.0-dist,0.0);
gl_FragColor = texColor + vec4(vec3(intensity), 0.0);
}
</script>
<canvas id="canvas"></canvas>
<script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>

Can adding a WebGL readPixels call change the output of shaders?

Is it possible to create a sequence of WebGL commands that gives a different output if a spurious readPixels command (writing to an otherwise unused buffer) is inserted into the middle of the program?
Context
I've run into a situation where I'm applying a series of shaders in WebGL, and they sometimes compute the wrong thing. Isolating the bug has proved extremely difficult because it seems to depend on random details of the shaders and the presence of certain calls that should be redundant. As far as I can tell, I am not doing anything wrong or even particularly out of the ordinary. I suspect a GPU driver bug, with some sort of race condition since it is not consistent how many iterations it takes to catch a bad result, but I could be a lot more confident in that inference if I knew that behavior when a readPixels line was present should match the behavior when it is not present.
For reference, this is code that reproduces the bug on my desktop (Windows, NVidia GeForce RTX 2060, AMD Ryzen 7 2700X). It does not reproduce on other machines I own:
const gl = document.createElement('canvas').getContext('webgl');
const GL = WebGLRenderingContext;
{
gl.getExtension('OES_texture_float');
gl.getExtension('WEBGL_color_buffer_float');
let positionBuffer = gl.createBuffer();
let positions = new Float32Array([-1, +1, +1, +1, -1, -1, +1, -1]);
gl.bindBuffer(GL.ARRAY_BUFFER, positionBuffer);
gl.bufferData(GL.ARRAY_BUFFER, positions, GL.STATIC_DRAW);
let indexBuffer = gl.createBuffer();
let indices = new Uint16Array([0, 2, 1, 2, 3, 1]);
gl.bindBuffer(GL.ELEMENT_ARRAY_BUFFER, indexBuffer);
gl.bufferData(GL.ELEMENT_ARRAY_BUFFER, indices, GL.STATIC_DRAW);
gl.viewport(0, 0, 4, 2);
}
function shader(fragmentShaderSource) {
let glVertexShader = gl.createShader(GL.VERTEX_SHADER);
gl.shaderSource(glVertexShader, `
precision highp float;
precision highp int;
attribute vec2 position;
void main() {
gl_Position = vec4(position, 0, 1);
}`);
gl.compileShader(glVertexShader);
let glFragmentShader = gl.createShader(GL.FRAGMENT_SHADER);
gl.shaderSource(glFragmentShader, `
precision highp float;
precision highp int;
${fragmentShaderSource}`);
gl.compileShader(glFragmentShader);
let program = gl.createProgram();
gl.attachShader(program, glVertexShader);
gl.attachShader(program, glFragmentShader);
gl.linkProgram(program);
gl.deleteShader(glVertexShader);
gl.deleteShader(glFragmentShader);
return program;
}
function tex() {
let texture = gl.createTexture();
let framebuffer = gl.createFramebuffer();
gl.bindTexture(GL.TEXTURE_2D, texture);
gl.bindFramebuffer(GL.FRAMEBUFFER, framebuffer);
gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MAG_FILTER, GL.NEAREST);
gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_MIN_FILTER, GL.NEAREST);
gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_S, GL.CLAMP_TO_EDGE);
gl.texParameteri(GL.TEXTURE_2D, GL.TEXTURE_WRAP_T, GL.CLAMP_TO_EDGE);
gl.texImage2D(GL.TEXTURE_2D, 0, GL.RGBA, 4, 2, 0, GL.RGBA, GL.FLOAT, null);
gl.framebufferTexture2D(GL.FRAMEBUFFER, GL.COLOR_ATTACHMENT0, GL.TEXTURE_2D, texture, 0);
return {texture, framebuffer};
}
let shader_less_than = shader(`
uniform float lim;
void main() {
gl_FragColor = vec4(float(gl_FragCoord.y < lim), 0.0, 0.0, 0.0);
}`);
let shader_zero = shader(`
void main() {
gl_FragColor = vec4(0.0, 0.0, 0.0, 0.0);
}`);
let shader_tricky = shader(`
uniform sampler2D tex_unused_dep;
uniform sampler2D tex_c;
void main() {
float c = texture2D(tex_c, gl_FragCoord.xy / vec2(4.0, 2.0)).x;
vec2 unused = texture2D(tex_unused_dep, vec2(0.0, c)).xy; // Without this line, test passes.
gl_FragColor = vec4(0.0, c, 0.0, 0.0);
}`);
let tex_unrelated = tex();
let tex_lim = tex();
let tex_unused_dep = tex();
let tex_out = tex();
let out_buf = new Float32Array(32);
for (let k = 0; k < 1000; k++) {
let flag = k % 2 === 0;
gl.useProgram(shader_zero);
gl.bindFramebuffer(GL.FRAMEBUFFER, tex_unused_dep.framebuffer);
gl.drawElements(GL.TRIANGLES, 6, GL.UNSIGNED_SHORT, 0);
gl.useProgram(shader_less_than);
gl.uniform1f(gl.getUniformLocation(shader_less_than, 'lim'), flag ? 1 : 2);
gl.bindFramebuffer(GL.FRAMEBUFFER, tex_unrelated.framebuffer);
gl.drawElements(GL.TRIANGLES, 6, GL.UNSIGNED_SHORT, 0);
gl.uniform1f(gl.getUniformLocation(shader_less_than, 'lim'), flag ? 1 : 2); // Commenting this line makes a pass more likely, but not guaranteed.
gl.bindFramebuffer(GL.FRAMEBUFFER, tex_lim.framebuffer);
gl.drawElements(GL.TRIANGLES, 6, GL.UNSIGNED_SHORT, 0);
// gl.readPixels(0, 0, 4, 2, GL.RGBA, GL.FLOAT, out_buf); // Uncommenting this line seems to guarantee a pass.
gl.useProgram(shader_tricky);
gl.uniform1i(gl.getUniformLocation(shader_tricky, 'tex_unused_dep'), 0);
gl.uniform1i(gl.getUniformLocation(shader_tricky, 'tex_c'), 1);
gl.activeTexture(GL.TEXTURE0 + 0);
gl.bindTexture(GL.TEXTURE_2D, tex_unused_dep.texture);
gl.activeTexture(GL.TEXTURE0 + 1);
gl.bindTexture(GL.TEXTURE_2D, tex_lim.texture);
gl.enableVertexAttribArray(gl.getAttribLocation(shader_tricky, 'position'));
gl.vertexAttribPointer(gl.getAttribLocation(shader_tricky, 'position'), 2, GL.FLOAT, false, 0, 0);
gl.bindFramebuffer(GL.FRAMEBUFFER, tex_out.framebuffer);
gl.drawElements(GL.TRIANGLES, 6, GL.UNSIGNED_SHORT, 0);
gl.readPixels(0, 0, 4, 2, GL.RGBA, GL.FLOAT, out_buf);
if (out_buf[17] !== (flag ? 0 : 1)) {
throw new Error("Bad output.")
}
}
console.log("PASS");

WebGL render to texture with framebuffer

I am new in webgl and trying to learn it.
I am trying to learn framebuffer and rendering to texture but I am stuck.
What I am trying to do is to copy the colors (pixel data) of a texture to another texture by using framebuffer. I have some other things in mind like doing some calculations before rendering to texture etc. but will do that afterwards.
I have created two 2x2 textures, put some random colors in one of them and bind other to the framebuffer. But I am not getting expected output.
Vertex shader
precision mediump float;
attribute vec2 a_texcoord;
varying vec2 v_texcoord;
void main() {
// Convert texture coordinate to render coordinate
gl_Position = vec4(2.0 * a_texcoord.x - 1.0, 1.0 - 2.0 * a_texcoord.y, 0, 1);
gl_PointSize = 1.0;
v_texcoord = a_texcoord;
}
Fragment shader
precision mediump float;
uniform sampler2D u_texture;
varying vec2 v_texcoord;
void main() {
vec4 data = texture2D(u_texture, v_texcoord);
gl_FragColor = data;
}
Javascript
var canvas = document.getElementById("canvas");
// WebGL context
var gl = canvas.getContext("webgl") || canvas.getContext("experimental-webgl");
if (!gl) {
console.log("WebGL not supported");
}
// Set canvas dimensions
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// Create program with shaders
var program = glUtils.createProgram(gl, 'vshader', 'fshader');
// Texture dimensions
var textureWidth = 2,
textureHeight = 2;
// Texture coordinates
var coords = [];
for (var i = 0; i < textureWidth; ++i) {
for (var j = 0; j < textureHeight; ++j) {
coords.push(i / textureWidth, j / textureHeight);
}
}
// Random colors for texture
var d = [];
for (var i = 0; i < textureWidth * textureHeight; ++i) {
d.push(
Math.floor(Math.random() * 256),
Math.floor(Math.random() * 256),
Math.floor(Math.random() * 256),
Math.floor(Math.random() * 256)
);
}
// Texture with random colors
var data = new Uint8Array(d);
var texture0 = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture0);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, textureWidth, textureHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, data);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.bindTexture(gl.TEXTURE_2D, null);
// Texture to render to
var texture1 = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture1);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, textureWidth, textureHeight, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.bindTexture(gl.TEXTURE_2D, null);
// Framebuffer
var fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture1, 0);
// Program
gl.useProgram(program);
// Bind texture0
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture0);
gl.uniform1i(program.uniforms.u_texture, 0);
// Bind framebuffer
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, texture1, 0);
// Set coordinate array
sendCoords(program);
// Set WebGL viewport
setupViewport(gl, textureWidth, textureHeight);
// Clear
gl.clearColor(0, 0, 0, 1);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
// Draw
gl.drawArrays(gl.POINTS, 0, textureWidth * textureHeight);
// Read pixels
var pixels = new Uint8Array(textureWidth * textureHeight * 4);
gl.readPixels(0, 0, textureWidth, textureHeight, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
gl.deleteFramebuffer(fb);
console.log(pixels);
// Enable and bind data to attribute for the texture coordinates
function sendCoords(program) {
var coordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, coordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(coords), gl.STATIC_DRAW);
gl.vertexAttribPointer(
program.attribs.a_texcoord,
2,
gl.FLOAT,
gl.FALSE,
0,
0
);
gl.enableVertexAttribArray(program.attribs.a_texcoord);
}
// Viewport setup
function setupViewport(gl, width, height) {
gl.viewport(0, 0, width, height);
}
I am expecting exact same data that I have in texture0 but not getting that in gl.readPixels(). Maybe I am doing wrong calculations.
Update:
Here is sample data to make my problem clear.
// Texture coordinates
[0, 0,
0, 0.5,
0.5, 0,
0.5, 0.5]
// Random colors to copy from
[197, 103, 13, 0,
199, 17, 0, 18,
61, 177, 5, 14,
15, 72, 18, 10]
// Data getting by gl.readPixels()
[61, 177, 5, 14,
15, 72, 18, 10,
197, 103, 13, 0,
199, 17, 0, 18]
As you can see, the order is not same. I would like to know why.
Any help would be much appreciated. Thanks.

How to use the OES_texture_float extension? and create a texture as a floating point one for that?

How to use the OES_texture_float extension?
and create a texture as a floating point one for that?
As webGL 1 extension lists:
var ext = gl.getExtension("OES_texture_float");
var linear = gl.getExtension("OES_texture_float_linear");
gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.FLOAT, image);
How to make a float-point image as a texture input?
First off you're not actually checking you got the extension
Your code should be something like
var ext = gl.getExtension("OES_texture_float");
if (!ext) {
alert("this machine or browser does not support OES_texture_float");
}
var linear = gl.getExtension("OES_texture_float_linear");
if (!linear) {
alert("this machine or browser does not support OES_texture_float_linear");
}
Otherwise you didn't show enough code to see what else might be wrong. Have you read any webgl tutorials? Where are you creating and binding the texture? What do your shaders look like? What kind of attributes are you using if any?
function main() {
var gl = document.querySelector("canvas").getContext("webgl");
var ext = gl.getExtension("OES_texture_float");
if (!ext) {
alert("this machine or browser does not support OES_texture_float");
return;
}
var linear = gl.getExtension("OES_texture_float_linear");
if (!linear) {
alert("this machine or browser does not support OES_texture_float_linear");
return;
}
var vs = `
void main() {
gl_PointSize = 100.0;
gl_Position = vec4(0, 0, 0, 1);
}
`;
var fs = `
precision mediump float;
uniform sampler2D u_tex;
void main () {
gl_FragColor = texture2D(u_tex, gl_PointCoord);
}
`;
var program = twgl.createProgramFromSources(gl, [vs, fs]);
// let's use a canvas instead of an image. It should be the same
var image = document.createElement("canvas");
var ctx = image.getContext("2d");
for (var i = 20; i > 0; --i) {
ctx.fillStyle = i % 2 ? "red" : "yellow";
ctx.beginPath();
ctx.arc(ctx.canvas.width / 2, ctx.canvas.height / 2, i * 20, 0, Math.PI * 2, false);
ctx.fill();
}
var tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.FLOAT, image);
gl.useProgram(program);
gl.drawArrays(gl.POINTS, 0, 1);
}
main();
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/twgl.min.js"></script>
<canvas></canvas>
Also it's not clear what you mean by "create a texture as a floating point one for that". If the features are supported then uploading the image it will get converted to floating point (which we see in the example above) but the input image is an 8bit image at best. If you really want floating point data you'll have to use binary data rather than an image.
function main() {
var gl = document.querySelector("canvas").getContext("webgl");
var ext = gl.getExtension("OES_texture_float");
if (!ext) {
alert("this machine or browser does not support OES_texture_float");
return;
}
var linear = gl.getExtension("OES_texture_float_linear");
if (!linear) {
alert("this machine or browser does not support OES_texture_float_linear");
return;
}
var vs = `
void main() {
gl_PointSize = 100.0;
gl_Position = vec4(0, 0, 0, 1);
}
`;
var fs = `
precision mediump float;
uniform sampler2D u_tex;
void main () {
gl_FragColor = texture2D(u_tex, gl_PointCoord) / vec4(32, 16, 32 + 16, 1);
}
`;
var program = twgl.createProgramFromSources(gl, [vs, fs]);
// create floating point data directly
var width = 32;
var height = 16;
var data = new Float32Array(width * height * 4); // RGBA
for (y = 0; y < height; ++y) {
for (x = 0; x < width; ++x) {
var off = (y * width + x) * 4;
data[off + 0] = x;
data[off + 1] = y;
data[off + 2] = x + y;
data[off + 3] = 1;
}
}
var tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameterf(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texImage2D(
gl.TEXTURE_2D, 0, gl.RGBA, width, height, 0, gl.RGBA, gl.FLOAT, data);
gl.useProgram(program);
gl.drawArrays(gl.POINTS, 0, 1);
}
main();
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/twgl.min.js"></script>
<canvas></canvas>

Resources