This is a big difference, and it’s especially noticeable on slower mobile devices - I’m seeing about 15fps difference on my phone.
I assume that the meshes are being considered still in frustum because they are in the shadow camera frustum. Is that correct? Does anyone know of a way to mitigate this so that I can still have shadows while getting better frustum culling?
In this case I think it would work fine to ignore the shadow frustum entirely for frustum culling, as each city block is quite separate. Only the tall buildings in the top cast shadow onto the next block.
Yes. Shadow map rendering also uses view frustum culling but based on the shadow camera’s projection matrix.
First thing that comes in my mind is to update the shadows camera frustum dynamically (e.g. based on the zoom level). But it really depends on the application design how easy it is to implement something like that.
Yeah, I had thought of doing that. There’s a lot of camera animation happening though so I’d need to do it every frame.
I guess it would be possible to create a projection matrix out of the intersection of two other projection matrices, although the shadow camera is orthographic and the main camera is perspective so that makes it a bit trickier.
But if I can get that to work, I can store the original shadow frustum and intersect it with the main camera frustum each frame. Will try that out tomorrow.
Alternatively, do you think there is some way to make the shadow camera use the main camera’s frustum culling results? I expect that would be harder to set up than just doing the intersection
Ha, this is a fun topic. In my game engine, I use a whole lot of tricks to work with that. I recommend that you use CSM instead of the standard shadow map. Why? Because the whole point of the CSM is to build the shadow camera frustum relative to the view frustum.
As for me, I use something very similar to a CSM, technically it’s a CSM, but it has only 1 cascade, which makes it hard to call it CMS. It does exactly what @Mugen87 mentioned, and what CSM does - it auto-magically updates shadow camera. I also found that three.js’s frustum culling is painfully slow for scenes with 1000s of objects, but that’s a whole different story not really applicable here.
Next, set each light intensity to half the original value so overall brightness is the same.
Before rendering the first frame:
Hide the canvas
Enable castShadow on all static meshes
Disable castShadow on all dynamic meshes.
dymanicShadowLight.shadow.enabled = false
staticShadowLight.shadow.enabled = true
Render the first frame, then:
Disable castShadow on all static meshes (they can still receive shadow)
Enable castShadow on all dynamic meshes.
dymanicShadowLight.shadow.enabled = true
staticShadowLight.shadow.enabled = false
Render another frame now with all the shadows
Unhide the canvas
I was kind of surprised this works but it does, and frustum culling results are the same level as they are with shadows disabled. The dynamic objects even receive the shadow cast by static objects when they move into the shadow of a building.
However, the shadows look different because they are still modulated by the brightness of the light that is casting them. So they are half as intense as they were previously.
In this case I’m fortunate that it’s a cartoony scene and I’m not using an environment map, so I can probably tweak the lighting to make this look ok. I don’t think it’s a general solution, however, and it does require adding an additional light to the scene.
It would be amazing if there was some way to bake the results of the initial staticShadowLight shadow so that I could then completely disable this light and still keep the shadows.
Then I could render the initial shadows with this light at full brightness, and also have the dymanicShadowLight at full brightness. After the first frame I could then simply set staticShadowLight.visible=false (or even remove it from the scene entirely).
That is fantastic! I’ve always wondered if there’s a way to “save” the shadowmap for static objects and use it as a template on each frame so it only needs to re-draw the moving shadows. Looks like you found a pretty good solution with two shadowmaps!
I was thinking of cloning the shadowmap WebGLRenderTarget to store it for future frames, and feed it to the renderer at the beginning of each frame. But the renderer has so many private methods and properties, that it might not be possible to access them. I like your solution better, it’s simpler.
PS: dy-ma-nic typo cracked me up. Looks like you’re committed to it!