fix: preserve native sky sprite screen depth
Docs Deploy / Build and Deploy MkDocs (push) Successful in 33s
Docs Deploy / Build and Deploy MkDocs (push) Successful in 33s
This commit is contained in:
7 files changed
+221
-28
No files matched your search
@@ -232,9 +232,10 @@ void main() {
|
||||
out_uv = in_uv;
|
||||
out_detail_uv = in_detail_uv;
|
||||
out_overlay_alpha = in_overlay_alpha;
|
||||
out_fog_factor = native_fog_factor(in_position);
|
||||
int mode = int(round(material.material_mode));
|
||||
int combiner_mode = mode & 3;
|
||||
bool screen_tl = (mode & 8) != 0;
|
||||
out_fog_factor = screen_tl ? 0.0 : native_fog_factor(in_position);
|
||||
if (combiner_mode == 0) {
|
||||
LightingTerms base_terms = native_material_lighting(
|
||||
in_position,
|
||||
@@ -262,12 +263,14 @@ void main() {
|
||||
out_overlay_diffuse = vec3(1.0);
|
||||
out_overlay_specular = vec3(0.0);
|
||||
}
|
||||
vec4 clip_position = frame.clip_from_world * vec4(in_position, 1.0);
|
||||
vec4 clip_position = screen_tl
|
||||
? vec4(in_position, 1.0)
|
||||
: frame.clip_from_world * vec4(in_position, 1.0);
|
||||
// Sky far-depth is a Vulkan projection choice for layers authored around
|
||||
// the camera. Keep world positions, normals, and UVs intact while placing
|
||||
// the resulting primitive at the far depth; this does not assert native
|
||||
// D3D depth-bit equivalence.
|
||||
if ((mode & 4) != 0) {
|
||||
if ((mode & 4) != 0 && !screen_tl) {
|
||||
clip_position.z = clip_position.w;
|
||||
}
|
||||
gl_Position = clip_position;
|
||||
|
||||
Binary file not shown.
@@ -3,7 +3,7 @@
|
||||
use ash::vk;
|
||||
use fparkan_render::{LegacyDepthMode, LegacyPipelineState};
|
||||
|
||||
use super::smoke_types::resolve_draw_texture_indices;
|
||||
use super::smoke_types::{resolve_draw_texture_indices, VulkanStaticMaterial};
|
||||
use super::update_material_texture_descriptor;
|
||||
use super::{
|
||||
create_command_pool, create_frame_sync, create_index_buffer, create_static_mesh_index_buffer,
|
||||
@@ -361,18 +361,7 @@ impl VulkanSmokeRenderer {
|
||||
create_info
|
||||
.materials
|
||||
.iter()
|
||||
.map(|material| {
|
||||
let combiner_mode = if material.lightmap_mode {
|
||||
3.0
|
||||
} else if material.sky_nebula_stars {
|
||||
2.0
|
||||
} else if material.unlit {
|
||||
1.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
combiner_mode + if material.sky_far_depth { 4.0 } else { 0.0 }
|
||||
})
|
||||
.map(VulkanStaticMaterial::encoded_material_mode)
|
||||
.collect()
|
||||
};
|
||||
let material_specular = if create_info.materials.is_empty() {
|
||||
|
||||
@@ -505,6 +505,33 @@ pub struct VulkanStaticMaterial {
|
||||
/// renderer projection choice; it is not a claim about the original D3D
|
||||
/// depth-bit encoding.
|
||||
pub sky_far_depth: bool,
|
||||
/// Push material mode bit 8 and treat positions as Vulkan clip-space
|
||||
/// `XYZRHW` vertices. Their x/y coordinates are converted from viewport
|
||||
/// pixels by the caller and z retains the native `0.999` depth.
|
||||
pub screen_tl: bool,
|
||||
}
|
||||
|
||||
impl VulkanStaticMaterial {
|
||||
pub(crate) fn encoded_material_mode(&self) -> f32 {
|
||||
let combiner_mode = if self.screen_tl {
|
||||
1.0
|
||||
} else if self.lightmap_mode {
|
||||
3.0
|
||||
} else if self.sky_nebula_stars {
|
||||
2.0
|
||||
} else if self.unlit {
|
||||
1.0
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
combiner_mode
|
||||
+ if self.sky_far_depth && !self.screen_tl {
|
||||
4.0
|
||||
} else {
|
||||
0.0
|
||||
}
|
||||
+ if self.screen_tl { 8.0 } else { 0.0 }
|
||||
}
|
||||
}
|
||||
|
||||
/// One authored RGBA8 mip level accepted by the Vulkan texture upload path.
|
||||
@@ -1028,6 +1055,7 @@ mod static_mesh_tests {
|
||||
sky_nebula_stars: false,
|
||||
lightmap_mode: false,
|
||||
sky_far_depth: false,
|
||||
screen_tl: false,
|
||||
},
|
||||
VulkanStaticMaterial {
|
||||
material_index: 7,
|
||||
@@ -1054,9 +1082,19 @@ mod static_mesh_tests {
|
||||
sky_nebula_stars: false,
|
||||
lightmap_mode: false,
|
||||
sky_far_depth: false,
|
||||
screen_tl: false,
|
||||
},
|
||||
];
|
||||
|
||||
let mut screen_tl = materials[0].clone();
|
||||
screen_tl.screen_tl = true;
|
||||
screen_tl.sky_far_depth = true;
|
||||
assert_eq!(screen_tl.encoded_material_mode(), 9.0);
|
||||
let mut far_sky = materials[0].clone();
|
||||
far_sky.unlit = true;
|
||||
far_sky.sky_far_depth = true;
|
||||
assert_eq!(far_sky.encoded_material_mode(), 5.0);
|
||||
|
||||
assert_eq!(
|
||||
resolve_draw_texture_indices(&ranges, &materials),
|
||||
Ok(vec![1, 0])
|
||||
|
||||
@@ -533,7 +533,7 @@ fn native_specular_reference_uses_squared_power_and_total_overflow_curve() {
|
||||
fn shader_computes_radial_fog_per_vertex_and_interpolates_it() {
|
||||
let vertex = include_str!("../../shaders/triangle.vert");
|
||||
let fragment = include_str!("../../shaders/triangle.frag");
|
||||
assert!(vertex.contains("out_fog_factor = native_fog_factor(in_position);"));
|
||||
assert!(vertex.contains("out_fog_factor = screen_tl ? 0.0 : native_fog_factor(in_position);"));
|
||||
assert!(fragment.contains("layout(location = 4) in float in_fog_factor;"));
|
||||
assert!(!fragment.contains("distance(in_world_position"));
|
||||
assert!(!fragment.contains("float fog_factor()"));
|
||||
|
||||
+145
-11
@@ -1261,6 +1261,7 @@ fn append_environment_material(
|
||||
sky_nebula_stars: false,
|
||||
lightmap_mode: false,
|
||||
sky_far_depth: matches!(fallback_row, 0..=2),
|
||||
screen_tl: ENVIRONMENT_SPRITE_ROWS.contains(&fallback_row),
|
||||
});
|
||||
phase_material_indices.push(index);
|
||||
}
|
||||
@@ -1321,6 +1322,7 @@ fn append_environment_gradient_material(
|
||||
sky_nebula_stars: false,
|
||||
lightmap_mode: false,
|
||||
sky_far_depth: true,
|
||||
screen_tl: false,
|
||||
});
|
||||
Ok(index)
|
||||
}
|
||||
@@ -1371,6 +1373,7 @@ fn append_shadow_material(materials: &mut Vec<VulkanStaticMaterial>) -> Result<u
|
||||
sky_nebula_stars: false,
|
||||
lightmap_mode: false,
|
||||
sky_far_depth: false,
|
||||
screen_tl: false,
|
||||
});
|
||||
Ok(index)
|
||||
}
|
||||
@@ -2648,17 +2651,33 @@ impl EnvironmentGpuScene {
|
||||
viewport,
|
||||
horizontal_fov(camera, viewport[0] / viewport[1].max(1.0)),
|
||||
) {
|
||||
vertices = Some(sprite_quad_vertices_pixels(
|
||||
camera,
|
||||
frame.direction,
|
||||
(camera.far_plane * 0.5).max(camera.near_plane * 2.0),
|
||||
half_width,
|
||||
half_height,
|
||||
viewport,
|
||||
frame.color,
|
||||
frame.alpha,
|
||||
frame.uv,
|
||||
));
|
||||
if ENVIRONMENT_SPRITE_ROWS.contains(&sprite.row) {
|
||||
if let Some(center_pixels) = projected {
|
||||
vertices = Some(screen_tl_quad_vertices_pixels(
|
||||
center_pixels,
|
||||
half_width,
|
||||
half_height,
|
||||
viewport,
|
||||
frame.color,
|
||||
frame.alpha,
|
||||
frame.uv,
|
||||
));
|
||||
} else {
|
||||
active = false;
|
||||
}
|
||||
} else {
|
||||
vertices = Some(sprite_quad_vertices_pixels(
|
||||
camera,
|
||||
frame.direction,
|
||||
(camera.far_plane * 0.5).max(camera.near_plane * 2.0),
|
||||
half_width,
|
||||
half_height,
|
||||
viewport,
|
||||
frame.color,
|
||||
frame.alpha,
|
||||
frame.uv,
|
||||
));
|
||||
}
|
||||
} else {
|
||||
active = false;
|
||||
}
|
||||
@@ -3083,6 +3102,50 @@ fn sprite_quad_vertices_pixels(
|
||||
]
|
||||
}
|
||||
|
||||
fn screen_tl_quad_vertices_pixels(
|
||||
center_pixels: [f32; 2],
|
||||
half_width_pixels: f32,
|
||||
half_height_pixels: f32,
|
||||
viewport: [f32; 2],
|
||||
color: [f32; 3],
|
||||
alpha: f32,
|
||||
uv: [[f32; 2]; 4],
|
||||
) -> [VulkanStaticVertex; 4] {
|
||||
let width = viewport[0].max(1.0);
|
||||
let height = viewport[1].max(1.0);
|
||||
let half_width = half_width_pixels.max(0.0);
|
||||
let half_height = half_height_pixels.max(0.0);
|
||||
let pixel_corners = [
|
||||
[
|
||||
center_pixels[0] - half_width,
|
||||
center_pixels[1] + half_height,
|
||||
],
|
||||
[
|
||||
center_pixels[0] + half_width,
|
||||
center_pixels[1] + half_height,
|
||||
],
|
||||
[
|
||||
center_pixels[0] - half_width,
|
||||
center_pixels[1] - half_height,
|
||||
],
|
||||
[
|
||||
center_pixels[0] + half_width,
|
||||
center_pixels[1] - half_height,
|
||||
],
|
||||
];
|
||||
// Positive-height Vulkan viewports map NDC -Y to top-down screen pixels.
|
||||
let uv = [uv[1], uv[2], uv[0], uv[3]];
|
||||
std::array::from_fn(|index| {
|
||||
let pixel = pixel_corners[index];
|
||||
let position = [
|
||||
pixel[0] * (2.0 / width) - 1.0,
|
||||
pixel[1] * (2.0 / height) - 1.0,
|
||||
0.999,
|
||||
];
|
||||
environment_vertex_with_alpha(position, color, alpha, uv[index])
|
||||
})
|
||||
}
|
||||
|
||||
fn precipitation_quad_vertices(
|
||||
kind: PrecipitationKind,
|
||||
screen: &ScreenBillboard,
|
||||
@@ -4305,6 +4368,7 @@ fn static_preview_terrain_base_materials(
|
||||
sky_nebula_stars: false,
|
||||
lightmap_mode: false,
|
||||
sky_far_depth: false,
|
||||
screen_tl: false,
|
||||
});
|
||||
phase_material_indices.push(usize::from(preview_selector));
|
||||
}
|
||||
@@ -4510,6 +4574,7 @@ fn static_preview_component_materials(
|
||||
sky_nebula_stars: false,
|
||||
lightmap_mode: false,
|
||||
sky_far_depth: false,
|
||||
screen_tl: false,
|
||||
});
|
||||
phase_material_indices.push(usize::from(preview_selector));
|
||||
}
|
||||
@@ -7336,4 +7401,73 @@ mod tests {
|
||||
[uv[1], uv[2], uv[0], uv[3]]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_tl_sprite_pixels_use_current_viewport_and_preserve_depth_999() {
|
||||
let uv = [[0.0, 0.0], [0.0, 1.0], [1.0, 1.0], [1.0, 0.0]];
|
||||
let small = screen_tl_quad_vertices_pixels(
|
||||
[160.0, 120.0],
|
||||
40.0,
|
||||
30.0,
|
||||
[640.0, 480.0],
|
||||
[1.0; 3],
|
||||
1.0,
|
||||
uv,
|
||||
);
|
||||
let resized = screen_tl_quad_vertices_pixels(
|
||||
[320.0, 240.0],
|
||||
80.0,
|
||||
60.0,
|
||||
[1280.0, 960.0],
|
||||
[1.0; 3],
|
||||
1.0,
|
||||
uv,
|
||||
);
|
||||
let wide = screen_tl_quad_vertices_pixels(
|
||||
[640.0, 360.0],
|
||||
100.0,
|
||||
45.0,
|
||||
[1280.0, 720.0],
|
||||
[1.0; 3],
|
||||
1.0,
|
||||
uv,
|
||||
);
|
||||
|
||||
for (small, resized) in small.iter().zip(resized) {
|
||||
for axis in 0..2 {
|
||||
assert!((small.position[axis] - resized.position[axis]).abs() < 1.0e-6);
|
||||
}
|
||||
assert_eq!(small.position[2], 0.999);
|
||||
assert_eq!(resized.position[2], 0.999);
|
||||
}
|
||||
assert_eq!(small[0].uv, [0.0, 1.0]);
|
||||
assert_eq!(small[2].uv, [0.0, 0.0]);
|
||||
for (actual, expected) in [
|
||||
(wide[0].position, [-0.15625, 0.125, 0.999]),
|
||||
(wide[2].position, [-0.15625, -0.125, 0.999]),
|
||||
] {
|
||||
for axis in 0..3 {
|
||||
assert!((actual[axis] - expected[axis]).abs() < 1.0e-6);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn native_tl_depth_999_is_nearer_than_opaque_geometry_at_320_units() {
|
||||
let camera = FreeFlightCamera {
|
||||
position: [0.0; 3],
|
||||
yaw: 0.0,
|
||||
pitch: 0.0,
|
||||
horizontal_fov: 1.3,
|
||||
near_plane: 0.5,
|
||||
far_plane: 700.0,
|
||||
move_speed: 1.0,
|
||||
};
|
||||
let frame = camera.preview_frame(16.0 / 9.0);
|
||||
let opaque_clip = row_vector_transform([320.0, 0.0, 0.0, 1.0], frame.clip_from_world);
|
||||
let opaque_depth = opaque_clip[2] / opaque_clip[3];
|
||||
|
||||
assert!((opaque_depth - 0.999151_18).abs() < 1.0e-6);
|
||||
assert!(0.999 < opaque_depth);
|
||||
}
|
||||
}
|
||||
@@ -207,6 +207,35 @@ packed: [key.colors[10], key.colors[14]],
|
||||
`AtmosphereFrame` пока не несёт этот бит обновления, поэтому sampler
|
||||
выдаёт интерполированный цвет при каждом запросе.
|
||||
|
||||
## Экранная геометрия солнца и луны
|
||||
|
||||
`CSun::Render` сначала переводит направление небесного тела в пиксели текущего
|
||||
viewport, затем собирает quad с native XYZRHW-позицией. При положительной высоте
|
||||
viewport Vulkan пиксели переводятся в clip-space так:
|
||||
|
||||
```rust
|
||||
position = [
|
||||
pixel[0] * (2.0 / width) - 1.0,
|
||||
pixel[1] * (2.0 / height) - 1.0,
|
||||
0.999,
|
||||
];
|
||||
```
|
||||
|
||||
Vertex shader пропускает `clip_from_world` для строк солнца и луны. CPU каждый
|
||||
кадр пересчитывает центр и размер quad по текущему viewport; старые пиксельные
|
||||
координаты не переносятся на новый размер. World-space billboards бликов
|
||||
остаются отдельным путём. Материал экранного quad не освещается, vertex fog
|
||||
отключён, а sky far-depth bit к нему не применяется.
|
||||
|
||||
Native `z = 0.999` не означает, что спрайт гарантированно лежит за любой
|
||||
поверхностью. Vulkan использует `LESS_OR_EQUAL` и не записывает depth для
|
||||
прозрачного ряда. При near `0.5`, far `700` мировая поверхность на расстоянии
|
||||
`320` получает depth
|
||||
`700 / (700 - 0.5) * (1 - 0.5 / 320) ≈ 0.999151`; поэтому экранный `0.999`
|
||||
чуть ближе этой поверхности и проходит depth test перед ней. Это фиксированное
|
||||
native значение сохраняется для совместимости, но само по себе не обещает
|
||||
окклюзию солнца всей геометрией местности.
|
||||
|
||||
## Что остаётся за владельцем
|
||||
|
||||
`EnvironmentSystem` не извлекает камеру из renderer, не разрешает материалы
|
||||
|
||||
Reference in new issue
Block a user