If I use webgl2 to render something (e.g. a triangle) to an RGBA UNSIGNED_BYTE texture, then render that texture to the canvas, everything works fine. But I'm having trouble getting my example to work when rendering a RGBA32F FLOAT texture to the canvas. Instead I just get a solid black box, but no error message. Any ideas how to get the float example working?
Not working float texture jsfiddle
This example is with an int texture, which works as expected (outputs purple triangle with white background as the texture, which is overlayed on a larger grey background)
"use strict";
function main() {
// Get the WebGL context
const canvas = document.querySelector("#canvas");
const gl = canvas.getContext("webgl2");
const textureWidth = 100;
const textureHeight = 100;
// Render purple triangle to texture
{
const vertexShaderSource = `#version 300 es
in vec4 a_position;
void main() {
gl_Position = a_position;
}
`;
const fragmentShaderSource = `#version 300 es
precision highp float;
out vec4 outColor;
void main() {
outColor = vec4(1,0,1, 1);
}
`;
// create/compile shaders
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
const program = createProgram(gl, vertexShader, fragmentShader);
// set a_position attribute
const positionAttributeLocation = gl.getAttribLocation(program, "a_position");
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
const positions = [
-1, -1,
0, 1,
1, 0
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
gl.enableVertexAttribArray(positionAttributeLocation);
// Tell the attribute how to get data out of positionBuffer
const size = 2; // 2 components per iteration
let type = gl.FLOAT; // the data is 32bit floats
const normalize = false; // don't normalize the data
const stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
let offset = 0; // start at the beginning of the buffer
gl.vertexAttribPointer(
positionAttributeLocation, size, type, normalize, stride, offset);
gl.useProgram(program);
// set up the target texture
const targetTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, targetTexture);
const level = 0;
const internalFormat = gl.RGBA;
const border = 0;
const format = gl.RGBA;
type = gl.UNSIGNED_BYTE;
const data = null;
gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
textureWidth, textureHeight, border,
format, type, data);
// set the filtering so we don't need mips
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);
// Create and bind the framebuffer
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
// attach the texture as the first color attachment
const attachmentPoint = gl.COLOR_ATTACHMENT0;
gl.framebufferTexture2D(
gl.FRAMEBUFFER, attachmentPoint, gl.TEXTURE_2D, targetTexture, level);
gl.viewport(0, 0, textureWidth, textureHeight);
// Clear the canvas
gl.clearColor(1,1,1,1);
gl.clear(gl.COLOR_BUFFER_BIT);
const primitiveType = gl.TRIANGLES;
offset = 0;
const count = 3;
gl.drawArrays(primitiveType, offset, count);
}
// Now draw the texture to the canvas
{
const vertexShaderSource = `#version 300 es
in vec2 a_position;
in vec2 a_texCoord;
out vec2 v_texCoord;
void main() {
gl_Position = vec4(a_position, 0, 1);
v_texCoord = a_texCoord;
}
`;
const fragmentShaderSource = `#version 300 es
precision highp float;
uniform sampler2D u_image;
in vec2 v_texCoord;
out vec4 outColor;
void main() {
outColor = texture(u_image , v_texCoord);
}
`;
// render to the canvas
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
const program = webglUtils.createProgramFromSources(gl,
[vertexShaderSource, fragmentShaderSource]);
const positionAttributeLocation = gl.getAttribLocation(program, "a_position");
const texCoordAttributeLocation = gl.getAttribLocation(program, "a_texCoord");
// lookup uniforms
const imageLocation = gl.getUniformLocation(program, "u_image");
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
const positionBuffer = gl.createBuffer();
gl.enableVertexAttribArray(positionAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
let size = 2; // 2 components per iteration
let type = gl.FLOAT; // the data is 32bit floats
let normalize = false; // don't normalize the data
let stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
let offset = 0; // start at the beginning of the buffer
gl.vertexAttribPointer(
positionAttributeLocation, size, type, normalize, stride, offset);
const texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0, 0,
0, 1,
1, 0,
1, 0,
1, 1,
0, 1
]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(texCoordAttributeLocation);
size = 2; // 2 components per iteration
type = gl.FLOAT; // the data is 32bit floats
normalize = false; // don't normalize the data
stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
offset = 0; // start at the beginning of the buffer
gl.vertexAttribPointer(
texCoordAttributeLocation, size, type, normalize, stride, offset);
window.gl = gl
webglUtils.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// Clear the canvas to grey
gl.clearColor(0.5,0.5,0.5,1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(program);
gl.uniform1i(imageLocation, 0);
// Bind the position buffer so gl.bufferData that will be called
// in setRectangle puts data in the position buffer
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Set a rectangle the same size as the image.
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
-0.5, -0.5,
-0.5, 0.5,
0.5, -0.5,
0.5, -0.5,
0.5, 0.5,
-0.5, 0.5
]), gl.STATIC_DRAW);
// Draw the rectangle.
const primitiveType = gl.TRIANGLES;
offset = 0;
const count = 6;
gl.drawArrays(primitiveType, offset, count);
}
}
main();
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (success) {
return shader;
}
console.log(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return undefined;
}
function createProgram(gl, vertexShader, fragmentShader) {
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
const success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (success) {
return program;
}
console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
return undefined;
}
And this is the very similar code with the changes to enable a float32 texture. No errors in the console, but I just get a black square on a grey background. No purple triangle :(
"use strict";
function main() {
// Get the WebGL context
const canvas = document.querySelector("#canvas");
const gl = canvas.getContext("webgl2");
const textureWidth = 100;
const textureHeight = 100;
// Render purple triangle to texture
{
const vertexShaderSource = `#version 300 es
in vec4 a_position;
void main() {
gl_Position = a_position;
}
`;
const fragmentShaderSource = `#version 300 es
precision highp float;
out vec4 outColor;
void main() {
outColor = vec4(1,0,1, 1);
}
`;
// create/compile shaders
const vertexShader = createShader(gl, gl.VERTEX_SHADER, vertexShaderSource);
const fragmentShader = createShader(gl, gl.FRAGMENT_SHADER, fragmentShaderSource);
const program = createProgram(gl, vertexShader, fragmentShader);
// set a_position attribute
const positionAttributeLocation = gl.getAttribLocation(program, "a_position");
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
const positions = [
-1, -1,
0, 1,
1, 0
];
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(positions), gl.STATIC_DRAW);
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
gl.enableVertexAttribArray(positionAttributeLocation);
// Tell the attribute how to get data out of positionBuffer
const size = 2; // 2 components per iteration
let type = gl.FLOAT; // the data is 32bit floats
const normalize = false; // don't normalize the data
const stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
let offset = 0; // start at the beginning of the buffer
gl.vertexAttribPointer(
positionAttributeLocation, size, type, normalize, stride, offset);
gl.useProgram(program);
// set up the target texture
// enable float texture rendering
const ext = gl.getExtension("EXT_color_buffer_float");
const targetTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, targetTexture);
const level = 0;
const internalFormat = gl.RGBA32F;
const border = 0;
const format = gl.RGBA;
type = gl.FLOAT;
const data = null;
gl.texImage2D(gl.TEXTURE_2D, level, internalFormat,
textureWidth, textureHeight, border,
format, type, data);
// set the filtering so we don't need mips
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);
// Create and bind the framebuffer
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
// attach the texture as the first color attachment
const attachmentPoint = gl.COLOR_ATTACHMENT0;
gl.framebufferTexture2D(
gl.FRAMEBUFFER, attachmentPoint, gl.TEXTURE_2D, targetTexture, level);
gl.viewport(0, 0, textureWidth, textureHeight);
// Clear the canvas
gl.clearColor(1,1,1,1);
gl.clear(gl.COLOR_BUFFER_BIT);
const primitiveType = gl.TRIANGLES;
offset = 0;
const count = 3;
gl.drawArrays(primitiveType, offset, count);
}
// Now draw the texture to the canvas
{
const vertexShaderSource = `#version 300 es
in vec2 a_position;
in vec2 a_texCoord;
out vec2 v_texCoord;
void main() {
gl_Position = vec4(a_position, 0, 1);
v_texCoord = a_texCoord;
}
`;
const fragmentShaderSource = `#version 300 es
precision highp float;
uniform sampler2D u_image;
in vec2 v_texCoord;
out vec4 outColor;
void main() {
outColor = texture(u_image , v_texCoord);
}
`;
// render to the canvas
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
const program = webglUtils.createProgramFromSources(gl,
[vertexShaderSource, fragmentShaderSource]);
const positionAttributeLocation = gl.getAttribLocation(program, "a_position");
const texCoordAttributeLocation = gl.getAttribLocation(program, "a_texCoord");
// lookup uniforms
const imageLocation = gl.getUniformLocation(program, "u_image");
const vao = gl.createVertexArray();
gl.bindVertexArray(vao);
const positionBuffer = gl.createBuffer();
gl.enableVertexAttribArray(positionAttributeLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
let size = 2; // 2 components per iteration
let type = gl.FLOAT; // the data is 32bit floats
let normalize = false; // don't normalize the data
let stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
let offset = 0; // start at the beginning of the buffer
gl.vertexAttribPointer(
positionAttributeLocation, size, type, normalize, stride, offset);
const texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0, 0,
0, 1,
1, 0,
1, 0,
1, 1,
0, 1
]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(texCoordAttributeLocation);
size = 2; // 2 components per iteration
type = gl.FLOAT; // the data is 32bit floats
normalize = false; // don't normalize the data
stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
offset = 0; // start at the beginning of the buffer
gl.vertexAttribPointer(
texCoordAttributeLocation, size, type, normalize, stride, offset);
window.gl = gl
webglUtils.resizeCanvasToDisplaySize(gl.canvas);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// Clear the canvas to grey
gl.clearColor(0.5,0.5,0.5,1);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.useProgram(program);
gl.uniform1i(imageLocation, 0);
// Bind the position buffer so gl.bufferData that will be called
// in setRectangle puts data in the position buffer
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Set a rectangle the same size as the image.
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
-0.5, -0.5,
-0.5, 0.5,
0.5, -0.5,
0.5, -0.5,
0.5, 0.5,
-0.5, 0.5
]), gl.STATIC_DRAW);
// Draw the rectangle.
const primitiveType = gl.TRIANGLES;
offset = 0;
const count = 6;
gl.drawArrays(primitiveType, offset, count);
}
}
main();
function createShader(gl, type, source) {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
const success = gl.getShaderParameter(shader, gl.COMPILE_STATUS);
if (success) {
return shader;
}
console.log(gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return undefined;
}
function createProgram(gl, vertexShader, fragmentShader) {
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
const success = gl.getProgramParameter(program, gl.LINK_STATUS);
if (success) {
return program;
}
console.log(gl.getProgramInfoLog(program));
gl.deleteProgram(program);
return undefined;
}
According to the documentation, the internal format RGBA32F
is not filterable. So it's invalid to set TEXTURE_MIN_FILTER
to LINEAR
, and you should change it to NEAREST
. However, this is not enough, because the default value of TEXTURE_MAG_FILTER
is LINEAR
, so you have to change it to NEAREST
as well. So change
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
to
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
I'm not sure why this is not an error in the console.