Three.js: DoubleSide, transparent material produces artefacts between overlapping front and back faces

Created on 2 Oct 2012  路  17Comments  路  Source: mrdoob/three.js

Hey Team Three,

_Incredible work on the library - I am having an absolute blast on my first WegGL project thanks to you guys : )_

However, I am experiencing some rendering artefacts between overlapping front and back faces when setting a MeshBasicMaterial up like so:

map = THREE.ImageUtils.loadTexture('some_transparent_image.png');
material = new THREE.MeshBasicMaterial({
    map: map,
    transparent: true,
    side: THREE.DoubleSide,
    depthTest: false
});

I have created a quick fiddle to demonstrate the issue I am experiencing:

http://jsfiddle.net/92Mb7/19/

Notice that as you slowly roll the sphere/camera up and down along the X axis, artefacts start to appear as 'bands' between overlapping front and back faces. This is even more evident when depthTest is set to true. However when depthTest is set to true, these artefacts only appear when looking down on the sphere from the top. Conversely, looking at the sphere from the bottom renders the mesh and material as I would expect.

I have spoken to @aerotwist about this and he has explained that it is likely an issue to do with the depth buffer, but was hoping that it was resolvable?

Any aid you can provide would be greatly appreciated :D

Many Thanks,

Matt

Question

Most helpful comment

@MatthewWagerfield Why not try rendering the model twice? First with side = THREE.BackSide, than with side = THREE.FrontSide. This should do the job for all convex geometries without any need to sort objects / triangles or whatever. BTW, I've been always thinking that this particular use-case is the reason to introduce the THREE.BackFace functionality, but now I'm not sure ;)

All 17 comments

So I'm not seeing any issues when it's set to depthTest:false, which suggests it _may_ be a driver issue, depending on what you're using. Are you seeing the same thing in Firefox?

Generally speaking though, as I mentioned, if you have transparency you have to guarantee the draw order as being back to front or disable the depth testing as you can effectively have pixels overdrawn in the depth buffer giving you "missing" areas.

Generally speaking I'd say it's just a reality of working with WebGL and not something I would expect to see automagically handled, although @mrdoob and @alteredq may feel differently! :D

Mmm, tell a lie I _do_ see a little bit of banding, which may well be an issue to do with the doubleSided texturing. I'll bow out as I don't know how that's implemented.

What you are trying to achieve is basically the worst possible scenario for transparency in 3D - self-transparency in a single geometry with full-range alpha values with normal blending, viewable from arbitrary angle.

For your use case I don't think it's possible to get something 100% artefact free (at least not without implementing some novel solution).

Depth sorting works only between objects, not inside a single object. What you would need is to sort all triangles of the sphere, every frame, based on distance to the camera. Which would be a performance killer, that's why we don't do this and only sort per-object.

That's btw also what games usually do - artists try very hard not to create objects with self-transparency and if, then only with on/off transparency so that alphaTest could be applied, or they disable depth writing and pray artefacts wouldn't look too horrible (depthWrite: false).

What @alteredq said.

However... if you really want to achieve this effect, you could split the sphere in 8 equal parts, add them as children of a Object3D and rotate that Objec3D instead. By splitting the sphere in 8 parts your making much unlikely that a part will be transparent with itself.

You may still get some glitches, but it'll be much harder to notice.

What @mrdoob said, just be aware that sorting is done according to a pivot of the object, so if you just naively split full sphere into quadrants, it would be unsortable as all pieces would have exactly the same position.

You would need to move triangles of these pieces so that 0,0,0 would be somewhere in their quadrant and then reconstruct full sphere using inverse transforms.

Would the texturing prove more taxing if you did it this way, or is there a option I've not seen that would help with this kind of mesh breakdown?

Oh course, rendering 8 objects is more taxing than rendering just 1 but should be ok. It's just a solution for this effect.

This. If we are speaking about a single sphere, that should be fine. If it's 1,000 spheres vs 8,000 sphere chunks then it would be painful.

But even then, it would be a single material and chunk transforms can be static, so overhead should be mostly just extra draw calls.

@MatthewWagerfield Why not try rendering the model twice? First with side = THREE.BackSide, than with side = THREE.FrontSide. This should do the job for all convex geometries without any need to sort objects / triangles or whatever. BTW, I've been always thinking that this particular use-case is the reason to introduce the THREE.BackFace functionality, but now I'm not sure ;)

@alteredq I'ts not the worst possible scenario because the object we're working with is convex... if it's not, then the things are getting much more complicated.

@apendua You are right, I completely missed that trick, being a sphere saves it ;)

http://jsfiddle.net/92Mb7/23/

BTW, I've been always thinking that this particular use-case is the reason to introduce the THREE.BackFace functionality, but now I'm not sure ;)

If I remember well, it was for being inside objects, e.g. skydome.

@alteredq or you can do it like this by rendering twice:

http://jsfiddle.net/92Mb7/29/

But here, we also need to perform render.clear() manually to make that work. I guess, that your solution is much more clean :)

WOW! Thanks soooo much guys - this is a great solution and I really appreciate the detailed feedback :D. @alteredq I will be sure to try and avoid "the worst possible scenario for transparency in 3D" in future. I'm just getting started with WebGL, so the underlying complexities of what I was trying to achieve were not immediately obvious to me, but this has cleared a lot up, so thanks again everyone.

Here is another approach.

Use two materials like @alteredq did, but set material.transparent = false for both materials, and add the back-faced material _after_ the front-sided one.

renderer.sortObjects doesn't matter in this case.

http://jsfiddle.net/92Mb7/30/

The material is not transparent, but the texture is.

There is some really clever reason why this works. Maybe someone will tell me. :-)

@alteredq or you can do it like this by rendering twice:

http://jsfiddle.net/92Mb7/29/

But here, we also need to perform render.clear() manually to make that work. I guess, that your solution is much more clean :)

Here you would need to be careful with Lambert / Phong, this wouldn't work properly there. For lit materials sidedness is baked into a shader program, you can't just change it in runtime. You would need to have two materials and alternate between them.

There is some really clever reason why this works. Maybe someone will tell me. :-)

material.transparent doesn't matter if all all objects are transparent (and use normal blending), this flag is just for putting objects into separate transparent and opaque buckets (opaque objects must get rendered before transparent ones).

Now transparent render list is sorted back-to-front while opaque one is sorted front-to-back. I guess for this example, as both spheres are at exactly the same position, sorting doesn't do anything and then front-to-back order renders them in a desired order.

Hello!
I'm also experiencing these rendering artefacts between overlapping front and back faces. Have there been introduced any other solutions to this over last couple of years?
Would be fantastic!

@Anniks Please redirect your question to the three.js forum.

Was this page helpful?
0 / 5 - 0 ratings