Skip to content

[Batch 4] Separate water render pass + planar reflection #386

@MichaelFisher1997

Description

@MichaelFisher1997

Summary

Split water rendering into its own sub-pass after the opaque pass, with planar reflection texture. Currently water is rendered in the same drawOffset() loop as solid/cutout blocks with flat tinted color. This produces a visually flat appearance.

Depends on: #371 (MDI — avoids render loop conflicts)

Current Behavior

Water blocks are meshed in the fluid pass by chunk_mesh.zig. In WorldRenderer.render(), fluid allocations are drawn in the same loop as solid/cutout using the same terrain pipeline and shaders. The fragment shader applies a flat blue tint. There is no reflection, refraction, or animated normals.

Target Behavior

  1. Opaque pass renders all solid + cutout geometry (no water)
  2. Water reflection pass: re-render scene from reflected camera into texture
  3. Water pass: render water blocks using reflection texture + depth comparison for refraction
  4. Post-processing continues as normal

Implementation Plan

Step 1: Reflection texture

  • Create offscreen render target: VK_FORMAT_R8G8B8A8_SRGB color + VK_FORMAT_D32_SFLOAT depth
  • Resolution: half screen resolution (performance trade-off)
  • Attach to frame resources, recreated on swapchain resize

Step 2: Reflection camera

  • Mirror the main camera position across the water surface plane (y = water_level)
  • Flip the view matrix: reflect_view = scale(1, -1, 1) * view
  • Use a user-defined clip plane at y = water_level to prevent geometry below water from rendering

Step 3: Reflection render pass

  • New render pass in render_graph.zig: WaterReflectionPass
  • Renders the same scene (opaque + cutout only, no fluids) from reflected camera
  • Outputs: reflection color texture
  • Optimization: only render chunks near water (within reflection influence distance)
  • Optimization: render at half resolution

Step 4: Water render pass

  • New render pass in render_graph.zig: WaterPass
  • Inputs: scene depth buffer, reflection texture, water geometry
  • Water fragment shader samples reflection texture with reflected UV
  • Depth comparison: pixels behind water surface get refraction tint
  • Add per-pixel depth-based alpha: shallow water is clearer, deep water is opaque

Step 5: Integration

  • WorldRenderer.render() split:
    • First pass: draw solid + cutout (skip fluid)
    • Reflection pass: draw solid + cutout with reflected camera
    • Water pass: draw fluid blocks with reflection texture
  • Render order managed by RenderGraph

Files to Create

  • src/engine/graphics/vulkan/water_system.zig — reflection texture, water pipeline
  • assets/shaders/vulkan/water.vert — water vertex shader
  • assets/shaders/vulkan/water.frag — water fragment shader with reflection
  • assets/shaders/vulkan/water.vert.spv + water.frag.spv

Files to Modify

  • src/engine/graphics/render_graph.zig — add WaterReflectionPass and WaterPass
  • src/world/world_renderer.zig — separate fluid rendering from solid/cutout
  • src/engine/graphics/vulkan/frame_manager.zig — reflection texture resources
  • src/engine/graphics/vulkan/pipeline_manager.zig — water pipeline
  • build.zig — glslangValidator checks for water shaders

Testing

  • Water reflects the sky and terrain above it
  • Reflection updates when camera moves
  • No rendering artifacts at water edges
  • Performance impact measurable and acceptable (<2ms for reflection pass)
  • Water still renders without reflection on LOW preset (fallback)
  • Depth-based color absorption visible (shallow = clear, deep = opaque)
  • Works with TAA (no flickering)

Roadmap: docs/PERFORMANCE_ROADMAP.md — Batch 4, Issue 4C-1

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions