Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement percentage-closer soft shadows (PCSS). #13497

Merged
merged 17 commits into from
Sep 18, 2024

Conversation

pcwalton
Copy link
Contributor

@pcwalton pcwalton commented May 24, 2024

Percentage-closer soft shadows are a technique from 2004 that allow shadows to become blurrier farther from the objects that cast them. It works by introducing a blocker search step that runs before the normal shadow map sampling. The blocker search step detects the difference between the depth of the fragment being rasterized and the depth of the nearby samples in the depth buffer. Larger depth differences result in a larger penumbra and therefore a blurrier shadow.

To enable PCSS, fill in the soft_shadow_size value in DirectionalLight, PointLight, or SpotLight, as appropriate. This shadow size value represents the size of the light and should be tuned as appropriate for your scene. Higher values result in a wider penumbra (i.e. blurrier shadows).

When using PCSS, temporal shadow maps (ShadowFilteringMethod::Temporal) are recommended. If you don't use ShadowFilteringMethod::Temporal and instead use ShadowFilteringMethod::Gaussian, Bevy will use the same technique as Temporal, but the result won't vary over time. This produces a rather noisy result. Doing better would likely require downsampling the shadow map, which would be complex and slower (and would require PR #13003 to land first).

In addition to PCSS, this commit makes the near Z plane for the shadow map configurable on a per-light basis. Previously, it had been hardcoded to 0.1 meters. This change was necessary to make the point light shadow map in the example look reasonable, as otherwise the shadows appeared far too aliased.

A new example, pcss, has been added. It demonstrates the percentage-closer soft shadow technique with directional lights, point lights, spot lights, non-temporal operation, and temporal operation. The assets are my original work.

Both temporal and non-temporal shadows are rather noisy in the example, and, as mentioned before, this is unavoidable without downsampling the depth buffer, which we can't do yet. Note also that the shadows don't look particularly great for point lights; the example simply isn't an ideal scene for them. Nevertheless, I felt that the benefits of the ability to do a side-by-side comparison of directional and point lights outweighed the unsightliness of the point light shadows in that example, so I kept the point light feature in.

Fixes #3631.

Changelog

Added

  • Percentage-closer soft shadows (PCSS) are now supported, allowing shadows to become blurrier as they stretch away from objects. To use them, set the soft_shadow_size field in DirectionalLight, PointLight, or SpotLight, as applicable.

  • The near Z value for shadow maps is now customizable via the shadow_map_near_z field in DirectionalLight, PointLight, and SpotLight.

Screenshots

PCSS off:
Screenshot 2024-05-24 120012

PCSS on:
Screenshot 2024-05-24 115959

@pcwalton pcwalton added A-Rendering Drawing game state to the screen C-Enhancement A new feature S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels May 24, 2024
@pcwalton pcwalton added this to the 0.15 milestone May 24, 2024
@pcwalton pcwalton force-pushed the pcss branch 4 times, most recently from 5167130 to 0377266 Compare May 25, 2024 08:00
[*Percentage-closer soft shadows*] are a technique from 2004 that allow
shadows to become blurrier farther from the objects that cast them. It
works by introducing a *blocker search* step that runs before the normal
shadow map sampling. The blocker search step detects the difference
between the depth of the fragment being rasterized and the depth of the
nearby samples in the depth buffer. Larger depth differences result in a
larger penumbra and therefore a blurrier shadow.

To enable PCSS, fill in the `soft_shadow_size` value in
`DirectionalLight`, `PointLight`, or `SpotLight`, as appropriate. This
shadow size value represents the size of the light and should be tuned
as appropriate for your scene.  Higher values result in a wider penumbra
(i.e. blurrier shadows).

When using PCSS, temporal shadow maps
(`ShadowFilteringMethod::Temporal`) are recommended. If you don't use
`ShadowFilteringMethod::Temporal` and instead use
`ShadowFilteringMethod::Gaussian`, Bevy will use the same technique as
`Temporal`, but the result won't vary over time. This produces a rather
noisy result. Doing better would likely require downsampling the shadow
map, which would be complex and slower (and would require PR bevyengine#13003 to
land first).

In addition to PCSS, this commit makes the near Z plane for the shadow
map configurable on a per-light basis. Previously, it had been hardcoded
to 0.1 meters. This change was necessary to make the point light shadow
map in the example look reasonable, as otherwise the shadows appeared
far too aliased.

A new example, `pcss`, has been added. It demonstrates the
percentage-closer soft shadow technique with directional lights, point
lights, spot lights, non-temporal operation, and temporal operation. The
assets are my original work.

Both temporal and non-temporal shadows are rather noisy in the example,
and, as mentioned before, this is unavoidable without downsampling the
depth buffer, which we can't do yet. Note also that the shadows don't
look particularly great for point lights; the example simply isn't an
ideal scene for them. Nevertheless, I felt that the benefits of the
ability to do a side-by-side comparison of directional and point lights
outweighed the unsightliness of the point light shadows in that example,
so I kept the point light feature in.

Fixes bevyengine#3631.

[*Percentage-closer soft shadows*]:
https://developer.download.nvidia.com/shaderlibrary/docs/shadow_PCSS.pdf
@torsteingrindvik
Copy link
Contributor

I don't know the domain well- why do the shadows here:
image

Have curve-like imperfections?

I noticed the article you linked has this image which has a different type of noise:

image

Is it related to the geometry which casts the shadow or something else?

Also I was wondering how the shadows perform when the light source is moving.
I changed the example a bit to have the light undulate.

pcss-example-undulate.patch.txt

Feel free to git apply pcss-example-undulate.patch.txt if you want to try the same or add it to the PR.

pcss-undulate.mp4

I noticed an effect that spot lights have this effect where it looks like the shadow density changes a lot, perhaps related to shadow map resolution?

@pcwalton
Copy link
Contributor Author

pcwalton commented May 25, 2024

The patterns are because of the interleaved gradient noise. This is generally considered more desirable than the white noise that it looks like your other example has.

There isn't a whole lot we can do about the noise in this patch without generating mip chains for the shadow map, which would require #13003 (and even if that were in I wouldn't want to do it in the first patch because this patch is too big as it is). When I asked in Discord I was told that PCSS in games tends to be noisy; that's just the way it is.

