reze-engine 0.10.2 → 0.11.0

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 (62) hide show
  1. package/README.md +72 -13
  2. package/dist/engine.d.ts +170 -34
  3. package/dist/engine.d.ts.map +1 -1
  4. package/dist/engine.js +1080 -308
  5. package/dist/index.d.ts +2 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +1 -1
  8. package/dist/shaders/body.d.ts +2 -0
  9. package/dist/shaders/body.d.ts.map +1 -0
  10. package/dist/shaders/body.js +209 -0
  11. package/dist/shaders/classify.d.ts +4 -0
  12. package/dist/shaders/classify.d.ts.map +1 -0
  13. package/dist/shaders/classify.js +12 -0
  14. package/dist/shaders/cloth_rough.d.ts +2 -0
  15. package/dist/shaders/cloth_rough.d.ts.map +1 -0
  16. package/dist/shaders/cloth_rough.js +172 -0
  17. package/dist/shaders/cloth_smooth.d.ts +2 -0
  18. package/dist/shaders/cloth_smooth.d.ts.map +1 -0
  19. package/dist/shaders/cloth_smooth.js +171 -0
  20. package/dist/shaders/default.d.ts +2 -0
  21. package/dist/shaders/default.d.ts.map +1 -0
  22. package/dist/shaders/default.js +168 -0
  23. package/dist/shaders/dfg_lut.d.ts +4 -0
  24. package/dist/shaders/dfg_lut.d.ts.map +1 -0
  25. package/dist/shaders/dfg_lut.js +125 -0
  26. package/dist/shaders/eye.d.ts +2 -0
  27. package/dist/shaders/eye.d.ts.map +1 -0
  28. package/dist/shaders/eye.js +142 -0
  29. package/dist/shaders/face.d.ts +2 -0
  30. package/dist/shaders/face.d.ts.map +1 -0
  31. package/dist/shaders/face.js +211 -0
  32. package/dist/shaders/hair.d.ts +2 -0
  33. package/dist/shaders/hair.d.ts.map +1 -0
  34. package/dist/shaders/hair.js +186 -0
  35. package/dist/shaders/ltc_mag_lut.d.ts +3 -0
  36. package/dist/shaders/ltc_mag_lut.d.ts.map +1 -0
  37. package/dist/shaders/ltc_mag_lut.js +1033 -0
  38. package/dist/shaders/metal.d.ts +2 -0
  39. package/dist/shaders/metal.d.ts.map +1 -0
  40. package/dist/shaders/metal.js +171 -0
  41. package/dist/shaders/nodes.d.ts +2 -0
  42. package/dist/shaders/nodes.d.ts.map +1 -0
  43. package/dist/shaders/nodes.js +423 -0
  44. package/dist/shaders/stockings.d.ts +2 -0
  45. package/dist/shaders/stockings.d.ts.map +1 -0
  46. package/dist/shaders/stockings.js +229 -0
  47. package/package.json +1 -1
  48. package/src/engine.ts +1281 -376
  49. package/src/index.ts +12 -2
  50. package/src/shaders/body.ts +211 -0
  51. package/src/shaders/classify.ts +25 -0
  52. package/src/shaders/cloth_rough.ts +174 -0
  53. package/src/shaders/cloth_smooth.ts +173 -0
  54. package/src/shaders/default.ts +169 -0
  55. package/src/shaders/dfg_lut.ts +127 -0
  56. package/src/shaders/eye.ts +143 -0
  57. package/src/shaders/face.ts +213 -0
  58. package/src/shaders/hair.ts +188 -0
  59. package/src/shaders/ltc_mag_lut.ts +1035 -0
  60. package/src/shaders/metal.ts +173 -0
  61. package/src/shaders/nodes.ts +424 -0
  62. package/src/shaders/stockings.ts +231 -0
package/src/index.ts CHANGED
@@ -1,4 +1,13 @@
1
- export { Engine, type EngineStats, type LoadModelFromFilesOptions } from "./engine"
1
+ export {
2
+ Engine,
3
+ DEFAULT_BLOOM_OPTIONS,
4
+ DEFAULT_VIEW_TRANSFORM,
5
+ type EngineStats,
6
+ type EngineOptions,
7
+ type BloomOptions,
8
+ type ViewTransformOptions,
9
+ type LoadModelFromFilesOptions,
10
+ } from "./engine"
2
11
  export { parsePmxFolderInput, pmxFileAtRelativePath, type PmxFolderInputResult } from "./folder-upload"
3
12
  export { Model } from "./model"
4
13
  export { Vec3, Quat, Mat4 } from "./math"
@@ -12,4 +21,5 @@ export type {
12
21
  ControlPoint,
13
22
  } from "./animation"
14
23
  export { FPS } from "./animation"
