diff --git a/Cargo.toml b/Cargo.toml index 348e939f008db..16e6191507cc8 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3849,6 +3849,18 @@ description = "Demonstrates percentage-closer soft shadows (PCSS)" category = "3D Rendering" wasm = false +[[example]] +name = "mixed_lighting" +path = "examples/3d/mixed_lighting.rs" +doc-scrape-examples = true +required-features = ["jpeg"] + +[package.metadata.example.mixed_lighting] +name = "Mixed lighting" +description = "Demonstrates how to combine baked and dynamic lighting" +category = "3D Rendering" +wasm = true + [[example]] name = "animated_ui" path = "examples/animation/animated_ui.rs" diff --git a/assets/lightmaps/MixedLightingExample-Baked.zstd.ktx2 b/assets/lightmaps/MixedLightingExample-Baked.zstd.ktx2 new file mode 100644 index 0000000000000..6f699f561fc94 Binary files /dev/null and b/assets/lightmaps/MixedLightingExample-Baked.zstd.ktx2 differ diff --git a/assets/lightmaps/MixedLightingExample-MixedDirect.zstd.ktx2 b/assets/lightmaps/MixedLightingExample-MixedDirect.zstd.ktx2 new file mode 100644 index 0000000000000..1977d638e15eb Binary files /dev/null and b/assets/lightmaps/MixedLightingExample-MixedDirect.zstd.ktx2 differ diff --git a/assets/lightmaps/MixedLightingExample-MixedIndirect.zstd.ktx2 b/assets/lightmaps/MixedLightingExample-MixedIndirect.zstd.ktx2 new file mode 100644 index 0000000000000..070d8ac613dce Binary files /dev/null and b/assets/lightmaps/MixedLightingExample-MixedIndirect.zstd.ktx2 differ diff --git a/assets/models/MixedLightingExample/MixedLightingExample.bin b/assets/models/MixedLightingExample/MixedLightingExample.bin new file mode 100644 index 0000000000000..93321049fde71 Binary files /dev/null and b/assets/models/MixedLightingExample/MixedLightingExample.bin differ diff --git a/assets/models/MixedLightingExample/MixedLightingExample.gltf b/assets/models/MixedLightingExample/MixedLightingExample.gltf new file mode 100644 index 0000000000000..0da5f35947e48 --- /dev/null +++ b/assets/models/MixedLightingExample/MixedLightingExample.gltf @@ -0,0 +1,1012 @@ +{ + "asset":{ + "generator":"Khronos glTF Blender I/O v4.3.47", + "version":"2.0" + }, + "extensionsUsed":[ + "KHR_materials_sheen", + "KHR_texture_transform", + "KHR_materials_variants", + "KHR_lights_punctual" + ], + "extensionsRequired":[ + "KHR_texture_transform", + "KHR_lights_punctual" + ], + "extensions":{ + "KHR_materials_variants":{ + "variants":[ + { + "name":"Mango Velvet" + }, + { + "name":"Peacock Velvet" + } + ] + }, + "KHR_lights_punctual":{ + "lights":[ + { + "color":[ + 1, + 1, + 1 + ], + "intensity":2049, + "type":"directional", + "name":"Sun" + } + ] + } + }, + "scene":0, + "scenes":[ + { + "name":"Scene", + "nodes":[ + 0, + 1, + 2, + 3, + 4, + 5, + 6 + ] + } + ], + "nodes":[ + { + "mesh":0, + "name":"Plane", + "scale":[ + 1.5535465478897095, + 1.5535465478897095, + 1.5535465478897095 + ] + }, + { + "mesh":1, + "name":"Sphere", + "scale":[ + 0.1322203278541565, + 0.1322203278541565, + 0.1322203278541565 + ], + "translation":[ + 0, + 0.523322343826294, + 0 + ] + }, + { + "mesh":2, + "name":"SheenChair_fabric" + }, + { + "mesh":3, + "name":"SheenChair_label", + "rotation":[ + -0.04361938685178757, + 0, + 0, + 0.9990482330322266 + ], + "translation":[ + -0.000662992475554347, + 0.23622000217437744, + 0.059600744396448135 + ] + }, + { + "mesh":4, + "name":"SheenChair_metal" + }, + { + "mesh":5, + "name":"SheenChair_wood" + }, + { + "extensions":{ + "KHR_lights_punctual":{ + "light":0 + } + }, + "name":"Sun", + "rotation":[ + -0.48860564827919006, + 0.11653755605220795, + -0.20060963928699493, + 0.8410941362380981 + ], + "translation":[ + 0.6279287338256836, + 1.7347862720489502, + 0.7396787405014038 + ] + } + ], + "materials":[ + { + "doubleSided":true, + "name":"Floor", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0.800000011920929, + 0.800000011920929, + 0.800000011920929, + 1 + ], + "metallicFactor":0, + "roughnessFactor":0.5 + } + }, + { + "doubleSided":true, + "name":"Sphere", + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0.800000011920929, + 0.800000011920929, + 0.800000011920929, + 1 + ], + "metallicFactor":0, + "roughnessFactor":0.5 + } + }, + { + "extensions":{ + "KHR_materials_sheen":{ + "sheenColorFactor":[ + 1.0, + 0.32899999618530273, + 0.10000000149011612 + ], + "sheenRoughnessFactor":0.800000011920929 + } + }, + "name":"fabric Mystere Mango Velvet", + "occlusionTexture":{ + "index":0, + "texCoord":1 + }, + "pbrMetallicRoughness":{ + "baseColorTexture":{ + "index":1 + }, + "metallicFactor":0, + "roughnessFactor":0.800000011920929 + } + }, + { + "extensions":{ + "KHR_materials_sheen":{ + "sheenColorFactor":[ + 0.013000000268220901, + 0.2840000092983246, + 0.2980000078678131 + ], + "sheenRoughnessFactor":0.800000011920929 + } + }, + "name":"fabric Mystere Peacock Velvet", + "normalTexture":{ + "extensions":{ + "KHR_texture_transform":{ + "offset":[ + -0.5, + 0.5 + ], + "scale":[ + 2, + 2 + ] + } + }, + "index":2, + "scale":0.6000000238418579 + }, + "occlusionTexture":{ + "index":3, + "texCoord":1 + }, + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0, + 0.09399999678134918, + 0.0989999994635582, + 1 + ], + "baseColorTexture":{ + "extensions":{ + "KHR_texture_transform":{ + "offset":[ + -3, + 3 + ], + "scale":[ + 7, + 7 + ] + } + }, + "index":4 + }, + "roughnessFactor":0.800000011920929 + } + }, + { + "name":"label", + "occlusionTexture":{ + "index":5, + "texCoord":1 + }, + "pbrMetallicRoughness":{ + "baseColorTexture":{ + "index":6 + }, + "metallicFactor":0 + } + }, + { + "name":"metal", + "occlusionTexture":{ + "index":7, + "texCoord":1 + }, + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0.2800000011920929, + 0.25999999046325684, + 0.23000000417232513, + 1 + ], + "roughnessFactor":0.30000001192092896 + } + }, + { + "name":"wood Brown", + "occlusionTexture":{ + "index":8, + "texCoord":1 + }, + "pbrMetallicRoughness":{ + "baseColorTexture":{ + "extensions":{ + "KHR_texture_transform":{ + "offset":[ + -0.8635583765172254, + 1.125025697811076 + ], + "rotation":0.0872664675116539, + "scale":[ + 3, + 3 + ] + } + }, + "index":9 + }, + "metallicRoughnessTexture":{ + "extensions":{ + "KHR_texture_transform":{ + "offset":[ + -0.8635583765172254, + 1.125025697811076 + ], + "rotation":0.0872664675116539, + "scale":[ + 3, + 3 + ] + } + }, + "index":8 + } + } + }, + { + "name":"wood Black", + "occlusionTexture":{ + "index":10, + "texCoord":1 + }, + "pbrMetallicRoughness":{ + "baseColorFactor":[ + 0.035999998450279236, + 0.035999998450279236, + 0.035999998450279236, + 1 + ], + "baseColorTexture":{ + "extensions":{ + "KHR_texture_transform":{ + "offset":[ + -0.8635583765172254, + 1.125025697811076 + ], + "rotation":0.0872664675116539, + "scale":[ + 3, + 3 + ] + } + }, + "index":11 + }, + "metallicRoughnessTexture":{ + "extensions":{ + "KHR_texture_transform":{ + "offset":[ + -0.8635583765172254, + 1.125025697811076 + ], + "rotation":0.0872664675116539, + "scale":[ + 3, + 3 + ] + } + }, + "index":10 + } + } + } + ], + "meshes":[ + { + "name":"Plane", + "primitives":[ + { + "attributes":{ + "POSITION":0, + "NORMAL":1, + "TEXCOORD_0":2, + "TEXCOORD_1":3 + }, + "indices":4, + "material":0 + } + ] + }, + { + "name":"Sphere", + "primitives":[ + { + "attributes":{ + "POSITION":5, + "NORMAL":6, + "TEXCOORD_0":7, + "TEXCOORD_1":8 + }, + "indices":9, + "material":1 + } + ] + }, + { + "name":"SheenChair_fabric", + "primitives":[ + { + "attributes":{ + "POSITION":10, + "NORMAL":11, + "TEXCOORD_0":12, + "TEXCOORD_1":13 + }, + "extensions":{ + "KHR_materials_variants":{ + "mappings":[ + { + "material":2, + "variants":[ + 0 + ] + }, + { + "material":3, + "variants":[ + 1 + ] + } + ] + } + }, + "indices":14, + "material":2 + } + ] + }, + { + "name":"SheenChair_label", + "primitives":[ + { + "attributes":{ + "POSITION":15, + "NORMAL":16, + "TEXCOORD_0":17, + "TEXCOORD_1":18 + }, + "indices":19, + "material":4 + } + ] + }, + { + "name":"SheenChair_metal", + "primitives":[ + { + "attributes":{ + "POSITION":20, + "NORMAL":21, + "TEXCOORD_0":22, + "TEXCOORD_1":23 + }, + "indices":24, + "material":5 + } + ] + }, + { + "name":"SheenChair_wood", + "primitives":[ + { + "attributes":{ + "POSITION":25, + "NORMAL":26, + "TEXCOORD_0":27, + "TEXCOORD_1":28 + }, + "extensions":{ + "KHR_materials_variants":{ + "mappings":[ + { + "material":6, + "variants":[ + 0 + ] + }, + { + "material":7, + "variants":[ + 1 + ] + } + ] + } + }, + "indices":29, + "material":6 + } + ] + } + ], + "textures":[ + { + "sampler":0, + "source":0 + }, + { + "sampler":0, + "source":1 + }, + { + "sampler":0, + "source":2 + }, + { + "sampler":0, + "source":0 + }, + { + "sampler":0, + "source":3 + }, + { + "sampler":0, + "source":0 + }, + { + "sampler":0, + "source":4 + }, + { + "sampler":0, + "source":0 + }, + { + "sampler":0, + "source":5 + }, + { + "sampler":0, + "source":6 + }, + { + "sampler":0, + "source":7 + }, + { + "sampler":0, + "source":6 + } + ], + "images":[ + { + "mimeType":"image/jpeg", + "name":"SheenChairOcclusion", + "uri":"SheenChairOcclusion.jpg" + }, + { + "mimeType":"image/jpeg", + "name":"SheenChairBaseColor", + "uri":"SheenChairBaseColor.jpg" + }, + { + "mimeType":"image/jpeg", + "name":"SheenChairFabricNormal", + "uri":"SheenChairFabricNormal.jpg" + }, + { + "mimeType":"image/jpeg", + "name":"SheenChairFabricOcclusion", + "uri":"SheenChairFabricOcclusion.jpg" + }, + { + "mimeType":"image/jpeg", + "name":"SheenChairLabelBaseColor", + "uri":"SheenChairLabelBaseColor.jpg" + }, + { + "mimeType":"image/jpeg", + "name":"SheenChairOcclusion-SheenChairWoodMetallicRoughness", + "uri":"SheenChairOcclusion-SheenChairWoodMetallicRoughness.jpg" + }, + { + "mimeType":"image/jpeg", + "name":"SheenChairWoodBaseColor", + "uri":"SheenChairWoodBaseColor.jpg" + }, + { + "mimeType":"image/jpeg", + "name":"SheenChairOcclusion-SheenChairBaseColor", + "uri":"SheenChairOcclusion-SheenChairBaseColor.jpg" + } + ], + "accessors":[ + { + "bufferView":0, + "componentType":5126, + "count":4, + "max":[ + 1, + 0, + 1 + ], + "min":[ + -1, + 0, + -1 + ], + "type":"VEC3" + }, + { + "bufferView":1, + "componentType":5126, + "count":4, + "type":"VEC3" + }, + { + "bufferView":2, + "componentType":5126, + "count":4, + "type":"VEC2" + }, + { + "bufferView":3, + "componentType":5126, + "count":4, + "type":"VEC2" + }, + { + "bufferView":4, + "componentType":5123, + "count":6, + "type":"SCALAR" + }, + { + "bufferView":5, + "componentType":5126, + "count":559, + "max":[ + 0.9999997019767761, + 1, + 0.9999993443489075 + ], + "min":[ + -0.9999990463256836, + -1, + -1 + ], + "type":"VEC3" + }, + { + "bufferView":6, + "componentType":5126, + "count":559, + "type":"VEC3" + }, + { + "bufferView":7, + "componentType":5126, + "count":559, + "type":"VEC2" + }, + { + "bufferView":8, + "componentType":5126, + "count":559, + "type":"VEC2" + }, + { + "bufferView":9, + "componentType":5123, + "count":2880, + "type":"SCALAR" + }, + { + "bufferView":10, + "componentType":5126, + "count":14350, + "max":[ + 0.41253557801246643, + 0.6861773133277893, + 0.293540358543396 + ], + "min":[ + -0.41402238607406616, + 0.2265738844871521, + -0.2767251133918762 + ], + "type":"VEC3" + }, + { + "bufferView":11, + "componentType":5126, + "count":14350, + "type":"VEC3" + }, + { + "bufferView":12, + "componentType":5126, + "count":14350, + "type":"VEC2" + }, + { + "bufferView":13, + "componentType":5126, + "count":14350, + "type":"VEC2" + }, + { + "bufferView":14, + "componentType":5123, + "count":78528, + "type":"SCALAR" + }, + { + "bufferView":15, + "componentType":5126, + "count":81, + "max":[ + 0.13618184626102448, + 0.010055896826088428, + 0.04677271470427513 + ], + "min":[ + -0.13588549196720123, + 0.005801851861178875, + -0.11070986837148666 + ], + "type":"VEC3" + }, + { + "bufferView":16, + "componentType":5126, + "count":81, + "type":"VEC3" + }, + { + "bufferView":17, + "componentType":5126, + "count":81, + "type":"VEC2" + }, + { + "bufferView":18, + "componentType":5126, + "count":81, + "type":"VEC2" + }, + { + "bufferView":19, + "componentType":5123, + "count":384, + "type":"SCALAR" + }, + { + "bufferView":20, + "componentType":5126, + "count":2520, + "max":[ + 0.33995118737220764, + 0.5014813542366028, + 0.22082294523715973 + ], + "min":[ + -0.33995118737220764, + -6.977785233175382e-05, + -0.2393345683813095 + ], + "type":"VEC3" + }, + { + "bufferView":21, + "componentType":5126, + "count":2520, + "type":"VEC3" + }, + { + "bufferView":22, + "componentType":5126, + "count":2520, + "type":"VEC2" + }, + { + "bufferView":23, + "componentType":5126, + "count":2520, + "type":"VEC2" + }, + { + "bufferView":24, + "componentType":5123, + "count":11520, + "type":"SCALAR" + }, + { + "bufferView":25, + "componentType":5126, + "count":5514, + "max":[ + 0.3903724253177643, + 0.6404843330383301, + 0.2655284106731415 + ], + "min":[ + -0.3916984498500824, + 0.005148450843989849, + -0.27543526887893677 + ], + "type":"VEC3" + }, + { + "bufferView":26, + "componentType":5126, + "count":5514, + "type":"VEC3" + }, + { + "bufferView":27, + "componentType":5126, + "count":5514, + "type":"VEC2" + }, + { + "bufferView":28, + "componentType":5126, + "count":5514, + "type":"VEC2" + }, + { + "bufferView":29, + "componentType":5123, + "count":29376, + "type":"SCALAR" + } + ], + "bufferViews":[ + { + "buffer":0, + "byteLength":48, + "byteOffset":0, + "target":34962 + }, + { + "buffer":0, + "byteLength":48, + "byteOffset":48, + "target":34962 + }, + { + "buffer":0, + "byteLength":32, + "byteOffset":96, + "target":34962 + }, + { + "buffer":0, + "byteLength":32, + "byteOffset":128, + "target":34962 + }, + { + "buffer":0, + "byteLength":12, + "byteOffset":160, + "target":34963 + }, + { + "buffer":0, + "byteLength":6708, + "byteOffset":172, + "target":34962 + }, + { + "buffer":0, + "byteLength":6708, + "byteOffset":6880, + "target":34962 + }, + { + "buffer":0, + "byteLength":4472, + "byteOffset":13588, + "target":34962 + }, + { + "buffer":0, + "byteLength":4472, + "byteOffset":18060, + "target":34962 + }, + { + "buffer":0, + "byteLength":5760, + "byteOffset":22532, + "target":34963 + }, + { + "buffer":0, + "byteLength":172200, + "byteOffset":28292, + "target":34962 + }, + { + "buffer":0, + "byteLength":172200, + "byteOffset":200492, + "target":34962 + }, + { + "buffer":0, + "byteLength":114800, + "byteOffset":372692, + "target":34962 + }, + { + "buffer":0, + "byteLength":114800, + "byteOffset":487492, + "target":34962 + }, + { + "buffer":0, + "byteLength":157056, + "byteOffset":602292, + "target":34963 + }, + { + "buffer":0, + "byteLength":972, + "byteOffset":759348, + "target":34962 + }, + { + "buffer":0, + "byteLength":972, + "byteOffset":760320, + "target":34962 + }, + { + "buffer":0, + "byteLength":648, + "byteOffset":761292, + "target":34962 + }, + { + "buffer":0, + "byteLength":648, + "byteOffset":761940, + "target":34962 + }, + { + "buffer":0, + "byteLength":768, + "byteOffset":762588, + "target":34963 + }, + { + "buffer":0, + "byteLength":30240, + "byteOffset":763356, + "target":34962 + }, + { + "buffer":0, + "byteLength":30240, + "byteOffset":793596, + "target":34962 + }, + { + "buffer":0, + "byteLength":20160, + "byteOffset":823836, + "target":34962 + }, + { + "buffer":0, + "byteLength":20160, + "byteOffset":843996, + "target":34962 + }, + { + "buffer":0, + "byteLength":23040, + "byteOffset":864156, + "target":34963 + }, + { + "buffer":0, + "byteLength":66168, + "byteOffset":887196, + "target":34962 + }, + { + "buffer":0, + "byteLength":66168, + "byteOffset":953364, + "target":34962 + }, + { + "buffer":0, + "byteLength":44112, + "byteOffset":1019532, + "target":34962 + }, + { + "buffer":0, + "byteLength":44112, + "byteOffset":1063644, + "target":34962 + }, + { + "buffer":0, + "byteLength":58752, + "byteOffset":1107756, + "target":34963 + } + ], + "samplers":[ + { + "magFilter":9729, + "minFilter":9987 + } + ], + "buffers":[ + { + "byteLength":1166508, + "uri":"MixedLightingExample.bin" + } + ] +} diff --git a/assets/models/MixedLightingExample/SheenChairBaseColor.jpg b/assets/models/MixedLightingExample/SheenChairBaseColor.jpg new file mode 100644 index 0000000000000..3c6d2bd108ee9 Binary files /dev/null and b/assets/models/MixedLightingExample/SheenChairBaseColor.jpg differ diff --git a/assets/models/MixedLightingExample/SheenChairFabricNormal.jpg b/assets/models/MixedLightingExample/SheenChairFabricNormal.jpg new file mode 100644 index 0000000000000..d930a8fabcf59 Binary files /dev/null and b/assets/models/MixedLightingExample/SheenChairFabricNormal.jpg differ diff --git a/assets/models/MixedLightingExample/SheenChairFabricOcclusion.jpg b/assets/models/MixedLightingExample/SheenChairFabricOcclusion.jpg new file mode 100644 index 0000000000000..76abfffcd2daa Binary files /dev/null and b/assets/models/MixedLightingExample/SheenChairFabricOcclusion.jpg differ diff --git a/assets/models/MixedLightingExample/SheenChairLabelBaseColor.jpg b/assets/models/MixedLightingExample/SheenChairLabelBaseColor.jpg new file mode 100644 index 0000000000000..ba0e0b8abb497 Binary files /dev/null and b/assets/models/MixedLightingExample/SheenChairLabelBaseColor.jpg differ diff --git a/assets/models/MixedLightingExample/SheenChairOcclusion-SheenChairBaseColor.jpg b/assets/models/MixedLightingExample/SheenChairOcclusion-SheenChairBaseColor.jpg new file mode 100644 index 0000000000000..2e64ac017ad9b Binary files /dev/null and b/assets/models/MixedLightingExample/SheenChairOcclusion-SheenChairBaseColor.jpg differ diff --git a/assets/models/MixedLightingExample/SheenChairOcclusion-SheenChairWoodMetallicRoughness.jpg b/assets/models/MixedLightingExample/SheenChairOcclusion-SheenChairWoodMetallicRoughness.jpg new file mode 100644 index 0000000000000..0ee17743594bc Binary files /dev/null and b/assets/models/MixedLightingExample/SheenChairOcclusion-SheenChairWoodMetallicRoughness.jpg differ diff --git a/assets/models/MixedLightingExample/SheenChairOcclusion.jpg b/assets/models/MixedLightingExample/SheenChairOcclusion.jpg new file mode 100644 index 0000000000000..2759807704f2a Binary files /dev/null and b/assets/models/MixedLightingExample/SheenChairOcclusion.jpg differ diff --git a/assets/models/MixedLightingExample/SheenChairWoodBaseColor.jpg b/assets/models/MixedLightingExample/SheenChairWoodBaseColor.jpg new file mode 100644 index 0000000000000..43631a2b4f09c Binary files /dev/null and b/assets/models/MixedLightingExample/SheenChairWoodBaseColor.jpg differ diff --git a/crates/bevy_pbr/src/light/ambient_light.rs b/crates/bevy_pbr/src/light/ambient_light.rs index 9c42eea516bd3..068e445f3b496 100644 --- a/crates/bevy_pbr/src/light/ambient_light.rs +++ b/crates/bevy_pbr/src/light/ambient_light.rs @@ -19,12 +19,21 @@ use super::*; #[reflect(Resource, Debug, Default)] pub struct AmbientLight { pub color: Color, + /// A direct scale factor multiplied with `color` before being passed to the shader. /// /// After applying this multiplier, the resulting value should be in units of [cd/m^2]. /// /// [cd/m^2]: https://en.wikipedia.org/wiki/Candela_per_square_metre pub brightness: f32, + + /// Whether this ambient light has an effect on meshes with lightmaps. + /// + /// Set this to false if your lightmap baking tool bakes the ambient light + /// into the lightmaps, to avoid rendering that light twice. + /// + /// By default, this is set to true. + pub affects_lightmapped_meshes: bool, } impl Default for AmbientLight { @@ -32,6 +41,7 @@ impl Default for AmbientLight { Self { color: Color::WHITE, brightness: 80.0, + affects_lightmapped_meshes: true, } } } @@ -39,5 +49,6 @@ impl AmbientLight { pub const NONE: AmbientLight = AmbientLight { color: Color::WHITE, brightness: 0.0, + affects_lightmapped_meshes: true, }; } diff --git a/crates/bevy_pbr/src/light/directional_light.rs b/crates/bevy_pbr/src/light/directional_light.rs index 06c277aef731c..f351ad7340cac 100644 --- a/crates/bevy_pbr/src/light/directional_light.rs +++ b/crates/bevy_pbr/src/light/directional_light.rs @@ -98,6 +98,18 @@ pub struct DirectionalLight { #[cfg(feature = "experimental_pbr_pcss")] pub soft_shadow_size: Option, + /// Whether this directional light contributes diffuse lighting to meshes + /// with lightmaps. + /// + /// Set this to false if your lightmap baking tool bakes the direct diffuse + /// light from this directional light into the lightmaps in order to avoid + /// counting the radiance from this light twice. Note that the specular + /// portion of the light is always considered, because Bevy currently has no + /// means to bake specular light. + /// + /// By default, this is set to true. + pub affects_lightmapped_mesh_diffuse: bool, + /// A value that adjusts the tradeoff between self-shadowing artifacts and /// proximity of shadows to their casters. /// @@ -123,6 +135,7 @@ impl Default for DirectionalLight { shadows_enabled: false, shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS, shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS, + affects_lightmapped_mesh_diffuse: true, #[cfg(feature = "experimental_pbr_pcss")] soft_shadow_size: None, } diff --git a/crates/bevy_pbr/src/light/point_light.rs b/crates/bevy_pbr/src/light/point_light.rs index ee08a57ba1e39..50097772bbe29 100644 --- a/crates/bevy_pbr/src/light/point_light.rs +++ b/crates/bevy_pbr/src/light/point_light.rs @@ -64,6 +64,18 @@ pub struct PointLight { #[cfg(feature = "experimental_pbr_pcss")] pub soft_shadows_enabled: bool, + /// Whether this point light contributes diffuse lighting to meshes with + /// lightmaps. + /// + /// Set this to false if your lightmap baking tool bakes the direct diffuse + /// light from this point light into the lightmaps in order to avoid + /// counting the radiance from this light twice. Note that the specular + /// portion of the light is always considered, because Bevy currently has no + /// means to bake specular light. + /// + /// By default, this is set to true. + pub affects_lightmapped_mesh_diffuse: bool, + /// A bias used when sampling shadow maps to avoid "shadow-acne", or false shadow occlusions /// that happen as a result of shadow-map fragments not mapping 1:1 to screen-space fragments. /// Too high of a depth bias can lead to shadows detaching from their casters, or @@ -96,6 +108,7 @@ impl Default for PointLight { range: 20.0, radius: 0.0, shadows_enabled: false, + affects_lightmapped_mesh_diffuse: true, shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS, shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS, shadow_map_near_z: Self::DEFAULT_SHADOW_MAP_NEAR_Z, diff --git a/crates/bevy_pbr/src/light/spot_light.rs b/crates/bevy_pbr/src/light/spot_light.rs index 845d55e697317..90be62c79e565 100644 --- a/crates/bevy_pbr/src/light/spot_light.rs +++ b/crates/bevy_pbr/src/light/spot_light.rs @@ -60,6 +60,18 @@ pub struct SpotLight { #[cfg(feature = "experimental_pbr_pcss")] pub soft_shadows_enabled: bool, + /// Whether this spot light contributes diffuse lighting to meshes with + /// lightmaps. + /// + /// Set this to false if your lightmap baking tool bakes the direct diffuse + /// light from this directional light into the lightmaps in order to avoid + /// counting the radiance from this light twice. Note that the specular + /// portion of the light is always considered, because Bevy currently has no + /// means to bake specular light. + /// + /// By default, this is set to true. + pub affects_lightmapped_mesh_diffuse: bool, + /// A value that adjusts the tradeoff between self-shadowing artifacts and /// proximity of shadows to their casters. /// @@ -116,6 +128,7 @@ impl Default for SpotLight { range: 20.0, radius: 0.0, shadows_enabled: false, + affects_lightmapped_mesh_diffuse: true, shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS, shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS, shadow_map_near_z: Self::DEFAULT_SHADOW_MAP_NEAR_Z, diff --git a/crates/bevy_pbr/src/light_probe/environment_map.rs b/crates/bevy_pbr/src/light_probe/environment_map.rs index 6b47c9915a836..4bbb7c76afb25 100644 --- a/crates/bevy_pbr/src/light_probe/environment_map.rs +++ b/crates/bevy_pbr/src/light_probe/environment_map.rs @@ -106,6 +106,16 @@ pub struct EnvironmentMapLight { /// This is useful for users who require a different axis, such as the Z-axis, to serve /// as the vertical axis. pub rotation: Quat, + + /// Whether the light from this environment map contributes diffuse lighting + /// to meshes with lightmaps. + /// + /// Set this to false if your lightmap baking tool bakes the diffuse light + /// from this environment light into the lightmaps in order to avoid + /// counting the radiance from this environment map twice. + /// + /// By default, this is set to true. + pub affects_lightmapped_mesh_diffuse: bool, } impl Default for EnvironmentMapLight { @@ -115,6 +125,7 @@ impl Default for EnvironmentMapLight { specular_map: Handle::default(), intensity: 0.0, rotation: Quat::IDENTITY, + affects_lightmapped_mesh_diffuse: true, } } } @@ -199,6 +210,9 @@ pub struct EnvironmentMapViewLightProbeInfo { /// The scale factor applied to the diffuse and specular light in the /// cubemap. This is in units of cd/m² (candela per square meter). pub(crate) intensity: f32, + /// Whether this lightmap affects the diffuse lighting of lightmapped + /// meshes. + pub(crate) affects_lightmapped_mesh_diffuse: bool, } impl ExtractInstance for EnvironmentMapIds { @@ -326,6 +340,10 @@ impl LightProbeComponent for EnvironmentMapLight { self.intensity } + fn affects_lightmapped_mesh_diffuse(&self) -> bool { + self.affects_lightmapped_mesh_diffuse + } + fn create_render_view_light_probes( view_component: Option<&EnvironmentMapLight>, image_assets: &RenderAssets, @@ -338,6 +356,7 @@ impl LightProbeComponent for EnvironmentMapLight { diffuse_map: diffuse_map_handle, specular_map: specular_map_handle, intensity, + affects_lightmapped_mesh_diffuse, .. }) = view_component { @@ -354,6 +373,7 @@ impl LightProbeComponent for EnvironmentMapLight { ) as i32, smallest_specular_mip_level: specular_map.mip_level_count - 1, intensity: *intensity, + affects_lightmapped_mesh_diffuse: *affects_lightmapped_mesh_diffuse, }; } }; @@ -368,6 +388,7 @@ impl Default for EnvironmentMapViewLightProbeInfo { cubemap_index: -1, smallest_specular_mip_level: 0, intensity: 1.0, + affects_lightmapped_mesh_diffuse: true, } } } diff --git a/crates/bevy_pbr/src/light_probe/environment_map.wgsl b/crates/bevy_pbr/src/light_probe/environment_map.wgsl index 1bfbdb2da0e9b..36d5ac25e4de3 100644 --- a/crates/bevy_pbr/src/light_probe/environment_map.wgsl +++ b/crates/bevy_pbr/src/light_probe/environment_map.wgsl @@ -49,6 +49,8 @@ fn compute_radiances( if (query_result.texture_index < 0) { query_result.texture_index = light_probes.view_cubemap_index; query_result.intensity = light_probes.intensity_for_view; + query_result.affects_lightmapped_mesh_diffuse = + light_probes.view_environment_map_affects_lightmapped_mesh_diffuse != 0u; } // If there's no cubemap, bail out. @@ -62,7 +64,14 @@ fn compute_radiances( let radiance_level = perceptual_roughness * f32(textureNumLevels( bindings::specular_environment_maps[query_result.texture_index]) - 1u); - if (!found_diffuse_indirect) { + // If we're lightmapped, and we shouldn't accumulate diffuse light from the + // environment map, note that. + var enable_diffuse = !found_diffuse_indirect; +#ifdef LIGHTMAP + enable_diffuse = enable_diffuse && query_result.affects_lightmapped_mesh_diffuse; +#endif // LIGHTMAP + + if (enable_diffuse) { var irradiance_sample_dir = N; // Rotating the world space ray direction by the environment light map transform matrix, it is // equivalent to rotating the diffuse environment cubemap itself. @@ -121,7 +130,15 @@ fn compute_radiances( let intensity = light_probes.intensity_for_view; - if (!found_diffuse_indirect) { + // If we're lightmapped, and we shouldn't accumulate diffuse light from the + // environment map, note that. + var enable_diffuse = !found_diffuse_indirect; +#ifdef LIGHTMAP + enable_diffuse = enable_diffuse && + light_probes.view_environment_map_affects_lightmapped_mesh_diffuse; +#endif // LIGHTMAP + + if (enable_diffuse) { var irradiance_sample_dir = N; // Rotating the world space ray direction by the environment light map transform matrix, it is // equivalent to rotating the diffuse environment cubemap itself. diff --git a/crates/bevy_pbr/src/light_probe/irradiance_volume.rs b/crates/bevy_pbr/src/light_probe/irradiance_volume.rs index bc8fc542fb32d..141e70e191b84 100644 --- a/crates/bevy_pbr/src/light_probe/irradiance_volume.rs +++ b/crates/bevy_pbr/src/light_probe/irradiance_volume.rs @@ -143,6 +143,7 @@ use bevy_render::{ renderer::RenderDevice, texture::{FallbackImage, GpuImage}, }; +use bevy_utils::default; use core::{num::NonZero, ops::Deref}; use bevy_asset::{AssetId, Handle}; @@ -166,7 +167,7 @@ pub(crate) const IRRADIANCE_VOLUMES_ARE_USABLE: bool = cfg!(not(target_arch = "w /// The component that defines an irradiance volume. /// /// See [`crate::irradiance_volume`] for detailed information. -#[derive(Clone, Default, Reflect, Component, Debug)] +#[derive(Clone, Reflect, Component, Debug)] #[reflect(Component, Default, Debug)] pub struct IrradianceVolume { /// The 3D texture that represents the ambient cubes, encoded in the format @@ -180,6 +181,30 @@ pub struct IrradianceVolume { /// /// See also . pub intensity: f32, + + /// Whether the light from this irradiance volume has an effect on meshes + /// with lightmaps. + /// + /// Set this to false if your lightmap baking tool bakes the light from this + /// irradiance volume into the lightmaps in order to avoid counting the + /// irradiance twice. Frequently, applications use irradiance volumes as a + /// lower-quality alternative to lightmaps for capturing indirect + /// illumination on dynamic objects, and such applications will want to set + /// this value to false. + /// + /// By default, this is set to true. + pub affects_lightmapped_meshes: bool, +} + +impl Default for IrradianceVolume { + #[inline] + fn default() -> Self { + IrradianceVolume { + voxels: default(), + intensity: 0.0, + affects_lightmapped_meshes: true, + } + } } /// All the bind group entries necessary for PBR shaders to access the @@ -336,6 +361,10 @@ impl LightProbeComponent for IrradianceVolume { self.intensity } + fn affects_lightmapped_mesh_diffuse(&self) -> bool { + self.affects_lightmapped_meshes + } + fn create_render_view_light_probes( _: Option<&Self>, _: &RenderAssets, diff --git a/crates/bevy_pbr/src/light_probe/irradiance_volume.wgsl b/crates/bevy_pbr/src/light_probe/irradiance_volume.wgsl index b4ba8b2e6127d..f079bd6a2e6e6 100644 --- a/crates/bevy_pbr/src/light_probe/irradiance_volume.wgsl +++ b/crates/bevy_pbr/src/light_probe/irradiance_volume.wgsl @@ -31,6 +31,14 @@ fn irradiance_volume_light( return vec3(0.0f); } + // If we're lightmapped, and the irradiance volume contributes no diffuse + // light, then bail out. +#ifdef LIGHTMAP + if (!query_result.affects_lightmapped_mesh_diffuse) { + return vec3(0.0f); + } +#endif // LIGHTMAP + #ifdef MULTIPLE_LIGHT_PROBES_IN_ARRAY let irradiance_volume_texture = irradiance_volumes[query_result.texture_index]; #else diff --git a/crates/bevy_pbr/src/light_probe/light_probe.wgsl b/crates/bevy_pbr/src/light_probe/light_probe.wgsl index ab80f0dd92366..f98759c293b9c 100644 --- a/crates/bevy_pbr/src/light_probe/light_probe.wgsl +++ b/crates/bevy_pbr/src/light_probe/light_probe.wgsl @@ -16,6 +16,8 @@ struct LightProbeQueryResult { // Transform from world space to the light probe model space. In light probe // model space, the light probe is a 1×1×1 cube centered on the origin. light_from_world: mat4x4, + // Whether this light probe contributes diffuse light to lightmapped meshes. + affects_lightmapped_mesh_diffuse: bool, }; fn transpose_affine_matrix(matrix: mat3x4) -> mat4x4 { @@ -80,6 +82,8 @@ fn query_light_probe( result.texture_index = light_probe.cubemap_index; result.intensity = light_probe.intensity; result.light_from_world = light_from_world; + result.affects_lightmapped_mesh_diffuse = + light_probe.affects_lightmapped_mesh_diffuse != 0u; break; } } @@ -132,6 +136,8 @@ fn query_light_probe( result.texture_index = light_probe.cubemap_index; result.intensity = light_probe.intensity; result.light_from_world = light_from_world; + result.affects_lightmapped_mesh_diffuse = + light_probe.affects_lightmapped_mesh_diffuse != 0u; // TODO: Workaround for ICE in DXC https://github.com/microsoft/DirectXShaderCompiler/issues/6183 // We can't use `break` here because of the ICE. diff --git a/crates/bevy_pbr/src/light_probe/mod.rs b/crates/bevy_pbr/src/light_probe/mod.rs index f5516e413bab0..b259a3e7927e2 100644 --- a/crates/bevy_pbr/src/light_probe/mod.rs +++ b/crates/bevy_pbr/src/light_probe/mod.rs @@ -125,6 +125,10 @@ struct RenderLightProbe { /// /// See the comment in [`EnvironmentMapLight`] for details. intensity: f32, + + /// Whether this light probe adds to the diffuse contribution of the + /// irradiance for meshes with lightmaps. + affects_lightmapped_mesh_diffuse: u32, } /// A per-view shader uniform that specifies all the light probes that the view @@ -158,6 +162,12 @@ pub struct LightProbesUniform { /// /// See the comment in [`EnvironmentMapLight`] for details. intensity_for_view: f32, + + /// Whether the environment map attached to the view affects the diffuse + /// lighting for lightmapped meshes. + /// + /// This will be 1 if the map does affect lightmapped meshes or 0 otherwise. + view_environment_map_affects_lightmapped_mesh_diffuse: u32, } /// A GPU buffer that stores information about all light probes. @@ -191,6 +201,10 @@ where // See the comment in [`EnvironmentMapLight`] for details. intensity: f32, + // Whether this light probe adds to the diffuse contribution of the + // irradiance for meshes with lightmaps. + affects_lightmapped_mesh_diffuse: bool, + // The IDs of all assets associated with this light probe. // // Because each type of light probe component may reference different types @@ -279,6 +293,10 @@ pub trait LightProbeComponent: Send + Sync + Component + Sized { /// sampled from the texture. fn intensity(&self) -> f32; + /// Returns true if this light probe contributes diffuse lighting to meshes + /// with lightmaps or false otherwise. + fn affects_lightmapped_mesh_diffuse(&self) -> bool; + /// Creates an instance of [`RenderViewLightProbes`] containing all the /// information needed to render this light probe. /// @@ -537,6 +555,9 @@ fn upload_light_probes( intensity_for_view: render_view_environment_maps .map(|maps| maps.view_light_probe_info.intensity) .unwrap_or(1.0), + view_environment_map_affects_lightmapped_mesh_diffuse: render_view_environment_maps + .map(|maps| maps.view_light_probe_info.affects_lightmapped_mesh_diffuse as u32) + .unwrap_or(1), }; // Add any environment maps that [`gather_light_probes`] found to the @@ -576,6 +597,7 @@ impl Default for LightProbesUniform { view_cubemap_index: -1, smallest_specular_mip_level_for_view: 0, intensity_for_view: 1.0, + view_environment_map_affects_lightmapped_mesh_diffuse: 1, } } } @@ -596,6 +618,7 @@ where light_from_world: light_probe_transform.compute_matrix().inverse(), asset_id: id, intensity: environment_map.intensity(), + affects_lightmapped_mesh_diffuse: environment_map.affects_lightmapped_mesh_diffuse(), }) } @@ -693,6 +716,8 @@ where ], texture_index: cubemap_index as i32, intensity: light_probe.intensity, + affects_lightmapped_mesh_diffuse: light_probe.affects_lightmapped_mesh_diffuse + as u32, }); } } @@ -707,6 +732,7 @@ where light_from_world: self.light_from_world, world_from_light: self.world_from_light, intensity: self.intensity, + affects_lightmapped_mesh_diffuse: self.affects_lightmapped_mesh_diffuse, asset_id: self.asset_id.clone(), } } diff --git a/crates/bevy_pbr/src/render/light.rs b/crates/bevy_pbr/src/render/light.rs index 3705424adfb8b..50e126da73a12 100644 --- a/crates/bevy_pbr/src/render/light.rs +++ b/crates/bevy_pbr/src/render/light.rs @@ -59,6 +59,8 @@ pub struct ExtractedPointLight { pub spot_light_angles: Option<(f32, f32)>, pub volumetric: bool, pub soft_shadows_enabled: bool, + /// whether this point light contributes diffuse light to lightmapped meshes + pub affects_lightmapped_mesh_diffuse: bool, } #[derive(Component, Debug)] @@ -68,6 +70,9 @@ pub struct ExtractedDirectionalLight { pub transform: GlobalTransform, pub shadows_enabled: bool, pub volumetric: bool, + /// whether this directional light contributes diffuse light to lightmapped + /// meshes + pub affects_lightmapped_mesh_diffuse: bool, pub shadow_depth_bias: f32, pub shadow_normal_bias: f32, pub cascade_shadow_config: CascadeShadowConfig, @@ -81,11 +86,12 @@ pub struct ExtractedDirectionalLight { bitflags::bitflags! { #[repr(transparent)] struct PointLightFlags: u32 { - const SHADOWS_ENABLED = 1 << 0; - const SPOT_LIGHT_Y_NEGATIVE = 1 << 1; - const VOLUMETRIC = 1 << 2; - const NONE = 0; - const UNINITIALIZED = 0xFFFF; + const SHADOWS_ENABLED = 1 << 0; + const SPOT_LIGHT_Y_NEGATIVE = 1 << 1; + const VOLUMETRIC = 1 << 2; + const AFFECTS_LIGHTMAPPED_MESH_DIFFUSE = 1 << 3; + const NONE = 0; + const UNINITIALIZED = 0xFFFF; } } @@ -115,10 +121,11 @@ pub struct GpuDirectionalLight { bitflags::bitflags! { #[repr(transparent)] struct DirectionalLightFlags: u32 { - const SHADOWS_ENABLED = 1 << 0; - const VOLUMETRIC = 1 << 1; - const NONE = 0; - const UNINITIALIZED = 0xFFFF; + const SHADOWS_ENABLED = 1 << 0; + const VOLUMETRIC = 1 << 1; + const AFFECTS_LIGHTMAPPED_MESH_DIFFUSE = 1 << 2; + const NONE = 0; + const UNINITIALIZED = 0xFFFF; } } @@ -135,6 +142,7 @@ pub struct GpuLights { n_directional_lights: u32, // offset from spot light's light index to spot light's shadow map index spot_light_shadowmap_offset: i32, + ambient_light_affects_lightmapped_meshes: u32, } // NOTE: When running bevy on Adreno GPU chipsets in WebGL, any value above 1 will result in a crash @@ -311,6 +319,7 @@ pub fn extract_lights( shadow_map_near_z: point_light.shadow_map_near_z, spot_light_angles: None, volumetric: volumetric_light.is_some(), + affects_lightmapped_mesh_diffuse: point_light.affects_lightmapped_mesh_diffuse, #[cfg(feature = "experimental_pbr_pcss")] soft_shadows_enabled: point_light.soft_shadows_enabled, #[cfg(not(feature = "experimental_pbr_pcss"))] @@ -373,6 +382,8 @@ pub fn extract_lights( shadow_map_near_z: spot_light.shadow_map_near_z, spot_light_angles: Some((spot_light.inner_angle, spot_light.outer_angle)), volumetric: volumetric_light.is_some(), + affects_lightmapped_mesh_diffuse: spot_light + .affects_lightmapped_mesh_diffuse, #[cfg(feature = "experimental_pbr_pcss")] soft_shadows_enabled: spot_light.soft_shadows_enabled, #[cfg(not(feature = "experimental_pbr_pcss"))] @@ -448,6 +459,8 @@ pub fn extract_lights( illuminance: directional_light.illuminance, transform: *transform, volumetric: volumetric_light.is_some(), + affects_lightmapped_mesh_diffuse: directional_light + .affects_lightmapped_mesh_diffuse, #[cfg(feature = "experimental_pbr_pcss")] soft_shadow_size: directional_light.soft_shadow_size, #[cfg(not(feature = "experimental_pbr_pcss"))] @@ -885,6 +898,10 @@ pub fn prepare_lights( flags |= PointLightFlags::VOLUMETRIC; } + if light.affects_lightmapped_mesh_diffuse { + flags |= PointLightFlags::AFFECTS_LIGHTMAPPED_MESH_DIFFUSE; + } + let (light_custom_data, spot_light_tan_angle) = match light.spot_light_angles { Some((inner, outer)) => { let light_direction = light.transform.forward(); @@ -963,6 +980,10 @@ pub fn prepare_lights( flags |= DirectionalLightFlags::SHADOWS_ENABLED; } + if light.affects_lightmapped_mesh_diffuse { + flags |= DirectionalLightFlags::AFFECTS_LIGHTMAPPED_MESH_DIFFUSE; + } + let num_cascades = light .cascade_shadow_config .bounds @@ -1137,6 +1158,8 @@ pub fn prepare_lights( // index to shadow map index, we need to subtract point light count and add directional shadowmap count. spot_light_shadowmap_offset: num_directional_cascades_enabled as i32 - point_light_count as i32, + ambient_light_affects_lightmapped_meshes: ambient_light.affects_lightmapped_meshes + as u32, }; // TODO: this should select lights based on relevance to the view instead of the first ones that show up in a query diff --git a/crates/bevy_pbr/src/render/mesh_view_types.wgsl b/crates/bevy_pbr/src/render/mesh_view_types.wgsl index a3648340f3bd0..ee3b2475e35e9 100644 --- a/crates/bevy_pbr/src/render/mesh_view_types.wgsl +++ b/crates/bevy_pbr/src/render/mesh_view_types.wgsl @@ -17,9 +17,10 @@ struct ClusterableObject { pad_b: f32, }; -const POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT: u32 = 1u; -const POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE: u32 = 2u; -const POINT_LIGHT_FLAGS_VOLUMETRIC_BIT: u32 = 4u; +const POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT: u32 = 1u; +const POINT_LIGHT_FLAGS_SPOT_LIGHT_Y_NEGATIVE: u32 = 2u; +const POINT_LIGHT_FLAGS_VOLUMETRIC_BIT: u32 = 4u; +const POINT_LIGHT_FLAGS_AFFECTS_LIGHTMAPPED_MESH_DIFFUSE_BIT: u32 = 8u; struct DirectionalCascade { clip_from_world: mat4x4, @@ -42,8 +43,9 @@ struct DirectionalLight { skip: u32, }; -const DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT: u32 = 1u; -const DIRECTIONAL_LIGHT_FLAGS_VOLUMETRIC_BIT: u32 = 2u; +const DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT: u32 = 1u; +const DIRECTIONAL_LIGHT_FLAGS_VOLUMETRIC_BIT: u32 = 2u; +const DIRECTIONAL_LIGHT_FLAGS_AFFECTS_LIGHTMAPPED_MESH_DIFFUSE_BIT: u32 = 4u; struct Lights { // NOTE: this array size must be kept in sync with the constants defined in bevy_pbr/src/render/light.rs @@ -124,6 +126,8 @@ struct LightProbe { light_from_world_transposed: mat3x4, cubemap_index: i32, intensity: f32, + // Whether this light probe contributes diffuse light to lightmapped meshes. + affects_lightmapped_mesh_diffuse: u32, }; struct LightProbes { @@ -140,6 +144,9 @@ struct LightProbes { smallest_specular_mip_level_for_view: u32, // The intensity of the environment map associated with the view. intensity_for_view: f32, + // Whether the environment map attached to the view affects the diffuse + // lighting for lightmapped meshes. + view_environment_map_affects_lightmapped_mesh_diffuse: u32, }; // Settings for screen space reflections. diff --git a/crates/bevy_pbr/src/render/pbr_functions.wgsl b/crates/bevy_pbr/src/render/pbr_functions.wgsl index 1983257199577..b6187bc4b2b4d 100644 --- a/crates/bevy_pbr/src/render/pbr_functions.wgsl +++ b/crates/bevy_pbr/src/render/pbr_functions.wgsl @@ -406,13 +406,24 @@ fn apply_pbr_lighting( i < clusterable_object_index_ranges.first_spot_light_index_offset; i = i + 1u) { let light_id = clustering::get_clusterable_object_id(i); + + // If we're lightmapped, disable diffuse contribution from the light if + // requested, to avoid double-counting light. +#ifdef LIGHTMAP + let enable_diffuse = + (view_bindings::clusterable_objects.data[light_id].flags & + mesh_view_types::POINT_LIGHT_FLAGS_AFFECTS_LIGHTMAPPED_MESH_DIFFUSE_BIT) != 0u; +#else // LIGHTMAP + let enable_diffuse = true; +#endif // LIGHTMAP + var shadow: f32 = 1.0; if ((in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u && (view_bindings::clusterable_objects.data[light_id].flags & mesh_view_types::POINT_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { shadow = shadows::fetch_point_shadow(light_id, in.world_position, in.world_normal); } - let light_contrib = lighting::point_light(light_id, &lighting_input); + let light_contrib = lighting::point_light(light_id, &lighting_input, enable_diffuse); direct_light += light_contrib * shadow; #ifdef STANDARD_MATERIAL_DIFFUSE_TRANSMISSION @@ -443,6 +454,16 @@ fn apply_pbr_lighting( i = i + 1u) { let light_id = clustering::get_clusterable_object_id(i); + // If we're lightmapped, disable diffuse contribution from the light if + // requested, to avoid double-counting light. +#ifdef LIGHTMAP + let enable_diffuse = + (view_bindings::clusterable_objects.data[light_id].flags & + mesh_view_types::POINT_LIGHT_FLAGS_AFFECTS_LIGHTMAPPED_MESH_DIFFUSE_BIT) != 0u; +#else // LIGHTMAP + let enable_diffuse = true; +#endif // LIGHTMAP + var shadow: f32 = 1.0; if ((in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u && (view_bindings::clusterable_objects.data[light_id].flags & @@ -455,7 +476,7 @@ fn apply_pbr_lighting( ); } - let light_contrib = lighting::spot_light(light_id, &lighting_input); + let light_contrib = lighting::spot_light(light_id, &lighting_input, enable_diffuse); direct_light += light_contrib * shadow; #ifdef STANDARD_MATERIAL_DIFFUSE_TRANSMISSION @@ -495,13 +516,24 @@ fn apply_pbr_lighting( continue; } + // If we're lightmapped, disable diffuse contribution from the light if + // requested, to avoid double-counting light. +#ifdef LIGHTMAP + let enable_diffuse = + ((*light).flags & + mesh_view_types::DIRECTIONAL_LIGHT_FLAGS_AFFECTS_LIGHTMAPPED_MESH_DIFFUSE_BIT) != + 0u; +#else // LIGHTMAP + let enable_diffuse = true; +#endif // LIGHTMAP + var shadow: f32 = 1.0; if ((in.flags & MESH_FLAGS_SHADOW_RECEIVER_BIT) != 0u && (view_bindings::lights.directional_lights[i].flags & mesh_view_types::DIRECTIONAL_LIGHT_FLAGS_SHADOWS_ENABLED_BIT) != 0u) { shadow = shadows::fetch_directional_shadow(i, in.world_position, in.world_normal, view_z); } - var light_contrib = lighting::directional_light(i, &lighting_input); + var light_contrib = lighting::directional_light(i, &lighting_input, enable_diffuse); #ifdef DIRECTIONAL_LIGHT_SHADOW_MAP_DEBUG_CASCADES light_contrib = shadows::cascade_debug_visualization(light_contrib, i, view_z); diff --git a/crates/bevy_pbr/src/render/pbr_lighting.wgsl b/crates/bevy_pbr/src/render/pbr_lighting.wgsl index 0e88642333643..4497b567e9ff8 100644 --- a/crates/bevy_pbr/src/render/pbr_lighting.wgsl +++ b/crates/bevy_pbr/src/render/pbr_lighting.wgsl @@ -440,7 +440,11 @@ fn perceptualRoughnessToRoughness(perceptualRoughness: f32) -> f32 { return clampedPerceptualRoughness * clampedPerceptualRoughness; } -fn point_light(light_id: u32, input: ptr) -> vec3 { +fn point_light( + light_id: u32, + input: ptr, + enable_diffuse: bool +) -> vec3 { // Unpack. let diffuse_color = (*input).diffuse_color; let P = (*input).P; @@ -507,7 +511,10 @@ fn point_light(light_id: u32, input: ptr) -> vec3 // Diffuse. // Comes after specular since its N⋅L is used in the lighting equation. var derived_input = derive_lighting_input(N, V, L); - let diffuse = diffuse_color * Fd_Burley(input, &derived_input); + var diffuse = vec3(0.0); + if (enable_diffuse) { + diffuse = diffuse_color * Fd_Burley(input, &derived_input); + } // See https://google.github.io/filament/Filament.html#mjx-eqn-pointLightLuminanceEquation // Lout = f(v,l) Φ / { 4 π d^2 }⟨n⋅l⟩ @@ -536,9 +543,13 @@ fn point_light(light_id: u32, input: ptr) -> vec3 (rangeAttenuation * derived_input.NdotL); } -fn spot_light(light_id: u32, input: ptr) -> vec3 { +fn spot_light( + light_id: u32, + input: ptr, + enable_diffuse: bool +) -> vec3 { // reuse the point light calculations - let point_light = point_light(light_id, input); + let point_light = point_light(light_id, input, enable_diffuse); let light = &view_bindings::clusterable_objects.data[light_id]; @@ -560,7 +571,11 @@ fn spot_light(light_id: u32, input: ptr) -> vec3 { return point_light * spot_attenuation; } -fn directional_light(light_id: u32, input: ptr) -> vec3 { +fn directional_light( + light_id: u32, + input: ptr, + enable_diffuse: bool +) -> vec3 { // Unpack. let diffuse_color = (*input).diffuse_color; let NdotV = (*input).layers[LAYER_BASE].NdotV; @@ -573,7 +588,10 @@ fn directional_light(light_id: u32, input: ptr) -> vec3 let L = (*light).direction_to_light.xyz; var derived_input = derive_lighting_input(N, V, L); - let diffuse = diffuse_color * Fd_Burley(input, &derived_input); + var diffuse = vec3(0.0); + if (enable_diffuse) { + diffuse = diffuse_color * Fd_Burley(input, &derived_input); + } #ifdef STANDARD_MATERIAL_ANISOTROPY let specular_light = specular_anisotropy(input, &derived_input, L, 1.0); diff --git a/examples/2d/custom_gltf_vertex_attribute.rs b/examples/2d/custom_gltf_vertex_attribute.rs index 6cb10bdb267e9..b9d7b8141d4d7 100644 --- a/examples/2d/custom_gltf_vertex_attribute.rs +++ b/examples/2d/custom_gltf_vertex_attribute.rs @@ -27,6 +27,7 @@ fn main() { .insert_resource(AmbientLight { color: Color::WHITE, brightness: 1.0 / 5.0f32, + ..default() }) .add_plugins(( DefaultPlugins.set( diff --git a/examples/3d/auto_exposure.rs b/examples/3d/auto_exposure.rs index e18cec24f2aca..79fece61c8e36 100644 --- a/examples/3d/auto_exposure.rs +++ b/examples/3d/auto_exposure.rs @@ -104,6 +104,7 @@ fn setup( commands.insert_resource(AmbientLight { color: Color::WHITE, brightness: 0.0, + ..default() }); commands.spawn(( diff --git a/examples/3d/irradiance_volumes.rs b/examples/3d/irradiance_volumes.rs index 39e097f895b3c..31529c421986d 100644 --- a/examples/3d/irradiance_volumes.rs +++ b/examples/3d/irradiance_volumes.rs @@ -160,6 +160,7 @@ fn main() { .insert_resource(AmbientLight { color: Color::WHITE, brightness: 0.0, + ..default() }) .add_systems(Startup, setup) .add_systems(PreUpdate, create_cubes) @@ -248,6 +249,7 @@ fn spawn_irradiance_volume(commands: &mut Commands, assets: &ExampleAssets) { IrradianceVolume { voxels: assets.irradiance_volume.clone(), intensity: IRRADIANCE_VOLUME_INTENSITY, + ..default() }, LightProbe, )); @@ -431,6 +433,7 @@ fn toggle_irradiance_volumes( commands.entity(light_probe).insert(IrradianceVolume { voxels: assets.irradiance_volume.clone(), intensity: IRRADIANCE_VOLUME_INTENSITY, + ..default() }); ambient_light.brightness = 0.0; app_status.irradiance_volume_present = true; diff --git a/examples/3d/lighting.rs b/examples/3d/lighting.rs index d256897c8bfa3..ea58396fa71f3 100644 --- a/examples/3d/lighting.rs +++ b/examples/3d/lighting.rs @@ -114,6 +114,7 @@ fn setup( commands.insert_resource(AmbientLight { color: ORANGE_RED.into(), brightness: 0.02, + ..default() }); // red point light diff --git a/examples/3d/mixed_lighting.rs b/examples/3d/mixed_lighting.rs new file mode 100644 index 0000000000000..88b2459f8e61c --- /dev/null +++ b/examples/3d/mixed_lighting.rs @@ -0,0 +1,523 @@ +//! Demonstrates how to combine baked and dynamic lighting. + +use bevy::{ + pbr::Lightmap, + picking::{backend::HitData, pointer::PointerInteraction}, + prelude::*, + scene::SceneInstanceReady, +}; + +use crate::widgets::{RadioButton, RadioButtonText, WidgetClickEvent, WidgetClickSender}; + +#[path = "../helpers/widgets.rs"] +mod widgets; + +/// How bright the lightmaps are. +const LIGHTMAP_EXPOSURE: f32 = 600.0; + +/// How far above the ground the sphere's origin is when moved, in scene units. +const SPHERE_OFFSET: f32 = 0.2; + +/// The settings that the user has currently chosen for the app. +#[derive(Clone, Default, Resource)] +struct AppStatus { + /// The lighting mode that the user currently has set: baked, mixed, or + /// real-time. + lighting_mode: LightingMode, +} + +/// The type of lighting to use in the scene. +#[derive(Clone, Copy, PartialEq, Default)] +enum LightingMode { + /// All light is computed ahead of time; no lighting takes place at runtime. + /// + /// In this mode, the sphere can't be moved, as the light shining on it was + /// precomputed. On the plus side, the sphere has indirect lighting in this + /// mode, as the red hue on the bottom of the sphere demonstrates. + Baked, + + /// All light for the static objects is computed ahead of time, but the + /// light for the dynamic sphere is computed at runtime. + /// + /// In this mode, the sphere can be moved, and the light will be computed + /// for it as you do so. The sphere loses indirect illumination; notice the + /// lack of a red hue at the base of the sphere. However, the rest of the + /// scene has indirect illumination. Note also that the sphere doesn't cast + /// a shadow on the static objects in this mode, because shadows are part of + /// the lighting computation. + MixedDirect, + + /// Indirect light for the static objects is computed ahead of time, and + /// direct light for all objects is computed at runtime. + /// + /// In this mode, the sphere can be moved, and the light will be computed + /// for it as you do so. The sphere loses indirect illumination; notice the + /// lack of a red hue at the base of the sphere. However, the rest of the + /// scene has indirect illumination. The sphere does cast a shadow on + /// objects in this mode, because the direct light for all objects is being + /// computed dynamically. + #[default] + MixedIndirect, + + /// Light is computed at runtime for all objects. + /// + /// In this mode, no lightmaps are used at all. All objects are dynamically + /// lit, which provides maximum flexibility. However, the downside is that + /// global illumination is lost; note that the base of the sphere isn't red + /// as it is in baked mode. + RealTime, +} + +/// An event that's fired whenever the user changes the lighting mode. +/// +/// This is also fired when the scene loads for the first time. +#[derive(Clone, Copy, Default, Event)] +struct LightingModeChanged; + +#[derive(Clone, Copy, Component, Debug)] +struct HelpText; + +/// The name of every static object in the scene that has a lightmap, as well as +/// the UV rect of its lightmap. +/// +/// Storing this as an array and doing a linear search through it is rather +/// inefficient, but we do it anyway for clarity's sake. +static LIGHTMAPS: [(&str, Rect); 5] = [ + ( + "Plane", + uv_rect_opengl(Vec2::splat(0.026), Vec2::splat(0.710)), + ), + ( + "SheenChair_fabric", + uv_rect_opengl(vec2(0.7864, 0.02377), vec2(0.1910, 0.1912)), + ), + ( + "SheenChair_label", + uv_rect_opengl(vec2(0.275, -0.016), vec2(0.858, 0.486)), + ), + ( + "SheenChair_metal", + uv_rect_opengl(vec2(0.998, 0.506), vec2(-0.029, -0.067)), + ), + ( + "SheenChair_wood", + uv_rect_opengl(vec2(0.787, 0.257), vec2(0.179, 0.177)), + ), +]; + +static SPHERE_UV_RECT: Rect = uv_rect_opengl(vec2(0.788, 0.484), Vec2::splat(0.062)); + +/// The initial position of the sphere. +/// +/// When the user sets the light mode to [`LightingMode::Baked`], we reset the +/// position to this point. +const INITIAL_SPHERE_POSITION: Vec3 = vec3(0.0, 0.5233223, 0.0); + +fn main() { + App::new() + .add_plugins(DefaultPlugins.set(WindowPlugin { + primary_window: Some(Window { + title: "Bevy Mixed Lighting Example".into(), + ..default() + }), + ..default() + })) + .add_plugins(MeshPickingPlugin) + .insert_resource(AmbientLight { + color: ClearColor::default().0, + brightness: 10000.0, + affects_lightmapped_meshes: true, + }) + .init_resource::() + .add_event::>() + .add_event::() + .add_systems(Startup, setup) + .add_systems(Update, update_lightmaps) + .add_systems(Update, update_directional_light) + .add_systems(Update, make_sphere_nonpickable) + .add_systems(Update, update_radio_buttons) + .add_systems(Update, handle_lighting_mode_change) + .add_systems(Update, widgets::handle_ui_interactions::) + .add_systems(Update, reset_sphere_position) + .add_systems(Update, move_sphere) + .add_systems(Update, adjust_help_text) + .run(); +} + +/// Creates the scene. +fn setup(mut commands: Commands, asset_server: Res, app_status: Res) { + spawn_camera(&mut commands); + spawn_scene(&mut commands, &asset_server); + spawn_buttons(&mut commands); + spawn_help_text(&mut commands, &app_status); +} + +/// Spawns the 3D camera. +fn spawn_camera(commands: &mut Commands) { + commands + .spawn(Camera3d::default()) + .insert(Transform::from_xyz(-0.7, 0.7, 1.0).looking_at(vec3(0.0, 0.3, 0.0), Vec3::Y)); +} + +/// Spawns the scene. +/// +/// The scene is loaded from a glTF file. +fn spawn_scene(commands: &mut Commands, asset_server: &AssetServer) { + commands + .spawn(SceneRoot( + asset_server.load( + GltfAssetLabel::Scene(0) + .from_asset("models/MixedLightingExample/MixedLightingExample.gltf"), + ), + )) + .observe( + |_: Trigger, + mut lighting_mode_change_event_writer: EventWriter| { + // When the scene loads, send a `LightingModeChanged` event so + // that we set up the lightmaps. + lighting_mode_change_event_writer.send(LightingModeChanged); + }, + ); +} + +/// Spawns the buttons that allow the user to change the lighting mode. +fn spawn_buttons(commands: &mut Commands) { + commands + .spawn(widgets::main_ui_node()) + .with_children(|parent| { + widgets::spawn_option_buttons( + parent, + "Lighting", + &[ + (LightingMode::Baked, "Baked"), + (LightingMode::MixedDirect, "Mixed (Direct)"), + (LightingMode::MixedIndirect, "Mixed (Indirect)"), + (LightingMode::RealTime, "Real-Time"), + ], + ); + }); +} + +/// Spawns the help text at the top of the window. +fn spawn_help_text(commands: &mut Commands, app_status: &AppStatus) { + commands.spawn(( + create_help_text(app_status), + Node { + position_type: PositionType::Absolute, + top: Val::Px(12.0), + left: Val::Px(12.0), + ..default() + }, + HelpText, + )); +} + +/// Adds lightmaps to and/or removes lightmaps from objects in the scene when +/// the lighting mode changes. +/// +/// This is also called right after the scene loads in order to set up the +/// lightmaps. +fn update_lightmaps( + mut commands: Commands, + asset_server: Res, + mut materials: ResMut>, + meshes: Query<(Entity, &Name, &MeshMaterial3d), With>, + mut lighting_mode_change_event_reader: EventReader, + app_status: Res, +) { + // Only run if the lighting mode changed. (Note that a change event is fired + // when the scene first loads.) + if lighting_mode_change_event_reader.read().next().is_none() { + return; + } + + // Select the lightmap to use, based on the lighting mode. + let lightmap: Option> = match app_status.lighting_mode { + LightingMode::Baked => { + Some(asset_server.load("lightmaps/MixedLightingExample-Baked.zstd.ktx2")) + } + LightingMode::MixedDirect => { + Some(asset_server.load("lightmaps/MixedLightingExample-MixedDirect.zstd.ktx2")) + } + LightingMode::MixedIndirect => { + Some(asset_server.load("lightmaps/MixedLightingExample-MixedIndirect.zstd.ktx2")) + } + LightingMode::RealTime => None, + }; + + 'outer: for (entity, name, material) in &meshes { + // Add lightmaps to or remove lightmaps from the scenery objects in the + // scene (all objects but the sphere). + // + // Note that doing a linear search through the `LIGHTMAPS` array is + // inefficient, but we do it anyway in this example to improve clarity. + for (lightmap_name, uv_rect) in LIGHTMAPS { + if &**name != lightmap_name { + continue; + } + + // Lightmap exposure defaults to zero, so we need to set it. + if let Some(ref mut material) = materials.get_mut(material) { + material.lightmap_exposure = LIGHTMAP_EXPOSURE; + } + + // Add or remove the lightmap. + match lightmap { + Some(ref lightmap) => { + commands.entity(entity).insert(Lightmap { + image: (*lightmap).clone(), + uv_rect, + }); + } + None => { + commands.entity(entity).remove::(); + } + } + continue 'outer; + } + + // Add lightmaps to or remove lightmaps from the sphere. + if &**name == "Sphere" { + // Lightmap exposure defaults to zero, so we need to set it. + if let Some(ref mut material) = materials.get_mut(material) { + material.lightmap_exposure = LIGHTMAP_EXPOSURE; + } + + // Add or remove the lightmap from the sphere. We only apply the + // lightmap in fully-baked mode. + match (&lightmap, app_status.lighting_mode) { + (Some(lightmap), LightingMode::Baked) => { + commands.entity(entity).insert(Lightmap { + image: (*lightmap).clone(), + uv_rect: SPHERE_UV_RECT, + }); + } + _ => { + commands.entity(entity).remove::(); + } + } + } + } +} + +/// Converts a uv rectangle from the OpenGL coordinate system (origin in the +/// lower left) to the Vulkan coordinate system (origin in the upper left) that +/// Bevy uses. +/// +/// For this particular example, the baking tool happened to use the OpenGL +/// coordinate system, so it was more convenient to do the conversion at compile +/// time than to pre-calculate and hard-code the values. +const fn uv_rect_opengl(gl_min: Vec2, size: Vec2) -> Rect { + let min = vec2(gl_min.x, 1.0 - gl_min.y - size.y); + Rect { + min, + max: vec2(min.x + size.x, min.y + size.y), + } +} + +/// Ensures that clicking on the scene to move the sphere doesn't result in a +/// hit on the sphere itself. +fn make_sphere_nonpickable( + mut commands: Commands, + mut query: Query<(Entity, &Name), (With, Without)>, +) { + for (sphere, name) in &mut query { + if &**name == "Sphere" { + commands.entity(sphere).insert(PickingBehavior::IGNORE); + } + } +} + +/// Updates the directional light settings as necessary when the lighting mode +/// changes. +fn update_directional_light( + mut lights: Query<&mut DirectionalLight>, + mut lighting_mode_change_event_reader: EventReader, + app_status: Res, +) { + // Only run if the lighting mode changed. (Note that a change event is fired + // when the scene first loads.) + if lighting_mode_change_event_reader.read().next().is_none() { + return; + } + + // Real-time direct light is used on the scenery if we're using mixed + // indirect or real-time mode. + let scenery_is_lit_in_real_time = matches!( + app_status.lighting_mode, + LightingMode::MixedIndirect | LightingMode::RealTime + ); + + for mut light in &mut lights { + light.affects_lightmapped_mesh_diffuse = scenery_is_lit_in_real_time; + // Don't bother enabling shadows if they won't show up on the scenery. + light.shadows_enabled = scenery_is_lit_in_real_time; + } +} + +/// Updates the state of the selection widgets at the bottom of the window when +/// the lighting mode changes. +fn update_radio_buttons( + mut widgets: Query< + ( + Entity, + Option<&mut BackgroundColor>, + Has, + &WidgetClickSender, + ), + Or<(With, With)>, + >, + app_status: Res, + mut writer: TextUiWriter, +) { + for (entity, image, has_text, sender) in &mut widgets { + let selected = **sender == app_status.lighting_mode; + + if let Some(mut bg_color) = image { + widgets::update_ui_radio_button(&mut bg_color, selected); + } + if has_text { + widgets::update_ui_radio_button_text(entity, &mut writer, selected); + } + } +} + +/// Handles clicks on the widgets at the bottom of the screen and fires +/// [`LightingModeChanged`] events. +fn handle_lighting_mode_change( + mut widget_click_event_reader: EventReader>, + mut lighting_mode_change_event_writer: EventWriter, + mut app_status: ResMut, +) { + for event in widget_click_event_reader.read() { + app_status.lighting_mode = **event; + lighting_mode_change_event_writer.send(LightingModeChanged); + } +} + +/// Moves the sphere to its original position when the user selects the baked +/// lighting mode. +/// +/// As the light from the sphere is precomputed and depends on the sphere's +/// original position, the sphere must be placed there in order for the lighting +/// to be correct. +fn reset_sphere_position( + mut objects: Query<(&Name, &mut Transform)>, + mut lighting_mode_change_event_reader: EventReader, + app_status: Res, +) { + // Only run if the lighting mode changed and if the lighting mode is + // `LightingMode::Baked`. (Note that a change event is fired when the scene + // first loads.) + if lighting_mode_change_event_reader.read().next().is_none() + || app_status.lighting_mode != LightingMode::Baked + { + return; + } + + for (name, mut transform) in &mut objects { + if &**name == "Sphere" { + transform.translation = INITIAL_SPHERE_POSITION; + break; + } + } +} + +/// Updates the position of the sphere when the user clicks on a spot in the +/// scene. +/// +/// Note that the position of the sphere is locked in baked lighting mode. +fn move_sphere( + mouse_button_input: Res>, + pointers: Query<&PointerInteraction>, + mut meshes: Query<(&Name, &Parent), With>, + mut transforms: Query<&mut Transform>, + app_status: Res, +) { + // Only run when the left button is clicked and we're not in baked lighting + // mode. + if app_status.lighting_mode == LightingMode::Baked + || !mouse_button_input.pressed(MouseButton::Left) + { + return; + } + + // Find the sphere. + let Some(parent) = meshes + .iter_mut() + .filter_map(|(name, parent)| { + if &**name == "Sphere" { + Some(parent) + } else { + None + } + }) + .next() + else { + return; + }; + + // Grab its transform. + let Ok(mut transform) = transforms.get_mut(**parent) else { + return; + }; + + // Set its transform to the appropriate position, as determined by the + // picking subsystem. + for interaction in pointers.iter() { + if let Some(&( + _, + HitData { + position: Some(position), + .. + }, + )) = interaction.get_nearest_hit() + { + transform.translation = position + vec3(0.0, SPHERE_OFFSET, 0.0); + } + } +} + +/// Changes the help text at the top of the screen when the lighting mode +/// changes. +fn adjust_help_text( + mut commands: Commands, + help_texts: Query>, + app_status: Res, + mut lighting_mode_change_event_reader: EventReader, +) { + if lighting_mode_change_event_reader.read().next().is_none() { + return; + } + + for help_text in &help_texts { + commands + .entity(help_text) + .insert(create_help_text(&app_status)); + } +} + +/// Returns appropriate text to display at the top of the screen. +fn create_help_text(app_status: &AppStatus) -> Text { + match app_status.lighting_mode { + LightingMode::Baked => Text::new( + "Scenery: Static, baked direct light, baked indirect light +Sphere: Static, baked direct light, baked indirect light", + ), + LightingMode::MixedDirect => Text::new( + "Scenery: Static, baked direct light, baked indirect light +Sphere: Dynamic, real-time direct light, no indirect light +Click in the scene to move the sphere", + ), + LightingMode::MixedIndirect => Text::new( + "Scenery: Static, real-time direct light, baked indirect light +Sphere: Dynamic, real-time direct light, no indirect light +Click in the scene to move the sphere", + ), + LightingMode::RealTime => Text::new( + "Scenery: Dynamic, real-time direct light, no indirect light +Sphere: Dynamic, real-time direct light, no indirect light +Click in the scene to move the sphere", + ), + } +} diff --git a/examples/3d/motion_blur.rs b/examples/3d/motion_blur.rs index 8e1bfea468011..66c90f51bb1ff 100644 --- a/examples/3d/motion_blur.rs +++ b/examples/3d/motion_blur.rs @@ -62,6 +62,7 @@ fn setup_scene( commands.insert_resource(AmbientLight { color: Color::WHITE, brightness: 300.0, + ..default() }); commands.insert_resource(CameraMode::Chase); commands.spawn(( diff --git a/examples/3d/skybox.rs b/examples/3d/skybox.rs index 8dc94d9f94aff..dd797473bf57d 100644 --- a/examples/3d/skybox.rs +++ b/examples/3d/skybox.rs @@ -86,6 +86,7 @@ fn setup(mut commands: Commands, asset_server: Res) { commands.insert_resource(AmbientLight { color: Color::srgb_u8(210, 220, 240), brightness: 1.0, + ..default() }); commands.insert_resource(Cubemap { diff --git a/examples/README.md b/examples/README.md index bf4e04d0f5010..b60a8da6fe432 100644 --- a/examples/README.md +++ b/examples/README.md @@ -158,6 +158,7 @@ Example | Description [Load glTF extras](../examples/3d/load_gltf_extras.rs) | Loads and renders a glTF file as a scene, including the gltf extras [Mesh Ray Cast](../examples/3d/mesh_ray_cast.rs) | Demonstrates ray casting with the `MeshRayCast` system parameter [Meshlet](../examples/3d/meshlet.rs) | Meshlet rendering for dense high-poly scenes (experimental) +[Mixed lighting](../examples/3d/mixed_lighting.rs) | Demonstrates how to combine baked and dynamic lighting [Motion Blur](../examples/3d/motion_blur.rs) | Demonstrates per-pixel motion blur [Order Independent Transparency](../examples/3d/order_independent_transparency.rs) | Demonstrates how to use OIT [Orthographic View](../examples/3d/orthographic.rs) | Shows how to create a 3D orthographic view (for isometric-look in games or CAD applications) diff --git a/examples/animation/animated_fox.rs b/examples/animation/animated_fox.rs index 0de7389569690..912b2d082b0de 100644 --- a/examples/animation/animated_fox.rs +++ b/examples/animation/animated_fox.rs @@ -18,6 +18,7 @@ fn main() { .insert_resource(AmbientLight { color: Color::WHITE, brightness: 2000., + ..default() }) .add_plugins(DefaultPlugins) .init_resource::() diff --git a/examples/animation/animated_transform.rs b/examples/animation/animated_transform.rs index 21b85c5489d8b..decb3d34a69df 100644 --- a/examples/animation/animated_transform.rs +++ b/examples/animation/animated_transform.rs @@ -13,6 +13,7 @@ fn main() { .insert_resource(AmbientLight { color: Color::WHITE, brightness: 150.0, + ..default() }) .add_systems(Startup, setup) .run(); diff --git a/examples/animation/animation_graph.rs b/examples/animation/animation_graph.rs index 8238bfd116c5c..afe01c288c6d1 100644 --- a/examples/animation/animation_graph.rs +++ b/examples/animation/animation_graph.rs @@ -91,6 +91,7 @@ fn main() { .insert_resource(AmbientLight { color: WHITE.into(), brightness: 100.0, + ..default() }) .run(); } diff --git a/examples/animation/animation_masks.rs b/examples/animation/animation_masks.rs index 7a27d6acd0347..1bde5c909f54f 100644 --- a/examples/animation/animation_masks.rs +++ b/examples/animation/animation_masks.rs @@ -108,6 +108,7 @@ fn main() { .insert_resource(AmbientLight { color: WHITE.into(), brightness: 100.0, + ..default() }) .init_resource::() .run(); diff --git a/examples/asset/multi_asset_sync.rs b/examples/asset/multi_asset_sync.rs index 2e21f8fe32df3..0df4f71aec8ed 100644 --- a/examples/asset/multi_asset_sync.rs +++ b/examples/asset/multi_asset_sync.rs @@ -20,6 +20,7 @@ fn main() { .insert_resource(AmbientLight { color: Color::WHITE, brightness: 2000., + ..default() }) .add_systems(Startup, setup_assets) .add_systems(Startup, setup_scene)