bevy/crates/bevy_pbr/src/render/fog.rs
Rafał Harabień 51c70bc98c
Fix fog color being inaccurate (#10226)
# Objective

Fog color was passed to shaders without conversion from sRGB to linear
color space. Because shaders expect colors in linear space this resulted
in wrong color being used. This is most noticeable in open scenes with
dark fog color and clear color set to the same color. In such case
background/clear color (which is properly processed) is going to be
darker than very far objects.

Example:

![image](https://github.com/bevyengine/bevy/assets/160391/89b70d97-b2d0-4bc5-80f4-c9e8b8801c4c)

[bevy-fog-color-bug.zip](https://github.com/bevyengine/bevy/files/13063718/bevy-fog-color-bug.zip)

## Solution

Add missing conversion of fog color to linear color space.

---

## Changelog

* Fixed conversion of fog color

## Migration Guide

- Colors in `FogSettings` struct (`color` and `directional_light_color`)
are now sent to the GPU in linear space. If you were using
`Color::rgb()`/`Color::rgba()` and would like to retain the previous
colors, you can quickly fix it by switching to
`Color::rgb_linear()`/`Color::rgba_linear()`.
2023-10-23 12:45:18 +00:00

156 lines
5.8 KiB
Rust

use bevy_app::{App, Plugin};
use bevy_asset::{load_internal_asset, Handle};
use bevy_ecs::prelude::*;
use bevy_math::{Vec3, Vec4};
use bevy_render::{
extract_component::ExtractComponentPlugin,
render_resource::{DynamicUniformBuffer, Shader, ShaderType},
renderer::{RenderDevice, RenderQueue},
view::ExtractedView,
Render, RenderApp, RenderSet,
};
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>>,
) {
let views_iter = views.iter();
let view_count = views_iter.len();
let Some(mut writer) = fog_meta
.gpu_fogs
.get_writer(view_count, &render_device, &render_queue)
else {
return;
};
for (entity, fog) in views_iter {
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.as_linear_rgba_f32().into(),
directional_light_color: fog
.directional_light_color
.as_linear_rgba_f32()
.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.as_linear_rgba_f32().into(),
directional_light_color: fog
.directional_light_color
.as_linear_rgba_f32()
.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.as_linear_rgba_f32().into(),
directional_light_color: fog
.directional_light_color
.as_linear_rgba_f32()
.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.as_linear_rgba_f32().into(),
directional_light_color: fog
.directional_light_color
.as_linear_rgba_f32()
.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: writer.write(&gpu_fog),
});
}
}
/// 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: Handle<Shader> = Handle::weak_from_u128(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.register_type::<FogSettings>();
app.add_plugins(ExtractComponentPlugin::<FogSettings>::default());
if let Ok(render_app) = app.get_sub_app_mut(RenderApp) {
render_app
.init_resource::<FogMeta>()
.add_systems(Render, prepare_fog.in_set(RenderSet::PrepareResources));
}
}
}