mirror of
https://github.com/bevyengine/bevy
synced 2024-11-21 20:23:28 +00:00
Add Distance and Atmospheric Fog support (#6412)
<img width="1392" alt="image" src="https://user-images.githubusercontent.com/418473/203873533-44c029af-13b7-4740-8ea3-af96bd5867c9.png"> <img width="1392" alt="image" src="https://user-images.githubusercontent.com/418473/203873549-36be7a23-b341-42a2-8a9f-ceea8ac7a2b8.png"> # Objective - Add support for the “classic” distance fog effect, as well as a more advanced atmospheric fog effect. ## Solution This PR: - Introduces a new `FogSettings` component that controls distance fog per-camera. - Adds support for three widely used “traditional” fog falloff modes: `Linear`, `Exponential` and `ExponentialSquared`, as well as a more advanced `Atmospheric` fog; - Adds support for directional light influence over fog color; - Extracts fog via `ExtractComponent`, then uses a prepare system that sets up a new dynamic uniform struct (`Fog`), similar to other mesh view types; - Renders fog in PBR material shader, as a final adjustment to the `output_color`, after PBR is computed (but before tone mapping); - Adds a new `StandardMaterial` flag to enable fog; (`fog_enabled`) - Adds convenience methods for easier artistic control when creating non-linear fog types; - Adds documentation around fog. --- ## Changelog ### Added - Added support for distance-based fog effects for PBR materials, controllable per-camera via the new `FogSettings` component; - Added `FogFalloff` enum for selecting between three widely used “traditional” fog falloff modes: `Linear`, `Exponential` and `ExponentialSquared`, as well as a more advanced `Atmospheric` fog;
This commit is contained in:
parent
adae877be2
commit
1a96d820fd
18 changed files with 1439 additions and 15 deletions
20
Cargo.toml
20
Cargo.toml
|
@ -297,6 +297,26 @@ description = "A scene showcasing the built-in 3D shapes"
|
|||
category = "3D Rendering"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "atmospheric_fog"
|
||||
path = "examples/3d/atmospheric_fog.rs"
|
||||
|
||||
[package.metadata.example.atmospheric_fog]
|
||||
name = "Atmospheric Fog"
|
||||
description = "A scene showcasing the atmospheric fog effect"
|
||||
category = "3D Rendering"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "fog"
|
||||
path = "examples/3d/fog.rs"
|
||||
|
||||
[package.metadata.example.fog]
|
||||
name = "Fog"
|
||||
description = "A scene showcasing the distance fog effect"
|
||||
category = "3D Rendering"
|
||||
wasm = true
|
||||
|
||||
[[example]]
|
||||
name = "blend_modes"
|
||||
path = "examples/3d/blend_modes.rs"
|
||||
|
|
BIN
assets/models/terrain/Mountains.bin
Normal file
BIN
assets/models/terrain/Mountains.bin
Normal file
Binary file not shown.
143
assets/models/terrain/Mountains.gltf
Normal file
143
assets/models/terrain/Mountains.gltf
Normal file
|
@ -0,0 +1,143 @@
|
|||
{
|
||||
"asset" : {
|
||||
"generator" : "Khronos glTF Blender I/O v3.3.17",
|
||||
"version" : "2.0"
|
||||
},
|
||||
"extensionsUsed" : [
|
||||
"KHR_materials_specular",
|
||||
"KHR_materials_ior"
|
||||
],
|
||||
"scene" : 0,
|
||||
"scenes" : [
|
||||
{
|
||||
"name" : "Scene",
|
||||
"nodes" : [
|
||||
0,
|
||||
1
|
||||
]
|
||||
}
|
||||
],
|
||||
"nodes" : [
|
||||
{
|
||||
"mesh" : 0,
|
||||
"name" : "Grid"
|
||||
},
|
||||
{
|
||||
"mesh" : 0,
|
||||
"name" : "Grid.001",
|
||||
"translation" : [
|
||||
0.0018983177142217755,
|
||||
-2.7217100068810396e-05,
|
||||
0.0012765892315655947
|
||||
]
|
||||
}
|
||||
],
|
||||
"materials" : [
|
||||
{
|
||||
"doubleSided" : true,
|
||||
"extensions" : {
|
||||
"KHR_materials_specular" : {
|
||||
"specularColorFactor" : [
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
},
|
||||
"KHR_materials_ior" : {
|
||||
"ior" : 1.4500000476837158
|
||||
}
|
||||
},
|
||||
"name" : "Material.001",
|
||||
"pbrMetallicRoughness" : {
|
||||
"baseColorFactor" : [
|
||||
0.12338346652686596,
|
||||
0.35653680562973022,
|
||||
0.065849664583802223,
|
||||
1
|
||||
],
|
||||
"metallicFactor" : 0,
|
||||
"roughnessFactor" : 0.9980237483978271
|
||||
}
|
||||
}
|
||||
],
|
||||
"meshes" : [
|
||||
{
|
||||
"name" : "Grid",
|
||||
"primitives" : [
|
||||
{
|
||||
"attributes" : {
|
||||
"POSITION" : 0,
|
||||
"NORMAL" : 1,
|
||||
"TEXCOORD_0" : 2
|
||||
},
|
||||
"indices" : 3,
|
||||
"material" : 0
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"accessors" : [
|
||||
{
|
||||
"bufferView" : 0,
|
||||
"componentType" : 5126,
|
||||
"count" : 6561,
|
||||
"max" : [
|
||||
1.0000003576278687,
|
||||
0.2493455857038498,
|
||||
1.0051095485687256
|
||||
],
|
||||
"min" : [
|
||||
-1.0000003576278687,
|
||||
-0.08555418252944946,
|
||||
-1.0000003576278687
|
||||
],
|
||||
"type" : "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView" : 1,
|
||||
"componentType" : 5126,
|
||||
"count" : 6561,
|
||||
"type" : "VEC3"
|
||||
},
|
||||
{
|
||||
"bufferView" : 2,
|
||||
"componentType" : 5126,
|
||||
"count" : 6561,
|
||||
"type" : "VEC2"
|
||||
},
|
||||
{
|
||||
"bufferView" : 3,
|
||||
"componentType" : 5123,
|
||||
"count" : 38400,
|
||||
"type" : "SCALAR"
|
||||
}
|
||||
],
|
||||
"bufferViews" : [
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteLength" : 78732,
|
||||
"byteOffset" : 0
|
||||
},
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteLength" : 78732,
|
||||
"byteOffset" : 78732
|
||||
},
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteLength" : 52488,
|
||||
"byteOffset" : 157464
|
||||
},
|
||||
{
|
||||
"buffer" : 0,
|
||||
"byteLength" : 76800,
|
||||
"byteOffset" : 209952
|
||||
}
|
||||
],
|
||||
"buffers" : [
|
||||
{
|
||||
"byteLength" : 286752,
|
||||
"uri" : "Mountains.bin"
|
||||
}
|
||||
]
|
||||
}
|
486
crates/bevy_pbr/src/fog.rs
Normal file
486
crates/bevy_pbr/src/fog.rs
Normal file
|
@ -0,0 +1,486 @@
|
|||
use crate::ReflectComponent;
|
||||
use bevy_ecs::{prelude::*, query::QueryItem};
|
||||
use bevy_math::Vec3;
|
||||
use bevy_reflect::Reflect;
|
||||
use bevy_render::{color::Color, extract_component::ExtractComponent, prelude::Camera};
|
||||
|
||||
/// Configures the “classic” computer graphics [distance fog](https://en.wikipedia.org/wiki/Distance_fog) effect,
|
||||
/// in which objects appear progressively more covered in atmospheric haze the further away they are from the camera.
|
||||
/// Affects meshes rendered via the PBR [`StandardMaterial`](crate::StandardMaterial).
|
||||
///
|
||||
/// ## Falloff
|
||||
///
|
||||
/// The rate at which fog intensity increases with distance is controlled by the falloff mode.
|
||||
/// Currently, the following fog falloff modes are supported:
|
||||
///
|
||||
/// - [`FogFalloff::Linear`]
|
||||
/// - [`FogFalloff::Exponential`]
|
||||
/// - [`FogFalloff::ExponentialSquared`]
|
||||
/// - [`FogFalloff::Atmospheric`]
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_ecs::prelude::*;
|
||||
/// # use bevy_render::prelude::*;
|
||||
/// # use bevy_core_pipeline::prelude::*;
|
||||
/// # use bevy_pbr::prelude::*;
|
||||
/// # fn system(mut commands: Commands) {
|
||||
/// commands.spawn((
|
||||
/// // Setup your camera as usual
|
||||
/// Camera3dBundle {
|
||||
/// // ... camera options
|
||||
/// # ..Default::default()
|
||||
/// },
|
||||
/// // Add fog to the same entity
|
||||
/// FogSettings {
|
||||
/// color: Color::WHITE,
|
||||
/// falloff: FogFalloff::Exponential { density: 1e-3 },
|
||||
/// ..Default::default()
|
||||
/// },
|
||||
/// ));
|
||||
/// # }
|
||||
/// # bevy_ecs::system::assert_is_system(system);
|
||||
/// ```
|
||||
///
|
||||
/// ## Material Override
|
||||
///
|
||||
/// Once enabled for a specific camera, the fog effect can also be disabled for individual
|
||||
/// [`StandardMaterial`](crate::StandardMaterial) instances via the `fog_enabled` flag.
|
||||
#[derive(Debug, Clone, Component, Reflect)]
|
||||
#[reflect(Component)]
|
||||
pub struct FogSettings {
|
||||
/// The color of the fog effect.
|
||||
///
|
||||
/// **Tip:** The alpha channel of the color can be used to “modulate” the fog effect without
|
||||
/// changing the fog falloff mode or parameters.
|
||||
pub color: Color,
|
||||
|
||||
/// Color used to modulate the influence of directional light colors on the
|
||||
/// fog, where the view direction aligns with each directional light direction,
|
||||
/// producing a “glow” or light dispersion effect. (e.g. around the sun)
|
||||
///
|
||||
/// Use [`Color::NONE`] to disable the effect.
|
||||
pub directional_light_color: Color,
|
||||
|
||||
/// The exponent applied to the directional light alignment calculation.
|
||||
/// A higher value means a more concentrated “glow”.
|
||||
pub directional_light_exponent: f32,
|
||||
|
||||
/// Determines which falloff mode to use, and its parameters.
|
||||
pub falloff: FogFalloff,
|
||||
}
|
||||
|
||||
/// Allows switching between different fog falloff modes, and configuring their parameters.
|
||||
///
|
||||
/// ## Convenience Methods
|
||||
///
|
||||
/// When using non-linear fog modes it can be hard to determine the right parameter values
|
||||
/// for a given scene.
|
||||
///
|
||||
/// For easier artistic control, instead of creating the enum variants directly, you can use the
|
||||
/// visibility-based convenience methods:
|
||||
///
|
||||
/// - For `FogFalloff::Exponential`:
|
||||
/// - [`FogFalloff::from_visibility()`]
|
||||
/// - [`FogFalloff::from_visibility_contrast()`]
|
||||
///
|
||||
/// - For `FogFalloff::ExponentialSquared`:
|
||||
/// - [`FogFalloff::from_visibility_squared()`]
|
||||
/// - [`FogFalloff::from_visibility_contrast_squared()`]
|
||||
///
|
||||
/// - For `FogFalloff::Atmospheric`:
|
||||
/// - [`FogFalloff::from_visibility_color()`]
|
||||
/// - [`FogFalloff::from_visibility_colors()`]
|
||||
/// - [`FogFalloff::from_visibility_contrast_color()`]
|
||||
/// - [`FogFalloff::from_visibility_contrast_colors()`]
|
||||
#[derive(Debug, Clone, Reflect)]
|
||||
pub enum FogFalloff {
|
||||
/// A linear fog falloff that grows in intensity between `start` and `end` distances.
|
||||
///
|
||||
/// This falloff mode is simpler to control than other modes, however it can produce results that look “artificial”, depending on the scene.
|
||||
///
|
||||
/// ## Formula
|
||||
///
|
||||
/// The fog intensity for a given point in the scene is determined by the following formula:
|
||||
///
|
||||
/// ```text
|
||||
/// let fog_intensity = 1.0 - ((end - distance) / (end - start)).clamp(0.0, 1.0);
|
||||
/// ```
|
||||
///
|
||||
/// <svg width="370" height="212" viewBox="0 0 370 212" fill="none">
|
||||
/// <title>Plot showing how linear fog falloff behaves for start and end values of 0.8 and 2.2, respectively.</title>
|
||||
/// <path d="M331 151H42V49" stroke="currentColor" stroke-width="2"/>
|
||||
/// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-family="Inter" font-size="12" letter-spacing="0em"><tspan x="136" y="173.864">1</tspan></text>
|
||||
/// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-family="Inter" font-size="12" letter-spacing="0em"><tspan x="30" y="53.8636">1</tspan></text>
|
||||
/// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-family="Inter" font-size="12" letter-spacing="0em"><tspan x="42" y="173.864">0</tspan></text>
|
||||
/// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-family="Inter" font-size="12" letter-spacing="0em"><tspan x="232" y="173.864">2</tspan></text>
|
||||
/// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-family="Inter" font-size="12" letter-spacing="0em"><tspan x="332" y="173.864">3</tspan></text>
|
||||
/// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-family="Inter" font-size="12" letter-spacing="0em"><tspan x="161" y="190.864">distance</tspan></text>
|
||||
/// <text font-family="sans-serif" transform="translate(10 132) rotate(-90)" fill="currentColor" style="white-space: pre" font-family="Inter" font-size="12" letter-spacing="0em"><tspan x="0" y="11.8636">fog intensity</tspan></text>
|
||||
/// <path d="M43 150H117.227L263 48H331" stroke="#FF00E5"/>
|
||||
/// <path d="M118 151V49" stroke="#FF00E5" stroke-dasharray="1 4"/>
|
||||
/// <path d="M263 151V49" stroke="#FF00E5" stroke-dasharray="1 4"/>
|
||||
/// <text font-family="sans-serif" fill="#FF00E5" style="white-space: pre" font-family="Inter" font-size="10" letter-spacing="0em"><tspan x="121" y="58.6364">start</tspan></text>
|
||||
/// <text font-family="sans-serif" fill="#FF00E5" style="white-space: pre" font-family="Inter" font-size="10" letter-spacing="0em"><tspan x="267" y="58.6364">end</tspan></text>
|
||||
/// </svg>
|
||||
Linear {
|
||||
/// Distance from the camera where fog is completely transparent, in world units.
|
||||
start: f32,
|
||||
|
||||
/// Distance from the camera where fog is completely opaque, in world units.
|
||||
end: f32,
|
||||
},
|
||||
|
||||
/// An exponential fog falloff with a given `density`.
|
||||
///
|
||||
/// Initially gains intensity quickly with distance, then more slowly. Typically produces more natural results than [`FogFalloff::Linear`],
|
||||
/// but is a bit harder to control.
|
||||
///
|
||||
/// To move the fog “further away”, use lower density values. To move it “closer” use higher density values.
|
||||
///
|
||||
/// ## Tips
|
||||
///
|
||||
/// - Use the [`FogFalloff::from_visibility()`] convenience method to create an exponential falloff with the proper
|
||||
/// density for a desired visibility distance in world units;
|
||||
/// - It's not _unusual_ to have very large or very small values for the density, depending on the scene
|
||||
/// scale. Typically, for scenes with objects in the scale of thousands of units, you might want density values
|
||||
/// in the ballpark of `0.001`. Conversely, for really small scale scenes you might want really high values of
|
||||
/// density;
|
||||
/// - Combine the `density` parameter with the [`FogSettings`] `color`'s alpha channel for easier artistic control.
|
||||
///
|
||||
/// ## Formula
|
||||
///
|
||||
/// The fog intensity for a given point in the scene is determined by the following formula:
|
||||
///
|
||||
/// ```text
|
||||
/// let fog_intensity = 1.0 - 1.0 / (distance * density).exp();
|
||||
/// ```
|
||||
///
|
||||
/// <svg width="370" height="212" viewBox="0 0 370 212" fill="none">
|
||||
/// <title>Plot showing how exponential fog falloff behaves for different density values</title>
|
||||
/// <mask id="mask0_3_31" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="42" y="42" width="286" height="108">
|
||||
/// <rect x="42" y="42" width="286" height="108" fill="#D9D9D9"/>
|
||||
/// </mask>
|
||||
/// <g mask="url(#mask0_3_31)">
|
||||
/// <path d="M42 150C42 150 98.3894 53 254.825 53L662 53" stroke="#FF003D" stroke-width="1"/>
|
||||
/// <path d="M42 150C42 150 139.499 53 409.981 53L1114 53" stroke="#001AFF" stroke-width="1"/>
|
||||
/// <path d="M42 150C42 150 206.348 53 662.281 53L1849 53" stroke="#14FF00" stroke-width="1"/>
|
||||
/// </g>
|
||||
/// <path d="M331 151H42V49" stroke="currentColor" stroke-width="2"/>
|
||||
/// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-size="12" letter-spacing="0em"><tspan x="136" y="173.864">1</tspan></text>
|
||||
/// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-size="12" letter-spacing="0em"><tspan x="30" y="53.8636">1</tspan></text>
|
||||
/// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-size="12" letter-spacing="0em"><tspan x="42" y="173.864">0</tspan></text>
|
||||
/// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-size="12" letter-spacing="0em"><tspan x="232" y="173.864">2</tspan></text>
|
||||
/// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-size="12" letter-spacing="0em"><tspan x="332" y="173.864">3</tspan></text>
|
||||
/// <text font-family="sans-serif" fill="#FF003D" style="white-space: pre" font-size="10" letter-spacing="0em"><tspan x="77" y="64.6364">density = 2</tspan></text>
|
||||
/// <text font-family="sans-serif" fill="#001AFF" style="white-space: pre" font-size="10" letter-spacing="0em"><tspan x="236" y="76.6364">density = 1</tspan></text>
|
||||
/// <text font-family="sans-serif" fill="#14FF00" style="white-space: pre" font-size="10" letter-spacing="0em"><tspan x="205" y="115.636">density = 0.5</tspan></text>
|
||||
/// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-size="12" letter-spacing="0em"><tspan x="161" y="190.864">distance</tspan></text>
|
||||
/// <text font-family="sans-serif" transform="translate(10 132) rotate(-90)" fill="currentColor" style="white-space: pre" font-size="12" letter-spacing="0em"><tspan x="0" y="11.8636">fog intensity</tspan></text>
|
||||
/// </svg>
|
||||
Exponential {
|
||||
/// Multiplier applied to the world distance (within the exponential fog falloff calculation).
|
||||
density: f32,
|
||||
},
|
||||
|
||||
/// A squared exponential fog falloff with a given `density`.
|
||||
///
|
||||
/// Similar to [`FogFalloff::Exponential`], but grows more slowly in intensity for closer distances
|
||||
/// before “catching up”.
|
||||
///
|
||||
/// To move the fog “further away”, use lower density values. To move it “closer” use higher density values.
|
||||
///
|
||||
/// ## Tips
|
||||
///
|
||||
/// - Use the [`FogFalloff::from_visibility_squared()`] convenience method to create an exponential squared falloff
|
||||
/// with the proper density for a desired visibility distance in world units;
|
||||
/// - Combine the `density` parameter with the [`FogSettings`] `color`'s alpha channel for easier artistic control.
|
||||
///
|
||||
/// ## Formula
|
||||
///
|
||||
/// The fog intensity for a given point in the scene is determined by the following formula:
|
||||
///
|
||||
/// ```text
|
||||
/// let fog_intensity = 1.0 - 1.0 / (distance * density).powi(2).exp();
|
||||
/// ```
|
||||
///
|
||||
/// <svg width="370" height="212" viewBox="0 0 370 212" fill="none">
|
||||
/// <title>Plot showing how exponential squared fog falloff behaves for different density values</title>
|
||||
/// <mask id="mask0_1_3" style="mask-type:alpha" maskUnits="userSpaceOnUse" x="42" y="42" width="286" height="108">
|
||||
/// <rect x="42" y="42" width="286" height="108" fill="#D9D9D9"/>
|
||||
/// </mask>
|
||||
/// <g mask="url(#mask0_1_3)">
|
||||
/// <path d="M42 150C75.4552 150 74.9241 53.1724 166.262 53.1724L404 53.1724" stroke="#FF003D" stroke-width="1"/>
|
||||
/// <path d="M42 150C107.986 150 106.939 53.1724 287.091 53.1724L756 53.1724" stroke="#001AFF" stroke-width="1"/>
|
||||
/// <path d="M42 150C166.394 150 164.42 53.1724 504.035 53.1724L1388 53.1724" stroke="#14FF00" stroke-width="1"/>
|
||||
/// </g>
|
||||
/// <path d="M331 151H42V49" stroke="currentColor" stroke-width="2"/>
|
||||
/// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-size="12" letter-spacing="0em"><tspan x="136" y="173.864">1</tspan></text>
|
||||
/// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-size="12" letter-spacing="0em"><tspan x="30" y="53.8636">1</tspan></text>
|
||||
/// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-size="12" letter-spacing="0em"><tspan x="42" y="173.864">0</tspan></text>
|
||||
/// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-size="12" letter-spacing="0em"><tspan x="232" y="173.864">2</tspan></text>
|
||||
/// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-size="12" letter-spacing="0em"><tspan x="332" y="173.864">3</tspan></text>
|
||||
/// <text font-family="sans-serif" fill="#FF003D" style="white-space: pre" font-size="10" letter-spacing="0em"><tspan x="61" y="54.6364">density = 2</tspan></text>
|
||||
/// <text font-family="sans-serif" fill="#001AFF" style="white-space: pre" font-size="10" letter-spacing="0em"><tspan x="168" y="84.6364">density = 1</tspan></text>
|
||||
/// <text font-family="sans-serif" fill="#14FF00" style="white-space: pre" font-size="10" letter-spacing="0em"><tspan x="174" y="121.636">density = 0.5</tspan></text>
|
||||
/// <text font-family="sans-serif" fill="currentColor" style="white-space: pre" font-size="12" letter-spacing="0em"><tspan x="161" y="190.864">distance</tspan></text>
|
||||
/// <text font-family="sans-serif" transform="translate(10 132) rotate(-90)" fill="currentColor" style="white-space: pre" font-size="12" letter-spacing="0em"><tspan x="0" y="11.8636">fog intensity</tspan></text>
|
||||
/// </svg>
|
||||
ExponentialSquared {
|
||||
/// Multiplier applied to the world distance (within the exponential squared fog falloff calculation).
|
||||
density: f32,
|
||||
},
|
||||
|
||||
/// A more general form of the [`FogFalloff::Exponential`] mode. The falloff formula is separated into
|
||||
/// two terms, `extinction` and `inscattering`, for a somewhat simplified atmospheric scattering model.
|
||||
/// Additionally, individual color channels can have their own density values, resulting in a total of
|
||||
/// six different configuration parameters.
|
||||
///
|
||||
/// ## Tips
|
||||
///
|
||||
/// - Use the [`FogFalloff::from_visibility_colors()`] or [`FogFalloff::from_visibility_color()`] convenience methods
|
||||
/// to create an atmospheric falloff with the proper densities for a desired visibility distance in world units and
|
||||
/// extinction and inscattering colors;
|
||||
/// - Combine the atmospheric fog parameters with the [`FogSettings`] `color`'s alpha channel for easier artistic control.
|
||||
///
|
||||
/// ## Formula
|
||||
///
|
||||
/// Unlike other modes, atmospheric falloff doesn't use a simple intensity-based blend of fog color with
|
||||
/// object color. Instead, it calculates per-channel extinction and inscattering factors, which are
|
||||
/// then used to calculate the final color.
|
||||
///
|
||||
/// ```text
|
||||
/// let extinction_factor = 1.0 - 1.0 / (distance * extinction).exp();
|
||||
/// let inscattering_factor = 1.0 - 1.0 / (distance * inscattering).exp();
|
||||
/// let result = input_color * (1.0 - extinction_factor) + fog_color * inscattering_factor;
|
||||
/// ```
|
||||
///
|
||||
/// ## Equivalence to [`FogFalloff::Exponential`]
|
||||
///
|
||||
/// For a density value of `D`, the following two falloff modes will produce identical visual results:
|
||||
///
|
||||
/// ```
|
||||
/// # use bevy_pbr::prelude::*;
|
||||
/// # use bevy_math::prelude::*;
|
||||
/// # const D: f32 = 0.5;
|
||||
/// #
|
||||
/// let exponential = FogFalloff::Exponential {
|
||||
/// density: D,
|
||||
/// };
|
||||
///
|
||||
/// let atmospheric = FogFalloff::Atmospheric {
|
||||
/// extinction: Vec3::new(D, D, D),
|
||||
/// inscattering: Vec3::new(D, D, D),
|
||||
/// };
|
||||
/// ```
|
||||
///
|
||||
/// **Note:** While the results are identical, [`FogFalloff::Atmospheric`] is computationally more expensive.
|
||||
Atmospheric {
|
||||
/// Controls how much light is removed due to atmospheric “extinction”, i.e. loss of light due to
|
||||
/// photons being absorbed by atmospheric particles.
|
||||
///
|
||||
/// Each component can be thought of as an independent per `R`/`G`/`B` channel `density` factor from
|
||||
/// [`FogFalloff::Exponential`]: Multiplier applied to the world distance (within the fog
|
||||
/// falloff calculation) for that specific channel.
|
||||
///
|
||||
/// **Note:**
|
||||
/// This value is not a `Color`, since it affects the channels exponentially in a non-intuitive way.
|
||||
/// For artistic control, use the [`FogFalloff::from_visibility_colors()`] convenience method.
|
||||
extinction: Vec3,
|
||||
|
||||
/// Controls how much light is added due to light scattering from the sun through the atmosphere.
|
||||
///
|
||||
/// Each component can be thought of as an independent per `R`/`G`/`B` channel `density` factor from
|
||||
/// [`FogFalloff::Exponential`]: A multiplier applied to the world distance (within the fog
|
||||
/// falloff calculation) for that specific channel.
|
||||
///
|
||||
/// **Note:**
|
||||
/// This value is not a `Color`, since it affects the channels exponentially in a non-intuitive way.
|
||||
/// For artistic control, use the [`FogFalloff::from_visibility_colors()`] convenience method.
|
||||
inscattering: Vec3,
|
||||
},
|
||||
}
|
||||
|
||||
impl FogFalloff {
|
||||
/// Creates a [`FogFalloff::Exponential`] value from the given visibility distance in world units,
|
||||
/// using the revised Koschmieder contrast threshold, [`FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD`].
|
||||
pub fn from_visibility(visibility: f32) -> FogFalloff {
|
||||
FogFalloff::from_visibility_contrast(
|
||||
visibility,
|
||||
FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD,
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a [`FogFalloff::Exponential`] value from the given visibility distance in world units,
|
||||
/// and a given contrast threshold in the range of `0.0` to `1.0`.
|
||||
pub fn from_visibility_contrast(visibility: f32, contrast_threshold: f32) -> FogFalloff {
|
||||
FogFalloff::Exponential {
|
||||
density: FogFalloff::koschmieder(visibility, contrast_threshold),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [`FogFalloff::ExponentialSquared`] value from the given visibility distance in world units,
|
||||
/// using the revised Koschmieder contrast threshold, [`FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD`].
|
||||
pub fn from_visibility_squared(visibility: f32) -> FogFalloff {
|
||||
FogFalloff::from_visibility_contrast_squared(
|
||||
visibility,
|
||||
FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD,
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a [`FogFalloff::ExponentialSquared`] value from the given visibility distance in world units,
|
||||
/// and a given contrast threshold in the range of `0.0` to `1.0`.
|
||||
pub fn from_visibility_contrast_squared(
|
||||
visibility: f32,
|
||||
contrast_threshold: f32,
|
||||
) -> FogFalloff {
|
||||
FogFalloff::ExponentialSquared {
|
||||
density: (FogFalloff::koschmieder(visibility, contrast_threshold) / visibility).sqrt(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a [`FogFalloff::Atmospheric`] value from the given visibility distance in world units,
|
||||
/// and a shared color for both extinction and inscattering, using the revised Koschmieder contrast threshold,
|
||||
/// [`FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD`].
|
||||
pub fn from_visibility_color(
|
||||
visibility: f32,
|
||||
extinction_inscattering_color: Color,
|
||||
) -> FogFalloff {
|
||||
FogFalloff::from_visibility_contrast_colors(
|
||||
visibility,
|
||||
FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD,
|
||||
extinction_inscattering_color,
|
||||
extinction_inscattering_color,
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a [`FogFalloff::Atmospheric`] value from the given visibility distance in world units,
|
||||
/// extinction and inscattering colors, using the revised Koschmieder contrast threshold,
|
||||
/// [`FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD`].
|
||||
///
|
||||
/// ## Tips
|
||||
/// - Alpha values of the provided colors can modulate the `extinction` and `inscattering` effects;
|
||||
/// - Using an `extinction_color` of [`Color::WHITE`] or [`Color::NONE`] disables the extinction effect;
|
||||
/// - Using an `inscattering_color` of [`Color::BLACK`] or [`Color::NONE`] disables the inscattering effect.
|
||||
pub fn from_visibility_colors(
|
||||
visibility: f32,
|
||||
extinction_color: Color,
|
||||
inscattering_color: Color,
|
||||
) -> FogFalloff {
|
||||
FogFalloff::from_visibility_contrast_colors(
|
||||
visibility,
|
||||
FogFalloff::REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD,
|
||||
extinction_color,
|
||||
inscattering_color,
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a [`FogFalloff::Atmospheric`] value from the given visibility distance in world units,
|
||||
/// a contrast threshold in the range of `0.0` to `1.0`, and a shared color for both extinction and inscattering.
|
||||
pub fn from_visibility_contrast_color(
|
||||
visibility: f32,
|
||||
contrast_threshold: f32,
|
||||
extinction_inscattering_color: Color,
|
||||
) -> FogFalloff {
|
||||
FogFalloff::from_visibility_contrast_colors(
|
||||
visibility,
|
||||
contrast_threshold,
|
||||
extinction_inscattering_color,
|
||||
extinction_inscattering_color,
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates a [`FogFalloff::Atmospheric`] value from the given visibility distance in world units,
|
||||
/// a contrast threshold in the range of `0.0` to `1.0`, extinction and inscattering colors.
|
||||
///
|
||||
/// ## Tips
|
||||
/// - Alpha values of the provided colors can modulate the `extinction` and `inscattering` effects;
|
||||
/// - Using an `extinction_color` of [`Color::WHITE`] or [`Color::NONE`] disables the extinction effect;
|
||||
/// - Using an `inscattering_color` of [`Color::BLACK`] or [`Color::NONE`] disables the inscattering effect.
|
||||
pub fn from_visibility_contrast_colors(
|
||||
visibility: f32,
|
||||
contrast_threshold: f32,
|
||||
extinction_color: Color,
|
||||
inscattering_color: Color,
|
||||
) -> FogFalloff {
|
||||
use std::f32::consts::E;
|
||||
|
||||
let [r_e, g_e, b_e, a_e] = extinction_color.as_linear_rgba_f32();
|
||||
let [r_i, g_i, b_i, a_i] = inscattering_color.as_linear_rgba_f32();
|
||||
|
||||
FogFalloff::Atmospheric {
|
||||
extinction: Vec3::new(
|
||||
// Values are subtracted from 1.0 here to preserve the intuitive/artistic meaning of
|
||||
// colors, since they're later subtracted. (e.g. by giving a blue extinction color, you
|
||||
// get blue and _not_ yellow results)
|
||||
(1.0 - r_e).powf(E),
|
||||
(1.0 - g_e).powf(E),
|
||||
(1.0 - b_e).powf(E),
|
||||
) * FogFalloff::koschmieder(visibility, contrast_threshold)
|
||||
* a_e.powf(E),
|
||||
|
||||
inscattering: Vec3::new(r_i.powf(E), g_i.powf(E), b_i.powf(E))
|
||||
* FogFalloff::koschmieder(visibility, contrast_threshold)
|
||||
* a_i.powf(E),
|
||||
}
|
||||
}
|
||||
|
||||
/// A 2% contrast threshold was originally proposed by Koschmieder, being the
|
||||
/// minimum visual contrast at which a human observer could detect an object.
|
||||
/// We use a revised 5% contrast threshold, deemed more realistic for typical human observers.
|
||||
pub const REVISED_KOSCHMIEDER_CONTRAST_THRESHOLD: f32 = 0.05;
|
||||
|
||||
/// Calculates the extinction coefficient β, from V and Cₜ, where:
|
||||
///
|
||||
/// - Cₜ is the contrast threshold, in the range of `0.0` to `1.0`
|
||||
/// - V is the visibility distance in which a perfectly black object is still identifiable
|
||||
/// against the horizon sky within the contrast threshold
|
||||
///
|
||||
/// We start with Koschmieder's equation:
|
||||
///
|
||||
/// ```text
|
||||
/// -ln(Cₜ)
|
||||
/// V = ─────────
|
||||
/// β
|
||||
/// ```
|
||||
///
|
||||
/// Multiplying both sides by β/V, that gives us:
|
||||
///
|
||||
/// ```text
|
||||
/// -ln(Cₜ)
|
||||
/// β = ─────────
|
||||
/// V
|
||||
/// ```
|
||||
///
|
||||
/// See:
|
||||
/// - <https://en.wikipedia.org/wiki/Visibility>
|
||||
/// - <https://www.biral.com/wp-content/uploads/2015/02/Introduction_to_visibility-v2-2.pdf>
|
||||
pub fn koschmieder(v: f32, c_t: f32) -> f32 {
|
||||
-c_t.ln() / v
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FogSettings {
|
||||
fn default() -> Self {
|
||||
FogSettings {
|
||||
color: Color::rgba(1.0, 1.0, 1.0, 1.0),
|
||||
falloff: FogFalloff::Linear {
|
||||
start: 0.0,
|
||||
end: 100.0,
|
||||
},
|
||||
directional_light_color: Color::NONE,
|
||||
directional_light_exponent: 8.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ExtractComponent for FogSettings {
|
||||
type Query = &'static Self;
|
||||
type Filter = With<Camera>;
|
||||
type Out = Self;
|
||||
|
||||
fn extract_component(item: QueryItem<Self::Query>) -> Option<Self::Out> {
|
||||
Some(item.clone())
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@ pub mod wireframe;
|
|||
|
||||
mod alpha;
|
||||
mod bundle;
|
||||
mod fog;
|
||||
mod light;
|
||||
mod material;
|
||||
mod pbr_material;
|
||||
|
@ -11,6 +12,7 @@ mod render;
|
|||
pub use alpha::*;
|
||||
use bevy_utils::default;
|
||||
pub use bundle::*;
|
||||
pub use fog::*;
|
||||
pub use light::*;
|
||||
pub use material::*;
|
||||
pub use pbr_material::*;
|
||||
|
@ -27,6 +29,7 @@ pub mod prelude {
|
|||
DirectionalLightBundle, MaterialMeshBundle, PbrBundle, PointLightBundle,
|
||||
SpotLightBundle,
|
||||
},
|
||||
fog::{FogFalloff, FogSettings},
|
||||
light::{AmbientLight, DirectionalLight, PointLight, SpotLight},
|
||||
material::{Material, MaterialPlugin},
|
||||
pbr_material::StandardMaterial,
|
||||
|
@ -169,6 +172,7 @@ impl Plugin for PbrPlugin {
|
|||
.init_resource::<DirectionalLightShadowMap>()
|
||||
.init_resource::<PointLightShadowMap>()
|
||||
.add_plugin(ExtractResourcePlugin::<AmbientLight>::default())
|
||||
.add_plugin(FogPlugin)
|
||||
.add_system_to_stage(
|
||||
CoreStage::PostUpdate,
|
||||
// NOTE: Clusters need to have been added before update_clusters is run so
|
||||
|
|
|
@ -207,6 +207,9 @@ pub struct StandardMaterial {
|
|||
/// shadows, alpha mode and ambient light are ignored if this is set to `true`.
|
||||
pub unlit: bool,
|
||||
|
||||
/// Whether to enable fog for this material.
|
||||
pub fog_enabled: bool,
|
||||
|
||||
/// How to apply the alpha channel of the `base_color_texture`.
|
||||
///
|
||||
/// See [`AlphaMode`] for details. Defaults to [`AlphaMode::Opaque`].
|
||||
|
@ -257,6 +260,7 @@ impl Default for StandardMaterial {
|
|||
double_sided: false,
|
||||
cull_mode: Some(Face::Back),
|
||||
unlit: false,
|
||||
fog_enabled: true,
|
||||
alpha_mode: AlphaMode::Opaque,
|
||||
depth_bias: 0.0,
|
||||
}
|
||||
|
@ -300,6 +304,7 @@ bitflags::bitflags! {
|
|||
const UNLIT = (1 << 5);
|
||||
const TWO_COMPONENT_NORMAL_MAP = (1 << 6);
|
||||
const FLIP_NORMAL_MAP_Y = (1 << 7);
|
||||
const FOG_ENABLED = (1 << 8);
|
||||
const ALPHA_MODE_RESERVED_BITS = (Self::ALPHA_MODE_MASK_BITS << Self::ALPHA_MODE_SHIFT_BITS); // ← Bitmask reserving bits for the `AlphaMode`
|
||||
const ALPHA_MODE_OPAQUE = (0 << Self::ALPHA_MODE_SHIFT_BITS); // ← Values are just sequential values bitshifted into
|
||||
const ALPHA_MODE_MASK = (1 << Self::ALPHA_MODE_SHIFT_BITS); // the bitmask, and can range from 0 to 7.
|
||||
|
@ -362,6 +367,9 @@ impl AsBindGroupShaderType<StandardMaterialUniform> for StandardMaterial {
|
|||
if self.unlit {
|
||||
flags |= StandardMaterialFlags::UNLIT;
|
||||
}
|
||||
if self.fog_enabled {
|
||||
flags |= StandardMaterialFlags::FOG_ENABLED;
|
||||
}
|
||||
let has_normal_map = self.normal_map_texture.is_some();
|
||||
if has_normal_map {
|
||||
if let Some(texture) = images.get(self.normal_map_texture.as_ref().unwrap()) {
|
||||
|
|
147
crates/bevy_pbr/src/render/fog.rs
Normal file
147
crates/bevy_pbr/src/render/fog.rs
Normal file
|
@ -0,0 +1,147 @@
|
|||
use bevy_app::{App, Plugin};
|
||||
use bevy_asset::{load_internal_asset, HandleUntyped};
|
||||
use bevy_ecs::{prelude::*, schedule::SystemLabel};
|
||||
use bevy_math::{Vec3, Vec4};
|
||||
use bevy_reflect::TypeUuid;
|
||||
use bevy_render::{
|
||||
extract_component::ExtractComponentPlugin,
|
||||
render_resource::{DynamicUniformBuffer, Shader, ShaderType},
|
||||
renderer::{RenderDevice, RenderQueue},
|
||||
view::ExtractedView,
|
||||
RenderApp, RenderStage,
|
||||
};
|
||||
|
||||
use crate::{FogFalloff, FogSettings};
|
||||
|
||||
/// The GPU-side representation of the fog configuration that's sent as a uniform to the shader
|
||||
#[derive(Copy, Clone, ShaderType, Default, Debug)]
|
||||
pub struct GpuFog {
|
||||
/// Fog color
|
||||
base_color: Vec4,
|
||||
/// The color used for the fog where the view direction aligns with directional lights
|
||||
directional_light_color: Vec4,
|
||||
/// Allocated differently depending on fog mode.
|
||||
/// See `mesh_view_types.wgsl` for a detailed explanation
|
||||
be: Vec3,
|
||||
/// The exponent applied to the directional light alignment calculation
|
||||
directional_light_exponent: f32,
|
||||
/// Allocated differently depending on fog mode.
|
||||
/// See `mesh_view_types.wgsl` for a detailed explanation
|
||||
bi: Vec3,
|
||||
/// Unsigned int representation of the active fog falloff mode
|
||||
mode: u32,
|
||||
}
|
||||
|
||||
// Important: These must be kept in sync with `mesh_view_types.wgsl`
|
||||
const GPU_FOG_MODE_OFF: u32 = 0;
|
||||
const GPU_FOG_MODE_LINEAR: u32 = 1;
|
||||
const GPU_FOG_MODE_EXPONENTIAL: u32 = 2;
|
||||
const GPU_FOG_MODE_EXPONENTIAL_SQUARED: u32 = 3;
|
||||
const GPU_FOG_MODE_ATMOSPHERIC: u32 = 4;
|
||||
|
||||
/// Metadata for fog
|
||||
#[derive(Default, Resource)]
|
||||
pub struct FogMeta {
|
||||
pub gpu_fogs: DynamicUniformBuffer<GpuFog>,
|
||||
}
|
||||
|
||||
/// Prepares fog metadata and writes the fog-related uniform buffers to the GPU
|
||||
pub fn prepare_fog(
|
||||
mut commands: Commands,
|
||||
render_device: Res<RenderDevice>,
|
||||
render_queue: Res<RenderQueue>,
|
||||
mut fog_meta: ResMut<FogMeta>,
|
||||
views: Query<(Entity, Option<&FogSettings>), With<ExtractedView>>,
|
||||
) {
|
||||
for (entity, fog) in &views {
|
||||
let gpu_fog = if let Some(fog) = fog {
|
||||
match &fog.falloff {
|
||||
FogFalloff::Linear { start, end } => GpuFog {
|
||||
mode: GPU_FOG_MODE_LINEAR,
|
||||
base_color: fog.color.into(),
|
||||
directional_light_color: fog.directional_light_color.into(),
|
||||
directional_light_exponent: fog.directional_light_exponent,
|
||||
be: Vec3::new(*start, *end, 0.0),
|
||||
..Default::default()
|
||||
},
|
||||
FogFalloff::Exponential { density } => GpuFog {
|
||||
mode: GPU_FOG_MODE_EXPONENTIAL,
|
||||
base_color: fog.color.into(),
|
||||
directional_light_color: fog.directional_light_color.into(),
|
||||
directional_light_exponent: fog.directional_light_exponent,
|
||||
be: Vec3::new(*density, 0.0, 0.0),
|
||||
..Default::default()
|
||||
},
|
||||
FogFalloff::ExponentialSquared { density } => GpuFog {
|
||||
mode: GPU_FOG_MODE_EXPONENTIAL_SQUARED,
|
||||
base_color: fog.color.into(),
|
||||
directional_light_color: fog.directional_light_color.into(),
|
||||
directional_light_exponent: fog.directional_light_exponent,
|
||||
be: Vec3::new(*density, 0.0, 0.0),
|
||||
..Default::default()
|
||||
},
|
||||
FogFalloff::Atmospheric {
|
||||
extinction,
|
||||
inscattering,
|
||||
} => GpuFog {
|
||||
mode: GPU_FOG_MODE_ATMOSPHERIC,
|
||||
base_color: fog.color.into(),
|
||||
directional_light_color: fog.directional_light_color.into(),
|
||||
directional_light_exponent: fog.directional_light_exponent,
|
||||
be: *extinction,
|
||||
bi: *inscattering,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
// If no fog is added to a camera, by default it's off
|
||||
GpuFog {
|
||||
mode: GPU_FOG_MODE_OFF,
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
|
||||
// This is later read by `SetMeshViewBindGroup<I>`
|
||||
commands.entity(entity).insert(ViewFogUniformOffset {
|
||||
offset: fog_meta.gpu_fogs.push(gpu_fog),
|
||||
});
|
||||
}
|
||||
|
||||
fog_meta
|
||||
.gpu_fogs
|
||||
.write_buffer(&render_device, &render_queue);
|
||||
}
|
||||
|
||||
/// Labels for fog-related systems
|
||||
#[derive(Debug, Hash, PartialEq, Eq, Clone, SystemLabel)]
|
||||
pub enum RenderFogSystems {
|
||||
PrepareFog,
|
||||
}
|
||||
|
||||
/// Inserted on each `Entity` with an `ExtractedView` to keep track of its offset
|
||||
/// in the `gpu_fogs` `DynamicUniformBuffer` within `FogMeta`
|
||||
#[derive(Component)]
|
||||
pub struct ViewFogUniformOffset {
|
||||
pub offset: u32,
|
||||
}
|
||||
|
||||
/// Handle for the fog WGSL Shader internal asset
|
||||
pub const FOG_SHADER_HANDLE: HandleUntyped =
|
||||
HandleUntyped::weak_from_u64(Shader::TYPE_UUID, 4913569193382610166);
|
||||
|
||||
/// A plugin that consolidates fog extraction, preparation and related resources/assets
|
||||
pub struct FogPlugin;
|
||||
|
||||
impl Plugin for FogPlugin {
|
||||
fn build(&self, app: &mut App) {
|
||||
load_internal_asset!(app, FOG_SHADER_HANDLE, "fog.wgsl", Shader::from_wgsl);
|
||||
|
||||
app.add_plugin(ExtractComponentPlugin::<FogSettings>::default());
|
||||
|
||||
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
|
||||
render_app.init_resource::<FogMeta>().add_system_to_stage(
|
||||
RenderStage::Prepare,
|
||||
prepare_fog.label(RenderFogSystems::PrepareFog),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
69
crates/bevy_pbr/src/render/fog.wgsl
Normal file
69
crates/bevy_pbr/src/render/fog.wgsl
Normal file
|
@ -0,0 +1,69 @@
|
|||
#define_import_path bevy_pbr::fog
|
||||
|
||||
// Fog formulas adapted from:
|
||||
// https://learn.microsoft.com/en-us/windows/win32/direct3d9/fog-formulas
|
||||
// https://catlikecoding.com/unity/tutorials/rendering/part-14/
|
||||
// https://iquilezles.org/articles/fog/ (Atmospheric Fog and Scattering)
|
||||
|
||||
fn scattering_adjusted_fog_color(
|
||||
scattering: vec3<f32>,
|
||||
) -> vec4<f32> {
|
||||
if (fog.directional_light_color.a > 0.0) {
|
||||
return vec4<f32>(
|
||||
fog.base_color.rgb
|
||||
+ scattering * fog.directional_light_color.rgb * fog.directional_light_color.a,
|
||||
fog.base_color.a,
|
||||
);
|
||||
} else {
|
||||
return fog.base_color;
|
||||
}
|
||||
}
|
||||
|
||||
fn linear_fog(
|
||||
input_color: vec4<f32>,
|
||||
distance: f32,
|
||||
scattering: vec3<f32>,
|
||||
) -> vec4<f32> {
|
||||
var fog_color = scattering_adjusted_fog_color(scattering);
|
||||
let start = fog.be.x;
|
||||
let end = fog.be.y;
|
||||
fog_color.a *= 1.0 - clamp((end - distance) / (end - start), 0.0, 1.0);
|
||||
return vec4<f32>(mix(input_color.rgb, fog_color.rgb, fog_color.a), input_color.a);
|
||||
}
|
||||
|
||||
fn exponential_fog(
|
||||
input_color: vec4<f32>,
|
||||
distance: f32,
|
||||
scattering: vec3<f32>,
|
||||
) -> vec4<f32> {
|
||||
var fog_color = scattering_adjusted_fog_color(scattering);
|
||||
let density = fog.be.x;
|
||||
fog_color.a *= 1.0 - 1.0 / exp(distance * density);
|
||||
return vec4<f32>(mix(input_color.rgb, fog_color.rgb, fog_color.a), input_color.a);
|
||||
}
|
||||
|
||||
fn exponential_squared_fog(
|
||||
input_color: vec4<f32>,
|
||||
distance: f32,
|
||||
scattering: vec3<f32>,
|
||||
) -> vec4<f32> {
|
||||
var fog_color = scattering_adjusted_fog_color(scattering);
|
||||
let distance_times_density = distance * fog.be.x;
|
||||
fog_color.a *= 1.0 - 1.0 / exp(distance_times_density * distance_times_density);
|
||||
return vec4<f32>(mix(input_color.rgb, fog_color.rgb, fog_color.a), input_color.a);
|
||||
}
|
||||
|
||||
fn atmospheric_fog(
|
||||
input_color: vec4<f32>,
|
||||
distance: f32,
|
||||
scattering: vec3<f32>,
|
||||
) -> vec4<f32> {
|
||||
var fog_color = scattering_adjusted_fog_color(scattering);
|
||||
let extinction_factor = 1.0 - 1.0 / exp(distance * fog.be);
|
||||
let inscattering_factor = 1.0 - 1.0 / exp(distance * fog.bi);
|
||||
return vec4<f32>(
|
||||
input_color.rgb * (1.0 - extinction_factor * fog_color.a)
|
||||
+ fog_color.rgb * inscattering_factor * fog_color.a,
|
||||
input_color.a
|
||||
);
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
use crate::{
|
||||
GlobalLightMeta, GpuLights, GpuPointLights, LightMeta, NotShadowCaster, NotShadowReceiver,
|
||||
ShadowPipeline, ViewClusterBindings, ViewLightsUniformOffset, ViewShadowBindings,
|
||||
CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT, MAX_CASCADES_PER_LIGHT, MAX_DIRECTIONAL_LIGHTS,
|
||||
FogMeta, GlobalLightMeta, GpuFog, GpuLights, GpuPointLights, LightMeta, NotShadowCaster,
|
||||
NotShadowReceiver, ShadowPipeline, ViewClusterBindings, ViewFogUniformOffset,
|
||||
ViewLightsUniformOffset, ViewShadowBindings, CLUSTERED_FORWARD_STORAGE_BUFFER_COUNT,
|
||||
MAX_CASCADES_PER_LIGHT, MAX_DIRECTIONAL_LIGHTS,
|
||||
};
|
||||
use bevy_app::Plugin;
|
||||
use bevy_asset::{load_internal_asset, Assets, Handle, HandleUntyped};
|
||||
|
@ -400,11 +401,22 @@ impl FromWorld for MeshPipeline {
|
|||
},
|
||||
count: None,
|
||||
},
|
||||
// Fog
|
||||
BindGroupLayoutEntry {
|
||||
binding: 10,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Buffer {
|
||||
ty: BufferBindingType::Uniform,
|
||||
has_dynamic_offset: true,
|
||||
min_binding_size: Some(GpuFog::min_size()),
|
||||
},
|
||||
count: None,
|
||||
},
|
||||
];
|
||||
if cfg!(not(feature = "webgl")) {
|
||||
// Depth texture
|
||||
entries.push(BindGroupLayoutEntry {
|
||||
binding: 10,
|
||||
binding: 11,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Texture {
|
||||
multisampled,
|
||||
|
@ -415,7 +427,7 @@ impl FromWorld for MeshPipeline {
|
|||
});
|
||||
// Normal texture
|
||||
entries.push(BindGroupLayoutEntry {
|
||||
binding: 11,
|
||||
binding: 12,
|
||||
visibility: ShaderStages::FRAGMENT,
|
||||
ty: BindingType::Texture {
|
||||
multisampled,
|
||||
|
@ -887,6 +899,7 @@ pub fn queue_mesh_view_bind_groups(
|
|||
shadow_pipeline: Res<ShadowPipeline>,
|
||||
light_meta: Res<LightMeta>,
|
||||
global_light_meta: Res<GlobalLightMeta>,
|
||||
fog_meta: Res<FogMeta>,
|
||||
view_uniforms: Res<ViewUniforms>,
|
||||
views: Query<(
|
||||
Entity,
|
||||
|
@ -899,11 +912,18 @@ pub fn queue_mesh_view_bind_groups(
|
|||
msaa: Res<Msaa>,
|
||||
globals_buffer: Res<GlobalsBuffer>,
|
||||
) {
|
||||
if let (Some(view_binding), Some(light_binding), Some(point_light_binding), Some(globals)) = (
|
||||
if let (
|
||||
Some(view_binding),
|
||||
Some(light_binding),
|
||||
Some(point_light_binding),
|
||||
Some(globals),
|
||||
Some(fog_binding),
|
||||
) = (
|
||||
view_uniforms.uniforms.binding(),
|
||||
light_meta.view_gpu_lights.binding(),
|
||||
global_light_meta.gpu_point_lights.binding(),
|
||||
globals_buffer.buffer.binding(),
|
||||
fog_meta.gpu_fogs.binding(),
|
||||
) {
|
||||
for (entity, view_shadow_bindings, view_cluster_bindings, prepass_textures) in &views {
|
||||
let layout = if msaa.samples() > 1 {
|
||||
|
@ -957,6 +977,10 @@ pub fn queue_mesh_view_bind_groups(
|
|||
binding: 9,
|
||||
resource: globals.clone(),
|
||||
},
|
||||
BindGroupEntry {
|
||||
binding: 10,
|
||||
resource: fog_binding.clone(),
|
||||
},
|
||||
];
|
||||
|
||||
// When using WebGL with MSAA, we can't create the fallback textures required by the prepass
|
||||
|
@ -971,7 +995,7 @@ pub fn queue_mesh_view_bind_groups(
|
|||
}
|
||||
};
|
||||
entries.push(BindGroupEntry {
|
||||
binding: 10,
|
||||
binding: 11,
|
||||
resource: BindingResource::TextureView(depth_view),
|
||||
});
|
||||
|
||||
|
@ -984,7 +1008,7 @@ pub fn queue_mesh_view_bind_groups(
|
|||
}
|
||||
};
|
||||
entries.push(BindGroupEntry {
|
||||
binding: 11,
|
||||
binding: 12,
|
||||
resource: BindingResource::TextureView(normal_view),
|
||||
});
|
||||
}
|
||||
|
@ -1008,6 +1032,7 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshViewBindGroup<I>
|
|||
type ViewWorldQuery = (
|
||||
Read<ViewUniformOffset>,
|
||||
Read<ViewLightsUniformOffset>,
|
||||
Read<ViewFogUniformOffset>,
|
||||
Read<MeshViewBindGroup>,
|
||||
);
|
||||
type ItemWorldQuery = ();
|
||||
|
@ -1015,7 +1040,10 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshViewBindGroup<I>
|
|||
#[inline]
|
||||
fn render<'w>(
|
||||
_item: &P,
|
||||
(view_uniform, view_lights, mesh_view_bind_group): ROQueryItem<'w, Self::ViewWorldQuery>,
|
||||
(view_uniform, view_lights, view_fog, mesh_view_bind_group): ROQueryItem<
|
||||
'w,
|
||||
Self::ViewWorldQuery,
|
||||
>,
|
||||
_entity: (),
|
||||
_: SystemParamItem<'w, '_, Self::Param>,
|
||||
pass: &mut TrackedRenderPass<'w>,
|
||||
|
@ -1023,7 +1051,7 @@ impl<P: PhaseItem, const I: usize> RenderCommand<P> for SetMeshViewBindGroup<I>
|
|||
pass.set_bind_group(
|
||||
I,
|
||||
&mesh_view_bind_group.value,
|
||||
&[view_uniform.offset, view_lights.offset],
|
||||
&[view_uniform.offset, view_lights.offset, view_fog.offset],
|
||||
);
|
||||
|
||||
RenderCommandResult::Success
|
||||
|
|
|
@ -43,15 +43,17 @@ var<uniform> cluster_offsets_and_counts: ClusterOffsetsAndCounts;
|
|||
|
||||
@group(0) @binding(9)
|
||||
var<uniform> globals: Globals;
|
||||
@group(0) @binding(10)
|
||||
var<uniform> fog: Fog;
|
||||
|
||||
#ifdef MULTISAMPLED
|
||||
@group(0) @binding(10)
|
||||
var depth_prepass_texture: texture_depth_multisampled_2d;
|
||||
@group(0) @binding(11)
|
||||
var depth_prepass_texture: texture_depth_multisampled_2d;
|
||||
@group(0) @binding(12)
|
||||
var normal_prepass_texture: texture_multisampled_2d<f32>;
|
||||
#else
|
||||
@group(0) @binding(10)
|
||||
var depth_prepass_texture: texture_depth_2d;
|
||||
@group(0) @binding(11)
|
||||
var depth_prepass_texture: texture_depth_2d;
|
||||
@group(0) @binding(12)
|
||||
var normal_prepass_texture: texture_2d<f32>;
|
||||
#endif
|
||||
#endif
|
||||
|
|
|
@ -70,6 +70,31 @@ struct Lights {
|
|||
spot_light_shadowmap_offset: i32,
|
||||
};
|
||||
|
||||
struct Fog {
|
||||
base_color: vec4<f32>,
|
||||
directional_light_color: vec4<f32>,
|
||||
// `be` and `bi` are allocated differently depending on the fog mode
|
||||
//
|
||||
// For Linear Fog:
|
||||
// be.x = start, be.y = end
|
||||
// For Exponential and ExponentialSquared Fog:
|
||||
// be.x = density
|
||||
// For Atmospheric Fog:
|
||||
// be = per-channel extinction density
|
||||
// bi = per-channel inscattering density
|
||||
be: vec3<f32>,
|
||||
directional_light_exponent: f32,
|
||||
bi: vec3<f32>,
|
||||
mode: u32,
|
||||
}
|
||||
|
||||
// Important: These must be kept in sync with `fog.rs`
|
||||
let FOG_MODE_OFF: u32 = 0u;
|
||||
let FOG_MODE_LINEAR: u32 = 1u;
|
||||
let FOG_MODE_EXPONENTIAL: u32 = 2u;
|
||||
let FOG_MODE_EXPONENTIAL_SQUARED: u32 = 3u;
|
||||
let FOG_MODE_ATMOSPHERIC: u32 = 4u;
|
||||
|
||||
#if AVAILABLE_STORAGE_BUFFER_BINDINGS >= 3
|
||||
struct PointLights {
|
||||
data: array<PointLight>,
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
mod fog;
|
||||
mod light;
|
||||
mod mesh;
|
||||
|
||||
pub use fog::*;
|
||||
pub use light::*;
|
||||
pub use mesh::*;
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
#import bevy_pbr::clustered_forward
|
||||
#import bevy_pbr::lighting
|
||||
#import bevy_pbr::shadows
|
||||
#import bevy_pbr::fog
|
||||
#import bevy_pbr::pbr_functions
|
||||
|
||||
struct FragmentInput {
|
||||
|
@ -95,6 +96,11 @@ fn fragment(in: FragmentInput) -> @location(0) vec4<f32> {
|
|||
output_color = alpha_discard(material, output_color);
|
||||
}
|
||||
|
||||
// fog
|
||||
if (fog.mode != FOG_MODE_OFF && (material.flags & STANDARD_MATERIAL_FLAGS_FOG_ENABLED_BIT) != 0u) {
|
||||
output_color = apply_fog(output_color, in.world_position.xyz, view.world_position.xyz);
|
||||
}
|
||||
|
||||
#ifdef TONEMAP_IN_SHADER
|
||||
output_color = tone_mapping(output_color);
|
||||
#endif
|
||||
|
|
|
@ -269,6 +269,46 @@ fn dither(color: vec4<f32>, pos: vec2<f32>) -> vec4<f32> {
|
|||
}
|
||||
#endif // DEBAND_DITHER
|
||||
|
||||
#ifndef NORMAL_PREPASS
|
||||
fn apply_fog(input_color: vec4<f32>, fragment_world_position: vec3<f32>, view_world_position: vec3<f32>) -> vec4<f32> {
|
||||
let view_to_world = fragment_world_position.xyz - view_world_position.xyz;
|
||||
|
||||
// `length()` is used here instead of just `view_to_world.z` since that produces more
|
||||
// high quality results, especially for denser/smaller fogs. we get a "curved"
|
||||
// fog shape that remains consistent with camera rotation, instead of a "linear"
|
||||
// fog shape that looks a bit fake
|
||||
let distance = length(view_to_world);
|
||||
|
||||
var scattering = vec3<f32>(0.0);
|
||||
if (fog.directional_light_color.a > 0.0) {
|
||||
let view_to_world_normalized = view_to_world / distance;
|
||||
let n_directional_lights = lights.n_directional_lights;
|
||||
for (var i: u32 = 0u; i < n_directional_lights; i = i + 1u) {
|
||||
let light = lights.directional_lights[i];
|
||||
scattering += pow(
|
||||
max(
|
||||
dot(view_to_world_normalized, light.direction_to_light),
|
||||
0.0
|
||||
),
|
||||
fog.directional_light_exponent
|
||||
) * light.color.rgb;
|
||||
}
|
||||
}
|
||||
|
||||
if (fog.mode == FOG_MODE_LINEAR) {
|
||||
return linear_fog(input_color, distance, scattering);
|
||||
} else if (fog.mode == FOG_MODE_EXPONENTIAL) {
|
||||
return exponential_fog(input_color, distance, scattering);
|
||||
} else if (fog.mode == FOG_MODE_EXPONENTIAL_SQUARED) {
|
||||
return exponential_squared_fog(input_color, distance, scattering);
|
||||
} else if (fog.mode == FOG_MODE_ATMOSPHERIC) {
|
||||
return atmospheric_fog(input_color, distance, scattering);
|
||||
} else {
|
||||
return input_color;
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef PREMULTIPLY_ALPHA
|
||||
fn premultiply_alpha(standard_material_flags: u32, color: vec4<f32>) -> vec4<f32> {
|
||||
// `Blend`, `Premultiplied` and `Alpha` all share the same `BlendState`. Depending
|
||||
|
|
|
@ -19,6 +19,7 @@ let STANDARD_MATERIAL_FLAGS_DOUBLE_SIDED_BIT: u32 = 16u;
|
|||
let STANDARD_MATERIAL_FLAGS_UNLIT_BIT: u32 = 32u;
|
||||
let STANDARD_MATERIAL_FLAGS_TWO_COMPONENT_NORMAL_MAP: u32 = 64u;
|
||||
let STANDARD_MATERIAL_FLAGS_FLIP_NORMAL_MAP_Y: u32 = 128u;
|
||||
let STANDARD_MATERIAL_FLAGS_FOG_ENABLED_BIT: u32 = 256u;
|
||||
let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_RESERVED_BITS: u32 = 3758096384u; // (0b111u32 << 29)
|
||||
let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_OPAQUE: u32 = 0u; // (0u32 << 29)
|
||||
let STANDARD_MATERIAL_FLAGS_ALPHA_MODE_MASK: u32 = 536870912u; // (1u32 << 29)
|
||||
|
|
124
examples/3d/atmospheric_fog.rs
Normal file
124
examples/3d/atmospheric_fog.rs
Normal file
|
@ -0,0 +1,124 @@
|
|||
//! This example showcases atmospheric fog
|
||||
//!
|
||||
//! ## Controls
|
||||
//!
|
||||
//! | Key Binding | Action |
|
||||
//! |:-------------------|:---------------------------------------|
|
||||
//! | `Spacebar` | Toggle Atmospheric Fog |
|
||||
//! | `S` | Toggle Directional Light Fog Influence |
|
||||
|
||||
use bevy::{
|
||||
pbr::{CascadeShadowConfig, NotShadowCaster},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_startup_system(setup_camera_fog)
|
||||
.add_startup_system(setup_terrain_scene)
|
||||
.add_startup_system(setup_instructions)
|
||||
.add_system(toggle_system)
|
||||
.run();
|
||||
}
|
||||
|
||||
fn setup_camera_fog(mut commands: Commands) {
|
||||
commands.spawn((
|
||||
Camera3dBundle {
|
||||
transform: Transform::from_xyz(-1.0, 0.1, 1.0)
|
||||
.looking_at(Vec3::new(0.0, 0.0, 0.0), Vec3::Y),
|
||||
..default()
|
||||
},
|
||||
FogSettings {
|
||||
color: Color::rgba(0.1, 0.2, 0.4, 1.0),
|
||||
directional_light_color: Color::rgba(1.0, 0.95, 0.75, 0.5),
|
||||
directional_light_exponent: 30.0,
|
||||
falloff: FogFalloff::from_visibility_colors(
|
||||
15.0, // distance in world units up to which objects retain visibility (>= 5% contrast)
|
||||
Color::rgb(0.35, 0.5, 0.66), // atmospheric extinction color (after light is lost due to absorption by atmospheric particles)
|
||||
Color::rgb(0.8, 0.844, 1.0), // atmospheric inscattering color (light gained due to scattering from the sun)
|
||||
),
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
fn setup_terrain_scene(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
asset_server: Res<AssetServer>,
|
||||
) {
|
||||
// Configure a properly scaled cascade shadow map for this scene (defaults are too large, mesh units are in km)
|
||||
// For WebGL we only support 1 cascade level for now
|
||||
let cascade_shadow_config =
|
||||
CascadeShadowConfig::new(if cfg!(feature = "webgl") { 1 } else { 4 }, 0.5, 2.5, 0.2);
|
||||
|
||||
// Sun
|
||||
commands.spawn(DirectionalLightBundle {
|
||||
directional_light: DirectionalLight {
|
||||
color: Color::rgb(0.98, 0.95, 0.82),
|
||||
shadows_enabled: true,
|
||||
..default()
|
||||
},
|
||||
transform: Transform::from_xyz(0.0, 0.0, 0.0)
|
||||
.looking_at(Vec3::new(-0.15, -0.05, 0.25), Vec3::Y),
|
||||
cascade_shadow_config,
|
||||
..default()
|
||||
});
|
||||
|
||||
// Terrain
|
||||
commands.spawn(SceneBundle {
|
||||
scene: asset_server.load("models/terrain/Mountains.gltf#Scene0"),
|
||||
..default()
|
||||
});
|
||||
|
||||
// Sky
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Box::default())),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::hex("888888").unwrap(),
|
||||
unlit: true,
|
||||
cull_mode: None,
|
||||
..default()
|
||||
}),
|
||||
transform: Transform::from_scale(Vec3::splat(20.0)),
|
||||
..default()
|
||||
},
|
||||
NotShadowCaster,
|
||||
));
|
||||
}
|
||||
|
||||
fn setup_instructions(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
commands.spawn((TextBundle::from_section(
|
||||
"Press Spacebar to Toggle Atmospheric Fog.\nPress S to Toggle Directional Light Fog Influence.",
|
||||
TextStyle {
|
||||
font: asset_server.load("fonts/FiraMono-Medium.ttf"),
|
||||
font_size: 15.0,
|
||||
color: Color::WHITE,
|
||||
},
|
||||
)
|
||||
.with_style(Style {
|
||||
position_type: PositionType::Absolute,
|
||||
position: UiRect {
|
||||
bottom: Val::Px(10.0),
|
||||
left: Val::Px(10.0),
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
}),));
|
||||
}
|
||||
|
||||
fn toggle_system(keycode: Res<Input<KeyCode>>, mut fog: Query<&mut FogSettings>) {
|
||||
let mut fog_settings = fog.single_mut();
|
||||
|
||||
if keycode.just_pressed(KeyCode::Space) {
|
||||
let a = fog_settings.color.a();
|
||||
fog_settings.color.set_a(1.0 - a);
|
||||
}
|
||||
|
||||
if keycode.just_pressed(KeyCode::S) {
|
||||
let a = fog_settings.directional_light_color.a();
|
||||
fog_settings.directional_light_color.set_a(0.5 - a);
|
||||
}
|
||||
}
|
317
examples/3d/fog.rs
Normal file
317
examples/3d/fog.rs
Normal file
|
@ -0,0 +1,317 @@
|
|||
//! This interactive example shows how to use distance fog,
|
||||
//! and allows playing around with different fog settings.
|
||||
//!
|
||||
//! ## Controls
|
||||
//!
|
||||
//! | Key Binding | Action |
|
||||
//! |:-------------------|:------------------------------------|
|
||||
//! | `1` / `2` / `3` | Fog Falloff Mode |
|
||||
//! | `A` / `S` | Move Start Distance (Linear Fog) |
|
||||
//! | | Change Density (Exponential Fogs) |
|
||||
//! | `Z` / `X` | Move End Distance (Linear Fog) |
|
||||
//! | `-` / `=` | Adjust Fog Red Channel |
|
||||
//! | `[` / `]` | Adjust Fog Green Channel |
|
||||
//! | `;` / `'` | Adjust Fog Blue Channel |
|
||||
//! | `.` / `?` | Adjust Fog Alpha Channel |
|
||||
|
||||
use bevy::{
|
||||
pbr::{NotShadowCaster, NotShadowReceiver},
|
||||
prelude::*,
|
||||
};
|
||||
|
||||
fn main() {
|
||||
App::new()
|
||||
.add_plugins(DefaultPlugins)
|
||||
.add_startup_system(setup_camera_fog)
|
||||
.add_startup_system(setup_pyramid_scene)
|
||||
.add_startup_system(setup_instructions)
|
||||
.add_system(update_system)
|
||||
.run();
|
||||
}
|
||||
|
||||
fn setup_camera_fog(mut commands: Commands) {
|
||||
commands.spawn((
|
||||
Camera3dBundle::default(),
|
||||
FogSettings {
|
||||
color: Color::rgba(0.05, 0.05, 0.05, 1.0),
|
||||
falloff: FogFalloff::Linear {
|
||||
start: 5.0,
|
||||
end: 20.0,
|
||||
},
|
||||
..default()
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
fn setup_pyramid_scene(
|
||||
mut commands: Commands,
|
||||
mut meshes: ResMut<Assets<Mesh>>,
|
||||
mut materials: ResMut<Assets<StandardMaterial>>,
|
||||
) {
|
||||
let stone = materials.add(StandardMaterial {
|
||||
base_color: Color::hex("28221B").unwrap(),
|
||||
perceptual_roughness: 1.0,
|
||||
..default()
|
||||
});
|
||||
|
||||
// pillars
|
||||
for (x, z) in &[(-1.5, -1.5), (1.5, -1.5), (1.5, 1.5), (-1.5, 1.5)] {
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Box {
|
||||
min_x: -0.5,
|
||||
max_x: 0.5,
|
||||
min_z: -0.5,
|
||||
max_z: 0.5,
|
||||
min_y: 0.0,
|
||||
max_y: 3.0,
|
||||
})),
|
||||
material: stone.clone(),
|
||||
transform: Transform::from_xyz(*x, 0.0, *z),
|
||||
..default()
|
||||
});
|
||||
}
|
||||
|
||||
// orb
|
||||
commands.spawn((
|
||||
PbrBundle {
|
||||
mesh: meshes.add(Mesh::try_from(shape::Icosphere::default()).unwrap()),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::hex("126212CC").unwrap(),
|
||||
reflectance: 1.0,
|
||||
perceptual_roughness: 0.0,
|
||||
metallic: 0.5,
|
||||
alpha_mode: AlphaMode::Blend,
|
||||
..default()
|
||||
}),
|
||||
transform: Transform::from_scale(Vec3::splat(1.75))
|
||||
.with_translation(Vec3::new(0.0, 4.0, 0.0)),
|
||||
..default()
|
||||
},
|
||||
NotShadowCaster,
|
||||
NotShadowReceiver,
|
||||
));
|
||||
|
||||
// steps
|
||||
for i in 0..50 {
|
||||
let size = i as f32 / 2.0 + 3.0;
|
||||
let y = -i as f32 / 2.0;
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Box {
|
||||
min_x: -size,
|
||||
max_x: size,
|
||||
min_z: -size,
|
||||
max_z: size,
|
||||
min_y: 0.0,
|
||||
max_y: 0.5,
|
||||
})),
|
||||
material: stone.clone(),
|
||||
transform: Transform::from_xyz(0.0, y, 0.0),
|
||||
..default()
|
||||
});
|
||||
}
|
||||
|
||||
// sky
|
||||
commands.spawn(PbrBundle {
|
||||
mesh: meshes.add(Mesh::from(shape::Box::default())),
|
||||
material: materials.add(StandardMaterial {
|
||||
base_color: Color::hex("888888").unwrap(),
|
||||
unlit: true,
|
||||
cull_mode: None,
|
||||
..default()
|
||||
}),
|
||||
transform: Transform::from_scale(Vec3::splat(1_000_000.0)),
|
||||
..default()
|
||||
});
|
||||
|
||||
// light
|
||||
commands.spawn(PointLightBundle {
|
||||
transform: Transform::from_xyz(0.0, 1.0, 0.0),
|
||||
point_light: PointLight {
|
||||
intensity: 1500.,
|
||||
range: 100.,
|
||||
shadows_enabled: true,
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
});
|
||||
}
|
||||
|
||||
fn setup_instructions(mut commands: Commands, asset_server: Res<AssetServer>) {
|
||||
commands.spawn((TextBundle::from_section(
|
||||
"",
|
||||
TextStyle {
|
||||
font: asset_server.load("fonts/FiraMono-Medium.ttf"),
|
||||
font_size: 15.0,
|
||||
color: Color::WHITE,
|
||||
},
|
||||
)
|
||||
.with_style(Style {
|
||||
position_type: PositionType::Absolute,
|
||||
position: UiRect {
|
||||
top: Val::Px(10.0),
|
||||
left: Val::Px(10.0),
|
||||
..default()
|
||||
},
|
||||
..default()
|
||||
}),));
|
||||
}
|
||||
|
||||
fn update_system(
|
||||
mut camera: Query<(&mut FogSettings, &mut Transform)>,
|
||||
mut text: Query<&mut Text>,
|
||||
time: Res<Time>,
|
||||
keycode: Res<Input<KeyCode>>,
|
||||
) {
|
||||
let now = time.elapsed_seconds();
|
||||
let delta = time.delta_seconds();
|
||||
|
||||
let (mut fog, mut transform) = camera.single_mut();
|
||||
let mut text = text.single_mut();
|
||||
|
||||
// Orbit camera around pyramid
|
||||
let orbit_scale = 8.0 + (now / 10.0).sin() * 7.0;
|
||||
*transform = Transform::from_xyz(
|
||||
(now / 5.0).cos() * orbit_scale,
|
||||
12.0 - orbit_scale / 2.0,
|
||||
(now / 5.0).sin() * orbit_scale,
|
||||
)
|
||||
.looking_at(Vec3::ZERO, Vec3::Y);
|
||||
|
||||
// Fog Information
|
||||
text.sections[0].value = format!("Fog Falloff: {:?}\nFog Color: {:?}", fog.falloff, fog.color);
|
||||
|
||||
// Fog Falloff Mode Switching
|
||||
text.sections[0]
|
||||
.value
|
||||
.push_str("\n\n1 / 2 / 3 - Fog Falloff Mode");
|
||||
|
||||
if keycode.pressed(KeyCode::Key1) {
|
||||
if let FogFalloff::Linear { .. } = fog.falloff {
|
||||
// No change
|
||||
} else {
|
||||
fog.falloff = FogFalloff::Linear {
|
||||
start: 5.0,
|
||||
end: 20.0,
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
if keycode.pressed(KeyCode::Key2) {
|
||||
if let FogFalloff::Exponential { .. } = fog.falloff {
|
||||
// No change
|
||||
} else if let FogFalloff::ExponentialSquared { density } = fog.falloff {
|
||||
fog.falloff = FogFalloff::Exponential { density };
|
||||
} else {
|
||||
fog.falloff = FogFalloff::Exponential { density: 0.07 };
|
||||
};
|
||||
}
|
||||
|
||||
if keycode.pressed(KeyCode::Key3) {
|
||||
if let FogFalloff::Exponential { density } = fog.falloff {
|
||||
fog.falloff = FogFalloff::ExponentialSquared { density };
|
||||
} else if let FogFalloff::ExponentialSquared { .. } = fog.falloff {
|
||||
// No change
|
||||
} else {
|
||||
fog.falloff = FogFalloff::Exponential { density: 0.07 };
|
||||
};
|
||||
}
|
||||
|
||||
// Linear Fog Controls
|
||||
if let FogFalloff::Linear {
|
||||
ref mut start,
|
||||
ref mut end,
|
||||
} = &mut fog.falloff
|
||||
{
|
||||
text.sections[0]
|
||||
.value
|
||||
.push_str("\nA / S - Move Start Distance\nZ / X - Move End Distance");
|
||||
|
||||
if keycode.pressed(KeyCode::A) {
|
||||
*start -= delta * 3.0;
|
||||
}
|
||||
if keycode.pressed(KeyCode::S) {
|
||||
*start += delta * 3.0;
|
||||
}
|
||||
if keycode.pressed(KeyCode::Z) {
|
||||
*end -= delta * 3.0;
|
||||
}
|
||||
if keycode.pressed(KeyCode::X) {
|
||||
*end += delta * 3.0;
|
||||
}
|
||||
}
|
||||
|
||||
// Exponential Fog Controls
|
||||
if let FogFalloff::Exponential { ref mut density } = &mut fog.falloff {
|
||||
text.sections[0].value.push_str("\nA / S - Change Density");
|
||||
|
||||
if keycode.pressed(KeyCode::A) {
|
||||
*density -= delta * 0.5 * *density;
|
||||
if *density < 0.0 {
|
||||
*density = 0.0;
|
||||
}
|
||||
}
|
||||
if keycode.pressed(KeyCode::S) {
|
||||
*density += delta * 0.5 * *density;
|
||||
}
|
||||
}
|
||||
|
||||
// ExponentialSquared Fog Controls
|
||||
if let FogFalloff::ExponentialSquared { ref mut density } = &mut fog.falloff {
|
||||
text.sections[0].value.push_str("\nA / S - Change Density");
|
||||
|
||||
if keycode.pressed(KeyCode::A) {
|
||||
*density -= delta * 0.5 * *density;
|
||||
if *density < 0.0 {
|
||||
*density = 0.0;
|
||||
}
|
||||
}
|
||||
if keycode.pressed(KeyCode::S) {
|
||||
*density += delta * 0.5 * *density;
|
||||
}
|
||||
}
|
||||
|
||||
// RGBA Controls
|
||||
text.sections[0]
|
||||
.value
|
||||
.push_str("\n\n- / = - Red\n[ / ] - Green\n; / ' - Blue\n. / ? - Alpha");
|
||||
|
||||
if keycode.pressed(KeyCode::Minus) {
|
||||
let r = (fog.color.r() - 0.1 * delta).max(0.0);
|
||||
fog.color.set_r(r);
|
||||
}
|
||||
|
||||
if keycode.pressed(KeyCode::Equals) {
|
||||
let r = (fog.color.r() + 0.1 * delta).min(1.0);
|
||||
fog.color.set_r(r);
|
||||
}
|
||||
|
||||
if keycode.pressed(KeyCode::LBracket) {
|
||||
let g = (fog.color.g() - 0.1 * delta).max(0.0);
|
||||
fog.color.set_g(g);
|
||||
}
|
||||
|
||||
if keycode.pressed(KeyCode::RBracket) {
|
||||
let g = (fog.color.g() + 0.1 * delta).min(1.0);
|
||||
fog.color.set_g(g);
|
||||
}
|
||||
|
||||
if keycode.pressed(KeyCode::Semicolon) {
|
||||
let b = (fog.color.b() - 0.1 * delta).max(0.0);
|
||||
fog.color.set_b(b);
|
||||
}
|
||||
|
||||
if keycode.pressed(KeyCode::Apostrophe) {
|
||||
let b = (fog.color.b() + 0.1 * delta).min(1.0);
|
||||
fog.color.set_b(b);
|
||||
}
|
||||
|
||||
if keycode.pressed(KeyCode::Period) {
|
||||
let a = (fog.color.a() - 0.1 * delta).max(0.0);
|
||||
fog.color.set_a(a);
|
||||
}
|
||||
|
||||
if keycode.pressed(KeyCode::Slash) {
|
||||
let a = (fog.color.a() + 0.1 * delta).min(1.0);
|
||||
fog.color.set_a(a);
|
||||
}
|
||||
}
|
|
@ -107,9 +107,11 @@ Example | Description
|
|||
--- | ---
|
||||
[3D Scene](../examples/3d/3d_scene.rs) | Simple 3D scene with basic shapes and lighting
|
||||
[3D Shapes](../examples/3d/3d_shapes.rs) | A scene showcasing the built-in 3D shapes
|
||||
[Atmospheric Fog](../examples/3d/atmospheric_fog.rs) | A scene showcasing the atmospheric fog effect
|
||||
[Blend Modes](../examples/3d/blend_modes.rs) | Showcases different blend modes
|
||||
[Bloom](../examples/3d/bloom.rs) | Illustrates bloom configuration using HDR and emissive materials
|
||||
[FXAA](../examples/3d/fxaa.rs) | Compares MSAA (Multi-Sample Anti-Aliasing) and FXAA (Fast Approximate Anti-Aliasing)
|
||||
[Fog](../examples/3d/fog.rs) | A scene showcasing the distance fog effect
|
||||
[Lighting](../examples/3d/lighting.rs) | Illustrates various lighting options in a simple scene
|
||||
[Lines](../examples/3d/lines.rs) | Create a custom material to draw 3d lines
|
||||
[Load glTF](../examples/3d/load_gltf.rs) | Loads and renders a glTF file as a scene
|
||||
|
|
Loading…
Reference in a new issue