diff options
| -rw-r--r-- | doc/3D.md | 17 | ||||
| -rw-r--r-- | src/3d/renderer.h | 8 | ||||
| -rw-r--r-- | src/3d/renderer_pipelines.cc | 1 | ||||
| -rw-r--r-- | src/effects/ntsc.wgsl | 2 | ||||
| -rw-r--r-- | src/effects/rotating_cube_effect.cc | 1 | ||||
| -rw-r--r-- | src/effects/rotating_cube_effect.h | 5 | ||||
| -rw-r--r-- | src/util/mini_math.h | 2 |
7 files changed, 25 insertions, 11 deletions
@@ -24,6 +24,23 @@ - WebGPU framebuffer: y-down (pixel row 0 at top) - `uv.y = 0` → NDC `y = -1` → bottom of framebuffer ✓ +### Rasterized 3D and the Y-flip rule + +The fullscreen post-process VS (`fullscreen_uv_vs.wgsl`) uses **Y-up UVs** (`uv.y=0` = bottom of screen). +`textureSample(tex, s, uv)` with those UVs samples the **top** of the input texture at the bottom of +the screen — a Y-flip. SDF effects are authored Y-down and appear correct after this flip. Rasterized +effects (RotatingCube, Hybrid3D) render into an offscreen texture and are consumed by the post-process +chain, so they must **pre-flip** their geometry to compensate: + +- **`mat4::perspective()`** uses `m[5] = -t` (negated Y scale) — renders geometry upside-down into the + texture so the post-process chain flips it right-side up. +- **Rasterized pipelines** with `cullMode = Back` must set `frontFace = WGPUFrontFace_CW` — the Y-flip + reverses winding order on screen, so CW becomes the visible (front) face. +- Pipelines with `cullMode = None` (SDF proxy hull, skybox) need no winding change. + +**Do not** set `m[5] = +t` or use `frontFace = WGPUFrontFace_CCW` (default) in rasterized 3D +pipelines — the result will be upside-down with back-faces culled. + ## Core Concept Hybrid SDF/rasterization pipeline with physics and collision detection. diff --git a/src/3d/renderer.h b/src/3d/renderer.h index 4c31c97..11a7931 100644 --- a/src/3d/renderer.h +++ b/src/3d/renderer.h @@ -24,12 +24,9 @@ struct GlobalUniforms { vec2 resolution; vec2 padding; - // Transpose matrices: mini_math is row-major, WGSL expects column-major. static GlobalUniforms make(const mat4& vp, const vec4& cam_pos_time, const vec4& p, const vec2& res) { - return {mat4::transpose(vp), mat4::transpose(vp.inverse()), - cam_pos_time, p, - res, vec2(0.0f, 0.0f)}; + return {vp, vp.inverse(), cam_pos_time, p, res, vec2(0.0f, 0.0f)}; } }; @@ -42,9 +39,8 @@ struct ObjectData { // applicable) vec4 params; - // Transpose matrices: mini_math is row-major, WGSL expects column-major. static ObjectData make(const mat4& m, const vec4& col, const vec4& p) { - return {mat4::transpose(m), mat4::transpose(m.inverse()), col, p}; + return {m, m.inverse(), col, p}; } }; diff --git a/src/3d/renderer_pipelines.cc b/src/3d/renderer_pipelines.cc index be4a317..2950a7f 100644 --- a/src/3d/renderer_pipelines.cc +++ b/src/3d/renderer_pipelines.cc @@ -249,6 +249,7 @@ 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 pipeline_desc.multisample.count = 1; pipeline_desc.fragment = &fragment_state; pipeline_desc.depthStencil = &depth_stencil; diff --git a/src/effects/ntsc.wgsl b/src/effects/ntsc.wgsl index 5c27695..4e72f0a 100644 --- a/src/effects/ntsc.wgsl +++ b/src/effects/ntsc.wgsl @@ -2,6 +2,7 @@ #include "sequence_uniforms" #include "render/fullscreen_uv_vs" #include "math/noise" +#include "debug/debug_print" const vignetteRounding = 160.0f; const vignetteSmoothness = 0.7f; @@ -68,5 +69,6 @@ fn vignette(uv: vec2f) -> f32 { col.g *= 1.01; col.b *= 0.94; + col = debug_f32(col, in.position.xy / uniforms.resolution, vec2f(10., 10.), 3.2); return vec4f(clamp(col, vec3f(0.0), vec3f(1.0)), 1.0); } diff --git a/src/effects/rotating_cube_effect.cc b/src/effects/rotating_cube_effect.cc index 757d64c..197bc26 100644 --- a/src/effects/rotating_cube_effect.cc +++ b/src/effects/rotating_cube_effect.cc @@ -75,6 +75,7 @@ RotatingCube::RotatingCube(const GpuContext& ctx, pipeline_desc.vertex.entryPoint = str_view("vs_main"); pipeline_desc.primitive.topology = WGPUPrimitiveTopology_TriangleList; pipeline_desc.primitive.cullMode = WGPUCullMode_Back; + pipeline_desc.primitive.frontFace = WGPUFrontFace_CW; // Y-flipped perspective pipeline_desc.depthStencil = &depth_stencil; pipeline_desc.multisample.count = 1; pipeline_desc.multisample.mask = 0xFFFFFFFF; diff --git a/src/effects/rotating_cube_effect.h b/src/effects/rotating_cube_effect.h index 920be5c..fb321fa 100644 --- a/src/effects/rotating_cube_effect.h +++ b/src/effects/rotating_cube_effect.h @@ -29,12 +29,9 @@ class RotatingCube : public Effect { float aspect_ratio; float _pad; - // Transpose matrices: mini_math is row-major, WGSL expects column-major. static Uniforms make(const mat4& vp, const vec4& cam_pos_time, const vec4& p, const vec2& res, float asp) { - return {mat4::transpose(vp), mat4::transpose(vp.inverse()), - cam_pos_time, p, - res, asp}; + return {vp, vp.inverse(), cam_pos_time, p, res, asp}; } }; static_assert(sizeof(Uniforms) == 176, "Uniforms size mismatch"); diff --git a/src/util/mini_math.h b/src/util/mini_math.h index 29dc38f..0aa66a3 100644 --- a/src/util/mini_math.h +++ b/src/util/mini_math.h @@ -229,7 +229,7 @@ struct mat4 { mat4 r = {}; float t = 1.0f / std::tan(fov * 0.5f); r.m[0] = t / asp; - r.m[5] = t; + r.m[5] = -t; // Negate Y: post-process chain samples textures Y-flipped r.m[10] = f / (n - f); r.m[11] = -1; r.m[14] = (n * f) / (n - f); |
