WebGL render to texture with framebuffer - webgl

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.

Related

RENDER WARNING: texture bound to texture unit 0 is not renderable. It might be non-power-of-2 or have incompatible texture filtering (maybe)?

I'm trying to render the 2d image in WebGL with no animation. I'm getting the issue. Since i have passed the proper image data. Here is the code.What am i missing? Or how to render the text or image.
Without matrix can't we render the image or text?
When i say image or text it means it is purely 2d not 3d.
const vsSource = `
attribute vec4 a_position;
attribute vec2 a_texcoord;
uniform mat4 u_matrix;
varying vec2 v_texcoord;
void main() {
gl_Position = u_matrix * a_position;
v_texcoord = a_texcoord;
}
`;
// Fragment shader program
const fsSource = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D u_texture;
void main() {
gl_FragColor = texture2D(u_texture, v_texcoord);
}
`;
function loadTexture(gl, url) {
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
var textureInfo = {
width: 1, // we don't know the size until it loads
height: 1,
texture: texture,
};
var img = new Image();
img.src = url;
//img.crossOrigin="*"
img.addEventListener('load', function() {
textureInfo.width = img.width;
textureInfo.height = img.height;
gl.bindTexture(gl.TEXTURE_2D, textureInfo.texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_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);
});
return textureInfo;
}
drawImage(texture);
function drawImage(tex) {
// gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.clearColor(1, 1, 0.0, 1.0); // Clear to black, fully opaque
// gl.clearDepth(1.0); // Clear everything
// gl.enable(gl.DEPTH_TEST); // Enable depth testing
// gl.depthFunc(gl.LEQUAL);
var positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Put a unit quad in the buffer
var positions = [
0, 0,
0, 1,
1, 0,
1, 0,
0, 1,
1, 1,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
var texcoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
// Put texcoords in the buffer
var texcoords = [
0, 0,
0, 1,
1, 0,
1, 0,
0, 1,
1, 1,
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(texcoords), gl.STATIC_DRAW);
gl.bindTexture(gl.TEXTURE_2D, tex.texture);
// Tell WebGL to use our shader program pair
gl.useProgram(programInfo.program);
// Setup the attributes to pull data from our buffers
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.enableVertexAttribArray(programInfo.attribLocations.vertexPosition);
gl.vertexAttribPointer(programInfo.attribLocations.vertexPosition, 2, gl.FLOAT, false, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
gl.enableVertexAttribArray(programInfo.attribLocations.textureCoord);
gl.vertexAttribPointer(programInfo.attribLocations.textureCoord, 2, gl.FLOAT, false, 0, 0);
gl.uniform1i(programInfo.uniformLocations.textureLocation, 0);
>
Here getting issue
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
Your code never creates a texture. It never calls loadImage. If it did call loadImage the code doesn't draw after the image has loaded.
I'd suggest you call loadImage and inside the load handler you call drawImage.
I'd also suggest you initialize the texture with a single pixel so it's usable immediately if you happen to want to draw before the image has loaded. That is what this article does

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");

How to use packed pixel array in WebGL Texture? [duplicate]

Currently, I'm using 2D canvas context to draw an image generated (from pixel to pixel, but refreshed as a whole buffer in once after a generated frame) from JavaScript at about a 25fps rate. The generated image is always one byte (integer / typed array) per pixel and a fixed palette is used to generate RGB final result. Scaling is also needed to adopt to the size of the canvas (ie: going to fullscreen) and/or at user request (zoom in/out buttons).
The 2D context of canvas is OK for this purpose, however I'm curious if WebGL can provide better result and/or better performance. Please note: I don't want to put pixels via webGL, I want to put pixels into my buffer (which is basically Uint8Array), and use that buffer (in once) to refresh the context. I don't know too much about WebGL, but using the needed generated image as some kind of texture would work somehow for example? Then I would need to refresh the texture at about 25fps rate, I guess.
It would be really fantastic, if WebGL support the colour space conversion somehow. With 2D context, I need to convert 1 byte / pixel buffer into RGBA for the imagedata in JavaScript for every pixel ... Scaling (for 2D context) is done now by altering the height/width style of the canvas, so browsers scales the image then. However I guess it can be slower than what WebGL can do with hw support, and also (I hope) WebGL can give greater flexibility to control the scaling, eg with the 2D context, browsers will do antialiasing even if I don't want to do (eg: integer zooming factor), and maybe that's a reason it can be quite slow sometimes.
I've already tried to learn several WebGL tutorials but all of them starts with objects, shapes, 3D cubes, etc, I don't need any - classical - object to render only what 2D context can do as well - in the hope that WebGL can be a faster solution for the very same task! Of course if there is no win here with WebGL at all, I would continue to use 2D context.
To be clear: this is some kind of computer hardware emulator done in JavaScript, and its output (what would be seen on a PAL TV connected to it) is rendered via a canvas context. The machine has fixed palette with 256 elements, internally it only needs one byte for a pixel to define its colour.
You can use a texture as your palette and a different texture as your image. You then get a value from the image texture and use it too look up a color from the palette texture.
The palette texture is 256x1 RGBA pixels. Your image texture is any size you want but just a single channel ALPHA texture. You can then look up a value from the image
float index = texture2D(u_image, v_texcoord).a * 255.0;
And use that value to look up a color in the palette
gl_FragColor = texture2D(u_palette, vec2((index + 0.5) / 256.0, 0.5));
Your shaders might be something like this
Vertex Shader
attribute vec4 a_position;
varying vec2 v_texcoord;
void main() {
gl_Position = a_position;
// assuming a unit quad for position we
// can just use that for texcoords. Flip Y though so we get the top at 0
v_texcoord = a_position.xy * vec2(0.5, -0.5) + 0.5;
}
Fragment shader
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D u_image;
uniform sampler2D u_palette;
void main() {
float index = texture2D(u_image, v_texcoord).a * 255.0;
gl_FragColor = texture2D(u_palette, vec2((index + 0.5) / 256.0, 0.5));
}
Then you just need a palette texture.
// Setup a palette.
var palette = new Uint8Array(256 * 4);
// I'm lazy so just setting 4 colors in palette
function setPalette(index, r, g, b, a) {
palette[index * 4 + 0] = r;
palette[index * 4 + 1] = g;
palette[index * 4 + 2] = b;
palette[index * 4 + 3] = a;
}
setPalette(1, 255, 0, 0, 255); // red
setPalette(2, 0, 255, 0, 255); // green
setPalette(3, 0, 0, 255, 255); // blue
// upload palette
...
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 256, 1, 0, gl.RGBA,
gl.UNSIGNED_BYTE, palette);
And your image. It's an alpha only image so just 1 channel.
// Make image. Just going to make something 8x8
var image = new Uint8Array([
0,0,1,1,1,1,0,0,
0,1,0,0,0,0,1,0,
1,0,0,0,0,0,0,1,
1,0,2,0,0,2,0,1,
1,0,0,0,0,0,0,1,
1,0,3,3,3,3,0,1,
0,1,0,0,0,0,1,0,
0,0,1,1,1,1,0,0,
]);
// upload image
....
gl.texImage2D(gl.TEXTURE_2D, 0, gl.ALPHA, 8, 8, 0, gl.ALPHA,
gl.UNSIGNED_BYTE, image);
You also need to make sure both textures are using gl.NEAREST for filtering since one represents indices and the other a palette and filtering between values in those cases makes no sense.
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
Here's a working example:
var canvas = document.getElementById("c");
var gl = canvas.getContext("webgl");
// Note: createProgramFromScripts will call bindAttribLocation
// based on the index of the attibute names we pass to it.
var program = twgl.createProgramFromScripts(
gl,
["vshader", "fshader"],
["a_position", "a_textureIndex"]);
gl.useProgram(program);
var imageLoc = gl.getUniformLocation(program, "u_image");
var paletteLoc = gl.getUniformLocation(program, "u_palette");
// tell it to use texture units 0 and 1 for the image and palette
gl.uniform1i(imageLoc, 0);
gl.uniform1i(paletteLoc, 1);
// Setup a unit quad
var positions = [
1, 1,
-1, 1,
-1, -1,
1, 1,
-1, -1,
1, -1,
];
var vertBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
// Setup a palette.
var palette = new Uint8Array(256 * 4);
// I'm lazy so just setting 4 colors in palette
function setPalette(index, r, g, b, a) {
palette[index * 4 + 0] = r;
palette[index * 4 + 1] = g;
palette[index * 4 + 2] = b;
palette[index * 4 + 3] = a;
}
setPalette(1, 255, 0, 0, 255); // red
setPalette(2, 0, 255, 0, 255); // green
setPalette(3, 0, 0, 255, 255); // blue
// make palette texture and upload palette
gl.activeTexture(gl.TEXTURE1);
var paletteTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, paletteTex);
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);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 256, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, palette);
// Make image. Just going to make something 8x8
var image = new Uint8Array([
0,0,1,1,1,1,0,0,
0,1,0,0,0,0,1,0,
1,0,0,0,0,0,0,1,
1,0,2,0,0,2,0,1,
1,0,0,0,0,0,0,1,
1,0,3,3,3,3,0,1,
0,1,0,0,0,0,1,0,
0,0,1,1,1,1,0,0,
]);
// make image textures and upload image
gl.activeTexture(gl.TEXTURE0);
var imageTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, imageTex);
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);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.ALPHA, 8, 8, 0, gl.ALPHA, gl.UNSIGNED_BYTE, image);
gl.drawArrays(gl.TRIANGLES, 0, positions.length / 2);
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/twgl.min.js"></script>
<script id="vshader" type="whatever">
attribute vec4 a_position;
varying vec2 v_texcoord;
void main() {
gl_Position = a_position;
// assuming a unit quad for position we
// can just use that for texcoords. Flip Y though so we get the top at 0
v_texcoord = a_position.xy * vec2(0.5, -0.5) + 0.5;
}
</script>
<script id="fshader" type="whatever">
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D u_image;
uniform sampler2D u_palette;
void main() {
float index = texture2D(u_image, v_texcoord).a * 255.0;
gl_FragColor = texture2D(u_palette, vec2((index + 0.5) / 256.0, 0.5));
}
</script>
<canvas id="c" width="256" height="256"></canvas>
To animate just update the image and then re-upload it into the texture
gl.texImage2D(gl.TEXTURE_2D, 0, gl.ALPHA, 8, 8, 0, gl.ALPHA,
gl.UNSIGNED_BYTE, image);
Example:
var canvas = document.getElementById("c");
var gl = canvas.getContext("webgl");
// Note: createProgramFromScripts will call bindAttribLocation
// based on the index of the attibute names we pass to it.
var program = twgl.createProgramFromScripts(
gl,
["vshader", "fshader"],
["a_position", "a_textureIndex"]);
gl.useProgram(program);
var imageLoc = gl.getUniformLocation(program, "u_image");
var paletteLoc = gl.getUniformLocation(program, "u_palette");
// tell it to use texture units 0 and 1 for the image and palette
gl.uniform1i(imageLoc, 0);
gl.uniform1i(paletteLoc, 1);
// Setup a unit quad
var positions = [
1, 1,
-1, 1,
-1, -1,
1, 1,
-1, -1,
1, -1,
];
var vertBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
// Setup a palette.
var palette = new Uint8Array(256 * 4);
// I'm lazy so just setting 4 colors in palette
function setPalette(index, r, g, b, a) {
palette[index * 4 + 0] = r;
palette[index * 4 + 1] = g;
palette[index * 4 + 2] = b;
palette[index * 4 + 3] = a;
}
setPalette(1, 255, 0, 0, 255); // red
setPalette(2, 0, 255, 0, 255); // green
setPalette(3, 0, 0, 255, 255); // blue
// make palette texture and upload palette
gl.activeTexture(gl.TEXTURE1);
var paletteTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, paletteTex);
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);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 256, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, palette);
// Make image. Just going to make something 8x8
var width = 8;
var height = 8;
var image = new Uint8Array([
0,0,1,1,1,1,0,0,
0,1,0,0,0,0,1,0,
1,0,0,0,0,0,0,1,
1,0,2,0,0,2,0,1,
1,0,0,0,0,0,0,1,
1,0,3,3,3,3,0,1,
0,1,0,0,0,0,1,0,
0,0,1,1,1,1,0,0,
]);
// make image textures and upload image
gl.activeTexture(gl.TEXTURE0);
var imageTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, imageTex);
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);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.ALPHA, width, height, 0, gl.ALPHA, gl.UNSIGNED_BYTE, image);
var frameCounter = 0;
function render() {
++frameCounter;
// skip 3 of 4 frames so the animation is not too fast
if ((frameCounter & 3) == 0) {
// rotate the image left
for (var y = 0; y < height; ++y) {
var temp = image[y * width];
for (var x = 0; x < width - 1; ++x) {
image[y * width + x] = image[y * width + x + 1];
}
image[y * width + width - 1] = temp;
}
// re-upload image
gl.activeTexture(gl.TEXTURE0);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.ALPHA, width, height, 0, gl.ALPHA,
gl.UNSIGNED_BYTE, image);
gl.drawArrays(gl.TRIANGLES, 0, positions.length / 2);
}
requestAnimationFrame(render);
}
render();
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/twgl.min.js"></script>
<script id="vshader" type="whatever">
attribute vec4 a_position;
varying vec2 v_texcoord;
void main() {
gl_Position = a_position;
// assuming a unit quad for position we
// can just use that for texcoords. Flip Y though so we get the top at 0
v_texcoord = a_position.xy * vec2(0.5, -0.5) + 0.5;
}
</script>
<script id="fshader" type="whatever">
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D u_image;
uniform sampler2D u_palette;
void main() {
float index = texture2D(u_image, v_texcoord).a * 255.0;
gl_FragColor = texture2D(u_palette, vec2((index + 0.5) / 256.0, 0.5));
}
</script>
<canvas id="c" width="256" height="256"></canvas>
Of course that assumes your goal is to do the animation on the CPU by manipulating pixels. Otherwise you can use any normal webgl techniques to manipulate texture coordinates or whatever.
You can also update the palette similarly for palette animation. Just modify the palette and re-upload it
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 256, 1, 0, gl.RGBA,
gl.UNSIGNED_BYTE, palette);
Example:
var canvas = document.getElementById("c");
var gl = canvas.getContext("webgl");
// Note: createProgramFromScripts will call bindAttribLocation
// based on the index of the attibute names we pass to it.
var program = twgl.createProgramFromScripts(
gl,
["vshader", "fshader"],
["a_position", "a_textureIndex"]);
gl.useProgram(program);
var imageLoc = gl.getUniformLocation(program, "u_image");
var paletteLoc = gl.getUniformLocation(program, "u_palette");
// tell it to use texture units 0 and 1 for the image and palette
gl.uniform1i(imageLoc, 0);
gl.uniform1i(paletteLoc, 1);
// Setup a unit quad
var positions = [
1, 1,
-1, 1,
-1, -1,
1, 1,
-1, -1,
1, -1,
];
var vertBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
gl.enableVertexAttribArray(0);
gl.vertexAttribPointer(0, 2, gl.FLOAT, false, 0, 0);
// Setup a palette.
var palette = new Uint8Array(256 * 4);
// I'm lazy so just setting 4 colors in palette
function setPalette(index, r, g, b, a) {
palette[index * 4 + 0] = r;
palette[index * 4 + 1] = g;
palette[index * 4 + 2] = b;
palette[index * 4 + 3] = a;
}
setPalette(1, 255, 0, 0, 255); // red
setPalette(2, 0, 255, 0, 255); // green
setPalette(3, 0, 0, 255, 255); // blue
// make palette texture and upload palette
gl.activeTexture(gl.TEXTURE1);
var paletteTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, paletteTex);
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);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 256, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, palette);
// Make image. Just going to make something 8x8
var width = 8;
var height = 8;
var image = new Uint8Array([
0,0,1,1,1,1,0,0,
0,1,0,0,0,0,1,0,
1,0,0,0,0,0,0,1,
1,0,2,0,0,2,0,1,
1,0,0,0,0,0,0,1,
1,0,3,3,3,3,0,1,
0,1,0,0,0,0,1,0,
0,0,1,1,1,1,0,0,
]);
// make image textures and upload image
gl.activeTexture(gl.TEXTURE0);
var imageTex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, imageTex);
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);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.ALPHA, width, height, 0, gl.ALPHA, gl.UNSIGNED_BYTE, image);
var frameCounter = 0;
function render() {
++frameCounter;
// skip 3 of 4 frames so the animation is not too fast
if ((frameCounter & 3) == 0) {
// rotate the 3 palette colors
var tempR = palette[4 + 0];
var tempG = palette[4 + 1];
var tempB = palette[4 + 2];
var tempA = palette[4 + 3];
setPalette(1, palette[2 * 4 + 0], palette[2 * 4 + 1], palette[2 * 4 + 2], palette[2 * 4 + 3]);
setPalette(2, palette[3 * 4 + 0], palette[3 * 4 + 1], palette[3 * 4 + 2], palette[3 * 4 + 3]);
setPalette(3, tempR, tempG, tempB, tempA);
// re-upload palette
gl.activeTexture(gl.TEXTURE1);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 256, 1, 0, gl.RGBA,
gl.UNSIGNED_BYTE, palette);
gl.drawArrays(gl.TRIANGLES, 0, positions.length / 2);
}
requestAnimationFrame(render);
}
render();
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/twgl.min.js"></script>
<script id="vshader" type="whatever">
attribute vec4 a_position;
varying vec2 v_texcoord;
void main() {
gl_Position = a_position;
// assuming a unit quad for position we
// can just use that for texcoords. Flip Y though so we get the top at 0
v_texcoord = a_position.xy * vec2(0.5, -0.5) + 0.5;
}
</script>
<script id="fshader" type="whatever">
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D u_image;
uniform sampler2D u_palette;
void main() {
float index = texture2D(u_image, v_texcoord).a * 255.0;
gl_FragColor = texture2D(u_palette, vec2((index + 0.5) / 256.0, 0.5));
}
</script>
<canvas id="c" width="256" height="256"></canvas>
Slightly related is this tile shader example
http://blog.tojicode.com/2012/07/sprite-tile-maps-on-gpu.html
Presumably you're building up a javascript array that's around 512 x 512 (PAL size)...
A WebGL fragment shader could definitely do your palette conversion pretty nicely. The recipe would go something like this:
Set up WebGL with a "geometry" of just two triangles that span your viewport. (GL is all triangles.) This is the biggest bother, if you're not already GL fluent. But it's not that bad. Spend some quality time with http://learningwebgl.com/blog/?page_id=1217 . But it will be ~100 lines of stuff. Price of admission.
Build your in-memory frame buffer 4 times bigger. (I think textures always have to be RGBA?) And populate every fourth byte, the R component, with your pixel values. Use new Float32Array to allocate it. You can use values 0-255, or divide it down to 0.0 to 1.0. We'll pass this to webgl as a texture. This one changes every frame.
Build a second texture that's 256 x 1 pixels, which is your palette lookup table. This one never changes (unless the palette can be modified?).
In your fragment shader, use your emulated frame buffer texture as a lookup into your palette. The first pixel in the palette is accessed at location (0.5/256.0, 0.5), middle of the pixel.
On each frame, resubmit the emulated frame buffer texture and redraw. Pushing pixels to the GPU is expensive... but a PAL-sized image is pretty small by modern standards.
Bonus step: You could enhance the fragment shader to imitate scanlines, interlace video, or other cute emulation artifacts (phosphor dots?) on modern high-resolution displays, all at no cost to your javascript!
This is just a sketch. But it will work. WebGL is a pretty low-level API, and quite flexible, but well worth the effort (if you like that kind of thing, which I do. :-) ).
Again, http://learningwebgl.com/blog/?page_id=1217 is well-recommended for overall WebGL guidance.

Unable to read pixels from a grayscale heightmap with 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>

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