Blending (Rendering Partially-Transparent Objects)

Blending Sorting

1. Introduction

Blending is the technique to render partially transparent objects with real-time graphics. Partially transparent objects mean that the algorithm accounts for transparency values between 0.0 and 1.0, e.g. 0.25 or 0.5.

2. Short summary

If you have multiple partially transparent objects in your game, set the Viewport.Items.BlendingSort to:

You can set it in the editor.

If you leave it at default bsNone value, you may experience rendering artifacts: When multiple partially transparent objects are visible at the same screen pixel, things that should be obscured could be rendered instead in front of others.

Read on to understand it better — how do we render partially transparent objects, what is sorting, what and why can go wrong.

3. Terminology

  • Transparency is a value in 0..1 range, where 0 means that object is opaque, and 1 means that object is completely transparent (invisible).

  • Alpha is "opaqueness", it is defined simply as "1.0 - transparency". So alpha = 0.0 is something "completely invisible", alpha = 1.0 is "something totally opaque".

    When you provide an RGBA color (TCastleColor, which is just equal to TVector4) then the last (4th) component is alpha.

    When you provide an RGBA texture, then it includes an alpha channel.

    When you provide a texture without alpha channel (like RGB or grayscale) then it is always treated like alpha is 1.0 everywhere, i.e. it is fully opaque.

4. How various algorithms for transparency work

The engine can handle transparency at each Shape in 3 ways:

  • No handling of transparency, i.e. the object is opaque.

  • Handle transparency using alpha-testing, which means that at each pixel, we test the shape RGBA color (which is a result of mixing material color, per-vertex colors, and texture colors). If the shape color’s alpha is > 0.5 then the pixel is rendered (as if the shape was opaque at this pixel). Otherwise the pixel is not rendered (as if the shape was completely invisible at this pixel).

    While alpha-testing is a less capable algorithm than blending (as alpha-testing cannot account for partial transparency), it also doesn’t have various problems unavoidable with blending, and mentioned on this page. E.g. alpha-testing works without any problems with shadow maps.

  • Finally, engine can handle transparency using blending. This means that all partially-transparent shapes are rendered after all other shapes (that are opaque or use alpha-testing). Moreover,

    • The partially-transparent shapes are rendered with Z-testing but without Z-writing. This means that partially-transparent shapes that are behind opaque objects are correctly hidden, but partially-transparent shapes in front of all opaque objects are always considered visible (even if they are behind other transparent objects). This matches reality, as partially-transparent shapes never fully "obscure" stuff behind them, by definition.

    • When the partially-transparent pixel is rendered, it is mixed with the existing screen color using the "blending equation". By default is it screen_color.rgb = incoming_color.rgb * incoming_color.a + screen_color.rgb * (1 - incoming_color.a). This equation can be configured using Scene.RenderOptions.BlendingSourceFactor, Scene.RenderOptions.BlendingDestinationFactor and can be overridden per-shape using Appearance.blendMode.

    • The partially-transparent shapes are rendered in back-to-front order (by default, when Scene.RenderOptions.BlendingSort is bs3D and it is not overridden by TNavigationInfoNode.BlendingSort in the loaded model). That is because the default blending equation (see above) assumes such order. Some other blending equations do not require sorting, and thus Scene.RenderOptions.BlendingSort may be bsNone, but they look less realistic.

    The "ordering shapes" stage means that each shape is treated as a whole. We sort shape using "distance to the middle of the 3D bounding box" in case of BlendingSort = bs3D. The possible problem: in some cases, shapes may be concave and intertwined in various crazy ways in 3D. It is not possible to strictly say "X is in front of Y" in general for two shapes, because they may be sometimes in front, sometimes behind each other, at each pixel of the screen. So using blending requires that partially-transparent shapes stay simple, preferably convex and not mixed with each other at the same distance from camera.

5. How does engine determine what algorithm to use

Engine makes the decision about blending per-shape.

By default (if TAppearanceNode.AlphaChannel is acAuto) the engine looks at material transparency field, and the texture’s alpha channel. If transparency is > 0, or if the texture has non-trivial alpha channel (this is detected by analyzing alpha contents, see TEncodedImage.AlphaChannel description), then we use blending.

Note that this auto-detection cannot be perfect in all cases. For example, the alpha channel detection at the image (TEncodedImage.AlphaChannel) is a heuristic, with some alpha tolerance. And what happens when multiple textures are used, with different alpha channel? Again, the engine assumes something reasonable, but it may not be what you want. Also, if you use GLSL shader code to set/modify alpha value, then the engine doesn’t know about it (in general, engine never parses your GLSL code).

