Skip to content

Conversation

@mrdoob
Copy link
Owner

@mrdoob mrdoob commented Dec 13, 2025

Related issue: #31246

Description

When MultiplyBlending or SubtractiveBlending is used without premultipliedAlpha = true, the renderer now:

  1. Logs a warning: "Material premultipliedAlpha was set to true because MultiplyBlending and SubtractiveBlending require it."
  2. Internally sets premultipliedAlpha to true so the scene renders correctly

This improves the developer experience by producing correct output while informing users about the requirement.

@mrdoob mrdoob added this to the r183 milestone Dec 13, 2025
@mrdoob mrdoob requested a review from WestLangley December 13, 2025 06:53
@github-actions
Copy link

github-actions bot commented Dec 13, 2025

📦 Bundle size

Full ESM build, minified and gzipped.

Before After Diff
WebGL 355.69
84.54
355.72
84.57
+24 B
+34 B
WebGPU 617.48
171.3
617.52
171.36
+44 B
+63 B
WebGPU Nodes 616.08
171.05
616.13
171.12
+44 B
+71 B

🌳 Bundle size after tree-shaking

Minimal build including a renderer, camera, empty scene, and dependencies.

Before After Diff
WebGL 487.86
119.41
487.88
119.44
+23 B
+33 B
WebGPU 688.93
187.08
688.97
187.14
+42 B
+64 B
WebGPU Nodes 638.77
174.27
638.81
174.34
+42 B
+66 B

@mrdoob
Copy link
Owner Author

mrdoob commented Dec 13, 2025

I bumped into this while working on #32496 and it really broke my flow...

@gkjohnson
Copy link
Collaborator

gkjohnson commented Dec 13, 2025

This isn't correct. These warnings are there for a reason. The values used during blending are fundamentally different so you cannot just use the same formula for premultiplied and non premultiplied alpha values - I've explained the difference between the shader output values used in blending here. There is no way to create a blending formula such that you can multiply the alpha into the RGB channel before multiplying by the destination color.

Here is an example showing the difference in result. It may look okay when the output alpha is set to 1.0 but it is not at all right otherwise. This is using this normal PNG from the repo which is storing color as "straight alpha" (ie non-premultiplied):

image

Top left: premultipliedAlpha = false, NormalBlending
Top right: premultipliedAlpha = true, NormalBlending
Bottom left: premultipliedAlpha = false, Custom blending with the "MultiplyBlending" factors specified
Bottom right: premultipliedAlpha = true, MultiplyBlending

@mrdoob
Copy link
Owner Author

mrdoob commented Dec 14, 2025

Similar to this issue, this is another issue where correctness gets in the way of a nice user development experience.

If the user sets blending: THREE.MultiplyBlending getting this output doesn't feel right:

Screenshot 2025-12-14 at 10 38 40

Using THREE.MultiplyBlending without premultiplyAlpha: true isn't correct, but I don't think I'm going to notice the correctness in the car shadow.

So, is there something we can do that doesn't break the user's development flow even if it's not 100% correct?

Maybe logging a warning but still getting an okay output?

@WestLangley
Copy link
Collaborator

WestLangley commented Dec 14, 2025

If the user sets blending: THREE.MultiplyBlending

I think I will need to understand why you are doing that in this example... EDIT. Oh, I assume you want to render a shadow under the car, and you are using THREE.MultiplyBlending for that.

//

#31246 explains the motivation for the implemented change. It also explains the behavior you should see. I tested it again today, and it is working as intended.

I understand your concern about the premultiplied alpha limitation and the warning, but I am not sure what to do about it so far. A live example may help.

@gkjohnson
Copy link
Collaborator

Using THREE.MultiplyBlending without premultiplyAlpha: true isn't correct, but I don't think I'm going to notice the correctness in the car shadow.

This is because you're using a texture with 1.0 alpha and opacity 1.0. It will work in this case but break as soon as you try to do anything else. Here's what happens if you try to set the opacity to anything other than 1.0 in order to fade the effect:

image

Top left: premultipliedAlpha = false, opacity = 1.0, Custom blending with the "MultiplyBlending" factors specified
Top right: premultipliedAlpha = true, opacity = 1.0, NormalBlending
Bottom left: premultipliedAlpha = false, opacity = 0.2, Custom blending with the "MultiplyBlending" factors specified
Bottom right: premultipliedAlpha = true, opacity = 0.2, MultiplyBlending

Notice that the "multiply" effect is now impossible to fade.

If the user sets blending: THREE.MultiplyBlending getting this output doesn't feel right:

We should decide what to do when a user specifies an invalid combination of settings, then. We can't support every combination of everything and even then not every combination of every setting is even possible (as seen here) but I don't think half-implementations of a feature are the solution. This will just cause worse confusion in other cases. The log also tells you what you can do to fix the issue, which will work just fine unless you've written a custom shader that does not support the "premultipliedAlpha" flag correctly.

