Pmndrs drei vanilla: Questions on baking a static shadow map

Hello :blush: I have a couple of questions concerning shadow map baking. In my application, I plan to render gltf Car models in a three js scene. I would like to have shadows appear automatically under the cars when they are uploaded. Iā€™ve learned how to render shadows using PCFSoftShadowMap but it is tailored towards dynamic shadows. In my application, I think it would be more efficient if I bake a shadow map once the car is loaded and display it under the model. While searching how to do this, I came across the progressive lightmap example and @orion_prime 's work. Orion kindly pointed me towards drei vanilla library which has a demo that bakes shadow maps and updates them on each frame.

I made a codesandbox to experiment and adapt the original drei demo to fit my use case. So now in the demo, a torus geometry and a gltf model are displayed, a static shadow map is generated, and it doesnā€™t update in each frame. I have the following questions and issues:

  • Main issue: For some reason the gltf models do not get a shadow map but three js geometries do. Anyone has an idea what could be the issue here?
  • Have I correctly modified the original demo to achieve my desired goal? I kinda did it with trial and error.
  • I run the function renderShadows() in a for loop because I noticed it improved the shadow map, is this okay? How would you guys do it?
  • Shadow quality was also changing based on the shadowParams (temporal, frame, etc). I donā€™t fully understand them so I would appreciate recommendations on how I should set them
  • I see that a create deal of effort is done to achieve the dynamic shadow effects in the drei demo or this three js example. Whats the difference between using these methods and the simple PCFSoftShadowMap? cause they seem to achieve the same thing.

I really appreciate any feedback and help! Thank you in advance :smiley:

the model is most likely async and comes in later. the shadowcatcher shouldnā€™t run before your models are present. just guessing but perhaps you use new GLTFLoader().load(url, data => { ā€¦ } and if yes that is an anti pattern. your app must use async/await + Promise.all or race conditions cannot be avoided.

  • I see that a create deal of effort is done to achieve the dynamic shadow effects in the drei demo or this three js example. Whats the difference between using these methods and the simple PCFSoftShadowMap? cause they seem to achieve the same thing.

pcfsoft looks cheap and fake, it isnā€™t about the softness, itā€™s about tapering off, being sharp where light is close and softening up in the distance. real shadows are also darker where light doesnā€™t reach and brighten up where it does. all runtime shadows look fake. that isnā€™t threeā€™s fault, raycast shadows are expensive.

in my opinion the progressive lightmap example is a nice idea but too opinionated to work for anything real. drei accumulativeshadows base on it though, but it was simplified and works like a regular, planar shadow catcher that bakes the result.

  • I run the function renderShadows() in a for loop because I noticed it improved the shadow map, is this okay? How would you guys do it?

it has to run in a loop, it accumulates, hence the name. if you run 40-100 samples thatā€™s usually enough and after this it should stop and just use the baked shadow image.

2 Likes

i looked through the code and itā€™s like i thought, race conditions. three is fundamentally async, trying to tackle this with good hopes, timeouts/delays and callbacks will always create problems that will seem confusing. learn async/await and promises until you are able to await all async task. i would also suggest that you keep functions pure. setupAccumulativeShadows for instance writes into a global ā€œlet plmā€. this is called a side effect, functions should aspire to be free of these. a function should take input and return output.

1 Like

Thank you @drcmda for taking the time to help :slight_smile: i learned about asycn/wait and tried to implement it in my code to my best understanding. This solved the issue i was having with the gltf in the codesandbox example i created. I would appreciate if someone would let me know if I have done the implementation properly. I did it with the help of chatGPT but it sometimes messes things up.

I also tried implementing it with a .glb model that I am using in my main project. Its an incomplete model of a car, but the generated shadow looks weird. It looks as if only the bottom portion of the car is being considered for the shadow (Have a look at the picture). Any ideas why this is happening? Iā€™ve added the model in the codesandbox example along with a commented function to load it.

shadow_bad

Many thanks to anyone who can help me push forward in this :slight_smile: iā€™ve also added a second picture of the floor shadow quality that I am trying to achieve. In the second picture, ive baked the shadow through blender and imported it, but I would like to minimize the work done in blender and have the shadow prepared simply by uploading the model, just like in the playcanvas gltf viewer. Any tips?

the second is exactly what youā€™d get with accumulativeshadows. i think youā€™re fighting a near/far issue, looks like itā€™s gotten cut off.

1 Like

i tried changing the near/far value under ā€œlightParamsā€ but it didnt have an effect, im still having the same issue. I changed the position of the light source to have a better look and whats being baked on the shadow plane. You can see that it looks like some faces in the car model (like the faces on the enginecover) are ignored, and no shadow is created from them. I checked the face normals in blender and they are pointing in the right way. Also the material .side parameter in three js is set to double sided. So im not sure whats going on here

When i compare this result with your Soft Shadow Example , i also notice that the shadows here have sharper edges than how they should be. So this raises doubts that maybe iā€™m missing something when it comes to implementing AccumulativeShadow. Unfortunately im unable to do a direct comparison with your example since its written in react which im not familiar with.

Might be a scale issue, try shrinking the car

Is there a sandbox I can check ? (Iā€™m on phone so havenā€™t see all the replies)

Thanks for the feedback!

I tried shrinking the car but the shadow still looks the same. Hereā€™s the link to the sandbox

I also tried making a simple box on wheels model and using that instead, the resultant floor shadow doesnt have gaps like the first car model, but I still feel like my implementation of accumulativeshadows is not completely right because I expect the floor shadows to have softer edges.

Both car models are in the sandbox

Try changing material.shadowSide to DoubleSide maybe :thinking:

Thanks for the feedback. Based on how the shadows are looking, im also convinced that its an issue with the face normals or the sides which three js considers for the shadow. I decided to start over with a simple .glb model of a cube with a hole in the bottom face. You can see the hole in the picture along with the face normals pointing out.

Iā€™ve added the model to the sandbox and also added your recommendations. Heres how the .glb model is loaded:

async function loadModel() {
  const loaderGLB = new GLTFLoader();
  return new Promise((resolve, reject) => {
    loaderGLB.load(
      "TestCube.glb",
      (gltf) => {
        const GLBMesh = gltf.scene;
        gltf.scene.traverse(function (child) {
          if (child.isMesh) {
            child.castShadow = true;
            child.receiveShadow = true;
            child.material.side = THREE.DoubleSide;
            child.material.shadowSide = THREE.DoubleSide;
          }
        });
        resolve(GLBMesh);
      },
      null,
      reject
    );
  });
}

However, here is how the resultant shadow looks like:

Update:
As suggested by @vis_prime here:
plm.discardMat.side = THREE.DoubleSide ; solves the issue
Thank you all for the help!

1 Like