reze-engine 0.12.0 → 0.12.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.
Files changed (100) hide show
  1. package/README.md +99 -58
  2. package/dist/bezier-interpolate.d.ts +15 -0
  3. package/dist/bezier-interpolate.d.ts.map +1 -0
  4. package/dist/bezier-interpolate.js +40 -0
  5. package/dist/engine_ts.d.ts +143 -0
  6. package/dist/engine_ts.d.ts.map +1 -0
  7. package/dist/engine_ts.js +1575 -0
  8. package/dist/ik.d.ts +32 -0
  9. package/dist/ik.d.ts.map +1 -0
  10. package/dist/ik.js +337 -0
  11. package/dist/player.d.ts +64 -0
  12. package/dist/player.d.ts.map +1 -0
  13. package/dist/player.js +220 -0
  14. package/dist/pool-scene.d.ts +52 -0
  15. package/dist/pool-scene.d.ts.map +1 -0
  16. package/dist/pool-scene.js +1122 -0
  17. package/dist/pool.d.ts +38 -0
  18. package/dist/pool.d.ts.map +1 -0
  19. package/dist/pool.js +422 -0
  20. package/dist/rzm-converter.d.ts +12 -0
  21. package/dist/rzm-converter.d.ts.map +1 -0
  22. package/dist/rzm-converter.js +40 -0
  23. package/dist/rzm-loader.d.ts +24 -0
  24. package/dist/rzm-loader.d.ts.map +1 -0
  25. package/dist/rzm-loader.js +488 -0
  26. package/dist/rzm-writer.d.ts +27 -0
  27. package/dist/rzm-writer.d.ts.map +1 -0
  28. package/dist/rzm-writer.js +701 -0
  29. package/dist/shaders/body.d.ts +1 -1
  30. package/dist/shaders/body.d.ts.map +1 -1
  31. package/dist/shaders/body.js +28 -7
  32. package/dist/shaders/cloth_rough.d.ts +1 -1
  33. package/dist/shaders/cloth_rough.d.ts.map +1 -1
  34. package/dist/shaders/cloth_rough.js +16 -4
  35. package/dist/shaders/cloth_smooth.d.ts +1 -1
  36. package/dist/shaders/cloth_smooth.d.ts.map +1 -1
  37. package/dist/shaders/cloth_smooth.js +17 -5
  38. package/dist/shaders/default.d.ts +1 -1
  39. package/dist/shaders/default.d.ts.map +1 -1
  40. package/dist/shaders/eye.d.ts +1 -1
  41. package/dist/shaders/eye.d.ts.map +1 -1
  42. package/dist/shaders/face.d.ts +1 -1
  43. package/dist/shaders/face.d.ts.map +1 -1
  44. package/dist/shaders/face.js +57 -21
  45. package/dist/shaders/hair.d.ts +1 -1
  46. package/dist/shaders/hair.d.ts.map +1 -1
  47. package/dist/shaders/hair.js +27 -7
  48. package/dist/shaders/materials/body.d.ts +1 -1
  49. package/dist/shaders/materials/body.d.ts.map +1 -1
  50. package/dist/shaders/materials/body.js +86 -197
  51. package/dist/shaders/materials/cloth_rough.d.ts +1 -1
  52. package/dist/shaders/materials/cloth_rough.d.ts.map +1 -1
  53. package/dist/shaders/materials/cloth_rough.js +10 -121
  54. package/dist/shaders/materials/cloth_smooth.d.ts +1 -1
  55. package/dist/shaders/materials/cloth_smooth.d.ts.map +1 -1
  56. package/dist/shaders/materials/cloth_smooth.js +59 -172
  57. package/dist/shaders/materials/common.d.ts +6 -0
  58. package/dist/shaders/materials/common.d.ts.map +1 -0
  59. package/dist/shaders/materials/common.js +144 -0
  60. package/dist/shaders/materials/default.d.ts +1 -1
  61. package/dist/shaders/materials/default.d.ts.map +1 -1
  62. package/dist/shaders/materials/default.js +12 -145
  63. package/dist/shaders/materials/eye.d.ts +1 -1
  64. package/dist/shaders/materials/eye.d.ts.map +1 -1
  65. package/dist/shaders/materials/eye.js +12 -117
  66. package/dist/shaders/materials/face.d.ts +1 -1
  67. package/dist/shaders/materials/face.d.ts.map +1 -1
  68. package/dist/shaders/materials/face.js +85 -197
  69. package/dist/shaders/materials/hair.d.ts +1 -1
  70. package/dist/shaders/materials/hair.d.ts.map +1 -1
  71. package/dist/shaders/materials/hair.js +78 -183
  72. package/dist/shaders/materials/metal.d.ts +1 -1
  73. package/dist/shaders/materials/metal.d.ts.map +1 -1
  74. package/dist/shaders/materials/metal.js +15 -121
  75. package/dist/shaders/materials/nodes.d.ts +1 -1
  76. package/dist/shaders/materials/nodes.d.ts.map +1 -1
  77. package/dist/shaders/materials/nodes.js +77 -0
  78. package/dist/shaders/materials/stockings.d.ts +1 -1
  79. package/dist/shaders/materials/stockings.d.ts.map +1 -1
  80. package/dist/shaders/materials/stockings.js +26 -152
  81. package/dist/shaders/metal.d.ts +1 -1
  82. package/dist/shaders/metal.d.ts.map +1 -1
  83. package/dist/shaders/metal.js +17 -4
  84. package/dist/shaders/nodes.d.ts +1 -1
  85. package/dist/shaders/nodes.d.ts.map +1 -1
  86. package/dist/shaders/nodes.js +9 -0
  87. package/dist/shaders/stockings.d.ts +1 -1
  88. package/dist/shaders/stockings.d.ts.map +1 -1
  89. package/package.json +1 -1
  90. package/src/shaders/materials/body.ts +90 -201
  91. package/src/shaders/materials/cloth_rough.ts +10 -121
  92. package/src/shaders/materials/cloth_smooth.ts +63 -176
  93. package/src/shaders/materials/common.ts +155 -0
  94. package/src/shaders/materials/default.ts +12 -145
  95. package/src/shaders/materials/eye.ts +12 -117
  96. package/src/shaders/materials/face.ts +89 -201
  97. package/src/shaders/materials/hair.ts +82 -187
  98. package/src/shaders/materials/metal.ts +15 -121
  99. package/src/shaders/materials/nodes.ts +77 -0
  100. package/src/shaders/materials/stockings.ts +27 -153
