Blend Two Canvases Onto One With WebGL
What I'm trying to do is blend two canvases onto a single canvas for a drawing app I am creating. I know Javascript very well, but I really don't have any clue where to start with
Solution 1:
There's an example of drawing with images here: https://webglfundamentals.org/webgl/lessons/webgl-image-processing.html
WebGL doesn't care if the sources are images, canvases or video. So change the samples from
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, someImage);
to
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, someCanvas);
Then write a fragment shader to blend the 2 textures as in
precision mediump float;
// our 2 canvases
uniform sampler2D u_canvas1;
uniform sampler2D u_canvas2;
// the texCoords passed in from the vertex shader.
// note: we're only using 1 set of texCoords which means
// we're assuming the canvases are the same size.
varying vec2 v_texCoord;
void main() {
// Look up a pixel from first canvas
vec4 color1 = texture2D(u_canvas1, v_texCoord);
// Look up a pixel from second canvas
vec4 color2 = texture2D(u_canvas2, v_texCoord);
// return the 2 colors multiplied
gl_FragColor = color1 * color2;
}
You'll need to setup the 2 textures and tell your GLSL program which texture units you put them on.
function setupTexture(canvas, textureUnit, program, uniformName) {
var tex = gl.createTexture();
updateTextureFromCanvas(tex, canvas, textureUnit);
// 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);
var location = gl.getUniformLocation(program, uniformName);
gl.uniform1i(location, textureUnit);
}
function updateTextureFromCanvas(tex, canvas, textureUnit) {
gl.activeTexture(gl.TEXTURE0 + textureUnit);
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
}
var tex1 = setupTexture(canvas1, 0, program, "u_canvas1");
var tex2 = setupTexture(canvas2, 1, program, "u_canvas2");
Sample here:
function main() {
var canvas1 = document.getElementById("canvas1");
var canvas2 = document.getElementById("canvas2");
var ctx1 = canvas1.getContext("2d");
var ctx2 = canvas2.getContext("2d");
ctx1.fillStyle = "purple";
ctx1.arc(64, 64, 30, 0, Math.PI * 2, false);
ctx1.fill();
ctx2.fillStyle = "cyan";
ctx2.fillRect(50, 10, 28, 108);
// Get A WebGL context
var canvas = document.getElementById("webgl");
var gl = canvas.getContext("webgl");
if (!gl) {
return;
}
// setup GLSL program
var program = twgl.createProgramFromScripts(gl, ["2d-vertex-shader", "2d-fragment-shader"]);
gl.useProgram(program);
// look up where the vertex data needs to go.
var positionLocation = gl.getAttribLocation(program, "a_position");
var texCoordLocation = gl.getAttribLocation(program, "a_texCoord");
// provide texture coordinates for the rectangle.
var texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
0.0, 0.0,
1.0, 0.0,
0.0, 1.0,
0.0, 1.0,
1.0, 0.0,
1.0, 1.0]), gl.STATIC_DRAW);
gl.enableVertexAttribArray(texCoordLocation);
gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
// lookup uniforms
var resolutionLocation = gl.getUniformLocation(program, "u_resolution");
// set the resolution
gl.uniform2f(resolutionLocation, canvas1.width, canvas1.height);
// Create a buffer for the position of the rectangle corners.
var buffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, buffer);
gl.enableVertexAttribArray(positionLocation);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
// Set a rectangle the same size as the image.
setRectangle(gl, 0, 0, canvas.width, canvas.height);
function setupTexture(canvas, textureUnit, program, uniformName) {
var tex = gl.createTexture();
updateTextureFromCanvas(tex, canvas, textureUnit);
// 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);
var location = gl.getUniformLocation(program, uniformName);
gl.uniform1i(location, textureUnit);
}
function updateTextureFromCanvas(tex, canvas, textureUnit) {
gl.activeTexture(gl.TEXTURE0 + textureUnit);
gl.bindTexture(gl.TEXTURE_2D, tex);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, canvas);
}
var tex1 = setupTexture(canvas1, 0, program, "u_canvas1");
var tex2 = setupTexture(canvas2, 1, program, "u_canvas2");
// Draw the rectangle.
gl.drawArrays(gl.TRIANGLES, 0, 6);
}
function setRectangle(gl, x, y, width, height) {
var x1 = x;
var x2 = x + width;
var y1 = y;
var y2 = y + height;
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array([
x1, y1,
x2, y1,
x1, y2,
x1, y2,
x2, y1,
x2, y2]), gl.STATIC_DRAW);
}
main();
canvas {
border: 2px solid black;
width: 128px;
height: 128px;
}
<script src="https://twgljs.org/dist/3.x/twgl.min.js"></script>
<!-- vertex shader -->
<script id="2d-vertex-shader" type="x-shader/x-vertex">
attribute vec2 a_position;
attribute vec2 a_texCoord;
uniform vec2 u_resolution;
varying vec2 v_texCoord;
void main() {
// convert the rectangle from pixels to 0.0 to 1.0
vec2 zeroToOne = a_position / u_resolution;
// convert from 0->1 to 0->2
vec2 zeroToTwo = zeroToOne * 2.0;
// convert from 0->2 to -1->+1 (clipspace)
vec2 clipSpace = zeroToTwo - 1.0;
gl_Position = vec4(clipSpace * vec2(1, -1), 0, 1);
// pass the texCoord to the fragment shader
// The GPU will interpolate this value between points.
v_texCoord = a_texCoord;
}
</script>
<!-- fragment shader -->
<script id="2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
// our 2 canvases
uniform sampler2D u_canvas1;
uniform sampler2D u_canvas2;
// the texCoords passed in from the vertex shader.
// note: we're only using 1 set of texCoords which means
// we're assuming the canvases are the same size.
varying vec2 v_texCoord;
void main() {
// Look up a pixel from first canvas
vec4 color1 = texture2D(u_canvas1, v_texCoord);
// Look up a pixel from second canvas
vec4 color2 = texture2D(u_canvas2, v_texCoord);
// return the 2 colors multiplied
gl_FragColor = color1 * color2;
}
</script>
<!-- fragment shader -->
<script id="aa2d-fragment-shader" type="x-shader/x-fragment">
precision mediump float;
// our texture
uniform sampler2D u_canvas1;
uniform sampler2D u_canvas2;
// the texCoords passed in from the vertex shader.
varying vec2 v_texCoord;
void main() {
gl_FragColor = texture2D(u_canvas1, v_texCoord);
}
</script>
<canvas id="canvas1" width="128" height="128"></canvas>
<canvas id="canvas2" width="128" height="128"></canvas>
<canvas id="webgl" width="128" height="128"></canvas>
Solution 2:
I think it will be simpler for you to just use the canvas 2D API.
You can first draw canvas A in canvas C then change canvas C global opacity before drawing canvas B in canvas C.
You can also change the way the canvas are blend using globalCompositeOperation
var canvasA = document.getElementById('C');
var canvasB = document.getElementById('C');
// The target canvas
var canvasC = document.getElementById('C');
var ctx = canvasC.getContext('2d');
ctx.drawImage(canvasA, 0,0);
ctx.globalAlpha = 0.5;
ctx.drawImage(canvasB,0, 0);
Post a Comment for "Blend Two Canvases Onto One With WebGL"