fix(render): use native squared distance for vertex fog
Docs Deploy / Build and Deploy MkDocs (push) Successful in 36s
Docs Deploy / Build and Deploy MkDocs (push) Successful in 36s
This commit is contained in:
7 files changed
+75
-8
No files matched your search
@@ -130,12 +130,13 @@ float native_specular_power(float cosine, float power) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
float native_fog_factor(vec3 world_position) {
|
float native_fog_factor(vec3 world_position) {
|
||||||
float fog_span = frame.fog_distances.y - frame.fog_distances.x;
|
float fog_span_squared = frame.fog_distances.w - frame.fog_distances.z;
|
||||||
float distance_to_camera = distance(world_position, frame.camera_position.xyz);
|
vec3 to_camera = world_position - frame.camera_position.xyz;
|
||||||
if (fog_span <= 0.000001 || distance_to_camera <= frame.fog_distances.x) {
|
float distance_squared = dot(to_camera, to_camera);
|
||||||
|
if (fog_span_squared <= 0.000001 || distance_squared <= frame.fog_distances.z) {
|
||||||
return 0.0;
|
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) {
|
vec3 normalized_or(vec3 value, vec3 fallback) {
|
||||||
|
|||||||
Binary file not shown.
@@ -284,6 +284,9 @@ impl VulkanFrameUniforms {
|
|||||||
.chain([self.fog_start, self.fog_end])
|
.chain([self.fog_start, self.fog_end])
|
||||||
.chain(self.camera_position)
|
.chain(self.camera_position)
|
||||||
.all(f32::is_finite)
|
.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.
|
/// Serializes one frame slot in the exact `std140` member order.
|
||||||
@@ -344,7 +347,17 @@ impl VulkanFrameUniforms {
|
|||||||
self.fog_color[2],
|
self.fog_color[2],
|
||||||
0.0,
|
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(&[
|
values[88..92].copy_from_slice(&[
|
||||||
self.camera_position[0],
|
self.camera_position[0],
|
||||||
self.camera_position[1],
|
self.camera_position[1],
|
||||||
@@ -874,6 +887,31 @@ mod static_mesh_tests {
|
|||||||
assert_eq!(mesh.validate(), Ok(()));
|
assert_eq!(mesh.validate(), Ok(()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[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]
|
#[test]
|
||||||
fn static_camera_composes_recovered_d3d7_view_before_projection() {
|
fn static_camera_composes_recovered_d3d7_view_before_projection() {
|
||||||
let transform = RawCameraTransform {
|
let transform = RawCameraTransform {
|
||||||
|
|||||||
@@ -9454,6 +9454,7 @@ impl StaticVulkanApp {
|
|||||||
})
|
})
|
||||||
.count();
|
.count();
|
||||||
eprintln!("render_coverage control_op1_jitter_unsupported={randomized_lights} (using authored color and radius; native RNG sampling pending)");
|
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 {
|
for &range_index in &self.world_range_indices {
|
||||||
let range = self
|
let range = self
|
||||||
.mesh
|
.mesh
|
||||||
@@ -14499,7 +14500,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[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};
|
use fparkan_fx::atmosphere::{PackedColor, SkySample, SKY_COLOR_COUNT};
|
||||||
|
|
||||||
// Compact camera fields from the native 1493.337-second AutoDemo capture.
|
// Compact camera fields from the native 1493.337-second AutoDemo capture.
|
||||||
@@ -14521,12 +14522,12 @@ mod tests {
|
|||||||
values: [0.0; 2],
|
values: [0.0; 2],
|
||||||
packed: [PackedColor(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])
|
fparkan_fx::sky::screen_gradient_frame(&sample, camera.native_sky_heading(), [0.0; 3])
|
||||||
.color;
|
.color;
|
||||||
|
|
||||||
assert_eq!(capture.atmosphere_seconds, Some(1493.337));
|
assert_eq!(capture.atmosphere_seconds, Some(1493.337));
|
||||||
assert_eq!(fog_color, PackedColor(0xFF3E_3E64));
|
assert_eq!(horizon_color, PackedColor(0xFF3E_3E64));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -10,6 +10,10 @@ authoring API без динамического подтверждения.
|
|||||||
Не доказаны символами точные имена renderer vtable slots, полный набор CShade
|
Не доказаны символами точные имена renderer vtable slots, полный набор CShade
|
||||||
state transitions и окончательный порядок части transparent/FX/shadow subpasses.
|
state transitions и окончательный порядок части transparent/FX/shadow subpasses.
|
||||||
|
|
||||||
|
Дистанционная кривая тумана по квадратам границ подтверждена в живом CShade.
|
||||||
|
Выбор проходов по native shade-state и GPU fog color пока заменён общим туманом мира с цветом горизонта;
|
||||||
|
это приближение считается как `render_coverage fog_pass_eligibility_approx`.
|
||||||
|
|
||||||
После ограничения цели рельефом интерактивная камера проверяет движение
|
После ограничения цели рельефом интерактивная камера проверяет движение
|
||||||
против каждого отрисовываемого корневого строения класса `FORT` и его
|
против каждого отрисовываемого корневого строения класса `FORT` и его
|
||||||
отрисовываемых компонентов. Для этого пути используются текущая Node38-поза
|
отрисовываемых компонентов. Для этого пути используются текущая Node38-поза
|
||||||
|
|||||||
@@ -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
|
## Радиус Control Op1
|
||||||
|
|
||||||
GOG `Effect.dll` (SHA-256
|
GOG `Effect.dll` (SHA-256
|
||||||
|
|||||||
@@ -845,6 +845,9 @@ projected shadows. Радиус источника масштабируется
|
|||||||
синий свет завода почти незаметным. Неположительный итог заменяется на 0,01.
|
синий свет завода почти незаметным. Неположительный итог заменяется на 0,01.
|
||||||
Случайные добавки к цвету и радиусу пока явно считаются в
|
Случайные добавки к цвету и радиусу пока явно считаются в
|
||||||
`render_coverage control_op1_jitter_unsupported`.
|
`render_coverage control_op1_jitter_unsupported`.
|
||||||
|
Туман также зависит от расстояния нелинейно: между ближней и дальней границами
|
||||||
|
движок интерполирует по квадрату дистанции. При границах 0 и 700 точка в 350
|
||||||
|
получает четверть тумана, а не половину. Это сохраняет контраст среднего плана.
|
||||||
Effect renderer подавляет header bit `0x800` только при
|
Effect renderer подавляет header bit `0x800` только при
|
||||||
render selector 0. Native AniMesh component path захвачен с selector 1, а в
|
render selector 0. Native AniMesh component path захвачен с selector 1, а в
|
||||||
его 53-entry manager присутствуют FX entries с header `0xD00`; preview
|
его 53-entry manager присутствуют FX entries с header `0xD00`; preview
|
||||||
|
|||||||
Reference in new issue
Block a user