reze-engine 0.10.2 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -13
- package/dist/engine.d.ts +170 -34
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +1080 -308
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/shaders/body.d.ts +2 -0
- package/dist/shaders/body.d.ts.map +1 -0
- package/dist/shaders/body.js +209 -0
- package/dist/shaders/classify.d.ts +4 -0
- package/dist/shaders/classify.d.ts.map +1 -0
- package/dist/shaders/classify.js +12 -0
- package/dist/shaders/cloth_rough.d.ts +2 -0
- package/dist/shaders/cloth_rough.d.ts.map +1 -0
- package/dist/shaders/cloth_rough.js +172 -0
- package/dist/shaders/cloth_smooth.d.ts +2 -0
- package/dist/shaders/cloth_smooth.d.ts.map +1 -0
- package/dist/shaders/cloth_smooth.js +171 -0
- package/dist/shaders/default.d.ts +2 -0
- package/dist/shaders/default.d.ts.map +1 -0
- package/dist/shaders/default.js +168 -0
- package/dist/shaders/dfg_lut.d.ts +4 -0
- package/dist/shaders/dfg_lut.d.ts.map +1 -0
- package/dist/shaders/dfg_lut.js +125 -0
- package/dist/shaders/eye.d.ts +2 -0
- package/dist/shaders/eye.d.ts.map +1 -0
- package/dist/shaders/eye.js +142 -0
- package/dist/shaders/face.d.ts +2 -0
- package/dist/shaders/face.d.ts.map +1 -0
- package/dist/shaders/face.js +211 -0
- package/dist/shaders/hair.d.ts +2 -0
- package/dist/shaders/hair.d.ts.map +1 -0
- package/dist/shaders/hair.js +186 -0
- package/dist/shaders/ltc_mag_lut.d.ts +3 -0
- package/dist/shaders/ltc_mag_lut.d.ts.map +1 -0
- package/dist/shaders/ltc_mag_lut.js +1033 -0
- package/dist/shaders/metal.d.ts +2 -0
- package/dist/shaders/metal.d.ts.map +1 -0
- package/dist/shaders/metal.js +171 -0
- package/dist/shaders/nodes.d.ts +2 -0
- package/dist/shaders/nodes.d.ts.map +1 -0
- package/dist/shaders/nodes.js +423 -0
- package/dist/shaders/stockings.d.ts +2 -0
- package/dist/shaders/stockings.d.ts.map +1 -0
- package/dist/shaders/stockings.js +229 -0
- package/package.json +1 -1
- package/src/engine.ts +1281 -376
- package/src/index.ts +12 -2
- package/src/shaders/body.ts +211 -0
- package/src/shaders/classify.ts +25 -0
- package/src/shaders/cloth_rough.ts +174 -0
- package/src/shaders/cloth_smooth.ts +173 -0
- package/src/shaders/default.ts +169 -0
- package/src/shaders/dfg_lut.ts +127 -0
- package/src/shaders/eye.ts +143 -0
- package/src/shaders/face.ts +213 -0
- package/src/shaders/hair.ts +188 -0
- package/src/shaders/ltc_mag_lut.ts +1035 -0
- package/src/shaders/metal.ts +173 -0
- package/src/shaders/nodes.ts +424 -0
- package/src/shaders/stockings.ts +231 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloth_smooth.d.ts","sourceRoot":"","sources":["../../src/shaders/cloth_smooth.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,wBAAwB,yu1BAwKpC,CAAA"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
// M_Smooth_Cloth — dump socket order + m_graphs ramps/overlay/noise-bump (dump omits 噪波→凹凸 subtree).
|
|
2
|
+
import { NODES_WGSL } from "./nodes";
|
|
3
|
+
export const CLOTH_SMOOTH_SHADER_WGSL = /* wgsl */ `
|
|
4
|
+
|
|
5
|
+
${NODES_WGSL}
|
|
6
|
+
|
|
7
|
+
struct CameraUniforms {
|
|
8
|
+
view: mat4x4f,
|
|
9
|
+
projection: mat4x4f,
|
|
10
|
+
viewPos: vec3f,
|
|
11
|
+
_padding: f32,
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
struct Light {
|
|
15
|
+
direction: vec4f,
|
|
16
|
+
color: vec4f,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
struct LightUniforms {
|
|
20
|
+
ambientColor: vec4f,
|
|
21
|
+
lights: array<Light, 4>,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
struct MaterialUniforms {
|
|
25
|
+
diffuseColor: vec3f,
|
|
26
|
+
alpha: f32,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
struct VertexOutput {
|
|
30
|
+
@builtin(position) position: vec4f,
|
|
31
|
+
@location(0) normal: vec3f,
|
|
32
|
+
@location(1) uv: vec2f,
|
|
33
|
+
@location(2) worldPos: vec3f,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
struct LightVP { viewProj: mat4x4f, };
|
|
37
|
+
|
|
38
|
+
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
39
|
+
@group(0) @binding(1) var<uniform> light: LightUniforms;
|
|
40
|
+
@group(0) @binding(2) var diffuseSampler: sampler;
|
|
41
|
+
@group(0) @binding(3) var shadowMap: texture_depth_2d;
|
|
42
|
+
@group(0) @binding(4) var shadowSampler: sampler_comparison;
|
|
43
|
+
@group(0) @binding(5) var<uniform> lightVP: LightVP;
|
|
44
|
+
@group(1) @binding(0) var<storage, read> skinMats: array<mat4x4f>;
|
|
45
|
+
@group(2) @binding(0) var diffuseTexture: texture_2d<f32>;
|
|
46
|
+
@group(2) @binding(1) var<uniform> material: MaterialUniforms;
|
|
47
|
+
|
|
48
|
+
fn sampleShadow(worldPos: vec3f, n: vec3f) -> f32 {
|
|
49
|
+
let biasedPos = worldPos + n * 0.08;
|
|
50
|
+
let lclip = lightVP.viewProj * vec4f(biasedPos, 1.0);
|
|
51
|
+
let ndc = lclip.xyz / max(lclip.w, 1e-6);
|
|
52
|
+
let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
|
|
53
|
+
let cmpZ = ndc.z - 0.001;
|
|
54
|
+
let ts = 1.0 / 4096.0;
|
|
55
|
+
var vis = 0.0;
|
|
56
|
+
for (var y = -1; y <= 1; y++) {
|
|
57
|
+
for (var x = -1; x <= 1; x++) {
|
|
58
|
+
vis += textureSampleCompare(shadowMap, shadowSampler, suv + vec2f(f32(x), f32(y)) * ts, cmpZ);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return vis / 9.0;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const PI_C: f32 = 3.141592653589793;
|
|
65
|
+
const CLOTH_SPECULAR: f32 = 0.8;
|
|
66
|
+
const CLOTH_ROUGHNESS: f32 = 0.5;
|
|
67
|
+
const CLOTH_TOON_EDGE: f32 = 0.2966;
|
|
68
|
+
const CLOTH_MIX04_MUL: f32 = 0.5; // 运算.004 MULTIPLY Value_001 (dump)
|
|
69
|
+
const NPR_EMIT_STR: f32 = 18.200000762939453;
|
|
70
|
+
const NPR_MIX_SHADER_FAC: f32 = 0.8999999761581421;
|
|
71
|
+
|
|
72
|
+
@vertex fn vs(
|
|
73
|
+
@location(0) position: vec3f,
|
|
74
|
+
@location(1) normal: vec3f,
|
|
75
|
+
@location(2) uv: vec2f,
|
|
76
|
+
@location(3) joints0: vec4<u32>,
|
|
77
|
+
@location(4) weights0: vec4<f32>
|
|
78
|
+
) -> VertexOutput {
|
|
79
|
+
var output: VertexOutput;
|
|
80
|
+
let pos4 = vec4f(position, 1.0);
|
|
81
|
+
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
82
|
+
let invWeightSum = select(1.0, 1.0 / weightSum, weightSum > 0.0001);
|
|
83
|
+
let nw = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 * invWeightSum, weightSum > 0.0001);
|
|
84
|
+
var skinnedPos = vec4f(0.0);
|
|
85
|
+
var skinnedNrm = vec3f(0.0);
|
|
86
|
+
for (var i = 0u; i < 4u; i++) {
|
|
87
|
+
let m = skinMats[joints0[i]];
|
|
88
|
+
let w = nw[i];
|
|
89
|
+
skinnedPos += (m * pos4) * w;
|
|
90
|
+
skinnedNrm += (mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz) * normal) * w;
|
|
91
|
+
}
|
|
92
|
+
output.position = camera.projection * camera.view * vec4f(skinnedPos.xyz, 1.0);
|
|
93
|
+
output.normal = normalize(skinnedNrm);
|
|
94
|
+
output.uv = uv;
|
|
95
|
+
output.worldPos = skinnedPos.xyz;
|
|
96
|
+
return output;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
100
|
+
let n = normalize(input.normal);
|
|
101
|
+
let v = normalize(camera.viewPos - input.worldPos);
|
|
102
|
+
let l = -light.lights[0].direction.xyz;
|
|
103
|
+
let sun = light.lights[0].color.xyz * light.lights[0].color.w;
|
|
104
|
+
let amb = light.ambientColor.xyz;
|
|
105
|
+
let shadow = sampleShadow(input.worldPos, n);
|
|
106
|
+
|
|
107
|
+
let tex_s = textureSample(diffuseTexture, diffuseSampler, input.uv);
|
|
108
|
+
let tex_rgb = tex_s.rgb;
|
|
109
|
+
let out_alpha = material.alpha * tex_s.a;
|
|
110
|
+
if (out_alpha < 0.001) { discard; }
|
|
111
|
+
|
|
112
|
+
// Shader→RGB → 颜色渐变.008 CONSTANT — AA like face (same terminator artifact class)
|
|
113
|
+
let lum_shade = shader_to_rgb_diffuse(n, l, sun, amb, shadow);
|
|
114
|
+
let ramp008 = ramp_constant_edge_aa(lum_shade, CLOTH_TOON_EDGE, vec4f(0,0,0,1), vec4f(1,1,1,1));
|
|
115
|
+
let toon_r = ramp008.r;
|
|
116
|
+
// 颜色渐变.008 → 运算.004 MULTIPLY 0.5 → 混合.004 Factor
|
|
117
|
+
let mix04_fac = math_multiply(toon_r, CLOTH_MIX04_MUL);
|
|
118
|
+
|
|
119
|
+
// 混合.004: A=色相/饱和度/明度.002, B=纹理
|
|
120
|
+
let dark_tex = hue_sat(0.5, 1.0, 0.19999998807907104, 1.0, tex_rgb);
|
|
121
|
+
let mix04 = mix_blend(mix04_fac, dark_tex, tex_rgb);
|
|
122
|
+
|
|
123
|
+
// 倒角.001→Z → 混合.003 Factor; A=混合.004, B=色相/饱和度/明度.002
|
|
124
|
+
let bevel_z = clamp(n.y, 0.0, 1.0);
|
|
125
|
+
let mix03 = mix_blend(bevel_z, mix04, dark_tex);
|
|
126
|
+
|
|
127
|
+
// 环境光遮蔽 → 颜色渐变.001 LINEAR → 混合.001 (白/黑) → 混合.002 OVERLAY Fac
|
|
128
|
+
let ao = ao_fake(n, v);
|
|
129
|
+
let ao_ramp_c = ramp_linear(ao, 0.0, vec4f(1,1,1,1), 0.8808, vec4f(0,0,0,1));
|
|
130
|
+
let mix01_fac = ao_ramp_c.r;
|
|
131
|
+
let mix01_rgb = mix(vec3f(1.0), vec3f(0.0), mix01_fac);
|
|
132
|
+
|
|
133
|
+
// 混合.002 OVERLAY: Fac=混合.001, A=混合.003, B=色相/饱和度/明度.004
|
|
134
|
+
let hue004 = hue_sat(0.5, 0.800000011920929, 2.0, 1.0, mix03);
|
|
135
|
+
let overlay_fac = mix01_rgb.r;
|
|
136
|
+
let npr_rgb = mix_overlay(overlay_fac, mix03, hue004);
|
|
137
|
+
let npr_emission = npr_rgb * NPR_EMIT_STR;
|
|
138
|
+
|
|
139
|
+
// 原理化BSDF (EEVEE port): metallic=0, specular=0.8, roughness=0.5, specular_tint=0.
|
|
140
|
+
// Bump subtree is dead in the Blender graph (噪波→凹凸 not linked to Principled.Normal).
|
|
141
|
+
let principled_base = hue_sat(0.5, 1.0, 0.800000011920929, 1.0, tex_rgb);
|
|
142
|
+
let NL = max(dot(n, l), 0.0);
|
|
143
|
+
let NV = max(dot(n, v), 1e-4);
|
|
144
|
+
|
|
145
|
+
// f0/f90 per gpu_shader_material_principled.glsl — specular_tint=0 → dielectric_f0_color=white.
|
|
146
|
+
let f0 = vec3f(0.08 * CLOTH_SPECULAR);
|
|
147
|
+
let f90 = mix(f0, vec3f(1.0), sqrt(CLOTH_SPECULAR));
|
|
148
|
+
let split_sum = brdf_lut_baked(NV, CLOTH_ROUGHNESS);
|
|
149
|
+
let reflection_color = F_brdf_multi_scatter(f0, f90, split_sum);
|
|
150
|
+
|
|
151
|
+
// Direct glossy — bsdf_ggx already includes NL; no F applied here (tinted after accum).
|
|
152
|
+
// ltc_brdf_scale: EEVEE direct path uses LTC; split-sum LUT path is rescaled to match.
|
|
153
|
+
let spec_direct = bsdf_ggx(n, l, v, CLOTH_ROUGHNESS) * sun * shadow * ltc_brdf_scale(NV, CLOTH_ROUGHNESS);
|
|
154
|
+
// Indirect glossy — flat world probe (solid color). Phase 2 adds cubemap.
|
|
155
|
+
let spec_indirect = amb;
|
|
156
|
+
let spec_radiance = (spec_direct + spec_indirect) * reflection_color;
|
|
157
|
+
|
|
158
|
+
// Diffuse (Lambert), no (1-F) factor per EEVEE — it doesn't energy-conserve spec<->diffuse.
|
|
159
|
+
// probe_evaluate_world_diff returns radiance L_w (SH projected, not cosine-convolved); in
|
|
160
|
+
// closure_eval_surface_lib line 302: closure.radiance += diffuse_accum * L_w * diffuse.color.
|
|
161
|
+
// So indirect diffuse = base_color × L_w, no π factor.
|
|
162
|
+
let diffuse_radiance = principled_base * (sun * NL * shadow / PI_C + amb);
|
|
163
|
+
let principled = diffuse_radiance + spec_radiance;
|
|
164
|
+
|
|
165
|
+
// 混合着色器.001: Shader=自发光.005, Shader_001=原理化BSDF, Fac=0.9
|
|
166
|
+
let final_color = mix(npr_emission, principled, NPR_MIX_SHADER_FAC);
|
|
167
|
+
|
|
168
|
+
return vec4f(final_color, out_alpha);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
`;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const DEFAULT_SHADER_WGSL = "\n\nconst PI: f32 = 3.141592653589793;\nconst F0_DIELECTRIC: f32 = 0.04;\nconst ROUGHNESS: f32 = 0.5;\n\nstruct CameraUniforms {\n view: mat4x4f,\n projection: mat4x4f,\n viewPos: vec3f,\n _padding: f32,\n};\n\nstruct Light {\n direction: vec4f,\n color: vec4f,\n};\n\nstruct LightUniforms {\n ambientColor: vec4f,\n lights: array<Light, 4>,\n};\n\n// Per-material uniforms. Add fields here only when a shader actually reads them;\n// preset-specific shaders (face.ts, future hair.ts) share this struct so the\n// engine can use one material bind-group layout.\nstruct MaterialUniforms {\n diffuseColor: vec3f, // tint; multiplies sampled albedo (unused by current fs, reserved)\n alpha: f32, // 0 \u2192 discard; <1 \u2192 transparent draw call\n};\n\nstruct VertexOutput {\n @builtin(position) position: vec4f,\n @location(0) normal: vec3f,\n @location(1) uv: vec2f,\n @location(2) worldPos: vec3f,\n};\n\nstruct LightVP { viewProj: mat4x4f, };\n\n@group(0) @binding(0) var<uniform> camera: CameraUniforms;\n@group(0) @binding(1) var<uniform> light: LightUniforms;\n@group(0) @binding(2) var diffuseSampler: sampler;\n@group(0) @binding(3) var shadowMap: texture_depth_2d;\n@group(0) @binding(4) var shadowSampler: sampler_comparison;\n@group(0) @binding(5) var<uniform> lightVP: LightVP;\n@group(1) @binding(0) var<storage, read> skinMats: array<mat4x4f>;\n@group(2) @binding(0) var diffuseTexture: texture_2d<f32>;\n@group(2) @binding(1) var<uniform> material: MaterialUniforms;\n\n// \u2500\u2500\u2500 GGX specular helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfn ggx_d(ndoth: f32, a2: f32) -> f32 {\n let denom = ndoth * ndoth * (a2 - 1.0) + 1.0;\n return a2 / (PI * denom * denom);\n}\n\nfn smith_g1(ndotx: f32, a2: f32) -> f32 {\n return 2.0 * ndotx / (ndotx + sqrt(a2 + (1.0 - a2) * ndotx * ndotx));\n}\n\nfn fresnel_schlick(cosTheta: f32, f0: f32) -> f32 {\n return f0 + (1.0 - f0) * pow(1.0 - cosTheta, 5.0);\n}\n\n// \u2500\u2500\u2500 Filmic tone mapping (LUT extracted from Blender 3.6 OCIO) \u2500\u2500\u2500\u2500\u2500\n// View transform = Filmic, Look = Medium High Contrast, Exposure = -0.3.\n// 14 samples at integer log2 stops from -10 to +3 (inclusive).\n// Extracted via scripts/extract_filmic_lut.py \u2192 probe image through scene\n// color management. Input: linear scene-referred. Output: sRGB display.\n\nfn filmic(x: f32) -> f32 {\n var lut = array<f32, 14>(\n 0.0067, 0.0141, 0.0272, 0.0499, 0.0885, 0.1512, 0.2462,\n 0.3753, 0.5273, 0.6776, 0.8031, 0.8929, 0.9495, 0.9814\n );\n let t = clamp(log2(max(x, 1e-10)) + 10.0, 0.0, 13.0);\n let i = u32(t);\n let j = min(i + 1u, 13u);\n return mix(lut[i], lut[j], t - f32(i));\n}\n\nfn tonemap(hdr: vec3f) -> vec3f {\n return vec3f(filmic(hdr.x), filmic(hdr.y), filmic(hdr.z));\n}\n\n// \u2500\u2500\u2500 Shadow sampling (3\u00D73 PCF) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfn sampleShadow(worldPos: vec3f, n: vec3f) -> f32 {\n let biasedPos = worldPos + n * 0.08;\n let lclip = lightVP.viewProj * vec4f(biasedPos, 1.0);\n let ndc = lclip.xyz / max(lclip.w, 1e-6);\n let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);\n let cmpZ = ndc.z - 0.001;\n let ts = 1.0 / 4096.0;\n var vis = 0.0;\n for (var y = -1; y <= 1; y++) {\n for (var x = -1; x <= 1; x++) {\n vis += textureSampleCompare(shadowMap, shadowSampler, suv + vec2f(f32(x), f32(y)) * ts, cmpZ);\n }\n }\n return vis / 9.0;\n}\n\n// \u2500\u2500\u2500 Vertex / Fragment \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\n@vertex fn vs(\n @location(0) position: vec3f,\n @location(1) normal: vec3f,\n @location(2) uv: vec2f,\n @location(3) joints0: vec4<u32>,\n @location(4) weights0: vec4<f32>\n) -> VertexOutput {\n var output: VertexOutput;\n let pos4 = vec4f(position, 1.0);\n let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;\n let invWeightSum = select(1.0, 1.0 / weightSum, weightSum > 0.0001);\n let nw = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 * invWeightSum, weightSum > 0.0001);\n var skinnedPos = vec4f(0.0);\n var skinnedNrm = vec3f(0.0);\n for (var i = 0u; i < 4u; i++) {\n let m = skinMats[joints0[i]];\n let w = nw[i];\n skinnedPos += (m * pos4) * w;\n skinnedNrm += (mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz) * normal) * w;\n }\n output.position = camera.projection * camera.view * vec4f(skinnedPos.xyz, 1.0);\n output.normal = normalize(skinnedNrm);\n output.uv = uv;\n output.worldPos = skinnedPos.xyz;\n return output;\n}\n\n@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {\n let alpha = material.alpha;\n if (alpha < 0.001) { discard; }\n\n let n = normalize(input.normal);\n let v = normalize(camera.viewPos - input.worldPos);\n let albedo = textureSample(diffuseTexture, diffuseSampler, input.uv).rgb;\n\n let l = -light.lights[0].direction.xyz;\n let sunColor = light.lights[0].color.xyz * light.lights[0].color.w;\n let h = normalize(l + v);\n\n let ndotl = max(dot(n, l), 0.0);\n let ndotv = max(dot(n, v), 0.001);\n let ndoth = max(dot(n, h), 0.0);\n let vdoth = max(dot(v, h), 0.0);\n\n let a2 = ROUGHNESS * ROUGHNESS;\n let D = ggx_d(ndoth, a2);\n let G = smith_g1(ndotl, a2) * smith_g1(ndotv, a2);\n let F = fresnel_schlick(vdoth, F0_DIELECTRIC);\n let spec = (D * G * F) / max(4.0 * ndotl * ndotv, 0.001);\n\n let shadow = sampleShadow(input.worldPos, n);\n let kd = (1.0 - F) * albedo / PI;\n let direct = (kd + spec) * sunColor * ndotl * shadow;\n let ambient = albedo * light.ambientColor.xyz;\n\n return vec4f(ambient + direct, alpha);\n}\n\n";
|
|
2
|
+
//# sourceMappingURL=default.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"default.d.ts","sourceRoot":"","sources":["../../src/shaders/default.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,mBAAmB,qkMAoK/B,CAAA"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
// Blender 3.6 Principled BSDF defaults + Filmic "Medium High Contrast" tone mapping.
|
|
2
|
+
// Metallic=0, Specular=0.5 (F0=0.04), Roughness=0.5.
|
|
3
|
+
// Tone mapping via LUT sampled from Blender's OCIO pipeline (exposure -0.3 baked in).
|
|
4
|
+
export const DEFAULT_SHADER_WGSL = /* wgsl */ `
|
|
5
|
+
|
|
6
|
+
const PI: f32 = 3.141592653589793;
|
|
7
|
+
const F0_DIELECTRIC: f32 = 0.04;
|
|
8
|
+
const ROUGHNESS: f32 = 0.5;
|
|
9
|
+
|
|
10
|
+
struct CameraUniforms {
|
|
11
|
+
view: mat4x4f,
|
|
12
|
+
projection: mat4x4f,
|
|
13
|
+
viewPos: vec3f,
|
|
14
|
+
_padding: f32,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
struct Light {
|
|
18
|
+
direction: vec4f,
|
|
19
|
+
color: vec4f,
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
struct LightUniforms {
|
|
23
|
+
ambientColor: vec4f,
|
|
24
|
+
lights: array<Light, 4>,
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Per-material uniforms. Add fields here only when a shader actually reads them;
|
|
28
|
+
// preset-specific shaders (face.ts, future hair.ts) share this struct so the
|
|
29
|
+
// engine can use one material bind-group layout.
|
|
30
|
+
struct MaterialUniforms {
|
|
31
|
+
diffuseColor: vec3f, // tint; multiplies sampled albedo (unused by current fs, reserved)
|
|
32
|
+
alpha: f32, // 0 → discard; <1 → transparent draw call
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
struct VertexOutput {
|
|
36
|
+
@builtin(position) position: vec4f,
|
|
37
|
+
@location(0) normal: vec3f,
|
|
38
|
+
@location(1) uv: vec2f,
|
|
39
|
+
@location(2) worldPos: vec3f,
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
struct LightVP { viewProj: mat4x4f, };
|
|
43
|
+
|
|
44
|
+
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
45
|
+
@group(0) @binding(1) var<uniform> light: LightUniforms;
|
|
46
|
+
@group(0) @binding(2) var diffuseSampler: sampler;
|
|
47
|
+
@group(0) @binding(3) var shadowMap: texture_depth_2d;
|
|
48
|
+
@group(0) @binding(4) var shadowSampler: sampler_comparison;
|
|
49
|
+
@group(0) @binding(5) var<uniform> lightVP: LightVP;
|
|
50
|
+
@group(1) @binding(0) var<storage, read> skinMats: array<mat4x4f>;
|
|
51
|
+
@group(2) @binding(0) var diffuseTexture: texture_2d<f32>;
|
|
52
|
+
@group(2) @binding(1) var<uniform> material: MaterialUniforms;
|
|
53
|
+
|
|
54
|
+
// ─── GGX specular helpers ───────────────────────────────────────────
|
|
55
|
+
|
|
56
|
+
fn ggx_d(ndoth: f32, a2: f32) -> f32 {
|
|
57
|
+
let denom = ndoth * ndoth * (a2 - 1.0) + 1.0;
|
|
58
|
+
return a2 / (PI * denom * denom);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
fn smith_g1(ndotx: f32, a2: f32) -> f32 {
|
|
62
|
+
return 2.0 * ndotx / (ndotx + sqrt(a2 + (1.0 - a2) * ndotx * ndotx));
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fn fresnel_schlick(cosTheta: f32, f0: f32) -> f32 {
|
|
66
|
+
return f0 + (1.0 - f0) * pow(1.0 - cosTheta, 5.0);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// ─── Filmic tone mapping (LUT extracted from Blender 3.6 OCIO) ─────
|
|
70
|
+
// View transform = Filmic, Look = Medium High Contrast, Exposure = -0.3.
|
|
71
|
+
// 14 samples at integer log2 stops from -10 to +3 (inclusive).
|
|
72
|
+
// Extracted via scripts/extract_filmic_lut.py → probe image through scene
|
|
73
|
+
// color management. Input: linear scene-referred. Output: sRGB display.
|
|
74
|
+
|
|
75
|
+
fn filmic(x: f32) -> f32 {
|
|
76
|
+
var lut = array<f32, 14>(
|
|
77
|
+
0.0067, 0.0141, 0.0272, 0.0499, 0.0885, 0.1512, 0.2462,
|
|
78
|
+
0.3753, 0.5273, 0.6776, 0.8031, 0.8929, 0.9495, 0.9814
|
|
79
|
+
);
|
|
80
|
+
let t = clamp(log2(max(x, 1e-10)) + 10.0, 0.0, 13.0);
|
|
81
|
+
let i = u32(t);
|
|
82
|
+
let j = min(i + 1u, 13u);
|
|
83
|
+
return mix(lut[i], lut[j], t - f32(i));
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
fn tonemap(hdr: vec3f) -> vec3f {
|
|
87
|
+
return vec3f(filmic(hdr.x), filmic(hdr.y), filmic(hdr.z));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// ─── Shadow sampling (3×3 PCF) ──────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
fn sampleShadow(worldPos: vec3f, n: vec3f) -> f32 {
|
|
93
|
+
let biasedPos = worldPos + n * 0.08;
|
|
94
|
+
let lclip = lightVP.viewProj * vec4f(biasedPos, 1.0);
|
|
95
|
+
let ndc = lclip.xyz / max(lclip.w, 1e-6);
|
|
96
|
+
let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
|
|
97
|
+
let cmpZ = ndc.z - 0.001;
|
|
98
|
+
let ts = 1.0 / 4096.0;
|
|
99
|
+
var vis = 0.0;
|
|
100
|
+
for (var y = -1; y <= 1; y++) {
|
|
101
|
+
for (var x = -1; x <= 1; x++) {
|
|
102
|
+
vis += textureSampleCompare(shadowMap, shadowSampler, suv + vec2f(f32(x), f32(y)) * ts, cmpZ);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return vis / 9.0;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// ─── Vertex / Fragment ──────────────────────────────────────────────
|
|
109
|
+
|
|
110
|
+
@vertex fn vs(
|
|
111
|
+
@location(0) position: vec3f,
|
|
112
|
+
@location(1) normal: vec3f,
|
|
113
|
+
@location(2) uv: vec2f,
|
|
114
|
+
@location(3) joints0: vec4<u32>,
|
|
115
|
+
@location(4) weights0: vec4<f32>
|
|
116
|
+
) -> VertexOutput {
|
|
117
|
+
var output: VertexOutput;
|
|
118
|
+
let pos4 = vec4f(position, 1.0);
|
|
119
|
+
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
120
|
+
let invWeightSum = select(1.0, 1.0 / weightSum, weightSum > 0.0001);
|
|
121
|
+
let nw = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 * invWeightSum, weightSum > 0.0001);
|
|
122
|
+
var skinnedPos = vec4f(0.0);
|
|
123
|
+
var skinnedNrm = vec3f(0.0);
|
|
124
|
+
for (var i = 0u; i < 4u; i++) {
|
|
125
|
+
let m = skinMats[joints0[i]];
|
|
126
|
+
let w = nw[i];
|
|
127
|
+
skinnedPos += (m * pos4) * w;
|
|
128
|
+
skinnedNrm += (mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz) * normal) * w;
|
|
129
|
+
}
|
|
130
|
+
output.position = camera.projection * camera.view * vec4f(skinnedPos.xyz, 1.0);
|
|
131
|
+
output.normal = normalize(skinnedNrm);
|
|
132
|
+
output.uv = uv;
|
|
133
|
+
output.worldPos = skinnedPos.xyz;
|
|
134
|
+
return output;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
138
|
+
let alpha = material.alpha;
|
|
139
|
+
if (alpha < 0.001) { discard; }
|
|
140
|
+
|
|
141
|
+
let n = normalize(input.normal);
|
|
142
|
+
let v = normalize(camera.viewPos - input.worldPos);
|
|
143
|
+
let albedo = textureSample(diffuseTexture, diffuseSampler, input.uv).rgb;
|
|
144
|
+
|
|
145
|
+
let l = -light.lights[0].direction.xyz;
|
|
146
|
+
let sunColor = light.lights[0].color.xyz * light.lights[0].color.w;
|
|
147
|
+
let h = normalize(l + v);
|
|
148
|
+
|
|
149
|
+
let ndotl = max(dot(n, l), 0.0);
|
|
150
|
+
let ndotv = max(dot(n, v), 0.001);
|
|
151
|
+
let ndoth = max(dot(n, h), 0.0);
|
|
152
|
+
let vdoth = max(dot(v, h), 0.0);
|
|
153
|
+
|
|
154
|
+
let a2 = ROUGHNESS * ROUGHNESS;
|
|
155
|
+
let D = ggx_d(ndoth, a2);
|
|
156
|
+
let G = smith_g1(ndotl, a2) * smith_g1(ndotv, a2);
|
|
157
|
+
let F = fresnel_schlick(vdoth, F0_DIELECTRIC);
|
|
158
|
+
let spec = (D * G * F) / max(4.0 * ndotl * ndotv, 0.001);
|
|
159
|
+
|
|
160
|
+
let shadow = sampleShadow(input.worldPos, n);
|
|
161
|
+
let kd = (1.0 - F) * albedo / PI;
|
|
162
|
+
let direct = (kd + spec) * sunColor * ndotl * shadow;
|
|
163
|
+
let ambient = albedo * light.ambientColor.xyz;
|
|
164
|
+
|
|
165
|
+
return vec4f(ambient + direct, alpha);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
`;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare const DFG_LUT_SIZE = 64;
|
|
2
|
+
export declare const DFG_LUT_SAMPLE_COUNT = 32;
|
|
3
|
+
export declare const DFG_LUT_WGSL = "\nconst LUT_SIZE: f32 = 64.0;\nconst SAMPLE_COUNT: u32 = 32u;\nconst M_2PI: f32 = 6.283185307179586;\n\n@vertex fn vs(@builtin(vertex_index) vid: u32) -> @builtin(position) vec4f {\n // Full-screen triangle covering [-1,1]\u00B2 in NDC.\n let x = f32((vid << 1u) & 2u) * 2.0 - 1.0;\n let y = f32(vid & 2u) * 2.0 - 1.0;\n return vec4f(x, y, 0.0, 1.0);\n}\n\n// common_math_geom_lib.glsl:165 \u2014 make_orthonormal_basis.\nfn orthonormal_basis(N: vec3f) -> mat2x3f {\n let up = select(vec3f(1.0, 0.0, 0.0), vec3f(0.0, 0.0, 1.0), abs(N.z) < 0.99999);\n let T = normalize(cross(up, N));\n let B = cross(N, T);\n return mat2x3f(T, B);\n}\n\n// bsdf_sampling_lib.glsl:27 \u2014 Heitz 2018 VNDF sampling in tangent space.\nfn sample_ggx_vndf(rand: vec3f, alpha: f32, Vt: vec3f) -> vec3f {\n let Vh = normalize(vec3f(alpha * Vt.xy, Vt.z));\n let tb = orthonormal_basis(Vh);\n let Th = tb[0];\n let Bh = tb[1];\n let r = sqrt(rand.x);\n let x = r * rand.y;\n var y = r * rand.z;\n let s = 0.5 * (1.0 + Vh.z);\n y = (1.0 - s) * sqrt(1.0 - x * x) + s * y;\n let z = sqrt(saturate(1.0 - x * x - y * y));\n let Hh = x * Th + y * Bh + z * Vh;\n return normalize(vec3f(alpha * Hh.xy, saturate(Hh.z)));\n}\n\n// bsdf_common_lib.glsl:105 \u2014 G1 Smith GGX (Brian Karis opti form).\nfn G1_Smith_GGX_opti(NX: f32, a2: f32) -> f32 {\n return NX + sqrt(NX * (NX - NX * a2) + a2);\n}\n\n// bsdf_common_lib.glsl:50 \u2014 exact dielectric Fresnel (monochromatic).\nfn F_eta(eta: f32, cos_theta: f32) -> f32 {\n let c = abs(cos_theta);\n var g = eta * eta - 1.0 + c * c;\n if (g > 0.0) {\n g = sqrt(g);\n let A = (g - c) / (g + c);\n let B = (c * (g + c) - 1.0) / (c * (g - c) + 1.0);\n return 0.5 * A * A * (1.0 + B * B);\n }\n return 1.0; // total internal reflection\n}\n\nfn f0_from_ior(eta: f32) -> f32 {\n let A = (eta - 1.0) / (eta + 1.0);\n return A * A;\n}\n\n// F_color_blend(eta, fresnel, vec3(0)).r \u2014 blend factor only.\nfn F_color_blend_zero(eta: f32, fresnel: f32) -> f32 {\n let f0 = f0_from_ior(eta);\n return saturate((fresnel - f0) / (1.0 - f0));\n}\n\n@fragment fn fs(@builtin(position) frag: vec4f) -> @location(0) vec2f {\n let y_uv = floor(frag.y) / (LUT_SIZE - 1.0);\n let x_uv = floor(frag.x) / (LUT_SIZE - 1.0);\n\n let NV = clamp(1.0 - y_uv * y_uv, 1e-4, 0.9999);\n let a = x_uv * x_uv;\n let a2 = clamp(a * a, 1e-4, 0.9999);\n\n let V = vec3f(sqrt(1.0 - NV * NV), 0.0, NV);\n\n // principled specular=1.0 \u2014 max value, matches bsdf_lut_frag.glsl:41.\n let eta = (2.0 / (1.0 - sqrt(0.08 * 1.0))) - 1.0;\n\n var brdf_accum = 0.0;\n var fresnel_accum = 0.0;\n let sc_f = f32(SAMPLE_COUNT);\n for (var j: u32 = 0u; j < SAMPLE_COUNT; j = j + 1u) {\n for (var i: u32 = 0u; i < SAMPLE_COUNT; i = i + 1u) {\n let ix = (f32(i) + 0.5) / sc_f;\n let iy = (f32(j) + 0.5) / sc_f;\n // Xi.x = radial, Xi.yz = (cos, sin) of azimuth \u2014 bsdf_lut_frag.glsl:22.\n let Xi = vec3f(ix, cos(iy * M_2PI), sin(iy * M_2PI));\n\n let H = sample_ggx_vndf(Xi, a, V);\n let L = -reflect(V, H);\n let NL = L.z;\n if (NL > 0.0) {\n let NH = max(H.z, 0.0);\n let VH = max(dot(V, H), 0.0);\n\n // G_smith (divided form): 4\u00B7NV\u00B7NL / (G1_v\u00B7G1_l). See bsdf_common_lib.glsl:105.\n let G1v = G1_Smith_GGX_opti(NV, a2);\n let G1l = G1_Smith_GGX_opti(NL, a2);\n let G_smith = 4.0 * NV * NL / (G1v * G1l);\n\n let brdf = (G_smith * VH) / (NH * NV);\n\n let fresnel = F_eta(eta, VH);\n let Fc = F_color_blend_zero(eta, fresnel);\n\n brdf_accum = brdf_accum + (1.0 - Fc) * brdf;\n fresnel_accum = fresnel_accum + Fc * brdf;\n }\n }\n }\n let n2 = sc_f * sc_f;\n return vec2f(brdf_accum / n2, fresnel_accum / n2);\n}\n";
|
|
4
|
+
//# sourceMappingURL=dfg_lut.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dfg_lut.d.ts","sourceRoot":"","sources":["../../src/shaders/dfg_lut.ts"],"names":[],"mappings":"AAUA,eAAO,MAAM,YAAY,KAAK,CAAA;AAC9B,eAAO,MAAM,oBAAoB,KAAK,CAAA;AAEtC,eAAO,MAAM,YAAY,otHAiHxB,CAAA"}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
// One-shot bake pass that precomputes EEVEE's BRDF split-sum DFG LUT.
|
|
2
|
+
// Direct WGSL port of Blender 3.6 source/blender/draw/engines/eevee/shaders/
|
|
3
|
+
// bsdf_lut_frag.glsl + bsdf_sampling_lib.glsl (VNDF GGX branch).
|
|
4
|
+
//
|
|
5
|
+
// Output texture: 64×64 rg16float, written once at engine init.
|
|
6
|
+
// R = (1 - Fc) × BRDF integrated over GGX VNDF — scales f0
|
|
7
|
+
// G = Fc × BRDF integrated over GGX VNDF — scales f90
|
|
8
|
+
// Runtime sampler: see brdf_lut_baked() in nodes.ts. Plug into
|
|
9
|
+
// F_brdf_single_scatter / F_brdf_multi_scatter verbatim.
|
|
10
|
+
export const DFG_LUT_SIZE = 64;
|
|
11
|
+
export const DFG_LUT_SAMPLE_COUNT = 32;
|
|
12
|
+
export const DFG_LUT_WGSL = /* wgsl */ `
|
|
13
|
+
const LUT_SIZE: f32 = ${DFG_LUT_SIZE}.0;
|
|
14
|
+
const SAMPLE_COUNT: u32 = ${DFG_LUT_SAMPLE_COUNT}u;
|
|
15
|
+
const M_2PI: f32 = 6.283185307179586;
|
|
16
|
+
|
|
17
|
+
@vertex fn vs(@builtin(vertex_index) vid: u32) -> @builtin(position) vec4f {
|
|
18
|
+
// Full-screen triangle covering [-1,1]² in NDC.
|
|
19
|
+
let x = f32((vid << 1u) & 2u) * 2.0 - 1.0;
|
|
20
|
+
let y = f32(vid & 2u) * 2.0 - 1.0;
|
|
21
|
+
return vec4f(x, y, 0.0, 1.0);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// common_math_geom_lib.glsl:165 — make_orthonormal_basis.
|
|
25
|
+
fn orthonormal_basis(N: vec3f) -> mat2x3f {
|
|
26
|
+
let up = select(vec3f(1.0, 0.0, 0.0), vec3f(0.0, 0.0, 1.0), abs(N.z) < 0.99999);
|
|
27
|
+
let T = normalize(cross(up, N));
|
|
28
|
+
let B = cross(N, T);
|
|
29
|
+
return mat2x3f(T, B);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// bsdf_sampling_lib.glsl:27 — Heitz 2018 VNDF sampling in tangent space.
|
|
33
|
+
fn sample_ggx_vndf(rand: vec3f, alpha: f32, Vt: vec3f) -> vec3f {
|
|
34
|
+
let Vh = normalize(vec3f(alpha * Vt.xy, Vt.z));
|
|
35
|
+
let tb = orthonormal_basis(Vh);
|
|
36
|
+
let Th = tb[0];
|
|
37
|
+
let Bh = tb[1];
|
|
38
|
+
let r = sqrt(rand.x);
|
|
39
|
+
let x = r * rand.y;
|
|
40
|
+
var y = r * rand.z;
|
|
41
|
+
let s = 0.5 * (1.0 + Vh.z);
|
|
42
|
+
y = (1.0 - s) * sqrt(1.0 - x * x) + s * y;
|
|
43
|
+
let z = sqrt(saturate(1.0 - x * x - y * y));
|
|
44
|
+
let Hh = x * Th + y * Bh + z * Vh;
|
|
45
|
+
return normalize(vec3f(alpha * Hh.xy, saturate(Hh.z)));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// bsdf_common_lib.glsl:105 — G1 Smith GGX (Brian Karis opti form).
|
|
49
|
+
fn G1_Smith_GGX_opti(NX: f32, a2: f32) -> f32 {
|
|
50
|
+
return NX + sqrt(NX * (NX - NX * a2) + a2);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// bsdf_common_lib.glsl:50 — exact dielectric Fresnel (monochromatic).
|
|
54
|
+
fn F_eta(eta: f32, cos_theta: f32) -> f32 {
|
|
55
|
+
let c = abs(cos_theta);
|
|
56
|
+
var g = eta * eta - 1.0 + c * c;
|
|
57
|
+
if (g > 0.0) {
|
|
58
|
+
g = sqrt(g);
|
|
59
|
+
let A = (g - c) / (g + c);
|
|
60
|
+
let B = (c * (g + c) - 1.0) / (c * (g - c) + 1.0);
|
|
61
|
+
return 0.5 * A * A * (1.0 + B * B);
|
|
62
|
+
}
|
|
63
|
+
return 1.0; // total internal reflection
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
fn f0_from_ior(eta: f32) -> f32 {
|
|
67
|
+
let A = (eta - 1.0) / (eta + 1.0);
|
|
68
|
+
return A * A;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// F_color_blend(eta, fresnel, vec3(0)).r — blend factor only.
|
|
72
|
+
fn F_color_blend_zero(eta: f32, fresnel: f32) -> f32 {
|
|
73
|
+
let f0 = f0_from_ior(eta);
|
|
74
|
+
return saturate((fresnel - f0) / (1.0 - f0));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
@fragment fn fs(@builtin(position) frag: vec4f) -> @location(0) vec2f {
|
|
78
|
+
let y_uv = floor(frag.y) / (LUT_SIZE - 1.0);
|
|
79
|
+
let x_uv = floor(frag.x) / (LUT_SIZE - 1.0);
|
|
80
|
+
|
|
81
|
+
let NV = clamp(1.0 - y_uv * y_uv, 1e-4, 0.9999);
|
|
82
|
+
let a = x_uv * x_uv;
|
|
83
|
+
let a2 = clamp(a * a, 1e-4, 0.9999);
|
|
84
|
+
|
|
85
|
+
let V = vec3f(sqrt(1.0 - NV * NV), 0.0, NV);
|
|
86
|
+
|
|
87
|
+
// principled specular=1.0 — max value, matches bsdf_lut_frag.glsl:41.
|
|
88
|
+
let eta = (2.0 / (1.0 - sqrt(0.08 * 1.0))) - 1.0;
|
|
89
|
+
|
|
90
|
+
var brdf_accum = 0.0;
|
|
91
|
+
var fresnel_accum = 0.0;
|
|
92
|
+
let sc_f = f32(SAMPLE_COUNT);
|
|
93
|
+
for (var j: u32 = 0u; j < SAMPLE_COUNT; j = j + 1u) {
|
|
94
|
+
for (var i: u32 = 0u; i < SAMPLE_COUNT; i = i + 1u) {
|
|
95
|
+
let ix = (f32(i) + 0.5) / sc_f;
|
|
96
|
+
let iy = (f32(j) + 0.5) / sc_f;
|
|
97
|
+
// Xi.x = radial, Xi.yz = (cos, sin) of azimuth — bsdf_lut_frag.glsl:22.
|
|
98
|
+
let Xi = vec3f(ix, cos(iy * M_2PI), sin(iy * M_2PI));
|
|
99
|
+
|
|
100
|
+
let H = sample_ggx_vndf(Xi, a, V);
|
|
101
|
+
let L = -reflect(V, H);
|
|
102
|
+
let NL = L.z;
|
|
103
|
+
if (NL > 0.0) {
|
|
104
|
+
let NH = max(H.z, 0.0);
|
|
105
|
+
let VH = max(dot(V, H), 0.0);
|
|
106
|
+
|
|
107
|
+
// G_smith (divided form): 4·NV·NL / (G1_v·G1_l). See bsdf_common_lib.glsl:105.
|
|
108
|
+
let G1v = G1_Smith_GGX_opti(NV, a2);
|
|
109
|
+
let G1l = G1_Smith_GGX_opti(NL, a2);
|
|
110
|
+
let G_smith = 4.0 * NV * NL / (G1v * G1l);
|
|
111
|
+
|
|
112
|
+
let brdf = (G_smith * VH) / (NH * NV);
|
|
113
|
+
|
|
114
|
+
let fresnel = F_eta(eta, VH);
|
|
115
|
+
let Fc = F_color_blend_zero(eta, fresnel);
|
|
116
|
+
|
|
117
|
+
brdf_accum = brdf_accum + (1.0 - Fc) * brdf;
|
|
118
|
+
fresnel_accum = fresnel_accum + Fc * brdf;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
let n2 = sc_f * sc_f;
|
|
123
|
+
return vec2f(brdf_accum / n2, fresnel_accum / n2);
|
|
124
|
+
}
|
|
125
|
+
`;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const EYE_SHADER_WGSL = "\n\nconst PI: f32 = 3.141592653589793;\nconst F0_DIELECTRIC: f32 = 0.04;\nconst ROUGHNESS: f32 = 0.5;\nconst EYE_EMISSION_STRENGTH: f32 = 1.5;\n\nstruct CameraUniforms {\n view: mat4x4f,\n projection: mat4x4f,\n viewPos: vec3f,\n _padding: f32,\n};\n\nstruct Light {\n direction: vec4f,\n color: vec4f,\n};\n\nstruct LightUniforms {\n ambientColor: vec4f,\n lights: array<Light, 4>,\n};\n\nstruct MaterialUniforms {\n diffuseColor: vec3f,\n alpha: f32,\n};\n\nstruct VertexOutput {\n @builtin(position) position: vec4f,\n @location(0) normal: vec3f,\n @location(1) uv: vec2f,\n @location(2) worldPos: vec3f,\n};\n\nstruct LightVP { viewProj: mat4x4f, };\n\n@group(0) @binding(0) var<uniform> camera: CameraUniforms;\n@group(0) @binding(1) var<uniform> light: LightUniforms;\n@group(0) @binding(2) var diffuseSampler: sampler;\n@group(0) @binding(3) var shadowMap: texture_depth_2d;\n@group(0) @binding(4) var shadowSampler: sampler_comparison;\n@group(0) @binding(5) var<uniform> lightVP: LightVP;\n@group(1) @binding(0) var<storage, read> skinMats: array<mat4x4f>;\n@group(2) @binding(0) var diffuseTexture: texture_2d<f32>;\n@group(2) @binding(1) var<uniform> material: MaterialUniforms;\n\nfn ggx_d(ndoth: f32, a2: f32) -> f32 {\n let denom = ndoth * ndoth * (a2 - 1.0) + 1.0;\n return a2 / (PI * denom * denom);\n}\n\nfn smith_g1(ndotx: f32, a2: f32) -> f32 {\n return 2.0 * ndotx / (ndotx + sqrt(a2 + (1.0 - a2) * ndotx * ndotx));\n}\n\nfn fresnel_schlick(cosTheta: f32, f0: f32) -> f32 {\n return f0 + (1.0 - f0) * pow(1.0 - cosTheta, 5.0);\n}\n\nfn sampleShadow(worldPos: vec3f, n: vec3f) -> f32 {\n let biasedPos = worldPos + n * 0.08;\n let lclip = lightVP.viewProj * vec4f(biasedPos, 1.0);\n let ndc = lclip.xyz / max(lclip.w, 1e-6);\n let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);\n let cmpZ = ndc.z - 0.001;\n let ts = 1.0 / 4096.0;\n var vis = 0.0;\n for (var y = -1; y <= 1; y++) {\n for (var x = -1; x <= 1; x++) {\n vis += textureSampleCompare(shadowMap, shadowSampler, suv + vec2f(f32(x), f32(y)) * ts, cmpZ);\n }\n }\n return vis / 9.0;\n}\n\n@vertex fn vs(\n @location(0) position: vec3f,\n @location(1) normal: vec3f,\n @location(2) uv: vec2f,\n @location(3) joints0: vec4<u32>,\n @location(4) weights0: vec4<f32>\n) -> VertexOutput {\n var output: VertexOutput;\n let pos4 = vec4f(position, 1.0);\n let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;\n let invWeightSum = select(1.0, 1.0 / weightSum, weightSum > 0.0001);\n let nw = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 * invWeightSum, weightSum > 0.0001);\n var skinnedPos = vec4f(0.0);\n var skinnedNrm = vec3f(0.0);\n for (var i = 0u; i < 4u; i++) {\n let m = skinMats[joints0[i]];\n let w = nw[i];\n skinnedPos += (m * pos4) * w;\n skinnedNrm += (mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz) * normal) * w;\n }\n output.position = camera.projection * camera.view * vec4f(skinnedPos.xyz, 1.0);\n output.normal = normalize(skinnedNrm);\n output.uv = uv;\n output.worldPos = skinnedPos.xyz;\n return output;\n}\n\n@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {\n let alpha = material.alpha;\n if (alpha < 0.001) { discard; }\n\n let n = normalize(input.normal);\n let v = normalize(camera.viewPos - input.worldPos);\n let albedo = textureSample(diffuseTexture, diffuseSampler, input.uv).rgb;\n\n let l = -light.lights[0].direction.xyz;\n let sunColor = light.lights[0].color.xyz * light.lights[0].color.w;\n let h = normalize(l + v);\n\n let ndotl = max(dot(n, l), 0.0);\n let ndotv = max(dot(n, v), 0.001);\n let ndoth = max(dot(n, h), 0.0);\n let vdoth = max(dot(v, h), 0.0);\n\n let a2 = ROUGHNESS * ROUGHNESS;\n let D = ggx_d(ndoth, a2);\n let G = smith_g1(ndotl, a2) * smith_g1(ndotv, a2);\n let F = fresnel_schlick(vdoth, F0_DIELECTRIC);\n let spec = (D * G * F) / max(4.0 * ndotl * ndotv, 0.001);\n\n let shadow = sampleShadow(input.worldPos, n);\n let kd = (1.0 - F) * albedo / PI;\n let direct = (kd + spec) * sunColor * ndotl * shadow;\n let ambient = albedo * light.ambientColor.xyz;\n\n // Principled Emission socket: emissive = emission_color \u00D7 strength, added on top of shading.\n let emission = albedo * EYE_EMISSION_STRENGTH;\n\n return vec4f(ambient + direct + emission, alpha);\n}\n\n";
|
|
2
|
+
//# sourceMappingURL=eye.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"eye.d.ts","sourceRoot":"","sources":["../../src/shaders/eye.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,eAAe,2tIA0I3B,CAAA"}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
// Eye preset — default Principled BSDF (F0=0.04, Roughness=0.5) + Emission socket set to albedo × 1.5.
|
|
2
|
+
// Matches the published preset's instruction: "keep eyes in the default nodegraph, add emission 1.5".
|
|
3
|
+
// Blender's Principled BSDF Emission socket is added on top of the shaded output (pre-tonemap, feeds bloom).
|
|
4
|
+
export const EYE_SHADER_WGSL = /* wgsl */ `
|
|
5
|
+
|
|
6
|
+
const PI: f32 = 3.141592653589793;
|
|
7
|
+
const F0_DIELECTRIC: f32 = 0.04;
|
|
8
|
+
const ROUGHNESS: f32 = 0.5;
|
|
9
|
+
const EYE_EMISSION_STRENGTH: f32 = 1.5;
|
|
10
|
+
|
|
11
|
+
struct CameraUniforms {
|
|
12
|
+
view: mat4x4f,
|
|
13
|
+
projection: mat4x4f,
|
|
14
|
+
viewPos: vec3f,
|
|
15
|
+
_padding: f32,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
struct Light {
|
|
19
|
+
direction: vec4f,
|
|
20
|
+
color: vec4f,
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
struct LightUniforms {
|
|
24
|
+
ambientColor: vec4f,
|
|
25
|
+
lights: array<Light, 4>,
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
struct MaterialUniforms {
|
|
29
|
+
diffuseColor: vec3f,
|
|
30
|
+
alpha: f32,
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
struct VertexOutput {
|
|
34
|
+
@builtin(position) position: vec4f,
|
|
35
|
+
@location(0) normal: vec3f,
|
|
36
|
+
@location(1) uv: vec2f,
|
|
37
|
+
@location(2) worldPos: vec3f,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
struct LightVP { viewProj: mat4x4f, };
|
|
41
|
+
|
|
42
|
+
@group(0) @binding(0) var<uniform> camera: CameraUniforms;
|
|
43
|
+
@group(0) @binding(1) var<uniform> light: LightUniforms;
|
|
44
|
+
@group(0) @binding(2) var diffuseSampler: sampler;
|
|
45
|
+
@group(0) @binding(3) var shadowMap: texture_depth_2d;
|
|
46
|
+
@group(0) @binding(4) var shadowSampler: sampler_comparison;
|
|
47
|
+
@group(0) @binding(5) var<uniform> lightVP: LightVP;
|
|
48
|
+
@group(1) @binding(0) var<storage, read> skinMats: array<mat4x4f>;
|
|
49
|
+
@group(2) @binding(0) var diffuseTexture: texture_2d<f32>;
|
|
50
|
+
@group(2) @binding(1) var<uniform> material: MaterialUniforms;
|
|
51
|
+
|
|
52
|
+
fn ggx_d(ndoth: f32, a2: f32) -> f32 {
|
|
53
|
+
let denom = ndoth * ndoth * (a2 - 1.0) + 1.0;
|
|
54
|
+
return a2 / (PI * denom * denom);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
fn smith_g1(ndotx: f32, a2: f32) -> f32 {
|
|
58
|
+
return 2.0 * ndotx / (ndotx + sqrt(a2 + (1.0 - a2) * ndotx * ndotx));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
fn fresnel_schlick(cosTheta: f32, f0: f32) -> f32 {
|
|
62
|
+
return f0 + (1.0 - f0) * pow(1.0 - cosTheta, 5.0);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fn sampleShadow(worldPos: vec3f, n: vec3f) -> f32 {
|
|
66
|
+
let biasedPos = worldPos + n * 0.08;
|
|
67
|
+
let lclip = lightVP.viewProj * vec4f(biasedPos, 1.0);
|
|
68
|
+
let ndc = lclip.xyz / max(lclip.w, 1e-6);
|
|
69
|
+
let suv = vec2f(ndc.x * 0.5 + 0.5, 0.5 - ndc.y * 0.5);
|
|
70
|
+
let cmpZ = ndc.z - 0.001;
|
|
71
|
+
let ts = 1.0 / 4096.0;
|
|
72
|
+
var vis = 0.0;
|
|
73
|
+
for (var y = -1; y <= 1; y++) {
|
|
74
|
+
for (var x = -1; x <= 1; x++) {
|
|
75
|
+
vis += textureSampleCompare(shadowMap, shadowSampler, suv + vec2f(f32(x), f32(y)) * ts, cmpZ);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return vis / 9.0;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
@vertex fn vs(
|
|
82
|
+
@location(0) position: vec3f,
|
|
83
|
+
@location(1) normal: vec3f,
|
|
84
|
+
@location(2) uv: vec2f,
|
|
85
|
+
@location(3) joints0: vec4<u32>,
|
|
86
|
+
@location(4) weights0: vec4<f32>
|
|
87
|
+
) -> VertexOutput {
|
|
88
|
+
var output: VertexOutput;
|
|
89
|
+
let pos4 = vec4f(position, 1.0);
|
|
90
|
+
let weightSum = weights0.x + weights0.y + weights0.z + weights0.w;
|
|
91
|
+
let invWeightSum = select(1.0, 1.0 / weightSum, weightSum > 0.0001);
|
|
92
|
+
let nw = select(vec4f(1.0, 0.0, 0.0, 0.0), weights0 * invWeightSum, weightSum > 0.0001);
|
|
93
|
+
var skinnedPos = vec4f(0.0);
|
|
94
|
+
var skinnedNrm = vec3f(0.0);
|
|
95
|
+
for (var i = 0u; i < 4u; i++) {
|
|
96
|
+
let m = skinMats[joints0[i]];
|
|
97
|
+
let w = nw[i];
|
|
98
|
+
skinnedPos += (m * pos4) * w;
|
|
99
|
+
skinnedNrm += (mat3x3f(m[0].xyz, m[1].xyz, m[2].xyz) * normal) * w;
|
|
100
|
+
}
|
|
101
|
+
output.position = camera.projection * camera.view * vec4f(skinnedPos.xyz, 1.0);
|
|
102
|
+
output.normal = normalize(skinnedNrm);
|
|
103
|
+
output.uv = uv;
|
|
104
|
+
output.worldPos = skinnedPos.xyz;
|
|
105
|
+
return output;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
@fragment fn fs(input: VertexOutput) -> @location(0) vec4f {
|
|
109
|
+
let alpha = material.alpha;
|
|
110
|
+
if (alpha < 0.001) { discard; }
|
|
111
|
+
|
|
112
|
+
let n = normalize(input.normal);
|
|
113
|
+
let v = normalize(camera.viewPos - input.worldPos);
|
|
114
|
+
let albedo = textureSample(diffuseTexture, diffuseSampler, input.uv).rgb;
|
|
115
|
+
|
|
116
|
+
let l = -light.lights[0].direction.xyz;
|
|
117
|
+
let sunColor = light.lights[0].color.xyz * light.lights[0].color.w;
|
|
118
|
+
let h = normalize(l + v);
|
|
119
|
+
|
|
120
|
+
let ndotl = max(dot(n, l), 0.0);
|
|
121
|
+
let ndotv = max(dot(n, v), 0.001);
|
|
122
|
+
let ndoth = max(dot(n, h), 0.0);
|
|
123
|
+
let vdoth = max(dot(v, h), 0.0);
|
|
124
|
+
|
|
125
|
+
let a2 = ROUGHNESS * ROUGHNESS;
|
|
126
|
+
let D = ggx_d(ndoth, a2);
|
|
127
|
+
let G = smith_g1(ndotl, a2) * smith_g1(ndotv, a2);
|
|
128
|
+
let F = fresnel_schlick(vdoth, F0_DIELECTRIC);
|
|
129
|
+
let spec = (D * G * F) / max(4.0 * ndotl * ndotv, 0.001);
|
|
130
|
+
|
|
131
|
+
let shadow = sampleShadow(input.worldPos, n);
|
|
132
|
+
let kd = (1.0 - F) * albedo / PI;
|
|
133
|
+
let direct = (kd + spec) * sunColor * ndotl * shadow;
|
|
134
|
+
let ambient = albedo * light.ambientColor.xyz;
|
|
135
|
+
|
|
136
|
+
// Principled Emission socket: emissive = emission_color × strength, added on top of shading.
|
|
137
|
+
let emission = albedo * EYE_EMISSION_STRENGTH;
|
|
138
|
+
|
|
139
|
+
return vec4f(ambient + direct + emission, alpha);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
`;
|