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

Higher quality lightmap sampling #16740

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 62 additions & 12 deletions crates/bevy_pbr/src/lightmap/lightmap.wgsl
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,72 @@
// Samples the lightmap, if any, and returns indirect illumination from it.
fn lightmap(uv: vec2<f32>, exposure: f32, instance_index: u32) -> vec3<f32> {
let packed_uv_rect = mesh[instance_index].lightmap_uv_rect;
let uv_rect = vec4<f32>(vec4<u32>(
packed_uv_rect.x & 0xffffu,
packed_uv_rect.x >> 16u,
packed_uv_rect.y & 0xffffu,
packed_uv_rect.y >> 16u)) / 65535.0;
let uv_rect = vec4<f32>(
unpack2x16unorm(packed_uv_rect.x),
unpack2x16unorm(packed_uv_rect.y),
);

let lightmap_uv = mix(uv_rect.xy, uv_rect.zw, uv);

// Bicubic 4-tap
// https://developer.nvidia.com/gpugems/gpugems2/part-iii-high-quality-rendering/chapter-20-fast-third-order-texture-filtering
// https://advances.realtimerendering.com/s2021/jpatry_advances2021/index.html#/111/0/2
let texture_size = vec2<f32>(textureDimensions(lightmaps_texture));
let texel_size = 1.0 / texture_size;
let puv = lightmap_uv * texture_size + 0.5;
let iuv = floor(puv);
let fuv = fract(puv);
let g0x = g0(fuv.x);
let g1x = g1(fuv.x);
let h0x = h0_approx(fuv.x);
let h1x = h1_approx(fuv.x);
let h0y = h0_approx(fuv.y);
let h1y = h1_approx(fuv.y);
let p0 = (vec2(iuv.x + h0x, iuv.y + h0y) - 0.5) * texel_size;
let p1 = (vec2(iuv.x + h1x, iuv.y + h0y) - 0.5) * texel_size;
let p2 = (vec2(iuv.x + h0x, iuv.y + h1y) - 0.5) * texel_size;
let p3 = (vec2(iuv.x + h1x, iuv.y + h1y) - 0.5) * texel_size;
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: Odd indentation here

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy+pasted tabs :/. Fixed.

let filtered_sample = g0(fuv.y) * (g0x * sample(p0) + g1x * sample(p1)) +
g1(fuv.y) * (g0x * sample(p2) + g1x * sample(p3));

return filtered_sample * exposure;
}

fn sample(uv: vec2<f32>) -> vec3<f32> {
// Mipmapping lightmaps is usually a bad idea due to leaking across UV
// islands, so there's no harm in using mip level 0 and it lets us avoid
// control flow uniformity problems.
//
// TODO(pcwalton): Consider bicubic filtering.
return textureSampleLevel(
lightmaps_texture,
lightmaps_sampler,
lightmap_uv,
0.0).rgb * exposure;
return textureSampleLevel(lightmaps_texture, lightmaps_sampler, uv, 0.0).rgb;
}

fn w0(a: f32) -> f32 {
return (1.0 / 6.0) * (a * (a * (-a + 3.0) - 3.0) + 1.0);
}

fn w1(a: f32) -> f32 {
return (1.0 / 6.0) * (a * a * (3.0 * a - 6.0) + 4.0);
}

fn w2(a: f32) -> f32 {
return (1.0 / 6.0) * (a * (a * (-3.0 * a + 3.0) + 3.0) + 1.0);
}

fn w3(a: f32) -> f32 {
return (1.0 / 6.0) * (a * a * a);
}

fn g0(a: f32) -> f32 {
return w0(a) + w1(a);
}

fn g1(a: f32) -> f32 {
return w2(a) + w3(a);
}

fn h0_approx(a: f32) -> f32 {
return 0.2 + a * (0.24 * a - 0.44);
Copy link
Contributor

Choose a reason for hiding this comment

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

For comparison with the code below, this is a fine approximation.

OkApprox

}

fn h1_approx(a: f32) -> f32 {
return 1.0 + a * (0.24 * a - 0.04);
Copy link
Contributor

Choose a reason for hiding this comment

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

I saw that comment, but the rounding on the coefficients for h1_approx leads to some serious error:
BadApprox

I calculated out the real coefficients here:
GoodApprox

Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
return 1.0 + a * (0.24 * a - 0.04);
return 1.00158 + a * (0.230963 * a - 0.0353001);

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 plugged it into desmos. Both yours and my h1 approx have inaccuracies. The difference is whether it occurs before or after the midpoint.

https://www.desmos.com/calculator/588bmvpwp9

Copy link
Contributor

Choose a reason for hiding this comment

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

Oops, I replaced the - with a +! Sign error. Ignore my comment, your code is fine the way it is

}
2 changes: 2 additions & 0 deletions crates/bevy_pbr/src/lightmap/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ pub struct LightmapPlugin;
#[reflect(Component, Default)]
pub struct Lightmap {
/// The lightmap texture.
///
/// The texture's sampler must be set to [`bevy_image::ImageSampler::linear`].
pub image: Handle<Image>,

/// The rectangle within the lightmap texture that the UVs are relative to.
Expand Down
Loading