@@ -1,187 +1,82 @@
1
- // M_Hair — WGSL trace of 仿深空之眼渲染预设v1.0_by_小绿毛猫_material_graph_dump.json "M_Hair" (socket ids + defaults).
2
- // MixShader.001: Add→Shader (first), Principled→Shader_001 (second) out = mix(first, second, Fac).
3
-
4
- import { NODES_WGSL } from "./nodes"
5
-
6
- export const HAIR_SHADER_WGSL = /* wgsl */ `
7
-
8
- ${NODES_WGSL}
9
-
10
- // Pipeline-override: the engine compiles two variants — the normal opaque hair pipeline
11
- // (IS_OVER_EYES=false) and a second pipeline that re-draws hair fragments stencil-matched
12
- // against the eye stamp with 50% alpha so eyes read through the hair silhouette. Resolved
13
- // at pipeline-compile time; the dead branch is dropped by the shader compiler.
14
- override IS_OVER_EYES: bool = false;
15
-
16
- struct CameraUniforms {
17
- view: mat4x4f,
18
- projection: mat4x4f,
19
- viewPos: vec3f,
20
- _padding: f32,
21
- };
22
-
23
- struct Light {
24
- direction: vec4f,
25
- color: vec4f,
26
- };
27
-
28
- struct LightUniforms {
29
- ambientColor: vec4f,
30
- lights: array<Light, 4>,
31
- };
32
-
33
- struct MaterialUniforms {
34
- diffuseColor: vec3f,
35
- alpha: f32,
36
- };
37
-
38
- struct VertexOutput {
39
- @builtin(position) position: vec4f,
40
- @location(0) normal: vec3f,
41
- @location(1) uv: vec2f,
42
- @location(2) worldPos: vec3f,
43
- };
44
-
45
- struct LightVP { viewProj: mat4x4f, };
46
-
47
- @group(0) @binding(0) var<uniform> camera: CameraUniforms;
48
- @group(0) @binding(1) var<uniform> light: LightUniforms;
49
- @group(0) @binding(2) var diffuseSampler: sampler;
50
- @group(0) @binding(3) var shadowMap: texture_depth_2d;
51
- @group(0) @binding(4) var shadowSampler: sampler_comparison;
52
- @group(0) @binding(5) var<uniform> lightVP: LightVP;
53
- @group(1) @binding(0) var<storage, read> skinMats: array<mat4x4f>;
54
- @group(2) @binding(0) var diffuseTexture: texture_2d<f32>;
55
- @group(2) @binding(1) var<uniform> material: MaterialUniforms;
56
-
57
- fn sampleShadow(worldPos: vec3f, n: vec3f) -> f32 {
58
- // Back-facing to key light: direct contribution is zero anyway, skip 9 texture samples.
59
- if (dot(n, -light.lights[0].direction.xyz) <= 0.0) { return 0.0; }
60
- let biasedPos = worldPos + n * 0.08;
61
- let lclip = lightVP.viewProj * vec4f(biasedPos, 1.0);
62
- let ndc = lclip.xyz / max(lclip.w, 1e-6);
63
- let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
64
- let cmpZ = ndc.z - 0.001;
65
- let ts = 1.0 / 2048.0;
66
- // 3x3 PCF unrolled — Safari's Metal backend doesn't unroll nested shadow loops reliably.
67
- let s00 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(-ts, -ts), cmpZ);
68
- let s10 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(0.0, -ts), cmpZ);
69
- let s20 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f( ts, -ts), cmpZ);
70
- let s01 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(-ts, 0.0), cmpZ);
71
- let s11 = textureSampleCompareLevel(shadowMap, shadowSampler, suv, cmpZ);
72
- let s21 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f( ts, 0.0), cmpZ);
73
- let s02 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(-ts, ts), cmpZ);
74
- let s12 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(0.0, ts), cmpZ);
75
- let s22 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f( ts, ts), cmpZ);
76
- return (s00 + s10 + s20 + s01 + s11 + s21 + s02 + s12 + s22) * (1.0 / 9.0);
77
- }
78
-
79
- const PI_H: f32 = 3.141592653589793;
80
- const HAIR_SPECULAR: f32 = 1.0;
81
- const HAIR_ROUGHNESS: f32 = 0.3;
82
- const HAIR_TEX_GATE_THRESH: f32 = 0.15000000596046448;
83
- const HAIR_RIM2_POW: f32 = 0.6300000548362732;
84
- const HAIR_MIX_BG: vec3f = vec3f(0.1673291176557541);
85
-
86
- @vertex fn vs(
87
- @location(0) position: vec3f,
88
- @location(1) normal: vec3f,
89
- @location(2) uv: vec2f,
90
- @location(3) joints0: vec4<u32>,
91
- @location(4) weights0: vec4<f32>
92
- ) -> VertexOutput {
93
- var output: VertexOutput;
94
- let pos4 = vec4f(position, 1.0);
95
- let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
96
- let invWeightSum = select(1.0, 1.0 / weightSum, weightSum > 0.0001);
97
- let nw = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 * invWeightSum, weightSum > 0.0001);
98
- var skinnedPos = vec4f(0.0);
99
- var skinnedNrm = vec3f(0.0);
100
- for (var i = 0u; i < 4u; i++) {
101
- let m = skinMats[joints0[i]];
102
- let w = nw[i];
103
- skinnedPos += (m * pos4) * w;
104
- skinnedNrm += (mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz) * normal) * w;
105
- }
106
- output.position = camera.projection * camera.view * vec4f(skinnedPos.xyz, 1.0);
107
- // Skip VS normalize — interpolation denormalizes anyway, and FS always does normalize(input.normal).
108
- output.normal = skinnedNrm;
109
- output.uv = uv;
110
- output.worldPos = skinnedPos.xyz;
111
- return output;
112
- }
113
-
114
- struct FSOut {
115
- @location(0) color: vec4f,
116
- @location(1) mask: f32,
117
- };
118
-
119
- @fragment fn fs(input: VertexOutput) -> FSOut {
120
- let alpha = material.alpha;
121
- if (alpha < 0.001) { discard; }
122
-
123
- let n = normalize(input.normal);
124
- let v = normalize(camera.viewPos - input.worldPos);
125
- let l = -light.lights[0].direction.xyz;
126
- let sun = light.lights[0].color.xyz * light.lights[0].color.w;
127
-
128
- let tex_color = textureSample(diffuseTexture, diffuseSampler, input.uv).rgb;
129
- let shadow = sampleShadow(input.worldPos, n);
130
-
131
- let hue_sat_shadow = hue_sat_id(1.2, 0.5, 1.0, tex_color);
132
- let hue_sat_002 = hue_sat(0.48, 1.2, 0.7, 1.0, hue_sat_shadow);
133
- let hue_sat_001 = hue_sat_id(1.5, 1.0, 1.0, tex_color);
134
-
135
- let ndotl_raw = shader_to_rgb_diffuse(n, l, sun, light.ambientColor.xyz, shadow);
136
- let ramp_008 = ramp_constant(ndotl_raw, 0.0, vec4f(0,0,0,1), 0.2966, vec4f(1,1,1,1)).r;
137
-
138
- let mix_004 = mix_blend(ramp_008, hue_sat_002, hue_sat_001);
139
- let bc = bright_contrast(mix_004, 0.1, 0.2);
140
-
141
- let bevel_z = clamp(n.y, 0.0, 1.0);
142
- let mix_003 = mix_blend(bevel_z, bc, hue_sat_002);
143
-
144
- let rim2_raw = fresnel(1.45, n, v) * layer_weight_fresnel(0.61, n, v);
145
- let rim2_fac = math_power(rim2_raw, HAIR_RIM2_POW);
146
- let mix_shader_002 = mix(mix_003, HAIR_MIX_BG, rim2_fac);
147
-
148
- // Blender's GREATER_THAN converts Color→Float via BT.601 luminance, not raw R — same
149
- // socket-semantic fix as M_Face.
150
- let tex_gate = math_greater_than(color_to_value(tex_color), HAIR_TEX_GATE_THRESH);
151
- let gate_emit = vec3f(tex_gate) * 0.1;
152
-
153
- let add_shader = mix_shader_002 + gate_emit;
154
-
155
- // Principled BSDF (EEVEE port): metallic=0, specular=1.0, roughness=0.3, specular_tint=0.
156
- // Graph has a noise→normal_map bump (Strength=0.1) on Principled.Normal, but MixShader.001
157
- // weights Principled at only 0.2 — the bumped spec × that weight is imperceptible, so we
158
- // drop the subtree and keep plain n (saves a tex_noise + bump_lh per hair fragment).
159
- let NL = max(dot(n, l), 0.0);
160
- let NV = max(dot(n, v), 1e-4);
161
-
162
- let f0 = vec3f(0.08 * HAIR_SPECULAR);
163
- let f90 = mix(f0, vec3f(1.0), sqrt(HAIR_SPECULAR));
164
- let brdf_lut = brdf_lut_sample(NV, HAIR_ROUGHNESS);
165
- let reflection_color = F_brdf_multi_scatter(f0, f90, brdf_lut.xy);
166
-
167
- let spec_direct = bsdf_ggx(n, l, v, NL, NV, HAIR_ROUGHNESS) * sun * shadow * ltc_brdf_scale_from_lut(brdf_lut);
168
- let spec_indirect = light.ambientColor.xyz;
169
- let spec_radiance = (spec_direct + spec_indirect) * reflection_color;
170
-
171
- // Indirect diffuse = base_color × L_w per Blender closure_eval_surface_lib.glsl line 302;
172
- // probe_evaluate_world_diff returns radiance (SH-projected, not cosine-convolved).
173
- let diffuse_radiance = bc * (sun * NL * shadow / PI_H + light.ambientColor.xyz);
174
- let principled = diffuse_radiance + spec_radiance;
175
-
176
- let final_color = mix(add_shader, principled, 0.2);
177
-
178
- var outAlpha = alpha;
179
- if (IS_OVER_EYES) { outAlpha = alpha * 0.6; }
180
-
181
- var out: FSOut;
182
- out.color = vec4f(final_color, outAlpha);
183
- out.mask = 1.0;
184
- return out;
185
- }
186
-
187
- `
1
+ // M_Hair — 仿深空之眼渲染预设v1.0_by_小绿毛猫 "M_Hair". Toon + fresnel rim + bevel +
2
+ // bright-tex gate, mixed 80/20 NPR/PBR. MixShader.001 Fac=0.2 keeps Principled subtle.
3
+
4
+ import { NODES_WGSL } from "./nodes"
5
+ import { COMMON_MATERIAL_PRELUDE_WGSL } from "./common"
6
+
7
+ export const HAIR_SHADER_WGSL = /* wgsl */ `
8
+
9
+ ${NODES_WGSL}
10
+ ${COMMON_MATERIAL_PRELUDE_WGSL}
11
+
12
+ // Pipeline-override: the engine compiles two variants the normal opaque hair pipeline
13
+ // (IS_OVER_EYES=false) and a second pipeline that re-draws hair fragments stencil-matched
14
+ // against the eye stamp with 50% alpha so eyes read through the hair silhouette. Resolved
15
+ // at pipeline-compile time; the dead branch is dropped by the shader compiler.
16
+ override IS_OVER_EYES: bool = false;
17
+
18
+ const HAIR_SPECULAR: f32 = 1.0;
19
+ const HAIR_ROUGHNESS: f32 = 0.3;
20
+ const HAIR_TEX_GATE_THRESH: f32 = 0.15000000596046448;
21
+ const HAIR_RIM2_POW: f32 = 0.6300000548362732;
22
+ const HAIR_MIX_BG: vec3f = vec3f(0.1673291176557541);
23
+ const HAIR_MIX_NPR: f32 = 0.2;
24
+
25
+ @fragment fn fs(input: VertexOutput) -> FSOut {
26
+ let alpha = material.alpha;
27
+ if (alpha < 0.001) { discard; }
28
+
29
+ let n = normalize(input.normal);
30
+ let v = normalize(camera.viewPos - input.worldPos);
31
+ let l = -light.lights[0].direction.xyz;
32
+ let sun = light.lights[0].color.xyz * light.lights[0].color.w;
33
+ let amb = light.ambientColor.xyz;
34
+ let shadow = sampleShadow(input.worldPos, n);
35
+
36
+ let tex_color = textureSample(diffuseTexture, diffuseSampler, input.uv).rgb;
37
+
38
+ // ═══ NPR STACK ═══
39
+ let hue_sat_shadow = hue_sat_id(1.2, 0.5, 1.0, tex_color);
40
+ let hue_sat_002 = hue_sat(0.48, 1.2, 0.7, 1.0, hue_sat_shadow);
41
+ let hue_sat_001 = hue_sat_id(1.5, 1.0, 1.0, tex_color);
42
+
43
+ let ndotl_raw = shader_to_rgb_diffuse(n, l, sun, amb, shadow);
44
+ let ramp_008 = ramp_constant(ndotl_raw, 0.0, vec4f(0,0,0,1), 0.2966, vec4f(1,1,1,1)).r;
45
+
46
+ let mix_004 = mix_blend(ramp_008, hue_sat_002, hue_sat_001);
47
+ let bc = bright_contrast(mix_004, 0.1, 0.2);
48
+
49
+ let bevel_z = clamp(n.y, 0.0, 1.0);
50
+ let mix_003 = mix_blend(bevel_z, bc, hue_sat_002);
51
+
52
+ let rim2_raw = fresnel(1.45, n, v) * layer_weight_fresnel(0.61, n, v);
53
+ let rim2_fac = math_power(rim2_raw, HAIR_RIM2_POW);
54
+ let mix_shader_002 = mix(mix_003, HAIR_MIX_BG, rim2_fac);
55
+
56
+ // GREATER_THAN on a color input uses BT.601 luminance — same socket-semantic fix as face.ts.
57
+ let tex_gate = math_greater_than(color_to_value(tex_color), HAIR_TEX_GATE_THRESH);
58
+ let gate_emit = vec3f(tex_gate) * 0.1;
59
+
60
+ let npr_stack = mix_shader_002 + gate_emit;
61
+
62
+ // ═══ PRINCIPLED BSDF ═══
63
+ // Graph has a noise→normal_map bump (Strength=0.1) on Principled.Normal, but MixShader.001
64
+ // weights Principled at only 0.2 — the bumped spec × that weight is imperceptible, so we
65
+ // drop the subtree and keep plain n (saves a tex_noise + bump_lh per hair fragment).
66
+ let principled = eval_principled(
67
+ PrincipledIn(bc, 0.0, HAIR_SPECULAR, HAIR_ROUGHNESS, 1e30, 0.0, 0.0),
68
+ n, l, v, sun, amb, shadow
69
+ );
70
+
71
+ let final_color = mix(npr_stack, principled, HAIR_MIX_NPR);
72
+
73
+ var outAlpha = alpha;
74
+ if (IS_OVER_EYES) { outAlpha = alpha * 0.5; }
75
+
76
+ var out: FSOut;
77
+ out.color = vec4f(final_color, outAlpha);
78
+ out.mask = 1.0;
79
+ return out;
80
+ }
81
+
82
+ `
@@ -1,127 +1,30 @@
1
- // M_Metal — Metallic Principled (Metallic=1.0, Specular=1.0, Specular Tint=0.114, Roughness=0.3)
2
- // + NPR toon/AO emission stack (Strength=8.1), MixShader Fac=0.6967.
3
- // Base color uses a Voronoi pattern sampled in reflection-coord space (Blender 纹理坐标.Reflection)
4
- // to add subtle metallic sparkle variation. No Normal link in the graph.
1
+ // M_Metal — Metallic Principled (Metallic=1.0, Specular=1.0, Roughness=0.3) with a
2
+ // reflection-coord Voronoi pattern driving base color for metallic sparkle, plus an
3
+ // NPR toon/overlay emission stack mixed at MixShader Fac=0.6967.
5
4
  //
