Future of alphaMode
In Blender's legacy EEVEE renderer, a material has a "Blend Mode" property that controls the interpretation of the alpha value produced from the node graph. For example, the nodes could calculate Alpha=0.5, but if the Blend Mode was "Opaque", the alpha value would be ignored and it would be rendered opaque. Both the importer and exporter have up to this point leaned on this feature to represent glTF's alphaMode+alphaCutoff properties.
However, this has only ever worked in EEVEE. In Cycles, alpha always has the normal interpretation as semi-transparency/coverage. If you want a surface to be opaque, the node graph should calculate Alpha=1, and not rely on an "after the fact" reinterpretation of the alpha value.
With Blender 4.1's introduction of the EEVEE-Next renderer with no Blend Mode, intended to eventually replace current (legacy) EEVEE, this addon should probably start thinking about moving to encoding alphaMode entirely in the node graph.
That would mean
| alphaMode | Blender Implementation |
|---|---|
| OPAQUE | 1.0→Alpha Socket |
| MASK | glTF alpha→[Math node: X >= alphaCutoff]→Alpha socket |
| BLEND | glTF alpha→Alpha socket |
Import
- OPAQUE and BLEND should already be correct.
- For MASK, the base color code would learn how to insert a Greater-Than node in front of the Alpha socket (same as it worked in 2.79).
- It can continue setting the Eevee Blend Mode for now.
Export
- The alphaMode+alphaCutoff would need to be gathered from the Alpha socket. Constant 1 = OPAQUE, Greater-Than node = MASK, otherwise = BLEND.
- Backwards compatibility considerations with current Blend Mode based setups?
Thoughts?
Note: EEVEE-next is postponed to 4.2
@julienduroure Did you decide if this idea sounds good to you?
This approach sounds good to me
Okay, the next problem is how to implement MASK mode. Spec says
5.19.10. material.alphaCutoff
Specifies the cutoff threshold when in MASK alpha mode. If the alpha value is greater than or equal to this value then it is rendered as fully opaque, otherwise, it is rendered as fully transparent. A value greater than 1.0 will render the entire material as fully transparent.
ie.
alpha = alpha >= cutoff ? 1.0 : 0.0;
The problem is Blender does not have a Greater-Than-Or-Equal node, only a Greater-Than node. So how to implement?
Ideas
- When cutoff=0.5, round(alpha). Rounds 0.5 up to 1.0, so it's correct. Best by far, but only works for cutoff=0.5.
- alpha > cutoff. ie. we simply do not guarantee correctness at the cutoff value. Simple, not correct.
- 1 - (alpha < cutoff). Correct, but requires two nodes, annoying to create.
- alpha > cutoff - epsilon. Complicated, difficult to know whether it will actually be correct.
- round(alpha * (0.5/cutoff)). ie. scale so cutoff=0.5, then round. The scaling would be absorbed into the alpha factor. Algebraically correct and superset of 1, but kind of complicated and irreversibly mixes the alpha factor and cutoff together.
What do you think? Can you think of another way to do it?
Hello! I'd like to suggest that during the import process, the Render Method could be set to "Blended" for materials utilizing alphaMode="BLEND" since it replaces Blend Mode: Alpha Blend
Material blend modes have been replaced by Material Render methods. Dithered render method replaces Hashed blend mode. Blended replaces Alpha blended. Alpha clipped should be solved in the shader.
This is what is already planned. See here : https://github.com/KhronosGroup/glTF-Blender-IO/blob/49b79c301d21d6f12044016f10a34544d0f389f7/addons/io_scene_gltf2/blender/imp/gltf2_blender_material.py#L74
EEVEE next is now default since yesterday. I am out of office this week, PR will be merged next week
Merged. Thanks!
@julienduroure
This is what is already planned.
Sorry for bothering you but I'd like to know if this is still planned.
Currently, the function set_eevee_blend_method sets mat.blend_method to "BLEND", which only affects EEVEE Legacy. For EEVEE Next mat.surface_render_method should be set to "BLENDED" to match that. By default it's equal to "DITHERED", which matches the "HASHED" blend method in EEVEE Legacy.
Also, I think in cases where a material uses the "BLENDED" render method alphaMode can be always set to BLEND on export (even if the alpha channel is unused) since this mode might also disable depth testing and affect the rendering order.
Also, I think in cases where a material uses the "BLENDED" render method alphaMode can be always set to BLEND on export (even if the alpha channel is unused)
I disagree. Blender modes DITHERED and BLENDED are two approximations to the same ideal OIT result, also implied by glTF's BLEND. Essentially they let you trade noise for order-independence. These do not correspond to anything in glTF, because glTF leaves the choice of OIT method entirely up to the implementation. Therefore surface_render_method should be completely ignored by the exporter.
For importing, OPAQUE and MASK should use DITHERED, since DITHERED is guaranteed correct and noiseless when we know alpha is always 0 or 1. For BLEND, either DITHERED or BLENDED is correct, because, again, the choice is entirely up to us as the glTF consumer.
since this mode might also disable depth testing and affect the rendering order.
This is not implied by glTF's BLEND mode. These are concepts that exist only in certain rendering implementations, and BLEND specifies only the ideal result, not any particular rendering method.
Don't make the mistake of thinking BLEND corresponds to Blender BLENDED or to naive glEnable(GL_BLEND) just because of the names. It's perfectly possible for a glTF viewer to use alpha hashing for its BLEND approximation for example.
Here's what the spec says about BLEND
BLEND - The rendered output is combined with the background using the “over” operator as described in [Porter and Duff's "Compositing digital images" paper].
Implementation Note for Real-Time Rasterizers
Real-time rasterizers typically use depth buffers and mesh sorting to support alpha modes. The following describe the expected behavior for these types of renderers. [...]
BLEND - Support for this mode varies. There is no perfect and fast solution that works for all cases. Client implementations should try to achieve the correct blending output for as many situations as possible. Whether depth value is written or whether to sort is up to the implementation. For example, implementations may discard pixels that have zero or close to zero alpha value to avoid sorting issues.
The glTF spec says:
BLEND - The rendered output is combined with the background using the “over” operator as described in Compositing digital images.
This implies some order and, as far as I know, that is not how the HASHED method works. It's rather a hack that gives an illusion of transparency that works by randomly replacing some fragments without combining them.
The previous color will be overwritten by the surface color, but only if the alpha value is above a random clip threshold.
Use noise to dither the binary visibility
The BLENDED method would fit better in this case since it uses forward rendering for translucent meshes and combines fragments.
Also, the spec doesn't necessarily imply that OIT techniques should be used. Instead, it's up to each implementation to decide how it handles the ordering issue. It even acknowledges that
Real-time rasterizers typically use depth buffers and mesh sorting to support alpha modes.
Maybe, that's why the render_method should not be ignored.
Both HASHED and BLENDED are approximations to the ideal OIT result. Neither is strictly a better fit to BLEND than the other, that's why they both exist.
Both HASHED and BLENDED are approximations to the ideal OIT result.
HASHED is an approximation to the ideal OIT result but BLENDED is not order independent. I looked at the source code of EEVEE Next and it seems like it still sorts translucent objects based on their world location.
https://github.com/blender/blender/blob/d1782f13c81004646f7cd6050c9a6d8c5dfcde7b/source/blender/draw/engines/eevee_next/eevee_pipeline.cc#L383
https://github.com/blender/blender/blob/d1782f13c81004646f7cd6050c9a6d8c5dfcde7b/source/blender/draw/intern/draw_pass.hh#L476
https://docs.blender.org/manual/en/4.2/render/eevee/materials/settings.html#sorting-problem
When writing to the color buffer using transparent blend modes, the order in which the color blending happens is important as it can change the final output color. As of now EEVEE does not support per-fragment (pixel) sorting or per-triangle sorting. Only per-object sorting is available and is automatically done on all transparent surfaces based on object origin.
Neither is strictly a better fit to BLEND than the other, that's why they both exist.
They have their own use cases but why wouldn't BLENDED be a better fit if the way it works seems to be closer to what the glTF spec describes as blending (combining colors using "over" operator)?
BLENDED is not order independent. I looked at the source code of EEVEE Next and it seems like it still sorts translucent objects based on their world location.
Yes. If BLENDED were completely order-independent it would be exact and not an approximation.
The trade-off is DITHERED is robust and order-independent but is noisy, while BLENDED is exact when you can ensure there are no sorting issues, but can be catastrophically wrong when you can't.
They have their own use cases but why wouldn't BLENDED be a better fit if the way it works seems to be closer to what the glTF spec describes as blending (combining colors using "over" operator)?
Because it isn't closer. DITHERED is also an approximation to the "over" operator!
For an obvious example, consider the case where every fragment in the whole frame has alpha either 0 or 1. Then DITHERED is exactly correct, whereas BLENDED may still have arbitrarily large sorting issues.
Anyway, there doesn't seem to be any point to it in the first place. If there's a Round or >= node, we have to use MASK mode regardless of BLENDED/DITHERED, since that's the only way to get that effect in glTF. And if the Alpha is 1, there's no point to using BLEND instead of OPAQUE in the first place. In all other cases we already use BLEND regardless of DITHERED/BLENDED.
BLENDED and DITHERED are both approximations to the correct result. However, only DITHERED is an approximation to the "over" operator, BLENDED does combine fragments.
BLENDED is exact when you can ensure there are no sorting issues, but can be catastrophically wrong when you can't.
DITHERED can also be wrong. For instance, in this case two planes occupy the same space. When the render method is BLENDED the second plane is correctly rendered on top of the first one.
if the Alpha is 1, there's no point to using BLEND instead of OPAQUE
Reordering and disabling depth could be that reason.
In all other cases we already use BLEND
It currently works for EEVEE Legacy. glTF materials that use alphaMode=BLEND end up using two different render methods in different Blender rendering engines.
As I've already said, BLEND does not mean "render in a second pass and disable depth".
I realize that but as glTF spec says that's how it's usually handled by real time render engines (which glTF was initially intended for) so this probably should be taken into consideration
It literally says nothing about if the depth test should pass on equality.
I don't really have a problem with importing BLEND as BLENDED btw. And the cloud example will export as BLEND regardless of surface_render_method.
It literally says nothing about if the depth test should pass on equality.
Right, but it seems like different rendering engines are able to correctly handle this case especially if one of the planes uses alphaMode=BLEND
I don't really have a problem with importing BLEND as BLENDED btw
Thanks. I appreciate that since this otherwise could be somewhat problematic. I apologize if this conversation took longer than expected.
Current status:
- There is a PR for Blender that will redirect
blend_methodtosurface_render_methodin the Python API. This means glTF OPAQUE and MASK will import as Blender DITHERED and BLEND will import as BLENDED, no code changes needed on the glTF side. (Of course, it will still be good to migrate to the new API after ~~that PR is merged~~ EEVEE Legacy is removed.) - There is an issue for automatically updating old .blend files that used Alpha Clip to use a Greater-Than node. For backwards compatibility reasons, the glTF exporter will need to recognize a > node and export it as MASK.
- The glTF importer/exporter needs to make sure the node setup it recognizes is compatible with USD import/export, since USD has the same feature (called
opacityThresholdthere). Post about this on the forums
Also this issue should probably be reopened to track progress on this for the 4.2 release.
There is a PR for Blender that will redirect blend_method to surface_render_method in the Python API. This means glTF OPAQUE and MASK will import as Blender DITHERED and BLEND will import as BLENDED, no code changes needed on the glTF side. (Of course, it will still be good to migrate to the new API after that PR is merged.)
I will notify when this PR will be merged. In case I miss it, feel free to ping me
There is an issue for automatically updating old .blend files that used Alpha Clip to use a Greater-Than node. For backwards compatibility reasons, the glTF exporter will need to recognize a > node and export it as MASK.
As you implement the initial work on this, do you plan to propose a PR for this enhancement?
The glTF importer/exporter needs to make sure the node setup it recognizes is compatible with USD import/export, since USD has the same feature (called opacityThreshold there). Post about this on the forums
I have in my TODO list to have a talk with USD devs to detail some area where we should be more aligned / discuss
For backwards compatibility reasons, the glTF exporter will need to recognize a > node and export it as MASK.
diff --git a/addons/io_scene_gltf2/blender/exp/material/gltf2_blender_search_node_tree.py b/addons/io_scene_gltf2/blender/exp/material/gltf2_blender_search_node_tree.py
index b33fa544..2960ca58 100644
--- a/addons/io_scene_gltf2/blender/exp/material/gltf2_blender_search_node_tree.py
+++ b/addons/io_scene_gltf2/blender/exp/material/gltf2_blender_search_node_tree.py
@@ -512,10 +512,6 @@ def gather_alpha_info(alpha_nav):
#
# If detected, alpha_nav is advanced to point to the new Alpha socket
# and the alphaCutoff value is returned. Otherwise, returns None.
-#
-# Nodes will look like:
-# Alpha -> [Math:Round] -> Alpha Socket
-# Alpha -> [Math:X < cutoff] -> [Math:1 - X] -> Alpha Socket
def detect_alpha_clip(alpha_nav):
nav = alpha_nav.peek_back()
if not nav.moved:
@@ -529,7 +525,7 @@ def detect_alpha_clip(alpha_nav):
# Detect 1 - (X < cutoff)
# (There is no >= node)
- elif nav.node.type == 'MATH' and nav.node.operation == 'SUBTRACT':
+ if nav.node.type == 'MATH' and nav.node.operation == 'SUBTRACT':
if nav.get_constant(0)[0] == 1.0:
nav2 = nav.peek_back(1)
if nav2.moved and nav2.node.type == 'MATH':
@@ -546,6 +542,21 @@ def detect_alpha_clip(alpha_nav):
alpha_nav.assign(nav2)
return in0
+ # Detect (X > cutoff)
+ # Wrong when X = cutoff, but backwards compatible with legacy
+ # Alpha Clip setup
+ if nav.node.type == 'MATH':
+ in0 = nav.get_constant(0)[0]
+ in1 = nav.get_constant(1)[0]
+ if nav.node.operation == 'GREATER_THAN' and in1 is not None:
+ nav.select_input_socket(0)
+ alpha_nav.assign(nav)
+ return in1
+ elif nav.node.operation == 'LESS_THAN' and in0 is not None:
+ nav.select_input_socket(1)
+ alpha_nav.assign(nav)
+ return in0
+
return None
Versioning code is now merged here: https://projects.blender.org/blender/blender/commit/38e398b63e80611870b383f68fda5cbd7c17a7c8
I will implement glTF side tomorrow
Thanks scurest for the patch
In attachment a file created with 3.6, then open with 4.2 alpha/beta => So with node generated by versioning file alpha_versioning_converted.blend.zip
Merged. Closing
The blend_method code can be replaced.
Ah, yes, Legacy EEVEE is gone now. Can you confirm everything is ok in #2264 ?
Looks fine.