I think PCSS is mostly used in directional lights in practice. The results for point lights in particular just aren't as good.

@pcwalton
Copy link
Contributor Author

I considered allowing the user to move the light but I realized that this will cause problems with the Z near value, which is carefully tuned so that the palm tree makes maximum use of the shadow map. So I'm less inclined to make the light movable, though I could be convinced otherwise.

@pcwalton
Copy link
Contributor Author

I suspect what's going wrong with spot lights is that the blocker search starts missing blockers. I'm not sure if this is something that can reasonably be fixed; it's always possible that the blocker search can miss things. The solution is for artists to tweak the soft_shadow_size manually.

Copy link
Contributor

@torsteingrindvik torsteingrindvik left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found out that Godot has PCSS and they have an example showcasing it: https://github.com/godotengine/godot-demo-projects/tree/master/3d/lights_and_shadows

I put the GLTF files in this PR in that project and dropped the palm tree into Godot and tried experimenting with the PCSS lights in that example.
I couldn't try the spot light (likely due to godotengine/godot#80137) but I saw similar results when using their omni light (point light) and their sun (directional light).

I learned that it's very finicky to get decent looking shadows, and I had to resort to a mix of tuning their shadow blur and their shadow opacity (not sure if we have that in Bevy yet) and only specific combos of those along with light strength and positioning yielded OK results.

I'm convinced that PCSS is hard to get right (as a user) and that this PR adds value and if we can get better noise patterns further down the line that's really great.

EDIT: When we have a Bevy editor where we can interactively tune parameters like I could in Godot it will be a lot easier to spot problems so I'm not worried about any minor issues right now.

Approving on the basis of the above, as well as:

  • Code quality to me looks great
  • Adds great docs
  • Great example

I haven't verified any math or looked in depth at the shaders- hoping rendering maintainers will do so.

@pcwalton
Copy link
Contributor Author

Yeah. Unity doesn't implement PCSS at all and in this PR I pretty much learned why. PCSS is just really hard to tune.

@theprotonfactor
Copy link

@pcwalton Unity does, in fact, implement PCSS as part of their High Definition Render Pipeline package. See the "Shadows" section on this changelog page, for example. If you ever want to know what Unity does and doesn't support then that repository is a fantastic place to search.

Copy link
Contributor

@IceSentry IceSentry left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Overall, the code looks good to me. The new docs for the existing shadow stuff is really nice.

As mentioned on discord, the volumetric_fog and trransmission example currently crash. Once those are fixed and assuming conflicts are fixed I'll approve.

examples/3d/pcss.rs Outdated Show resolved Hide resolved
Copy link
Contributor

@therealbnut therealbnut left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code looks good, just a small nit about clarifying some unused uncommented fields.

Comment on lines +156 to +157
pub(crate) pad_a: f32,
pub(crate) pad_b: f32,
Copy link
Contributor

@therealbnut therealbnut Jul 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: Is this intended to make the structure's size a multiple of 4 x f32? If so a comment and maybe a test might be good, assuming it's important and relied upon elsewhere. Maybe something like this would also make the intention clearer and more maintainable:

Suggested change
pub(crate) pad_a: f32,
pub(crate) pad_b: f32,
pub(crate) _reserved: [f32; 2],

