How do you render to an integer renderbuffer in webgl? - javascript

I'm having trouble using webgl2 to render to an integer renderbuffer. Using the example code at the bottom, I get all zeros in the pixels array and the following error on Firefox:
WebGL warning: readPixels: Format and type RGBA_INTEGER/<enum 0x1400> incompatible with this RGBA8I attachment. This framebuffer requires either RGBA_INTEGER/INT or getParameter(IMPLEMENTATION_COLOR_READ_FORMAT/_TYPE) RGBA_INTEGER/INT.
When I substitute:
gl.readPixels(
0, 0, 256, 256,
gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_FORMAT),
gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_TYPE),
pixels
);
for
gl.readPixels(0, 0, 256, 256, gl.RGBA_INTEGER, gl.BYTE, pixels);
The error becomes:
WebGL warning: readPixels: `pixels` type does not match `type`.
(On Firefox, gl.getParameter(gl.IMPLEMENTATION_COLOR_READ_TYPE) returns gl.INT instead of gl.BYTE.)
I've tried changing the TypedArray for pixels between Uint8Array and Int8Array, but none options works. I should note that the provided example does work on Chrome. Is there a way to render to int buffer on Firefox or is it entirely bugged?
const gl = document.getElementById('target').getContext('webgl2', { preserveDrawingBuffer: true });
const prog = gl.createProgram()
const vert = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vert, `#version 300 es
in vec2 a_position;
void main() {
gl_Position = vec4(a_position, 0., 1.);
}`);
gl.compileShader(vert);
gl.attachShader(prog, vert);
const frag = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(frag, `#version 300 es
precision highp isampler2D;
out ivec4 color;
void main() {
color = ivec4(255, 0, 0, 255);
}`);
gl.compileShader(frag);
gl.attachShader(prog, frag);
gl.linkProgram(prog);
const vao = gl.createVertexArray();
gl.bindVertexArray(vao)
const buff = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buff);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array([-1,-1, -1,1, 1,-1, 1,1, -1,1, 1,-1]),
gl.STATIC_DRAW
);
const pos = gl.getAttribLocation(prog, 'a_position');
gl.enableVertexAttribArray(pos);
gl.vertexAttribPointer(pos, 2, gl.FLOAT, false, 0, 0)
const rbo = gl.createRenderbuffer();
gl.bindRenderbuffer(gl.RENDERBUFFER, rbo);
gl.renderbufferStorage(gl.RENDERBUFFER, gl.RGBA8I, 256, 256);
const fbo = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
gl.framebufferRenderbuffer(
gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0,
gl.RENDERBUFFER, rbo
);
gl.viewport(0, 0, 256, 256);
gl.clearColor(0,0,0,0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.disable(gl.DEPTH_TEST);
gl.useProgram(prog);
gl.drawBuffers([gl.COLOR_ATTACHMENT0]);
gl.drawArrays(gl.TRIANGLES, 0, 6);
gl.readBuffer(gl.COLOR_ATTACHMENT0);
const pixels = new Int8Array(256 ** 2 * 4);
gl.readPixels(0, 0, 256, 256, gl.RGBA_INTEGER, gl.BYTE, pixels);
console.log(pixels);

Figured it out. For whatever reason, Firefox wants gl.INT and Int32Array to be passed to readPixels. Note that this is not to spec and fails on Chrome.

Related

WebGL: read from a texture and write the output to a second texture

I am using WebGL2. I have two programs. Program 1 renders my favorite triangle, in my favorite color, into a texture, for safe keeping.
Program 2 reads the output of program 1 (the first texture) and then renders the contents of that texture.
This works fine when program 2 renders to the canvas, but I would like program 2 to render to a second texture.
So after the first program's draw call I unbind the first fbo, create another fbo backed by a second texture, and then draw.
....
gl.bindFramebuffer(gl.FRAMEBUFFER,null)
gl.useProgram(program2)
....
const fbo2 = gl.createFramebuffer()
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo2)
const intermediateTexture = createEmptyTexture(gl,gl.canvas.width,gl.canvas.height,mipLevel)
gl.framebufferTexture2D(
gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, intermediateTexture, mipLevel)
gl.viewport(0,0,gl.canvas.width,gl.canvas.height)
gl.drawArrays(gl.TRIANGLES, 0, 6)
This produces the following error: :GL_INVALID_OPERATION : glDrawArrays: Source and destination textures of the draw are the same.
My interpretation of this error is that program 2 is reading from, and writing to, the same texture. I think the problem is that I haven't told program 2 which texture to read from.
But I don't know how to tell program 2 to read from the texture in fb1, after that frame buffer has been unbound.
How do I get program 2 to read from the first texture and draw to the second texture?
Thank you so much for your help
full code:
const vs1 = `#version 300 es
in vec4 a_position;
void main() {
gl_Position = a_position;
}
`
const fs1 = `#version 300 es
precision highp float;
out vec4 outColor;
void main() {
outColor = vec4(1, 0.5, .2, 1);
}
`
const vs2 = `#version 300 es
in vec3 a_texCoord;
out vec2 v_texCoord;
void main() {
v_texCoord = a_texCoord.xy;
gl_Position = vec4(a_texCoord, 1.0);
}`
const fs2 = `#version 300 es
precision highp float;
uniform sampler2D tex;
in vec2 v_texCoord;
out vec4 color;
void main() {
color = texture(tex,v_texCoord);
}
`
function createShader(gl, type, source) {
let shader = gl.createShader(type)
gl.shaderSource(shader, source)
gl.compileShader(shader)
return shader
}
function createProgram(gl, vertexShader, fragmentShader) {
let program = gl.createProgram()
gl.attachShader(program, vertexShader)
gl.attachShader(program, fragmentShader)
gl.linkProgram(program)
return program
}
function createEmptyTexture(gl, targetTextureWidth, targetTextureHeight, mipLevel) {
let texture = gl.createTexture()
gl.bindTexture(gl.TEXTURE_2D, texture)
gl.texImage2D(gl.TEXTURE_2D, mipLevel, gl.RGB,
targetTextureWidth, targetTextureHeight, 0,
gl.RGB, gl.UNSIGNED_BYTE, null)
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.LINEAR)
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR)
return texture
}
function main() {
const vertexShader1 = createShader(gl, gl.VERTEX_SHADER, vs1),
fragmentShader1 = createShader(gl, gl.FRAGMENT_SHADER, fs1),
vertexShader2 = createShader(gl, gl.VERTEX_SHADER, vs2),
fragmentShader2 = createShader(gl, gl.FRAGMENT_SHADER, fs2),
program1 = createProgram(gl, vertexShader1, fragmentShader1),
program2 = createProgram(gl, vertexShader2, fragmentShader2),
positionAttributeLocation = gl.getAttribLocation(program1, "a_position"),
texturePositionAttributeLocation = gl.getAttribLocation(program2, "a_texCoord"),
positionBuffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([-1, .5, 0, 0, 1, .5]), gl.STATIC_DRAW)
const vao1 = gl.createVertexArray()
gl.bindVertexArray(vao1)
gl.enableVertexAttribArray(positionAttributeLocation)
gl.vertexAttribPointer(positionAttributeLocation, 2, gl.FLOAT, false, 0, 0)
const mipLevel = 0
// program1 draws to fbo1 backed by intermediateTexture at color attachment 0
gl.useProgram(program1)
gl.bindVertexArray(vao1)
const fbo1 = gl.createFramebuffer()
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo1)
const intermediateTexture = createEmptyTexture(gl, gl.canvas.width, gl.canvas.height, mipLevel)
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, intermediateTexture, mipLevel)
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height)
gl.drawArrays(gl.TRIANGLES, 0, 3)
gl.bindFramebuffer(gl.FRAMEBUFFER, null) // Is this the problem?
// program2 is supposed to draw to fbo2 backed by new texture at color attachment 0
gl.useProgram(program2)
const vao2 = gl.createVertexArray()
gl.bindVertexArray(vao2)
gl.enableVertexAttribArray(texturePositionAttributeLocation)
gl.vertexAttribPointer(texturePositionAttributeLocation, 2, gl.FLOAT, false, 0, 0)
gl.bindVertexArray(vao2)
const quad = [-1, -1, 1, -1, -1, 1, 1, -1, 1, 1, -1, 1]
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(quad), gl.STATIC_DRAW)
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height)
const fbo2 = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo2);
const targetTexture = createEmptyTexture(gl, gl.canvas.width, gl.canvas.height, mipLevel)
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, targetTexture, mipLevel)
gl.drawArrays(gl.TRIANGLES, 0, 6) // error: glDrawArrays: Source and destination textures of the draw are the same
}
main();
Your function createEmptyTexture binds the texture it creates to the (implicitly) activated texture unit 0, and leaves it bound. So if you want to read from the texture of the first framebuffer you'll need to bind that before rendering with program 2. That being said, usually you'd setup all the vertex-, index- and framebuffers with their respective textures and programs upfront during initialization, your render loop then emits bind, draw and attribute pointer calls.