15
- export { Physics, type PhysicsOptions } from "./physics"
24
+ export { Physics, type PhysicsOptions } from "./physics"
25
+ export type { MaterialPreset, MaterialPresetMap } from "./shaders/classify"
@@ -0,0 +1,211 @@
1
+ // M_Body — 仿深空之眼渲染预设v1.0_by_小绿毛猫_material_graph_dump.json "M_Body"; VALTORGB / math ops from m_graphs where dump omits them.
2
+
3
+ import { NODES_WGSL } from "./nodes"
4
+
5
+ export const BODY_SHADER_WGSL = /* wgsl */ `
6
+
7
+ ${NODES_WGSL}
8
+
9
+ struct CameraUniforms {
10
+ view: mat4x4f,
11
+ projection: mat4x4f,
12
+ viewPos: vec3f,
13
+ _padding: f32,
14
+ };
15
+
16
+ struct Light {
17
+ direction: vec4f,
18
+ color: vec4f,
19
+ };
20
+
21
+ struct LightUniforms {
22
+ ambientColor: vec4f,
23
+ lights: array<Light, 4>,
24
+ };
25
+
26
+ struct MaterialUniforms {
27
+ diffuseColor: vec3f,
28
+ alpha: f32,
29
+ };
30
+
31
+ struct VertexOutput {
32
+ @builtin(position) position: vec4f,
33
+ @location(0) normal: vec3f,
34
+ @location(1) uv: vec2f,
35
+ @location(2) worldPos: vec3f,
36
+ };
37
+
38
+ struct LightVP { viewProj: mat4x4f, };
39
+
40
+ @group(0) @binding(0) var<uniform> camera: CameraUniforms;
41
+ @group(0) @binding(1) var<uniform> light: LightUniforms;
42
+ @group(0) @binding(2) var diffuseSampler: sampler;
43
+ @group(0) @binding(3) var shadowMap: texture_depth_2d;
44
+ @group(0) @binding(4) var shadowSampler: sampler_comparison;
45
+ @group(0) @binding(5) var<uniform> lightVP: LightVP;
46
+ @group(1) @binding(0) var<storage, read> skinMats: array<mat4x4f>;
47
+ @group(2) @binding(0) var diffuseTexture: texture_2d<f32>;
48
+ @group(2) @binding(1) var<uniform> material: MaterialUniforms;
49
+
50
+ fn sampleShadow(worldPos: vec3f, n: vec3f) -> f32 {
51
+ let biasedPos = worldPos + n * 0.08;
52
+ let lclip = lightVP.viewProj * vec4f(biasedPos, 1.0);
53
+ let ndc = lclip.xyz / max(lclip.w, 1e-6);
54
+ let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
55
+ let cmpZ = ndc.z - 0.001;
56
+ let ts = 1.0 / 4096.0;
57
+ var vis = 0.0;
58
+ for (var y = -1; y <= 1; y++) {
59
+ for (var x = -1; x <= 1; x++) {
60
+ vis += textureSampleCompare(shadowMap, shadowSampler, suv + vec2f(f32(x), f32(y)) * ts, cmpZ);
61
+ }
62
+ }
63
+ return vis / 9.0;
64
+ }
65
+
66
+ const PI_B: f32 = 3.141592653589793;
67
+ const F0_BODY: f32 = 0.04;
68
+ const BODY_ROUGHNESS: f32 = 0.3;
69
+ // Dump: 层权重.002 Blend; 运算.007 POWER exponent Value_001; 背景 Color; 运算.004 after invert
70
+ const BODY_RIM2_LAYER_BLEND: f32 = 0.20000000298023224;
71
+ const BODY_RIM2_POW: f32 = 1.4300000667572021;
72
+ const BODY_RIM2_BG: vec3f = vec3f(1.0, 0.4303792119026184, 0.3315804898738861);
73
+ const BODY_WARM_AO_MUL: f32 = 0.30000001192092896;
74
+ const BODY_MIX_NPR: f32 = 0.5;
75
+
76
+ fn ggx_d_body(ndoth: f32, a2: f32) -> f32 {
77
+ let denom = ndoth * ndoth * (a2 - 1.0) + 1.0;
78
+ return a2 / (PI_B * denom * denom);
79
+ }
80
+
81
+ fn smith_g1_body(ndotx: f32, a2: f32) -> f32 {
82
+ return 2.0 * ndotx / (ndotx + sqrt(a2 + (1.0 - a2) * ndotx * ndotx));
83
+ }
84
+
85
+ fn fresnel_schlick_body(cosTheta: f32, f0: f32) -> f32 {
86
+ return f0 + (1.0 - f0) * pow(1.0 - cosTheta, 5.0);
87
+ }
88
+
89
+ // smoothstep-based ramp: t*t*(3-2*t) between two color stops
90
+ fn ramp_ease(f: f32, p0: f32, c0: vec4f, p1: f32, c1: vec4f) -> vec4f {
91
+ let t = saturate((f - p0) / max(p1 - p0, 1e-6));
92
+ let ss = t * t * (3.0 - 2.0 * t);
93
+ return mix(c0, c1, ss);
94
+ }
95
+
96
+ @vertex fn vs(
97
+ @location(0) position: vec3f,
98
+ @location(1) normal: vec3f,
99
+ @location(2) uv: vec2f,
100
+ @location(3) joints0: vec4<u32>,
101
+ @location(4) weights0: vec4<f32>
102
+ ) -> VertexOutput {
103
+ var output: VertexOutput;
104
+ let pos4 = vec4f(position, 1.0);
105
+ let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
106
+ let invWeightSum = select(1.0, 1.0 / weightSum, weightSum > 0.0001);
107
+ let nw = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 * invWeightSum, weightSum > 0.0001);
108
+ var skinnedPos = vec4f(0.0);
109
+ var skinnedNrm = vec3f(0.0);
110
+ for (var i = 0u; i < 4u; i++) {
111
+ let m = skinMats[joints0[i]];
112
+ let w = nw[i];
113
+ skinnedPos += (m * pos4) * w;
114
+ skinnedNrm += (mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz) * normal) * w;
115
+ }
116
+ output.position = camera.projection * camera.view * vec4f(skinnedPos.xyz, 1.0);
117
+ output.normal = normalize(skinnedNrm);
118
+ output.uv = uv;
119
+ output.worldPos = skinnedPos.xyz;
120
+ return output;
121
+ }
122
+
123
+ @fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
124
+ let alpha = material.alpha;
125
+ if (alpha < 0.001) { discard; }
126
+
127
+ let n = normalize(input.normal);
128
+ let v = normalize(camera.viewPos - input.worldPos);
129
+ let l = -light.lights[0].direction.xyz;
130
+ let sun = light.lights[0].color.xyz * light.lights[0].color.w;
131
+
132
+ let tex_color = textureSample(diffuseTexture, diffuseSampler, input.uv).rgb;
133
+ let shadow = sampleShadow(input.worldPos, n);
134
+
135
+ // ═══ TOON MASK: ShaderToRGB → ramp.008 CONSTANT [0→black, 0.2966→white] ═══
136
+ let ndotl_raw = shader_to_rgb_diffuse(n, l, sun, light.ambientColor.xyz, shadow);
137
+ let toon = ramp_constant(ndotl_raw, 0.0, vec4f(0,0,0,1), 0.2966, vec4f(1,1,1,1)).r;
138
+
139
+ // ═══ TOON COLOR: Mix.004 A=HueSat, B=HueSat.001, Fac=ramp.008 (R) ═══
140
+ let shadow_tint = hue_sat(0.5, 2.0, 0.3499999940395355, 1.0, tex_color);
141
+ let lit_tint = hue_sat(0.5, 1.5, 1.0, 1.0, tex_color);
142
+ let toon_color = mix_blend(toon, shadow_tint, lit_tint);
143
+ let bc = bright_contrast(toon_color, 0.1, 0.2);
144
+
145
+ // ═══ AO CHAIN: AO → ramp CONSTANT [0→white, 0.5995→black] → Mix.003 ═══
146
+ let ao = ao_fake(n, v);
147
+ let ao_ramp = ramp_constant(ao, 0.0, vec4f(1,1,1,1), 0.5995, vec4f(0,0,0,1)).r;
148
+ let ao_mixed = mix_blend(ao_ramp, bc, vec3f(0.8301780223846436, 0.3345769941806793, 0.27946099638938904));
149
+
150
+ // ═══ EMISSION.003 (strength=4.0) ═══
151
+ let emission3 = ao_mixed * 4.0;
152
+
153
+ // ═══ WARM: 颜色渐变.008 → 运算.006 ADD +0.5 (m_graphs) → clamp → 颜色渐变.003 ═══
154
+ let ao_inv = invert_f(1.0, ao_ramp);
155
+ let warm_str = ao_inv * BODY_WARM_AO_MUL;
156
+ let warm_input = clamp(toon + 0.5, 0.0, 1.0);
157
+ let warm_color = ramp_cardinal(warm_input, 0.2409,
158
+ vec4f(0.2426, 0.068, 0.0588, 1.0), 0.4663,
159
+ vec4f(0.6677, 0.5024, 0.5126, 1.0)).rgb;
160
+ let warm_emission = warm_color * warm_str;
161
+
162
+ // ═══ RIM 1: 菲涅尔 × 层权重.001 Facing Blend=0.24 → 自发光 Strength ═══
163
+ let rim1_str = fresnel(2.0, n, v) * layer_weight_facing(0.24000005424022675, n, v);
164
+ let rim1 = vec3f(0.984157919883728, 0.6110184788703918, 0.5736401677131653) * rim1_str;
165
+
166
+ // ═══ RIM 2: 层权重.002 Facing → 运算.007 POWER → 颜色渐变.010 EASE → MixShader.002 Fac ═══
167
+ let facing_raw = layer_weight_facing(BODY_RIM2_LAYER_BLEND, n, v);
168
+ let facing_pow = math_power(facing_raw, BODY_RIM2_POW);
169
+ let rim2_fac = ramp_ease(facing_pow, 0.0, vec4f(0,0,0,1), 0.5052, vec4f(1,1,1,1)).r;
170
+ let rim2_mixed = mix(emission3, BODY_RIM2_BG, rim2_fac);
171
+
172
+ // ═══ NPR STACK: AddShader chain (no bright gate in body) ═══
173
+ let add0 = rim1 + rim2_mixed;
174
+ let npr_stack = add0 + warm_emission;
175
+
176
+ // ═══ PRINCIPLED BSDF: noise bump, GGX specular, SSS from AO ═══
177
+ let gen = mapping_point(input.worldPos, vec3f(0.0), vec3f(0.0), vec3f(1.0, 1.0, 1.5));
178
+ let noise_val = tex_noise(gen, 1.0, 2.0, 0.5, 0.0);
179
+ let noise_ramp = ramp_linear(noise_val, 0.0, vec4f(0,0,0,1), 1.0, vec4f(1,1,1,1)).r;
180
+ let bumped_n = bump_lh(0.324644535779953, noise_ramp, n, input.worldPos);
181
+
182
+ let principled_base = mix_blend(noise_ramp, bc, vec3f(0.6831911206245422, 0.19474034011363983, 0.13732507824897766));
183
+ let p_emission = bc * 0.2;
184
+
185
+ let ao2 = ao_fake(n, v);
186
+ let sss = ramp_linear(ao2, 0.003, vec4f(0,0,0,1), 1.0, vec4f(0.0786, 0.0786, 0.0786, 1.0)).r;
187
+
188
+ let p_ndotl = max(dot(bumped_n, l), 0.0);
189
+ let p_ndotv = max(dot(bumped_n, v), 0.001);
190
+ let h = normalize(l + v);
191
+ let p_ndoth = max(dot(bumped_n, h), 0.0);
192
+ let p_vdoth = max(dot(v, h), 0.0);
193
+ let a2 = BODY_ROUGHNESS * BODY_ROUGHNESS;
194
+ let D = ggx_d_body(p_ndoth, a2);
195
+ let G = smith_g1_body(p_ndotl, a2) * smith_g1_body(p_ndotv, a2);
196
+ let F = fresnel_schlick_body(p_vdoth, F0_BODY);
197
+ let spec = (D * G * F) / max(4.0 * p_ndotl * p_ndotv, 0.001) * ltc_brdf_scale(p_ndotv, BODY_ROUGHNESS);
198
+ let kd = (1.0 - F) * principled_base / PI_B;
199
+ let direct = (kd + spec) * sun * p_ndotl * shadow;
200
+ // Indirect diffuse = base_color × L_w per Blender closure_eval_surface_lib.glsl line 302;
201
+ // probe_evaluate_world_diff returns radiance (SH-projected, not cosine-convolved).
202
+ let ambient = principled_base * light.ambientColor.xyz;
203
+ let principled = ambient + direct + p_emission + vec3f(sss);
204
+
205
+ // 混合着色器.001: Shader=相加着色器.001, Shader_001=原理化BSDF
206
+ let final_color = mix(npr_stack, principled, BODY_MIX_NPR);
207
+
208
+ return vec4f(final_color, alpha);
209
+ }
210
+
211
+ `
@@ -0,0 +1,25 @@
1
+ // Material preset types for NPR pipeline dispatch.
2
+ // Mapping is explicit — the consumer provides a MaterialPresetMap that assigns
3
+ // material names to presets. Unmapped materials fall back to "default" (Principled BSDF).
4
+
5
+ export type MaterialPreset =
6
+ | "default"
7
+ | "face"
8
+ | "hair"
9
+ | "body"
10
+ | "eye"
11
+ | "stockings"
12
+ | "metal"
13
+ | "cloth_smooth"
14
+ | "cloth_rough"
15
+
16
+ // Keys = preset name, values = array of material names that should use that preset.
17
+ export type MaterialPresetMap = Partial<Record<MaterialPreset, string[]>>
18
+
19
+ export function resolvePreset(materialName: string, map: MaterialPresetMap | undefined): MaterialPreset {
20
+ if (!map) return "default"
21
+ for (const [preset, names] of Object.entries(map)) {
22
+ if (names && names.includes(materialName)) return preset as MaterialPreset
23
+ }
24
+ return "default"
25
+ }
@@ -0,0 +1,174 @@
1
+ // M_Rough_Cloth — NPR graph identical to M_Smooth_Cloth but bump chain IS live
2
+ // (噪波→渐变→凹凸.Normal → 原理化BSDF.Normal in m_graphs) and Roughness=0.8187.
3
+
4
+ import { NODES_WGSL } from "./nodes"
5
+
6
+ export const CLOTH_ROUGH_SHADER_WGSL = /* wgsl */ `
7
+
8
+ ${NODES_WGSL}
9
+
10
+ struct CameraUniforms {
11
+ view: mat4x4f,
12
+ projection: mat4x4f,
13
+ viewPos: vec3f,
14
+ _padding: f32,
15
+ };
16
+
17
+ struct Light {
18
+ direction: vec4f,
19
+ color: vec4f,
20
+ };
21
+
22
+ struct LightUniforms {
23
+ ambientColor: vec4f,
24
+ lights: array<Light, 4>,
25
+ };
26
+
27
+ struct MaterialUniforms {
28
+ diffuseColor: vec3f,
29
+ alpha: f32,
30
+ };
31
+
32
+ struct VertexOutput {
33
+ @builtin(position) position: vec4f,
34
+ @location(0) normal: vec3f,
35
+ @location(1) uv: vec2f,
36
+ @location(2) worldPos: vec3f,
37
+ };
38
+
39
+ struct LightVP { viewProj: mat4x4f, };
40
+
41
+ @group(0) @binding(0) var<uniform> camera: CameraUniforms;
42
+ @group(0) @binding(1) var<uniform> light: LightUniforms;
43
+ @group(0) @binding(2) var diffuseSampler: sampler;
44
+ @group(0) @binding(3) var shadowMap: texture_depth_2d;
45
+ @group(0) @binding(4) var shadowSampler: sampler_comparison;
46
+ @group(0) @binding(5) var<uniform> lightVP: LightVP;
47
+ @group(1) @binding(0) var<storage, read> skinMats: array<mat4x4f>;
48
+ @group(2) @binding(0) var diffuseTexture: texture_2d<f32>;
49
+ @group(2) @binding(1) var<uniform> material: MaterialUniforms;
50
+
51
+ fn sampleShadow(worldPos: vec3f, n: vec3f) -> f32 {
52
+ let biasedPos = worldPos + n * 0.08;
53
+ let lclip = lightVP.viewProj * vec4f(biasedPos, 1.0);
54
+ let ndc = lclip.xyz / max(lclip.w, 1e-6);
55
+ let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
56
+ let cmpZ = ndc.z - 0.001;
57
+ let ts = 1.0 / 4096.0;
58
+ var vis = 0.0;
59
+ for (var y = -1; y <= 1; y++) {
60
+ for (var x = -1; x <= 1; x++) {
61
+ vis += textureSampleCompare(shadowMap, shadowSampler, suv + vec2f(f32(x), f32(y)) * ts, cmpZ);
62
+ }
63
+ }
64
+ return vis / 9.0;
65
+ }
66
+
67
+ const PI_CR: f32 = 3.141592653589793;
68
+ const CLOTH_R_SPECULAR: f32 = 0.8;
69
+ const CLOTH_R_ROUGHNESS: f32 = 0.8187;
70
+ const CLOTH_R_TOON_EDGE: f32 = 0.2966;
71
+ const CLOTH_R_MIX04_MUL: f32 = 0.5;
72
+ const CLOTH_R_EMIT_STR: f32 = 18.200000762939453;
73
+ const CLOTH_R_MIX_SHADER_FAC: f32 = 0.8999999761581421;
74
+ const CLOTH_R_NOISE_SCALE: f32 = 17.7;
75
+ const CLOTH_R_BUMP_STR: f32 = 1.0;
76
+
77
+ @vertex fn vs(
78
+ @location(0) position: vec3f,
79
+ @location(1) normal: vec3f,
80
+ @location(2) uv: vec2f,
81
+ @location(3) joints0: vec4<u32>,
82
+ @location(4) weights0: vec4<f32>
83
+ ) -> VertexOutput {
84
+ var output: VertexOutput;
85
+ let pos4 = vec4f(position, 1.0);
86
+ let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
87
+ let invWeightSum = select(1.0, 1.0 / weightSum, weightSum > 0.0001);
88
+ let nw = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 * invWeightSum, weightSum > 0.0001);
89
+ var skinnedPos = vec4f(0.0);
90
+ var skinnedNrm = vec3f(0.0);
91
+ for (var i = 0u; i < 4u; i++) {
92
+ let m = skinMats[joints0[i]];
93
+ let w = nw[i];
94
+ skinnedPos += (m * pos4) * w;
95
+ skinnedNrm += (mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz) * normal) * w;
96
+ }
97
+ output.position = camera.projection * camera.view * vec4f(skinnedPos.xyz, 1.0);
98
+ output.normal = normalize(skinnedNrm);
99
+ output.uv = uv;
100
+ output.worldPos = skinnedPos.xyz;
101
+ return output;
102
+ }
103
+
104
+ @fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
105
+ let n = normalize(input.normal);
106
+ let v = normalize(camera.viewPos - input.worldPos);
107
+ let l = -light.lights[0].direction.xyz;
108
+ let sun = light.lights[0].color.xyz * light.lights[0].color.w;
109
+ let amb = light.ambientColor.xyz;
110
+ let shadow = sampleShadow(input.worldPos, n);
111
+
112
+ let tex_s = textureSample(diffuseTexture, diffuseSampler, input.uv);
113
+ let tex_rgb = tex_s.rgb;
114
+ let out_alpha = material.alpha * tex_s.a;
115
+ if (out_alpha < 0.001) { discard; }
116
+
117
+ // Shader→RGB → 颜色渐变.008 CONSTANT (edge AA terminator)
118
+ let lum_shade = shader_to_rgb_diffuse(n, l, sun, amb, shadow);
119
+ let ramp008 = ramp_constant_edge_aa(lum_shade, CLOTH_R_TOON_EDGE, vec4f(0,0,0,1), vec4f(1,1,1,1));
120
+ let toon_r = ramp008.r;
121
+ let mix04_fac = math_multiply(toon_r, CLOTH_R_MIX04_MUL);
122
+
123
+ // 混合.004: A=色相/饱和度/明度.002(Hue=0.5 Sat=1.0 Val=0.2), B=纹理
124
+ let dark_tex = hue_sat(0.5, 1.0, 0.19999998807907104, 1.0, tex_rgb);
125
+ let mix04 = mix_blend(mix04_fac, dark_tex, tex_rgb);
126
+
127
+ // 倒角.001.Z → 混合.003: A=混合.004, B=色相/饱和度/明度.002
128
+ let bevel_z = clamp(n.y, 0.0, 1.0);
129
+ let mix03 = mix_blend(bevel_z, mix04, dark_tex);
130
+
131
+ // 环境光遮蔽 → 颜色渐变.001 LINEAR → 混合.001 (white/black) → 混合.002 OVERLAY Fac
132
+ let ao = ao_fake(n, v);
133
+ let ao_ramp_c = ramp_linear(ao, 0.0, vec4f(1,1,1,1), 0.8808, vec4f(0,0,0,1));
134
+ let mix01_fac = ao_ramp_c.r;
135
+ let mix01_rgb = mix(vec3f(1.0), vec3f(0.0), mix01_fac);
136
+
137
+ // 混合.002 OVERLAY: Fac=混合.001, A=混合.003, B=色相/饱和度/明度.004
138
+ let hue004 = hue_sat(0.5, 0.800000011920929, 2.0, 1.0, mix03);
139
+ let overlay_fac = mix01_rgb.r;
140
+ let npr_rgb = mix_overlay(overlay_fac, mix03, hue004);
141
+ let npr_emission = npr_rgb * CLOTH_R_EMIT_STR;
142
+
143
+ // 噪波→渐变→凹凸 (LIVE in M_Rough_Cloth — unlike Smooth Cloth): Strength=1.0, noise Scale=17.7.
144
+ let noise_uv = mapping_point(input.worldPos, vec3f(0.0), vec3f(0.0), vec3f(1.0));
145
+ let noise_val = tex_noise(noise_uv, CLOTH_R_NOISE_SCALE, 2.0, 0.5, 0.0);
146
+ let noise_ramp = ramp_linear(noise_val, 0.0, vec4f(0,0,0,1), 1.0, vec4f(1,1,1,1)).r;
147
+ let bumped_n = bump_lh(CLOTH_R_BUMP_STR, noise_ramp, n, input.worldPos);
148
+
149
+ // 原理化BSDF (EEVEE port): metallic=0, specular=0.8, roughness=0.8187, specular_tint=0.
150
+ let principled_base = hue_sat(0.5, 1.0, 0.800000011920929, 1.0, tex_rgb);
151
+ let NL = max(dot(bumped_n, l), 0.0);
152
+ let NV = max(dot(bumped_n, v), 1e-4);
153
+
154
+ let f0 = vec3f(0.08 * CLOTH_R_SPECULAR);
155
+ let f90 = mix(f0, vec3f(1.0), sqrt(CLOTH_R_SPECULAR));
156
+ let split_sum = brdf_lut_baked(NV, CLOTH_R_ROUGHNESS);
157
+ let reflection_color = F_brdf_multi_scatter(f0, f90, split_sum);
158
+
159
+ let spec_direct = bsdf_ggx(bumped_n, l, v, CLOTH_R_ROUGHNESS) * sun * shadow * ltc_brdf_scale(NV, CLOTH_R_ROUGHNESS);
160
+ let spec_indirect = amb;
161
+ let spec_radiance = (spec_direct + spec_indirect) * reflection_color;
162
+
163
+ // Indirect diffuse = base_color × L_w per Blender closure_eval_surface_lib.glsl line 302;
164
+ // probe_evaluate_world_diff returns radiance (SH-projected, not cosine-convolved).
165
+ let diffuse_radiance = principled_base * (sun * NL * shadow / PI_CR + amb);
166
+ let principled = diffuse_radiance + spec_radiance;
167
+
168
+ // 混合着色器.001 Fac=0.9: Shader=自发光.005, Shader_001=原理化BSDF
169
+ let final_color = mix(npr_emission, principled, CLOTH_R_MIX_SHADER_FAC);
170
+
171
+ return vec4f(final_color, out_alpha);
172
+ }
173
+
174
+ `
@@ -0,0 +1,173 @@
1
+ // M_Smooth_Cloth — dump socket order + m_graphs ramps/overlay/noise-bump (dump omits 噪波→凹凸 subtree).
2
+
3
+ import { NODES_WGSL } from "./nodes"
4
+
5
+ export const CLOTH_SMOOTH_SHADER_WGSL = /* wgsl */ `
6
+
7
+ ${NODES_WGSL}
8
+
9
+ struct CameraUniforms {
10
+ view: mat4x4f,
11
+ projection: mat4x4f,
12
+ viewPos: vec3f,
13
+ _padding: f32,
14
+ };
15
+
16
+ struct Light {
17
+ direction: vec4f,
18
+ color: vec4f,
19
+ };
20
+
21
+ struct LightUniforms {
22
+ ambientColor: vec4f,
23
+ lights: array<Light, 4>,
24
+ };
25
+
26
+ struct MaterialUniforms {
27
+ diffuseColor: vec3f,
28
+ alpha: f32,
29
+ };
30
+
31
+ struct VertexOutput {
32
+ @builtin(position) position: vec4f,
33
+ @location(0) normal: vec3f,
34
+ @location(1) uv: vec2f,
35
+ @location(2) worldPos: vec3f,
36
+ };
37
+
38
+ struct LightVP { viewProj: mat4x4f, };
39
+
40
+ @group(0) @binding(0) var<uniform> camera: CameraUniforms;
41
+ @group(0) @binding(1) var<uniform> light: LightUniforms;
42
+ @group(0) @binding(2) var diffuseSampler: sampler;
43
+ @group(0) @binding(3) var shadowMap: texture_depth_2d;
44
+ @group(0) @binding(4) var shadowSampler: sampler_comparison;
45
+ @group(0) @binding(5) var<uniform> lightVP: LightVP;
46
+ @group(1) @binding(0) var<storage, read> skinMats: array<mat4x4f>;
47
+ @group(2) @binding(0) var diffuseTexture: texture_2d<f32>;
48
+ @group(2) @binding(1) var<uniform> material: MaterialUniforms;
49
+
50
+ fn sampleShadow(worldPos: vec3f, n: vec3f) -> f32 {
51
+ let biasedPos = worldPos + n * 0.08;
52
+ let lclip = lightVP.viewProj * vec4f(biasedPos, 1.0);
53
+ let ndc = lclip.xyz / max(lclip.w, 1e-6);
54
+ let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
55
+ let cmpZ = ndc.z - 0.001;
56
+ let ts = 1.0 / 4096.0;
57
+ var vis = 0.0;
58
+ for (var y = -1; y <= 1; y++) {
59
+ for (var x = -1; x <= 1; x++) {
60
+ vis += textureSampleCompare(shadowMap, shadowSampler, suv + vec2f(f32(x), f32(y)) * ts, cmpZ);
61
+ }
62
+ }
63
+ return vis / 9.0;
64
+ }
65
+
66
+ const PI_C: f32 = 3.141592653589793;
67
+ const CLOTH_SPECULAR: f32 = 0.8;
68
+ const CLOTH_ROUGHNESS: f32 = 0.5;
69
+ const CLOTH_TOON_EDGE: f32 = 0.2966;
70
+ const CLOTH_MIX04_MUL: f32 = 0.5; // 运算.004 MULTIPLY Value_001 (dump)
71
+ const NPR_EMIT_STR: f32 = 18.200000762939453;
72
+ const NPR_MIX_SHADER_FAC: f32 = 0.8999999761581421;
73
+
74
+ @vertex fn vs(
75
+ @location(0) position: vec3f,
76
+ @location(1) normal: vec3f,
77
+ @location(2) uv: vec2f,
78
+ @location(3) joints0: vec4<u32>,
79
+ @location(4) weights0: vec4<f32>
80
+ ) -> VertexOutput {
81
+ var output: VertexOutput;
82
+ let pos4 = vec4f(position, 1.0);
83
+ let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
84
+ let invWeightSum = select(1.0, 1.0 / weightSum, weightSum > 0.0001);
85
+ let nw = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 * invWeightSum, weightSum > 0.0001);
86
+ var skinnedPos = vec4f(0.0);
87
+ var skinnedNrm = vec3f(0.0);
88
+ for (var i = 0u; i < 4u; i++) {
89
+ let m = skinMats[joints0[i]];
90
+ let w = nw[i];
91
+ skinnedPos += (m * pos4) * w;
92
+ skinnedNrm += (mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz) * normal) * w;
93
+ }
94
+ output.position = camera.projection * camera.view * vec4f(skinnedPos.xyz, 1.0);
95
+ output.normal = normalize(skinnedNrm);
96
+ output.uv = uv;
97
+ output.worldPos = skinnedPos.xyz;
98
+ return output;
99
+ }
100
+
101
+ @fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
102
+ let n = normalize(input.normal);
103
+ let v = normalize(camera.viewPos - input.worldPos);
104
+ let l = -light.lights[0].direction.xyz;
105
+ let sun = light.lights[0].color.xyz * light.lights[0].color.w;
106
+ let amb = light.ambientColor.xyz;
107
+ let shadow = sampleShadow(input.worldPos, n);
108
+
109
+ let tex_s = textureSample(diffuseTexture, diffuseSampler, input.uv);
110
+ let tex_rgb = tex_s.rgb;
111
+ let out_alpha = material.alpha * tex_s.a;
112
+ if (out_alpha < 0.001) { discard; }
113
+
114
+ // Shader→RGB → 颜色渐变.008 CONSTANT — AA like face (same terminator artifact class)
115
+ let lum_shade = shader_to_rgb_diffuse(n, l, sun, amb, shadow);
116
+ let ramp008 = ramp_constant_edge_aa(lum_shade, CLOTH_TOON_EDGE, vec4f(0,0,0,1), vec4f(1,1,1,1));
117
+ let toon_r = ramp008.r;
118
+ // 颜色渐变.008 → 运算.004 MULTIPLY 0.5 → 混合.004 Factor
119
+ let mix04_fac = math_multiply(toon_r, CLOTH_MIX04_MUL);
120
+
121
+ // 混合.004: A=色相/饱和度/明度.002, B=纹理
122
+ let dark_tex = hue_sat(0.5, 1.0, 0.19999998807907104, 1.0, tex_rgb);
123
+ let mix04 = mix_blend(mix04_fac, dark_tex, tex_rgb);
124
+
125
+ // 倒角.001→Z → 混合.003 Factor; A=混合.004, B=色相/饱和度/明度.002
126
+ let bevel_z = clamp(n.y, 0.0, 1.0);
127
+ let mix03 = mix_blend(bevel_z, mix04, dark_tex);
128
+
129
+ // 环境光遮蔽 → 颜色渐变.001 LINEAR → 混合.001 (白/黑) → 混合.002 OVERLAY Fac
130
+ let ao = ao_fake(n, v);
131
+ let ao_ramp_c = ramp_linear(ao, 0.0, vec4f(1,1,1,1), 0.8808, vec4f(0,0,0,1));
132
+ let mix01_fac = ao_ramp_c.r;
133
+ let mix01_rgb = mix(vec3f(1.0), vec3f(0.0), mix01_fac);
134
+
135
+ // 混合.002 OVERLAY: Fac=混合.001, A=混合.003, B=色相/饱和度/明度.004
136
+ let hue004 = hue_sat(0.5, 0.800000011920929, 2.0, 1.0, mix03);
137
+ let overlay_fac = mix01_rgb.r;
138
+ let npr_rgb = mix_overlay(overlay_fac, mix03, hue004);
139
+ let npr_emission = npr_rgb * NPR_EMIT_STR;
140
+
141
+ // 原理化BSDF (EEVEE port): metallic=0, specular=0.8, roughness=0.5, specular_tint=0.
142
+ // Bump subtree is dead in the Blender graph (噪波→凹凸 not linked to Principled.Normal).
143
+ let principled_base = hue_sat(0.5, 1.0, 0.800000011920929, 1.0, tex_rgb);
144
+ let NL = max(dot(n, l), 0.0);
145
+ let NV = max(dot(n, v), 1e-4);
146
+
147
+ // f0/f90 per gpu_shader_material_principled.glsl — specular_tint=0 → dielectric_f0_color=white.
148
+ let f0 = vec3f(0.08 * CLOTH_SPECULAR);
149
+ let f90 = mix(f0, vec3f(1.0), sqrt(CLOTH_SPECULAR));
150
+ let split_sum = brdf_lut_baked(NV, CLOTH_ROUGHNESS);
151
+ let reflection_color = F_brdf_multi_scatter(f0, f90, split_sum);
152
+
153
+ // Direct glossy — bsdf_ggx already includes NL; no F applied here (tinted after accum).
154
+ // ltc_brdf_scale: EEVEE direct path uses LTC; split-sum LUT path is rescaled to match.
155
+ let spec_direct = bsdf_ggx(n, l, v, CLOTH_ROUGHNESS) * sun * shadow * ltc_brdf_scale(NV, CLOTH_ROUGHNESS);
156
+ // Indirect glossy — flat world probe (solid color). Phase 2 adds cubemap.
157
+ let spec_indirect = amb;
158
+ let spec_radiance = (spec_direct + spec_indirect) * reflection_color;
159
+
160
+ // Diffuse (Lambert), no (1-F) factor per EEVEE — it doesn't energy-conserve spec<->diffuse.
161
+ // probe_evaluate_world_diff returns radiance L_w (SH projected, not cosine-convolved); in
162
+ // closure_eval_surface_lib line 302: closure.radiance += diffuse_accum * L_w * diffuse.color.
163
+ // So indirect diffuse = base_color × L_w, no π factor.
164
+ let diffuse_radiance = principled_base * (sun * NL * shadow / PI_C + amb);
165
+ let principled = diffuse_radiance + spec_radiance;
166
+
167
+ // 混合着色器.001: Shader=自发光.005, Shader_001=原理化BSDF, Fac=0.9
168
+ let final_color = mix(npr_emission, principled, NPR_MIX_SHADER_FAC);
169
+
170
+ return vec4f(final_color, out_alpha);
171
+ }
172
+
173
+ `