reze-engine 0.11.0 → 0.11.1
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 +40 -22
- package/dist/engine.d.ts +13 -6
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +184 -70
- package/dist/shaders/body.d.ts +1 -1
- package/dist/shaders/body.d.ts.map +1 -1
- package/dist/shaders/body.js +44 -21
- package/dist/shaders/cloth_rough.d.ts +1 -1
- package/dist/shaders/cloth_rough.d.ts.map +1 -1
- package/dist/shaders/cloth_rough.js +38 -20
- package/dist/shaders/cloth_smooth.d.ts +1 -1
- package/dist/shaders/cloth_smooth.d.ts.map +1 -1
- package/dist/shaders/cloth_smooth.js +33 -18
- package/dist/shaders/default.d.ts +1 -1
- package/dist/shaders/default.d.ts.map +1 -1
- package/dist/shaders/default.js +29 -12
- package/dist/shaders/dfg_lut.d.ts +2 -3
- package/dist/shaders/dfg_lut.d.ts.map +1 -1
- package/dist/shaders/dfg_lut.js +29 -25
- package/dist/shaders/eye.d.ts +1 -1
- package/dist/shaders/eye.d.ts.map +1 -1
- package/dist/shaders/eye.js +29 -12
- package/dist/shaders/face.d.ts +1 -1
- package/dist/shaders/face.d.ts.map +1 -1
- package/dist/shaders/face.js +47 -23
- package/dist/shaders/hair.d.ts +1 -1
- package/dist/shaders/hair.d.ts.map +1 -1
- package/dist/shaders/hair.js +42 -32
- package/dist/shaders/metal.d.ts +1 -1
- package/dist/shaders/metal.d.ts.map +1 -1
- package/dist/shaders/metal.js +35 -19
- package/dist/shaders/nodes.d.ts +1 -1
- package/dist/shaders/nodes.d.ts.map +1 -1
- package/dist/shaders/nodes.js +79 -37
- package/dist/shaders/stockings.d.ts +1 -1
- package/dist/shaders/stockings.d.ts.map +1 -1
- package/dist/shaders/stockings.js +30 -15
- package/package.json +1 -1
- package/src/engine.ts +200 -78
- package/src/shaders/body.ts +44 -21
- package/src/shaders/cloth_rough.ts +38 -20
- package/src/shaders/cloth_smooth.ts +33 -18
- package/src/shaders/default.ts +29 -12
- package/src/shaders/dfg_lut.ts +31 -27
- package/src/shaders/eye.ts +29 -12
- package/src/shaders/face.ts +47 -23
- package/src/shaders/hair.ts +42 -32
- package/src/shaders/metal.ts +35 -19
- package/src/shaders/nodes.ts +79 -37
- package/src/shaders/stockings.ts +30 -15
package/src/shaders/body.ts
CHANGED
|
@@ -48,19 +48,25 @@ struct LightVP { viewProj: mat4x4f, };
|
|
|
48
48
|
@group(2) @binding(1) var<uniform> material: MaterialUniforms;
|
|
49
49
|
|
|
50
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; }
|
|
51
53
|
let biasedPos = worldPos + n * 0.08;
|
|
52
54
|
let lclip = lightVP.viewProj * vec4f(biasedPos, 1.0);
|
|
53
55
|
let ndc = lclip.xyz / max(lclip.w, 1e-6);
|
|
54
56
|
let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
|
|
55
57
|
let cmpZ = ndc.z - 0.001;
|
|
56
|
-
let ts = 1.0 /
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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);
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
const PI_B: f32 = 3.141592653589793;
|
|
@@ -72,6 +78,8 @@ const BODY_RIM2_POW: f32 = 1.4300000667572021;
|
|
|
72
78
|
const BODY_RIM2_BG: vec3f = vec3f(1.0, 0.4303792119026184, 0.3315804898738861);
|
|
73
79
|
const BODY_WARM_AO_MUL: f32 = 0.30000001192092896;
|
|
74
80
|
const BODY_MIX_NPR: f32 = 0.5;
|
|
81
|
+
// EEVEE Light Clamp equivalent — caps firefly specular from noise-bumped NDF aliasing.
|
|
82
|
+
const BODY_SPEC_CLAMP: f32 = 10.0;
|
|
75
83
|
|
|
76
84
|
fn ggx_d_body(ndoth: f32, a2: f32) -> f32 {
|
|
77
85
|
let denom = ndoth * ndoth * (a2 - 1.0) + 1.0;
|
|
@@ -83,7 +91,9 @@ fn smith_g1_body(ndotx: f32, a2: f32) -> f32 {
|
|
|
83
91
|
}
|
|
84
92
|
|
|
85
93
|
fn fresnel_schlick_body(cosTheta: f32, f0: f32) -> f32 {
|
|
86
|
-
|
|
94
|
+
let m = 1.0 - cosTheta;
|
|
95
|
+
let m2 = m * m;
|
|
96
|
+
return f0 + (1.0 - f0) * (m2 * m2 * m);
|
|
87
97
|
}
|
|
88
98
|
|
|
89
99
|
// smoothstep-based ramp: t*t*(3-2*t) between two color stops
|
|
@@ -114,13 +124,19 @@ fn ramp_ease(f: f32, p0: f32, c0: vec4f, p1: f32, c1: vec4f) -> vec4f {
|
|
|
114
124
|
skinnedNrm += (mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz) * normal) * w;
|
|
115
125
|
}
|
|
116
126
|
output.position = camera.projection * camera.view * vec4f(skinnedPos.xyz, 1.0);
|
|
117
|
-
|
|
127
|
+
// Skip VS normalize — interpolation denormalizes anyway, and FS always does normalize(input.normal).
|
|
128
|
+
output.normal = skinnedNrm;
|
|
118
129
|
output.uv = uv;
|
|
119
130
|
output.worldPos = skinnedPos.xyz;
|
|
120
131
|
return output;
|
|
121
132
|
}
|
|
122
133
|
|
|
123
|
-
|
|
134
|
+
struct FSOut {
|
|
135
|
+
@location(0) color: vec4f,
|
|
136
|
+
@location(1) mask: f32,
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
@fragment fn fs(input: VertexOutput) -> FSOut {
|
|
124
140
|
let alpha = material.alpha;
|
|
125
141
|
if (alpha < 0.001) { discard; }
|
|
126
142
|
|
|
@@ -137,13 +153,13 @@ fn ramp_ease(f: f32, p0: f32, c0: vec4f, p1: f32, c1: vec4f) -> vec4f {
|
|
|
137
153
|
let toon = ramp_constant(ndotl_raw, 0.0, vec4f(0,0,0,1), 0.2966, vec4f(1,1,1,1)).r;
|
|
138
154
|
|
|
139
155
|
// ═══ TOON COLOR: Mix.004 A=HueSat, B=HueSat.001, Fac=ramp.008 (R) ═══
|
|
140
|
-
let shadow_tint =
|
|
141
|
-
let lit_tint =
|
|
156
|
+
let shadow_tint = hue_sat_id(2.0, 0.3499999940395355, 1.0, tex_color);
|
|
157
|
+
let lit_tint = hue_sat_id(1.5, 1.0, 1.0, tex_color);
|
|
142
158
|
let toon_color = mix_blend(toon, shadow_tint, lit_tint);
|
|
143
159
|
let bc = bright_contrast(toon_color, 0.1, 0.2);
|
|
144
160
|
|
|
145
161
|
// ═══ AO CHAIN: AO → ramp CONSTANT [0→white, 0.5995→black] → Mix.003 ═══
|
|
146
|
-
let ao = ao_fake(n, v);
|
|
162
|
+
let ao = 1.0; // ao_fake(n, v) — no SSAO yet; inline 1.0 so the ramp/mix chain folds at compile time.
|
|
147
163
|
let ao_ramp = ramp_constant(ao, 0.0, vec4f(1,1,1,1), 0.5995, vec4f(0,0,0,1)).r;
|
|
148
164
|
let ao_mixed = mix_blend(ao_ramp, bc, vec3f(0.8301780223846436, 0.3345769941806793, 0.27946099638938904));
|
|
149
165
|
|
|
@@ -174,16 +190,16 @@ fn ramp_ease(f: f32, p0: f32, c0: vec4f, p1: f32, c1: vec4f) -> vec4f {
|
|
|
174
190
|
let npr_stack = add0 + warm_emission;
|
|
175
191
|
|
|
176
192
|
// ═══ PRINCIPLED BSDF: noise bump, GGX specular, SSS from AO ═══
|
|
177
|
-
|
|
178
|
-
let noise_val =
|
|
193
|
+
// Mapping loc=rot=0 → plain scale multiply, inline.
|
|
194
|
+
let noise_val = tex_noise_d2(input.worldPos * vec3f(1.0, 1.0, 1.5), 1.0);
|
|
179
195
|
let noise_ramp = ramp_linear(noise_val, 0.0, vec4f(0,0,0,1), 1.0, vec4f(1,1,1,1)).r;
|
|
180
196
|
let bumped_n = bump_lh(0.324644535779953, noise_ramp, n, input.worldPos);
|
|
181
197
|
|
|
182
198
|
let principled_base = mix_blend(noise_ramp, bc, vec3f(0.6831911206245422, 0.19474034011363983, 0.13732507824897766));
|
|
183
199
|
let p_emission = bc * 0.2;
|
|
184
200
|
|
|
185
|
-
|
|
186
|
-
let sss = ramp_linear(
|
|
201
|
+
// Reuse 'ao' (ao_fake(n, v) above) — identical inputs, avoid a second procedural AO pass.
|
|
202
|
+
let sss = ramp_linear(ao, 0.003, vec4f(0,0,0,1), 1.0, vec4f(0.0786, 0.0786, 0.0786, 1.0)).r;
|
|
187
203
|
|
|
188
204
|
let p_ndotl = max(dot(bumped_n, l), 0.0);
|
|
189
205
|
let p_ndotv = max(dot(bumped_n, v), 0.001);
|
|
@@ -194,9 +210,13 @@ fn ramp_ease(f: f32, p0: f32, c0: vec4f, p1: f32, c1: vec4f) -> vec4f {
|
|
|
194
210
|
let D = ggx_d_body(p_ndoth, a2);
|
|
195
211
|
let G = smith_g1_body(p_ndotl, a2) * smith_g1_body(p_ndotv, a2);
|
|
196
212
|
let F = fresnel_schlick_body(p_vdoth, F0_BODY);
|
|
197
|
-
let
|
|
213
|
+
let brdf_lut = brdf_lut_sample(p_ndotv, BODY_ROUGHNESS);
|
|
214
|
+
let spec = (D * G * F) / max(4.0 * p_ndotl * p_ndotv, 0.001) * ltc_brdf_scale_from_lut(brdf_lut);
|
|
198
215
|
let kd = (1.0 - F) * principled_base / PI_B;
|
|
199
|
-
|
|
216
|
+
// Split so we can clamp only the spec firefly contribution (EEVEE Light Clamp).
|
|
217
|
+
let spec_radiance = vec3f(spec) * sun * p_ndotl * shadow;
|
|
218
|
+
let spec_clamped = min(spec_radiance, vec3f(BODY_SPEC_CLAMP));
|
|
219
|
+
let direct = kd * sun * p_ndotl * shadow + spec_clamped;
|
|
200
220
|
// Indirect diffuse = base_color × L_w per Blender closure_eval_surface_lib.glsl line 302;
|
|
201
221
|
// probe_evaluate_world_diff returns radiance (SH-projected, not cosine-convolved).
|
|
202
222
|
let ambient = principled_base * light.ambientColor.xyz;
|
|
@@ -205,7 +225,10 @@ fn ramp_ease(f: f32, p0: f32, c0: vec4f, p1: f32, c1: vec4f) -> vec4f {
|
|
|
205
225
|
// 混合着色器.001: Shader=相加着色器.001, Shader_001=原理化BSDF
|
|
206
226
|
let final_color = mix(npr_stack, principled, BODY_MIX_NPR);
|
|
207
227
|
|
|
208
|
-
|
|
228
|
+
var out: FSOut;
|
|
229
|
+
out.color = vec4f(final_color, alpha);
|
|
230
|
+
out.mask = 1.0;
|
|
231
|
+
return out;
|
|
209
232
|
}
|
|
210
233
|
|
|
211
234
|
`
|
|
@@ -49,19 +49,25 @@ struct LightVP { viewProj: mat4x4f, };
|
|
|
49
49
|
@group(2) @binding(1) var<uniform> material: MaterialUniforms;
|
|
50
50
|
|
|
51
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; }
|
|
52
54
|
let biasedPos = worldPos + n * 0.08;
|
|
53
55
|
let lclip = lightVP.viewProj * vec4f(biasedPos, 1.0);
|
|
54
56
|
let ndc = lclip.xyz / max(lclip.w, 1e-6);
|
|
55
57
|
let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
|
|
56
58
|
let cmpZ = ndc.z - 0.001;
|
|
57
|
-
let ts = 1.0 /
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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);
|
|
65
71
|
}
|
|
66
72
|
|
|
67
73
|
const PI_CR: f32 = 3.141592653589793;
|
|
@@ -73,6 +79,8 @@ const CLOTH_R_EMIT_STR: f32 = 18.200000762939453;
|
|
|
73
79
|
const CLOTH_R_MIX_SHADER_FAC: f32 = 0.8999999761581421;
|
|
74
80
|
const CLOTH_R_NOISE_SCALE: f32 = 17.7;
|
|
75
81
|
const CLOTH_R_BUMP_STR: f32 = 1.0;
|
|
82
|
+
// EEVEE Light Clamp equivalent — caps firefly specular from noise-bumped NDF aliasing.
|
|
83
|
+
const CLOTH_R_SPEC_CLAMP: f32 = 10.0;
|
|
76
84
|
|
|
77
85
|
@vertex fn vs(
|
|
78
86
|
@location(0) position: vec3f,
|
|
@@ -95,13 +103,19 @@ const CLOTH_R_BUMP_STR: f32 = 1.0;
|
|
|
95
103
|
skinnedNrm += (mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz) * normal) * w;
|
|
96
104
|
}
|
|
97
105
|
output.position = camera.projection * camera.view * vec4f(skinnedPos.xyz, 1.0);
|
|
98
|
-
|
|
106
|
+
// Skip VS normalize — interpolation denormalizes anyway, and FS always does normalize(input.normal).
|
|
107
|
+
output.normal = skinnedNrm;
|
|
99
108
|
output.uv = uv;
|
|
100
109
|
output.worldPos = skinnedPos.xyz;
|
|
101
110
|
return output;
|
|
102
111
|
}
|
|
103
112
|
|
|
104
|
-
|
|
113
|
+
struct FSOut {
|
|
114
|
+
@location(0) color: vec4f,
|
|
115
|
+
@location(1) mask: f32,
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
@fragment fn fs(input: VertexOutput) -> FSOut {
|
|
105
119
|
let n = normalize(input.normal);
|
|
106
120
|
let v = normalize(camera.viewPos - input.worldPos);
|
|
107
121
|
let l = -light.lights[0].direction.xyz;
|
|
@@ -121,7 +135,7 @@ const CLOTH_R_BUMP_STR: f32 = 1.0;
|
|
|
121
135
|
let mix04_fac = math_multiply(toon_r, CLOTH_R_MIX04_MUL);
|
|
122
136
|
|
|
123
137
|
// 混合.004: A=色相/饱和度/明度.002(Hue=0.5 Sat=1.0 Val=0.2), B=纹理
|
|
124
|
-
let dark_tex =
|
|
138
|
+
let dark_tex = hue_sat_id(1.0, 0.19999998807907104, 1.0, tex_rgb);
|
|
125
139
|
let mix04 = mix_blend(mix04_fac, dark_tex, tex_rgb);
|
|
126
140
|
|
|
127
141
|
// 倒角.001.Z → 混合.003: A=混合.004, B=色相/饱和度/明度.002
|
|
@@ -129,34 +143,35 @@ const CLOTH_R_BUMP_STR: f32 = 1.0;
|
|
|
129
143
|
let mix03 = mix_blend(bevel_z, mix04, dark_tex);
|
|
130
144
|
|
|
131
145
|
// 环境光遮蔽 → 颜色渐变.001 LINEAR → 混合.001 (white/black) → 混合.002 OVERLAY Fac
|
|
132
|
-
let ao = ao_fake(n, v);
|
|
146
|
+
let ao = 1.0; // ao_fake(n, v) — no SSAO yet; inline 1.0 so the ramp/mix chain folds at compile time.
|
|
133
147
|
let ao_ramp_c = ramp_linear(ao, 0.0, vec4f(1,1,1,1), 0.8808, vec4f(0,0,0,1));
|
|
134
148
|
let mix01_fac = ao_ramp_c.r;
|
|
135
149
|
let mix01_rgb = mix(vec3f(1.0), vec3f(0.0), mix01_fac);
|
|
136
150
|
|
|
137
151
|
// 混合.002 OVERLAY: Fac=混合.001, A=混合.003, B=色相/饱和度/明度.004
|
|
138
|
-
let hue004 =
|
|
152
|
+
let hue004 = hue_sat_id(0.800000011920929, 2.0, 1.0, mix03);
|
|
139
153
|
let overlay_fac = mix01_rgb.r;
|
|
140
154
|
let npr_rgb = mix_overlay(overlay_fac, mix03, hue004);
|
|
141
155
|
let npr_emission = npr_rgb * CLOTH_R_EMIT_STR;
|
|
142
156
|
|
|
143
157
|
// 噪波→渐变→凹凸 (LIVE in M_Rough_Cloth — unlike Smooth Cloth): Strength=1.0, noise Scale=17.7.
|
|
144
|
-
|
|
145
|
-
let noise_val =
|
|
158
|
+
// mapping scale=(1,1,1), loc=rot=0 → identity; use worldPos directly.
|
|
159
|
+
let noise_val = tex_noise_d2(input.worldPos, CLOTH_R_NOISE_SCALE);
|
|
146
160
|
let noise_ramp = ramp_linear(noise_val, 0.0, vec4f(0,0,0,1), 1.0, vec4f(1,1,1,1)).r;
|
|
147
161
|
let bumped_n = bump_lh(CLOTH_R_BUMP_STR, noise_ramp, n, input.worldPos);
|
|
148
162
|
|
|
149
163
|
// 原理化BSDF (EEVEE port): metallic=0, specular=0.8, roughness=0.8187, specular_tint=0.
|
|
150
|
-
let principled_base =
|
|
164
|
+
let principled_base = hue_sat_id(1.0, 0.800000011920929, 1.0, tex_rgb);
|
|
151
165
|
let NL = max(dot(bumped_n, l), 0.0);
|
|
152
166
|
let NV = max(dot(bumped_n, v), 1e-4);
|
|
153
167
|
|
|
154
168
|
let f0 = vec3f(0.08 * CLOTH_R_SPECULAR);
|
|
155
169
|
let f90 = mix(f0, vec3f(1.0), sqrt(CLOTH_R_SPECULAR));
|
|
156
|
-
let
|
|
157
|
-
let reflection_color = F_brdf_multi_scatter(f0, f90,
|
|
170
|
+
let brdf_lut = brdf_lut_sample(NV, CLOTH_R_ROUGHNESS);
|
|
171
|
+
let reflection_color = F_brdf_multi_scatter(f0, f90, brdf_lut.xy);
|
|
158
172
|
|
|
159
|
-
let
|
|
173
|
+
let spec_direct_raw = bsdf_ggx(bumped_n, l, v, NL, NV, CLOTH_R_ROUGHNESS) * sun * shadow * ltc_brdf_scale_from_lut(brdf_lut);
|
|
174
|
+
let spec_direct = min(spec_direct_raw, vec3f(CLOTH_R_SPEC_CLAMP));
|
|
160
175
|
let spec_indirect = amb;
|
|
161
176
|
let spec_radiance = (spec_direct + spec_indirect) * reflection_color;
|
|
162
177
|
|
|
@@ -168,7 +183,10 @@ const CLOTH_R_BUMP_STR: f32 = 1.0;
|
|
|
168
183
|
// 混合着色器.001 Fac=0.9: Shader=自发光.005, Shader_001=原理化BSDF
|
|
169
184
|
let final_color = mix(npr_emission, principled, CLOTH_R_MIX_SHADER_FAC);
|
|
170
185
|
|
|
171
|
-
|
|
186
|
+
var out: FSOut;
|
|
187
|
+
out.color = vec4f(final_color, out_alpha);
|
|
188
|
+
out.mask = 1.0;
|
|
189
|
+
return out;
|
|
172
190
|
}
|
|
173
191
|
|
|
174
192
|
`
|
|
@@ -48,19 +48,25 @@ struct LightVP { viewProj: mat4x4f, };
|
|
|
48
48
|
@group(2) @binding(1) var<uniform> material: MaterialUniforms;
|
|
49
49
|
|
|
50
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; }
|
|
51
53
|
let biasedPos = worldPos + n * 0.08;
|
|
52
54
|
let lclip = lightVP.viewProj * vec4f(biasedPos, 1.0);
|
|
53
55
|
let ndc = lclip.xyz / max(lclip.w, 1e-6);
|
|
54
56
|
let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
|
|
55
57
|
let cmpZ = ndc.z - 0.001;
|
|
56
|
-
let ts = 1.0 /
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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);
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
const PI_C: f32 = 3.141592653589793;
|
|
@@ -92,13 +98,19 @@ const NPR_MIX_SHADER_FAC: f32 = 0.8999999761581421;
|
|
|
92
98
|
skinnedNrm += (mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz) * normal) * w;
|
|
93
99
|
}
|
|
94
100
|
output.position = camera.projection * camera.view * vec4f(skinnedPos.xyz, 1.0);
|
|
95
|
-
|
|
101
|
+
// Skip VS normalize — interpolation denormalizes anyway, and FS always does normalize(input.normal).
|
|
102
|
+
output.normal = skinnedNrm;
|
|
96
103
|
output.uv = uv;
|
|
97
104
|
output.worldPos = skinnedPos.xyz;
|
|
98
105
|
return output;
|
|
99
106
|
}
|
|
100
107
|
|
|
101
|
-
|
|
108
|
+
struct FSOut {
|
|
109
|
+
@location(0) color: vec4f,
|
|
110
|
+
@location(1) mask: f32,
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
@fragment fn fs(input: VertexOutput) -> FSOut {
|
|
102
114
|
let n = normalize(input.normal);
|
|
103
115
|
let v = normalize(camera.viewPos - input.worldPos);
|
|
104
116
|
let l = -light.lights[0].direction.xyz;
|
|
@@ -119,7 +131,7 @@ const NPR_MIX_SHADER_FAC: f32 = 0.8999999761581421;
|
|
|
119
131
|
let mix04_fac = math_multiply(toon_r, CLOTH_MIX04_MUL);
|
|
120
132
|
|
|
121
133
|
// 混合.004: A=色相/饱和度/明度.002, B=纹理
|
|
122
|
-
let dark_tex =
|
|
134
|
+
let dark_tex = hue_sat_id(1.0, 0.19999998807907104, 1.0, tex_rgb);
|
|
123
135
|
let mix04 = mix_blend(mix04_fac, dark_tex, tex_rgb);
|
|
124
136
|
|
|
125
137
|
// 倒角.001→Z → 混合.003 Factor; A=混合.004, B=色相/饱和度/明度.002
|
|
@@ -127,32 +139,32 @@ const NPR_MIX_SHADER_FAC: f32 = 0.8999999761581421;
|
|
|
127
139
|
let mix03 = mix_blend(bevel_z, mix04, dark_tex);
|
|
128
140
|
|
|
129
141
|
// 环境光遮蔽 → 颜色渐变.001 LINEAR → 混合.001 (白/黑) → 混合.002 OVERLAY Fac
|
|
130
|
-
let ao = ao_fake(n, v);
|
|
142
|
+
let ao = 1.0; // ao_fake(n, v) — no SSAO yet; inline 1.0 so the ramp/mix chain folds at compile time.
|
|
131
143
|
let ao_ramp_c = ramp_linear(ao, 0.0, vec4f(1,1,1,1), 0.8808, vec4f(0,0,0,1));
|
|
132
144
|
let mix01_fac = ao_ramp_c.r;
|
|
133
145
|
let mix01_rgb = mix(vec3f(1.0), vec3f(0.0), mix01_fac);
|
|
134
146
|
|
|
135
147
|
// 混合.002 OVERLAY: Fac=混合.001, A=混合.003, B=色相/饱和度/明度.004
|
|
136
|
-
let hue004 =
|
|
148
|
+
let hue004 = hue_sat_id(0.800000011920929, 2.0, 1.0, mix03);
|
|
137
149
|
let overlay_fac = mix01_rgb.r;
|
|
138
150
|
let npr_rgb = mix_overlay(overlay_fac, mix03, hue004);
|
|
139
151
|
let npr_emission = npr_rgb * NPR_EMIT_STR;
|
|
140
152
|
|
|
141
153
|
// 原理化BSDF (EEVEE port): metallic=0, specular=0.8, roughness=0.5, specular_tint=0.
|
|
142
154
|
// Bump subtree is dead in the Blender graph (噪波→凹凸 not linked to Principled.Normal).
|
|
143
|
-
let principled_base =
|
|
155
|
+
let principled_base = hue_sat_id(1.0, 0.800000011920929, 1.0, tex_rgb);
|
|
144
156
|
let NL = max(dot(n, l), 0.0);
|
|
145
157
|
let NV = max(dot(n, v), 1e-4);
|
|
146
158
|
|
|
147
159
|
// f0/f90 per gpu_shader_material_principled.glsl — specular_tint=0 → dielectric_f0_color=white.
|
|
148
160
|
let f0 = vec3f(0.08 * CLOTH_SPECULAR);
|
|
149
161
|
let f90 = mix(f0, vec3f(1.0), sqrt(CLOTH_SPECULAR));
|
|
150
|
-
let
|
|
151
|
-
let reflection_color = F_brdf_multi_scatter(f0, f90,
|
|
162
|
+
let brdf_lut = brdf_lut_sample(NV, CLOTH_ROUGHNESS);
|
|
163
|
+
let reflection_color = F_brdf_multi_scatter(f0, f90, brdf_lut.xy);
|
|
152
164
|
|
|
153
165
|
// Direct glossy — bsdf_ggx already includes NL; no F applied here (tinted after accum).
|
|
154
166
|
// 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 *
|
|
167
|
+
let spec_direct = bsdf_ggx(n, l, v, NL, NV, CLOTH_ROUGHNESS) * sun * shadow * ltc_brdf_scale_from_lut(brdf_lut);
|
|
156
168
|
// Indirect glossy — flat world probe (solid color). Phase 2 adds cubemap.
|
|
157
169
|
let spec_indirect = amb;
|
|
158
170
|
let spec_radiance = (spec_direct + spec_indirect) * reflection_color;
|
|
@@ -167,7 +179,10 @@ const NPR_MIX_SHADER_FAC: f32 = 0.8999999761581421;
|
|
|
167
179
|
// 混合着色器.001: Shader=自发光.005, Shader_001=原理化BSDF, Fac=0.9
|
|
168
180
|
let final_color = mix(npr_emission, principled, NPR_MIX_SHADER_FAC);
|
|
169
181
|
|
|
170
|
-
|
|
182
|
+
var out: FSOut;
|
|
183
|
+
out.color = vec4f(final_color, out_alpha);
|
|
184
|
+
out.mask = 1.0;
|
|
185
|
+
return out;
|
|
171
186
|
}
|
|
172
187
|
|
|
173
188
|
`
|
package/src/shaders/default.ts
CHANGED
|
@@ -64,7 +64,9 @@ fn smith_g1(ndotx: f32, a2: f32) -> f32 {
|
|
|
64
64
|
}
|
|
65
65
|
|
|
66
66
|
fn fresnel_schlick(cosTheta: f32, f0: f32) -> f32 {
|
|
67
|
-
|
|
67
|
+
let m = 1.0 - cosTheta;
|
|
68
|
+
let m2 = m * m;
|
|
69
|
+
return f0 + (1.0 - f0) * (m2 * m2 * m);
|
|
68
70
|
}
|
|
69
71
|
|
|
70
72
|
// ─── Filmic tone mapping (LUT extracted from Blender 3.6 OCIO) ─────
|
|
@@ -91,19 +93,25 @@ fn tonemap(hdr: vec3f) -> vec3f {
|
|
|
91
93
|
// ─── Shadow sampling (3×3 PCF) ──────────────────────────────────────
|
|
92
94
|
|
|
93
95
|
fn sampleShadow(worldPos: vec3f, n: vec3f) -> f32 {
|
|
96
|
+
// Back-facing to key light: direct contribution is zero anyway, skip 9 texture samples.
|
|
97
|
+
if (dot(n, -light.lights[0].direction.xyz) <= 0.0) { return 0.0; }
|
|
94
98
|
let biasedPos = worldPos + n * 0.08;
|
|
95
99
|
let lclip = lightVP.viewProj * vec4f(biasedPos, 1.0);
|
|
96
100
|
let ndc = lclip.xyz / max(lclip.w, 1e-6);
|
|
97
101
|
let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
|
|
98
102
|
let cmpZ = ndc.z - 0.001;
|
|
99
|
-
let ts = 1.0 /
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
103
|
+
let ts = 1.0 / 2048.0;
|
|
104
|
+
// 3x3 PCF unrolled — Safari's Metal backend doesn't unroll nested shadow loops reliably.
|
|
105
|
+
let s00 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(-ts, -ts), cmpZ);
|
|
106
|
+
let s10 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(0.0, -ts), cmpZ);
|
|
107
|
+
let s20 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f( ts, -ts), cmpZ);
|
|
108
|
+
let s01 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(-ts, 0.0), cmpZ);
|
|
109
|
+
let s11 = textureSampleCompareLevel(shadowMap, shadowSampler, suv, cmpZ);
|
|
110
|
+
let s21 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f( ts, 0.0), cmpZ);
|
|
111
|
+
let s02 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(-ts, ts), cmpZ);
|
|
112
|
+
let s12 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f(0.0, ts), cmpZ);
|
|
113
|
+
let s22 = textureSampleCompareLevel(shadowMap, shadowSampler, suv + vec2f( ts, ts), cmpZ);
|
|
114
|
+
return (s00 + s10 + s20 + s01 + s11 + s21 + s02 + s12 + s22) * (1.0 / 9.0);
|
|
107
115
|
}
|
|
108
116
|
|
|
109
117
|
// ─── Vertex / Fragment ──────────────────────────────────────────────
|
|
@@ -129,13 +137,19 @@ fn sampleShadow(worldPos: vec3f, n: vec3f) -> f32 {
|
|
|
129
137
|
skinnedNrm += (mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz) * normal) * w;
|
|
130
138
|
}
|
|
131
139
|
output.position = camera.projection * camera.view * vec4f(skinnedPos.xyz, 1.0);
|
|
132
|
-
|
|
140
|
+
// Skip VS normalize — interpolation denormalizes anyway, and FS always does normalize(input.normal).
|
|
141
|
+
output.normal = skinnedNrm;
|
|
133
142
|
output.uv = uv;
|
|
134
143
|
output.worldPos = skinnedPos.xyz;
|
|
135
144
|
return output;
|
|
136
145
|
}
|
|
137
146
|
|
|
138
|
-
|
|
147
|
+
struct FSOut {
|
|
148
|
+
@location(0) color: vec4f,
|
|
149
|
+
@location(1) mask: f32,
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
@fragment fn fs(input: VertexOutput) -> FSOut {
|
|
139
153
|
let alpha = material.alpha;
|
|
140
154
|
if (alpha < 0.001) { discard; }
|
|
141
155
|
|
|
@@ -163,7 +177,10 @@ fn sampleShadow(worldPos: vec3f, n: vec3f) -> f32 {
|
|
|
163
177
|
let direct = (kd + spec) * sunColor * ndotl * shadow;
|
|
164
178
|
let ambient = albedo * light.ambientColor.xyz;
|
|
165
179
|
|
|
166
|
-
|
|
180
|
+
var out: FSOut;
|
|
181
|
+
out.color = vec4f(ambient + direct, alpha);
|
|
182
|
+
out.mask = 1.0;
|
|
183
|
+
return out;
|
|
167
184
|
}
|
|
168
185
|
|
|
169
186
|
`
|
package/src/shaders/dfg_lut.ts
CHANGED
|
@@ -1,29 +1,37 @@
|
|
|
1
|
-
// One-shot bake pass that
|
|
2
|
-
//
|
|
3
|
-
//
|
|
1
|
+
// One-shot bake pass that produces the combined EEVEE BRDF LUT.
|
|
2
|
+
// Output: 64×64 rgba8unorm — .rg = split-sum DFG (Blender bsdf_lut_frag.glsl,
|
|
3
|
+
// Karis convention: tint = f0·x + f90·y), .ba = Heitz 2016 LTC magnitude
|
|
4
|
+
// (ltc_mag_ggx from eevee_lut.c), sampled from a temp rg16float source texture
|
|
5
|
+
// passed in at bake time.
|
|
4
6
|
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
const
|
|
7
|
+
// Packing both LUTs into one texture lets runtime shaders do a SINGLE texture
|
|
8
|
+
// fetch per fragment to get everything needed for F_brdf_multi_scatter AND
|
|
9
|
+
// ltc_brdf_scale. Was 3 taps (dfg in brdf_lut_baked + dfg+ltc in ltc_brdf_scale);
|
|
10
|
+
// now 1. Big win on Apple GPUs where fragment-stage texture fetches are the
|
|
11
|
+
// dominant cost with MSAA.
|
|
12
|
+
//
|
|
13
|
+
// rgba8unorm (vs rgba16float) is a deliberate precision drop: DFG values live in
|
|
14
|
+
// [0,1], LTC magnitude in [0,1], 1/255 quantization is below the perceptual
|
|
15
|
+
// threshold for direct-spec energy compensation. Halves bandwidth per sample.
|
|
16
|
+
|
|
17
|
+
export const BRDF_LUT_SIZE = 64
|
|
18
|
+
const BAKE_SAMPLE_COUNT = 32
|
|
19
|
+
|
|
20
|
+
export const BRDF_LUT_BAKE_WGSL = /* wgsl */ `
|
|
21
|
+
const LUT_SIZE: f32 = ${BRDF_LUT_SIZE}.0;
|
|
22
|
+
const SAMPLE_COUNT: u32 = ${BAKE_SAMPLE_COUNT}u;
|
|
17
23
|
const M_2PI: f32 = 6.283185307179586;
|
|
18
24
|
|
|
25
|
+
// Temp LTC magnitude source (rg16float, uploaded from eevee_lut.c ltc_mag_ggx).
|
|
26
|
+
// Sampled 1:1 by pixel — bake coord mapping matches runtime sample coord mapping.
|
|
27
|
+
@group(0) @binding(0) var ltcSrc: texture_2d<f32>;
|
|
28
|
+
|
|
19
29
|
@vertex fn vs(@builtin(vertex_index) vid: u32) -> @builtin(position) vec4f {
|
|
20
|
-
// Full-screen triangle covering [-1,1]² in NDC.
|
|
21
30
|
let x = f32((vid << 1u) & 2u) * 2.0 - 1.0;
|
|
22
31
|
let y = f32(vid & 2u) * 2.0 - 1.0;
|
|
23
32
|
return vec4f(x, y, 0.0, 1.0);
|
|
24
33
|
}
|
|
25
34
|
|
|
26
|
-
// common_math_geom_lib.glsl:165 — make_orthonormal_basis.
|
|
27
35
|
fn orthonormal_basis(N: vec3f) -> mat2x3f {
|
|
28
36
|
let up = select(vec3f(1.0, 0.0, 0.0), vec3f(0.0, 0.0, 1.0), abs(N.z) < 0.99999);
|
|
29
37
|
let T = normalize(cross(up, N));
|
|
@@ -31,7 +39,6 @@ fn orthonormal_basis(N: vec3f) -> mat2x3f {
|
|
|
31
39
|
return mat2x3f(T, B);
|
|
32
40
|
}
|
|
33
41
|
|
|
34
|
-
// bsdf_sampling_lib.glsl:27 — Heitz 2018 VNDF sampling in tangent space.
|
|
35
42
|
fn sample_ggx_vndf(rand: vec3f, alpha: f32, Vt: vec3f) -> vec3f {
|
|
36
43
|
let Vh = normalize(vec3f(alpha * Vt.xy, Vt.z));
|
|
37
44
|
let tb = orthonormal_basis(Vh);
|
|
@@ -47,12 +54,10 @@ fn sample_ggx_vndf(rand: vec3f, alpha: f32, Vt: vec3f) -> vec3f {
|
|
|
47
54
|
return normalize(vec3f(alpha * Hh.xy, saturate(Hh.z)));
|
|
48
55
|
}
|
|
49
56
|
|
|
50
|
-
// bsdf_common_lib.glsl:105 — G1 Smith GGX (Brian Karis opti form).
|
|
51
57
|
fn G1_Smith_GGX_opti(NX: f32, a2: f32) -> f32 {
|
|
52
58
|
return NX + sqrt(NX * (NX - NX * a2) + a2);
|
|
53
59
|
}
|
|
54
60
|
|
|
55
|
-
// bsdf_common_lib.glsl:50 — exact dielectric Fresnel (monochromatic).
|
|
56
61
|
fn F_eta(eta: f32, cos_theta: f32) -> f32 {
|
|
57
62
|
let c = abs(cos_theta);
|
|
58
63
|
var g = eta * eta - 1.0 + c * c;
|
|
@@ -62,7 +67,7 @@ fn F_eta(eta: f32, cos_theta: f32) -> f32 {
|
|
|
62
67
|
let B = (c * (g + c) - 1.0) / (c * (g - c) + 1.0);
|
|
63
68
|
return 0.5 * A * A * (1.0 + B * B);
|
|
64
69
|
}
|
|
65
|
-
return 1.0;
|
|
70
|
+
return 1.0;
|
|
66
71
|
}
|
|
67
72
|
|
|
68
73
|
fn f0_from_ior(eta: f32) -> f32 {
|
|
@@ -70,13 +75,12 @@ fn f0_from_ior(eta: f32) -> f32 {
|
|
|
70
75
|
return A * A;
|
|
71
76
|
}
|
|
72
77
|
|
|
73
|
-
// F_color_blend(eta, fresnel, vec3(0)).r — blend factor only.
|
|
74
78
|
fn F_color_blend_zero(eta: f32, fresnel: f32) -> f32 {
|
|
75
79
|
let f0 = f0_from_ior(eta);
|
|
76
80
|
return saturate((fresnel - f0) / (1.0 - f0));
|
|
77
81
|
}
|
|
78
82
|
|
|
79
|
-
@fragment fn fs(@builtin(position) frag: vec4f) -> @location(0)
|
|
83
|
+
@fragment fn fs(@builtin(position) frag: vec4f) -> @location(0) vec4f {
|
|
80
84
|
let y_uv = floor(frag.y) / (LUT_SIZE - 1.0);
|
|
81
85
|
let x_uv = floor(frag.x) / (LUT_SIZE - 1.0);
|
|
82
86
|
|
|
@@ -86,7 +90,6 @@ fn F_color_blend_zero(eta: f32, fresnel: f32) -> f32 {
|
|
|
86
90
|
|
|
87
91
|
let V = vec3f(sqrt(1.0 - NV * NV), 0.0, NV);
|
|
88
92
|
|
|
89
|
-
// principled specular=1.0 — max value, matches bsdf_lut_frag.glsl:41.
|
|
90
93
|
let eta = (2.0 / (1.0 - sqrt(0.08 * 1.0))) - 1.0;
|
|
91
94
|
|
|
92
95
|
var brdf_accum = 0.0;
|
|
@@ -96,7 +99,6 @@ fn F_color_blend_zero(eta: f32, fresnel: f32) -> f32 {
|
|
|
96
99
|
for (var i: u32 = 0u; i < SAMPLE_COUNT; i = i + 1u) {
|
|
97
100
|
let ix = (f32(i) + 0.5) / sc_f;
|
|
98
101
|
let iy = (f32(j) + 0.5) / sc_f;
|
|
99
|
-
// Xi.x = radial, Xi.yz = (cos, sin) of azimuth — bsdf_lut_frag.glsl:22.
|
|
100
102
|
let Xi = vec3f(ix, cos(iy * M_2PI), sin(iy * M_2PI));
|
|
101
103
|
|
|
102
104
|
let H = sample_ggx_vndf(Xi, a, V);
|
|
@@ -106,7 +108,6 @@ fn F_color_blend_zero(eta: f32, fresnel: f32) -> f32 {
|
|
|
106
108
|
let NH = max(H.z, 0.0);
|
|
107
109
|
let VH = max(dot(V, H), 0.0);
|
|
108
110
|
|
|
109
|
-
// G_smith (divided form): 4·NV·NL / (G1_v·G1_l). See bsdf_common_lib.glsl:105.
|
|
110
111
|
let G1v = G1_Smith_GGX_opti(NV, a2);
|
|
111
112
|
let G1l = G1_Smith_GGX_opti(NL, a2);
|
|
112
113
|
let G_smith = 4.0 * NV * NL / (G1v * G1l);
|
|
@@ -122,6 +123,9 @@ fn F_color_blend_zero(eta: f32, fresnel: f32) -> f32 {
|
|
|
122
123
|
}
|
|
123
124
|
}
|
|
124
125
|
let n2 = sc_f * sc_f;
|
|
125
|
-
|
|
126
|
+
let dfg = vec2f(brdf_accum / n2, fresnel_accum / n2);
|
|
127
|
+
// Pack preloaded LTC magnitude at matching (roughness, sqrt(1-NV)) pixel.
|
|
128
|
+
let ltc = textureLoad(ltcSrc, vec2i(i32(frag.x), i32(frag.y)), 0).rg;
|
|
129
|
+
return vec4f(dfg, ltc);
|
|
126
130
|
}
|
|
127
131
|
`
|