From e8942caa233a663155ffec14c1070ad5dbbb9c7b Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Sun, 1 Nov 2026 13:40:12 +0400 Subject: fix(render): preserve object-local point lights on models --- .../fparkan-render-vulkan/shaders/triangle.vert | 12 ++++++++---- .../fparkan-render-vulkan/shaders/triangle.vert.spv | Bin 20384 -> 20860 bytes adapters/fparkan-render-vulkan/src/asset_mesh.rs | 6 ++++++ .../fparkan-render-vulkan/src/ffi/reflection.rs | 1 + adapters/fparkan-render-vulkan/src/ffi/resources.rs | 3 ++- adapters/fparkan-render-vulkan/src/ffi/smoke.rs | 12 +++++++++++- .../fparkan-render-vulkan/src/ffi/smoke_types.rs | 12 +++++++++++- adapters/fparkan-render-vulkan/src/ffi/tests.rs | 11 ++++++++--- 8 files changed, 47 insertions(+), 10 deletions(-) (limited to 'adapters/fparkan-render-vulkan') diff --git a/adapters/fparkan-render-vulkan/shaders/triangle.vert b/adapters/fparkan-render-vulkan/shaders/triangle.vert index 8e04536..3accd2e 100644 --- a/adapters/fparkan-render-vulkan/shaders/triangle.vert +++ b/adapters/fparkan-render-vulkan/shaders/triangle.vert @@ -45,7 +45,8 @@ layout(std430, set = 0, binding = 5) readonly buffer MaterialSpecular { struct PointLightRecord { vec4 position_range; vec4 rgb_active; - vec4 coefficients_active; + vec3 coefficients; + uint owner_scope; }; layout(std430, set = 0, binding = 6) readonly buffer PointLightBuffer { @@ -197,6 +198,9 @@ LightingTerms native_material_lighting( } for (uint index = 0u; index < point_light_buffer.header.x; ++index) { PointLightRecord point_light = point_light_buffer.records[index]; + if (point_light.owner_scope != 0u && point_light.owner_scope != uint(gl_InstanceIndex)) { + continue; + } vec3 to_light = point_light.position_range.xyz - world_position; float distance_squared = dot(to_light, to_light); float range = point_light.position_range.w; @@ -209,9 +213,9 @@ LightingTerms native_material_lighting( float ndotl = dot(normal, light_direction); if (ndotl > 0.0) { float t = (range - distance_to_light) / range; - float attenuation = point_light.coefficients_active.x - + point_light.coefficients_active.y * t - + point_light.coefficients_active.z * t * t; + float attenuation = point_light.coefficients.x + + point_light.coefficients.y * t + + point_light.coefficients.z * t * t; attenuation = max(attenuation, 0.0); accumulated += ndotl * directional_rgb * point_light.rgb_active.rgb * attenuation; specular += specular_from_light( diff --git a/adapters/fparkan-render-vulkan/shaders/triangle.vert.spv b/adapters/fparkan-render-vulkan/shaders/triangle.vert.spv index 439e2a0..74adcc7 100644 Binary files a/adapters/fparkan-render-vulkan/shaders/triangle.vert.spv and b/adapters/fparkan-render-vulkan/shaders/triangle.vert.spv differ diff --git a/adapters/fparkan-render-vulkan/src/asset_mesh.rs b/adapters/fparkan-render-vulkan/src/asset_mesh.rs index d813b91..f25ab89 100644 --- a/adapters/fparkan-render-vulkan/src/asset_mesh.rs +++ b/adapters/fparkan-render-vulkan/src/asset_mesh.rs @@ -859,6 +859,7 @@ fn append_node_fallback_batch( indices.push(vertex_index); } draw_ranges.push(VulkanStaticDrawRange { + owner_scope: 0, first_index, index_count: u32::from(batch.index_count), material_index: u16::from(batch.material_index), @@ -1246,6 +1247,7 @@ fn static_terrain_draw_ranges( end_face += 1; } ranges.push(VulkanStaticDrawRange { + owner_scope: 0, first_index: u32::try_from(start_face) .map_err(|_| VulkanAssetMeshError::IndexOutOfRange)? .checked_mul(3) @@ -1299,6 +1301,7 @@ fn static_model_indices_and_ranges( indices.push(index); } draw_ranges.push(VulkanStaticDrawRange { + owner_scope: 0, first_index, index_count: u32::from(batch.index_count), material_index: u16::from(batch.material_index), @@ -1507,6 +1510,7 @@ mod tests { assert_eq!( mesh.draw_ranges, vec![VulkanStaticDrawRange { + owner_scope: 0, first_index: 0, index_count: 3, material_index: 0, @@ -2038,6 +2042,7 @@ mod tests { mesh.draw_ranges, vec![ VulkanStaticDrawRange { + owner_scope: 0, first_index: 0, index_count: 3, material_index: 0, @@ -2050,6 +2055,7 @@ mod tests { VulkanStaticDrawRange::NATIVE_DEFAULT_ALPHA_TEST_REFERENCE, }, VulkanStaticDrawRange { + owner_scope: 0, first_index: 3, index_count: 3, material_index: 7, diff --git a/adapters/fparkan-render-vulkan/src/ffi/reflection.rs b/adapters/fparkan-render-vulkan/src/ffi/reflection.rs index 1a9c9ae..6eb6c67 100644 --- a/adapters/fparkan-render-vulkan/src/ffi/reflection.rs +++ b/adapters/fparkan-render-vulkan/src/ffi/reflection.rs @@ -406,6 +406,7 @@ mod tests { ], indices: vec![0, 1, 2], draw_ranges: vec![VulkanStaticDrawRange { + owner_scope: 0, first_index: 0, index_count: 3, material_index: 0, diff --git a/adapters/fparkan-render-vulkan/src/ffi/resources.rs b/adapters/fparkan-render-vulkan/src/ffi/resources.rs index 498a472..449750f 100644 --- a/adapters/fparkan-render-vulkan/src/ffi/resources.rs +++ b/adapters/fparkan-render-vulkan/src/ffi/resources.rs @@ -687,12 +687,13 @@ pub(super) fn encode_point_lights( .chain(light.rgb) .chain([if light.active { 1.0 } else { 0.0 }]) .chain(light.coefficients) - .chain([if light.active { 1.0 } else { 0.0 }]) { bytes[offset..offset + std::mem::size_of::()] .copy_from_slice(&value.to_ne_bytes()); offset += std::mem::size_of::(); } + bytes[offset..offset + 4].copy_from_slice(&light.owner_scope.to_ne_bytes()); + offset += 4; } debug_assert_eq!(offset, total); Ok(bytes) diff --git a/adapters/fparkan-render-vulkan/src/ffi/smoke.rs b/adapters/fparkan-render-vulkan/src/ffi/smoke.rs index a016645..c4f0264 100644 --- a/adapters/fparkan-render-vulkan/src/ffi/smoke.rs +++ b/adapters/fparkan-render-vulkan/src/ffi/smoke.rs @@ -2105,7 +2105,7 @@ impl VulkanSmokeRenderer { 1, range.first_index, 0, - 0, + range.owner_scope, ); } device.device().cmd_end_render_pass(command_buffer); @@ -2898,6 +2898,7 @@ mod tests { }; let mut ranges = vec![ VulkanStaticDrawRange { + owner_scope: 0, first_index: 0, index_count: 3, material_index: 7, @@ -2909,6 +2910,7 @@ mod tests { alpha_test_reference: 17, }, VulkanStaticDrawRange { + owner_scope: 0, first_index: 3, index_count: 3, material_index: 9, @@ -3066,6 +3068,7 @@ mod tests { #[test] fn geometry_updates_dirty_world_order_on_activation_and_deactivation() { let mut ranges = [VulkanStaticDrawRange { + owner_scope: 0, first_index: 0, index_count: 0, material_index: 0, @@ -3253,6 +3256,7 @@ mod tests { fn native_transparent_order_is_descending_and_keeps_fixed_pass_slots() { let ranges = (0..5) .map(|_| super::super::VulkanStaticDrawRange { + owner_scope: 0, first_index: 0, index_count: 3, material_index: 0, @@ -3278,6 +3282,7 @@ mod tests { fn inactive_world_range_is_not_reused_as_a_sort_slot() { let ranges = vec![ super::super::VulkanStaticDrawRange { + owner_scope: 0, first_index: 0, index_count: 3, material_index: 0, @@ -3289,6 +3294,7 @@ mod tests { alpha_test_reference: 0, }, super::super::VulkanStaticDrawRange { + owner_scope: 0, first_index: 3, index_count: 0, material_index: 0, @@ -3300,6 +3306,7 @@ mod tests { alpha_test_reference: 0, }, super::super::VulkanStaticDrawRange { + owner_scope: 0, first_index: 3, index_count: 3, material_index: 0, @@ -3325,6 +3332,7 @@ mod tests { fn native_equal_key_insertion_uses_inclusive_midpoint() { let ranges = (0..4) .map(|index| super::super::VulkanStaticDrawRange { + owner_scope: 0, first_index: index * 3, index_count: 3, material_index: 0, @@ -3419,6 +3427,7 @@ mod tests { const WEATHER: usize = 10; let ranges = (0..11) .map(|index| VulkanStaticDrawRange { + owner_scope: 0, first_index: index * 3, index_count: 3, material_index: 0, @@ -3478,6 +3487,7 @@ mod tests { const WEATHER: usize = 10; let mut ranges = (0..11) .map(|index| VulkanStaticDrawRange { + owner_scope: 0, first_index: index * 3, index_count: 3, material_index: 0, diff --git a/adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs b/adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs index 90fd7b4..9fd0582 100644 --- a/adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs +++ b/adapters/fparkan-render-vulkan/src/ffi/smoke_types.rs @@ -158,6 +158,8 @@ impl Default for VulkanDirectionalLight { /// One point light in the frame lighting contract. #[derive(Clone, Copy, Debug, PartialEq)] pub struct VulkanPointLight { + /// Zero lights the world; a nonzero scope lights only matching object ranges. + pub owner_scope: u32, /// World-space position. pub position: [f32; 3], /// Light RGB multiplier. @@ -173,6 +175,7 @@ pub struct VulkanPointLight { impl Default for VulkanPointLight { fn default() -> Self { Self { + owner_scope: 0, position: [0.0; 3], rgb: [0.0; 3], range: 0.0, @@ -201,7 +204,7 @@ pub const VULKAN_FRAME_SLOT_COUNT: usize = 2; /// `uvec4` header bytes for the dynamic point-light storage buffer. pub const VULKAN_POINT_LIGHT_HEADER_BYTES: usize = 16; -/// Three `vec4` records per dynamically uploaded point light. +/// Two vec4 records, three attenuation floats and one u32 owner scope. pub const VULKAN_POINT_LIGHT_RECORD_BYTES: usize = 3 * 4 * std::mem::size_of::(); /// Bytes occupied by one material's base and overlay specular records. @@ -444,6 +447,8 @@ pub struct VulkanDynamicDrawRange { /// One indexed triangle-list draw retained from an original mesh batch. #[derive(Clone, Copy, Debug, Eq, PartialEq)] pub struct VulkanStaticDrawRange { + /// Root-object lighting scope; zero denotes terrain or unowned geometry. + pub owner_scope: u32, /// First index in the shared index buffer. pub first_index: u32, /// Number of indices in this draw. @@ -787,6 +792,7 @@ impl VulkanStaticMesh { ], indices: vec![0, 1, 2], draw_ranges: vec![VulkanStaticDrawRange { + owner_scope: 0, first_index: 0, index_count: 3, material_index: 0, @@ -872,6 +878,7 @@ mod static_mesh_tests { VulkanDirectionalLight::default(), ], point_light: VulkanPointLight { + owner_scope: 0, position: [28.0, 29.0, 30.0], rgb: [31.0, 32.0, 33.0], range: 34.0, @@ -1073,6 +1080,7 @@ mod static_mesh_tests { vertices: VulkanStaticMesh::smoke_triangle().vertices, indices: vec![0, 1], draw_ranges: vec![VulkanStaticDrawRange { + owner_scope: 0, first_index: 0, index_count: 2, material_index: 0, @@ -1105,6 +1113,7 @@ mod static_mesh_tests { fn material_descriptors_follow_source_batch_selectors() { let ranges = [ VulkanStaticDrawRange { + owner_scope: 0, first_index: 0, index_count: 3, material_index: 7, @@ -1116,6 +1125,7 @@ mod static_mesh_tests { alpha_test_reference: 0, }, VulkanStaticDrawRange { + owner_scope: 0, first_index: 3, index_count: 3, material_index: 2, diff --git a/adapters/fparkan-render-vulkan/src/ffi/tests.rs b/adapters/fparkan-render-vulkan/src/ffi/tests.rs index 4b2f7d2..8dda737 100644 --- a/adapters/fparkan-render-vulkan/src/ffi/tests.rs +++ b/adapters/fparkan-render-vulkan/src/ffi/tests.rs @@ -518,8 +518,9 @@ fn fragment_shader_dispatches_all_four_material_modes() { } #[test] -fn point_light_storage_payload_is_counted_and_packed_as_three_vec4s() { +fn point_light_storage_payload_preserves_integer_owner_scope_and_stride() { let lights = [VulkanPointLight { + owner_scope: 0x1234_5678, position: [1.0, 2.0, 3.0], rgb: [4.0, 5.0, 6.0], range: 7.0, @@ -532,7 +533,7 @@ fn point_light_storage_payload_is_counted_and_packed_as_three_vec4s() { VULKAN_POINT_LIGHT_HEADER_BYTES + VULKAN_POINT_LIGHT_RECORD_BYTES ); assert_eq!(u32::from_ne_bytes(bytes[0..4].try_into().unwrap()), 1); - let values = (0..12) + let values = (0..11) .map(|index| { let offset = VULKAN_POINT_LIGHT_HEADER_BYTES + index * std::mem::size_of::(); f32::from_ne_bytes(bytes[offset..offset + 4].try_into().unwrap()) @@ -540,7 +541,11 @@ fn point_light_storage_payload_is_counted_and_packed_as_three_vec4s() { .collect::>(); assert_eq!( values, - vec![1.0, 2.0, 3.0, 7.0, 4.0, 5.0, 6.0, 1.0, 8.0, 9.0, 10.0, 1.0] + vec![1.0, 2.0, 3.0, 7.0, 4.0, 5.0, 6.0, 1.0, 8.0, 9.0, 10.0] + ); + assert_eq!( + u32::from_ne_bytes(bytes[60..64].try_into().unwrap()), + 0x1234_5678 ); let many = vec![VulkanPointLight::default(); 33]; -- cgit v1.2.3