fix(render): match Ngi32 camera matrices
This commit is contained in:
@@ -353,7 +353,7 @@ mod static_mesh_tests {
|
|||||||
|
|
||||||
assert!(camera.is_finite());
|
assert!(camera.is_finite());
|
||||||
assert_eq!(camera.clip_from_world[0], 0.65_f32.cos());
|
assert_eq!(camera.clip_from_world[0], 0.65_f32.cos());
|
||||||
assert_eq!(camera.clip_from_world[6], 700.0_f32 / 699.5);
|
assert_eq!(camera.clip_from_world[6], 0.65_f32.sin() * 700.0 / 699.5);
|
||||||
assert_eq!(camera.clip_from_world[7], 0.65_f32.sin());
|
assert_eq!(camera.clip_from_world[7], 0.65_f32.sin());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ impl RawCameraTransform {
|
|||||||
m20,
|
m20,
|
||||||
0.0,
|
0.0,
|
||||||
m23.mul_add(m21, m13.mul_add(m11, m03 * m01)),
|
m23.mul_add(m21, m13.mul_add(m11, m03 * m01)),
|
||||||
-(m23.mul_add(m12, m13.mul_add(m02, m03 * m10))),
|
-(m23.mul_add(m22, m13.mul_add(m12, m03 * m02))),
|
||||||
-(m00.mul_add(m03, m23.mul_add(m20, m13 * m10))),
|
-(m00.mul_add(m03, m23.mul_add(m20, m13 * m10))),
|
||||||
1.0,
|
1.0,
|
||||||
])
|
])
|
||||||
@@ -193,10 +193,11 @@ pub struct LegacyD3d7Projection {
|
|||||||
impl LegacyD3d7Projection {
|
impl LegacyD3d7Projection {
|
||||||
/// Reconstructs the exact row-major matrix passed to D3D7 projection state.
|
/// Reconstructs the exact row-major matrix passed to D3D7 projection state.
|
||||||
///
|
///
|
||||||
/// Ngi32 uses the viewport's `height / width`, writes `cos(fov / 2)` to
|
/// Ngi32 uses the viewport's `width / height`, writes `cos(fov / 2)` to
|
||||||
/// the diagonal and `sin(fov / 2)` to the homogeneous-W term. The ratio
|
/// the diagonal and `sin(fov / 2)` to both the depth scale and
|
||||||
/// after D3D's perspective divide is therefore the expected cotangent
|
/// homogeneous-W term. The ratio after D3D's perspective divide is
|
||||||
/// scale. This remains legacy D3D7 data rather than a Vulkan projection.
|
/// therefore the expected cotangent scale. This remains legacy D3D7 data
|
||||||
|
/// rather than a Vulkan projection.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn try_direct3d7_projection_row_major(self) -> Option<[f32; 16]> {
|
pub fn try_direct3d7_projection_row_major(self) -> Option<[f32; 16]> {
|
||||||
let width = self.viewport[2].checked_sub(self.viewport[0])?;
|
let width = self.viewport[2].checked_sub(self.viewport[0])?;
|
||||||
@@ -217,11 +218,11 @@ impl LegacyD3d7Projection {
|
|||||||
// Ngi32 converts these signed viewport dimensions into single-precision
|
// Ngi32 converts these signed viewport dimensions into single-precision
|
||||||
// arithmetic before building the legacy matrix.
|
// arithmetic before building the legacy matrix.
|
||||||
#[allow(clippy::cast_precision_loss)]
|
#[allow(clippy::cast_precision_loss)]
|
||||||
let aspect = (height as f32) / (width as f32);
|
let aspect = (width as f32) / (height as f32);
|
||||||
let half_fov = self.field_of_view_radians * 0.5;
|
let half_fov = self.field_of_view_radians * 0.5;
|
||||||
let cosine = half_fov.cos();
|
let cosine = half_fov.cos();
|
||||||
let sine = half_fov.sin();
|
let sine = half_fov.sin();
|
||||||
let depth_scale = 1.0 / (1.0 - self.near_plane / self.far_plane);
|
let depth_scale = sine / (1.0 - self.near_plane / self.far_plane);
|
||||||
[aspect, cosine, sine, depth_scale]
|
[aspect, cosine, sine, depth_scale]
|
||||||
.iter()
|
.iter()
|
||||||
.all(|value| value.is_finite())
|
.all(|value| value.is_finite())
|
||||||
@@ -1185,7 +1186,7 @@ mod tests {
|
|||||||
assert_eq!(
|
assert_eq!(
|
||||||
transform.try_direct3d7_view_row_major(),
|
transform.try_direct3d7_view_row_major(),
|
||||||
Some([
|
Some([
|
||||||
1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, -10.0, -10.0, -20.0,
|
1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 1.0, 0.0, 0.0, -10.0, -30.0, -20.0,
|
||||||
1.0,
|
1.0,
|
||||||
])
|
])
|
||||||
);
|
);
|
||||||
@@ -1207,10 +1208,10 @@ mod tests {
|
|||||||
.try_direct3d7_projection_row_major()
|
.try_direct3d7_projection_row_major()
|
||||||
.expect("live Ngi32 projection parameters are valid");
|
.expect("live Ngi32 projection parameters are valid");
|
||||||
let half_fov = 0.65_f32;
|
let half_fov = 0.65_f32;
|
||||||
let depth_scale = 700.0_f32 / 699.5;
|
let depth_scale = half_fov.sin() * 700.0_f32 / 699.5;
|
||||||
|
|
||||||
assert_eq!(matrix[0], half_fov.cos());
|
assert_eq!(matrix[0], half_fov.cos());
|
||||||
assert_eq!(matrix[5], 0.75 * half_fov.cos());
|
assert_eq!(matrix[5], (4.0 / 3.0) * half_fov.cos());
|
||||||
assert_eq!(matrix[10], depth_scale);
|
assert_eq!(matrix[10], depth_scale);
|
||||||
assert_eq!(matrix[11], half_fov.sin());
|
assert_eq!(matrix[11], half_fov.sin());
|
||||||
assert_eq!(matrix[14], -(depth_scale * 0.5));
|
assert_eq!(matrix[14], -(depth_scale * 0.5));
|
||||||
|
|||||||
+22
-14
@@ -1398,13 +1398,14 @@ RVA `0x7030` exactly builds the row-major D3D7 projection from renderer FOV
|
|||||||
|
|
||||||
```text
|
```text
|
||||||
[ cos(f/2), 0, 0, 0 ]
|
[ cos(f/2), 0, 0, 0 ]
|
||||||
[ 0, h/w * cos(f/2), 0, 0 ]
|
[ 0, w/h * cos(f/2), 0, 0 ]
|
||||||
[ 0, 0, z/(z-n), sin(f/2) ]
|
[ 0, 0, sin(f/2)*z/(z-n), sin(f/2) ]
|
||||||
[ 0, 0, -n*z/(z-n), 0 ]
|
[ 0, 0, -sin(f/2)*n*z/(z-n), 0 ]
|
||||||
```
|
```
|
||||||
|
|
||||||
The sine in the homogeneous-W term is intentional: after the D3D perspective
|
The sine in the depth scale and homogeneous-W term is intentional: after the
|
||||||
divide the diagonal has the expected cotangent scale. RVA `0x9450` applies a
|
D3D perspective divide the diagonal has the expected cotangent scale and the
|
||||||
|
depth range remains finite. RVA `0x9450` applies a
|
||||||
specific axis permutation/sign change and translated dot products to selector
|
specific axis permutation/sign change and translated dot products to selector
|
||||||
0 before the view `SetTransform`; it is not merely the generic affine inverse.
|
0 before the view `SetTransform`; it is not merely the generic affine inverse.
|
||||||
|
|
||||||
@@ -1562,23 +1563,30 @@ hashes промежуточных buffers. Без такой трассиров
|
|||||||
[materials](../reference/materials.md), [Texm](../reference/texm.md) и
|
[materials](../reference/materials.md), [Texm](../reference/texm.md) и
|
||||||
[render frame](../reference/render-frame.md).
|
[render frame](../reference/render-frame.md).
|
||||||
|
|
||||||
### Live AutoDemo: видимый результат пока не достигнут
|
### Live AutoDemo: empty frame устранён, но parity ещё не достигнут
|
||||||
|
|
||||||
Read-only `PrintWindow` capture работающего GOG AutoDemo даёт полноценный
|
Read-only `PrintWindow` capture работающего GOG AutoDemo даёт полноценный
|
||||||
оригинальный кадр (terrain, варбот и близкая geometry), поэтому теперь есть
|
оригинальный кадр (terrain, варбот и близкая geometry), поэтому теперь есть
|
||||||
безвводный source для будущего visual comparison. Текущий полный static-Vulkan
|
безвводный source для будущего visual comparison. Текущий полный static-Vulkan
|
||||||
preview нельзя описывать как видимую сцену: хотя он загружает 8 TMA objects,
|
preview раньше состоял только из clear color. Validation `0/0` тогда доказывал
|
||||||
66 MSH components и 67 descriptors, его изображение состоит только из clear
|
только GPU lifecycle, но не совпадение с original renderer.
|
||||||
color. Validation остаётся `0/0`, но это доказывает корректность GPU lifecycle,
|
|
||||||
а не совпадение с оригинальным renderer.
|
|
||||||
|
|
||||||
Новая CPU-диагностика применяет к тем же source vertices ту же row-major D3D7
|
Новая CPU-диагностика применяет к тем же source vertices ту же row-major D3D7
|
||||||
matrix, которую GLSL получает как column-major push constant. Для свежего
|
matrix, которую GLSL получает как column-major push constant. Для свежего
|
||||||
captured Ngi32 camera (`viewport 1024x768`, near `5`, far `700`, FOV `1.3`)
|
captured Ngi32 camera (`viewport 1024x768`, near `5`, far `700`, FOV `1.3`)
|
||||||
она насчитала `clip_visible_vertices=0`. Следовательно, current empty frame не
|
она насчитала `clip_visible_vertices=0`. Последующая сверка с
|
||||||
объясняется texture selection, face culling (baseline state already disables
|
`Ngi32!sub_10009450` и `sub_10007030` нашла два literal translation defects:
|
||||||
it) или Vulkan validation: следующий исследовательский шаг — восстановить
|
Y translation view matrix должна быть `-(m23*m22 + m13*m12 + m03*m02)`, а
|
||||||
точный live camera transform/clip-space boundary.
|
projection использует `width/height` и множитель `sin(fov/2)` в обоих depth
|
||||||
|
coefficients. После исправления того же capture диагностировал
|
||||||
|
`clip_visible_vertices=938`, а `PrintWindow` native Vulkan window показал
|
||||||
|
реальную rasterized terrain/model geometry. Это исключает прежний empty-frame
|
||||||
|
барьер; face culling и texture selection не были его причиной.
|
||||||
|
|
||||||
|
Получившийся кадр всё ещё явно не похож на оригинал: geometry сосредоточена у
|
||||||
|
границы и material/terrain representation грубая. Значит, следующие задачи —
|
||||||
|
camera timing/selection, source model-node transforms, terrain material phases,
|
||||||
|
lighting и visibility, а не объявление pixel parity.
|
||||||
|
|
||||||
Параллельно bridge для legacy-camera path теперь переводит только высоту
|
Параллельно bridge для legacy-camera path теперь переводит только высоту
|
||||||
`Land.msh` в world units с масштабом `1/32`: raw AutoDemo heights
|
`Land.msh` в world units с масштабом `1/32`: raw AutoDemo heights
|
||||||
|
|||||||
Reference in New Issue
Block a user