Higher quality bicubic lightmap sampling (#16740)
# Objective - Closes https://github.com/bevyengine/bevy/issues/14322. ## Solution - Implement fast 4-sample bicubic filtering based on this shader toy https://www.shadertoy.com/view/4df3Dn, with a small speedup from a ghost of tushima presentation. ## Testing - Did you test these changes? If so, how? - Ran on lightmapped example. Practically no difference in that scene. - Are there any parts that need more testing? - Lightmapping a better scene. ## Changelog - Lightmaps now have a higher quality bicubic sampling method (off by default). --------- Co-authored-by: Patrick Walton <pcwalton@mimiga.net>
This commit is contained in:
parent
e808fbe987
commit
bb0a82b9a7
@ -13,33 +13,87 @@
|
||||
// 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);
|
||||
let lightmap_slot = mesh[instance_index].material_and_lightmap_bind_group_slot >> 16u;
|
||||
|
||||
// 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
|
||||
#ifdef LIGHTMAP_BICUBIC_SAMPLING
|
||||
let texture_size = vec2<f32>(lightmap_size(lightmap_slot));
|
||||
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;
|
||||
let color = g0(fuv.y) * (g0x * sample(p0, lightmap_slot) + g1x * sample(p1, lightmap_slot)) + g1(fuv.y) * (g0x * sample(p2, lightmap_slot) + g1x * sample(p3, lightmap_slot));
|
||||
#else
|
||||
let color = sample(lightmap_uv, lightmap_slot);
|
||||
#endif
|
||||
|
||||
return color * exposure;
|
||||
}
|
||||
|
||||
fn lightmap_size(lightmap_slot: u32) -> vec2<u32> {
|
||||
#ifdef MULTIPLE_LIGHTMAPS_IN_ARRAY
|
||||
return textureDimensions(lightmaps_textures[lightmap_slot]);
|
||||
#else
|
||||
return textureDimensions(lightmaps_texture);
|
||||
#endif
|
||||
}
|
||||
|
||||
fn sample(uv: vec2<f32>, lightmap_slot: u32) -> 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.
|
||||
#ifdef MULTIPLE_LIGHTMAPS_IN_ARRAY
|
||||
let lightmap_slot = mesh[instance_index].material_and_lightmap_bind_group_slot >> 16u;
|
||||
return textureSampleLevel(
|
||||
lightmaps_textures[lightmap_slot],
|
||||
lightmaps_samplers[lightmap_slot],
|
||||
lightmap_uv,
|
||||
0.0
|
||||
).rgb * exposure;
|
||||
#else // MULTIPLE_LIGHTMAPS_IN_ARRAY
|
||||
return textureSampleLevel(
|
||||
lightmaps_texture,
|
||||
lightmaps_sampler,
|
||||
lightmap_uv,
|
||||
0.0
|
||||
).rgb * exposure;
|
||||
#endif // MULTIPLE_LIGHTMAPS_IN_ARRAY
|
||||
return textureSampleLevel(lightmaps_textures[lightmap_slot], lightmaps_samplers[lightmap_slot], uv, 0.0).rgb;
|
||||
#else
|
||||
return textureSampleLevel(lightmaps_texture, lightmaps_sampler, uv, 0.0).rgb;
|
||||
#endif
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
fn h1_approx(a: f32) -> f32 {
|
||||
return 1.0 + a * (0.24 * a - 0.04);
|
||||
}
|
||||
|
||||
@ -100,6 +100,13 @@ pub struct Lightmap {
|
||||
/// This field allows lightmaps for a variety of meshes to be packed into a
|
||||
/// single atlas.
|
||||
pub uv_rect: Rect,
|
||||
|
||||
/// Whether bicubic sampling should be used for sampling this lightmap.
|
||||
///
|
||||
/// Bicubic sampling is higher quality, but slower, and may lead to light leaks.
|
||||
///
|
||||
/// If true, the lightmap texture's sampler must be set to [`bevy_image::ImageSampler::linear`].
|
||||
pub bicubic_sampling: bool,
|
||||
}
|
||||
|
||||
/// Lightmap data stored in the render world.
|
||||
@ -126,6 +133,9 @@ pub(crate) struct RenderLightmap {
|
||||
///
|
||||
/// If bindless lightmaps aren't in use, this will be 0.
|
||||
pub(crate) slot_index: LightmapSlotIndex,
|
||||
|
||||
// Whether or not bicubic sampling should be used for this lightmap.
|
||||
pub(crate) bicubic_sampling: bool,
|
||||
}
|
||||
|
||||
/// Stores data for all lightmaps in the render world.
|
||||
@ -237,6 +247,7 @@ fn extract_lightmaps(
|
||||
lightmap.uv_rect,
|
||||
slab_index,
|
||||
slot_index,
|
||||
lightmap.bicubic_sampling,
|
||||
),
|
||||
);
|
||||
|
||||
@ -296,12 +307,14 @@ impl RenderLightmap {
|
||||
uv_rect: Rect,
|
||||
slab_index: LightmapSlabIndex,
|
||||
slot_index: LightmapSlotIndex,
|
||||
bicubic_sampling: bool,
|
||||
) -> Self {
|
||||
Self {
|
||||
image,
|
||||
uv_rect,
|
||||
slab_index,
|
||||
slot_index,
|
||||
bicubic_sampling,
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -327,6 +340,7 @@ impl Default for Lightmap {
|
||||
Self {
|
||||
image: Default::default(),
|
||||
uv_rect: Rect::new(0.0, 0.0, 1.0, 1.0),
|
||||
bicubic_sampling: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -804,12 +804,14 @@ pub fn queue_material_meshes<M: Material>(
|
||||
| MeshPipelineKey::from_bits_retain(mesh.key_bits.bits())
|
||||
| mesh_pipeline_key_bits;
|
||||
|
||||
let lightmap_slab_index = render_lightmaps
|
||||
.render_lightmaps
|
||||
.get(visible_entity)
|
||||
.map(|lightmap| lightmap.slab_index);
|
||||
if lightmap_slab_index.is_some() {
|
||||
let mut lightmap_slab = None;
|
||||
if let Some(lightmap) = render_lightmaps.render_lightmaps.get(visible_entity) {
|
||||
lightmap_slab = Some(*lightmap.slab_index);
|
||||
mesh_key |= MeshPipelineKey::LIGHTMAPPED;
|
||||
|
||||
if lightmap.bicubic_sampling {
|
||||
mesh_key |= MeshPipelineKey::LIGHTMAP_BICUBIC_SAMPLING;
|
||||
}
|
||||
}
|
||||
|
||||
if render_visibility_ranges.entity_has_crossfading_visibility_ranges(*visible_entity) {
|
||||
@ -875,8 +877,7 @@ pub fn queue_material_meshes<M: Material>(
|
||||
material_bind_group_index: Some(material.binding.group.0),
|
||||
vertex_slab: vertex_slab.unwrap_or_default(),
|
||||
index_slab,
|
||||
lightmap_slab: lightmap_slab_index
|
||||
.map(|lightmap_slab_index| *lightmap_slab_index),
|
||||
lightmap_slab,
|
||||
};
|
||||
let bin_key = Opaque3dBinKey {
|
||||
asset_id: mesh_instance.mesh_asset_id.into(),
|
||||
|
||||
@ -488,6 +488,12 @@ where
|
||||
if key.mesh_key.contains(MeshPipelineKey::LIGHTMAPPED) {
|
||||
shader_defs.push("LIGHTMAP".into());
|
||||
}
|
||||
if key
|
||||
.mesh_key
|
||||
.contains(MeshPipelineKey::LIGHTMAP_BICUBIC_SAMPLING)
|
||||
{
|
||||
shader_defs.push("LIGHTMAP_BICUBIC_SAMPLING".into());
|
||||
}
|
||||
|
||||
if layout.0.contains(Mesh::ATTRIBUTE_COLOR) {
|
||||
shader_defs.push("VERTEX_COLORS".into());
|
||||
@ -911,12 +917,17 @@ pub fn queue_prepass_material_meshes<M: Material>(
|
||||
mesh_key |= MeshPipelineKey::DEFERRED_PREPASS;
|
||||
}
|
||||
|
||||
let lightmap_slab_index = render_lightmaps
|
||||
.render_lightmaps
|
||||
.get(visible_entity)
|
||||
.map(|lightmap| lightmap.slab_index);
|
||||
if lightmap_slab_index.is_some() {
|
||||
if let Some(lightmap) = render_lightmaps.render_lightmaps.get(visible_entity) {
|
||||
// Even though we don't use the lightmap in the forward prepass, the
|
||||
// `SetMeshBindGroup` render command will bind the data for it. So
|
||||
// we need to include the appropriate flag in the mesh pipeline key
|
||||
// to ensure that the necessary bind group layout entries are
|
||||
// present.
|
||||
mesh_key |= MeshPipelineKey::LIGHTMAPPED;
|
||||
|
||||
if lightmap.bicubic_sampling && deferred {
|
||||
mesh_key |= MeshPipelineKey::LIGHTMAP_BICUBIC_SAMPLING;
|
||||
}
|
||||
}
|
||||
|
||||
if render_visibility_ranges.entity_has_crossfading_visibility_ranges(*visible_entity) {
|
||||
|
||||
@ -1810,12 +1810,13 @@ bitflags::bitflags! {
|
||||
const TEMPORAL_JITTER = 1 << 11;
|
||||
const READS_VIEW_TRANSMISSION_TEXTURE = 1 << 12;
|
||||
const LIGHTMAPPED = 1 << 13;
|
||||
const IRRADIANCE_VOLUME = 1 << 14;
|
||||
const VISIBILITY_RANGE_DITHER = 1 << 15;
|
||||
const SCREEN_SPACE_REFLECTIONS = 1 << 16;
|
||||
const HAS_PREVIOUS_SKIN = 1 << 17;
|
||||
const HAS_PREVIOUS_MORPH = 1 << 18;
|
||||
const OIT_ENABLED = 1 << 19;
|
||||
const LIGHTMAP_BICUBIC_SAMPLING = 1 << 14;
|
||||
const IRRADIANCE_VOLUME = 1 << 15;
|
||||
const VISIBILITY_RANGE_DITHER = 1 << 16;
|
||||
const SCREEN_SPACE_REFLECTIONS = 1 << 17;
|
||||
const HAS_PREVIOUS_SKIN = 1 << 18;
|
||||
const HAS_PREVIOUS_MORPH = 1 << 19;
|
||||
const OIT_ENABLED = 1 << 20;
|
||||
const LAST_FLAG = Self::OIT_ENABLED.bits();
|
||||
|
||||
// Bitfields
|
||||
@ -2239,6 +2240,9 @@ impl SpecializedMeshPipeline for MeshPipeline {
|
||||
if key.contains(MeshPipelineKey::LIGHTMAPPED) {
|
||||
shader_defs.push("LIGHTMAP".into());
|
||||
}
|
||||
if key.contains(MeshPipelineKey::LIGHTMAP_BICUBIC_SAMPLING) {
|
||||
shader_defs.push("LIGHTMAP_BICUBIC_SAMPLING".into());
|
||||
}
|
||||
|
||||
if key.contains(MeshPipelineKey::TEMPORAL_JITTER) {
|
||||
shader_defs.push("TEMPORAL_JITTER".into());
|
||||
|
||||
@ -13,6 +13,9 @@ struct Args {
|
||||
/// enables deferred shading
|
||||
#[argh(switch)]
|
||||
deferred: bool,
|
||||
/// enables bicubic filtering
|
||||
#[argh(switch)]
|
||||
bicubic: bool,
|
||||
}
|
||||
|
||||
fn main() {
|
||||
@ -63,6 +66,7 @@ fn add_lightmaps_to_meshes(
|
||||
(Entity, &Name, &MeshMaterial3d<StandardMaterial>),
|
||||
(With<Mesh3d>, Without<Lightmap>),
|
||||
>,
|
||||
args: Res<Args>,
|
||||
) {
|
||||
let exposure = 250.0;
|
||||
for (entity, name, material) in meshes.iter() {
|
||||
@ -70,6 +74,7 @@ fn add_lightmaps_to_meshes(
|
||||
materials.get_mut(material).unwrap().lightmap_exposure = exposure;
|
||||
commands.entity(entity).insert(Lightmap {
|
||||
image: asset_server.load("lightmaps/CornellBox-Large.zstd.ktx2"),
|
||||
bicubic_sampling: args.bicubic,
|
||||
..default()
|
||||
});
|
||||
continue;
|
||||
@ -79,6 +84,7 @@ fn add_lightmaps_to_meshes(
|
||||
materials.get_mut(material).unwrap().lightmap_exposure = exposure;
|
||||
commands.entity(entity).insert(Lightmap {
|
||||
image: asset_server.load("lightmaps/CornellBox-Small.zstd.ktx2"),
|
||||
bicubic_sampling: args.bicubic,
|
||||
..default()
|
||||
});
|
||||
continue;
|
||||
@ -88,6 +94,7 @@ fn add_lightmaps_to_meshes(
|
||||
materials.get_mut(material).unwrap().lightmap_exposure = exposure;
|
||||
commands.entity(entity).insert(Lightmap {
|
||||
image: asset_server.load("lightmaps/CornellBox-Box.zstd.ktx2"),
|
||||
bicubic_sampling: args.bicubic,
|
||||
..default()
|
||||
});
|
||||
continue;
|
||||
|
||||
@ -267,6 +267,7 @@ fn update_lightmaps(
|
||||
commands.entity(entity).insert(Lightmap {
|
||||
image: (*lightmap).clone(),
|
||||
uv_rect,
|
||||
bicubic_sampling: false,
|
||||
});
|
||||
}
|
||||
None => {
|
||||
@ -290,6 +291,7 @@ fn update_lightmaps(
|
||||
commands.entity(entity).insert(Lightmap {
|
||||
image: (*lightmap).clone(),
|
||||
uv_rect: SPHERE_UV_RECT,
|
||||
bicubic_sampling: false,
|
||||
});
|
||||
}
|
||||
_ => {
|
||||
|
||||
Loading…
Reference in New Issue
Block a user