From 55e98d7ff6224aa3bf4b5d4260a594705ee2133f Mon Sep 17 00:00:00 2001 From: Valentin Popov Date: Sun, 1 Nov 2026 14:39:37 +0400 Subject: fix(render): draw translucent water into planar reflections --- .../fparkan-render-vulkan/shaders/triangle.frag | 11 +++--- .../shaders/triangle.frag.spv | Bin 14716 -> 15060 bytes adapters/fparkan-render-vulkan/src/ffi/smoke.rs | 40 ++++++++++++++++++--- 3 files changed, 42 insertions(+), 9 deletions(-) (limited to 'adapters') diff --git a/adapters/fparkan-render-vulkan/shaders/triangle.frag b/adapters/fparkan-render-vulkan/shaders/triangle.frag index 2c71278..b945905 100644 --- a/adapters/fparkan-render-vulkan/shaders/triangle.frag +++ b/adapters/fparkan-render-vulkan/shaders/triangle.frag @@ -75,7 +75,9 @@ void main() { if ((mode & 32) != 0 && (mode & 4) == 0 && in_world_position.z < frame.camera_position.w) { discard; } - if ((mode & 64) != 0) { + vec2 base_uv = vec2(material.base_page_x, material.base_page_y) + + in_uv * vec2(material.base_page_w, material.base_page_h); + if ((mode & (64 | 256)) != 0) { vec2 reflection_uv = gl_FragCoord.xy / vec2(textureSize(reflection_color, 0)); vec2 span = frame.embm_bounds.zw - frame.embm_bounds.xy; vec2 plane_uv = vec2(1.0) - (in_world_position.yx - frame.embm_bounds.yx) / span.yx; @@ -92,15 +94,16 @@ void main() { reflection_uv = displaced_clip.xy / displaced_clip.w * 0.5 + 0.5; } } - vec3 rgb = texture(reflection_color, reflection_uv).rgb * in_base_diffuse + in_base_specular; + vec3 water_color = (mode & 256) != 0 + ? texture(base_color, base_uv).rgb + : texture(reflection_color, reflection_uv).rgb; + vec3 rgb = water_color * in_base_diffuse + in_base_specular; out_color = vec4(mix(rgb * in_color, frame.fog_color.rgb, in_fog_factor), material.diffuse_alpha); if (out_color.a < material.alpha_cutoff) { discard; } return; } - vec2 base_uv = vec2(material.base_page_x, material.base_page_y) - + in_uv * vec2(material.base_page_w, material.base_page_h); vec2 detail_uv = vec2(material.detail_page_x, material.detail_page_y) + in_detail_uv * vec2(material.detail_page_w, material.detail_page_h); vec2 overlay_uv = vec2(material.overlay_page_x, material.overlay_page_y) diff --git a/adapters/fparkan-render-vulkan/shaders/triangle.frag.spv b/adapters/fparkan-render-vulkan/shaders/triangle.frag.spv index 6deef9d..02596d1 100644 Binary files a/adapters/fparkan-render-vulkan/shaders/triangle.frag.spv and b/adapters/fparkan-render-vulkan/shaders/triangle.frag.spv differ diff --git a/adapters/fparkan-render-vulkan/src/ffi/smoke.rs b/adapters/fparkan-render-vulkan/src/ffi/smoke.rs index c4f0264..8499c54 100644 --- a/adapters/fparkan-render-vulkan/src/ffi/smoke.rs +++ b/adapters/fparkan-render-vulkan/src/ffi/smoke.rs @@ -1936,7 +1936,16 @@ impl VulkanSmokeRenderer { &[], ); let mut viewport_depth = None; - for &range_index in &self.draw_order { + // Native reflection queues the translucent water after the reflected world. + let draw_order = + self.draw_order + .iter() + .copied() + .filter(|&index| !reflected || !self.draw_ranges[index].terrain_reflection) + .chain(self.draw_order.iter().copied().filter(|&index| { + reflected && self.draw_ranges[index].terrain_reflection + })); + for range_index in draw_order { let range = self.draw_ranges.get(range_index).ok_or( VulkanSmokeRendererError::InvariantViolation { context: "stable draw order range handle", @@ -1948,11 +1957,17 @@ impl VulkanSmokeRenderer { }, )?; let mode = self.material_unlit[texture_index].round() as i32; - if reflected && (range.terrain_reflection || mode & 8 != 0) { + if reflected && mode & 8 != 0 { continue; } let state = if reflected { - super::reflection::reflected_pipeline_state(range.pipeline_state) + let state = + super::reflection::reflected_pipeline_state(range.pipeline_state); + if range.terrain_reflection { + fparkan_render::water::legacy_reflected_water_pipeline(state) + } else { + state + } } else { range.pipeline_state }; @@ -1998,7 +2013,12 @@ impl VulkanSmokeRenderer { std::slice::from_ref(descriptor_set), &[frame_offset, material_offset, point_light_offset], ); - let alpha_cutoff = range.alpha_test_cutoff().to_ne_bytes(); + let alpha_cutoff = if state.alpha_test { + f32::from(range.alpha_test_reference) / 255.0 + } else { + 0.0 + } + .to_ne_bytes(); device.device().cmd_push_constants( command_buffer, resources.pipeline_layout, @@ -2017,8 +2037,13 @@ impl VulkanSmokeRenderer { .ok_or(VulkanSmokeRendererError::InvariantViolation { context: "draw-range diffuse-alpha override", })?; + let diffuse_alpha_override = if reflected && range.terrain_reflection { + Some(fparkan_render::water::REFLECTED_WATER_ALPHA) + } else { + *diffuse_alpha_override + }; let material_alphas = - draw_material_alphas(*material_alpha, *diffuse_alpha_override) + draw_material_alphas(*material_alpha, diffuse_alpha_override) .map(f32::to_ne_bytes) .concat(); device.device().cmd_push_constants( @@ -2075,6 +2100,11 @@ impl VulkanSmokeRenderer { }; let material_mode = (material_mode.round() as i32 | if reflected { 32 } else { 0 } + | if reflected && range.terrain_reflection { + 256 + } else { + 0 + } | if !reflected && reflection_plane.is_some() && self.reflection_planes[range_index] == reflection_plane -- cgit v1.2.3