How to pass attribute to ShaderMaterial used in ShadowDepthWrapper

I had a post about shadow performance on instanced models with baked animation, and @Evgeni_Popov suggested me to look into Optimizing further and standalone shadow depth wrapper section.

I revisited this topic today and I was able to achieve animated shadows on thin instances with a ShaderMaterial by adding my customized bonesVertex to shadowMapVertex.

Now my problem is: I have custom attributes added to the original PBRCustomMaterial used for the model. How can I pass the attribute used for each instanced model to the custom ShaderMaterial I pass to ShadowDepthWrapper?

Another question is about performance. It is much better than using PBRCustomMaterial itself in ShadowDepthWrapper, but FPS dropped to 40 with 200 animated units. Without shadow, I am able to maintain 600 units without FPS drops, and I believe I can have more units eventually when I replace the testing model with low-poly ones. But shadow seems to be the bottleneck. I am wondering how game like total war can support shadows of thousands units?

You can also pass custom attributes to a ShaderMaterial: cf the attributes parameter here IShaderMaterialOptions | Babylon.js Documentation

Regarding performance, what shadow filtering method are you using? PCF/PCSS are heavy, you can try Poisson or Exponential and see if that helps.

2 Likes

Came here to post a similar question, and hopefully it’s on topic enough for this thread. I’m setting the existing color attribute on a PBRCustomMaterial, but the alpha channel of that color doesn’t seem to affect the transparency of the produced shadow with or without the ShadowDepthWrapper. The instance fades in via animation, but the shadow appears abruptly at full strength.

Should that be working with instances based on the alpha channel of the color attribute, or would this require a custom shader for the shadow?

Vertex color alpha is not supported in the soft transparent shadow mode.

So you would need to write a custom shadow depth generator (standalone ShadowDepthWrapper, see Shadows | Babylon.js Documentation) to generate the shadow map.

1 Like

Hi @Evgeni_Popov

Thanks. I missed the definition of my custom attribute “animControl” when creating the ShaderMaterial instance. I already did it for uniforms but forgot the attribute.

      attributes: ["position", "normal", "uv", "animControl"]

The only problem I have is rendering performance now. I experienced with these two shadow generators.

if (webgl2) {
  const shadowGenerator = new CascadedShadowGenerator(1024, lightDirectional2);
  shadowGenerator.filteringQuality = ShadowGenerator.QUALITY_MEDIUM;
  GlobalContext.shadowGenerator = shadowGenerator;
} else {
  const shadowGenerator = new ShadowGenerator(1024, lightDirectional2);
  shadowGenerator.useExponentialShadowMap = true;
  GlobalContext.shadowGenerator = shadowGenerator;
}

The result looks like below.

ShadowGenerator
310 units: 58-60fps
330 units: 54fps
Shadow quality:
Screen Shot 2021-09-09 at 11.08.00 AM

CascadedShadowGenerator
120 units: 60fps
130 units: 56fps
Very clear shadow even the weapon is visible.
Screen Shot 2021-09-09 at 11.04.08 AM

My goal is CascadedShadowGenerator quality on 600 units. :frowning_face: :broken_heart: :sob:

Could you shed light on any directions I can look into to improve the shadow rendering?

Attached a total war shogun 2 video. 1:48 ~ 1:54 illustrate the shadow on large amount of units.

More units should not drop perf as much as long as they are instantiated, could you rely on instances in your game ?

Hi @sebavan

Everything in my game is thin instance except the singleton terrain and water. Shadow on static models also seem to be fine. The performance issue is only for adding shadows for animated models.

but animated models if there are thinInstances or instances should all be done in one draw call so it should not impact perf much to have 100 or 200 ???

You can try to use 2 cascades instead of 3 (which is the default). You should also monitor where the time is spent using the Performance tab of the browser, to have a clearer view of the spots that take time in the program.

1 Like

Hi @sebavan

This is all I have in my scene (see screenshot below) with 100 troll units. The Nodes sections lists all the meshes I use. The two root folders, one is for the troll model, the other is the asset pack for all the plants, rocks, and buildings. All these meshes are listed once since thin instances won’t be listed.

Similarly for the materials, a troll has 4 meshes, hair, body, weapon, and tanga. So I created 4 ShaderMaterial for each.

The GPU frame time keeps going up with more troll units if shadow is turned on. The draw call 65 is good. I have the troll and ~20 building/plants/rocks, terrain, and water material loaded into the scene.

Hi @Evgeni_Popov

I followed your instruction to profile the app. There is nothing significant on JS side. I notice some small GC overhead and corrected it. Also changed number of cascades to 2. These fixed helped a little bit.

I then think my issue is GPU bound. Some observations:

  • Inter frame time is always close to GPU frame time when I manipulate the number of units.
  • I tried to remove the body of the troll model, keeping the smaller parts like hair and the skirt. FPS is back to 60 again even when I have 1000 units. It seems to indicate I have very large active faces and vertices on the troll model. Removing the largest part (body) significantly reduced the GPU frame time. Other than the body mesh makes 70% of this model’s total polygons, it has not difference compare to other meshes of the model. In my code, I looped through the troll meshes, and applied all the customization for baked animation and shader material for ShadowDepthWrapper inside the loop.

I also tried to remove/disable everything else in my scene, and I divided the total number of active faces in babylon inspector by the number of units to calculate the number of active faces of a single unit. Then I found the result is very strange: I have over 25000 faces per unit. But if I load the model into babylon sandbox, the active faces is 10479. Even if I remove the body, I still have 6000+ faces per unit when loaded into my scene, compared with < 3000 faces in sandbox. I don’t write any code that adding vertices and faces (that would be too hard for me), so it feels very strange this happens. I will keep investigating. But I want to let you know and see if this behaviour ring a bell to you by any chance.

Now I have a strong feeling if I can fix this issue, I will be able to render 1000+ units for a unit that has <= 5000 polygons. :crossed_fingers:

Hi @Evgeni_Popov

I think I find out the cause. If I remove the shadow, the extra faces are removed. I can reproduce it using the dude model.

Without shadow, 22507 faces: https://playground.babylonjs.com/#92Y727#152

With shadow: 45014 faces: https://playground.babylonjs.com/#4G38H4#7

Same in my scene, if I remove the shadow, the number of active faces are correct. Is this expected?

Update: I also added a simple box next to the dude. With shadow the active faces for the box is also increased from 12 to 24.

https://playground.babylonjs.com/#4G38H4#551

Yes, it is expected: the number of active faces is the number of faces that are drawn. When using shadows, each face is drawn two times, once in the shadow map and another time for the regular rendering. With CSM, it can even be drawn more than two times because a face could be drawn in several cascades.

As the time spent is GPU time, I fear it will be difficult to optimize except by reducing the face count. Also, if you have a live link it could help to diagnose better.

1 Like

Hi @Evgeni_Popov

With CSM, it can even be drawn more than two times because a face could be drawn in several cascades.

Thanks for explaining. After my last post, I also noticed the faces are exactly doubled in my PG, but the faces are ~2.7 times in my scene that uses CSM. Now I understand the reason.

As the time spent is GPU time, I fear it will be difficult to optimize except by reducing the face count.

I looked the models on sketchfab, low poly warrior models with 1000 faces should be good enough for me. Compare to the testing model with 10k faces now, I should be able to rendering 2000 units. I will probably only need 1000 - 1500 units and leave some buffer for castle and town building blocks.

Many thanks for looking into my long questions.

1 Like