reze-engine 0.12.1 → 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 (46) hide show
  1. package/README.md +99 -58
  2. package/dist/shaders/materials/body.d.ts +1 -1
  3. package/dist/shaders/materials/body.d.ts.map +1 -1
  4. package/dist/shaders/materials/body.js +86 -197
  5. package/dist/shaders/materials/cloth_rough.d.ts +1 -1
  6. package/dist/shaders/materials/cloth_rough.d.ts.map +1 -1
  7. package/dist/shaders/materials/cloth_rough.js +10 -121
  8. package/dist/shaders/materials/cloth_smooth.d.ts +1 -1
  9. package/dist/shaders/materials/cloth_smooth.d.ts.map +1 -1
  10. package/dist/shaders/materials/cloth_smooth.js +59 -172
  11. package/dist/shaders/materials/common.d.ts +6 -0
  12. package/dist/shaders/materials/common.d.ts.map +1 -0
  13. package/dist/shaders/materials/common.js +144 -0
  14. package/dist/shaders/materials/default.d.ts +1 -1
  15. package/dist/shaders/materials/default.d.ts.map +1 -1
  16. package/dist/shaders/materials/default.js +12 -145
  17. package/dist/shaders/materials/eye.d.ts +1 -1
  18. package/dist/shaders/materials/eye.d.ts.map +1 -1
  19. package/dist/shaders/materials/eye.js +12 -117
  20. package/dist/shaders/materials/face.d.ts +1 -1
  21. package/dist/shaders/materials/face.d.ts.map +1 -1
  22. package/dist/shaders/materials/face.js +85 -197
  23. package/dist/shaders/materials/hair.d.ts +1 -1
  24. package/dist/shaders/materials/hair.d.ts.map +1 -1
  25. package/dist/shaders/materials/hair.js +78 -183
  26. package/dist/shaders/materials/metal.d.ts +1 -1
  27. package/dist/shaders/materials/metal.d.ts.map +1 -1
  28. package/dist/shaders/materials/metal.js +15 -121
  29. package/dist/shaders/materials/nodes.d.ts +1 -1
  30. package/dist/shaders/materials/nodes.d.ts.map +1 -1
  31. package/dist/shaders/materials/nodes.js +77 -0
  32. package/dist/shaders/materials/stockings.d.ts +1 -1
  33. package/dist/shaders/materials/stockings.d.ts.map +1 -1
  34. package/dist/shaders/materials/stockings.js +26 -152
  35. package/package.json +1 -1
  36. package/src/shaders/materials/body.ts +90 -201
  37. package/src/shaders/materials/cloth_rough.ts +10 -121
  38. package/src/shaders/materials/cloth_smooth.ts +63 -176
  39. package/src/shaders/materials/common.ts +155 -0
  40. package/src/shaders/materials/default.ts +12 -145
  41. package/src/shaders/materials/eye.ts +12 -117
  42. package/src/shaders/materials/face.ts +89 -201
  43. package/src/shaders/materials/hair.ts +82 -187
  44. package/src/shaders/materials/metal.ts +15 -121
  45. package/src/shaders/materials/nodes.ts +77 -0
  46. package/src/shaders/materials/stockings.ts +27 -153
@@ -1,76 +1,14 @@
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.
1
+ // M_Rough_Cloth — NPR graph identical to M_Smooth_Cloth, but the noise bump subtree
2
+ // IS live on Principled.Normal and Roughness is raised to 0.8187.
3
3
 
4
4
  import { NODES_WGSL } from "./nodes"
5
+ import { COMMON_MATERIAL_PRELUDE_WGSL } from "./common"
5
6
 