Generally I think we should be ferrying people towards "correct" solutions, which the log does. If anything I would consider implicitly treating "premultipliedAlpha" as true if the user is specifying "Multiply" or "Subtractive" blending even if it's set to false and keeping the warning. Then the setting will seem to work as long as a shader that supports premultipliedAlpha is being used.

@mrdoob
Copy link
Owner Author

mrdoob commented Dec 14, 2025

This is because you're using a texture with 1.0 alpha and opacity 1.0. It will work in this case...

If it would work in this case, why are we breaking the blending and throwing and error? 🤔

Shouldn't we check if opacity < 1 before breaking at least?

@gkjohnson
Copy link
Collaborator

gkjohnson commented Dec 14, 2025

If it would work in this case, why are we breaking the blending and throwing and error? 🤔

Shouldn't we check if opacity < 1 before breaking at least?

No because we can't know if the shader is going to output fragments with partial alpha. This only works in your case because your whole texture is alpha = 1.0 and opacity is 1.0. If any of those resulting fragments are < 1.0 from custom shader logic, texture alpha, opacity, etc then the result will be incorrect.

@mrdoob
Copy link
Owner Author

mrdoob commented Dec 14, 2025

I guess the solution here is to solve #32472 then...

@mrdoob mrdoob closed this Dec 14, 2025
@mrdoob mrdoob deleted the multiplyblending branch December 14, 2025 11:11
@mrdoob
Copy link
Owner Author

mrdoob commented Dec 14, 2025

Another option would be switching premultiplyAlpha to true automatically when using these blend modes and warnOnce the user that the engine did that.

Maybe we can do that while we solve #32472.

@gkjohnson
Copy link
Collaborator

Another option would be switching premultiplyAlpha to true automatically when using these blend modes and warnOnce the user that the engine did that.

Yeah this is what I suggested in #32542 (comment):

If anything I would consider implicitly treating "premultipliedAlpha" as true if the user is specifying "Multiply" or "Subtractive" blending even if it's set to false and keeping the warning.

I think this is a reasonable solution. It will only break if the user has written a custom shader that doesn't respect the premultiply alpha flag but I think this is a more simple case and at that point we're already dealing with someone who is a bit more knowledgeable about shaders and blending.

Maybe we can do that while we solve #32472.

I think it would be good to make sure more of the project supports the flag correctly and make sure that at least the examples are encouraging writing shaders that support it. I go back and forth on whether it should be defaulted to "true", though.

Either way with node materials we should be able to make sure that all shaders "just work" with the flag since we can control the final generated shader and make sure the pre multiplication is applied.

@mrdoob mrdoob restored the multiplyblending branch December 17, 2025 02:59
@mrdoob mrdoob reopened this Dec 17, 2025
@mrdoob mrdoob changed the title Renderers: Remove premultipliedAlpha requirement for MultiplyBlending/SubtractiveBlending. Renderers: Set premultipliedAlpha to true when using SubtractiveBlending/MultiplyBlending. Dec 17, 2025
@mrdoob mrdoob force-pushed the multiplyblending branch 2 times, most recently from 9023ca8 to a6a95c1 Compare December 17, 2025 03:23
@mrdoob mrdoob changed the title Renderers: Set premultipliedAlpha to true when using SubtractiveBlending/MultiplyBlending. Renderers: Set premultipliedAlpha to true when using MultiplyBlending/SubtractiveBlending. Dec 17, 2025
@mrdoob mrdoob changed the title Renderers: Set premultipliedAlpha to true when using MultiplyBlending/SubtractiveBlending. Renderers: Set premultipliedAlpha to true when using MultiplyBlending or SubtractiveBlending. Dec 17, 2025
@mrdoob
Copy link
Owner Author

mrdoob commented Dec 17, 2025

@gkjohnson Looks good?

@WestLangley
Copy link
Collaborator

Looks good?

I don't think so. The shader has to be recompiled, too. See #31166 (comment).

@mrdoob
Copy link
Owner Author

mrdoob commented Dec 17, 2025

Oh! So we need to set material.needsUpdate = true too, right?

@WestLangley
Copy link
Collaborator

material.premultipliedAlpha has turned out to be problematic because of its dual purpose, as explained in #31166 (comment).

//

Not to hijack the thread, but in addition, the three.js built-in materials cannot properly handle loading premultiplied alpha textures, such as EXR files, which are premultiplied by default. There is no texture flag that says "this texture already has alpha premultiplied." EDIT -- well, TSL has unpremultiplyAlpha( color ), but that has its own problems when alpha is zero.

Any consideration of improving the API should address both of these issues.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants