diff options
| -rw-r--r-- | adapters/fparkan-render-vulkan/shaders/triangle.vert | 9 | ||||
| -rw-r--r-- | adapters/fparkan-render-vulkan/shaders/triangle.vert.spv | bin | 20096 -> 20196 bytes | |||
| -rw-r--r-- | adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs | 40 | ||||
| -rw-r--r-- | apps/fparkan-game/src/main.rs | 7 | ||||
| -rw-r--r-- | docs/appendices/knowledge-boundaries.md | 4 | ||||
| -rw-r--r-- | docs/reference/render-frame.md | 20 | ||||
| -rw-r--r-- | docs/tomes/05-render.md | 3 |
7 files changed, 75 insertions, 8 deletions
diff --git a/adapters/fparkan-render-vulkan/shaders/triangle.vert b/adapters/fparkan-render-vulkan/shaders/triangle.vert index d1e2e04..078a0d1 100644 --- a/adapters/fparkan-render-vulkan/shaders/triangle.vert +++ b/adapters/fparkan-render-vulkan/shaders/triangle.vert @@ -130,12 +130,13 @@ float native_specular_power(float cosine, float power) { } float native_fog_factor(vec3 world_position) { - float fog_span = frame.fog_distances.y - frame.fog_distances.x; - float distance_to_camera = distance(world_position, frame.camera_position.xyz); - if (fog_span <= 0.000001 || distance_to_camera <= frame.fog_distances.x) { + float fog_span_squared = frame.fog_distances.w - frame.fog_distances.z; + vec3 to_camera = world_position - frame.camera_position.xyz; + float distance_squared = dot(to_camera, to_camera); + if (fog_span_squared <= 0.000001 || distance_squared <= frame.fog_distances.z) { return 0.0; } - return clamp((distance_to_camera - frame.fog_distances.x) / fog_span, 0.0, 1.0); + return clamp((distance_squared - frame.fog_distances.z) / fog_span_squared, 0.0, 1.0); } vec3 normalized_or(vec3 value, vec3 fallback) { diff --git a/adapters/fparkan-render-vulkan/shaders/triangle.vert.spv b/adapters/fparkan-render-vulkan/shaders/triangle.vert.spv Binary files differindex f74b1f8..4fc9a1b 100644 --- a/adapters/fparkan-render-vulkan/shaders/triangle.vert.spv +++ b/adapters/fparkan-render-vulkan/shaders/triangle.vert.spv diff --git a/adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs b/adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs index 278243b..0996fa1 100644 --- a/adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs +++ b/adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs @@ -284,6 +284,9 @@ impl VulkanFrameUniforms { .chain([self.fog_start, self.fog_end]) .chain(self.camera_position) .all(f32::is_finite) + && (self.fog_end <= self.fog_start + || ((self.fog_start * self.fog_start).is_finite() + && (self.fog_end * self.fog_end).is_finite())) } /// Serializes one frame slot in the exact `std140` member order. @@ -344,7 +347,17 @@ impl VulkanFrameUniforms { self.fog_color[2], 0.0, ]); - values[84..88].copy_from_slice(&[self.fog_start, self.fog_end, 0.0, 0.0]); + let squared_fog_range = if self.fog_end > self.fog_start { + [self.fog_start * self.fog_start, self.fog_end * self.fog_end] + } else { + [0.0; 2] + }; + values[84..88].copy_from_slice(&[ + self.fog_start, + self.fog_end, + squared_fog_range[0], + squared_fog_range[1], + ]); values[88..92].copy_from_slice(&[ self.camera_position[0], self.camera_position[1], @@ -875,6 +888,31 @@ mod static_mesh_tests { } #[test] + fn fog_uniforms_match_live_native_squared_distance_range() { + // Terrain ShadeIndexedStrided: far=0x442389e6, far²=0x48d0f1ca; + // distance²=0x47707b04 gives visibility=0x3f5b2b94. + let frame = VulkanFrameUniforms { + fog_start: 0.0, + fog_end: f32::from_bits(0x4423_89e6), + ..Default::default() + }; + assert!(frame.is_finite()); + let bytes = frame.to_ne_bytes(); + let read = |offset| f32::from_ne_bytes(bytes[offset..offset + 4].try_into().unwrap()); + assert_eq!(read(344), 0.0); + assert_eq!(read(348).to_bits(), 0x48d0_f1ca); + let visibility = 1.0 - (f32::from_bits(0x4770_7b04) - read(344)) / (read(348) - read(344)); + assert!((visibility - f32::from_bits(0x3f5b_2b94)).abs() < 1.0e-6); + assert!(VulkanFrameUniforms::default().is_finite()); + assert!(!VulkanFrameUniforms { + fog_start: 0.0, + fog_end: f32::MAX, + ..frame + } + .is_finite()); + } + + #[test] fn static_camera_composes_recovered_d3d7_view_before_projection() { let transform = RawCameraTransform { words: [ diff --git a/apps/fparkan-game/src/main.rs b/apps/fparkan-game/src/main.rs index 30380ed..279fa2c 100644 --- a/apps/fparkan-game/src/main.rs +++ b/apps/fparkan-game/src/main.rs @@ -9454,6 +9454,7 @@ impl StaticVulkanApp { }) .count(); eprintln!("render_coverage control_op1_jitter_unsupported={randomized_lights} (using authored color and radius; native RNG sampling pending)"); + eprintln!("render_coverage fog_pass_eligibility_approx={} (global world fog and horizon RGB; native shade-state/color verification pending)", self.world_range_indices.len()); for &range_index in &self.world_range_indices { let range = self .mesh @@ -14499,7 +14500,7 @@ mod tests { } #[test] - fn captured_native_camera_uses_view_dependent_sky_fog_color() { + fn captured_native_camera_selects_view_dependent_screen_horizon_color() { use fparkan_fx::atmosphere::{PackedColor, SkySample, SKY_COLOR_COUNT}; // Compact camera fields from the native 1493.337-second AutoDemo capture. @@ -14521,12 +14522,12 @@ mod tests { values: [0.0; 2], packed: [PackedColor(0); 2], }; - let fog_color = + let horizon_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)); + assert_eq!(horizon_color, PackedColor(0xFF3E_3E64)); } #[test] diff --git a/docs/appendices/knowledge-boundaries.md b/docs/appendices/knowledge-boundaries.md index 6fbedf4..7f049fc 100644 --- a/docs/appendices/knowledge-boundaries.md +++ b/docs/appendices/knowledge-boundaries.md @@ -10,6 +10,10 @@ authoring API без динамического подтверждения. Не доказаны символами точные имена renderer vtable slots, полный набор CShade state transitions и окончательный порядок части transparent/FX/shadow subpasses. +Дистанционная кривая тумана по квадратам границ подтверждена в живом CShade. +Выбор проходов по native shade-state и GPU fog color пока заменён общим туманом мира с цветом горизонта; +это приближение считается как `render_coverage fog_pass_eligibility_approx`. + После ограничения цели рельефом интерактивная камера проверяет движение против каждого отрисовываемого корневого строения класса `FORT` и его отрисовываемых компонентов. Для этого пути используются текущая Node38-поза diff --git a/docs/reference/render-frame.md b/docs/reference/render-frame.md index 3244d1b..b2a6a93 100644 --- a/docs/reference/render-frame.md +++ b/docs/reference/render-frame.md @@ -71,6 +71,26 @@ RVA `0x1B0F0`, заменяет diffuse alpha пакета без type 16 с bat путь и ещё не декодирован. Это ограничивает приёмку на частично отсечённых порталах и не позволяет считать интерьер принятой эталонной сценой. +## Дистанционная кривая тумана + +Программный `CShade::ShadeIndexedStrided` в GOG Terrain.dll сравнивает квадрат +дистанции с квадратами ближней и дальней границ. По RVA `0x2EEED–0x2EF45` +видимость равна `1 - (distance² - near²) / (far² - near²)` между границами, +единице перед ближней и нулю за дальней. Такой же закон использует отдельный +путь цвета пакета `0x2F230`. Линейная интерполяция по самой дистанции давала +слишком плотный туман в середине диапазона. + +В работающем оригинале границы 0 и `0x442389E6`, квадрат дистанции +`0x47707B04` дали видимость `0x3F5B2B94`. Rust-тест проверяет передаваемые GPU +квадраты границ по этому наблюдению. GLSL вычисляет квадрат расстояния вершины, +после чего растеризатор интерполирует коэффициент по треугольнику. +`native_fog.effective.sink_rgb` совпадает с `sky.packed[1]`, нижней границей +освещения CShade. Это не подтверждает значение GPU fog color. Приложение пока +сохраняет цвет экранного горизонта; выбор цвета и проходов требует отдельной +проверки GPU-состояния. +Выбор проходов по native shade-state пока приближён общим туманом мира и +считается как `render_coverage fog_pass_eligibility_approx`. + ## Радиус Control Op1 GOG `Effect.dll` (SHA-256 diff --git a/docs/tomes/05-render.md b/docs/tomes/05-render.md index bd23875..72b0685 100644 --- a/docs/tomes/05-render.md +++ b/docs/tomes/05-render.md @@ -845,6 +845,9 @@ projected shadows. Радиус источника масштабируется синий свет завода почти незаметным. Неположительный итог заменяется на 0,01. Случайные добавки к цвету и радиусу пока явно считаются в `render_coverage control_op1_jitter_unsupported`. +Туман также зависит от расстояния нелинейно: между ближней и дальней границами +движок интерполирует по квадрату дистанции. При границах 0 и 700 точка в 350 +получает четверть тумана, а не половину. Это сохраняет контраст среднего плана. Effect renderer подавляет header bit `0x800` только при render selector 0. Native AniMesh component path захвачен с selector 1, а в его 53-entry manager присутствуют FX entries с header `0xD00`; preview |
