3

Edit as of 2016: The "multiply" value is implemented for globalCompositeOperation. The performance is more than sufficient for real-time graphics.


Essentially, I have two canvases (one is an invisible canvas used for drawing) and I want to blend the invisible canvas onto the visible one with the multiply blend mode.

Context.globalCompositeOperation does not include multiply (though it should, in my opinion) and using imageData to manually blend the canvases is too slow (I need to be able to do this at 60fps).

Is there any faster method I could use to blend the canvases? I believe this could be done using WebGL, but I have no experience using it.

nondefault
  • 349
  • 3
  • 14

1 Answers1

5

WebGL's blend modes do not include multiplication (of the source and destination), either. However, you can perform the multiply operation efficiently using WebGL's render-to-texture capabilities:

  1. Let your visible canvas be a WebGL canvas.
  2. Create two textures; call them "source" and "destination".
  3. Render your invisible-canvas content to the "source" texture; this can be done either using WebGL drawing operations directly (using no extra canvas) or by uploading your 2D invisible canvas's contents to the texture (this is a built-in operation):

    var sourceTexture = gl.createTexture()
    gl.bindTexture(gl.TEXTURE_2D, sourceTexture)
    gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, sourceCanvas)
    // ...also set texture filters — see any WebGL tutorial...
    
  4. Render the other content to be multiplied to the "destination" texture.

  5. Finally, targeting the actual visible canvas, use a fragment shader to multiply the "source" and "destination" textures. The techniques here are those used for post-processing effects — I recommend looking up such a tutorial or simple example. Briefly, to apply a fragment shader to the entire canvas, you draw a full-screen quad - geometry that covers the entire viewport. The vertex shader is trivial, and the fragment shader would be like:

    varying vec2 texCoord;
    uniform sampler2D sourceTexture;
    uniform sampler2D destTexture;
    void main(void) {
      gl_FragColor = texture2D(sourceTexture, texCoord) * texture2D(destTexture, texCoord);
    }
    

If you want to do multiple overlapping multiply-blends per frame, then if there are no more than, say, 8, you can expand the above procedure to multiply several textures; if there are lots, you will need to do multiple stages of multiplying two textures while rendering to a third and then exchanging the roles.

If you would like a more specific answer, then please expand your question with more details on how many, and what kind of, graphics you are multiplying together.

legends2k
  • 31,634
  • 25
  • 118
  • 222
Kevin Reid
  • 37,492
  • 13
  • 80
  • 108
  • Thanks for the answer. I am extremely new to WebGL and need some help with the code. I've done some research and found that gl.texSubImage2D can be used to copy canvas contents into a webGL texture. I was thinking to do something like gl.texSubImage2D(GL_TEXTURE_2D,0,0,0,640,480,GL_COLOR_INDEX,type,imageData). However, I am not sure which type to use. Also, I have no idea how to approach the shader part. Thanks in advance. – nondefault Jul 23 '12 at 02:49
  • 1
    @user1175802 I've added some specific sample code to answer your questions. Broadly, I recommend looking up a WebGL tutorial on postprocessing effects (as most of the code would be similar) and then adapting it to your needs. – Kevin Reid Jul 23 '12 at 13:50