Using Nurbs Surface (Verb) in webGL, indices are always wrong unless I use corners instead of controlPoints

I am trying to make Verb's Nurbs Surface to work with vanilla webGL (cheating a bit with webgl-utils though). PIXI.js also yields the same results, corners work, but not the control points.
I could do it using corners verb.geom.NurbsSurface.byCorners however when I try to use the controlPoints using verb.geom.NurbsSurface.byKnotsControlPointsWeights the surface gets messed up, most probably the indices are wrong!
There is an example of using verb with THREE.js but even when I try the same functions used in the example, the results are the same.
I have commented out the function based on the corners, you can see that it works. I console logged srf and from the _data object I could see Verb is generating those control points under the hood, but the same points would not work if I try them with byKnotsControlPointsWeights
Apart from a working solution, I also would like to understand why the code does not work, and which part in the THREE.js is different from my vanilla code that makes it work with THREE but not here.
const flatten = _.flatten;
// const corners = [
// [100, 100], // top left
// [450, 50], // top right
// [650, 650], // bottom right
// [0, 750] // bottom left
// ];
// var srf = verb.geom.NurbsSurface.byCorners(...corners);
const degreeU = 3;
const degreeV = 3;
const knotsU = [0, 0, 0, 0, 1, 1, 1, 1];
const knotsV = [0, 0, 0, 0, 1, 1, 1, 1];
const controlPoints = [
[
[0, 0, 1],
[0, 249, 1],
[0, 500, 1],
[0, 750, 1]
],
[
[249, 0, 1],
[249, 249, 1],
[249, 500, 1],
[249, 750, 1]
],
[
[500, 0, 1],
[500, 249, 1],
[500, 500, 1],
[500, 750, 1]
],
[
[750, 0, 1],
[750, 249, 1],
[750, 500, 1],
[750, 750, 1]
]
];
var srf = verb.geom.NurbsSurface.byKnotsControlPointsWeights(
degreeU,
degreeV,
knotsU,
knotsV,
controlPoints
);
// tesselate the nurface and get the triangles
var tess = srf.tessellate();
console.log(tess);
const vertexSource = `
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;
}
`;
const fragmentSource = `
precision mediump float;
// our texture
uniform sampler2D u_image;
// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;
void main() {
gl_FragColor = texture2D(u_image, v_texCoord);
}
`;
function main() {
var image = new Image();
image.crossOrigin = "anonymous";
image.onload = function () {
render(image);
};
image.src = "https://pixijs.io/examples/examples/assets/bg_scene_rotate.jpg";
}
function render(image) {
// Get A WebGL context
/** #type {HTMLCanvasElement} */
var canvas = document.getElementById("c");
var gl = canvas.getContext("webgl");
// setup GLSL program
var program = webglUtils.createProgramFromSources(gl, [
vertexSource,
fragmentSource
]);
// look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation(program, "a_position");
var texcoordLocation = gl.getAttribLocation(program, "a_texCoord");
// Create a buffer to put three 2d clip space points in
var positionBuffer = gl.createBuffer();
// Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Set a rectangle the same size as the image.
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array(flatten(tess.points)),
gl.STATIC_DRAW
);
// provide texture coordinates for the rectangle.
var texcoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array(flatten(tess.uvs)),
gl.STATIC_DRAW
);
// Create a texture.
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// Set the parameters so we can render any size image.
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);
// Upload the image into the texture.
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
// lookup uniforms
var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
// resize canvas to display size
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// Tell WebGL how to convert from clip space to pixels
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// Clear the canvas
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
// Tell it to use our program (pair of shaders)
gl.useProgram(program);
// index buffer
const indexBuffer = gl.createBuffer();
// make this buffer the current 'ELEMENT_ARRAY_BUFFER'
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
// Fill the current element array buffer with data
const indices = new Uint16Array(flatten(tess.faces));
gl.bufferData(
gl.ELEMENT_ARRAY_BUFFER,
new Uint16Array(indices),
gl.STATIC_DRAW
);
// Turn on the position attribute
gl.enableVertexAttribArray(positionLocation);
// Bind the position buffer.
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
var size = 2; // 2 components per iteration
var type = gl.FLOAT; // the data is 32bit floats
var normalize = false; // don't normalize the data
var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
var offset = 0; // start at the beginning of the buffer
gl.vertexAttribPointer(
positionLocation,
size,
type,
normalize,
stride,
offset
);
// Turn on the texcoord attribute
gl.enableVertexAttribArray(texcoordLocation);
// bind the texcoord buffer.
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
gl.vertexAttribPointer(
texcoordLocation,
size,
type,
normalize,
stride,
offset
);
// set the resolution
gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height);
// Draw the rectangle.
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
}
main();
body { margin: 0; }
<script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
<script src="https://unpkg.com/verb-nurbs-web#2.1.3/build/js/verb.js"></script>
<script src="https://unpkg.com/lodash#4.17.20/lodash.js"></script>
<canvas id="c"></canvas>
I don't know what it's supposed to look like but the code was passing 2 for the size of the positions when calling gl.vertexAttribPointer but the data has 3 (x, y, z). Setting that to 3 certainly gets a different image. Still need to make sure UVs are set to a size of 2
const flatten = _.flatten;
// const corners = [
// [100, 100], // top left
// [450, 50], // top right
// [650, 650], // bottom right
// [0, 750] // bottom left
// ];
// var srf = verb.geom.NurbsSurface.byCorners(...corners);
const degreeU = 3;
const degreeV = 3;
const knotsU = [0, 0, 0, 0, 1, 1, 1, 1];
const knotsV = [0, 0, 0, 0, 1, 1, 1, 1];
const controlPoints = [
[
[0, 0, 1],
[0, 249, 1],
[0, 500, 1],
[0, 750, 1]
],
[
[249, 0, 1],
[249, 249, 1],
[249, 500, 1],
[249, 750, 1]
],
[
[500, 0, 1],
[500, 249, 1],
[500, 500, 1],
[500, 750, 1]
],
[
[750, 0, 1],
[750, 249, 1],
[750, 500, 1],
[750, 750, 1]
]
];
var srf = verb.geom.NurbsSurface.byKnotsControlPointsWeights(
degreeU,
degreeV,
knotsU,
knotsV,
controlPoints
);
// tesselate the nurface and get the triangles
var tess = srf.tessellate();
console.log(tess);
const vertexSource = `
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;
}
`;
const fragmentSource = `
precision mediump float;
// our texture
uniform sampler2D u_image;
// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;
void main() {
gl_FragColor = texture2D(u_image, v_texCoord);
}
`;
function main() {
var image = new Image();
image.crossOrigin = "anonymous";
image.onload = function () {
render(image);
};
image.src = "https://pixijs.io/examples/examples/assets/bg_scene_rotate.jpg";
}
function render(image) {
// Get A WebGL context
/** #type {HTMLCanvasElement} */
var canvas = document.getElementById("c");
var gl = canvas.getContext("webgl");
// setup GLSL program
var program = webglUtils.createProgramFromSources(gl, [
vertexSource,
fragmentSource
]);
// look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation(program, "a_position");
var texcoordLocation = gl.getAttribLocation(program, "a_texCoord");
// Create a buffer to put three 2d clip space points in
var positionBuffer = gl.createBuffer();
// Bind it to ARRAY_BUFFER (think of it as ARRAY_BUFFER = positionBuffer)
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Set a rectangle the same size as the image.
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array(flatten(tess.points)),
gl.STATIC_DRAW
);
// provide texture coordinates for the rectangle.
var texcoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
gl.bufferData(
gl.ARRAY_BUFFER,
new Float32Array(flatten(tess.uvs)),
gl.STATIC_DRAW
);
// Create a texture.
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
// Set the parameters so we can render any size image.
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);
// Upload the image into the texture.
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
// lookup uniforms
var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
// resize canvas to display size
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// Tell WebGL how to convert from clip space to pixels
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
// Clear the canvas
gl.clearColor(0, 0, 0, 0);
gl.clear(gl.COLOR_BUFFER_BIT);
// Tell it to use our program (pair of shaders)
gl.useProgram(program);
// index buffer
const indexBuffer = gl.createBuffer();
// make this buffer the current 'ELEMENT_ARRAY_BUFFER'
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, indexBuffer);
// Fill the current element array buffer with data
const indices = new Uint16Array(flatten(tess.faces));
gl.bufferData(
gl.ELEMENT_ARRAY_BUFFER,
new Uint16Array(indices),
gl.STATIC_DRAW
);
// Turn on the position attribute
gl.enableVertexAttribArray(positionLocation);
// Bind the position buffer.
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
// Tell the position attribute how to get data out of positionBuffer (ARRAY_BUFFER)
var size = 3; // 2 components per iteration
var type = gl.FLOAT; // the data is 32bit floats
var normalize = false; // don't normalize the data
var stride = 0; // 0 = move forward size * sizeof(type) each iteration to get the next position
var offset = 0; // start at the beginning of the buffer
gl.vertexAttribPointer(
positionLocation,
size,
type,
normalize,
stride,
offset
);
// Turn on the texcoord attribute
gl.enableVertexAttribArray(texcoordLocation);
// bind the texcoord buffer.
gl.bindBuffer(gl.ARRAY_BUFFER, texcoordBuffer);
gl.vertexAttribPointer(
texcoordLocation,
2, //size,
type,
normalize,
stride,
offset
);
// set the resolution
gl.uniform2f(resolutionLocation, gl.canvas.width, gl.canvas.height);
// Draw the rectangle.
gl.drawElements(gl.TRIANGLES, indices.length, gl.UNSIGNED_SHORT, 0);
}
main();
body { margin: 0; }
<script src="https://webglfundamentals.org/webgl/resources/webgl-utils.js"></script>
<script src="https://unpkg.com/verb-nurbs-web#2.1.3/build/js/verb.js"></script>
<script src="https://unpkg.com/lodash#4.17.20/lodash.js"></script>
<canvas id="c"></canvas>

