Shadows setup for large usually static world

Hi, I have some very general questions for someone familiar with the various options for shadow generators.

I’m rendering a large world whose terrain is split into evenly-sized chunks, and each chunk doesn’t change very often. Basically like Minecraft. I’m using a directional light, which seems to be the default for such a world. My questions are:

  1. If I render shadows on a given chunk once, and freeze the shadows until the chunk changes, does that mean I need to create and maintain a separate shadow generator for every chunk? Or can I bake shadows into a mesh so that they’ll stay there even if the shadow generator is disposed?

  2. Freezing a shadow doesn’t affect the targets receiving the shadow, right? E.g. if I freeze the shadow of a terrain chunk, and then a dynamic mesh moves into the shadow it should still receive the shadow?

  3. If a world has lots of static terrain and a few dynamic meshes, and you want both to cast shadows on each other, is the best approach to have separate shadow generators for each, and freeze the ones for the terrain? Or is it not possible to have static and dynamic meshes shadow each other if anything is frozen?

Thanks for any advice!

You need to keep the shadow generator alive, as it holds the shadow map and some other parameters that are needed to generate shadows when rendering a mesh.

Yes. You need to update the shadow generator only if the light changes (position/direction), as the shadow map is basically a rendering of the scene from the light point of view.

The problem I can see is that you normally should have a single directional light (the sun), meaning you can have a single shadow generator. So, if you want to create multiple shadow generators, you will need to create a light for each of them, meaning you will have to manage the light.excludedMeshes (or light.includedOnlyMeshes) array to make sure only the right chunk of terrain + the right dynamic objects are lit by the right light.

It is possible, you only need to have one shadow generator per light. It means that if you want to have a specifc shadow map (shadow generator) for the dynamic meshes, you need to have a specific light for them.

1 Like

Thank you for the replies!

Hmm, so each light can only have a single shadow generator, and vice versa? I guess that means there’s no way to do any kind of per-chunk handling for terrain shadows then.

In that case, do you have any ideas how one might performantly do shadows in a large world of mostly static terrain (like Minecraft)? Maybe create a single shadow generator, add only nearby terrain meshes as shadow casters, and then as the player moves around update the shadow caster list?

In fact, you can create multiple shadow generators for a single light, but you must give a different camera each time.

So, it’s a shadow generator per (light, camera). But in your case it won’t help, you have a single camera that renders the whole scene.

Where a shadow generator per (light, camera) can help is if you must display another view of your scene from another camera (think surveillance camera, for eg.) and if you are using cascaded shadow map (because CSM depends on the position/view direction of the camera).

[…] Actually, you could clone the camera multiple times, simply to be able to create one shadow generator per chunk of terrain… That way, you would be able to get a different shadow map for each chunk. The problem, however, would be at rendering time: for a dynamic mesh, you would need to determine which shadow map(s) to take into account, because the mesh could be in the shadow of multiple chunks simultaneously (depending on the light direction / the mesh position). You also have this problem when rendering a chunk, as nearby chunks could cast shadows on the current chunk.

Have you tried cascaded shadow map? It’s normally what we use when we need shadows for large scene.

The problem with “mostly static” is “mostly”: if it’s “static”, then you can pre-bake the shadows in lightmap textures and you will get the best performances. But if the shadows can change, even infrequently, it’s more difficult…

If the scene is large, you won’t be able to have a single shadow texture (so a single shadow generator) that will work for all your scene, it will be very pixelated because one texel of the texture will map to a large area of the scene.

Thanks for the further info Evgeni. I’ve read about cascaded map and I assume that’s what I’ll need to use if I do fully dynamic shadows, but since the terrain is mostly static I was hoping there was a way to render high quality shadows occasionally, rather than lower quality every frame. (And again it’s basically minecraft - even when terrain is static it’s also randomly generated, so no pre-baked textures.)

I meant that maybe I could have a single shadow generator casting shadows only on a small central part of the world, around the player - by manually setting only nearby terrain meshes to cast or receive shadows. Then as the player moves around, I could periodically remove distant terrain from the “caster” list and add new nearby terrain to it. Would something like that make sense?

