-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Initial raytraced lighting progress (bevy_solari) #19058
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
base: main
Are you sure you want to change the base?
Conversation
… sample of the integral
let cos_theta = dot(-ray_direction, ray_hit.world_normal); | ||
let cosine_hemisphere_pdf = cos_theta / PI; // Weight for the next bounce because we importance sampled the diffuse BRDF for the next ray direction | ||
throughput *= (diffuse_brdf * cos_theta) / cosine_hemisphere_pdf; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should all just cancel out to throughput *= base_color
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes but I don't want to do that, because then it'll make things more confusing in the future.
This isn't meant to be fast, just accurate and easy to understand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably can put the math (and cancellation) in a comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd really rather leave it explicit 😅
let p = tonemapping_luminance(throughput); | ||
if rand_f(&rng) > p { break; } | ||
throughput /= p; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For low luminances (that don't russian roulette), this would lead to dividing by a very small number (which can lead to infs). I would probably clamp that with something like:
let p = max(0.05, 1.0 - tonemapping_luminance(throughput));
if rand_f(&rng) < p { break; }
throughput /= 1.0 - p;
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll have to test this.
I had something somewhat like this before, but it lead to bias and failing the white furnace test.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah this is basically what I had before, but it breaks the white furnace test. I think it's fine in practice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's weird, it should only lead to an increase in variance, not bias.
|
||
impl SolariPlugin { | ||
/// [`WgpuFeatures`] required for this plugin to function. | ||
pub fn required_wgpu_features() -> WgpuFeatures { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(unrelated) we're really overdue for structured plugins. this is the kind of thing that should be centralized by a trait and collected at initialization to request the minimal feature set.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reviewed only the rust side for now, will do wgsl later. Looks good so far!
pull_requests: [19058] | ||
--- | ||
|
||
(TODO: Embed solari example screenshot here) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this TODO and the one below needs resolving
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not in this PR. Instructions say not to embed images now, and that we'll do that once the release notes are transferred to the bevy website repo for the release. Otherwise we bloat this repo with multimedia.
@@ -4328,3 +4349,6 @@ name = "Extended Bindless Material" | |||
description = "Demonstrates bindless `ExtendedMaterial`" | |||
category = "Shaders" | |||
wasm = false | |||
|
|||
[patch.crates-io] | |||
naga_oil = { git = "https://github.com/JMS55/naga_oil", rev = "82476f83023da017be4963eaafc0f7b30e811bc3" } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we're gonna need to wait until we can use a published version here
@@ -203,6 +205,7 @@ impl Mesh { | |||
morph_targets: None, | |||
morph_target_names: None, | |||
asset_usage, | |||
enable_raytracing: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this being true mean its building a BLAS for all meshes even when bevy_solari is not in use?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How about instead, defaulting to false, and having a method on Mesh which checks the set of vertex attributes is {{POSITION, NORMAL, UV_0, TANGENT}} before setting the bool to true, and returns an error otherwise?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this being true mean its building a BLAS for all meshes even when bevy_solari is not in use?
Depends by what you mean by "not in use". If the solari scene plugin is added to the app, your GPU supports RT, and the mesh is compatible and has enable_raytracing: true, then yes the mesh will get a BLAS built.
If the plugin is not loaded (it's not part of default apps) / your GPU does not support RT, or the mesh is not compatible or has RT off, then no a BLAS won't be built.
How about instead, defaulting to false, and having a method on Mesh which checks the set of vertex attributes is {{POSITION, NORMAL, UV_0, TANGENT}} before setting the bool to true, and returns an error otherwise?
The raytracing_enabled field is not meant to be "whether or not the mesh is compatible for RT". The field is meant for cases where you don't want to build a BLAS for a mesh even if it's compatible. E.g. for higher level LODs if you're using exclusively lower level LODs for RT to reduce BLAS and traversal cost. I'll update the docs to make this explicit.
min_binding_size: None, | ||
}, | ||
count: MAX_MESH_COUNT, | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought there was a shorthand api for this
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not for entries with count, idt. If someone knows otherwise, let me know.
Co-authored-by: atlv <email@atlasdostal.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Very cool, good job
fn sample_cosine_hemisphere(normal: vec3<f32>, rng: ptr<function, u32>) -> vec3<f32> { | ||
let cos_theta = 1.0 - 2.0 * rand_f(rng); | ||
let phi = 2.0 * PI * rand_f(rng); | ||
let sin_theta = sqrt(max(1.0 - cos_theta * cos_theta, 0.0)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i assume the max accounts for the precision problems the reference describes
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Must be. I wrote this code a few years ago, tbh it's just copied from an older version of solari so I no longer remember.
world_normal: vec3<f32>, | ||
geometric_world_normal: vec3<f32>, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
docs here one what the distinction is would be good: im guessing world_normal is the normal as dictated by the mesh, with vertex interpolation, and geometric_world_normal is the normal of the triangle.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah basically.
I'm not going to spend time documenting it because this is basically all subject to change. I haven't decided exactly what the APIs are going to look like yet because some passes probably don't even care about normals, or materials, and for shadow rays we don't have BVH intersections and have to use vertex and index buffers where for other rays we can get the positions directly from the BVH, etc.
I plan to figure things out over time and write the APIs as I need them.
|
||
let local_normal = mat3x3(vertices[0].normal, vertices[1].normal, vertices[2].normal) * barycentrics; // TODO: Use barycentric lerp, ray_hit.object_to_world, cross product geo normal | ||
var world_normal = normalize(mat3x3(transform[0].xyz, transform[1].xyz, transform[2].xyz) * local_normal); | ||
let geometric_world_normal = world_normal; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this assuming "flat" normals? should it be taking a cross product of the edges instead?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure, I wrote this code a while ago. But I think I had tested on bistro at some point and it looked fine.
|
||
// Setup RNG | ||
let pixel_index = global_id.x + global_id.y * u32(view.viewport.z); | ||
let frame_index = u32(old_color.a) * 5782582u; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this doesn't look like its a frame index, plus isn't old_color.a in [0..1]? so casting it to u32 is basically always zero? so this is not really contributing anything i see old_color.a is the frame count, perhaps move the multiplication down into the rng initialization instead so that the name makes sense
Bevy Solari
Preface
Connections
This PR
After nearly two years, I've revived the raytraced lighting effort I first started in #10000.
Unlike that PR, which has realtime techniques, I've limited this PR to:
RaytracingScenePlugin
- BLAS and TLAS building, geometry and texture binding, sampling functions.PathtracingPlugin
- A non-realtime path tracer intended to serve as a testbed and reference.What's implemented?
What's not implemented?
End-user Usage
SolariPlugin
to your appMesh
asset you want to use for raytracing hasenable_raytracing: true
(defaults to true), and that it uses the standard uncompressed position/normal/uv_0/tangent vertex attribute set, triangle list topology, and 32-bit indices.RaytracingMesh3d
component to your entity (separate fromMesh3d
orMeshletMesh3d
).Testing