Imperfect Shadows - jagged/ripples

All,
I am using DirectionalLight with setShadowCaster on and default shader (render.setShaderAuto). This causes the planar surface on which the shadows are cast appear imperfect with a jagged appearance/ripples/concentric circles (see attached pic).

There have been some previous discussion on this topic in the forum and the solutions ranged from changing depth offset, depth bits, setting two sided, anti-aliasing, film size etc. None of this fixes the issue for me. My graphics card is Radeon RX Vega 10 and I use pandadx9 (but same issue with opengl as well). Iā€™ve heard that shadow issues donā€™t occur in NVidia - but I canā€™t test on it.

Below is relevant code for reference. If it helps, some h (e.g. 180) have lower ripples than others (e.g. 45). The problem is triggered only when shadows are on.

dlight = DirectionalLight(f'dlight')
dlight.setColor((1, 1, 1, 1))
dlnp = render.attachNewNode(dlight)
dlnp.set_pos(0, 0, 0)
dlnp.setHpr(45, -150, 0)
dlight.setShadowCaster(True)
bmin, bmax = self.scene.get_tight_bounds(dlnp)
lens = dlight.get_lens()
lens.set_film_offset((bmin.xz + bmax.xz) * 0.5)
lens.set_film_size(bmax.xz - bmin.xz)
lens.set_near_far(bmin.y, bmax.y)

Wondering if there is some latest suggestions on this topic that I have missed. Thanks in advance.

Have you enabled ā€œBackface Cullingā€ for the respective model-parts in your modeling program?

Or conversely, have you perhaps set your floor-model to be ā€œtwo-sidedā€ (or disabled backface-culling for the floor, perhaps in your modelling program)?

I had tried setting two-sided and it makes no difference. In the below, Plane is the object that represents the floor. I presume setTwoSided is the function you are referring to, or is there another way from panda3d to disable culling?

self.scene.find('**/Plane').setTwoSided(True)

Ah, I was perhaps unclear (for which I apologise): I was concerned that you might have made the plane two-sided, as thatā€™s one potential cause of the problem that youā€™re seeing, I think.

Indeed, that is the method that I had in mind, I do believe!

There are other ways of disabling culling, I believeā€“but Iā€™d expect doing so to make things worse, if anything.

In short, what appears to be happening is that the planeā€™s shadow is being rendered at more or less the same depth as the plane itself. As a result, thereā€™s some depth-fighting occurring, with some shadow-pixels being found to be slightly closer than their corresponding visual-pixels, and some visual-pixels being found to be slightly closer than their corresponding shadow-pixelsā€“hence the artefacting that you see.

Now, usually this sort of thing is handled (in part) by only rendering back-faces into the shadow-buffer (something that should, I think, be happening automatically). The should prevent a given face from being (much) rendered to both the view and the shadow-buffer, and thus should (largely) prevent this sort shadow depth-fighting.

However, it appears that this isnā€™t happening in your case, which is why I thought that perhaps you had made the floor-plane two-sidedā€¦

Appreciate the follow up. Toggling this twoSided flag doesnā€™t help as you noted.

There was a discussion about this same topic a few years ago. e.g. see issue (1) in below link and I even tried setting depth offsets and depth-bits as user rdb suggested. Doesnā€™t improve my case.

Rendering and shading issues on OS X

I am using Windows 10 though. Makes me suspect I am not alone with a small percent of users facing this issue potentially based on graphics card. Perhaps writing a custom shader could help but I guess that wouldnā€™t be trivial for shadows.

Hmmā€¦ The thing is, if itā€™s your graphics card, then why does it not appear to affect the table in your screenshot? I see no ā€œshadow acneā€ (as I believe that itā€™s called) on the tabletop.

Would you be willing to share a file containing the floor-plane, please? Perhaps investigating the model itself will provide some clueā€¦

I have attached a simplest version of the model (removing all unnecessary textures) to simulate the issue in the below zip.

shadow_issue.zip (427.6 KB)

You will find following files:

table1.blend -> The model in blender 3.0.1
table1.glb -> The model exported in glTf from blender to use in panda3d
shadow_issue.py -> The python code for rendering with lights/views etc.
out_panda3d.jpg -> A screenshot of the generated image showing ripples.

You will note in line 104 of python file that I use pandadx9 (opengl doesnā€™t generate shadows in my machine).

Thank you! :slight_smile:

Hmmā€¦ For whatever reason, Iā€™m not seeing shadows at all when I run the program.

However, if I rotate the view to fall beneath the floor-quadā€¦ I still see the floor. Which implies that either the floor is two-sided, or there are two floor-planes, one facing up and once facing down. Either way, I suspect that this may well be the source of the problem.

Duh! You are right - if I go to blender, explicitly set the material settings Backface Culling as True (by default it seems to be False) and then export the glb, the shadow ripples/acne vanish.

I feel a bit embarrassed that this was suggested first up by you all, but I incorrectly relied on the setTwoSided function in panda3d which has let me down:-) I tried both model.find(ā€˜Planeā€™).setTwoSided(False) [which should affect all its children?] and also tried model.find(ā€˜Planeā€™).findAllMaterials()[0].setTwoside(False) but no change in the floor backside being drawn. A mystery for another day then, at least I can now disable it in blender.

Separately, is there a recommended solution for avoiding shadow acne IF backface culling is off then (say we need to rotate and see the floor backside)?

Thanks for your help.

Ah, Iā€™m glad that you found the problem! :slight_smile:

Based on some quick testing, it looks like an override value may be called for in order to overcome the effect of the ā€œCullFaceAttribā€ present in the geometry. Something like this:

self.model.setTwoSided(False, 1)
# "1" being the override value

Note, however, that doing this breaks your table, which seems to be inside-out and only rendering properly because itā€™s two-sidedā€¦

Hmmā€¦ A sufficiently large shadow-bias might do itā€“but it may be that it calls for an especially large bias. (Which might then introduce its own issues.)

Itā€™s likely that the exporter is exporting double-sided planes from Blender as a pair of back-to-back planes, in which case thereā€™s nothing for setTwoSided to do, since itā€™s two separate polygons.

I considered that, but a call to ā€œlsā€ seems to suggest otherwise.

In any case, a call to ā€œsetTwoSidedā€ with an override value does actually seem to work, suggesting that it is a matter of the plane being two-sided, but simply having a priority higher than the default.

Ah, Iā€™d missed that. Good detective work. :slight_smile:

1 Like

I was also very tired of it. I have found advice in several places on the internet to fix this by setting:

base.render.set_depth_offset(1)

But this solution (with a value of +1) did NOT work for me. Until finally I started experimenting myself and by trial and error I came to set a completely different value (-3):

base.render.set_depth_offset(-3)

and in addition, it was necessary to set:

spot.node().set_shadow_caster(True, 8192, 8192)

The spot is my lighting, created by:

spotlight = Spotlight('spotlight')
spot = base.render.attach_new_node(spotlight)

Honestly, Iā€™m not sure why it works, because I donā€™t think it should. But, as the saying goes, when something is stupid but works, that is, it is clearly not that stupid. :slight_smile:
PS: My case is macOS on the M1 processor. You, I see, run on a Windows PC, so my solution may not necessarily work.

You should really be setting this only when rendering the scene from the shadow cameraā€™s point of view, which you can do by manipulating the lightā€™s initial state via light.node().initial_state = light.node().initial_state.set_attrib(DepthOffsetAttrib.make(...))