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.
- package/README.md +99 -58
- package/dist/shaders/materials/body.d.ts +1 -1
- package/dist/shaders/materials/body.d.ts.map +1 -1
- package/dist/shaders/materials/body.js +86 -197
- package/dist/shaders/materials/cloth_rough.d.ts +1 -1
- package/dist/shaders/materials/cloth_rough.d.ts.map +1 -1
- package/dist/shaders/materials/cloth_rough.js +10 -121
- package/dist/shaders/materials/cloth_smooth.d.ts +1 -1
- package/dist/shaders/materials/cloth_smooth.d.ts.map +1 -1
- package/dist/shaders/materials/cloth_smooth.js +59 -172
- package/dist/shaders/materials/common.d.ts +6 -0
- package/dist/shaders/materials/common.d.ts.map +1 -0
- package/dist/shaders/materials/common.js +144 -0
- package/dist/shaders/materials/default.d.ts +1 -1
- package/dist/shaders/materials/default.d.ts.map +1 -1
- package/dist/shaders/materials/default.js +12 -145
- package/dist/shaders/materials/eye.d.ts +1 -1
- package/dist/shaders/materials/eye.d.ts.map +1 -1
- package/dist/shaders/materials/eye.js +12 -117
- package/dist/shaders/materials/face.d.ts +1 -1
- package/dist/shaders/materials/face.d.ts.map +1 -1
- package/dist/shaders/materials/face.js +85 -197
- package/dist/shaders/materials/hair.d.ts +1 -1
- package/dist/shaders/materials/hair.d.ts.map +1 -1
- package/dist/shaders/materials/hair.js +78 -183
- package/dist/shaders/materials/metal.d.ts +1 -1
- package/dist/shaders/materials/metal.d.ts.map +1 -1
- package/dist/shaders/materials/metal.js +15 -121
- package/dist/shaders/materials/nodes.d.ts +1 -1
- package/dist/shaders/materials/nodes.d.ts.map +1 -1
- package/dist/shaders/materials/nodes.js +77 -0
- package/dist/shaders/materials/stockings.d.ts +1 -1
- package/dist/shaders/materials/stockings.d.ts.map +1 -1
- package/dist/shaders/materials/stockings.js +26 -152
- package/package.json +1 -1
- package/src/shaders/materials/body.ts +90 -201
- package/src/shaders/materials/cloth_rough.ts +10 -121
- package/src/shaders/materials/cloth_smooth.ts +63 -176
- package/src/shaders/materials/common.ts +155 -0
- package/src/shaders/materials/default.ts +12 -145
- package/src/shaders/materials/eye.ts +12 -117
- package/src/shaders/materials/face.ts +89 -201
- package/src/shaders/materials/hair.ts +82 -187
- package/src/shaders/materials/metal.ts +15 -121
- package/src/shaders/materials/nodes.ts +77 -0
- package/src/shaders/materials/stockings.ts +27 -153
|
@@ -1,76 +1,14 @@
|
|
|
1
|
-
// M_Rough_Cloth — NPR graph identical to M_Smooth_Cloth but
|
|
2
|
-
//
|
|
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
|
-
//
|
|
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
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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 —
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
lights
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
let
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|