fix(render): apply celestial birth matrix to column vectors
Docs Deploy / Build and Deploy MkDocs (push) Successful in 40s
Docs Deploy / Build and Deploy MkDocs (push) Successful in 40s
This commit is contained in:
4 files changed
+51
-22
No files matched your search
@@ -1143,12 +1143,11 @@ impl SunFrame {
|
|||||||
/// Converts an interpolated atmosphere sun sample to a renderer-facing frame.
|
/// Converts an interpolated atmosphere sun sample to a renderer-facing frame.
|
||||||
///
|
///
|
||||||
/// The original update computes `orbit_direction = [cos(theta), 0,
|
/// The original update computes `orbit_direction = [cos(theta), 0,
|
||||||
/// -sin(theta)]`, then applies the row-vector product of the two birth-angle
|
/// -sin(theta)]`, then applies the column-vector product of the two birth-angle
|
||||||
/// matrices. With `A` and `B` in radians, the resulting direction is:
|
/// matrices. With `A` and `B` in radians, the resulting direction is:
|
||||||
///
|
///
|
||||||
/// ```text
|
/// ```text
|
||||||
/// [x*cos(A), -x*sin(A)*cos(B) + z*sin(B),
|
/// [x*cos(A) + z*sin(A)*sin(B), x*sin(A) - z*cos(A)*sin(B), z*cos(B)]
|
||||||
/// x*sin(A)*sin(B) + z*cos(B)]
|
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// where `x` and `z` are the two non-zero orbit components.
|
/// where `x` and `z` are the two non-zero orbit components.
|
||||||
@@ -1178,9 +1177,9 @@ pub fn sun_frame(sample: &SunSample, now_seconds: f32, config: SunConfig) -> Sun
|
|||||||
let x = orbit_direction[0];
|
let x = orbit_direction[0];
|
||||||
let z = orbit_direction[2];
|
let z = orbit_direction[2];
|
||||||
let direction = normalize3([
|
let direction = normalize3([
|
||||||
x * cos_a,
|
x * cos_a + z * sin_a * sin_b,
|
||||||
-x * sin_a * cos_b + z * sin_b,
|
x * sin_a - z * cos_a * sin_b,
|
||||||
x * sin_a * sin_b + z * cos_b,
|
z * cos_b,
|
||||||
]);
|
]);
|
||||||
SunFrame {
|
SunFrame {
|
||||||
active: true,
|
active: true,
|
||||||
@@ -1892,15 +1891,29 @@ mod tests {
|
|||||||
let sample = sun(true);
|
let sample = sun(true);
|
||||||
let at_start = sun_frame(&sample, 0.0, SunConfig::new(0.0, 1.0, [90.0, 0.0]));
|
let at_start = sun_frame(&sample, 0.0, SunConfig::new(0.0, 1.0, [90.0, 0.0]));
|
||||||
assert!((at_start.direction[0]).abs() < 1.0e-5);
|
assert!((at_start.direction[0]).abs() < 1.0e-5);
|
||||||
assert!((at_start.direction[1] + at_start.orbit_direction[0]).abs() < 1.0e-5);
|
assert!((at_start.direction[1] - at_start.orbit_direction[0]).abs() < 1.0e-5);
|
||||||
assert!((at_start.direction[2] - at_start.orbit_direction[2]).abs() < 1.0e-5);
|
assert!((at_start.direction[2] - at_start.orbit_direction[2]).abs() < 1.0e-5);
|
||||||
|
|
||||||
let side = sun_frame(&sample, 0.0, SunConfig::new(0.0, 1.0, [0.0, 90.0]));
|
let side = sun_frame(&sample, 0.0, SunConfig::new(0.0, 1.0, [0.0, 90.0]));
|
||||||
assert!((side.direction[0] - side.orbit_direction[0]).abs() < 1.0e-5);
|
assert!((side.direction[0] - side.orbit_direction[0]).abs() < 1.0e-5);
|
||||||
assert!((side.direction[1] - side.orbit_direction[2]).abs() < 1.0e-5);
|
assert!((side.direction[1] + side.orbit_direction[2]).abs() < 1.0e-5);
|
||||||
assert!(side.direction[2].abs() < 1.0e-5);
|
assert!(side.direction[2].abs() < 1.0e-5);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sun_direction_matches_live_native_birth_matrix() {
|
||||||
|
// Terrain CSun::Update, elapsed 282184 ms, lifetime 525000 ms.
|
||||||
|
let frame = sun_frame(
|
||||||
|
&sun(true),
|
||||||
|
282.184,
|
||||||
|
SunConfig::new(0.0, 525.0, [90.0, 30.0]),
|
||||||
|
);
|
||||||
|
let expected = [0xbefd_7269, 0xbe10_4168, 0xbf5b_7dda].map(f32::from_bits);
|
||||||
|
for (actual, expected) in frame.direction.into_iter().zip(expected) {
|
||||||
|
assert!((actual - expected).abs() < 3.0e-6, "{actual} != {expected}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn native_birth_keeps_the_real_start_time() {
|
fn native_birth_keeps_the_real_start_time() {
|
||||||
assert!(!celestial_frame(&sun(false), 100.0).active);
|
assert!(!celestial_frame(&sun(false), 100.0).active);
|
||||||
|
|||||||
@@ -55,7 +55,9 @@ MSH-запрос со свободной preview-камерой; это не п
|
|||||||
|
|
||||||
Данные type 16 ещё не декодированы. Внутренний ракурс «Конструктора» после
|
Данные type 16 ещё не декодированы. Внутренний ракурс «Конструктора» после
|
||||||
настоящего входа игрока подтверждает исчезновение чёрной поверхности в проходе;
|
настоящего входа игрока подтверждает исчезновение чёрной поверхности в проходе;
|
||||||
для всей приёмки R2 ещё нужны пары обеих миссий на трёх дистанциях.
|
наружные окна обеих миссий сверены на расстояниях около 60/85/110, а ближние
|
||||||
|
окна — с внутренней стороны. Внутренние дальние ракурсы и clipping на краю
|
||||||
|
кадра ещё не приняты; общей готовности R2 эти пары не доказывают.
|
||||||
|
|
||||||
Закрывающий эксперимент: запустить оригинал в совместимой Windows/DirectX
|
Закрывающий эксперимент: запустить оригинал в совместимой Windows/DirectX
|
||||||
среде, перехватить DirectDraw/Direct3D calls и surface flips, сохранить state
|
среде, перехватить DirectDraw/Direct3D calls и surface flips, сохранить state
|
||||||
|
|||||||
@@ -50,7 +50,25 @@ key_time = floor(duration * (key_hour * 60 + key_minute) * 60 / 86400)
|
|||||||
theta = (clamp((now - birth_start) / lifetime, 0, 1) * 1.2 - 0.1) * pi
|
theta = (clamp((now - birth_start) / lifetime, 0, 1) * 1.2 - 0.1) * pi
|
||||||
```
|
```
|
||||||
|
|
||||||
Затем к нему применяется матрица рождения. У солнца и луны независимые
|
Затем к нему применяется матрица рождения как к вектору-столбцу. Если `A` и
|
||||||
|
`B` — углы рождения, а `x = cos(theta)` и `z = -sin(theta)`, результат равен
|
||||||
|
|
||||||
|
```text
|
||||||
|
[x*cos(A) + z*sin(A)*sin(B),
|
||||||
|
x*sin(A) - z*cos(A)*sin(B),
|
||||||
|
z*cos(B)]
|
||||||
|
```
|
||||||
|
|
||||||
|
Порядок важен для всего освещения: транспонированное умножение переносит
|
||||||
|
солнце на другую сторону мира и меняет освещённые склоны, блики и тени.
|
||||||
|
В GOG `Terrain.dll` (SHA-256
|
||||||
|
`af87d1b2e728a0be73c52be3b44cc196ab46da7799f25a15d40f8c9b0b425ead`)
|
||||||
|
матрицу строит RVA `0x4A550`, а вектор преобразует `CSun::Update`, RVA
|
||||||
|
`0x4B570`. Живой замер после 282184 мс при длительности 525000 мс и углах
|
||||||
|
`[90, 30]` дал направление `[-0.4950135, -0.1408745, -0.8573891]`;
|
||||||
|
Rust-тест проверяет этот результат с допуском `3e-6`.
|
||||||
|
|
||||||
|
У солнца и луны независимые
|
||||||
интервалы и независимые направления: при отсутствии активной луны нельзя
|
интервалы и независимые направления: при отсутствии активной луны нельзя
|
||||||
подменять её направление противоположностью солнца. `SunFrame::color` — это
|
подменять её направление противоположностью солнца. `SunFrame::color` — это
|
||||||
единственный primary directional RGB. Второй источник получает направление
|
единственный primary directional RGB. Второй источник получает направление
|
||||||
|
|||||||
@@ -134,21 +134,17 @@ RVA `0xF6E0`, преобразует направление текущей ма
|
|||||||
глубину. За фиксированными диапазонами окружения сохраняются их явные состояния.
|
глубину. За фиксированными диапазонами окружения сохраняются их явные состояния.
|
||||||
|
|
||||||
Для MSH без потока цветов type 16 диапазон, цвет которого вычисляет CPU, при
|
Для MSH без потока цветов type 16 диапазон, цвет которого вычисляет CPU, при
|
||||||
raw-флаге `Batch20` `0x0008` получает переопределение alpha только в одном
|
raw-флаге `Batch20` `0x0008` получает переопределение alpha по
|
||||||
подтверждённом случае. В GOG `Terrain.dll` с SHA-256
|
[дистанционной кривой портала](#прозрачность-порталов-msh). Bit `0x0010`
|
||||||
`af87d1b2e728a0be73c52be3b44cc196ab46da7799f25a15d40f8c9b0b425ead` проверка
|
имеет приоритет; иначе bit `0x0040` задаёт нулевую alpha, а оставшаяся
|
||||||
по RVA `0x1B0F0` задаёт alpha `0`, когда установлен bit `0x0040` и сброшен
|
ветвь использует границы 75/95 с поправкой на FOV. При наличии type 16 или
|
||||||
`0x0010`; поэтому для `0x0148` alpha равна нулю. Для `0x0108` и `0x0118`
|
отсутствии `0x0008` переопределения нет. Оно относится к одному draw range и не меняет общий
|
||||||
превью сохраняет alpha MAT0; для `0x0058` действует приоритет bit `0x0010`,
|
|
||||||
также оставляя alpha MAT0. При наличии type 16 или отсутствии `0x0008`
|
|
||||||
переопределения нет. Оно относится к одному draw range и не меняет общий
|
|
||||||
материал; alpha `0` временно задаёт число индексов `0`, а при очистке
|
материал; alpha `0` временно задаёт число индексов `0`, а при очистке
|
||||||
переопределения исходное число индексов восстанавливается.
|
переопределения исходное число индексов восстанавливается.
|
||||||
|
|
||||||
Общий fade по расстоянию пока не реализован. Открыты точная метрика расстояния,
|
Fade обновляется с камерой каждый кадр. Открыт выбор первой принятой вершины
|
||||||
выбор первого CPU-принятого треугольника диапазона и эквивалентность frustum- и
|
после native CPU clipping: приложение пока берёт первую индексированную
|
||||||
culling-отбору оригинала. Поэтому состояния `0x0108` и `0x0118` оставляют alpha
|
вершину диапазона и считает это приближение в `render_coverage`.
|
||||||
MAT0, пока эти входные данные не установлены.
|
|
||||||
|
|
||||||
В превью с фиксированной камерой диапазоны туманности и звёзд, купола и облаков
|
В превью с фиксированной камерой диапазоны туманности и звёзд, купола и облаков
|
||||||
явно помечаются как прозрачный фон. Непрозрачные диапазоны мира остаются на
|
явно помечаются как прозрачный фон. Непрозрачные диапазоны мира остаются на
|
||||||
|
|||||||
Reference in new issue
Block a user