MultiplyBlending is affecting my canvas' alpha channel?

I’m creating a grey canvas sitting on top of a grid HTML background. I made the canvas grey with

renderer.setClearColor(0x888888, 1);

Then I’m rendering a plane mesh and a simple shader with a sin wave. The sin wave is created with the alpha channel. Where alpha = 1, you see color, where alpha = 0, you see grey.

gl_FragColor.a = step(sinWave, 0.5);

blending: THREE.AdditiveBlending :white_check_mark:

When I switch my material to additive blending, the colors add on top of the grey, as expected:

blending: THREE.MultiplyBlending :x:

However, when I switch my material to multiply blending, the alpha cuts out the grey from my canvas! You can see the HTML background behind it, which is not what I’d like at all.

How can I use MultiplyBlending without it affecting the canvas’ alpha channel? I only want the RGB channels to multiply. I’m setting the renderer’s alpha to false, but that doesn’t change anything:

renderer = new THREE.WebGLRenderer({
	alpha: false,
	premultipliedAlpha: false
});

Here’s a working demo:

Try mesh.renderOrder=-1; or mesh.renderOrder=1;

Thanks, but that doesn’t do anything. If there’s only 1 element to render, then there’s no order to be changed.

From the migration guide (r136r137):

WebGLRenderer now creates the WebGL context with an alpha channel regardless of the value of alpha passed in the constructor. However, the value of alpha is still used by the renderer when clearing the context first thing every frame.

Meaning the alpha parameter just controls the default clear alpha value. But since you define it via setClearColor() in your fiddle, alpha has no effect and can be removed from the constructor.

Because the default framebuffer always has an alpha channel, the result of THREE.MultiplyBlending is actually as expected since the 0 alpha values above the sine curve multiplies to 0 with whatever is behind in the framebuffer. Hence, the HTML background bleeds through.

1 Like

Thank you for that reference to the migration guide! I knew I wasn’t crazy when I remembered having used Multiply in the past :slight_smile:

So is there no way to override this default alpha setting in the renderer anymore? It seems this would ruin the possibility of using Multiply blending for smoke effects or tinted surfaces because it would burn a hole through the canvas.

Maybe if I manually create the WebGL context without alpha, then pass that to the renderer constructor…

Yes, because RGB textures and framebuffers are noticeably slower than RGBA (depending on the device). There is a general WebGL recommendation to not use RGB at all.

If you still need it, providing a custom context is still possible though.

I tried creating a custom WebGL context with alpha: false, and I lose all indication of the sine wave. Here’s what I’m getting vs what I’m trying to achieve:

questionMultiplyNoAlpha questionDesired

It seems that AdditiveBlending gives me a nice result, but I cannot find how to achieve the same with MultiplyBlending. Does setting blending to Additive or Multiply set up its own blendFunc() source and destination combination? If so where can I learn what they are?

I’ve created a more elaborate question on StackOverflow, if anybody is well-versed in blend functions, I would really appreciate the help!

The blend functions are performed on all four channels between the values in the target buffer and the shader output before being written. So if you don’t want transparency you need to make sure that the output alpha channel is always set to 1.0.

Changing the output lines in your shader to these will get you what you want. Here instead of relying on alpha we make the RGB values above the sin wave where we want nothing to change in the buffer to 1.0:

void main()	{

  float sinWave = vUv.y + sin(time * 5.0 + vUv.x * 10.0) * 0.3 - 0.2;
  vec3 col = vec3( vUv.xy, 0.0 );
            
  gl_FragColor.rgb = mix( vec3( 1.0 ), col, step( sinWave, 0.5 ) );
  gl_FragColor.a = 1.0;
            
}
2 Likes

Out of curiosity, why is the additive function not being performed on all channels, but the multiply function is? For example, let’s look at the top-right (yellow) corner of the shader. When alpha = 0, I’m seeing

r: 0.5,
g: 0.5,
b: 0.5,
a: 1.0

but I would expect the following:

questionAdditiveCalc

You can see the gl blending constants used here.

With “premultipliedAlpha = false” the RGB values are multiplied by the alpha value before the blend operation is performed. If “premultipliedAlpha = true” then it behaves like you’re expecting. Premultiplied alpha on it’s own is a separately complicated topic :sweat_smile:

2 Likes

Awesome, thank you for that link! I didn’t know it used blendFuncSeparate(). I thought blendFunc() was complicated enough 2 arguments, this looks like it handles 4 arguments!

I’ll spend tomorrow digging in and figuring out if I can come up with a solution that works with alpha without having to fake it in the RGB channels.

1 Like