From a178de4e8680051925af9454b140343bf7eae214 Mon Sep 17 00:00:00 2001 From: skal Date: Sun, 29 Mar 2026 01:50:47 +0100 Subject: fix(3d): restore correct orientation in test_3d_render (direct-to-surface) Add Renderer3D::set_direct_render(bool) flag (must be set before init()). When true, uses standard CCW winding and un-negates perspective Y, restoring the pre-ba7ea27 orientation for direct-to-surface rendering where the post-process Y-flip is absent. handoff(Gemini): set_direct_render() is the escape hatch for any future renderer usage that bypasses the post-process chain. --- src/3d/renderer.h | 5 +++++ src/3d/renderer_draw.cc | 9 ++++++--- src/3d/renderer_pipelines.cc | 5 ++++- src/tests/3d/test_3d_render.cc | 1 + 4 files changed, 16 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/3d/renderer.h b/src/3d/renderer.h index 11a7931..8f933b5 100644 --- a/src/3d/renderer.h +++ b/src/3d/renderer.h @@ -78,6 +78,10 @@ class Renderer3D { bvh_enabled_ = enabled; } + // Call before init(). When true, renders directly to surface (no post-process + // Y-flip): uses standard perspective (Y not negated) and CCW winding. + void set_direct_render(bool v) { direct_render_ = v; } + struct MeshGpuData { WGPUBuffer vertex_buffer; WGPUBuffer index_buffer; @@ -120,6 +124,7 @@ class Renderer3D { BVH cpu_bvh_; // Keep a CPU-side copy for building/uploading bool bvh_enabled_ = true; + bool direct_render_ = false; // true = render to surface (no post-process flip) std::map mesh_cache_; const MeshGpuData* temp_mesh_override_ = nullptr; // HACK for test_mesh tool diff --git a/src/3d/renderer_draw.cc b/src/3d/renderer_draw.cc index d4a35a8..dca7113 100644 --- a/src/3d/renderer_draw.cc +++ b/src/3d/renderer_draw.cc @@ -9,7 +9,9 @@ void Renderer3D::update_uniforms(const Scene& scene, const Camera& camera, float time) { - const mat4 vp = camera.get_projection_matrix() * camera.get_view_matrix(); + mat4 proj = camera.get_projection_matrix(); + if (direct_render_) proj.m[5] = -proj.m[5]; // undo Y-negate for direct surface + const mat4 vp = proj * camera.get_view_matrix(); const GlobalUniforms globals = GlobalUniforms::make( vp, vec4(camera.position.x, camera.position.y, camera.position.z, time), @@ -203,8 +205,9 @@ void Renderer3D::draw(WGPURenderPassEncoder pass, const Scene& scene, } } - // Calculate ViewProj matrix for the debug renderer - mat4 view_proj = camera.get_projection_matrix() * camera.get_view_matrix(); + mat4 dbg_proj = camera.get_projection_matrix(); + if (direct_render_) dbg_proj.m[5] = -dbg_proj.m[5]; + mat4 view_proj = dbg_proj * camera.get_view_matrix(); visual_debug_.render(pass, view_proj); } #endif diff --git a/src/3d/renderer_pipelines.cc b/src/3d/renderer_pipelines.cc index 3abc3bd..195baad 100644 --- a/src/3d/renderer_pipelines.cc +++ b/src/3d/renderer_pipelines.cc @@ -253,7 +253,10 @@ void Renderer3D::create_mesh_pipeline() { pipeline_desc.vertex.buffers = &vert_buffer_layout; pipeline_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList; pipeline_desc.primitive.cullMode = WGPUCullMode_Back; - pipeline_desc.primitive.frontFace = WGPUFrontFace_CW; // Y-flipped perspective + // CW winding compensates for Y-negation in perspective() (post-process chain). + // Direct-to-surface rendering uses standard CCW winding. + pipeline_desc.primitive.frontFace = + direct_render_ ? WGPUFrontFace_CCW : WGPUFrontFace_CW; pipeline_desc.multisample.count = 1; pipeline_desc.fragment = &fragment_state; pipeline_desc.depthStencil = &depth_stencil; diff --git a/src/tests/3d/test_3d_render.cc b/src/tests/3d/test_3d_render.cc index de9718e..ef799ec 100644 --- a/src/tests/3d/test_3d_render.cc +++ b/src/tests/3d/test_3d_render.cc @@ -140,6 +140,7 @@ int main(int argc, char** argv) { InitShaderComposer(); + g_renderer.set_direct_render(true); // Renders to surface, no post-process flip g_renderer.init(g_device, g_queue, g_format); g_renderer.resize(platform_state.width, platform_state.height); -- cgit v1.2.3