feat(render): Flat/Gouraud shading, WithEdges overlay, paper-viewport mode

Faz 3 — FlatShaded vs GouraudShaded
- Uniforms.flat_shade (f32, 0/1) + mesh shader fragment derivative
  bazlı per-triangle face normal hesabı; GouraudShaded ise interpolate
  edilen vertex normalini kullanıyor.

Faz 4 — *WithEdges varyantları
- Pipeline::render.show_3d_edges parametresi:
  - FlatShaded / GouraudShaded → 3DFACE edge pass'i atlanıyor
  - *WithEdges varyantları → shaded fill'in üstüne mesh_wireframe
    pipeline ile triangle-edge overlay (LessEqual depth test ile)
  - face3d_edges pass'i show_3d_edges ile gate'lendi

Faz 5 — Paper-space viewport entity round-trip
- build_viewport_primitive / build_active_viewport_primitive artık
  render_mode parametresi almıyor; viewport entity'sinin kendi
  render_mode alanından okuyor. PaperViewportPane'dan render_mode
  field'i kaldırıldı. Model layout'taki tab.render_mode hala
  pick_list ile yönetiliyor.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
Hakan SevenandClaude Opus 4.7 committed 2026-05-27 05:57:19 +03:00
1 parent 56834dfba6
commit 132efe6574
6 files changed
+141 -56

No files matched your search

+32 -12
View File
@@ -2,13 +2,23 @@
//
// Vertex layout: position [f32;3], normal [f32;3], color [f32;4] (40 bytes)
//
// Lighting: simple half-Lambert with a fixed directional light so solids look
// solid regardless of camera angle. Selection highlight overrides color.
// Lighting: simple half-Lambert with a fixed directional light. Two
// shading paths share this shader, picked per-frame via `u.flat_shade`:
// - 0.0 → per-vertex normals interpolated to the fragment (Gouraud).
// - 1.0 → per-triangle face normal from screen-space derivatives
// `cross(dpdx(pos), dpdy(pos))`, so each triangle reads as a single
// flat shade (FlatShaded).
struct Uniforms {
view_proj: mat4x4<f32>,
viewport: vec2<f32>,
_pad: vec2<f32>,
view_proj: mat4x4<f32>,
camera_pos: vec4<f32>,
viewport_size: vec2<f32>,
world_per_pixel: f32,
lwdisplay_enable: f32,
flat_shade: f32,
_pad0: f32,
_pad1: f32,
_pad2: f32,
};
@group(0) @binding(0)
@@ -21,17 +31,19 @@ struct VertexIn {
};
struct VertexOut {
@builtin(position) clip_pos: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) normal: vec3<f32>,
@builtin(position) clip_pos: vec4<f32>,
@location(0) color: vec4<f32>,
@location(1) normal: vec3<f32>,
@location(2) world_pos: vec3<f32>,
};
@vertex
fn vs_main(v: VertexIn) -> VertexOut {
var out: VertexOut;
out.clip_pos = u.view_proj * vec4<f32>(v.position, 1.0);
out.color = v.color;
out.normal = v.normal;
out.clip_pos = u.view_proj * vec4<f32>(v.position, 1.0);
out.color = v.color;
out.normal = v.normal;
out.world_pos = v.position;
return out;
}
@@ -39,7 +51,15 @@ fn vs_main(v: VertexIn) -> VertexOut {
fn fs_main(in: VertexOut) -> @location(0) vec4<f32> {
// Fixed directional light (world space, tilted toward the viewer).
let light_dir = normalize(vec3<f32>(0.4, 0.8, 0.6));
let n = normalize(in.normal);
var n: vec3<f32>;
if (u.flat_shade > 0.5) {
// Per-triangle face normal: derivatives of the interpolated
// world position are constant across the primitive, so every
// fragment in the same triangle sees the same normal.
n = normalize(cross(dpdx(in.world_pos), dpdy(in.world_pos)));
} else {
n = normalize(in.normal);
}
// Half-Lambert: [0.5, 1.0] so the dark side isn't fully black.
let diff = clamp(dot(n, light_dir), 0.0, 1.0) * 0.5 + 0.5;
let rgb = in.color.rgb * diff;