Search code examples
3drgl

rgl: How to avoid moire effect in transparent 3D ellipsoids?


In my heplots package, I plot hypothesis & error 3D ellipsoids to represent a multivariate linear test. I find that when the H ellipsoid is actually only 2D, I get an unpleasant moire effect as seen below. Note that the Error ellipsoid does not suffer this defect.

The call to heplot3d in this MWE specifies no wire frame but uses transparent shading of the surfaces.

library(rgl)
library(heplots)
data(penguins, package="palmerpenguins")

peng <- penguins
peng.mod0 <-lm(cbind(bill_length, bill_depth, flipper_length, body_mass) ~ species, data=peng)
heplot3d(peng.mod0, shade=TRUE, shade.alpha=0.2, wire=FALSE, size="effect")

Is there any way to modify the code in this function to avoid this problem? What causes it? Could this have anything to do with normals to the surfaces?

enter image description here


Solution

  • The problem here is that the front and back surfaces of the ellipsoid are being drawn in the same plane. The way rgl draws transparent objects depends on sorting the objects from back to front, but it can only do this approximately: e.g. different parts of two intersecting triangles would require different sorting. In your example, rgl tries to sort the front and back of the flat ellipsoid, but numerical errors mean it kind of randomizes the drawing order, and things look really ugly.

    The easiest way to avoid this is to use back = "cull" in the call to draw the ellipsoid. Then the back won't be drawn at all and things will look much better.

    There will still be problems where the two ellipsoids intersect; fixing that is harder. You would need to break up the red ellipsoid into parts on each side of the blue one and draw them separately. It might also be necessary to draw the parts of the blue one that are inside the red one separately from the parts that are outside. These things are possible now using the clipMesh3d function, but hard to get right.

    EDITED TO ADD:

    A better solution here is to set depth_mask = FALSE when drawing the transparent ellipsoids. (This is another material property, it defaults to TRUE.) Here's the explanation:

    • The "depth mask" determines whether drawing an object will prevent objects drawn later that are behind it from being visible. Opaque objects should always be drawn with depth_mask = TRUE because they should always hide what is behind them.

    • It's harder to decide which setting to use for transparent objects. Because they are sorted before drawing, the depth mask shouldn't matter, but because the sorting is only approximate, sometimes it helps. In your case, you are drawing two shapes that are exactly at the same depth up to rounding error. With depth_mask = TRUE, sometimes the second one isn't drawn because rounding error makes it look further away. If you set depth_mask = FALSE, both will always be drawn, and the ellipses look better. This isn't true for all shapes, but in this case it works.

    • Some calculations I've done suggest using depth_mask = TRUE whenever alpha > 0.5, and depth_mask = FALSE for small alpha values.