Is there a quick hack to render a transparent material beneath non-transparent material?

I’m trying to render an svg decal onto a flat wall surface.

The text elements are drawn separately using a canvas and as textures, and the resulting material has to be transparent.

Due to the way SVGs are layered, each path element is represented as a mesh with a material with depthWrite set to false, and renderOrder set in ascending order. Otherwise there is the potential for layers appearing in the wrong order.
Setting depthWrite to false also helps with potential z-fighting issue with the wall mesh. Due to the limited scope of the camera, it’s not possible to go behind the wall so an incorrect render order isn’t an issue.

A problem arises however with my text, due to its transparency it is always after every other mesh.
The image is not a great example but if you look closely the text is on top of the flower elements. It should be below. And nothing I’ve tried fixes this.

I can understand why this is the default behaviour, but is there a way hack Three’s renderer to revert this behaviour?

1 Like

As always, there are several ways. The first two are:

  • define the non-transparent object as transparent with opacity=1, thus it will be sorted together with the other transparent objects and its renderOrder will define when to render it in relation to them
  • use several renderer.render calls (with specific object as argument), thus you can manually set the render order (however, inside each render, Three.js will still split transparent from non-transparent objects)

Thank you for your quick and helpful response!
Is there a significant performance cost to defining all opaque materials as transparent?

If the rendered scenes are as complex as the sample image above, I’d doubt that you will notice any performance issue. Transparency is often slower than non-transparency, but there are other factors. For example, recent Three.js may render transparent object twice (depending on some properties).

Anyway, there might be other solutions, I will make a few experiments tomorrow. Meanwhile, to make sure that I understand the issue correctly, does this illustration shows what you need:

  • blue object is non-transparent
  • red object is not-transparent
  • black object is transparent (the area inside the circle is transparent) and the whole object is between the two non-transparent objects

Also, a few questions:

  • do you use orthographic projection or perspective?
  • do you have several overlapping transparent objects?

Your illustration correctly depicts the issue.
Also I exclusively use a perspective camera and there is only ever one transparent texture (the text) in any scene.

I just tested your first solution of making all materials transparent and it works as expected, however it doubled the number of draw calls. It was already excessive at 156 but now it’s 298.

I’m also curious why the draw call count is so high to begin with. I assumed the renderer would group together all sequentially ordered objects with the same material and render them in a single call, but it appears to be making a separate call for each mesh.
Is the only way to improve this by using mergeGeometries?

For the doubled number of draw calls, check whether Material.forceSinglePass is true or false.

As for the transparency, I am confused. I did not do anything special, and it works fine (a single transparent object and unchanged order of objects are perfect conditions that should not require any special treatment).

Below is my attempt (there is OrbitControls, so you can change the view point). Only the black circle with (Aa) inside is transparent. Blue objects are behind the circle and they look OK. Red objects are in front of the circle and they look OK too. Maybe I have misunderstood what the problem is?


Edit: The demo above has objects in 3D (i.e. different distance from the viewer). In case you need to have all objects on a flat surface, then I’d suggest to use polygonOffset/Factor/Units. Here is revised demo with flat surface:

I didn’t know about polygonOffset, but it’s not an ideal solution as it requires duplicating materials to order them appropriately.
Instead I’ve set all materials to transparent and set forceSinglePass to true on all except the text material, which has halved the number of draw calls. Thanks!

1 Like