Transparency in SceneJS
In this tutorial I’ll show you how to
- make objects transparent,
- switch transparency on and off
- vary the opacity of transparent objects
- pick through transparent objects, to pick objects behind them
- depth-sort transparent objects for correct alpha-blending order
When you’ve read this tutorial, you might be interested to read about texturing your transparency with Alpha Maps.
#Making objects transparent
As usual, we’ll point SceneJS at where you keep your plugins:
Then we’ll create the scene below, in which the outer blue box has a transparent material which allows you to see the teapot within it.
Run this demo
The code for this example is shown below. Our outer blue box is wrapped with a
flags node, which works in conjunction with
material node to make it transparent.
Things to note:
transparentflag on the
flagsnode around the outer blue box which enables it to be transparent.
materialnode around the blue box has an
0.4, which works in conjuction with the
flagsnode to make the box 60% transparent.
- The box would be invisible if
alphawas 0.0, and opaque if
SceneJS automatically ensures that all transparent objects are rendered after opaque objects. If the transparent box was
rendered before the teapot, then the teapot would not be visible because its fragments would have been rejected by the
WebGL depth test because the pixels for the box would already be in the depth buffer,
with closer depth values. Graphics geeks will note that there may be a problem with depth buffer rejection if we have
multiple transparent objects. I’ll address that in the following section on Transparency Sorting.
When designing SceneJS, I had considered just making the
transparent property on
flags nodes true by default.
That way, an
alpha value less than 1.0 would be sufficient to make the box transparent, no
flags node required. However,
that doesn’t quite fit with alpha mapping (see tutorial on Alpha Maps). We might have an
alpha of 1.0, then
have an alpha map that modifies it for fragments within the shader, which fails to create transparency for those because SceneJS has already
determined that the geometry is opaque. That’s one reason we have
flags in the mix, other being that we can use them
to easily switch transparency on and off for chunks of our scenes.
Switching transparency on and off
You can make the box opaque by flipping the
transparent flag on that
You can make the box less transparent my increasing the
alpha on its
Picking through transparent objects
Want to be able to pick the teapot through the box? That’s covered in the
tutorial on Picking in SceneJS.
It’s tricky making multiple overlapping transparent objects look good in WebGL. In this section I’ll describe the problem, some solutions that don’t work so well with WebGL, and a partial solution that SceneJS provides using prioritised render bins.
SceneJS renders transparent objects using the standard alpha blending technique,
where it combines each transparent object’s translucent colour with the colours of the objects behind it.
As mentioned earlier, there’s one catch: SceneJS may also be using the depth buffer’s Z-test to solve visibility for overlapping objects, which works by ensuring that each pixel is written to the colour buffer only if its Z-depth is closer than any pixel already at the same location.
The example below shows the problem. If we were to render the outer green box first, then when we try to render the inner yellow and blue boxes, the pixels of those boxes will not be rendered, because their Z-depths are greater than the pixels already rendered for the outer green box.
Run this example.
We therefore need to order the rendering of the boxes from the innermost box outwards, so that the pixels of each box don’t get rejected by the depth test, and thus get the chance to be blended with each other in the colour buffer - a technique called transparency sorting.
Solutions that don’t work so well in WebGL
Algorithms to find that order automatically are simple enough, but not easy to do efficiently on WebGL.
Painter’s Algorithm or BSP? Nope.
Ideally, I would have liked to use the painters algorithm, or a BSP tree,
which would render all the faces in the scene in order from furthest face first, through to closest face last.
Two show stoppers exist with that though:
- In WebGL we don’t get that kind of granular access to individual faces. Rendering the faces of a mesh is a batch operation, where we bind the vertex arrays for the mesh, then do a draw call to render them in one hit.
- If we were able to draw each face individually (like we could back in the days of fixed-function OpenGL), then the constant switching of colours and textures for each face would make for some horrendously inefficient GPU state changes.
Depth peeling? Nope.
I had also considered depth peeling, which is essentially an order-independent
technique in which you render the scene multiple times, each time clipping the objects to a min-max layer, which you
move forwards each time. Even though that’s awesome for solving our problem for nested objects, like our boxes, it requires multiple
passes on the draw list for each frame. That would severely impact SceneJS’ efficiency at rendering large quantities
of objects, which is a priority feature.
Solution: Ordered render bins
In the end I went for the lowest common denominator and simply allowed you to organise scene objects within
These work by manually partitioning the objects into prioritised render bins, ordered so that the objects within the
layer with the lowest priority are rendered first.
Note that since the
layer node’s priorities can be
dynamically reassigned, the way is also open to support some sort of automatic object Z-sorting later if needed.
Let’s have a look at how we use the
layer nodes to render our boxes in the correct order for transparency.
Here’s the scene that renders the nested boxes in the scene above.
Things to note:
- The boxes are wrapped with
priorityproperties set to order their rendering so that the innermost box renders first
- Anything not within a
layernode is implicitly at priority 0. The
layeraround the middle box is therefore actually redundant.
Changing layer priorities
You can change the
priority on the
layer nodes at any time. Let’s change the priority for the green box:
That’s going to make the green outermost box render before the innermost blue box, which is actually going to wreck our
Performance tip: changing those priorities causes SceneJS to re-sort its internal display list, so there will be a performance hit if you do that frequently.
In this tutorial I’ve shown you how to use the
material node in conjunction with
flags nodes to make geometry
appear transparent. I also touched on the classic problem of making multiple overlapping transparent objects look good,
and described the partial solutuion provided by SceneJS through its
layer nodes, which order the rendering of geometries
so that they alpha-blend nicely with one another.
- Learn how to texture your transparency using Alpha Maps