The test could be as simple as this:

#[cfg(test)]
const_assert_eq!(size_of::<GpuClusterableObject>() % size_of::<Vec4>(), 0);

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A test isn't that necessary for that. Things break pretty quickly if the padding is wrong anyway. We have padding in a bunch of places for rendering code because of some webgl limitations.

Copy link
Contributor

@therealbnut therealbnut Jul 15, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cool, no problem if it’s convention - although should it be commented all the same? I was thinking the test wasn't so much about ensuring it breaks, but ensuring the issue is determined statically and in the right location to fix it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For me, just the fact that it's called pad_x is enough documentation.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Righto, again no problem if it's an established convention in the codebase.

I was initially unsure whether this was perhaps something like padding from the edge of the shadow when sampling to avoid boundary conditions. It required me to check it wasn't used to understand its purpose and intention, but if I was adding a new field I may not have checked so thoroughly and just added new fields rather than removing these.

Again, it's a nit, and not a huge issue. I just saw it as something that could be made clearer. My intention if it's clearer is that more people can get across these changes, review, and contribute. Being knowledgable about graphics doesn't mean you're knowledgable about conventions in this codebase, or rust, etc. My understanding of Rust convention, for example, is that fields like this would have an underscore prefix _pad_a to indicate they're intentionally unused.

@pcwalton
Copy link
Contributor Author

pcwalton commented Jul 15, 2024

Please don't merge this yet as spot shadows are broken somehow due to merge fallout.

@pcwalton pcwalton marked this pull request as draft July 15, 2024 01:46
Comment on lines -220 to +222
4,
5,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a change for this PR, but after this is merged would a PR be welcome to replace these values with constants? and defined in the shader with defines like this:

@group(0) @binding(#TONEMAPPING_LUT_TEXTURE_BINDING_INDEX) var dt_lut_texture: texture_3d<f32>;
@group(0) @binding(#TONEMAPPING_LUT_SAMPLER_BINDING_INDEX) var dt_lut_sampler: sampler;

It might reduce merge conflict churn when there's lots of unrelated changes in a PR.

Copy link
Contributor Author

@pcwalton pcwalton Jul 16, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As I recall, non-boolean defines are kind of annoying in our PBR shaders at the moment, but sure, it could be done in the future.

@Olle-Lukowski Olle-Lukowski added the S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it label Aug 2, 2024
@alice-i-cecile alice-i-cecile added the C-Needs-Release-Note Work that should be called out in the blog due to impact label Aug 6, 2024
Co-authored-by: Torstein Grindvik <52322338+torsteingrindvik@users.noreply.github.com>
@alice-i-cecile
Copy link
Member

@pcwalton I'm inclined to merge this basically as is. When you have a moment, could you clean up merge conflicts? I don't feel confident doing so myself here.

@superdump's comments are solid as well, but I don't feel that they're ultimately blocking (feel free to correct me).

@alice-i-cecile alice-i-cecile added the S-Adopt-Me The original PR author has no intent to complete this work. Pick me up! label Aug 12, 2024
@pcwalton
Copy link
Contributor Author

@Olle-Lukowski It does work with point lights. This just isn't the prettiest scene for them.

@pcwalton
Copy link
Contributor Author

Rebased and comments addressed. I think this is OK to go unless it needs a rereview.

@pcwalton pcwalton removed the S-Adopt-Me The original PR author has no intent to complete this work. Pick me up! label Sep 17, 2024
@alice-i-cecile alice-i-cecile added this pull request to the merge queue Sep 17, 2024
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Sep 17, 2024
@alice-i-cecile
Copy link
Member

@pcwalton
Copy link
Contributor Author

@alice-i-cecile I think I fixed DX12.

@alice-i-cecile alice-i-cecile added this pull request to the merge queue Sep 18, 2024
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Sep 18, 2024
@pcwalton
Copy link
Contributor Author

This failure is because directional_shadow_textures can be sampled with both linear and PCF with PCSS and WebGL 2 doesn't support that. Grr. Trying to see if there's a way I can just ifdef off all of PCSS in WebGL 2.

@pcwalton
Copy link
Contributor Author

Should be fixed again.

@alice-i-cecile
Copy link
Member

I love WebGL2 🥲 Thanks for fixing this up <3

@alice-i-cecile alice-i-cecile added this pull request to the merge queue Sep 18, 2024
Merged via the queue into bevyengine:main with commit 2ae5a21 Sep 18, 2024
30 checks passed
coreh added a commit to coreh/bevy that referenced this pull request Sep 20, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Rendering Drawing game state to the screen C-Enhancement A new feature C-Needs-Release-Note Work that should be called out in the blog due to impact S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

Percentage Closer Soft Shadows
8 participants