Skip to content

bevy_solari: RIS for Direct Lighting #19620

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

Merged
merged 27 commits into from
Jun 23, 2025

Conversation

JMS55
Copy link
Contributor

@JMS55 JMS55 commented Jun 13, 2025

Objective

  • Start the realtime direct lighting work for bevy solari

Solution

  • Setup all the CPU-side code for the realtime lighting path (minus some parts for the temporal reuse I haven't written yet)
  • Implement RIS with 32 samples to choose a good random light
  • Don't sample a disk for the directional light, just treat it as a single point. This is faster and not much worse quality.

Future

  • Spatiotemporal reuse (ReSTIR DI)
  • Denoiser (DLSS-RR)
  • Light tile optimization for faster light selection
  • Indirect lighting (ReSTIR GI)

Testing

  • Run the solari example to see realtime
  • Run the solari example with -- --pathtracer to see the existing pathtracer

Showcase

1 frame direct lighting:
image

Accumulated pathtracer output:
image

@JMS55 JMS55 added this to the 0.17 milestone Jun 13, 2025
@JMS55 JMS55 added the A-Rendering Drawing game state to the screen label Jun 13, 2025
@alice-i-cecile alice-i-cecile added C-Feature A new feature, making something new possible M-Needs-Release-Note Work that should be called out in the blog due to impact labels Jun 13, 2025
Copy link
Contributor

It looks like your PR has been selected for a highlight in the next release blog post, but you didn't provide a release note.

Please review the instructions for writing release notes, then expand or revise the content in the release notes directory to showcase your changes.

@JMS55 JMS55 requested review from atlv24 and removed request for atlv24 June 13, 2025 20:12
@JMS55 JMS55 changed the title bevy_solari: Spatial ReSTIR bevy_solari: RIS for Direct Lighting Jun 15, 2025
@JMS55 JMS55 marked this pull request as ready for review June 15, 2025 17:14
@JMS55 JMS55 added the S-Needs-Review Needs reviewer attention (from anyone!) to move forward label Jun 16, 2025
@JMS55 JMS55 requested review from ecoskey and removed request for ecoskey June 19, 2025 01:57
},
};
use camera_controller::{CameraController, CameraControllerPlugin};
use std::f32::consts::PI;

/// `bevy_solari` demo.
Copy link
Member

Choose a reason for hiding this comment

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

I would really appreciate comments on exactly what users need to call to test this.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's intended that you just run cargo run --example solari --features bevy_solari, like any other example (it will warn you if you're missing the feature).

The extra options are meant more for me to use for development, and less for users to test.

@@ -0,0 +1,117 @@
#import bevy_core_pipeline::tonemapping::tonemapping_luminance as luminance
Copy link
Member

Choose a reason for hiding this comment

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

If you want a full approval from me, I'm going to need a bit of a comment-based walkthrough of each of the steps that we're taking inside of this shader. I understand the conceptual approach of restir, but I'm having trouble mapping it to this code because of my shader noobiness.

Copy link
Contributor Author

@JMS55 JMS55 Jun 19, 2025

Choose a reason for hiding this comment

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

initial_and_temporal:

  1. Load a bunch of data about the current pixel (depth, normal, albedo, etc)
  2. Call generate_initial_reservoir() to get a reservoir (see the ReSTIR paper/course)
  • Create an empty reservoir
  • 32 times:
    • Generate a random point on a light source (light sample)
    • Calculate a resampling weight for that sample, based on the brightness of the light (without tracing any rays to test visibility)
    • Add it to the reservoir
  • After keeping the best of the 32 samples, trace a ray to test visibility
  1. Write the reservoir to a buffer

spatial_and_shade:

  1. Load the same pixel data again
  2. Grab the reservoir from the buffer
  3. Calculate the light contribution again
  4. Shade the current pixel (emissive + (radiance * reservoir weight * brdf * view exposure)).

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Eventually these steps will be expanded to reuse reservoirs temporally and spatially for better quality, but I'll leave that to the next PR.

Copy link
Contributor

@TimJentzsch TimJentzsch left a comment