WebGL Stencils, How to use a 2D sprite's transparency as a mask?

if (statuseffect) {
// Clearing the stencil buffer
gl.clearStencil(0);
gl.clear(gl.STENCIL_BUFFER_BIT);
gl.stencilFunc(gl.ALWAYS, 1, 1);
gl.stencilOp(gl.REPLACE, gl.REPLACE, gl.REPLACE);
gl.colorMask(false, false, false, false);
gl.enable(gl.STENCIL_TEST);
// Renders the mask through gl.drawArrays L111
drawImage(statuseffectmask.texture, lerp(-725, 675, this.Transtion_Value), 280, 128 * 4, 32 * 4)
// Telling the stencil now to draw/keep only pixels that equals 1 - which we set earlier
gl.stencilFunc(gl.EQUAL, 1, 1);
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
// enabling back the color buffer
gl.colorMask(true, true, true, true);
drawImage(statuseffect.texture, lerp(-725, 675, this.Transtion_Value), 280, 128 * 4, 32 * 4)
gl.disable(gl.STENCIL_TEST);
}
Im trying to get something to work like this
Where it gets the transparency of the sprite, and then draws a sprite in areas where there is no transparency, thank you.
It's not clear why you want to use the stencil for this. Normally you'd just setup blending and use the transparency to blend.
If you really wanted to use the stencil you'd need to make a shader that calls discard if the transparency (alpha) is less then some value in order to make the stencil get set only where the sprite is not transparent
precision highp float;
varying vec2 v_texcoord;
uniform sampler2D u_texture;
uniform float u_alphaTest;
void main() {
vec4 color = texture2D(u_texture, v_texcoord);
if (color.a < u_alphaTest) {
discard; // don't draw this pixel
}
gl_FragColor = color;
}
But the thing is that would already draw the texture transparently without using the stencil.
const m4 = twgl.m4;
const gl = document.querySelector('canvas').getContext('webgl');
const vs = `
attribute vec4 position;
attribute vec2 texcoord;
uniform mat4 u_matrix;
varying vec2 v_texcoord;
void main() {
gl_Position = u_matrix * position;
v_texcoord = texcoord;
}
`;
const fs = `
precision highp float;
varying vec2 v_texcoord;
uniform sampler2D u_texture;
uniform float u_alphaTest;
void main() {
vec4 color = texture2D(u_texture, v_texcoord);
if (color.a < u_alphaTest) {
discard; // don't draw this pixel
}
gl_FragColor = color;
}
`;
// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// make buffers for positions and texcoords for a plane
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData
const bufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
const texture = makeSpriteTexture(gl, '🌲', 128);
function render(time) {
time *= 0.001; // convert to seconds
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
const mat = m4.ortho(-150, 150, 75, -75, -1, 1);
m4.translate(mat, [Math.cos(time) * 20, Math.sin(time) * 20, 0], mat);
m4.scale(mat, [64, 64, 1], mat);
// calls gl.activeTexture, gl.bindTexture, gl.uniformXXX
twgl.setUniformsAndBindTextures(programInfo, {
u_texture: texture,
u_alphaTest: 0.5,
u_matrix: mat,
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bufferInfo);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
// just so we don't have to download an image
// we'll make our own using a canvas and the 2D API
function makeSpriteTexture(gl, str, size) {
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
ctx.font = `${size * 3 / 4}px sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(str, size / 2, size / 2);
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(
gl.TEXTURE_2D,
0, // mip level
gl.RGBA, // internal format
gl.RGBA, // format
gl.UNSIGNED_BYTE, // type,
canvas);
// let's assume we used power of 2 dimensions
gl.generateMipmap(gl.TEXTURE_2D);
return tex;
}
canvas {
background: url(https://i.imgur.com/v38pV.jpg) no-repeat center center;
background-size: cover;
}
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
Otherwise if you really want to use the stencil now that the code is discarding some pixels it should work and your code was correct. note the code below doesn't clear the stencil because it defaults to being cleared every frame
const m4 = twgl.m4;
const gl = document.querySelector('canvas').getContext('webgl', {stencil: true});
const vs = `
attribute vec4 position;
attribute vec2 texcoord;
uniform mat4 u_matrix;
varying vec2 v_texcoord;
void main() {
gl_Position = u_matrix * position;
v_texcoord = texcoord;
}
`;
const fs = `
precision highp float;
varying vec2 v_texcoord;
uniform sampler2D u_texture;
uniform float u_alphaTest;
void main() {
vec4 color = texture2D(u_texture, v_texcoord);
if (color.a < u_alphaTest) {
discard; // don't draw this pixel
}
gl_FragColor = color;
}
`;
// compile shaders, link program, look up locations
const programInfo = twgl.createProgramInfo(gl, [vs, fs]);
// make buffers for positions and texcoords for a plane
// calls gl.createBuffer, gl.bindBuffer, gl.bufferData
const bufferInfo = twgl.primitives.createXYQuadBufferInfo(gl);
const starTexture = makeSpriteTexture(gl, '✱', 128);
const bugTexture = makeSpriteTexture(gl, '🐞', 128);
function render(time) {
time *= 0.001; // convert to seconds
gl.useProgram(programInfo.program);
// calls gl.bindBuffer, gl.enableVertexAttribArray, gl.vertexAttribPointer
twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);
const mat = m4.ortho(-150, 150, 75, -75, -1, 1);
m4.translate(mat, [Math.cos(time) * 20, 0, 0], mat);
m4.scale(mat, [64, 64, 1], mat);
gl.enable(gl.STENCIL_TEST);
// set the stencil to 1 everwhere we draw a pixel
gl.stencilFunc(gl.ALWAYS, 1, 0xFF);
gl.stencilOp(gl.REPLACE, gl.REPLACE, gl.REPLACE);
// don't render pixels
gl.colorMask(false, false, false, false);
// calls gl.activeTexture, gl.bindTexture, gl.uniformXXX
twgl.setUniformsAndBindTextures(programInfo, {
u_texture: starTexture,
u_alphaTest: 0.5,
u_matrix: mat,
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bufferInfo);
// only draw if the stencil = 1
gl.stencilFunc(gl.EQUAL, 1, 0xFF);
gl.stencilOp(gl.KEEP, gl.KEEP, gl.KEEP);
// render pixels
gl.colorMask(true, true, true, true);
m4.ortho(-150, 150, 75, -75, -1, 1, mat);
m4.translate(mat, [0, Math.cos(time * 1.1) * 20, 0], mat);
m4.scale(mat, [64, 64, 1], mat);
// calls gl.activeTexture, gl.bindTexture, gl.uniformXXX
twgl.setUniformsAndBindTextures(programInfo, {
u_texture: bugTexture,
u_alphaTest: 0, // draw all pixels (but stencil will prevent some)
u_matrix: mat,
});
// calls gl.drawArrays or gl.drawElements
twgl.drawBufferInfo(gl, bufferInfo);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
// just so we don't have to download an image
// we'll make our own using a canvas and the 2D API
function makeSpriteTexture(gl, str, size) {
const canvas = document.createElement('canvas');
canvas.width = size;
canvas.height = size;
const ctx = canvas.getContext('2d');
ctx.font = `${size * 3 / 4}px sans-serif`;
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
ctx.fillText(str, size / 2, size / 2);
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(
gl.TEXTURE_2D,
0, // mip level
gl.RGBA, // internal format
gl.RGBA, // format
gl.UNSIGNED_BYTE, // type,
canvas);
// let's assume we used power of 2 dimensions
gl.generateMipmap(gl.TEXTURE_2D);
return tex;
}
canvas {
background: url(https://i.imgur.com/v38pV.jpg) no-repeat center center;
background-size: cover;
}
<canvas></canvas>
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
Let me also point out that that this is also probably better done using alpha blending, passing both textures into a single shader and passing in another matrix or other uniforms to apply one texture's alpha ot the other. This would be more flexible as you could can blend across all values of 0 to 1 where as with the stencil you can only mask 0 or 1 period.
My point isn't to say "don't use the stencil" but rather that there are times where it's best and times where it's not. Only you can know for your situation which solution to choose.

How I can do something with the extension ANGLE_instanced_arrays?

I want to do any ANGLE_instanced_arrays and I was read the documentation on MDN and I did not understand anything. Well, I understood that I can make graphic 2D and 3D like : examples but for me these examples are too advanced and it's difficult to understand all the code. Can anyone help me with an example?
This part I understand of the documentation official:
The ANGLE_instanced_arrays extension is part of the WebGL API and allows to draw the same object, or groups of similar objects multiple times, if they share the same vertex data, primitive count and type.
here is what I read
Can you draw without ANGLE_instanced_arrays? The difference between drawing with and without are
you call a different draw function and pass the an extra parameter of how many instances to draw. ext.drawArraysInstancedANGLE or ext.drawElementsInstancedANGLE instead of the normal ext.drawArrays or ext.drawElements
you add one or more attributes to your vertex shader who's values will only change once per instance drawn. In other words if you're drawing a cube the attribute's value will be the same value for every vertex while drawing the first cube, a different value while drawing the 2nd cube, a 3rd value while drawn the 3rd cube. Where as normal attributes change for each vertex these attributes only change once per cube/item.
The most obvious attribute to add is an extra per cube position so that each cube can have a different position added to the vertex positions but you could add another attribute for a pure cube color or add matrix attributes so you can orient each cube completely independently or whatever you want.
For those attributes that only change once per cube you set their vertex divisor to 1 by calling ext.vertexAttribDivisorANGLE. The 1 means "advance the attribute every 1 instance". 0 (the default) means advance the attribute every vertex (every iteration of the vertex shader).
Here's an example drawing a single quad (2 triangles, 6 vertices)
const vs = `
attribute vec4 position;
uniform mat4 u_matrix;
void main() {
gl_Position = u_matrix * position;
}
`;
const fs = `
precision mediump float;
uniform vec4 u_color;
void main() {
gl_FragColor = u_color;
}
`;
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
const program = twgl.createProgram(gl, [vs, fs]);
const positionLocation = gl.getAttribLocation(program, "position");
const matrixLocation = gl.getUniformLocation(program, "u_matrix");
const colorLocation = gl.getUniformLocation(program, "u_color");
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
// one face
-1, -1,
1, -1,
-1, 1,
-1, 1,
1, -1,
1, 1,
],
), gl.STATIC_DRAW);
gl.enable(gl.DEPTH_TEST);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.enableVertexAttribArray(positionLocation);
{
const size = 2; // 2 values per vertex
const type = gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
gl.vertexAttribPointer(positionLocation, size, type, normalize, stride, offset);
}
gl.useProgram(program);
gl.uniform4fv(colorLocation, [1, .5, .2, 1]);
gl.uniformMatrix4fv(matrixLocation, false, m4.scaling([.25, .25, .25]));
const offset = 0;
const vertexCount = 6;
gl.drawArrays(gl.TRIANGLES, offset, vertexCount);
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>
and here's an example drawing 100 quads using ANGLE_instanced_arrays. We've added a planeOffset for an offset for each quad and a planeColor for a color for each quad.
const vs = `
attribute vec4 position;
attribute vec2 planeOffset; // per plane offset
attribute vec4 planeColor; // per plane color
uniform mat4 u_matrix;
varying vec4 v_color;
void main() {
mat4 translation = mat4(
vec4(1, 0, 0, 0),
vec4(0, 1, 0, 0),
vec4(0, 0, 1, 0),
vec4(planeOffset, 0, 1));
gl_Position = u_matrix * translation * position;
v_color = planeColor;
}
`;
const fs = `
precision mediump float;
varying vec4 v_color;
void main() {
gl_FragColor = v_color;
}
`;
function main() {
const m4 = twgl.m4;
const gl = document.querySelector("canvas").getContext("webgl");
const ext = gl.getExtension("ANGLE_instanced_arrays");
if (!ext) {
alert("need ANGLE_instanced_arrays");
return;
}
const program = twgl.createProgram(gl, [vs, fs]);
const positionLocation = gl.getAttribLocation(program, "position");
const offsetLocation = gl.getAttribLocation(program, "planeOffset");
const colorLocation = gl.getAttribLocation(program, "planeColor");
const matrixLocation = gl.getUniformLocation(program, "u_matrix");
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
// one face
-1, -1,
1, -1,
-1, 1,
-1, 1,
1, -1,
1, 1,
],
), gl.STATIC_DRAW);
// make 100 offsets and 100 colors
const colors = [];
const offsets = [];
const numInstances = 100;
for (let i = 0; i < 100; ++i) {
colors.push(Math.random(), Math.random(), Math.random(), 1);
offsets.push(Math.random() * 20 - 10, Math.random() * 20 - 10);
}
// put those in buffers
const offsetBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(offsets), gl.STATIC_DRAW);
const colorBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(colors), gl.STATIC_DRAW);
gl.enable(gl.DEPTH_TEST);
gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
gl.enableVertexAttribArray(positionLocation);
{
const size = 2; // 2 values per vertex
const type = gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
gl.vertexAttribPointer(positionLocation, size, type, normalize, stride, offset);
}
gl.bindBuffer(gl.ARRAY_BUFFER, offsetBuffer);
gl.enableVertexAttribArray(offsetLocation);
{
const size = 2; // 2 values per vertex
const type = gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
gl.vertexAttribPointer(offsetLocation, size, type, normalize, stride, offset);
ext.vertexAttribDivisorANGLE(offsetLocation, 1);
}
gl.bindBuffer(gl.ARRAY_BUFFER, colorBuffer);
gl.enableVertexAttribArray(colorLocation);
{
const size = 4; // 4 values per vertex
const type = gl.FLOAT;
const normalize = false;
const stride = 0;
const offset = 0;
gl.vertexAttribPointer(colorLocation, size, type, normalize, stride, offset);
ext.vertexAttribDivisorANGLE(colorLocation, 1);
}
gl.useProgram(program);
gl.uniformMatrix4fv(matrixLocation, false, m4.scaling([.1, .1, .1]));
const offset = 0;
const vertexCount = 6;
ext.drawArraysInstancedANGLE(gl.TRIANGLES, offset, vertexCount, numInstances);
}
main();
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/4.x/twgl-full.min.js"></script>
<canvas></canvas>

How to work with framebuffers in webgl?

I have been trying to understand framebuffer in WebGL/OpenGL-ES.
I know that we can blend multiple textures using framebuffer.
So, to understand that I wrote a sample by taking a 1*1 texture and tried to apply framebuffer logic on top of it.
But , it didn't work.
See snippet at bottom, if you click on "mix red and blue", the images doesn't get rendered, am I doing anything wrong?
Code :
`
var canvas, gl, attrPosition, texture, program, vertexBuffer, textureBuffer, vertices, texVertices, attrPos, attrTexPos, textures = [], framebuffers = [];
canvas = document.getElementById('canvas');
gl = getWebGL();
vertices = new Float32Array([
-1.0, -1.0,
1.0, -1.0,
1.0, 1.0,
-1.0, 1.0,
-1.0, -1.0,
]);
texVertices = new Float32Array([
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
0.0, 0.0
]);
var getProgram = function () {
var vs = createVertexShader([
'attribute vec2 attrPos;',
'attribute vec2 attrTexPos;',
'varying highp vec2 vTexCoord;',
'void main() {',
'\tgl_Position = vec4(attrPos, 0.0, 1.0);',
'}'
].join('\n'));
var fs = createFragmentShader([
'varying highp vec2 vTexCoord;',
'uniform sampler2D uImage;',
'void main() {',
'\tgl_FragColor = texture2D(uImage, vTexCoord);',
'}'
].join('\n'));
return createAndLinkPrograms(vs, fs);
};
var render = function () {
gl.clear(gl.DEPTH_BUFFER_BIT|gl.COLOR_BUFFER_BIT);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.vertexAttribPointer(attrPos, 2, gl.FLOAT, gl.FALSE, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, textureBuffer);
gl.vertexAttribPointer(attrTexPos, 2, gl.FLOAT, gl.FALSE, 0, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 5);
};
if (gl) {
gl.clearColor(0.1, 0.5, 1.0, 1.0);
render();
program = getProgram();
texture = createAndSetupTexture();
vertexBuffer = createAndBindBuffer(vertices, gl.ARRAY_BUFFER);
attrPos = gl.getUniformLocation(program, 'attrPos');
gl.enableVertexAttribArray(attrPos);
textureBuffer = createAndBindBuffer(texVertices, gl.ARRAY_BUFFER);
attrTexPos = gl.getUniformLocation(program, 'attrTexPos');
gl.enableVertexAttribArray(attrTexPos);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([123, 0, 60, 255]));
render();
}
var initPingPongTextures = function(textures, framebuffers) {
for (var i = 0; i < 2; ++i) {
var tex = createAndSetupTexture(gl);
textures.push(tex);
// make the texture the same size as the image
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
// Create a framebuffer
var fbo = gl.createFramebuffer();
framebuffers.push(fbo);
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
// Attach a texture to it.
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
}
}
var setFramebuffer = function(fbo, width, height) {
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
gl.viewport(0, 0, width, height);
};
var mixRedAndBlue = function () {
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
setFramebuffer(framebuffers[0], 1, 1);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 0, 0, 255]));
render();
gl.bindTexture(gl.TEXTURE_2D, textures[0]);
setFramebuffer(framebuffers[1], 1, 1);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 255, 0, 255]));
render();
gl.bindTexture(gl.TEXTURE_2D, textures[1]);
setFramebuffer(null, 1, 1);
render();
};`
var getWebGLContext = function(canvas) {
var webglContextParams = ['webgl', 'experimental-webgl', 'webkit-3d', 'moz-webgl'];
var webglContext = null;
for (var index = 0; index < webglContextParams.length; index++) {
try {
webglContext = canvas.getContext(webglContextParams[index]);
if(webglContext) {
//breaking as we got our context
break;
}
} catch (E) {
console.log(E);
}
}
if(webglContext === null) {
alert('WebGL is not supported on your browser.');
} else {
//WebGL is supported in your browser, lets render the texture
}
fillGLForCleanUp(webglContext);
return webglContext;
}
var createVertexShader = function (vertexShaderSource) {
console.log(vertexShaderSource);
var vertexShader = gl.createShader(gl.VERTEX_SHADER);
gl.shaderSource(vertexShader, vertexShaderSource);
gl.compileShader(vertexShader);
return vertexShader;
}
var createFragmentShader = function (fragmentShaderSource) {
console.log(fragmentShaderSource);
var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
gl.shaderSource(fragmentShader, fragmentShaderSource);
gl.compileShader(fragmentShader);
return fragmentShader;
}
var createAndLinkPrograms = function (vertexShader, fragmentShader) {
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
alert('Could not initialise shaders');
}
gl.useProgram(program);
return program;
}
var createAndBindBuffer = function (verticesOrIndices, bufferType) {
var buffer = gl.createBuffer();
gl.bindBuffer(bufferType, buffer);
gl.bufferData(bufferType, verticesOrIndices, gl.STATIC_DRAW);
//clear memory
gl.bindBuffer(bufferType, null);
return buffer;
}
var allowAllImageSizes = function() {
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.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
// gl.bindTexture(gl.TEXTURE_2D, null);
}
var createAndSetupTexture = function() {
var texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
allowAllImageSizes();
gl.textures.push(texture);
return texture;
}
var getWebGL = function (canvas, width, height) {
if(!canvas) {
canvas = document.createElement('canvas');
canvas.id = 'canvas';
canvas.width = !width ? 512 : width;
canvas.height = !height ? 512 : height;
document.body.appendChild(canvas);
} else {
canvas.width = !width ? 512 : width;
canvas.height = !height ? 512 : height;
}
return getWebGLContext(canvas);
}
var fillGLForCleanUp = function (gl) {
gl.textures = [];
gl.framebuffers = [];
gl.array_buffer = [];
gl.element_array_buffers = [];
}
var canvas, gl, attrPosition, texture, program,
vertexBuffer, textureBuffer, vertices, texVertices,
attrPos, attrTexPos, textures = [], framebuffers = [];
canvas = document.getElementById('canvas');
gl = getWebGL(canvas);
vertices = new Float32Array([
-1.0, -1.0,
1.0, -1.0,
1.0, 1.0,
-1.0, 1.0,
-1.0, -1.0,
]);
texVertices = new Float32Array([
0.0, 0.0,
1.0, 0.0,
1.0, 1.0,
0.0, 1.0,
0.0, 0.0
]);
var getProgram = function () {
var vs = createVertexShader([
'attribute vec2 attrPos;',
'attribute vec2 attrTexPos;',
'varying highp vec2 vTexCoord;',
'void main() {',
'\tgl_Position = vec4(attrPos, 0.0, 1.0);',
'}'
].join('\n'));
var fs = createFragmentShader([
'varying highp vec2 vTexCoord;',
'uniform sampler2D uImage;',
'void main() {',
'\tgl_FragColor = texture2D(uImage, vTexCoord);',
'}'
].join('\n'));
return createAndLinkPrograms(vs, fs);
};
var render = function () {
gl.clear(gl.DEPTH_BUFFER_BIT|gl.COLOR_BUFFER_BIT);
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.vertexAttribPointer(attrPos, 2, gl.FLOAT, gl.FALSE, 0, 0);
gl.bindBuffer(gl.ARRAY_BUFFER, textureBuffer);
gl.vertexAttribPointer(attrTexPos, 2, gl.FLOAT, gl.FALSE, 0, 0);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 5);
};
if (gl) {
gl.clearColor(0.1, 0.5, 1.0, 1.0);
render();
program = getProgram();
texture = createAndSetupTexture();
vertexBuffer = createAndBindBuffer(vertices, gl.ARRAY_BUFFER);
attrPos = gl.getUniformLocation(program, 'attrPos');
gl.enableVertexAttribArray(attrPos);
textureBuffer = createAndBindBuffer(texVertices, gl.ARRAY_BUFFER);
attrTexPos = gl.getUniformLocation(program, 'attrTexPos');
gl.enableVertexAttribArray(attrTexPos);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([123, 0, 60, 255]));
render();
}
var initPingPongTextures = function(textures, framebuffers) {
for (var i = 0; i < 2; ++i) {
var tex = createAndSetupTexture(gl);
textures.push(tex);
// make the texture the same size as the image
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
// Create a framebuffer
var fbo = gl.createFramebuffer();
framebuffers.push(fbo);
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
// Attach a texture to it.
gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
}
}
var setFramebuffer = function(fbo, width, height) {
gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
gl.viewport(0, 0, width, height);
};
var mixRedAndBlue = function () {
gl.activeTexture(gl.TEXTURE0);
gl.bindTexture(gl.TEXTURE_2D, texture);
setFramebuffer(framebuffers[0], 1, 1);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([255, 0, 0, 255]));
render();
gl.bindTexture(gl.TEXTURE_2D, textures[0]);
setFramebuffer(framebuffers[1], 1, 1);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, 1, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, new Uint8Array([0, 255, 0, 255]));
render();
gl.bindTexture(gl.TEXTURE_2D, textures[1]);
setFramebuffer(null, 1, 1);
render();
};
<button id="redImg" onclick="mixRedAndBlue()">Mix Red and blue</button><hr/>
<canvas id="canvas" width=512 height=512></canvas>
Edit 1 :
I am trying to achieve the same for multiple programs with multiple fragment shaders because having if/else statements within the fragment shader is not recommended as it runs for each pixel.
`
Shaders.prototype.VS_Base = [
'attribute vec3 verticesPosition;',
'attribute vec2 texturePosition;',
'varying highp vec2 vTextureCoord;',
'void main(void) {',
'\tgl_Position = vec4(verticesPosition * vec3(1.0, -1.0, 1.0), 0.5);',
'\tvTextureCoord = texturePosition;',
'}'
].join('\n');
Shaders.prototype.FS_Base_Image_RED = [
'#ifdef GL_ES',
'precision highp float;',
'#endif',
'uniform sampler2D uImage;',
'varying highp vec2 vTextureCoord;',
'void main (void) {',
'\tgl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);//texture2D(uImage, vTextureCoord);',
'}'
].join('\n');
Shaders.prototype.FS_Base_Image_BLUE = [
'#ifdef GL_ES',
'precision highp float;',
'#endif',
'uniform sampler2D uImage;',
'varying highp vec2 vTextureCoord;',
'void main (void) {',
'\tgl_FragColor = vec4(0.0, 0.0, 1.0, 1.0);//texture2D(uImage, vTextureCoord);',
'}'
].join('\n');`
Now I have 2 separate programs for both the fragment shader and I need to use framebuffers for mixing Red and Blue. I am not looking for mix() as the actual scenario is very complex and that's the reason I am using multiple programs with fragment shaders for avoiding conditional if/else statements.
It's not clear what you're trying to do. Framebuffers are just a list of attachments (textures and renderbuffers). You use them to render to a texture and/or renderbuffer. Then you can use the texture you just rendered to as input to some other render.
Here's an example with NO framebuffers. It blends 2 textures.
var vs = `
attribute vec4 position;
varying vec2 v_texcoord;
void main() {
gl_Position = position;
v_texcoord = position.xy * .5 + .5;
}
`;
var fs = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D tex1;
uniform sampler2D tex2;
void main() {
vec4 color1 = texture2D(tex1, v_texcoord);
vec4 color2 = texture2D(tex2, v_texcoord);
gl_FragColor = mix(color1, color2, 0.5);
}
`;
const gl = document.querySelector("canvas").getContext("webgl");
const program = twgl.createProgramFromSources(gl, [vs, fs]);
// make 2 textures with canvas 2d
const ctx = document.createElement("canvas").getContext("2d");
ctx.canvas.width = 64;
ctx.canvas.height = 64;
// first texture has a circle
ctx.fillStyle = "blue";
ctx.fillRect(0, 0, 64, 64);
ctx.strokeStyle = "yellow";
ctx.beginPath();
ctx.arc(32, 32, 20, 0, Math.PI * 2, false);
ctx.lineWidth = 12;
ctx.stroke();
const tex1 = createTextureFromCanvas(gl, ctx.canvas);
// second texture has a diamond (diagonal square)
ctx.fillStyle = "red";
ctx.fillRect(0, 0, 64, 64);
ctx.fillStyle = "cyan";
ctx.beginPath();
ctx.moveTo(32, 6);
ctx.lineTo(58, 32);
ctx.lineTo(32, 58);
ctx.lineTo(6, 32);
ctx.lineTo(32, 6);
ctx.fill();
const tex2 = createTextureFromCanvas(gl, ctx.canvas);
const buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
-1, -1,
1, -1,
-1, 1,
-1, 1,
1, -1,
1, 1,
]), gl.STATIC_DRAW);
const positionLoc = gl.getAttribLocation(program, "position");
gl.enableVertexAttribArray(positionLoc);
gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);
const tex1Loc = gl.getUniformLocation(program, "tex1");
const tex2Loc = gl.getUniformLocation(program, "tex2");
gl.useProgram(program);
gl.uniform1i(tex1Loc, 0);
gl.uniform1i(tex2Loc, 1);
gl.activeTexture(gl.TEXTURE0 + 0);
gl.bindTexture(gl.TEXTURE_2D, tex1);
gl.activeTexture(gl.TEXTURE0 + 1);
gl.bindTexture(gl.TEXTURE_2D, tex2);
gl.drawArrays(gl.TRIANGLES, 0, 6);
function createTextureFromCanvas(gl, canvas) {
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, ctx.canvas);
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);
return tex;
}
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/2.x/twgl.min.js"></script>
<canvas></canvas>
For your purpose there is no difference about the blending part, the only difference is where the textures come from. Above the textures were created by using a 2d canvas. Instead you can use framebuffer to render to a texture. AFTER you've rendered to a texture you can then use that texture in some other render just like above.
To render to a texture first you create a framebuffer
var fb = gl.createFramebuffer();
Then you attach a texture to it
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
gl.framebufferTexture2D(
gl.FRAMEBUFFER,
gl.COLOR_ATTACHMENT0, // attach texture as COLOR_ATTACHMENT0
gl.TEXTURE_2D, // attach a 2D texture
someTexture, // the texture to attach
0); // the mip level to render to (must be 0 in WebGL1)
Depending on your attachments you should check if they work.
if (gl.checkFramebufferStatus(gl.FRAMEBUFFER) !== gl.FRAMEBUFFER_COMPLETE) {
// these attachments don't work
}
The WebGL spec lists 3 combinations of attachments that are guaranteed to work. The example below is using one of those 3 so there's no need to check
Now if you bind the framebuffer
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
Then when you call any gl.drawXXX function or gl.clear it will be drawing to the someTexture instead of the canvas. To start drawing to the canvas again bind null
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
Remember that if the canvas and the texture are different sizes you'll need to call gl.viewport to render correctly
var vs = `
attribute vec4 position;
uniform mat4 matrix;
varying vec2 v_texcoord;
void main() {
gl_Position = matrix * position;
v_texcoord = position.xy * .5 + .5;
}
`;
var colorFS = `
precision mediump float;
uniform vec4 color;
void main() {
gl_FragColor = color;
}
`;
var mixFS = `
precision mediump float;
varying vec2 v_texcoord;
uniform sampler2D tex1;
uniform sampler2D tex2;
void main() {
// probably should use different texture coords for each
// texture for more flexibility but I'm lazy
vec4 color1 = texture2D(tex1, v_texcoord);
vec4 color2 = texture2D(tex2, v_texcoord);
gl_FragColor = mix(color1, color2, 0.5);
}
`;
const gl = document.querySelector("canvas").getContext("webgl");
const colorProgram = twgl.createProgramFromSources(gl, [vs, colorFS]);
const mixProgram = twgl.createProgramFromSources(gl, [vs, mixFS]);
// make 2 textures by attaching them to framebuffers and rendering to them
const texFbPair1 = createTextureAndFramebuffer(gl, 64, 64);
const texFbPair2 = createTextureAndFramebuffer(gl, 64, 64);
const buf = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buf);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
-1, -1,
1, -1,
-1, 1,
-1, 1,
1, -1,
1, 1,
]), gl.STATIC_DRAW);
function setAttributes(buf, positionLoc) {
gl.enableVertexAttribArray(positionLoc);
gl.vertexAttribPointer(positionLoc, 2, gl.FLOAT, false, 0, 0);
}
const colorPrgPositionLoc = gl.getAttribLocation(colorProgram, "position");
setAttributes(buf, colorPrgPositionLoc);
const colorLoc = gl.getUniformLocation(colorProgram, "color");
const colorProgMatrixLoc = gl.getUniformLocation(colorProgram, "matrix");
// draw red rect to first texture through the framebuffer it's attached to
gl.useProgram(colorProgram);
gl.bindFramebuffer(gl.FRAMEBUFFER, texFbPair1.fb);
gl.viewport(0, 0, 64, 64);
gl.uniform4fv(colorLoc, [1, 0, 0, 1]);
gl.uniformMatrix4fv(colorProgMatrixLoc, false, [
0.5, 0, 0, 0,
0,.25, 0, 0,
0, 0, 1, 0,
.2,.3, 0, 1,
]);
gl.drawArrays(gl.TRIANGLES, 0, 6);
// Draw a blue rect to the second texture through the framebuffer it's attached to
gl.bindFramebuffer(gl.FRAMEBUFFER, texFbPair2.fb);
gl.viewport(0, 0, 64, 64);
gl.uniform4fv(colorLoc, [0, 0, 1, 1]);
gl.uniformMatrix4fv(colorProgMatrixLoc, false, [
0.25, 0, 0, 0,
0,.5, 0, 0,
0, 0, 1, 0,
.2,.3, 0, 1,
]);
gl.drawArrays(gl.TRIANGLES, 0, 6);
// Draw both textures to the canvas
gl.bindFramebuffer(gl.FRAMEBUFFER, null);
gl.viewport(0, 0, gl.canvas.width, gl.canvas.height);
const mixPrgPositionLoc = gl.getAttribLocation(mixProgram, "position");
setAttributes(buf, mixPrgPositionLoc);
const mixProgMatrixLoc = gl.getUniformLocation(mixProgram, "matrix");
const tex1Loc = gl.getUniformLocation(mixProgram, "tex1");
const tex2Loc = gl.getUniformLocation(mixProgram, "tex2");
gl.useProgram(mixProgram);
gl.uniform1i(tex1Loc, 0);
gl.uniform1i(tex2Loc, 1);
gl.activeTexture(gl.TEXTURE0 + 0);
gl.bindTexture(gl.TEXTURE_2D, texFbPair1.tex);
gl.activeTexture(gl.TEXTURE0 + 1);
gl.bindTexture(gl.TEXTURE_2D, texFbPair2.tex);
gl.uniformMatrix4fv(mixProgMatrixLoc, false, [
1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1,
]);
gl.drawArrays(gl.TRIANGLES, 0, 6);
function createTextureAndFramebuffer(gl, width, height) {
const tex = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, tex);
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);
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);
const fb = gl.createFramebuffer();
gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
gl.framebufferTexture2D(
gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
return {tex: tex, fb: fb};
}
canvas { border: 1px solid black; }
<script src="https://twgljs.org/dist/2.x/twgl.min.js"></script>
<canvas></canvas>
The only functional difference between the first program and the second is how the textures got their data. In the first example the textures got their data from a canvas 2d. In the 2nd example the textures got their data by rendering to them using WebGL.
As for why your example doesn't blend textures, in order to blend 2 textures you need a shader that uses two textures.

Categories

Resources