Improvement(with code) to the shadowmaps example for cascades and stability. #3577
Replies: 6 comments 6 replies
-
|
This is great! Thanks!
You should just do fresh clone, and submit PR. That way you would be properly credited as a contributor. |
Beta Was this translation helpful? Give feedback.
-
|
since posting this i have found several issues with my proposal here. Let me fix them and i will try to do a pr |
Beta Was this translation helpful? Give feedback.
-
|
i have improved on this and updated the original comment here. Unfortunatelly i cannot do a PR without a fork. But i already have one with multiple commits and it would really be a hassle to manuver them around so for me it doesn't matter if i am credited as a contributor just want to contribute :). Love the library and all the work people invest in it. You can try it and if you like it just add it. If you are not finding it to your liking dont add it :). Another separate thing which if you like can improve the example is PCSS shadow method. Here is all the code for the PCSS |
Beta Was this translation helpful? Give feedback.
-
Actually you don't need multiple forks. Rather you should create branch in your fork, reset it to origin/master (which is bgfx repo, not your fork), then cherry-pick, or just copy files directly over. Then push that branch into your fork, and when creating PR, base it of that particular branch, not fork/master. |
Beta Was this translation helpful? Give feedback.
-
|
How do you rebuild the example shaders like all of them? I am using the bgfx.cmake project to build everything but on windows i only trigger these [build] [111/252 30% :: 50.468] Compiling shader 16-shadowmaps/fs_shadowmaps_color_texture.sc for DX11, ESSL, GLSL, SPIRV, WGSL The There may be missing some like dxil. Do i need to rebuild them for all types when submitting the PR? |
Beta Was this translation helpful? Give feedback.
-
|
Submitted the PR :). Thanks for your assistance. Hope you like the improvements. Love the library :). I have been using it for quite some time in my game engine. |
Beta Was this translation helpful? Give feedback.



Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
Hi i am playing around with the shadowmaps example and i have noticed several issues with the cascade splitting and stabilizing.
Cascades frustums felt off in some directions where most detailed cascade should be chosen but it wasn't.
Stabilize basically did not really stabilize anything since when rotating or moving the camera there were noticeable movement on the edges of the shadows(very noticeable when scene is static and camera moves). This new approach is based on GPU Gems 3, Chapter 10] and tries to solve that. The code can be copy pasted directly to test or you can add a checkbox to toggle between the current one and this to see the difference. I cannot make a PR since i have a fork of the repo with some local changes that is why i am posting it here. At the bottom is the whole code which is not so much.
Improved Cascade Shadow Map Calculation for Directional Lights
Summary
This PR introduces an alternative cascade shadow map (CSM) calculation for directional lights in the
16-shadowmapsexample. The new method resolves shadow edge flickering when the camera moves or rotates with static geometry/lighting, and extends shadow coverage to scenes whose geometry lives far from the world origin.Both approaches share the same high-level structure — split the camera frustum into cascades, fit an orthographic projection around each split — but they differ in several critical geometric details.
Key Differences
1. Light View Matrix Construction
-lightDir(negative of light direction vector){0, 0, 0}(origin){0, 0, 0}(world origin)lightDir(along light direction)+Y, with fallback to+Zwhen the light is nearly vertical (dot > 0.99)Why it matters:
The old code places the light "eye" at
-lightDirlooking at the origin. This means the light view is anchored to the world origin. Because a directional light has no position (it represents parallel rays), the view matrix should only encode the light's orientation, not a position relative to any world point. The new code constructs a pure orientation-only view matrix at the origin looking along the light direction, making it independent of any particular world position. This means shadow maps remain valid regardless of how far the scene geometry is from the origin.The explicit up-vector fallback in the new code also prevents a degenerate matrix when the light points straight down (a common sun configuration).
2. Cascade Split Calculation
splitFrustum)calculateCascadeSplits)[near0, far0, near1, far1, ...]pairs with a small overlap factor (far = near * 1.005f)[0..1]relative to clip rangenumSlices = numSplits * 2, iterates even/odd indicesnumSlicesF = numSplits * 2.5, iterates odd indices(i*2+1)for GPU Gems 3–style weightingnearClip + splitDist * clipRange; cascade near of splitiequals cascade far of spliti-1(tracked vialastSplitDist) — seamless, gap-freeWhy it matters:
The old split function produces small intentional overlaps (
1.005fmultiplier) between cascades, which can cause slight discontinuities at cascade boundaries. The new method produces seamless, gap-free cascades and uses a GPU Gems 3–inspired logarithmic/uniform blend that distributes resolution more evenly across the view range.3. Frustum Fitting — AABB vs. Bounding Sphere Scale
scalex = 2.0 / (maxproj.x - minproj.x)per-axisscalex = scaley = 1.0 / frustum_radius(uniform, sphere-based)Why it matters — this is the core stability improvement:
With a tight AABB, the bounding box dimensions change as the camera rotates, because the 8 frustum corners trace different extents in light space depending on the camera orientation. This causes the orthographic projection bounds to expand and contract frame-to-frame, which shifts shadow texels and produces edge shimmer/flickering.
Using the bounding sphere radius gives a rotation-invariant extent. No matter how the camera rotates, a sphere enclosing the frustum corners has the same radius, so the projection scale remains constant. Combined with the existing texel-snapping stabilization, this eliminates sub-texel jitter entirely.
4. Depth (Z) Centering via
mtxCrop[14]mtxCrop[14]is 0, identity)mtxCrop[14] = -lightSpaceCenter.z— re-centers the depth range on the cascade frustum's light-space centerWhy it matters:
The old code uses a fixed depth range (
-far .. +far) for all cascades with no per-cascade Z adjustment. If the camera is far from the origin, the actual shadow casters may fall outside this fixed range, causing shadow clipping or lost precision. The new code shifts the depth origin to the center of each cascade's frustum in light space, ensuring the depth range is always centered on the geometry that matters for that cascade. This is critical for scenes with large world coordinates — the shadows simply don't work in the old method when geometry is far from{0, 0, 0}.5. Frustum Corner Calculation
worldSpaceFrustumCorners()utility with absolute near/far valuesmtxViewInv, then to light spaceThe inline approach is functionally equivalent for the corner positions themselves, but the explicit frustum center computation is essential for the bounding-sphere and Z-centering improvements.
Visual Impact Summary
{0,0,0}, depth range misses distant geometry+Zup fallbackAll code
Beta Was this translation helpful? Give feedback.
All reactions