ThreeJS and the transparent problem

thanks, @ [donmccurdy], but i have semitransparency. The png I used in the demo on codepen only has no semitransparency. But in my reale case i use it.

I updated the codepen demo with an png with alpha.

polygonOffset not running. Can you modify my codepen, that it works?

I couldn’t get polygonOffset to work on that example. Only renderOrder and setting the background object to non-transparent worked but neither of those are ideal. polygonOffset should work with a large enough value.

Looking at the sorting functions in ThreeJS, it could be to do with the z value here swapping when rotating the camera. The relevant functions are WebGLRenderList.sort() and WebGLPrograms.painterSortStable and reversePainterSortStable.

It looks like those tests override some of the depth buffer. It checks renderlist.groupOrder, then renderOrder, then z value (I assume camera space z-value of the object), then lastly the id, which would be the order the objects are added to the scene.

If you are ok with modifying the ThreeJS library, you could add a custom condition in that sort function before the others that checks a custom value in the render objects and you’d set these values in the game like the renderOrderGroups I mentioned above.

There’s no satisfactory answer here yet (only workarounds that disable critical parts of the regular rendering abilities of the scene, which causes there to be other issues) so I posted the question on StackOverflow:

Codepen (based on above example, but with more depth/distance between planes, still same issue):

There’s no satisfactory answer here yet (only workarounds that disable critical parts of the regular rendering abilities of the scene, which causes there to be other issues)

I’m not sure what a satisfactory answer would be – there are only workarounds. There is no magic answer that makes transparent rendering just work in every scenario. It’s a difficult problem that you have to work within the limitations of the graphics API to achieve – even on with newer DirectX and OpenGL versions it’s still an issue (though newer raytracing APIs may change this). The only best answer is artistry and to select a combination of “hacks” that work best for your scenario. This is what modern games have to do, as well.

Here are some of the techniques available to render transparency some of which require more effort than others within three.js.

  • Use alphaTest to clip transparent textures. You won’t be able to achieve partial transparency here.
  • Render transparent objects with depth write enabled which will lead to the background clipping you’re seeing.
  • Render transparent objects with no depth write and back to front sorting. This is the most common approach but can result in objects “flipping” order when moving the camera which can be undesireable.
  • Render partial transparency with a dither pattern while still writing to depth. This is also called “screendoor transparency”. Transparent objects will render on top of eachother coherently but the dither pattern may be undesirable. Performance may also be affected negatively.
  • Weighted, blended order independent transparency seems to an approach that takes some weighted average of transparent pixels but again it won’t give you the correct overlap look. But it will avoid “popping” as you move the camera.
  • Depth peeling will give you correct transparency but is performance intensive and requires rendering all the transparent objects in the scene multiple times to properly blend all layers of transparency.
  • Per fragment sorted transparency will give you correct transparency but again is performance intensive and may not even be feasible using current WebGL APIs. This involves creating a linked list of every transparent color and depth at every pixel and sorting them front to back before blending them for the final color.

I’m sure there are other approaches but the point is there’s a lot of options and there’s not one well accepted approach for rendering transparency correctly. You really just have to pick what works for your use case.

8 Likes

I’ve been playing with dithering, i also randomly came across your screen door transparency @gkjohnson example :+1:, in old 2D games transparency was archived by this, while the result was smooth not dithered by the monitor blending pixels.

I’ve tested some approaches to smooth/“undither” the result. I think this could be the best option in terms of cost, this would be a great alternative to the hard/sharp edged alpha test, especially minor alpha transitions like edges for vegetation or hair.

I got a basically perfect looking result with supersampling, some modified blur to average and FXAA at the end, but a doubled resolution isn’t an option. I rendered the alpha masks to an alpha buffer to use for the average pass. Especially for wildly self-intersecting geometries like in vegetation this seems to be the only reasonable option. There was an engine (i see if i find it again) that implemented transparency this way too, though MSAA and other was used, i didn’t followed any approach using higher resolutions or subpixels further as i rely on a multi render target setup and either way, it would get way more expensive.

Another option for some might be enabling alpha to coverage

gl.enable(gl.SAMPLE_ALPHA_TO_COVERAGE);

With multisampling available it will give nice results, still with some minor visible bleeding but good enough, if multisampling isn’t available it will act like alpha test.

You choose a path but there is a big hole making you can’t go to destination, you tried a jump, but it’s too large, you tried to get a rope, but it’s too short, maybe the solution to get to your destination is to try another path!?