6
- // Graph's base color chain is: 纹理坐标.Reflection → 矢量运算.007(CROSS, Vec2=(0,1,0)) →
7
- // 沃罗诺伊纹理(F1, Color out) → 颜色渐变(linear) → 混合.005. The dumper did not capture the
8
- // VectorMath operation — CROSS is the assumed op based on the hardcoded (0,1,0) Vector_001
5
+ // Graph's base color chain: 纹理坐标.Reflection → 矢量运算.007(CROSS, Vec2=(0,1,0)) →
6
+ // 沃罗诺伊纹理(F1, Color out) → 颜色渐变(linear) → 混合.005. The dumper did not capture
7
+ // the VectorMath op — CROSS is assumed based on the hardcoded (0,1,0) Vector_001
9
8
  // constant (MULTIPLY would zero X/Z producing 1D bands; CROSS produces horizontal ring
10
9
  // patterns consistent with metallic anisotropy).
11
10
 
12
11
  import { NODES_WGSL } from "./nodes"
12
+ import { COMMON_MATERIAL_PRELUDE_WGSL } from "./common"
13
13
 
14
14
  export const METAL_SHADER_WGSL = /* wgsl */ `
15
15
 
16
16
  ${NODES_WGSL}
17
+ ${COMMON_MATERIAL_PRELUDE_WGSL}
17
18
 
18
- struct CameraUniforms {
19
- view: mat4x4f,
20
- projection: mat4x4f,
21
- viewPos: vec3f,
22
- _padding: f32,
23
- };
24
-
25
- struct Light {
26
- direction: vec4f,
27
- color: vec4f,
28
- };
29
-
30
- struct LightUniforms {
31
- ambientColor: vec4f,
32
- lights: array<Light, 4>,
33
- };
34
-
35
- struct MaterialUniforms {
36
- diffuseColor: vec3f,
37
- alpha: f32,
38
- };
39
-
40
- struct VertexOutput {
41
- @builtin(position) position: vec4f,
42
- @location(0) normal: vec3f,
43
- @location(1) uv: vec2f,
44
- @location(2) worldPos: vec3f,
45
- };
46
-
47
- struct LightVP { viewProj: mat4x4f, };
48
-
49
- @group(0) @binding(0) var<uniform> camera: CameraUniforms;
50
- @group(0) @binding(1) var<uniform> light: LightUniforms;
51
- @group(0) @binding(2) var diffuseSampler: sampler;
52
- @group(0) @binding(3) var shadowMap: texture_depth_2d;
53
- @group(0) @binding(4) var shadowSampler: sampler_comparison;
54
- @group(0) @binding(5) var<uniform> lightVP: LightVP;
55
- @group(1) @binding(0) var<storage, read> skinMats: array<mat4x4f>;
56
- @group(2) @binding(0) var diffuseTexture: texture_2d<f32>;
57
- @group(2) @binding(1) var<uniform> material: MaterialUniforms;
58
-
59
- fn sampleShadow(worldPos: vec3f, n: vec3f) -> f32 {
60
- // Back-facing to key light: direct contribution is zero anyway, skip 9 texture samples.
61
- if (dot(n, -light.lights[0].direction.xyz) <= 0.0) { return 0.0; }
62
- let biasedPos = worldPos + n * 0.08;
63
- let lclip = lightVP.viewProj * vec4f(biasedPos, 1.0);
64
- let ndc = lclip.xyz / max(lclip.w, 1e-6);
65
- let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
66
- let cmpZ = ndc.z - 0.001;
67
- let ts = 1.0 / 2048.0;
68
- // 3x3 PCF unrolled — Safari's Metal backend doesn't unroll nested shadow loops reliably.
69
- let s00 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(-ts, -ts), cmpZ);
70
- let s10 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(0.0, -ts), cmpZ);
71
- let s20 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f( ts, -ts), cmpZ);
72
- let s01 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(-ts, 0.0), cmpZ);
73
- let s11 = textureSampleCompareLevel(shadowMap, shadowSampler, suv, cmpZ);
74
- let s21 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f( ts, 0.0), cmpZ);
75
- let s02 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(-ts, ts), cmpZ);
76
- let s12 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(0.0, ts), cmpZ);
77
- let s22 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f( ts, ts), cmpZ);
78
- return (s00 + s10 + s20 + s01 + s11 + s21 + s02 + s12 + s22) * (1.0 / 9.0);
79
- }
80
-
81
- const PI_M: f32 = 3.141592653589793;
82
19
  const METAL_SPECULAR: f32 = 1.0;
83
20
  const METAL_METALLIC: f32 = 1.0;
84
21
  const METAL_ROUGHNESS: f32 = 0.3;
85
- const METAL_SPECULAR_TINT: f32 = 0.114;
86
22
  const METAL_TOON_EDGE: f32 = 0.2966;
87
23
  const METAL_MIX04_MUL: f32 = 0.5;
88
24
  const METAL_EMIT_STR: f32 = 8.100000381469727;
89
25
  const METAL_MIX_SHADER_FAC: f32 = 0.6967;
90
26
  const METAL_VORONOI_SCALE: f32 = 4.3;
91
27
 
92
- @vertex fn vs(
93
- @location(0) position: vec3f,
94
- @location(1) normal: vec3f,
95
- @location(2) uv: vec2f,
96
- @location(3) joints0: vec4<u32>,
97
- @location(4) weights0: vec4<f32>
98
- ) -> VertexOutput {
99
- var output: VertexOutput;
100
- let pos4 = vec4f(position, 1.0);
101
- let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
102
- let invWeightSum = select(1.0, 1.0 / weightSum, weightSum > 0.0001);
103
- let nw = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 * invWeightSum, weightSum > 0.0001);
104
- var skinnedPos = vec4f(0.0);
105
- var skinnedNrm = vec3f(0.0);
106
- for (var i = 0u; i < 4u; i++) {
107
- let m = skinMats[joints0[i]];
108
- let w = nw[i];
109
- skinnedPos += (m * pos4) * w;
110
- skinnedNrm += (mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz) * normal) * w;
111
- }
112
- output.position = camera.projection * camera.view * vec4f(skinnedPos.xyz, 1.0);
113
- // Skip VS normalize — interpolation denormalizes anyway, and FS always does normalize(input.normal).
114
- output.normal = skinnedNrm;
115
- output.uv = uv;
116
- output.worldPos = skinnedPos.xyz;
117
- return output;
118
- }
119
-
120
- struct FSOut {
121
- @location(0) color: vec4f,
122
- @location(1) mask: f32,
123
- };
124
-
125
28
  @fragment fn fs(input: VertexOutput) -> FSOut {
126
29
  let n = normalize(input.normal);
127
30
  let v = normalize(camera.viewPos - input.worldPos);
@@ -135,6 +38,7 @@ struct FSOut {
135
38
  let out_alpha = material.alpha * tex_s.a;
136
39
  if (out_alpha < 0.001) { discard; }
137
40
 
41
+ // ═══ NPR STACK ═══
138
42
  let tex_tint = hue_sat_id(1.0, 0.800000011920929, 1.0, tex_rgb);
139
43
  let lum_shade = shader_to_rgb_diffuse(n, l, sun, amb, shadow);
140
44
  let ramp008 = ramp_constant_edge_aa(lum_shade, METAL_TOON_EDGE, vec4f(0,0,0,1), vec4f(1,1,1,1));
@@ -147,6 +51,7 @@ struct FSOut {
147
51
  let npr_rgb = mix_overlay(1.0, mix04, hue004);
148
52
  let npr_emission = npr_rgb * METAL_EMIT_STR;
149
53
 
54
+ // ═══ PRINCIPLED BSDF (metallic=1, voronoi-driven base) ═══
150
55
  // Reflection-coord Voronoi produces the metallic sparkle variation.
151
56
  // VALTORGB takes Color → Fac via Blender's BT.601 implicit color_to_value.
152
57
  let refl_dir = reflect(-v, n);
@@ -157,23 +62,12 @@ struct FSOut {
157
62
  let hue006 = hue_sat_id(1.5, 1.2999999523162842, 1.0, tex_tint);
158
63
  let albedo = mix_blend(voro_ramp, vec3f(voro_ramp), hue006);
159
64
 
160
- // Principled BSDF (EEVEE port): metallic=1 collapses f0 = mix(dielectric, albedo, 1) = albedo;
161
- // specular_tint is dielectric-only and ignored here.
162
- let f0 = albedo;
163
- let f90 = mix(f0, vec3f(1.0), sqrt(METAL_SPECULAR));
164
- let NL = max(dot(n, l), 0.0);
165
- let NV = max(dot(n, v), 1e-4);
166
- let brdf_lut = brdf_lut_sample(NV, METAL_ROUGHNESS);
167
- let reflection_color = F_brdf_multi_scatter(f0, f90, brdf_lut.xy);
168
-
169
- let spec_direct = bsdf_ggx(n, l, v, NL, NV, METAL_ROUGHNESS) * sun * shadow * ltc_brdf_scale_from_lut(brdf_lut);
170
- let spec_indirect = amb;
171
- let spec_radiance = (spec_direct + spec_indirect) * reflection_color;
172
-
173
- // Pure metal — no diffuse lobe (diffuse_weight = (1 - metallic) = 0).
174
- let principled = spec_radiance;
65
+ // metallic=1 collapses f0 = mix(dielectric, albedo, 1) = albedo; diffuse_weight = 0.
66
+ let principled = eval_principled(
67
+ PrincipledIn(albedo, METAL_METALLIC, METAL_SPECULAR, METAL_ROUGHNESS, 1e30, 0.0, 0.0),
68
+ n, l, v, sun, amb, shadow
69
+ );
175
70
 
176
- // 混合着色器.001 Fac=0.6967: Shader=npr_emission, Shader_001=principled
177
71
  let final_color = mix(npr_emission, principled, METAL_MIX_SHADER_FAC);
178
72
 
179
73
  var out: FSOut;
@@ -480,4 +480,81 @@ fn tint_from_color(color: vec3f) -> vec3f {
480
480
  return select(vec3f(1.0), color / lum, lum > 0.0);
481
481
  }
482
482
 
483
+ // ─── Principled sheen (gpu_shader_material_principled.glsl:8-14) ────
484
+ // Empirical NV-only curve that approximates grazing retroreflection on cloth/velvet.
485
+ // Scales the sheen layer's diffuse contribution; no sheen call site has sheen=0
486
+ // shortcut because the multiplier is tiny at normal view angles anyway.
487
+ fn principled_sheen(NV: f32) -> f32 {
488
+ let f = 1.0 - NV;
489
+ return f * f * f * 0.077 + f * 0.01 + 0.00026;
490
+ }
491
+
492
+ // ─── Principled BSDF eval ───────────────────────────────────────────
493
+ // Shared EEVEE Principled path used by every material in the engine — metallic,
494
+ // dielectric, and sheen variants all fold into these ~15 lines via the struct
495
+ // fields. NPR materials still compute a separate toon/rim/warm stack on top and
496
+ // mix(npr_stack, eval_principled(...), fac); see body.ts / face.ts / etc.
497
+ //
498
+ // Field conventions:
499
+ // base — diffuse albedo. Mixed into f0 only when metallic > 0.
500
+ // metallic — 0 = dielectric (f0 from specular), 1 = pure metal (f0 = base).
501
+ // specular — Principled Specular input (0.5 default → f0 = 0.04). sqrt for f90.
502
+ // roughness — GGX roughness; drives BRDF LUT coord + bsdf_ggx.
503
+ // spec_clamp — EEVEE Light Clamp equivalent. Caps firefly spec from noise-bumped
504
+ // NDF aliasing (Blender hides this via TAA which we don't have).
505
+ // Pass 1e30 (effectively disabled) for materials that don't bump.
506
+ // sheen — 0 disables. Scales the sheen diffuse add; cloth/stockings use ~0.7.
507
+ // sheen_tint — 0 = white sheen, 1 = fully tinted by base. Multiplied by sheen,
508
+ // so value is don't-care when sheen=0.
509
+ struct PrincipledIn {
510
+ base: vec3f,
511
+ metallic: f32,
512
+ specular: f32,
513
+ roughness: f32,
514
+ spec_clamp: f32,
515
+ sheen: f32,
516
+ sheen_tint: f32,
517
+ };
518
+
519
+ fn eval_principled(
520
+ p: PrincipledIn,
521
+ N: vec3f, L: vec3f, V: vec3f,
522
+ sun_rgb: vec3f, amb_rgb: vec3f, shadow: f32
523
+ ) -> vec3f {
524
+ let NL = max(dot(N, L), 0.0);
525
+ let NV = max(dot(N, V), 1e-4);
526
+
527
+ // f0/f90 per gpu_shader_material_principled.glsl. specular_tint=0 is assumed
528
+ // (all presets in this engine use the default white dielectric tint).
529
+ let dielectric_f0 = vec3f(0.08 * p.specular);
530
+ let f0 = mix(dielectric_f0, p.base, p.metallic);
531
+ let f90 = mix(f0, vec3f(1.0), sqrt(p.specular));
532
+
533
+ // Single LUT tap feeds both F_brdf_multi_scatter (split-sum DFG) and
534
+ // ltc_brdf_scale_from_lut (LTC mag in .ba). See nodes.ts brdf_lut_sample.
535
+ let lut = brdf_lut_sample(NV, p.roughness);
536
+ let reflection_color = F_brdf_multi_scatter(f0, f90, lut.xy);
537
+
538
+ // Direct glossy — bsdf_ggx already includes NL; no F applied here (tinted after
539
+ // accum with reflection_color). ltc_brdf_scale rescales direct to match the
540
+ // split-sum indirect path, matching EEVEE closure_eval_glossy_lib behavior.
541
+ let spec_direct_raw = bsdf_ggx(N, L, V, NL, NV, p.roughness)
542
+ * sun_rgb * shadow * ltc_brdf_scale_from_lut(lut);
543
+ let spec_direct = min(spec_direct_raw, vec3f(p.spec_clamp));
544
+ let spec_indirect = amb_rgb;
545
+ let spec_radiance = (spec_direct + spec_indirect) * reflection_color;
546
+
547
+ // Sheen add — when p.sheen=0 the whole term collapses, leaving diffuse_color=base.
548
+ let base_tint = tint_from_color(p.base);
549
+ let sheen_color = mix(vec3f(1.0), base_tint, p.sheen_tint);
550
+ let diffuse_color = p.base + p.sheen * sheen_color * principled_sheen(NV);
551
+
552
+ // diffuse_weight = (1-metallic). Indirect diffuse uses amb (L_w) with no π factor
553
+ // (probe_evaluate_world_diff returns SH-projected radiance, not cosine-convolved).
554
+ let diffuse_weight = 1.0 - p.metallic;
555
+ let diffuse_radiance = diffuse_color * (sun_rgb * NL * shadow / EEVEE_PI + amb_rgb) * diffuse_weight;
556
+
557
+ return diffuse_radiance + spec_radiance;
558
+ }
559
+
483
560
  `;