Choose a reason for hiding this comment

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

As a novice to this topic, I feel like it could benefit a lot from additional documentation.
I definitely don't expect to understand everything, but I think it would be easier to review and spot potential errors with some more explanations :)

let gpixel = textureLoad(gbuffer, global_id.xy, 0);
let world_position = reconstruct_world_position(global_id.xy, depth);
let world_normal = octahedral_decode(unpack_24bit_normal(gpixel.a));
let base_color = pow(unpack4x8unorm(gpixel.r).rgb, vec3(2.2));
Copy link
Contributor

Choose a reason for hiding this comment

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

To me, the 2.2 sounds oddly specific and is used twice in this shader, maybe it justifies a comment?
Or is it an "obvious" value if you know the technique?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not too sure what it's doing, but this code is copied from pbr_input_from_deferred_gbuffer. It's unpacking the gbuffer data, which is in a compressed format.

Copy link
Contributor

Choose a reason for hiding this comment

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

pow 2.2 is fake gamma correction

Copy link
Contributor

Choose a reason for hiding this comment

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

(this should ideally be an implementation detail of the gbuffer, and access done through a function)

Copy link
Member

@alice-i-cecile alice-i-cecile left a comment

Choose a reason for hiding this comment

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

I am content enough with the code and docs quality here, and have a basic understanding of the rendering algorithm in play :) I also feel strongly that this is the right direction.

That said, please don't merge this without at least one rendering expert review.

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
@JMS55 JMS55 requested a review from IceSentry June 19, 2025 19:56
@JMS55 JMS55 added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jun 23, 2025
@JMS55 JMS55 requested a review from tychedelia June 23, 2025 00:16
Copy link
Member

@tychedelia tychedelia left a comment

Choose a reason for hiding this comment

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

Looks good just some minor feedback. Exciting!


let size = (view_size.x * view_size.y) as u64 * RESERVOIR_STRUCT_SIZE;

let reservoirs_a = render_device.create_buffer(&BufferDescriptor {
Copy link
Member

Choose a reason for hiding this comment

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

Do you really need new buffers every frame? Or could you cache the descriptor.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It already is cached :)

Copy link
Member

Choose a reason for hiding this comment

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

Ah, yup, glazed right past that!

solari_lighting.as_deref().unwrap().clone(),
SkipDeferredLighting,
));
solari_lighting.as_mut().unwrap().reset = false;
Copy link
Member

Choose a reason for hiding this comment

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

Don't feel super strongly about this since we all know these patterns are hard to express but might be cleaner with a First system that sets it to false each frame.

Copy link
Contributor Author

@JMS55 JMS55 Jun 23, 2025

Choose a reason for hiding this comment

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

Ehh this is already what I did for taa. I'd rather keep it consistent.

@alice-i-cecile alice-i-cecile added this pull request to the merge queue Jun 23, 2025
Merged via the queue into bevyengine:main with commit 0518eda Jun 23, 2025
38 checks passed
Trashtalk217 pushed a commit to Trashtalk217/bevy that referenced this pull request Jul 10, 2025
# Objective

- Start the realtime direct lighting work for bevy solari

## Solution

- Setup all the CPU-side code for the realtime lighting path (minus some
parts for the temporal reuse I haven't written yet)
- Implement RIS with 32 samples to choose a good random light
- Don't sample a disk for the directional light, just treat it as a
single point. This is faster and not much worse quality.

## Future
- Spatiotemporal reuse (ReSTIR DI)
- Denoiser (DLSS-RR)
- Light tile optimization for faster light selection 
- Indirect lighting (ReSTIR GI)

## Testing
- Run the solari example to see realtime
- Run the solari example with `-- --pathtracer` to see the existing
pathtracer

---

## Showcase

1 frame direct lighting:

![image](https://github.com/user-attachments/assets/b70b968d-9c73-4983-9b6b-b60cace9b47a)

Accumulated pathtracer output:

![image](https://github.com/user-attachments/assets/d681bded-ef53-4dbe-bcca-96997c58c3be)

---------

Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
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-Feature A new feature, making something new possible M-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.

5 participants