reze-engine 0.25.1 → 0.25.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,12 +1,25 @@
1
- // MMD-style screen-space outline via normal-extrusion in clip space.
2
- // Aspect-compensated so pixel thickness stays stable across viewport sizes.
1
+ // MMD-style inverted-hull outline, ported from babylon-mmd's mmdOutline shader
2
+ // (the reference implementation whose output matches MMD):
3
+ // 1. Extrude along the VIEW-SPACE normal's XY, normalized — a pure screen
4
+ // direction, so rims never smear toward the camera at grazing angles.
5
+ // 2. Offset in clip space by edgeSize · 4/viewport · w. The ×w cancels the
6
+ // perspective divide → CONSTANT screen thickness of exactly
7
+ // 2·edgeSize device pixels (babylon: `screenNormal / (viewport*0.25) *
8
+ // offset * projectedPosition.w`). PMX edgeSize ~0.3–1.0 ⇒ fine 0.6–2px
9
+ // rims, matching MMD instead of our former chunky constants.
10
+ // 3. The FRAGMENT stage samples the material's own diffuse texture and
11
+ // MODULATES the rim's alpha by it (discarding only near-zero cut-out
12
+ // margins) — sheer fabric gets a proportional rim, never a solid black
13
+ // hull, without dropping the author's edge flag.
3
14
 
4
15
  export const OUTLINE_SHADER_WGSL = /* wgsl */ `
5
16
  struct CameraUniforms {
6
17
  view: mat4x4f,
7
18
  projection: mat4x4f,
8
19
  viewPos: vec3f,
9
- _padding: f32,
20
+ // Render-target height in device pixels (engine writes it each frame);
21
+ // width is recovered via the projection matrix's aspect.
22
+ viewportHeight: f32,
10
23
  };
11
24
 
12
25
  struct MaterialUniforms {
@@ -18,16 +31,20 @@ struct MaterialUniforms {
18
31
  };
19
32
 
20
33
  @group(0) @binding(0) var<uniform> camera: CameraUniforms;
34
+ @group(0) @binding(1) var edgeSampler: sampler;
21
35
  @group(1) @binding(0) var<storage, read> skinMats: array<mat4x4f>;
22
36
  @group(2) @binding(0) var<uniform> material: MaterialUniforms;
37
+ @group(2) @binding(1) var diffuseTexture: texture_2d<f32>;
23
38
 
24
39
  struct VertexOutput {
25
40
  @builtin(position) position: vec4f,
41
+ @location(0) uv: vec2f,
26
42
  };
27
43
 
28
44
  @vertex fn vs(
29
45
  @location(0) position: vec3f,
30
46
  @location(1) normal: vec3f,
47
+ @location(2) uv: vec2f,
31
48
  @location(3) joints0: vec4<u32>,
32
49
  @location(4) weights0: vec4<f32>
33
50
  ) -> VertexOutput {
@@ -51,33 +68,43 @@ struct VertexOutput {
51
68
  let worldPos = skinnedPos.xyz;
52
69
  let worldNormal = normalize(skinnedNrm);
53
70
 
54
- // Screen-space outline extrusion MMD-style pixel-stable edge line.
55
- // 1. Project position and normal-as-direction to clip space.
56
- // 2. Normalize the 2D clip-space normal, aspect-compensated so "one pixel horizontally"
57
- // matches "one pixel vertically" (otherwise wide viewports squash the outline in X).
58
- // 3. Offset clip-space xy by (normal * edgeSize * edgeScale), then multiply by w
59
- // so the perspective divide cancels out offset stays constant in NDC regardless
60
- // of depth, matching how MMD / babylon-mmd style outlines look identical when zooming.
61
- // 4. edgeScale is in NDC-y units per PMX edgeSize. ≈ 0.006 gives ~3px at 1080p; it's
62
- // tied to viewport HEIGHT so resizing the window keeps pixel thickness stable.
63
- let viewProj = camera.projection * camera.view;
64
- let clipPos = viewProj * vec4f(worldPos, 1.0);
65
- let clipNormal = (viewProj * vec4f(worldNormal, 0.0)).xy;
66
- // projection is column-major: proj[0][0] = 1/(aspect·tan(fov/2)), proj[1][1] = 1/tan(fov/2).
67
- // Ratio proj[1][1]/proj[0][0] recovers the viewport aspect (width/height).
71
+ let clipPos = camera.projection * camera.view * vec4f(worldPos, 1.0);
72
+
73
+ // babylon-mmd: screenNormal = normalize((view * worldNormal).xy)
74
+ let viewNormal = (camera.view * vec4f(worldNormal, 0.0)).xyz;
75
+ let snLen = length(viewNormal.xy);
76
+ let screenNormal = select(vec2f(0.0, 0.0), viewNormal.xy / snLen, snLen > 1e-5);
77
+
78
+ // Reference-height normalization (babylon-mmd ships this variant commented
79
+ // out as \`renderHeight = 1080\`): thickness is a constant FRACTION of the
80
+ // frame 2·edgeSize px at 1080p — so retina DPR and 4K export don't thin
81
+ // the rims to sub-pixel. Width follows the projection aspect.
82
+ // projection[1][1]/projection[0][0] = width/height for a symmetric frustum.
68
83
  let aspect = camera.projection[1][1] / camera.projection[0][0];
69
- let pixelDir = normalize(vec2f(clipNormal.x * aspect, clipNormal.y));
70
- let ndcDir = vec2f(pixelDir.x / aspect, pixelDir.y);
71
- let edgeScale = 0.0016;
72
- let offset = ndcDir * material.edgeSize * edgeScale * clipPos.w;
84
+ let viewport = vec2f(1080.0 * aspect, 1080.0);
85
+
86
+ // NDC offset = edgeSize · 4/viewport, ×w so the perspective divide cancels:
87
+ // constant screen thickness at any distance (babylon-mmd parity).
88
+ let offset = screenNormal * (material.edgeSize * 4.0 / viewport) * clipPos.w;
73
89
  output.position = vec4f(clipPos.xy + offset, clipPos.z, clipPos.w);
90
+ output.uv = uv;
74
91
  return output;
75
92
  }
76
93
 
77
94
  struct FSOut { @location(0) color: vec4f, @location(1) mask: vec4f };
78
- @fragment fn fs() -> FSOut {
95
+ @fragment fn fs(input: VertexOutput) -> FSOut {
96
+ // Rim alpha FOLLOWS the fabric's texture alpha instead of a hard alpha test:
97
+ // MMD draws blend-material edges solid (only cutout materials alpha-test), so
98
+ // a 0.4 discard erased the whole hull on semi-transparent cloth — stockinged
99
+ // legs crossing lost their outline entirely. Modulating instead keeps a
100
+ // proportional rim on sheer weave (never a solid black hull) and still
101
+ // discards true cut-out margins like hair-card borders.
102
+ let texA = textureSample(diffuseTexture, edgeSampler, input.uv).a;
103
+ if (texA < 0.05) {
104
+ discard;
105
+ }
79
106
  var out: FSOut;
80
- out.color = material.edgeColor;
107
+ out.color = vec4f(material.edgeColor.rgb, material.edgeColor.a * texA);
81
108
  out.mask = vec4f(1.0, 1.0, 0.0, out.color.a);
82
109
  return out;
83
110
  }