1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
#![cfg(feature = "effects")]
/// Style controls for the fullscreen fog background pass.
///
/// These values are intentionally artistic rather than physical:
/// they shape how the sky / horizon / lower background blend with `FogSettings::color`.
///
/// Use these when you want to push the fog toward a mood such as
/// "morning mist" or "dense atmosphere" without changing how fog accumulates in space.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FogBackgroundSettings {
pub zenith_color: [f32; 3],
pub zenith_mix: f32,
pub horizon_color: [f32; 3],
pub horizon_mix: f32,
pub nadir_color: [f32; 3],
pub nadir_mix: f32,
pub horizon_glow_strength: f32,
pub sky_fog_blend: f32,
pub geometry_fog_blend: f32,
}
impl Default for FogBackgroundSettings {
fn default() -> Self {
Self {
zenith_color: [0.27, 0.38, 0.52],
zenith_mix: 0.38,
horizon_color: [0.75, 0.79, 0.80],
horizon_mix: 0.32,
nadir_color: [0.52, 0.56, 0.55],
nadir_mix: 0.18,
horizon_glow_strength: 0.05,
sky_fog_blend: 0.72,
geometry_fog_blend: 0.55,
}
}
}
impl FogBackgroundSettings {
/// A light, airy preset intended for clearer "morning mist" scenes.
pub fn morning_mist() -> Self {
Self::default()
}
/// A heavier preset intended for denser, moodier atmospheric scenes.
pub fn dense_atmosphere() -> Self {
Self {
zenith_color: [0.18, 0.24, 0.32],
zenith_mix: 0.50,
horizon_color: [0.68, 0.66, 0.62],
horizon_mix: 0.45,
nadir_color: [0.44, 0.42, 0.40],
nadir_mix: 0.28,
horizon_glow_strength: 0.03,
sky_fog_blend: 0.84,
geometry_fog_blend: 0.72,
}
}
/// Sets the upper-sky tint and its blend amount against the main fog color.
pub fn with_zenith(mut self, color: [f32; 3], mix: f32) -> Self {
self.zenith_color = color;
self.zenith_mix = mix;
self
}
/// Sets the horizon tint and its blend amount against the main fog color.
pub fn with_horizon(mut self, color: [f32; 3], mix: f32) -> Self {
self.horizon_color = color;
self.horizon_mix = mix;
self
}
/// Sets the lower-background tint and its blend amount against the main fog color.
pub fn with_nadir(mut self, color: [f32; 3], mix: f32) -> Self {
self.nadir_color = color;
self.nadir_mix = mix;
self
}
/// Adjusts the soft brightness boost around the horizon line.
pub fn with_horizon_glow(mut self, strength: f32) -> Self {
self.horizon_glow_strength = strength;
self
}
/// Controls how strongly the background and fogged geometry lean toward the fog color.
pub fn with_blend(mut self, sky_fog_blend: f32, geometry_fog_blend: f32) -> Self {
self.sky_fog_blend = sky_fog_blend;
self.geometry_fog_blend = geometry_fog_blend;
self
}
}
/// Quality / cost controls for the fullscreen fog pass.
///
/// These are not art-direction knobs. They tune sampling behavior and therefore
/// affect performance, stability, and how closely the height fog approximates
/// volumetric integration.
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FogSamplingSettings {
pub min_height_samples: u32,
pub max_height_samples: u32,
pub height_sample_scale: f32,
}
impl Default for FogSamplingSettings {
fn default() -> Self {
Self {
min_height_samples: 4,
max_height_samples: 10,
height_sample_scale: 0.6,
}
}
}
impl FogSamplingSettings {
/// Sets the minimum and maximum sample count used by height fog integration.
pub fn with_height_samples(mut self, min_height_samples: u32, max_height_samples: u32) -> Self {
self.min_height_samples = min_height_samples;
self.max_height_samples = max_height_samples;
self
}
/// Controls how quickly sample count grows with travel distance.
pub fn with_height_sample_scale(mut self, height_sample_scale: f32) -> Self {
self.height_sample_scale = height_sample_scale;
self
}
}
/// Engine-level fog configuration.
///
/// `FogSettings` is split into three conceptual groups:
///
/// Physical controls:
/// - `with_strength`: overall fog intensity multiplier
/// - `with_distance`: how fog accumulates with camera-to-fragment distance
/// - `with_height`: how fog accumulates relative to world height
///
/// Style controls:
/// - `with_color`: the base fog color used for blending
/// - `with_background`: background / sky styling for the fullscreen fog pass
///
/// Quality controls:
/// - `with_sampling`: height-fog sampling quality and performance tradeoffs
///
/// Fog is disabled by default. It only becomes active once both of these are true:
/// - effective strength is greater than `0.0`
/// - either distance fog density or height fog density is greater than `0.0`
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct FogSettings {
pub color: [f32; 4],
pub strength: Option<f32>,
pub background: FogBackgroundSettings,
pub sampling: FogSamplingSettings,
pub distance_start: f32,
pub distance_end: f32,
pub distance_exponent: f32,
pub distance_density: f32,
pub height_base: f32,
pub height_falloff: f32,
pub height_exponent: f32,
pub height_density: f32,
}
impl Default for FogSettings {
fn default() -> Self {
Self {
color: [0.0, 0.0, 0.0, 0.0],
strength: None,
background: FogBackgroundSettings::default(),
sampling: FogSamplingSettings::default(),
distance_start: 0.0,
distance_end: 1.0,
distance_exponent: 1.0,
distance_density: 0.0,
height_base: 0.0,
height_falloff: 1.0,
height_exponent: 1.0,
height_density: 0.0,
}
}
}
impl FogSettings {
/// Returns an explicitly disabled fog configuration.
pub fn disabled() -> Self {
Self::default()
}
/// Sets the base fog color.
///
/// For backward compatibility, `color[3]` is still used as the fog strength
/// when `with_strength(...)` is not provided.
pub fn with_color(mut self, color: [f32; 4]) -> Self {
self.color = color;
self
}
/// Sets the overall fog intensity multiplier.
pub fn with_strength(mut self, strength: f32) -> Self {
self.strength = Some(strength);
self
}
/// Applies artistic background styling for the fullscreen fog pass.
pub fn with_background(mut self, background: FogBackgroundSettings) -> Self {
self.background = background;
self
}
/// Applies quality / performance controls for height fog integration.
pub fn with_sampling(mut self, sampling: FogSamplingSettings) -> Self {
self.sampling = sampling;
self
}
/// Configures distance-based fog accumulation.
///
/// Parameters:
/// - `start`: distance where fog begins to ramp in
/// - `end`: distance where the main ramp reaches full effect
/// - `density`: contribution of the distance fog term
/// - `exponent`: shape of the distance ramp
pub fn with_distance(mut self, start: f32, end: f32, density: f32, exponent: f32) -> Self {
self.distance_start = start;
self.distance_end = end;
self.distance_density = density;
self.distance_exponent = exponent;
self
}
/// Configures height-based fog accumulation.
///
/// Parameters:
/// - `base`: world-space height where the fog layer starts
/// - `falloff`: vertical range over which the fog fades out
/// - `density`: contribution of the height fog term
/// - `exponent`: shape of the vertical density curve
pub fn with_height(mut self, base: f32, falloff: f32, density: f32, exponent: f32) -> Self {
self.height_base = base;
self.height_falloff = falloff;
self.height_density = density;
self.height_exponent = exponent;
self
}
#[cfg_attr(not(feature = "model-3d"), allow(dead_code))]
pub(crate) fn effective_strength(&self) -> f32 {
self.strength.unwrap_or(self.color[3])
}
#[cfg_attr(not(feature = "model-3d"), allow(dead_code))]
pub(crate) fn is_enabled(&self) -> bool {
self.effective_strength() > 0.0
&& (self.distance_density > 0.0 || self.height_density > 0.0)
}
}
#[cfg(test)]
mod tests {
use super::FogSettings;
#[test]
fn fog_is_disabled_by_default() {
let fog = FogSettings::default();
assert!(!fog.is_enabled());
assert_eq!(fog.effective_strength(), 0.0);
}
#[test]
fn fog_requires_strength_and_density() {
let no_density = FogSettings::default().with_strength(1.0);
assert!(!no_density.is_enabled());
let no_strength = FogSettings::default().with_distance(1.0, 10.0, 1.0, 1.0);
assert!(!no_strength.is_enabled());
let enabled = FogSettings::default()
.with_strength(1.0)
.with_distance(1.0, 10.0, 1.0, 1.0);
assert!(enabled.is_enabled());
}
}