使用 WebGL 将两个画布混合到一个上

2022-01-17 00:00:00 javascript html5-canvas glsl webgl

我要做的是将两个画布混合到一个画布上,用于我正在创建的绘图应用程序.我非常了解 Javascript,但我真的不知道从哪里开始使用 WebGL,而且由于这不是一项非常艰巨的任务,我假设如果我不使用它会产生更快的处理速度另一个库,例如 Three.js 或其他类似的库.

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 WebGL and since it isn't a very hard task to do, I'm assuming it would be yield quicker processing speeds if I don't use another library like Three.js or others of that sort.

我已经拥有的是用户将要绘制的画布(我们称它们为画布 A 和 B),它们都是隐藏的,而画布 C 正在显示.

What I already have are canvases that the user will be drawing on (Let's call them canvas A and B) which are both hidden and canvas C which is being shown.

<canvas id='C' width=800 height=600></canvas>

<canvas id='A' width=800 height=600 style='display:none'></canvas>
<canvas id='B' width=800 height=600 style='display:none'></canvas>

我已经完成了主绘图应用程序,供用户选择要在其上绘制的图层并在其上绘图,但是我如何能够使用 WebGL 使用某种混合模式将两个图层混合在一起(即:乘) 当用户继续使用 WebGL 编辑画布?

I already have the main drawing app done for the user to pick the layer to draw on and to draw on it, but how would I be able to use WebGL to blend the two layers together using some blend mode (ie: multiply) as the user continues to edit the canvases using WebGL?

起初我尝试在这里关注其他帖子:https://stackoverflow.com/a/11596922/1572938但我很困惑.

At first I tried following the other post here: https://stackoverflow.com/a/11596922/1572938 but I got confused.

如果有人想填补我的 jsfiddle 上的空白,那将非常有效!http://jsfiddle.net/W3fVV/1/

If someone wants to fill in the gaps on my jsfiddle for the other post that would work very well! http://jsfiddle.net/W3fVV/1/

推荐答案

这里有一个用图片绘制的例子:https://webglfundamentals.org/webgl/lessons/webgl-image-processing.html

There's an example of drawing with images here: https://webglfundamentals.org/webgl/lessons/webgl-image-processing.html

WebGL 不关心源是图像、画布还是视频.所以改变样本从

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

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;
}

您需要设置这 2 个纹理并告诉您的 GLSL 程序您将它们放在哪些纹理单元上.

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

示例:

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>

相关文章