The problem is that the notion of “nearby” is complicated: an object that is far from the player may still need to be added to the caster list because of its position/light direction… It will be very difficult for you to know if an object needs to be added to the caster list or not, because you won’t be able to know if this object casts shadows in the area near the player.

So, let’s say you add all the objects in the caster list. In that case, I guess it’s possible to have a single shadow generator and regenerate the texture when the player has moved enough so that the current light position is too far away from the player position and need to be moved.

2 Likes

Thanks for your help @Evgeni_Popov - it looks like CSM with a single light is definitely the way to go.

That said can you help with a few more questions?

  1. If a scene has N shadow casters and M shadow receivers, is it generally correct to assume that the cost of shadows scales like N*M? In my testing it seemed like the performance cost was low if either number was small, but does one matter more than the other?

  2. To save performance I’d like to ignore shadows far away from the camera. I found I can limit shadow draw range with shadowMaxZ, but this seems not to affect performance. Is the solution to manually curate the list of shadow casters/receivers based on their distance to the camera, or is there some built-in way of doing this?

  3. How can one hide a mesh (from normal rendering) but still have it cast a shadow? Is there a setting for this, without needing to enable transparency shadows on the generator?

Yes, but adding a mesh A as a shadow caster costs probably more than enabling shadows for a mesh B because A will be rendered a second time in the shadow map (so the number of draw calls increases) whereas B is still rendered a single time, but with some additional shader code to account for the shadows.

You should manually manage the meshes that are rendered into each cascade: by default, all the meshes of the scene (or shadowGenerator.renderList if not null) are rendered into all cascades. See Cascaded Shadow Maps | Babylon.js Documentation

But you must be careful that creating the list of meshes (which is done on the CPU side, which is generally the limiting factor) does not take more time than simply using the full list of meshes…

You can remove it from the scene.meshes array. For eg:

Another possibility is to set disableColorWrite = false on the material, but removing the mesh from scene.meshes is the best solution for performance.

Thanks @Evgeni_Popov! Sorry, another question :smiley:

In testing, I found that freezing materials interacted oddly with shadow rendering. When I set receiveShadows on a mesh who’s material is frozen the shadows didn’t render, so I was calling material.freeze() (to unfreeze/refreeze) when I changed a mesh’s receiveShadows property. However I found this sometimes caused WebGL warnings, and the warnings go away if I leave materials unfrozen.

Edit: actually in the PG it seems like freezing materials just disables shadows entirely?

What you can do is calling ground.resetDrawCache(); so that the shadows refresh correctly:

Also found out you can generate the shadowMap once, and then remake it when needed by doing this.

const shadowGenerator = new ShadowGenerator(2048, sunLight)
// some settings I have found looks good
shadowGenerator.enableSoftTransparentShadow = true
shadowGenerator.transparencyShadow = true
shadowGenerator.filter = ShadowGenerator.FILTER_BLURCLOSEEXPONENTIALSHADOWMAP

// render once at startup
shadowGenerator.getShadowMap().refreshRate = RenderTargetTexture.REFRESHRATE_RENDER_ONCE

// if you move anything you can force it to render by calling
shadowGenerator.getShadowMap().resetRefreshCounter()
5 Likes

Thanks @Leon, I also could use this and it increased performance for me.

1 Like

Hi
im trying to build game like (GTA)
i had huge scene with a lots of static objects and a character that moving
and i try to use two lights along two shadow generator

  1. for static objects ( ground building walls and … ) that only render once in start up
  2. for dynamic objects ( client character and other remote characters ) that render in each frame

but becase static object only effect by static light. dynamic shadow generator dosn’t work
i look for something like bake shadow map and result was nothing

is there any way to generate any kind of static shadow for static objects and dynamic shadow for dynamic objects?!

You can but this will not work on really large scenes as will never have enough space to store it all.

In general in this case, shadows are baked offline.

1 Like