nondefault nondefault - 4 months ago 27
Javascript Question

How can I use the multiply blend mode on a canvas in real time?

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.

Answer

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.