You can explicitly override this auto-detection using TAppearanceNode.AlphaChannel field. This makes sense when engine doesn’t do what you expect.

  • It can be set by Pascal code.

  • Or, when using X3D model, you can use Apperance.alphaChannel documentation).

  • Or when using glTF. In glTF, it is always explicitly set, glTF format requires it. So the auto-detection is not used for glTF, the imported shapes always have Appearance.alphaChannel <> acNone. You can set the alpha treatment explicitly in Blender material.

6. Sorting

6.1. Sort shapes within one TCastleScene (MyScene.RenderOptions.BlendingSort, default is to sort for 3D)

  • As mentioned above, this is controlled by Scene.RenderOptions.BlendingSort which is bs3D by default, and can be overridden by TNavigationInfoNode.BlendingSort in the loaded model.

  • You can set Scene.RenderOptions.BlendingSort to bs2D for 2D models. This makes their sorting faster. When importing Spine models, they automatically have NavigationInfo.blendingSort = "2D" so they automatically use this sorting.

  • You can set Scene.RenderOptions.BlendingSort to bsNone. This makes rendering faster, but assumes that you will never have more than one partially-transparent shapes visible in front of an opaque shape, or that your blending equation makes the order irrelevant.

You can also override sorting inside the X3D model. E.g. add this to force 2D sorting in X3D classic encoding:

NavigationInfo {
  blendingSort "2D"
}

To request correct blending sorting in 2D, you should set MyScene.RenderOptions.BlendingSort := bs2D (or call MyScene.Setup2D which is just a shortcut for it). Note that you actually don’t need to do this when loading Spine (where we include proper NavigationInfo inside model) or for sprite sheets and images (where there is only 1 layer, so blending sorting doesn’t matter). So actually you only need to care about this when you use "general (3D or 2D)" model format for 2D animation, e.g. you use glTF or X3D to define a 2D animation with layers.

6.2. Sort scenes in viewport (MyViewport.Items.BlendingSort, default none)

By default the engine does not sort the scenes. You need to request the sorting explicitly.

6.2.1. Automatic sorting at every frame

It is easiest to just set Viewport.Items.BlendingSort to something else than bsNone. We have a few algorithms, some better for 2D some for 3D:

  • bs2D - Sort along the Z axis.

  • bs3D - Sort based on the distance from current camera, looking at bounding box.

  • bs3DOrigin - Sort based on the distance from current camera, looking at origin point (point (0,0,0) in local coordinates). This may be better than bs3D in some cases because the origin point is sometimes more "persistent", e.g. animations or billboard orientation may change bounding box, but not origin point.

  • bs3DGround - Sort based on the distance from current camera, looking at origin point (point (0,0,0) in local coordinates) projected on Y=0 plane. This may be better than bs3DOrigin in some cases because it ignores the object height for the sorting purposes.

Note
The current implementation of Viewport.Items.BlendingSort mechanism has some flaws. Namely, it only sorts whole scenes (not shapes) and it only sorts non-recursively the direct children of Viewport.Items.BlendingSort. It is planned to be improved, see roadmap.

6.2.2. Manually doing sorting

An alternative to using Viewport.Items.BlendingSort is to sort manually by an explicit call to a method Viewport.Items.SortBackToFront2D or more general Viewport.Items.SortBackToFront.

You can also do it at design-time, from the CGE editor, using the "Viewport → Sort Items For Correct 2D Blending" menu item.

The drawback is that you have to do it often enough.

In case of 2D, you should call it always after:

  • adding a partially-transparent object,

  • or moving partially-transparent object in the Z (depth) axis.

Luckily, in 2D, the camera position doesn’t affect the bs2D sorting.

In 3D it is more difficult, and in general you should just sort every frame, which means that it will be easier to just rely on automatic Viewport.Items.BlendingSort documented above.

7. 2D drawing of primitives and images

If you draw using DrawPrimitive2D, DrawRectangle etc. — they automatically use blending when provided color has alpha < 1. They take blending factors (that determine the "blending equation" mentioned above) as explicit arguments.

If you draw using TDrawableImage then it automatically determines alpha treatment looking at image contents and the TDrawableImage.Color. You can override alpha treatment by TDrawableImage.Alpha property, there are also properties to determine blending equation: TDrawableImage.BlendingSourceFactor, TDrawableImage.BlendingDestinationFactor.

The above routines are used by user interface rendering, e.g. by TCastleButton or TCastleImageControl rendering, so they follow the same alpha treatment. The TCastleImageControl.AlphaChannel allows to control blending in case of TCastleImageControl, underneath it uses TDrawabbleImage to render.


To improve this documentation just edit the source of this page in AsciiDoctor (simple wiki-like syntax) and create a pull request to Castle Game Engine WWW (cge-www) repository.