6
7
  export const CLOTH_ROUGH_SHADER_WGSL = /* wgsl */ `
7
8
 
8
9
  ${NODES_WGSL}
10
+ ${COMMON_MATERIAL_PRELUDE_WGSL}
9
11
 
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
- // Back-facing to key light: direct contribution is zero anyway, skip 9 texture samples.
53
- if (dot(n, -light.lights[0].direction.xyz) <= 0.0) { return 0.0; }
54
- let biasedPos = worldPos + n * 0.08;
55
- let lclip = lightVP.viewProj * vec4f(biasedPos, 1.0);
56
- let ndc = lclip.xyz / max(lclip.w, 1e-6);
57
- let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
58
- let cmpZ = ndc.z - 0.001;
59
- let ts = 1.0 / 2048.0;
60
- // 3x3 PCF unrolled — Safari's Metal backend doesn't unroll nested shadow loops reliably.
61
- let s00 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(-ts, -ts), cmpZ);
62
- let s10 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(0.0, -ts), cmpZ);
63
- let s20 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f( ts, -ts), cmpZ);
64
- let s01 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(-ts, 0.0), cmpZ);
65
- let s11 = textureSampleCompareLevel(shadowMap, shadowSampler, suv, cmpZ);
66
- let s21 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f( ts, 0.0), cmpZ);
67
- let s02 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(-ts, ts), cmpZ);
68
- let s12 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(0.0, ts), cmpZ);
69
- let s22 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f( ts, ts), cmpZ);
70
- return (s00 + s10 + s20 + s01 + s11 + s21 + s02 + s12 + s22) * (1.0 / 9.0);
71
- }
72
-
73
- const PI_CR: f32 = 3.141592653589793;
74
12
  const CLOTH_R_SPECULAR: f32 = 0.8;
75
13
  const CLOTH_R_ROUGHNESS: f32 = 0.8187;
76
14
  const CLOTH_R_TOON_EDGE: f32 = 0.2966;
@@ -79,42 +17,8 @@ const CLOTH_R_EMIT_STR: f32 = 18.200000762939453;
79
17
  const CLOTH_R_MIX_SHADER_FAC: f32 = 0.8999999761581421;
80
18
  const CLOTH_R_NOISE_SCALE: f32 = 17.7;
81
19
  const CLOTH_R_BUMP_STR: f32 = 1.0;
82
- // EEVEE Light Clamp equivalent — caps firefly specular from noise-bumped NDF aliasing.
83
20
  const CLOTH_R_SPEC_CLAMP: f32 = 10.0;
84
21
 
85
- @vertex fn vs(
86
- @location(0) position: vec3f,
87
- @location(1) normal: vec3f,
88
- @location(2) uv: vec2f,
89
- @location(3) joints0: vec4<u32>,
90
- @location(4) weights0: vec4<f32>
91
- ) -> VertexOutput {
92
- var output: VertexOutput;
93
- let pos4 = vec4f(position, 1.0);
94
- let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
95
- let invWeightSum = select(1.0, 1.0 / weightSum, weightSum > 0.0001);
96
- let nw = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 * invWeightSum, weightSum > 0.0001);
97
- var skinnedPos = vec4f(0.0);
98
- var skinnedNrm = vec3f(0.0);
99
- for (var i = 0u; i < 4u; i++) {
100
- let m = skinMats[joints0[i]];
101
- let w = nw[i];
102
- skinnedPos += (m * pos4) * w;
103
- skinnedNrm += (mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz) * normal) * w;
104
- }
105
- output.position = camera.projection * camera.view * vec4f(skinnedPos.xyz, 1.0);
106
- // Skip VS normalize — interpolation denormalizes anyway, and FS always does normalize(input.normal).
107
- output.normal = skinnedNrm;
108
- output.uv = uv;
109
- output.worldPos = skinnedPos.xyz;
110
- return output;
111
- }
112
-
113
- struct FSOut {
114
- @location(0) color: vec4f,
115
- @location(1) mask: f32,
116
- };
117
-
118
22
  @fragment fn fs(input: VertexOutput) -> FSOut {
119
23
  let n = normalize(input.normal);
120
24
  let v = normalize(camera.viewPos - input.worldPos);
@@ -128,8 +32,8 @@ struct FSOut {
128
32
  let out_alpha = material.alpha * tex_s.a;
129
33
  if (out_alpha < 0.001) { discard; }
130
34
 
35
+ // ═══ NPR STACK ═══
131
36
  let lum_shade = shader_to_rgb_diffuse(n, l, sun, amb, shadow);
132
- // ramp_constant_edge_aa: avoids binary fac shimmer on terminator (fwidth + smoothstep).
133
37
  let ramp008 = ramp_constant_edge_aa(lum_shade, CLOTH_R_TOON_EDGE, vec4f(0,0,0,1), vec4f(1,1,1,1));
134
38
  let mix04_fac = math_multiply(ramp008.r, CLOTH_R_MIX04_MUL);
135
39
 
@@ -143,32 +47,17 @@ struct FSOut {
143
47
  let npr_rgb = mix_overlay(1.0, mix03, hue004);
144
48
  let npr_emission = npr_rgb * CLOTH_R_EMIT_STR;
145
49
 
146
- // Noise bump is LIVE in M_Rough_Cloth unlike Smooth Cloth where the subtree is dead.
50
+ // ═══ PRINCIPLED BSDF with noise bump (live in this preset) ═══
147
51
  let noise_val = tex_noise_d2(input.worldPos, CLOTH_R_NOISE_SCALE);
148
52
  let noise_ramp = ramp_linear(noise_val, 0.0, vec4f(0,0,0,1), 1.0, vec4f(1,1,1,1)).r;
149
53
  let bumped_n = bump_lh(CLOTH_R_BUMP_STR, noise_ramp, n, input.worldPos);
150
54
 
151
- // 原理化BSDF (EEVEE port): metallic=0, specular=0.8, roughness=0.8187, specular_tint=0.
152
55
  let principled_base = hue_sat_id(1.0, 0.800000011920929, 1.0, tex_rgb);
153
- let NL = max(dot(bumped_n, l), 0.0);
154
- let NV = max(dot(bumped_n, v), 1e-4);
155
-
156
- let f0 = vec3f(0.08 * CLOTH_R_SPECULAR);
157
- let f90 = mix(f0, vec3f(1.0), sqrt(CLOTH_R_SPECULAR));
158
- let brdf_lut = brdf_lut_sample(NV, CLOTH_R_ROUGHNESS);
159
- let reflection_color = F_brdf_multi_scatter(f0, f90, brdf_lut.xy);
160
-
161
- let spec_direct_raw = bsdf_ggx(bumped_n, l, v, NL, NV, CLOTH_R_ROUGHNESS) * sun * shadow * ltc_brdf_scale_from_lut(brdf_lut);
162
- let spec_direct = min(spec_direct_raw, vec3f(CLOTH_R_SPEC_CLAMP));
163
- let spec_indirect = amb;
164
- let spec_radiance = (spec_direct + spec_indirect) * reflection_color;
165
-
166
- // Indirect diffuse = base_color × L_w per Blender closure_eval_surface_lib.glsl line 302;
167
- // probe_evaluate_world_diff returns radiance (SH-projected, not cosine-convolved).
168
- let diffuse_radiance = principled_base * (sun * NL * shadow / PI_CR + amb);
169
- let principled = diffuse_radiance + spec_radiance;
56
+ let principled = eval_principled(
57
+ PrincipledIn(principled_base, 0.0, CLOTH_R_SPECULAR, CLOTH_R_ROUGHNESS, CLOTH_R_SPEC_CLAMP, 0.0, 0.0),
58
+ bumped_n, l, v, sun, amb, shadow
59
+ );
170
60
 
171
- // 混合着色器.001 Fac=0.9: Shader=自发光.005, Shader_001=原理化BSDF
172
61
  let final_color = mix(npr_emission, principled, CLOTH_R_MIX_SHADER_FAC);
173
62
 
174
63
  var out: FSOut;
@@ -1,176 +1,63 @@
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
- // Back-facing to key light: direct contribution is zero anyway, skip 9 texture samples.
52
- if (dot(n, -light.lights[0].direction.xyz) <= 0.0) { return 0.0; }
53
- let biasedPos = worldPos + n * 0.08;
54
- let lclip = lightVP.viewProj * vec4f(biasedPos, 1.0);
55
- let ndc = lclip.xyz / max(lclip.w, 1e-6);
56
- let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
57
- let cmpZ = ndc.z - 0.001;
58
- let ts = 1.0 / 2048.0;
59
- // 3x3 PCF unrolled — Safari's Metal backend doesn't unroll nested shadow loops reliably.
60
- let s00 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(-ts, -ts), cmpZ);
61
- let s10 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(0.0, -ts), cmpZ);
62
- let s20 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f( ts, -ts), cmpZ);
63
- let s01 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(-ts, 0.0), cmpZ);
64
- let s11 = textureSampleCompareLevel(shadowMap, shadowSampler, suv, cmpZ);
65
- let s21 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f( ts, 0.0), cmpZ);
66
- let s02 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(-ts, ts), cmpZ);
67
- let s12 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(0.0, ts), cmpZ);
68
- let s22 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f( ts, ts), cmpZ);
69
- return (s00 + s10 + s20 + s01 + s11 + s21 + s02 + s12 + s22) * (1.0 / 9.0);
70
- }
71
-
72
- const PI_C: f32 = 3.141592653589793;
73
- const CLOTH_SPECULAR: f32 = 0.8;
74
- const CLOTH_ROUGHNESS: f32 = 0.5;
75
- const CLOTH_TOON_EDGE: f32 = 0.2966;
76
- const CLOTH_MIX04_MUL: f32 = 0.5; // 运算.004 MULTIPLY Value_001 (dump)
77
- const NPR_EMIT_STR: f32 = 18.200000762939453;
78
- const NPR_MIX_SHADER_FAC: f32 = 0.8999999761581421;
79
-
80
- @vertex fn vs(
81
- @location(0) position: vec3f,
82
- @location(1) normal: vec3f,
83
- @location(2) uv: vec2f,
84
- @location(3) joints0: vec4<u32>,
85
- @location(4) weights0: vec4<f32>
86
- ) -> VertexOutput {
87
- var output: VertexOutput;
88
- let pos4 = vec4f(position, 1.0);
89
- let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
90
- let invWeightSum = select(1.0, 1.0 / weightSum, weightSum > 0.0001);
91
- let nw = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 * invWeightSum, weightSum > 0.0001);
92
- var skinnedPos = vec4f(0.0);
93
- var skinnedNrm = vec3f(0.0);
94
- for (var i = 0u; i < 4u; i++) {
95
- let m = skinMats[joints0[i]];
96
- let w = nw[i];
97
- skinnedPos += (m * pos4) * w;
98
- skinnedNrm += (mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz) * normal) * w;
99
- }
100
- output.position = camera.projection * camera.view * vec4f(skinnedPos.xyz, 1.0);
101
- // Skip VS normalize — interpolation denormalizes anyway, and FS always does normalize(input.normal).
102
- output.normal = skinnedNrm;
103
- output.uv = uv;
104
- output.worldPos = skinnedPos.xyz;
105
- return output;
106
- }
107
-
108
- struct FSOut {
109
- @location(0) color: vec4f,
110
- @location(1) mask: f32,
111
- };
112
-
113
- @fragment fn fs(input: VertexOutput) -> FSOut {
114
- let n = normalize(input.normal);
115
- let v = normalize(camera.viewPos - input.worldPos);
116
- let l = -light.lights[0].direction.xyz;
117
- let sun = light.lights[0].color.xyz * light.lights[0].color.w;
118
- let amb = light.ambientColor.xyz;
119
- let shadow = sampleShadow(input.worldPos, n);
120
-
121
- let tex_s = textureSample(diffuseTexture, diffuseSampler, input.uv);
122
- let tex_rgb = tex_s.rgb;
123
- let out_alpha = material.alpha * tex_s.a;
124
- if (out_alpha < 0.001) { discard; }
125
-
126
- let lum_shade = shader_to_rgb_diffuse(n, l, sun, amb, shadow);
127
- // ramp_constant_edge_aa: avoids binary fac shimmer on terminator (fwidth + smoothstep).
128
- let ramp008 = ramp_constant_edge_aa(lum_shade, CLOTH_TOON_EDGE, vec4f(0,0,0,1), vec4f(1,1,1,1));
129
- let mix04_fac = math_multiply(ramp008.r, CLOTH_MIX04_MUL);
130
-
131
- let dark_tex = hue_sat_id(1.0, 0.19999998807907104, 1.0, tex_rgb);
132
- let mix04 = mix_blend(mix04_fac, dark_tex, tex_rgb);
133
-
134
- let bevel_z = clamp(n.y, 0.0, 1.0);
135
- let mix03 = mix_blend(bevel_z, mix04, dark_tex);
136
-
137
- let hue004 = hue_sat_id(0.800000011920929, 2.0, 1.0, mix03);
138
- let npr_rgb = mix_overlay(1.0, mix03, hue004);
139
- let npr_emission = npr_rgb * NPR_EMIT_STR;
140
-
141
- // Principled BSDF (EEVEE port): metallic=0, specular=0.8, roughness=0.5, specular_tint=0.
142
- // Bump subtree is dead in the Blender graph (noise→bump not linked to Principled.Normal).
143
- let principled_base = hue_sat_id(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 brdf_lut = brdf_lut_sample(NV, CLOTH_ROUGHNESS);
151
- let reflection_color = F_brdf_multi_scatter(f0, f90, brdf_lut.xy);
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, NL, NV, CLOTH_ROUGHNESS) * sun * shadow * ltc_brdf_scale_from_lut(brdf_lut);
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
- var out: FSOut;
171
- out.color = vec4f(final_color, out_alpha);
172
- out.mask = 1.0;
173
- return out;
174
- }
175
-
176
- `
1
+ // M_Smooth_Cloth — NPR toon + bevel + overlay-boosted emission mixed 10/90 against
2
+ // a plain Principled BSDF. Bump subtree is dead in the Blender graph.
3
+
4
+ import { NODES_WGSL } from "./nodes"
5
+ import { COMMON_MATERIAL_PRELUDE_WGSL } from "./common"
6
+
7
+ export const CLOTH_SMOOTH_SHADER_WGSL = /* wgsl */ `
8
+
9
+ ${NODES_WGSL}
10
+ ${COMMON_MATERIAL_PRELUDE_WGSL}
11
+
12
+ const CLOTH_SPECULAR: f32 = 0.8;
13
+ const CLOTH_ROUGHNESS: f32 = 0.5;
14
+ const CLOTH_TOON_EDGE: f32 = 0.2966;
15
+ const CLOTH_MIX04_MUL: f32 = 0.5;
16
+ const NPR_EMIT_STR: f32 = 18.200000762939453;
17
+ const NPR_MIX_SHADER_FAC: f32 = 0.8999999761581421;
18
+
19
+ @fragment fn fs(input: VertexOutput) -> FSOut {
20
+ let n = normalize(input.normal);
21
+ let v = normalize(camera.viewPos - input.worldPos);
22
+ let l = -light.lights[0].direction.xyz;
23
+ let sun = light.lights[0].color.xyz * light.lights[0].color.w;
24
+ let amb = light.ambientColor.xyz;
25
+ let shadow = sampleShadow(input.worldPos, n);
26
+
27
+ let tex_s = textureSample(diffuseTexture, diffuseSampler, input.uv);
28
+ let tex_rgb = tex_s.rgb;
29
+ let out_alpha = material.alpha * tex_s.a;
30
+ if (out_alpha < 0.001) { discard; }
31
+
32
+ // ═══ NPR STACK ═══
33
+ let lum_shade = shader_to_rgb_diffuse(n, l, sun, amb, shadow);
34
+ let ramp008 = ramp_constant_edge_aa(lum_shade, CLOTH_TOON_EDGE, vec4f(0,0,0,1), vec4f(1,1,1,1));
35
+ let mix04_fac = math_multiply(ramp008.r, CLOTH_MIX04_MUL);
36
+
37
+ let dark_tex = hue_sat_id(1.0, 0.19999998807907104, 1.0, tex_rgb);
38
+ let mix04 = mix_blend(mix04_fac, dark_tex, tex_rgb);
39
+
40
+ let bevel_z = clamp(n.y, 0.0, 1.0);
41
+ let mix03 = mix_blend(bevel_z, mix04, dark_tex);
42
+
43
+ let hue004 = hue_sat_id(0.800000011920929, 2.0, 1.0, mix03);
44
+ let npr_rgb = mix_overlay(1.0, mix03, hue004);
45
+ let npr_emission = npr_rgb * NPR_EMIT_STR;
46
+
47
+ // ═══ PRINCIPLED BSDF ═══
48
+ let principled_base = hue_sat_id(1.0, 0.800000011920929, 1.0, tex_rgb);
49
+ let principled = eval_principled(
50
+ PrincipledIn(principled_base, 0.0, CLOTH_SPECULAR, CLOTH_ROUGHNESS, 1e30, 0.0, 0.0),
51
+ n, l, v, sun, amb, shadow
52
+ );
53
+
54
+ // MixShader.001: Shader=自发光.005, Shader_001=原理化BSDF, Fac=0.9
55
+ let final_color = mix(npr_emission, principled, NPR_MIX_SHADER_FAC);
56
+
57
+ var out: FSOut;
58
+ out.color = vec4f(final_color, out_alpha);
59
+ out.mask = 1.0;
60
+ return out;
61
+ }
62
+
63
+ `
@@ -0,0 +1,155 @@
1
+ // Shared WGSL blocks concatenated by every material shader.
2
+ // Splits the boilerplate (uniform structs, bind group layout, skinning VS, PCF shadow)
3
+ // away from the per-material fragment code so each material file only contains what
4
+ // makes it visually distinct.
5
+ //
6
+ // Concat order in every material:
7
+ // NODES_WGSL (nodes.ts — math/noise/BSDF helpers)
8
+ // COMMON_BINDINGS_WGSL (uniform structs + @group/@binding declarations)
9
+ // SAMPLE_SHADOW_WGSL (3×3 PCF shadow sampler; reads bindings above)
10
+ // COMMON_VS_WGSL (skinning vertex shader; reads bindings above)
11
+ // <material's own constants + @fragment fn fs>
12
+ //
13
+ // WGSL is a whole-module compile — declaration order at module scope doesn't matter,
14
+ // but the readable order is: types → bindings → helpers → entry points.
15
+
16
+ // ─── Uniform structs + bind group layout ────────────────────────────
17
+ // Every material pipeline uses the same bind group layout, so the same bindings are
18
+ // declared here once. Groups:
19
+ // group(0): per-frame scene (camera, lights, shadow map, BRDF LUT via nodes.ts)
20
+ // group(1): per-model skinning
21
+ // group(2): per-material (diffuse texture + material uniforms)
22
+
23
+ export const COMMON_BINDINGS_WGSL = /* wgsl */ `
24
+
25
+ struct CameraUniforms {
26
+ view: mat4x4f,
27
+ projection: mat4x4f,
28
+ viewPos: vec3f,
29
+ _padding: f32,
30
+ };
31
+
32
+ struct Light {
33
+ direction: vec4f,
34
+ color: vec4f,
35
+ };
36
+
37
+ struct LightUniforms {
38
+ ambientColor: vec4f,
39
+ lights: array<Light, 4>,
40
+ };
41
+
42
+ // Per-material uniforms. Every material binds this layout even if it ignores fields;
43
+ // the engine keeps one bind group layout across all material pipelines.
44
+ struct MaterialUniforms {
45
+ diffuseColor: vec3f, // tint; reserved (currently unused by all material fs)
46
+ alpha: f32, // 0 → discard; <1 → transparent draw call
47
+ };
48
+
49
+ struct VertexOutput {
50
+ @builtin(position) position: vec4f,
51
+ @location(0) normal: vec3f,
52
+ @location(1) uv: vec2f,
53
+ @location(2) worldPos: vec3f,
54
+ };
55
+
56
+ struct LightVP { viewProj: mat4x4f, };
57
+
58
+ @group(0) @binding(0) var<uniform> camera: CameraUniforms;
59
+ @group(0) @binding(1) var<uniform> light: LightUniforms;
60
+ @group(0) @binding(2) var diffuseSampler: sampler;
61
+ @group(0) @binding(3) var shadowMap: texture_depth_2d;
62
+ @group(0) @binding(4) var shadowSampler: sampler_comparison;
63
+ @group(0) @binding(5) var<uniform> lightVP: LightVP;
64
+ // binding(9) brdfLut is declared inside NODES_WGSL (nodes.ts).
65
+ @group(1) @binding(0) var<storage, read> skinMats: array<mat4x4f>;
66
+ @group(2) @binding(0) var diffuseTexture: texture_2d<f32>;
67
+ @group(2) @binding(1) var<uniform> material: MaterialUniforms;
68
+
69
+ `;
70
+
71
+ // ─── Shadow sampler (3×3 PCF) ───────────────────────────────────────
72
+ // 2048-map, normal-bias 0.08, depth-bias 0.001. Unrolled — Safari's Metal backend
73
+ // doesn't unroll nested shadow loops reliably, and the early out on back-facing
74
+ // fragments saves 9 texture taps per skipped pixel.
75
+
76
+ export const SAMPLE_SHADOW_WGSL = /* wgsl */ `
77
+
78
+ fn sampleShadow(worldPos: vec3f, n: vec3f) -> f32 {
79
+ if (dot(n, -light.lights[0].direction.xyz) <= 0.0) { return 0.0; }
80
+ let biasedPos = worldPos + n * 0.08;
81
+ let lclip = lightVP.viewProj * vec4f(biasedPos, 1.0);
82
+ let ndc = lclip.xyz / max(lclip.w, 1e-6);
83
+ let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
84
+ let cmpZ = ndc.z - 0.001;
85
+ let ts = 1.0 / 2048.0;
86
+ let s00 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(-ts, -ts), cmpZ);
87
+ let s10 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(0.0, -ts), cmpZ);
88
+ let s20 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f( ts, -ts), cmpZ);
89
+ let s01 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(-ts, 0.0), cmpZ);
90
+ let s11 = textureSampleCompareLevel(shadowMap, shadowSampler, suv, cmpZ);
91
+ let s21 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f( ts, 0.0), cmpZ);
92
+ let s02 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(-ts, ts), cmpZ);
93
+ let s12 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(0.0, ts), cmpZ);
94
+ let s22 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f( ts, ts), cmpZ);
95
+ return (s00 + s10 + s20 + s01 + s11 + s21 + s02 + s12 + s22) * (1.0 / 9.0);
96
+ }
97
+
98
+ `;
99
+
100
+ // ─── Skinning vertex shader ─────────────────────────────────────────
101
+ // Four-bone linear blend skinning. Renormalizes weights when they don't sum to 1
102
+ // (PMX models occasionally ship with unnormalized weights on extras like hair tips).
103
+ // VS normalize on the outgoing normal is skipped — interpolation denormalizes it
104
+ // anyway and every fragment shader does `normalize(input.normal)` as its first line.
105
+
106
+ export const COMMON_VS_WGSL = /* wgsl */ `
107
+
108
+ @vertex fn vs(
109
+ @location(0) position: vec3f,
110
+ @location(1) normal: vec3f,
111
+ @location(2) uv: vec2f,
112
+ @location(3) joints0: vec4<u32>,
113
+ @location(4) weights0: vec4<f32>
114
+ ) -> VertexOutput {
115
+ var output: VertexOutput;
116
+ let pos4 = vec4f(position, 1.0);
117
+ let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
118
+ let invWeightSum = select(1.0, 1.0 / weightSum, weightSum > 0.0001);
119
+ let nw = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 * invWeightSum, weightSum > 0.0001);
120
+ var skinnedPos = vec4f(0.0);
121
+ var skinnedNrm = vec3f(0.0);
122
+ for (var i = 0u; i < 4u; i++) {
123
+ let m = skinMats[joints0[i]];
124
+ let w = nw[i];
125
+ skinnedPos += (m * pos4) * w;
126
+ skinnedNrm += (mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz) * normal) * w;
127
+ }
128
+ output.position = camera.projection * camera.view * vec4f(skinnedPos.xyz, 1.0);
129
+ output.normal = skinnedNrm;
130
+ output.uv = uv;
131
+ output.worldPos = skinnedPos.xyz;
132
+ return output;
133
+ }
134
+
135
+ `;
136
+
137
+ // ─── FS output struct ───────────────────────────────────────────────
138
+ // Location 0: final radiance+alpha. Location 1: bloom/highlight mask (single f32,
139
+ // currently 1.0 everywhere — slot is reserved for future per-material bloom control).
140
+
141
+ export const COMMON_FS_OUT_WGSL = /* wgsl */ `
142
+
143
+ struct FSOut {
144
+ @location(0) color: vec4f,
145
+ @location(1) mask: f32,
146
+ };
147
+
148
+ `;
149
+
150
+ // ─── Convenience: full shared prelude ───────────────────────────────
151
+ // Material files compose this as `${NODES_WGSL}${COMMON_MATERIAL_PRELUDE_WGSL}` to
152
+ // pull in everything structural. Each material then adds its own constants + fs().
153
+
154
+ export const COMMON_MATERIAL_PRELUDE_WGSL =
155
+ COMMON_BINDINGS_WGSL + SAMPLE_SHADOW_WGSL + COMMON_VS_WGSL + COMMON_FS_OUT_WGSL