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,140 +1,17 @@
|
|
|
1
|
-
// Blender 3.6 Principled BSDF defaults
|
|
2
|
-
// Metallic=0, Specular=0.5 (F0=0.04), Roughness=0.5.
|
|
3
|
-
//
|
|
1
|
+
// Default material — Blender 3.6 Principled BSDF defaults, no NPR stack.
|
|
2
|
+
// Metallic=0, Specular=0.5 (F0=0.04), Roughness=0.5. Serves as the EEVEE reference
|
|
3
|
+
// path that every NPR material mixes against in its final stage.
|
|
4
4
|
|
|
5
5
|
import { NODES_WGSL } from "./nodes"
|
|
6
|
+
import { COMMON_MATERIAL_PRELUDE_WGSL } from "./common"
|
|
6
7
|
|
|
7
8
|
export const DEFAULT_SHADER_WGSL = /* wgsl */ `
|
|
8
9
|
|
|
9
10
|
${NODES_WGSL}
|
|
11
|
+
${COMMON_MATERIAL_PRELUDE_WGSL}
|
|
10
12
|
|
|
11
|
-
const PI: f32 = 3.141592653589793;
|
|
12
13
|
const DEFAULT_SPECULAR: f32 = 0.5;
|
|
13
|
-
const
|
|
14
|
-
|
|
15
|
-
struct CameraUniforms {
|
|
16
|
-
view: mat4x4f,
|
|
17
|
-
projection: mat4x4f,
|
|
18
|
-
viewPos: vec3f,
|
|
19
|
-
_padding: f32,
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
struct Light {
|
|
23
|
-
direction: vec4f,
|
|
24
|
-
color: vec4f,
|
|
25
|
-
};
|
|
26
|
-
|
|
27
|
-
struct LightUniforms {
|
|
28
|
-
ambientColor: vec4f,
|
|
29
|
-
lights: array<Light, 4>,
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
// Per-material uniforms. Add fields here only when a shader actually reads them;
|
|
33
|
-
// preset-specific shaders (face.ts, future hair.ts) share this struct so the
|
|
34
|
-
// engine can use one material bind-group layout.
|
|
35
|
-
struct MaterialUniforms {
|
|
36
|
-
diffuseColor: vec3f, // tint; multiplies sampled albedo (unused by current fs, reserved)
|
|
37
|
-
alpha: f32, // 0 → discard; <1 → transparent draw call
|
|
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
|
-
// ─── Filmic tone mapping (LUT extracted from Blender 3.6 OCIO) ─────
|
|
60
|
-
// View transform = Filmic, Look = Medium High Contrast, Exposure = -0.3.
|
|
61
|
-
// 14 samples at integer log2 stops from -10 to +3 (inclusive).
|
|
62
|
-
// Extracted via scripts/extract_filmic_lut.py → probe image through scene
|
|
63
|
-
// color management. Input: linear scene-referred. Output: sRGB display.
|
|
64
|
-
|
|
65
|
-
fn filmic(x: f32) -> f32 {
|
|
66
|
-
var lut = array<f32, 14>(
|
|
67
|
-
0.0067, 0.0141, 0.0272, 0.0499, 0.0885, 0.1512, 0.2462,
|
|
68
|
-
0.3753, 0.5273, 0.6776, 0.8031, 0.8929, 0.9495, 0.9814
|
|
69
|
-
);
|
|
70
|
-
let t = clamp(log2(max(x, 1e-10)) + 10.0, 0.0, 13.0);
|
|
71
|
-
let i = u32(t);
|
|
72
|
-
let j = min(i + 1u, 13u);
|
|
73
|
-
return mix(lut[i], lut[j], t - f32(i));
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
fn tonemap(hdr: vec3f) -> vec3f {
|
|
77
|
-
return vec3f(filmic(hdr.x), filmic(hdr.y), filmic(hdr.z));
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// ─── Shadow sampling (3×3 PCF) ──────────────────────────────────────
|
|
81
|
-
|
|
82
|
-
fn sampleShadow(worldPos: vec3f, n: vec3f) -> f32 {
|
|
83
|
-
// Back-facing to key light: direct contribution is zero anyway, skip 9 texture samples.
|
|
84
|
-
if (dot(n, -light.lights[0].direction.xyz) <= 0.0) { return 0.0; }
|
|
85
|
-
let biasedPos = worldPos + n * 0.08;
|
|
86
|
-
let lclip = lightVP.viewProj * vec4f(biasedPos, 1.0);
|
|
87
|
-
let ndc = lclip.xyz / max(lclip.w, 1e-6);
|
|
88
|
-
let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
|
|
89
|
-
let cmpZ = ndc.z - 0.001;
|
|
90
|
-
let ts = 1.0 / 2048.0;
|
|
91
|
-
// 3x3 PCF unrolled — Safari's Metal backend doesn't unroll nested shadow loops reliably.
|
|
92
|
-
let s00 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(-ts, -ts), cmpZ);
|
|
93
|
-
let s10 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(0.0, -ts), cmpZ);
|
|
94
|
-
let s20 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f( ts, -ts), cmpZ);
|
|
95
|
-
let s01 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(-ts, 0.0), cmpZ);
|
|
96
|
-
let s11 = textureSampleCompareLevel(shadowMap, shadowSampler, suv, cmpZ);
|
|
97
|
-
let s21 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f( ts, 0.0), cmpZ);
|
|
98
|
-
let s02 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(-ts, ts), cmpZ);
|
|
99
|
-
let s12 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(0.0, ts), cmpZ);
|
|
100
|
-
let s22 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f( ts, ts), cmpZ);
|
|
101
|
-
return (s00 + s10 + s20 + s01 + s11 + s21 + s02 + s12 + s22) * (1.0 / 9.0);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
// ─── Vertex / Fragment ──────────────────────────────────────────────
|
|
105
|
-
|
|
106
|
-
@vertex fn vs(
|
|
107
|
-
@location(0) position: vec3f,
|
|
108
|
-
@location(1) normal: vec3f,
|
|
109
|
-
@location(2) uv: vec2f,
|
|
110
|
-
@location(3) joints0: vec4<u32>,
|
|
111
|
-
@location(4) weights0: vec4<f32>
|
|
112
|
-
) -> VertexOutput {
|
|
113
|
-
var output: VertexOutput;
|
|
114
|
-
let pos4 = vec4f(position, 1.0);
|
|
115
|
-
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
116
|
-
let invWeightSum = select(1.0, 1.0 / weightSum, weightSum > 0.0001);
|
|
117
|
-
let nw = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 * invWeightSum, weightSum > 0.0001);
|
|
118
|
-
var skinnedPos = vec4f(0.0);
|
|
119
|
-
var skinnedNrm = vec3f(0.0);
|
|
120
|
-
for (var i = 0u; i < 4u; i++) {
|
|
121
|
-
let m = skinMats[joints0[i]];
|
|
122
|
-
let w = nw[i];
|
|
123
|
-
skinnedPos += (m * pos4) * w;
|
|
124
|
-
skinnedNrm += (mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz) * normal) * w;
|
|
125
|
-
}
|
|
126
|
-
output.position = camera.projection * camera.view * vec4f(skinnedPos.xyz, 1.0);
|
|
127
|
-
// Skip VS normalize — interpolation denormalizes anyway, and FS always does normalize(input.normal).
|
|
128
|
-
output.normal = skinnedNrm;
|
|
129
|
-
output.uv = uv;
|
|
130
|
-
output.worldPos = skinnedPos.xyz;
|
|
131
|
-
return output;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
struct FSOut {
|
|
135
|
-
@location(0) color: vec4f,
|
|
136
|
-
@location(1) mask: f32,
|
|
137
|
-
};
|
|
14
|
+
const DEFAULT_ROUGHNESS: f32 = 0.5;
|
|
138
15
|
|
|
139
16
|
@fragment fn fs(input: VertexOutput) -> FSOut {
|
|
140
17
|
let alpha = material.alpha;
|
|
@@ -142,30 +19,20 @@ struct FSOut {
|
|
|
142
19
|
|
|
143
20
|
let n = normalize(input.normal);
|
|
144
21
|
let v = normalize(camera.viewPos - input.worldPos);
|
|
145
|
-
let albedo = textureSample(diffuseTexture, diffuseSampler, input.uv).rgb;
|
|
146
|
-
|
|
147
22
|
let l = -light.lights[0].direction.xyz;
|
|
148
23
|
let sun = light.lights[0].color.xyz * light.lights[0].color.w;
|
|
149
24
|
let amb = light.ambientColor.xyz;
|
|
150
25
|
let shadow = sampleShadow(input.worldPos, n);
|
|
151
26
|
|
|
152
|
-
|
|
153
|
-
let NL = max(dot(n, l), 0.0);
|
|
154
|
-
let NV = max(dot(n, v), 1e-4);
|
|
155
|
-
|
|
156
|
-
let f0 = vec3f(0.08 * DEFAULT_SPECULAR);
|
|
157
|
-
let f90 = mix(f0, vec3f(1.0), sqrt(DEFAULT_SPECULAR));
|
|
158
|
-
let brdf_lut = brdf_lut_sample(NV, ROUGHNESS);
|
|
159
|
-
let reflection_color = F_brdf_multi_scatter(f0, f90, brdf_lut.xy);
|
|
160
|
-
|
|
161
|
-
let spec_direct = bsdf_ggx(n, l, v, NL, NV, ROUGHNESS) * sun * shadow * ltc_brdf_scale_from_lut(brdf_lut);
|
|
162
|
-
let spec_indirect = amb;
|
|
163
|
-
let spec_radiance = (spec_direct + spec_indirect) * reflection_color;
|
|
27
|
+
let albedo = textureSample(diffuseTexture, diffuseSampler, input.uv).rgb;
|
|
164
28
|
|
|
165
|
-
let
|
|
29
|
+
let color = eval_principled(
|
|
30
|
+
PrincipledIn(albedo, 0.0, DEFAULT_SPECULAR, DEFAULT_ROUGHNESS, 1e30, 0.0, 0.0),
|
|
31
|
+
n, l, v, sun, amb, shadow
|
|
32
|
+
);
|
|
166
33
|
|
|
167
34
|
var out: FSOut;
|
|
168
|
-
out.color = vec4f(
|
|
35
|
+
out.color = vec4f(color, alpha);
|
|
169
36
|
out.mask = 1.0;
|
|
170
37
|
return out;
|
|
171
38
|
}
|
|
@@ -1,146 +1,41 @@
|
|
|
1
|
-
// Eye preset — default Principled BSDF
|
|
2
|
-
// Matches the published preset's instruction: "keep eyes in the default
|
|
3
|
-
//
|
|
1
|
+
// Eye preset — default Principled BSDF + Emission socket set to albedo × 1.5.
|
|
2
|
+
// Matches the published preset author's instruction: "keep eyes in the default
|
|
3
|
+
// nodegraph, add emission 1.5". Emission feeds bloom pre-tonemap.
|
|
4
4
|
|
|
5
5
|
import { NODES_WGSL } from "./nodes"
|
|
6
|
+
import { COMMON_MATERIAL_PRELUDE_WGSL } from "./common"
|
|
6
7
|
|
|
7
8
|
export const EYE_SHADER_WGSL = /* wgsl */ `
|
|
8
9
|
|
|
9
10
|
${NODES_WGSL}
|
|
11
|
+
${COMMON_MATERIAL_PRELUDE_WGSL}
|
|
10
12
|
|
|
11
|
-
const PI_E: f32 = 3.141592653589793;
|
|
12
13
|
const EYE_SPECULAR: f32 = 0.5;
|
|
13
14
|
const EYE_ROUGHNESS: f32 = 0.5;
|
|
14
15
|
const EYE_EMISSION_STRENGTH: f32 = 1.5;
|
|
15
16
|
|
|
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
|
-
@vertex fn vs(
|
|
80
|
-
@location(0) position: vec3f,
|
|
81
|
-
@location(1) normal: vec3f,
|
|
82
|
-
@location(2) uv: vec2f,
|
|
83
|
-
@location(3) joints0: vec4<u32>,
|
|
84
|
-
@location(4) weights0: vec4<f32>
|
|
85
|
-
) -> VertexOutput {
|
|
86
|
-
var output: VertexOutput;
|
|
87
|
-
let pos4 = vec4f(position, 1.0);
|
|
88
|
-
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
89
|
-
let invWeightSum = select(1.0, 1.0 / weightSum, weightSum > 0.0001);
|
|
90
|
-
let nw = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 * invWeightSum, weightSum > 0.0001);
|
|
91
|
-
var skinnedPos = vec4f(0.0);
|
|
92
|
-
var skinnedNrm = vec3f(0.0);
|
|
93
|
-
for (var i = 0u; i < 4u; i++) {
|
|
94
|
-
let m = skinMats[joints0[i]];
|
|
95
|
-
let w = nw[i];
|
|
96
|
-
skinnedPos += (m * pos4) * w;
|
|
97
|
-
skinnedNrm += (mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz) * normal) * w;
|
|
98
|
-
}
|
|
99
|
-
output.position = camera.projection * camera.view * vec4f(skinnedPos.xyz, 1.0);
|
|
100
|
-
// Skip VS normalize — interpolation denormalizes anyway, and FS always does normalize(input.normal).
|
|
101
|
-
output.normal = skinnedNrm;
|
|
102
|
-
output.uv = uv;
|
|
103
|
-
output.worldPos = skinnedPos.xyz;
|
|
104
|
-
return output;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
struct FSOut {
|
|
108
|
-
@location(0) color: vec4f,
|
|
109
|
-
@location(1) mask: f32,
|
|
110
|
-
};
|
|
111
|
-
|
|
112
17
|
@fragment fn fs(input: VertexOutput) -> FSOut {
|
|
113
18
|
let alpha = material.alpha;
|
|
114
19
|
if (alpha < 0.001) { discard; }
|
|
115
20
|
|
|
116
21
|
let n = normalize(input.normal);
|
|
117
22
|
let v = normalize(camera.viewPos - input.worldPos);
|
|
118
|
-
let albedo = textureSample(diffuseTexture, diffuseSampler, input.uv).rgb;
|
|
119
|
-
|
|
120
23
|
let l = -light.lights[0].direction.xyz;
|
|
121
24
|
let sun = light.lights[0].color.xyz * light.lights[0].color.w;
|
|
122
25
|
let amb = light.ambientColor.xyz;
|
|
123
26
|
let shadow = sampleShadow(input.worldPos, n);
|
|
124
27
|
|
|
125
|
-
|
|
126
|
-
let NL = max(dot(n, l), 0.0);
|
|
127
|
-
let NV = max(dot(n, v), 1e-4);
|
|
128
|
-
|
|
129
|
-
let f0 = vec3f(0.08 * EYE_SPECULAR);
|
|
130
|
-
let f90 = mix(f0, vec3f(1.0), sqrt(EYE_SPECULAR));
|
|
131
|
-
let brdf_lut = brdf_lut_sample(NV, EYE_ROUGHNESS);
|
|
132
|
-
let reflection_color = F_brdf_multi_scatter(f0, f90, brdf_lut.xy);
|
|
133
|
-
|
|
134
|
-
let spec_direct = bsdf_ggx(n, l, v, NL, NV, EYE_ROUGHNESS) * sun * shadow * ltc_brdf_scale_from_lut(brdf_lut);
|
|
135
|
-
let spec_indirect = amb;
|
|
136
|
-
let spec_radiance = (spec_direct + spec_indirect) * reflection_color;
|
|
28
|
+
let albedo = textureSample(diffuseTexture, diffuseSampler, input.uv).rgb;
|
|
137
29
|
|
|
138
|
-
let
|
|
139
|
-
|
|
30
|
+
let shaded = eval_principled(
|
|
31
|
+
PrincipledIn(albedo, 0.0, EYE_SPECULAR, EYE_ROUGHNESS, 1e30, 0.0, 0.0),
|
|
32
|
+
n, l, v, sun, amb, shadow
|
|
33
|
+
);
|
|
34
|
+
// Principled Emission socket: emissive = emission_color × strength, added on top.
|
|
140
35
|
let emission = albedo * EYE_EMISSION_STRENGTH;
|
|
141
36
|
|
|
142
37
|
var out: FSOut;
|
|
143
|
-
out.color = vec4f(
|
|
38
|
+
out.color = vec4f(shaded + emission, alpha);
|
|
144
39
|
out.mask = 1.0;
|
|
145
40
|
return out;
|
|
146
41
|
}
|
|
@@ -1,201 +1,89 @@
|
|
|
1
|
-
// M_Face —
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
let
|
|
57
|
-
let
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
//
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
let
|
|
64
|
-
let
|
|
65
|
-
|
|
66
|
-
let
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
let
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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 intensity = light.lights[0].color.w;
|
|
127
|
-
let sun = light.lights[0].color.xyz * intensity;
|
|
128
|
-
|
|
129
|
-
let tex_color = textureSample(diffuseTexture, diffuseSampler, input.uv).rgb;
|
|
130
|
-
let shadow = sampleShadow(input.worldPos, n);
|
|
131
|
-
|
|
132
|
-
let ndotl_raw = shader_to_rgb_diffuse(n, l, sun, light.ambientColor.xyz, shadow);
|
|
133
|
-
// ramp_constant_edge_aa: avoids binary fac shimmer on terminator (fwidth + smoothstep).
|
|
134
|
-
let toon = ramp_constant_edge_aa(ndotl_raw, 0.2966, vec4f(0,0,0,1), vec4f(1,1,1,1)).r;
|
|
135
|
-
|
|
136
|
-
let shadow_tint = hue_sat(0.46000000834465027, 2.0, 0.3499999940395355, 1.0, tex_color);
|
|
137
|
-
let lit_tint = hue_sat(0.46000000834465027, 1.600000023841858, 1.5, 1.0, tex_color);
|
|
138
|
-
let toon_color = mix_blend(toon, shadow_tint, lit_tint);
|
|
139
|
-
let bc = bright_contrast(toon_color, 0.1, 0.2);
|
|
140
|
-
|
|
141
|
-
let emission3 = bc * 2.5;
|
|
142
|
-
|
|
143
|
-
let warm_input = clamp(toon * 0.5 + 0.5, 0.0, 1.0);
|
|
144
|
-
let warm_color = ramp_cardinal(warm_input, 0.2409,
|
|
145
|
-
vec4f(0.2426, 0.068, 0.0588, 1.0), 0.4663,
|
|
146
|
-
vec4f(0.6677, 0.5024, 0.5126, 1.0)).rgb;
|
|
147
|
-
let warm_emission = warm_color * FACE_WARM_STR;
|
|
148
|
-
|
|
149
|
-
let rim1_str = fresnel(2.0, n, v) * layer_weight_facing(0.24, n, v);
|
|
150
|
-
let rim1 = vec3f(0.984157919883728, 0.6110184788703918, 0.5736401677131653) * rim1_str;
|
|
151
|
-
|
|
152
|
-
let rim2_raw = fresnel(1.45, n, v) * layer_weight_fresnel(0.61, n, v);
|
|
153
|
-
let rim2_fac = math_power(rim2_raw, FACE_RIM2_POW);
|
|
154
|
-
let rim2_mixed = mix(emission3, FACE_RIM2_BG, rim2_fac);
|
|
155
|
-
|
|
156
|
-
// Blender implicitly converts Color → Float via BT.601 grayscale when plugging a color
|
|
157
|
-
// output into a Math node's Value input. An earlier trace used tex_color.r, which fires
|
|
158
|
-
// aggressively on R-dominant skin — single near-white R pixels produced firefly speckles.
|
|
159
|
-
// color_to_value matches the Blender socket semantic and only fires on genuinely near-
|
|
160
|
-
// white painted features (the author's intent).
|
|
161
|
-
let tex_gate = math_greater_than(color_to_value(tex_color), FACE_BRIGHT_TEX_THRESH);
|
|
162
|
-
let bright_emit = vec3f(tex_gate) * 3.0;
|
|
163
|
-
|
|
164
|
-
let npr_stack = rim1 + rim2_mixed + bright_emit + warm_emission;
|
|
165
|
-
|
|
166
|
-
// Noise bump — Mapping loc=rot=0 folds to a plain scale multiply.
|
|
167
|
-
let noise_val = tex_noise_d2(input.worldPos * vec3f(1.0, 1.0, 1.5), 1.0);
|
|
168
|
-
let noise_ramp = ramp_linear(noise_val, 0.0, vec4f(0,0,0,1), 1.0, vec4f(1,1,1,1)).r;
|
|
169
|
-
let bumped_n = bump_lh(0.324644535779953, noise_ramp, n, input.worldPos);
|
|
170
|
-
|
|
171
|
-
let principled_base = mix_blend(noise_ramp, bc, vec3f(0.6832, 0.1947, 0.1373));
|
|
172
|
-
let p_emission = bc * 0.2;
|
|
173
|
-
|
|
174
|
-
// Principled BSDF (EEVEE port): metallic=0, specular=0.5, roughness=0.3, specular_tint=0.
|
|
175
|
-
let NL = max(dot(bumped_n, l), 0.0);
|
|
176
|
-
let NV = max(dot(bumped_n, v), 1e-4);
|
|
177
|
-
|
|
178
|
-
let f0 = vec3f(0.08 * FACE_SPECULAR);
|
|
179
|
-
let f90 = mix(f0, vec3f(1.0), sqrt(FACE_SPECULAR));
|
|
180
|
-
let brdf_lut = brdf_lut_sample(NV, FACE_ROUGHNESS);
|
|
181
|
-
let reflection_color = F_brdf_multi_scatter(f0, f90, brdf_lut.xy);
|
|
182
|
-
|
|
183
|
-
let spec_direct_raw = bsdf_ggx(bumped_n, l, v, NL, NV, FACE_ROUGHNESS) * sun * shadow * ltc_brdf_scale_from_lut(brdf_lut);
|
|
184
|
-
let spec_direct = min(spec_direct_raw, vec3f(FACE_SPEC_CLAMP));
|
|
185
|
-
let spec_indirect = light.ambientColor.xyz;
|
|
186
|
-
let spec_radiance = (spec_direct + spec_indirect) * reflection_color;
|
|
187
|
-
|
|
188
|
-
// Indirect diffuse = base_color × L_w per Blender closure_eval_surface_lib.glsl line 302;
|
|
189
|
-
// probe_evaluate_world_diff returns radiance (SH-projected, not cosine-convolved).
|
|
190
|
-
let diffuse_radiance = principled_base * (sun * NL * shadow / PI_F + light.ambientColor.xyz);
|
|
191
|
-
let principled = diffuse_radiance + spec_radiance + p_emission;
|
|
192
|
-
|
|
193
|
-
let final_color = mix(npr_stack, principled, FACE_MIX_NPR);
|
|
194
|
-
|
|
195
|
-
var out: FSOut;
|
|
196
|
-
out.color = vec4f(final_color, alpha);
|
|
197
|
-
out.mask = 1.0;
|
|
198
|
-
return out;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
`
|
|
1
|
+
// M_Face — 仿深空之眼渲染预设v1.0_by_小绿毛猫 "M_Face". Toon + warm rim + dual fresnel
|
|
2
|
+
// rim + BT.601 bright-tex gate, mixed 50/50 against a Principled BSDF with noise bump.
|
|
3
|
+
|
|
4
|
+
import { NODES_WGSL } from "./nodes"
|
|
5
|
+
import { COMMON_MATERIAL_PRELUDE_WGSL } from "./common"
|
|
6
|
+
|
|
7
|
+
export const FACE_SHADER_WGSL = /* wgsl */ `
|
|
8
|
+
|
|
9
|
+
${NODES_WGSL}
|
|
10
|
+
${COMMON_MATERIAL_PRELUDE_WGSL}
|
|
11
|
+
|
|
12
|
+
const FACE_SPECULAR: f32 = 0.5;
|
|
13
|
+
const FACE_ROUGHNESS: f32 = 0.3;
|
|
14
|
+
const FACE_MIX_NPR: f32 = 0.5;
|
|
15
|
+
const FACE_SPEC_CLAMP: f32 = 10.0;
|
|
16
|
+
const FACE_RIM2_POW: f32 = 0.6300000548362732;
|
|
17
|
+
const FACE_RIM2_BG: vec3f = vec3f(1.0, 0.4684903025627136, 0.3698573112487793);
|
|
18
|
+
const FACE_WARM_STR: f32 = 0.30000001192092896;
|
|
19
|
+
const FACE_BRIGHT_TEX_THRESH: f32 = 0.9300000071525574;
|
|
20
|
+
|
|
21
|
+
@fragment fn fs(input: VertexOutput) -> FSOut {
|
|
22
|
+
let alpha = material.alpha;
|
|
23
|
+
if (alpha < 0.001) { discard; }
|
|
24
|
+
|
|
25
|
+
let n = normalize(input.normal);
|
|
26
|
+
let v = normalize(camera.viewPos - input.worldPos);
|
|
27
|
+
let l = -light.lights[0].direction.xyz;
|
|
28
|
+
let sun = light.lights[0].color.xyz * light.lights[0].color.w;
|
|
29
|
+
let amb = light.ambientColor.xyz;
|
|
30
|
+
let shadow = sampleShadow(input.worldPos, n);
|
|
31
|
+
|
|
32
|
+
let tex_color = textureSample(diffuseTexture, diffuseSampler, input.uv).rgb;
|
|
33
|
+
|
|
34
|
+
// ═══ NPR STACK ═══
|
|
35
|
+
let ndotl_raw = shader_to_rgb_diffuse(n, l, sun, amb, shadow);
|
|
36
|
+
// ramp_constant_edge_aa: avoids binary fac shimmer on terminator (fwidth + smoothstep).
|
|
37
|
+
let toon = ramp_constant_edge_aa(ndotl_raw, 0.2966, vec4f(0,0,0,1), vec4f(1,1,1,1)).r;
|
|
38
|
+
|
|
39
|
+
let shadow_tint = hue_sat(0.46000000834465027, 2.0, 0.3499999940395355, 1.0, tex_color);
|
|
40
|
+
let lit_tint = hue_sat(0.46000000834465027, 1.600000023841858, 1.5, 1.0, tex_color);
|
|
41
|
+
let toon_color = mix_blend(toon, shadow_tint, lit_tint);
|
|
42
|
+
let bc = bright_contrast(toon_color, 0.1, 0.2);
|
|
43
|
+
|
|
44
|
+
let emission3 = bc * 2.5;
|
|
45
|
+
|
|
46
|
+
let warm_input = clamp(toon * 0.5 + 0.5, 0.0, 1.0);
|
|
47
|
+
let warm_color = ramp_cardinal(warm_input, 0.2409,
|
|
48
|
+
vec4f(0.2426, 0.068, 0.0588, 1.0), 0.4663,
|
|
49
|
+
vec4f(0.6677, 0.5024, 0.5126, 1.0)).rgb;
|
|
50
|
+
let warm_emission = warm_color * FACE_WARM_STR;
|
|
51
|
+
|
|
52
|
+
let rim1_str = fresnel(2.0, n, v) * layer_weight_facing(0.24, n, v);
|
|
53
|
+
let rim1 = vec3f(0.984157919883728, 0.6110184788703918, 0.5736401677131653) * rim1_str;
|
|
54
|
+
|
|
55
|
+
let rim2_raw = fresnel(1.45, n, v) * layer_weight_fresnel(0.61, n, v);
|
|
56
|
+
let rim2_fac = math_power(rim2_raw, FACE_RIM2_POW);
|
|
57
|
+
let rim2_mixed = mix(emission3, FACE_RIM2_BG, rim2_fac);
|
|
58
|
+
|
|
59
|
+
// Blender implicitly converts Color → Float via BT.601 grayscale when a color output
|
|
60
|
+
// feeds a Math node's Value input. Using tex_color.r instead fires on R-dominant skin
|
|
61
|
+
// and produces firefly speckles on near-white R pixels — color_to_value matches the
|
|
62
|
+
// Blender socket semantic and only gates genuinely near-white painted features.
|
|
63
|
+
let tex_gate = math_greater_than(color_to_value(tex_color), FACE_BRIGHT_TEX_THRESH);
|
|
64
|
+
let bright_emit = vec3f(tex_gate) * 3.0;
|
|
65
|
+
|
|
66
|
+
let npr_stack = rim1 + rim2_mixed + bright_emit + warm_emission;
|
|
67
|
+
|
|
68
|
+
// ═══ PRINCIPLED BSDF with noise bump ═══
|
|
69
|
+
let noise_val = tex_noise_d2(input.worldPos * vec3f(1.0, 1.0, 1.5), 1.0);
|
|
70
|
+
let noise_ramp = ramp_linear(noise_val, 0.0, vec4f(0,0,0,1), 1.0, vec4f(1,1,1,1)).r;
|
|
71
|
+
let bumped_n = bump_lh(0.324644535779953, noise_ramp, n, input.worldPos);
|
|
72
|
+
|
|
73
|
+
let principled_base = mix_blend(noise_ramp, bc, vec3f(0.6832, 0.1947, 0.1373));
|
|
74
|
+
let p_emission = bc * 0.2;
|
|
75
|
+
|
|
76
|
+
let principled = eval_principled(
|
|
77
|
+
PrincipledIn(principled_base, 0.0, FACE_SPECULAR, FACE_ROUGHNESS, FACE_SPEC_CLAMP, 0.0, 0.0),
|
|
78
|
+
bumped_n, l, v, sun, amb, shadow
|
|
79
|
+
) + p_emission;
|
|
80
|
+
|
|
81
|
+
let final_color = mix(npr_stack, principled, FACE_MIX_NPR);
|
|
82
|
+
|
|
83
|
+
var out: FSOut;
|
|
84
|
+
out.color = vec4f(final_color, alpha);
|
|
85
|
+
out.mask = 1.0;
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
`
|