fix(render): match native reflection target sizes
Docs Deploy / Build and Deploy MkDocs (push) Successful in 35s
Docs Deploy / Build and Deploy MkDocs (push) Successful in 35s
This commit is contained in:
5 files changed
+68
-16
No files matched your search
@@ -221,6 +221,9 @@ impl ReflectionResources {
|
||||
depth_request: fparkan_platform::DepthStencilSupport,
|
||||
render_pass: vk::RenderPass,
|
||||
) -> Result<(), VulkanSmokeRendererError> {
|
||||
let side = fparkan_render::water::legacy_embm_reflection_size([extent.0, extent.1]);
|
||||
let extent = (side, side);
|
||||
eprintln!("render_reflection target_extent={side}x{side}");
|
||||
let info = vk::ImageCreateInfo::default()
|
||||
.image_type(vk::ImageType::TYPE_2D)
|
||||
.format(vk::Format::from_raw(format))
|
||||
@@ -236,7 +239,7 @@ impl ReflectionResources {
|
||||
.usage(vk::ImageUsageFlags::COLOR_ATTACHMENT | vk::ImageUsageFlags::SAMPLED)
|
||||
.sharing_mode(vk::SharingMode::EXCLUSIVE)
|
||||
.initial_layout(vk::ImageLayout::UNDEFINED);
|
||||
// SAFETY: This target uses the validated non-zero swapchain extent and format.
|
||||
// SAFETY: The validated swapchain format is retained; the square side is in 2..=1024.
|
||||
let image = unsafe { self.device.create_image(&info, None) }.map_err(|result| {
|
||||
VulkanSmokeRendererError::VulkanOperation {
|
||||
context: "vkCreateImage(reflection)",
|
||||
|
||||
@@ -373,7 +373,7 @@ impl VulkanSmokeRenderer {
|
||||
plane_heights.sort_unstable();
|
||||
plane_heights.dedup();
|
||||
if !plane_heights.is_empty() {
|
||||
eprintln!("render_coverage planar_reflection_render_context_approx={} (native shifted window and bump UV; main-view depth range, sky/fog/FX and swapchain-sized target remain approximations; non-perspective cameras use mirror fallback)", plane_heights.len());
|
||||
eprintln!("render_coverage planar_reflection_render_context_approx={} (native shifted window, target size and bump UV; main-view depth range and sky/fog/FX remain approximations; non-perspective cameras use mirror fallback)", plane_heights.len());
|
||||
eprintln!("render_coverage planar_reflection_other_planes_unsupported={} (nearest active plane is reflected)", plane_heights.len().saturating_sub(1));
|
||||
}
|
||||
if unsupported_nonplanar != 0 {
|
||||
@@ -1874,17 +1874,27 @@ impl VulkanSmokeRenderer {
|
||||
},
|
||||
},
|
||||
];
|
||||
let render_area = vk::Rect2D {
|
||||
offset: vk::Offset2D { x: 0, y: 0 },
|
||||
extent: vk::Extent2D {
|
||||
width: swapchain.report.plan.extent.0,
|
||||
height: swapchain.report.plan.extent.1,
|
||||
},
|
||||
};
|
||||
for reflected in [true, false] {
|
||||
if reflected && reflection_plane.is_none() {
|
||||
continue;
|
||||
}
|
||||
let extent = if reflected {
|
||||
let target = resources.reflection.color.as_ref().ok_or(
|
||||
VulkanSmokeRendererError::InvariantViolation {
|
||||
context: "active reflection without a color target",
|
||||
},
|
||||
)?;
|
||||
(target.width, target.height)
|
||||
} else {
|
||||
swapchain.report.plan.extent
|
||||
};
|
||||
let render_area = vk::Rect2D {
|
||||
offset: vk::Offset2D { x: 0, y: 0 },
|
||||
extent: vk::Extent2D {
|
||||
width: extent.0,
|
||||
height: extent.1,
|
||||
},
|
||||
};
|
||||
let framebuffer = if reflected {
|
||||
resources.reflection.framebuffer
|
||||
} else {
|
||||
@@ -2073,10 +2083,7 @@ impl VulkanSmokeRenderer {
|
||||
} else {
|
||||
0
|
||||
}) as f32;
|
||||
let viewport = super::swapchain_resources::draw_viewport(
|
||||
swapchain.report.plan.extent,
|
||||
material_mode,
|
||||
);
|
||||
let viewport = super::swapchain_resources::draw_viewport(extent, material_mode);
|
||||
let next_viewport_depth = [viewport.min_depth, viewport.max_depth];
|
||||
if viewport_depth != Some(next_viewport_depth) {
|
||||
device
|
||||
|
||||
@@ -7,6 +7,17 @@ pub const EMBM_BUMP_TILE: f32 = 100.0;
|
||||
/// Native diagonal bump matrix, without luminance modulation.
|
||||
pub const EMBM_COEFFICIENTS: [f32; 2] = [0.01, 0.01];
|
||||
|
||||
/// Square reflection size chosen by Terrain.dll RVA 0x27FC7 from renderer dimensions.
|
||||
#[must_use]
|
||||
pub fn legacy_embm_reflection_size(extent: [u32; 2]) -> u32 {
|
||||
let limit = extent[0].min(extent[1]).min(1024);
|
||||
let mut side = 2;
|
||||
while side * 2 <= limit {
|
||||
side *= 2;
|
||||
}
|
||||
side
|
||||
}
|
||||
|
||||
/// Generates the signed bytes written by Terrain.dll RVA 0x2BF50 with CW 0x007F.
|
||||
#[must_use]
|
||||
pub fn legacy_embm_bump_map() -> Vec<[i8; 2]> {
|
||||
@@ -116,6 +127,24 @@ pub fn legacy_embm_reflection_clip(
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn reflection_size_matches_native_renderer_dimension_vectors() {
|
||||
for (extent, expected) in [
|
||||
([1280, 1024], 1024),
|
||||
([1280, 720], 512),
|
||||
([800, 600], 512),
|
||||
([640, 480], 256),
|
||||
([1920, 1080], 1024),
|
||||
([2048, 2048], 1024),
|
||||
([4, 7], 4),
|
||||
([3, 300], 2),
|
||||
([1, 1], 2),
|
||||
([700, 700], 512),
|
||||
] {
|
||||
assert_eq!(legacy_embm_reflection_size(extent), expected, "{extent:?}");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shifted_camera_maps_native_water_corners_and_retains_perspective_depth() {
|
||||
let projection = crate::LegacyD3d7Projection {
|
||||
|
||||
@@ -76,7 +76,7 @@ MSH-запрос со свободной preview-камерой; это не п
|
||||
среде, перехватить DirectDraw/Direct3D calls и surface flips, сохранить state
|
||||
log на минимальных сценах с одним типом материала.
|
||||
|
||||
Горизонтальные грани Land.msh с флагом `0x20000` используют исходную карту Du/Dv и окно shifted-камеры, проверенное исполнением Terrain.dll. Диапазон глубины, небо/туман/FX основного вида и размер target остаются приближениями: `planar_reflection_render_context_approx`; неперспективная камера API использует обычное зеркало.
|
||||
Горизонтальные грани Land.msh с флагом `0x20000` используют исходную карту Du/Dv, окно shifted-камеры и квадратный размер текстуры, проверенные исполнением DLL. Диапазон глубины, небо/туман/FX основного вида и соответствие размеров при обрезанном capture остаются приближениями: `planar_reflection_render_context_approx`; неперспективная камера API использует обычное зеркало.
|
||||
Raw queue clock задаёт фазу волн отдельно от времени суток и sky elapsed; старые захваты без него считаются в `captured_water_phase_unverified`.
|
||||
Один target отражает ближайшую активную высоту воды; остальные высоты и неплоские грани считаются в `planar_reflection_other_planes_unsupported` и `planar_reflection_nonplanar_unsupported` и сохраняют обычный terrain shader.
|
||||
`terrain_face8_pipeline_unverified` теперь считает только исходные грани с `0x8` без `0x20000`; сама семантика оставшихся специальных проходов ещё не установлена.
|
||||
|
||||
+15
-2
@@ -1169,6 +1169,18 @@ Rust-тест отдельно проверяет углы водоёма и г
|
||||
водоёма с добавленным смещением волн. Для неперспективной камеры API остаётся
|
||||
обычное зеркало с экранными координатами.
|
||||
|
||||
Размер изображения отражения выбирается отдельно от размера основного кадра.
|
||||
Terrain.dll в конструкторе CShade (RVA `0x278E0`, цикл с `0x27FC7`) берёт
|
||||
ширину и высоту renderer через Ngi32.dll RVA `0x7FB0`. Сторона квадратной
|
||||
текстуры — наибольшая степень двойки, не превосходящая меньшую из этих
|
||||
величин и 1024; нижний предел равен 2. Например, экран 1280×1024 даёт
|
||||
отражение 1024×1024, а 1280×720 — 512×512. Getter размеров и исходный цикл
|
||||
выбора проверены исполнением DLL на десяти входах; создание нативной текстуры
|
||||
в этом оракуле пропущено. Vulkan создаёт цвет и глубину с этой стороной и
|
||||
задаёт каждому проходу собственные viewport и область очистки. Так уменьшение
|
||||
отражения не обрезает основной кадр и воспроизводит исходный масштаб его
|
||||
пикселей, от которого зависит чёткость берегов и ряби.
|
||||
|
||||
Цветовой target переходит из attachment layout в shader-read layout после
|
||||
записи. Следующий зеркальный проход ждёт предыдущего чтения этой текстуры;
|
||||
основной и отражённый виды используют разные слоты uniform buffer. Во время
|
||||
@@ -1196,9 +1208,10 @@ Rust-тест отдельно проверяет углы водоёма и г
|
||||
В Vulkan карта хранится в двух каналах несжатой текстуры и возвращается к
|
||||
знаковым значениям перед вычислением смещения.
|
||||
|
||||
Матрица положения камеры, её окно и координаты волн восстановлены, но весь
|
||||
Матрица положения камеры, её окно, размер текстуры и координаты волн восстановлены, но весь
|
||||
контекст отражённого вида ещё не повторён. Диапазон глубины, небо, туман и FX
|
||||
заимствуются из основного вида, а размер target совпадает со swapchain.
|
||||
заимствуются из основного вида. Для захвата с обрезанным viewport ещё не
|
||||
подтверждено соответствие размера окна FParkan полному renderer оригинала.
|
||||
Эти ограничения учитываются в `planar_reflection_render_context_approx`;
|
||||
совпадение генератора волн и окна не доказывает совпадения итогового отражения.
|
||||
Один target обслуживает ближайшую
|
||||
|
||||
Reference in new issue
Block a user