Your box menu looks like very much an HTML popup! Why not render it in a hidden DOM with HTML/CSS, capture the view bitmap and project it on a plane in 3D as a texture? Then, you raytrace the mouse pointer and create fake mouse event on the DOM to trigger your actions…

@Fyrestar

Glad you got some use out of it! I’ve had some luck with screen door transparency, as well. Higher resolutions make the pattern much less noticeable nowadays, as well. Using blue noise for the the dither pattern might get an even less noticeable pattern, as well.

@devingfx

Why not render it in a hidden DOM with HTML/CSS, capture the view bitmap and project it on a plane in 3D as a texture

This isn’t actually possible in browsers without heavy manual solutions I don’t believe.


The planes issue laid out in this thread is actually a relatively simple case – you just have to manually sort and render them into the order you want, which renderOrder can be used for.

The moment we do that, and add more objects, the complexity becomes unmanageable. I mean, if there was a way to manage it, then I think WebGLRenderer would be doing exactly that. Yeah, the two planes is a simple example, but it’s a simple example taken from a more complicated app with a lot more objects everywhere.

Beyond Full HD or Full HD at the size of smartphones that pattern will get much less visible yes. Unfortunately at most common screens (still Full HD) it’s very noticeable, but smoothing it out with a proper technique seems almost like some holy grail of order independent transparency for WebGL, without relying on multisampling and with the least cost i guess, even if the quality is a bit lower, as for larger surfaces it sure would blur anything behind it a bit, but that seems rather more of a feature :smiley_cat:

The most common issue with transparency people ask for help over and over again is having partially transparent objects like plants, self-intersections, sorting works good enough for objects like windows, but transparency on vegetation, hair and such is a different case i think screen door transparency is the best candidate, at least for WebGL right now.

1 Like

The moment we do that, and add more objects, the complexity becomes unmanageable… Yeah, the two planes is a simple example, but it’s a simple example taken from a more complicated app with a lot more objects everywhere.

There’s not nearly enough information given to extrapolate out to whatever scenario you’re dealing with. I’d be happy to give more advice but your problem statement is too broad for a domain that relies on handling special cases. This post and your example show transparent parallel planes (or thin boxes) on top of each other without intersecting and a solution has been given with render order to handle that case which can be set dynamically, as well. You’re also provided the ability to override the sort function that three.js uses for transparent objects which may be useful. Regarding complexity of course you have more data and objects to manage but you can write code that handles sorting in a way that suits your app.

I mean, if there was a way to manage it, then I think WebGLRenderer would be doing exactly that.

My whole other post was about how there’s no single general solution for transparency. Three.js has picked a sorting solution that is fast and works “well enough” in the general case (I believe sorting based on distance to the world space origin of the mesh). If you have some notion of the “right” order in which to draw geometry that’s different you’re going to have to program that. But the tools should be there.

It would be sweet if by default the transparency mode in Three.js was one that prioritized accurate visuals (so the issue with the two-parallel-planes example wouldn’t happen), and that there were a option to tell Three which mode to use (perhaps an option passed to WebGLRenderer?).

If you find out which one that is, please let us know. :wink:

1 Like

I guess it would be one of the ones in the above list, but I’ll let the experts decide on that one:

Which one has better performance without too much visual sacrifice?

The current visual sacrifice (as in the above parallel-plane example) is a lot of visual sacrifice.

Even if the default mode remained the current one, it’d still be great if there were an option to change mode.

I don’t think there is any method that consistently gives better visuals without a really unacceptable hit to performance. I’d like to see three.js include an alpha hash or “dither” transparency mode as an alternative:

That can be more predictable than alpha blending, and easier to set up, but the screen door pattern means it’s only visually “better” in certain situations and not others.

Also note that if you’re setting .transparent = true on a material, you should generally be setting .depthWrite = false as well. This is often overlooked.

4 Likes

I never knew that. Why is that?

Doing so can prevent transparent objects from “disappearing” behind other transparent objects when they shouldn’t — more details in https://github.com/mrdoob/three.js/pull/18235.

1 Like

Filtering the dithered areas again can give very good results, but it requires post processing.

Like i said i wouldn’t use it as general solution, as windows for example work fine, but as solution for partial transparent and self intersecting objects, you won’t get any working transparency out of box here except alpha test in the latter case, screen door transparency is alpha test just with dither patterns according to the alpha value.

Here’s another example of alpha hash/dither: