fix(render): match native packet distance approximation
Docs Deploy / Build and Deploy MkDocs (push) Successful in 1m15s
Docs Deploy / Build and Deploy MkDocs (push) Successful in 1m15s
This commit is contained in:
7 files changed
+188
-40
No files matched your search
@@ -11,6 +11,7 @@
|
|||||||
mod asset_mesh;
|
mod asset_mesh;
|
||||||
mod capabilities;
|
mod capabilities;
|
||||||
mod instance;
|
mod instance;
|
||||||
|
mod packet_distance;
|
||||||
mod reflection;
|
mod reflection;
|
||||||
mod resources;
|
mod resources;
|
||||||
mod runtime;
|
mod runtime;
|
||||||
@@ -50,6 +51,7 @@ pub use self::instance::{
|
|||||||
};
|
};
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
use self::instance::{cstring_vec, ensure_instance_extensions_available};
|
use self::instance::{cstring_vec, ensure_instance_extensions_available};
|
||||||
|
pub use self::packet_distance::legacy_packet_distance_squared;
|
||||||
use self::resources::{
|
use self::resources::{
|
||||||
color_subresource_range, create_command_pool, create_depth_attachment, create_frame_sync,
|
color_subresource_range, create_command_pool, create_depth_attachment, create_frame_sync,
|
||||||
create_frame_uniform_buffer, create_index_buffer, create_material_specular_buffer,
|
create_frame_uniform_buffer, create_index_buffer, create_material_specular_buffer,
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
#![allow(unsafe_code)]
|
||||||
|
//! The native CPU instruction used for render-packet distance keys.
|
||||||
|
|
||||||
|
/// Reproduces Ngi32's squared length multiplied by `RSQRTSS` on x86-64.
|
||||||
|
/// Other architectures retain the portable distance and report that boundary.
|
||||||
|
#[must_use]
|
||||||
|
pub fn legacy_packet_distance_squared(squared: f32) -> Option<f32> {
|
||||||
|
if !squared.is_finite() || squared < 0.0 {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
if squared == 0.0 {
|
||||||
|
static NOTICE: std::sync::Once = std::sync::Once::new();
|
||||||
|
NOTICE.call_once(|| {
|
||||||
|
eprintln!("render_coverage packet_zero_distance_fallback=1 (stable zero instead of native 0 * infinity)");
|
||||||
|
});
|
||||||
|
return Some(0.0);
|
||||||
|
}
|
||||||
|
#[cfg(target_arch = "x86_64")]
|
||||||
|
let distance = {
|
||||||
|
use core::arch::x86_64::{_mm_cvtss_f32, _mm_rsqrt_ss, _mm_set_ss};
|
||||||
|
// SAFETY: SSE is part of the x86-64 baseline; these intrinsics only
|
||||||
|
// operate on register values and do not access memory.
|
||||||
|
let inverse = unsafe { _mm_cvtss_f32(_mm_rsqrt_ss(_mm_set_ss(squared))) };
|
||||||
|
squared * inverse
|
||||||
|
};
|
||||||
|
#[cfg(not(target_arch = "x86_64"))]
|
||||||
|
let distance = {
|
||||||
|
static NOTICE: std::sync::Once = std::sync::Once::new();
|
||||||
|
NOTICE.call_once(|| {
|
||||||
|
eprintln!("render_coverage packet_distance_native_rsqrt_unsupported=1 (using portable square root)");
|
||||||
|
});
|
||||||
|
squared.sqrt()
|
||||||
|
};
|
||||||
|
distance.is_finite().then_some(distance)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn packet_distance_preserves_the_native_approximation_error_bound() {
|
||||||
|
for squared in [0.000_001_f32, 1.0, 9.0, 11_393.854, 1_000_000.0] {
|
||||||
|
let distance = legacy_packet_distance_squared(squared).unwrap();
|
||||||
|
assert!((distance / squared.sqrt() - 1.0).abs() < 0.000_3);
|
||||||
|
}
|
||||||
|
assert_eq!(legacy_packet_distance_squared(0.0), Some(0.0));
|
||||||
|
for invalid in [-1.0, f32::NAN, f32::INFINITY] {
|
||||||
|
assert_eq!(legacy_packet_distance_squared(invalid), None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -61,8 +61,9 @@ use fparkan_render::{
|
|||||||
RawCameraTransform,
|
RawCameraTransform,
|
||||||
};
|
};
|
||||||
use fparkan_render_vulkan::{
|
use fparkan_render_vulkan::{
|
||||||
node38_pose, node38_pose_from_hierarchy, node38_pose_relative_to_root,
|
legacy_packet_distance_squared, node38_pose, node38_pose_from_hierarchy,
|
||||||
node38_pose_relative_to_root_from_hierarchy, project_land_msh_to_static_mesh_in_world_space,
|
node38_pose_relative_to_root, node38_pose_relative_to_root_from_hierarchy,
|
||||||
|
project_land_msh_to_static_mesh_in_world_space,
|
||||||
project_msh_to_static_mesh_in_world_space_with_node_fallback_poses,
|
project_msh_to_static_mesh_in_world_space_with_node_fallback_poses,
|
||||||
project_msh_to_static_mesh_in_world_space_with_node_fallback_poses_and_mount,
|
project_msh_to_static_mesh_in_world_space_with_node_fallback_poses_and_mount,
|
||||||
project_msh_to_static_mesh_in_world_space_with_node_pose_buffer,
|
project_msh_to_static_mesh_in_world_space_with_node_pose_buffer,
|
||||||
@@ -1373,7 +1374,7 @@ fn native_msh_diffuse_alpha_override(
|
|||||||
let distance = if range.batch_flags & 0x0050 == 0x0040 {
|
let distance = if range.batch_flags & 0x0050 == 0x0040 {
|
||||||
0.0
|
0.0
|
||||||
} else {
|
} else {
|
||||||
// shortcut: use the first indexed vertex; replace with native CPU clipping selection when clipped-portal captures are available.
|
// The native alpha callback reads the input packet before renderer clipping.
|
||||||
first_indexed_vertex_distance(mesh, range_index, eye_world)?
|
first_indexed_vertex_distance(mesh, range_index, eye_world)?
|
||||||
};
|
};
|
||||||
legacy_msh_portal_alpha(range.batch_flags, distance, horizontal_fov)
|
legacy_msh_portal_alpha(range.batch_flags, distance, horizontal_fov)
|
||||||
@@ -3978,10 +3979,7 @@ fn first_indexed_vertex_distance(
|
|||||||
.get(vertex_index)
|
.get(vertex_index)
|
||||||
.ok_or_else(|| "world draw range vertex index is outside the vertex buffer".to_string())?;
|
.ok_or_else(|| "world draw range vertex index is outside the vertex buffer".to_string())?;
|
||||||
let delta = sub3(vertex.position, camera_position);
|
let delta = sub3(vertex.position, camera_position);
|
||||||
let distance = dot3(delta, delta).sqrt();
|
legacy_packet_distance_squared(dot3(delta, delta))
|
||||||
distance
|
|
||||||
.is_finite()
|
|
||||||
.then_some(distance)
|
|
||||||
.ok_or_else(|| "world draw range sort distance is non-finite".to_string())
|
.ok_or_else(|| "world draw range sort distance is non-finite".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -9601,17 +9599,6 @@ impl StaticVulkanApp {
|
|||||||
horizontal_fov(&self.view_camera, aspect),
|
horizontal_fov(&self.view_camera, aspect),
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
let approximate_portals = self
|
|
||||||
.world_range_indices
|
|
||||||
.iter()
|
|
||||||
.filter(|&&index| {
|
|
||||||
let flags = self.mesh.draw_ranges[index].batch_flags;
|
|
||||||
flags & 8 != 0
|
|
||||||
&& flags & 0x50 != 0x40
|
|
||||||
&& !self.type16_present_ranges.contains(&index)
|
|
||||||
})
|
|
||||||
.count();
|
|
||||||
eprintln!("render_coverage portal_fade_packet_selection_approx={approximate_portals} (first indexed vertex; native CPU clipping selection pending)");
|
|
||||||
eprintln!("render_coverage msh_type16_colors_unsupported={} (authored vertex colors are not decoded; using white)", self.type16_present_ranges.len());
|
eprintln!("render_coverage msh_type16_colors_unsupported={} (authored vertex colors are not decoded; using white)", self.type16_present_ranges.len());
|
||||||
let randomized_lights = self
|
let randomized_lights = self
|
||||||
.control_point_light_slots
|
.control_point_light_slots
|
||||||
@@ -13355,7 +13342,8 @@ mod tests {
|
|||||||
let alpha = native_msh_diffuse_alpha_override(&mesh, 0, false, [0.0, -0.55, -25.0], 1.04)
|
let alpha = native_msh_diffuse_alpha_override(&mesh, 0, false, [0.0, -0.55, -25.0], 1.04)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!((alpha - 0.5).abs() < 0.000_001);
|
// Native RSQRTSS perturbs the distance before the extra square root.
|
||||||
|
assert!((alpha - 0.5).abs() < 0.000_3);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
native_msh_diffuse_alpha_override(&mesh, 0, false, [0.0, -0.55, -60.0], 1.04),
|
native_msh_diffuse_alpha_override(&mesh, 0, false, [0.0, -0.55, -60.0], 1.04),
|
||||||
Ok(Some(1.0))
|
Ok(Some(1.0))
|
||||||
@@ -15360,7 +15348,97 @@ mod tests {
|
|||||||
let mesh = VulkanStaticMesh::smoke_triangle();
|
let mesh = VulkanStaticMesh::smoke_triangle();
|
||||||
let distance = first_indexed_vertex_distance(&mesh, 0, [0.0, -0.55, -3.0])
|
let distance = first_indexed_vertex_distance(&mesh, 0, [0.0, -0.55, -3.0])
|
||||||
.expect("first indexed vertex distance");
|
.expect("first indexed vertex distance");
|
||||||
assert!((distance - 3.0).abs() < 1.0e-6);
|
assert!((distance - 3.0).abs() < 1.0e-3);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore = "requires the original GOG installation and native SSE reference"]
|
||||||
|
fn gog_portal_alpha_keeps_the_reference_vertex_outside_the_viewport() {
|
||||||
|
let root = std::env::var_os("FPARKAN_CORPUS_PART1_ROOT")
|
||||||
|
.expect("set FPARKAN_CORPUS_PART1_ROOT to the original GOG installation");
|
||||||
|
let mut engine =
|
||||||
|
create(EngineServices::new(Arc::new(DirectoryVfs::new(root)))).expect("engine");
|
||||||
|
load_mission_static_preview_roots(
|
||||||
|
&mut engine,
|
||||||
|
MissionRequest {
|
||||||
|
key: "MISSIONS/Autodemo.00/data.tma".to_string(),
|
||||||
|
mode: MissionMode::SingleMission,
|
||||||
|
},
|
||||||
|
NonZeroUsize::new(usize::MAX).unwrap(),
|
||||||
|
)
|
||||||
|
.expect("original AutoDemo");
|
||||||
|
let assets = loaded_mission_assets(&engine).expect("mission assets");
|
||||||
|
let root = &loaded_mission_object_drafts(&engine).expect("mission objects")[4];
|
||||||
|
let visual = assets
|
||||||
|
.visual_by_id(assets.object_visuals[4][0])
|
||||||
|
.expect("factory visual");
|
||||||
|
let model = assets.model_by_id(visual.model_id.unwrap()).unwrap();
|
||||||
|
let mesh = project_msh_to_static_mesh_in_world_space_with_node_sampled_poses(
|
||||||
|
&model.validated,
|
||||||
|
LegacyIron3dEulerTransform {
|
||||||
|
translation: root.position,
|
||||||
|
orientation_radians: root.orientation_raw,
|
||||||
|
},
|
||||||
|
root.scale,
|
||||||
|
0,
|
||||||
|
)
|
||||||
|
.expect("factory mesh");
|
||||||
|
// GOG capture: yaw/roll expose only part of the window; alpha was read
|
||||||
|
// after Terrain's callback stored it, before the renderer consumed it.
|
||||||
|
let transform = RawCameraTransform {
|
||||||
|
words: [
|
||||||
|
3_204_541_090,
|
||||||
|
3_209_392_467,
|
||||||
|
1_051_461_443,
|
||||||
|
1_135_728_591,
|
||||||
|
1_063_051_501,
|
||||||
|
3_203_294_891,
|
||||||
|
1_045_010_100,
|
||||||
|
1_143_538_729,
|
||||||
|
0,
|
||||||
|
1_053_254_103,
|
||||||
|
1_064_028_839,
|
||||||
|
1_093_589_271,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
1_065_353_216,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
let camera = VulkanStaticCamera::from_legacy_d3d7(
|
||||||
|
transform,
|
||||||
|
LegacyD3d7Projection {
|
||||||
|
viewport: [0, 0, 800, 600],
|
||||||
|
near_plane: 0.5,
|
||||||
|
far_plane: 700.0,
|
||||||
|
field_of_view_radians: 1.04,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("native edge camera");
|
||||||
|
let range = mesh.draw_ranges[33];
|
||||||
|
assert_eq!(range.batch_flags, 0x0108);
|
||||||
|
let clip_vertex = |offset: usize| -> [f32; 4] {
|
||||||
|
let index = mesh.indices[range.first_index as usize + offset] as usize;
|
||||||
|
let [x, y, z] = mesh.vertices[index].position;
|
||||||
|
let point = [x, y, z, 1.0];
|
||||||
|
std::array::from_fn(|column| {
|
||||||
|
(0..4)
|
||||||
|
.map(|row| point[row] * camera.clip_from_world[row * 4 + column])
|
||||||
|
.sum()
|
||||||
|
})
|
||||||
|
};
|
||||||
|
let outside = clip_vertex(0);
|
||||||
|
let inside = clip_vertex(1);
|
||||||
|
assert!(outside[0] > outside[3]);
|
||||||
|
assert!(inside[0].abs() < inside[3]);
|
||||||
|
let distance = first_indexed_vertex_distance(&mesh, 33, transform.translation())
|
||||||
|
.expect("portal reference distance");
|
||||||
|
assert!((distance - f32::from_bits(0x42D5_7EE4)).abs() < 2.0e-5);
|
||||||
|
let alpha =
|
||||||
|
native_msh_diffuse_alpha_override(&mesh, 33, false, transform.translation(), 1.04)
|
||||||
|
.expect("portal alpha")
|
||||||
|
.expect("portal override");
|
||||||
|
assert!((alpha - f32::from_bits(0x3F05_190C)).abs() < 1.0e-6);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -16515,7 +16593,7 @@ mod tests {
|
|||||||
let expected = dot3(expected, expected).sqrt();
|
let expected = dot3(expected, expected).sqrt();
|
||||||
let actual = first_indexed_vertex_distance(&mesh, range.range_index, camera)
|
let actual = first_indexed_vertex_distance(&mesh, range.range_index, camera)
|
||||||
.expect("native Op9 primitive distance key");
|
.expect("native Op9 primitive distance key");
|
||||||
assert!((actual - expected).abs() < 1e-5);
|
assert!((actual - expected).abs() < expected * 0.000_3 + 1e-5);
|
||||||
}
|
}
|
||||||
|
|
||||||
for selector in [1, 2] {
|
for selector in [1, 2] {
|
||||||
|
|||||||
@@ -382,8 +382,9 @@ pub enum LegacyCullMode {
|
|||||||
|
|
||||||
/// CPU diffuse alpha for a colourless MSH batch carrying flag `0x0008`.
|
/// CPU diffuse alpha for a colourless MSH batch carrying flag `0x0008`.
|
||||||
///
|
///
|
||||||
/// `packet_distance` is the distance to the packet's first accepted vertex,
|
/// `packet_distance` is the native distance to the input packet's first indexed
|
||||||
/// after its object transform. Flag `0x0010` takes precedence over `0x0040`.
|
/// vertex after its object transform, even when that vertex is outside the
|
||||||
|
/// viewport. Flag `0x0010` takes precedence over `0x0040`.
|
||||||
/// Returns `None` for invalid distance or projection inputs. Callers must keep
|
/// Returns `None` for invalid distance or projection inputs. Callers must keep
|
||||||
/// the material alpha for batches with a type-16 colour stream or without `0x0008`.
|
/// the material alpha for batches with a type-16 colour stream or without `0x0008`.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
|
|||||||
@@ -50,9 +50,10 @@ MSH-запрос со свободной preview-камерой; это не п
|
|||||||
стратегической и следящей камерами не подтверждено
|
стратегической и следящей камерами не подтверждено
|
||||||
([MSH](../reference/msh.md)).
|
([MSH](../reference/msh.md)).
|
||||||
|
|
||||||
Кривые alpha и метрика расстояния порталов реализованы для MSH без type 16
|
Кривые alpha и опорная вершина порталов сверены для MSH без type 16
|
||||||
([описание кадра](../reference/render-frame.md#прозрачность-порталов-msh)). Native CPU clipping может выбрать другую первую вершину;
|
([описание кадра](../reference/render-frame.md#прозрачность-порталов-msh)). Native `RSQRTSS`
|
||||||
это приближение явно считается как `portal_fade_packet_selection_approx`.
|
воспроизводится на x86-64; другие архитектуры и нулевая дистанция используют
|
||||||
|
явные `packet_distance_native_rsqrt_unsupported` / `packet_zero_distance_fallback`.
|
||||||
|
|
||||||
Данные type 16 ещё не декодированы и считаются в `msh_type16_colors_unsupported`;
|
Данные type 16 ещё не декодированы и считаются в `msh_type16_colors_unsupported`;
|
||||||
в статических сценах 29 миссий GOG (1736 загрузок моделей) этот поток отсутствует.
|
в статических сценах 29 миссий GOG (1736 загрузок моделей) этот поток отсутствует.
|
||||||
|
|||||||
@@ -68,13 +68,24 @@ RVA `0x1B0F0`, заменяет diffuse alpha пакета без type 16 с bat
|
|||||||
Скаляр камеры в шестом float getter `0x4F5F0` и поле CShade `+0xBB4`
|
Скаляр камеры в шестом float getter `0x4F5F0` и поле CShade `+0xBB4`
|
||||||
проверены в работающем оригинале: оба содержат текущий горизонтальный FOV.
|
проверены в работающем оригинале: оба содержат текущий горизонтальный FOV.
|
||||||
|
|
||||||
`legacy_msh_portal_alpha` хранит кривую в `fparkan-render`. Приложение пока
|
`legacy_msh_portal_alpha` хранит кривую в `fparkan-render`. Её опорная вершина
|
||||||
берёт первую индексированную вершину своего диапазона и обычный `sqrt`,
|
принадлежит входному пакету: выбирать вместо неё первую видимую точку нельзя.
|
||||||
поэтому выбор вершины после native CPU clipping и точность `RSQRTSS` не
|
В живом краевом ракурсе AutoDemo первая вершина окна находится за правой
|
||||||
воспроизводятся. Счётчик `render_coverage portal_fade_packet_selection_approx`
|
границей viewport (`NDC x ≈ 1,048`), а следующая — внутри (`x ≈ 0,815`).
|
||||||
показывает число таких диапазонов. Поток type 16 сохраняет прежний отдельный
|
Оригинал всё равно использует первую: расстояние `106,74783` даёт alpha
|
||||||
путь и ещё не декодирован. Это ограничивает приёмку на частично отсечённых
|
`0,51991343`. Выбор следующей изменил бы alpha примерно на `0,026`.
|
||||||
порталах и не позволяет считать интерьер принятой эталонной сценой.
|
Передача пакета renderer-у происходит после записи этой alpha.
|
||||||
|
|
||||||
|
Адаптер изолирует инструкцию `RSQRTSS`; на x86-64 расстояние и alpha этого
|
||||||
|
ракурса совпадают с оригиналом побитно. Обычный `sqrt` давал alpha
|
||||||
|
`0,5196797`: при упаковке прозрачности этого достаточно для отличия отдельных
|
||||||
|
пикселей. Прежнее предположение о выборе вершины после clipping и связанный
|
||||||
|
счётчик `portal_fade_packet_selection_approx` удалены. На других архитектурах
|
||||||
|
сохраняется переносимый корень с явным `packet_distance_native_rsqrt_unsupported`;
|
||||||
|
совпадение камеры с вершиной использует нулевую дистанцию и
|
||||||
|
`packet_zero_distance_fallback` вместо native `0 × infinity`.
|
||||||
|
Поток type 16 ещё не декодирован. Проверка краевого окна не закрывает остальные
|
||||||
|
внутренние ракурсы завода.
|
||||||
|
|
||||||
## Дистанционная кривая тумана
|
## Дистанционная кривая тумана
|
||||||
|
|
||||||
@@ -151,9 +162,8 @@ raw-флаге `Batch20` `0x0008` получает переопределени
|
|||||||
материал; alpha `0` временно задаёт число индексов `0`, а при очистке
|
материал; alpha `0` временно задаёт число индексов `0`, а при очистке
|
||||||
переопределения исходное число индексов восстанавливается.
|
переопределения исходное число индексов восстанавливается.
|
||||||
|
|
||||||
Fade обновляется с камерой каждый кадр. Открыт выбор первой принятой вершины
|
Fade обновляется с камерой каждый кадр и сохраняет исходную опорную вершину
|
||||||
после native CPU clipping: приложение пока берёт первую индексированную
|
диапазона, в том числе когда часть окна выходит за границу экрана.
|
||||||
вершину диапазона и считает это приближение в `render_coverage`.
|
|
||||||
|
|
||||||
В превью с фиксированной камерой диапазоны туманности и звёзд, купола и облаков
|
В превью с фиксированной камерой диапазоны туманности и звёзд, купола и облаков
|
||||||
явно помечаются как прозрачный фон. Непрозрачные диапазоны мира остаются на
|
явно помечаются как прозрачный фон. Непрозрачные диапазоны мира остаются на
|
||||||
|
|||||||
@@ -510,8 +510,11 @@ Material manager кэширует разобранный MAT0 и texture handles
|
|||||||
Для MSH без потока цветов type 16 правило включает флаг batch `0x0008`.
|
Для MSH без потока цветов type 16 правило включает флаг batch `0x0008`.
|
||||||
В обычной ветви при горизонтальном FOV `1.3` поверхность прозрачна до расстояния
|
В обычной ветви при горизонтальном FOV `1.3` поверхность прозрачна до расстояния
|
||||||
75 и непрозрачна после 95; между ними alpha растёт линейно. При другом FOV обе
|
75 и непрозрачна после 95; между ними alpha растёт линейно. При другом FOV обе
|
||||||
границы умножаются на `1.3 / FOV`. Расстояние берётся до первой вершины первого
|
границы умножаются на `1.3 / FOV`. Расстояние берётся до первой индексированной
|
||||||
принятого треугольника, а не до центра здания.
|
вершины входного пакета, а не до центра здания. Эта точка может находиться за
|
||||||
|
экраном: видимая часть окна всё равно сохраняет её прозрачность. Если при повороте
|
||||||
|
камеры выбирать новую видимую точку, окно начнёт менять прозрачность без движения
|
||||||
|
игрока.
|
||||||
|
|
||||||
Флаг `0x0010` выбирает другую кривую: сравнивается квадратный корень уже
|
Флаг `0x0010` выбирает другую кривую: сравнивается квадратный корень уже
|
||||||
вычисленного расстояния, границы равны `2.6 / FOV` и `7.7999997 / FOV`.
|
вычисленного расстояния, границы равны `2.6 / FOV` и `7.7999997 / FOV`.
|
||||||
@@ -521,8 +524,9 @@ Material manager кэширует разобранный MAT0 и texture handles
|
|||||||
|
|
||||||
FParkan пересчитывает эту alpha при каждом обновлении камеры, после анимации
|
FParkan пересчитывает эту alpha при каждом обновлении камеры, после анимации
|
||||||
модели. Нулевое значение временно выключает диапазон индексов; при удалении
|
модели. Нулевое значение временно выключает диапазон индексов; при удалении
|
||||||
камеры диапазон восстанавливается. Подробности оригинального вычисления и
|
камеры диапазон восстанавливается. Расстояние использует тот же приближённый
|
||||||
пока приближённого выбора вершины приведены в [описании кадра](../reference/render-frame.md#прозрачность-порталов-msh).
|
обратный корень, что и оригинал: округление влияет на записанную alpha.
|
||||||
|
Подробности вычисления приведены в [описании кадра](../reference/render-frame.md#прозрачность-порталов-msh).
|
||||||
|
|
||||||
## Texm: текстуры, mip-уровни и атласы
|
## Texm: текстуры, mip-уровни и атласы
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user