diff options
| author | Valentin Popov <valentin@popov.link> | 2026-10-19 21:43:16 +0300 |
|---|---|---|
| committer | Valentin Popov <valentin@popov.link> | 2026-10-19 21:43:16 +0300 |
| commit | 84ebd259ea552e278173db5d842ae16a929411ff (patch) | |
| tree | 20bb3a11ea49cb20875fd8efccd77bad8c61227e | |
| parent | cde92e17ab37f93ddf0146c293e0ead9f979e573 (diff) | |
| download | fparkan-84ebd259ea552e278173db5d842ae16a929411ff.tar.xz fparkan-84ebd259ea552e278173db5d842ae16a929411ff.zip | |
fix(render): use view-dependent sky fog color
| -rw-r--r-- | apps/fparkan-game/src/main.rs | 37 | ||||
| -rw-r--r-- | crates/fparkan-fx/src/sky.rs | 24 | ||||
| -rw-r--r-- | docs/reference/environment-effects.md | 7 |
3 files changed, 55 insertions, 13 deletions
diff --git a/apps/fparkan-game/src/main.rs b/apps/fparkan-game/src/main.rs index c94778d..49e40c0 100644 --- a/apps/fparkan-game/src/main.rs +++ b/apps/fparkan-game/src/main.rs @@ -3468,7 +3468,11 @@ impl DynamicEnvironment { target.active = source.active; } uniforms.lighting_floor = sky_frame.lighting_floor; - uniforms.fog_color = sky_frame.fog.color; + let fog_rgb = sky_frame + .screen_gradient(camera.native_sky_heading(), [0.0; 3]) + .color + .rgba(); + uniforms.fog_color = [fog_rgb[0], fog_rgb[1], fog_rgb[2]]; uniforms.fog_start = sky_frame.fog.start.max(0.0); uniforms.fog_end = sky_frame.fog.end.max(uniforms.fog_start); uniforms.camera_position = camera.position; @@ -7902,6 +7906,37 @@ mod tests { } #[test] + fn captured_native_camera_uses_view_dependent_sky_fog_color() { + use fparkan_fx::atmosphere::{PackedColor, SkySample, SKY_COLOR_COUNT}; + + // Compact camera fields from the native 1493.337-second AutoDemo capture. + let capture = parse_legacy_camera_capture( + br#"{"schema":"fparkan-legacy-camera-v1","render_input_usable":true,"parented_camera":true,"effective_world_matrix_space":"world","effective_world_matrix_words":[1052770830,1064129158,991881820,1141874407,3211610778,1052772554,3144770972,1145115441,3146624790,3127057462,1065352580,1087889619,0,0,0,1065353216],"selector0_words":[1063123992,3204380634,996787387,3130169600,1056894806,1063125889,990301923,3129510813,3146181515,3070164857,1065352633,1080608413,0,0,0,1065353216],"viewport":[0,0,1280,1024],"near_plane":5.0,"far_plane":700.0,"field_of_view_radians":1.3,"atmosphere_seconds":1493.337}"#, + ) + .expect("valid native camera fixture"); + let camera = capture.preview_camera_frame().expect("valid camera frame"); + + let mut colors = [PackedColor(0xFF00_0000); SKY_COLOR_COUNT]; + colors[..4].copy_from_slice(&[ + PackedColor(0xFF38_3864), + PackedColor(0xFF38_3864), + PackedColor(0xFF40_4064), + PackedColor(0xFF38_3864), + ]); + let sample = SkySample { + colors, + values: [0.0; 2], + packed: [PackedColor(0); 2], + }; + let fog_color = + fparkan_fx::sky::screen_gradient_frame(&sample, camera.native_sky_heading(), [0.0; 3]) + .color; + + assert_eq!(capture.atmosphere_seconds, Some(1493.337)); + assert_eq!(fog_color, PackedColor(0xFF3E_3E64)); + } + + #[test] fn sky_normals_keep_native_signed_byte_scale_without_renormalizing() { assert_eq!(sky_normal([-128, 127, -64]), [-1.0, 127.0 / 128.0, -0.5]); } diff --git a/crates/fparkan-fx/src/sky.rs b/crates/fparkan-fx/src/sky.rs index f41e920..26e68ab 100644 --- a/crates/fparkan-fx/src/sky.rs +++ b/crates/fparkan-fx/src/sky.rs @@ -957,12 +957,14 @@ impl fmt::Display for SkyUpdateError { impl std::error::Error for SkyUpdateError {} -/// Fog values sent by the original `CSky` update. +/// Distance and ambient-light values derived from an original `CSky` sample. +/// +/// The renderer's `D3DRS_FOGCOLOR` is view-dependent: it is interpolated from +/// `SkySample::colors[0..4]` using the native sky heading. It is not this +/// sample-only frame value. #[derive(Clone, Copy, Debug, PartialEq)] pub struct FogFrame { - /// Normalized RGB fog color from `SkySample::packed[1]`. - pub color: [f32; 3], - /// Native global lighting floor from the same sampled RGB state. + /// Native global lighting floor from `SkySample::packed[1]`. /// /// `CShade` merges this vector with the material lighting accumulator by /// taking the per-channel maximum before the shader receives the result. @@ -980,10 +982,8 @@ impl FogFrame { let start = finite_or_zero(sample.values[0]) * FOG_DISTANCE_SCALE; let end = finite_or_zero(sample.values[1]) * FOG_DISTANCE_SCALE; let rgba = sample.packed[1].rgba(); - let color = [rgba[0], rgba[1], rgba[2]]; Self { - color, - lighting_floor: color, + lighting_floor: [rgba[0], rgba[1], rgba[2]], start, end, } @@ -1845,12 +1845,12 @@ mod tests { } #[test] - fn fog_uses_original_scale_and_packed_rgb() { + fn fog_distances_use_original_scale_and_floor_uses_packed_rgb() { let frame = fog_frame(&sample()); assert_eq!(frame.start, 700.0); assert_eq!(frame.end, 1_400.0); assert_eq!( - frame.color, + frame.lighting_floor, [ 0x11 as f32 / 255.0, 0x22 as f32 / 255.0, @@ -1945,11 +1945,10 @@ mod tests { let mut atmosphere = atmosphere(true); atmosphere.sky.colors[12] = PackedColor(0xFF12_3456); - let (lighting_floor, fog_color, sprites_have_native_uv) = { + let (lighting_floor, sprites_have_native_uv) = { let frame = system.update(&atmosphere, 10.0).expect("sky update"); ( frame.lighting_floor, - frame.fog.color, frame .sprites .iter() @@ -1969,7 +1968,8 @@ mod tests { positions ); assert_eq!(system.mesh().vertices[0].color, PackedColor(0xFF12_3456)); - assert_eq!(lighting_floor, fog_color); + let packed = atmosphere.sky.packed[1].rgba(); + assert_eq!(lighting_floor, [packed[0], packed[1], packed[2]]); assert!(sprites_have_native_uv); } diff --git a/docs/reference/environment-effects.md b/docs/reference/environment-effects.md index 0435a6a..3267a2f 100644 --- a/docs/reference/environment-effects.md +++ b/docs/reference/environment-effects.md @@ -20,6 +20,13 @@ sky.wea backend разрешает архив и имя звука. Это позволяет одной и той же модели погоды работать в preview и в игровом цикле. +Цвета неба, ambient lighting и fog renderer-а используют разные поля того же +`sky.ske` sample. `SkySample::packed[1]` задаёт глобальный lighting floor; +`CShade` объединяет его с material lighting по поканальному максимуму. +`D3DRS_FOGCOLOR` (`0x22`) зависит от направления камеры и интерполируется из +`SkySample::colors[0..4]` по native sky heading. Значения `sky.values[0..1]` +продолжают задавать fog start/end, масштабируемые на 700. + ## Входы и границы Для расписания используется такой вызов: |
