@es3d/es3d 1.0.0 → 1.0.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/LICENSE +201 -201
- package/README.md +86 -86
- package/docs/SPEC.md +417 -417
- package/package.json +1 -1
- package/scripts/es3d-cli.mjs +377 -377
- package/src/cache/geometry-store.js +392 -392
- package/src/cache/mesh-bundle.js +123 -123
- package/src/cache/sha256.js +73 -73
- package/src/format/ast.js +238 -238
- package/src/format/colors.js +93 -93
- package/src/format/compact-parser.js +171 -171
- package/src/format/compact-tokenizer.js +165 -165
- package/src/format/errors.js +247 -247
- package/src/format/json.js +55 -55
- package/src/format/limits.js +14 -14
- package/src/format/parse.js +92 -92
- package/src/format/schema.js +617 -617
- package/src/format/serializer.js +333 -333
- package/src/format/validator.js +330 -330
- package/src/format/value-error.js +12 -12
- package/src/format/value-format.js +314 -314
- package/src/format/values.js +459 -459
- package/src/format/xml-parser.js +136 -136
- package/src/format/xml-tokenizer.js +237 -237
- package/src/geometry/contour.js +86 -86
- package/src/geometry/extrude.js +175 -175
- package/src/geometry/gear.js +85 -85
- package/src/geometry/geometry-cache.js +159 -159
- package/src/geometry/lod-error.js +178 -178
- package/src/geometry/mesh.js +458 -458
- package/src/geometry/primitives.js +299 -299
- package/src/geometry/registry.js +645 -645
- package/src/geometry/revolve.js +136 -136
- package/src/geometry/sweep.js +441 -441
- package/src/index.js +47 -47
- package/src/io/compression.js +67 -67
- package/src/io/convert.js +236 -236
- package/src/io/download.js +13 -13
- package/src/io/external-scene.js +127 -127
- package/src/io/file-loader.js +28 -28
- package/src/io/gltf.js +630 -630
- package/src/io/mesh-import.js +104 -104
- package/src/io/obj.js +352 -352
- package/src/io/step-brep.js +439 -439
- package/src/io/step-exact.js +1149 -1149
- package/src/io/step.js +1418 -1418
- package/src/io/stl.js +193 -193
- package/src/io/svg-import.js +787 -787
- package/src/material/materials.js +154 -154
- package/src/math/mat4.js +475 -475
- package/src/math/random.js +30 -30
- package/src/math/trig.js +65 -65
- package/src/math/vec3.js +81 -81
- package/src/optimize/optimizer.js +579 -579
- package/src/path/path-flatten.js +322 -322
- package/src/path/path-parser.js +332 -332
- package/src/path/shapes.js +129 -129
- package/src/path/triangulate.js +517 -517
- package/src/pipeline.js +74 -74
- package/src/render/camera.js +168 -168
- package/src/render/frame-builder.js +362 -362
- package/src/render/geometry-pool.js +149 -149
- package/src/render/lighting.js +149 -149
- package/src/render/orbit-controls.js +237 -237
- package/src/render/renderer.js +258 -249
- package/src/render/scroll-timeline.js +104 -104
- package/src/render/shaders/glsl.js +285 -285
- package/src/render/shaders/wgsl.js +252 -252
- package/src/render/stage.js +364 -352
- package/src/render/texture-cache.js +40 -40
- package/src/render/textures.js +223 -223
- package/src/render/webgl2/webgl2-backend.js +586 -532
- package/src/render/webgpu/webgpu-backend.js +482 -480
- package/src/scene/flatten.js +754 -754
- package/src/scene/picking.js +161 -161
- package/src/scene/scene-graph.js +320 -320
- package/src/transform/transform.js +211 -211
- package/src/wasm/kernels-binary.js +3 -3
- package/src/wasm/kernels.js +385 -385
|
@@ -1,252 +1,252 @@
|
|
|
1
|
-
// WGSL shaders for the WebGPU backend. They mirror shaders/glsl.js.
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
MAT_FLAG_MAP, MAT_FLAG_NORMAL_MAP, MAT_FLAG_UNLIT, MAT_FLAG_DOUBLE_SIDED, MAT_FLAG_MR_MAP,
|
|
5
|
-
MAT_FLAG_OCCLUSION, MAT_FLAG_EMISSIVE_MAP, MAT_FLAG_ALPHA_MASK,
|
|
6
|
-
} from '../lighting.js';
|
|
7
|
-
|
|
8
|
-
const STRUCTS = /* wgsl */ `
|
|
9
|
-
struct Frame {
|
|
10
|
-
viewProj: mat4x4f,
|
|
11
|
-
view: mat4x4f,
|
|
12
|
-
cameraPos: vec4f,
|
|
13
|
-
sky: vec4f,
|
|
14
|
-
ground: vec4f,
|
|
15
|
-
params: vec4f,
|
|
16
|
-
lightPos: array<vec4f, 16>,
|
|
17
|
-
lightDir: array<vec4f, 16>,
|
|
18
|
-
lightColor: array<vec4f, 16>,
|
|
19
|
-
lightExtra: array<vec4f, 16>,
|
|
20
|
-
};
|
|
21
|
-
struct Material {
|
|
22
|
-
baseColor: vec4f,
|
|
23
|
-
emissive: vec4f,
|
|
24
|
-
params: vec4f,
|
|
25
|
-
uvScale: vec4f,
|
|
26
|
-
overlay: vec4f,
|
|
27
|
-
};
|
|
28
|
-
@group(0) @binding(0) var<uniform> frame: Frame;
|
|
29
|
-
@group(1) @binding(0) var<uniform> material: Material;
|
|
30
|
-
@group(1) @binding(1) var samp: sampler;
|
|
31
|
-
@group(1) @binding(2) var baseTex: texture_2d<f32>;
|
|
32
|
-
@group(1) @binding(3) var normalTex: texture_2d<f32>;
|
|
33
|
-
@group(1) @binding(4) var mrTex: texture_2d<f32>;
|
|
34
|
-
@group(1) @binding(5) var occlusionTex: texture_2d<f32>;
|
|
35
|
-
@group(1) @binding(6) var emissiveTex: texture_2d<f32>;
|
|
36
|
-
|
|
37
|
-
struct VSIn {
|
|
38
|
-
@location(0) position: vec3f,
|
|
39
|
-
@location(1) normal: vec3f,
|
|
40
|
-
@location(2) uv: vec2f,
|
|
41
|
-
@location(3) m0: vec4f,
|
|
42
|
-
@location(4) m1: vec4f,
|
|
43
|
-
@location(5) m2: vec4f,
|
|
44
|
-
@location(6) m3: vec4f,
|
|
45
|
-
};
|
|
46
|
-
|
|
47
|
-
const PI = 3.14159265359;
|
|
48
|
-
|
|
49
|
-
fn linearToSrgb(c0: vec3f) -> vec3f {
|
|
50
|
-
let c = max(c0, vec3f(0.0));
|
|
51
|
-
return select(1.055 * pow(c, vec3f(1.0 / 2.4)) - 0.055, c * 12.92, c <= vec3f(0.0031308));
|
|
52
|
-
}
|
|
53
|
-
fn aces(x: vec3f) -> vec3f {
|
|
54
|
-
return clamp((x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14), vec3f(0.0), vec3f(1.0));
|
|
55
|
-
}
|
|
56
|
-
fn worldNormal(inp: VSIn) -> vec3f {
|
|
57
|
-
let c0 = inp.m0.xyz;
|
|
58
|
-
let c1 = inp.m1.xyz;
|
|
59
|
-
let c2 = inp.m2.xyz;
|
|
60
|
-
let cof = mat3x3f(cross(c1, c2), cross(c2, c0), cross(c0, c1));
|
|
61
|
-
let det = dot(c0, cross(c1, c2));
|
|
62
|
-
return cof * inp.normal * select(1.0, -1.0, det < 0.0);
|
|
63
|
-
}
|
|
64
|
-
`;
|
|
65
|
-
|
|
66
|
-
/** Vertex stage of the mesh modules; texture coordinates only for textured materials. */
|
|
67
|
-
const meshVertex = (textured) => /* wgsl */ `
|
|
68
|
-
struct VSOut {
|
|
69
|
-
@builtin(position) pos: vec4f,
|
|
70
|
-
@location(0) world: vec3f,
|
|
71
|
-
@location(1) normal: vec3f,
|
|
72
|
-
${textured ? '@location(2) uv: vec2f,' : ''}
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
@vertex
|
|
76
|
-
fn vs_main(inp: VSIn) -> VSOut {
|
|
77
|
-
let M = mat4x4f(inp.m0, inp.m1, inp.m2, inp.m3);
|
|
78
|
-
let wp = M * vec4f(inp.position, 1.0);
|
|
79
|
-
var out: VSOut;
|
|
80
|
-
out.pos = frame.viewProj * wp;
|
|
81
|
-
out.world = wp.xyz;
|
|
82
|
-
out.normal = worldNormal(inp);
|
|
83
|
-
${textured ? 'out.uv = inp.uv;' : ''}
|
|
84
|
-
return out;
|
|
85
|
-
}
|
|
86
|
-
`;
|
|
87
|
-
|
|
88
|
-
const BRDF = /* wgsl */ `
|
|
89
|
-
fn D_GGX(NdotH: f32, a: f32) -> f32 {
|
|
90
|
-
let a2 = a * a;
|
|
91
|
-
let d = NdotH * NdotH * (a2 - 1.0) + 1.0;
|
|
92
|
-
return a2 / (PI * d * d);
|
|
93
|
-
}
|
|
94
|
-
fn V_Smith(NdotV: f32, NdotL: f32, a: f32) -> f32 {
|
|
95
|
-
let a2 = a * a;
|
|
96
|
-
let gv = NdotL * sqrt(NdotV * NdotV * (1.0 - a2) + a2);
|
|
97
|
-
let gl = NdotV * sqrt(NdotL * NdotL * (1.0 - a2) + a2);
|
|
98
|
-
return 0.5 / max(gv + gl, 1e-5);
|
|
99
|
-
}
|
|
100
|
-
fn F_Schlick(f0: vec3f, VdotH: f32) -> vec3f {
|
|
101
|
-
return f0 + (vec3f(1.0) - f0) * pow(1.0 - VdotH, 5.0);
|
|
102
|
-
}
|
|
103
|
-
fn envBRDF(f0: vec3f, roughness: f32, NdotV: f32) -> vec3f {
|
|
104
|
-
let c0 = vec4f(-1.0, -0.0275, -0.572, 0.022);
|
|
105
|
-
let c1 = vec4f(1.0, 0.0425, 1.04, -0.04);
|
|
106
|
-
let r = roughness * c0 + c1;
|
|
107
|
-
let a004 = min(r.x * r.x, exp2(-9.28 * NdotV)) * r.x + r.y;
|
|
108
|
-
let AB = vec2f(-1.04, 1.04) * a004 + r.zw;
|
|
109
|
-
return f0 * AB.x + AB.y;
|
|
110
|
-
}
|
|
111
|
-
// one light's contribution, zero when it is behind the surface
|
|
112
|
-
fn shade(N: vec3f, V: vec3f, L: vec3f, radiance: vec3f, NdotV: f32, f0: vec3f, diffuse: vec3f, a: f32) -> vec3f {
|
|
113
|
-
let NdotL = dot(N, L);
|
|
114
|
-
if (NdotL <= 0.0) {
|
|
115
|
-
return vec3f(0.0);
|
|
116
|
-
}
|
|
117
|
-
let H = normalize(V + L);
|
|
118
|
-
let NdotH = max(dot(N, H), 0.0);
|
|
119
|
-
let VdotH = max(dot(V, H), 0.0);
|
|
120
|
-
let F = F_Schlick(f0, VdotH);
|
|
121
|
-
let spec = D_GGX(NdotH, a) * V_Smith(NdotV, NdotL, a) * F;
|
|
122
|
-
let kd = (vec3f(1.0) - F) * diffuse / PI;
|
|
123
|
-
return (kd + spec) * radiance * NdotL;
|
|
124
|
-
}
|
|
125
|
-
`;
|
|
126
|
-
|
|
127
|
-
/**
|
|
128
|
-
* Shader module for one material feature set (MAT_FLAG_* bits) and one light
|
|
129
|
-
* setup (lightTypes() of the frame), as meshFragmentShader() in
|
|
130
|
-
* glsl.js: everything the material does not use is compiled out, and the
|
|
131
|
-
* light loop is unrolled. Textures are sampled in uniform control flow (the
|
|
132
|
-
* double-sided normal flip is a select, not a branch); after an alpha-mask
|
|
133
|
-
* discard, lookups use explicit gradients taken before it.
|
|
134
|
-
*/
|
|
135
|
-
export function meshShader(flags, lights) {
|
|
136
|
-
const has = (f) => (flags & f) !== 0;
|
|
137
|
-
const map = has(MAT_FLAG_MAP), normalMap = has(MAT_FLAG_NORMAL_MAP), mrMap = has(MAT_FLAG_MR_MAP);
|
|
138
|
-
const occlusion = has(MAT_FLAG_OCCLUSION), emissiveMap = has(MAT_FLAG_EMISSIVE_MAP);
|
|
139
|
-
const textured = map || normalMap || mrMap || occlusion || emissiveMap;
|
|
140
|
-
const grad = has(MAT_FLAG_ALPHA_MASK);
|
|
141
|
-
const sample = (tex) => (grad ? `textureSampleGrad(${tex}, samp, uv, ddx, ddy)` : `textureSample(${tex}, samp, uv)`);
|
|
142
|
-
const L = [STRUCTS, meshVertex(textured)];
|
|
143
|
-
if (!has(MAT_FLAG_UNLIT)) L.push(BRDF);
|
|
144
|
-
L.push('@fragment', 'fn fs_main(inp: VSOut, @builtin(front_facing) frontFacing: bool) -> @location(0) vec4f {');
|
|
145
|
-
if (textured) L.push(' let uv = inp.uv * material.uvScale.xy;');
|
|
146
|
-
if ((textured && grad) || normalMap) L.push(' let ddx = dpdx(uv);', ' let ddy = dpdy(uv);');
|
|
147
|
-
if (normalMap) {
|
|
148
|
-
// WGSL derivatives use framebuffer y (down); negate dpdy to match GLSL's
|
|
149
|
-
// window-space dFdy (up) so the cotangent frame has the same handedness
|
|
150
|
-
L.push(' let dp1 = dpdx(inp.world);', ' let dp2 = -dpdy(inp.world);', ' let duv1 = ddx;', ' let duv2 = -ddy;');
|
|
151
|
-
}
|
|
152
|
-
L.push(' var base = material.baseColor;');
|
|
153
|
-
if (map) L.push(` base = base * ${sample('baseTex')};`);
|
|
154
|
-
if (grad) L.push(' if (base.a < material.params.w) {', ' discard;', ' }');
|
|
155
|
-
L.push(' var emitted = material.emissive.rgb;');
|
|
156
|
-
if (emissiveMap) L.push(` emitted = emitted * ${sample('emissiveTex')}.rgb;`);
|
|
157
|
-
if (has(MAT_FLAG_UNLIT)) {
|
|
158
|
-
L.push(' return vec4f(linearToSrgb(base.rgb + emitted), base.a);', '}');
|
|
159
|
-
return L.join('\n');
|
|
160
|
-
}
|
|
161
|
-
L.push(' var N = normalize(inp.normal);');
|
|
162
|
-
if (has(MAT_FLAG_DOUBLE_SIDED)) L.push(' N = select(-N, N, frontFacing);');
|
|
163
|
-
if (normalMap) {
|
|
164
|
-
L.push(` let dp2perp = cross(dp2, N);
|
|
165
|
-
let dp1perp = cross(N, dp1);
|
|
166
|
-
let T = dp2perp * duv1.x + dp1perp * duv2.x;
|
|
167
|
-
let B = dp2perp * duv1.y + dp1perp * duv2.y;
|
|
168
|
-
let invmax = inverseSqrt(max(max(dot(T, T), dot(B, B)), 1e-20));
|
|
169
|
-
let n0 = ${sample('normalTex')}.xyz * 2.0 - 1.0;
|
|
170
|
-
let n = vec3f(n0.xy * material.emissive.w, n0.z);
|
|
171
|
-
N = normalize(mat3x3f(T * invmax, B * invmax, N) * n);`);
|
|
172
|
-
}
|
|
173
|
-
L.push(` let V = normalize(frame.cameraPos.xyz - inp.world);
|
|
174
|
-
let NdotV = max(dot(N, V), 1e-4);
|
|
175
|
-
var rough = clamp(material.params.y, 0.045, 1.0);
|
|
176
|
-
var metal = clamp(material.params.x, 0.0, 1.0);`);
|
|
177
|
-
if (mrMap) {
|
|
178
|
-
L.push(` let mr = ${sample('mrTex')};
|
|
179
|
-
rough = clamp(rough * mr.g, 0.045, 1.0);
|
|
180
|
-
metal = clamp(metal * mr.b, 0.0, 1.0);`);
|
|
181
|
-
}
|
|
182
|
-
L.push(` let a = rough * rough;
|
|
183
|
-
let f0 = mix(vec3f(0.04), base.rgb, metal);
|
|
184
|
-
let diffuse = base.rgb * (1.0 - metal);
|
|
185
|
-
var color = vec3f(0.0);
|
|
186
|
-
var ambient = vec3f(0.0);`);
|
|
187
|
-
if (lights) lights.split(',').forEach((type, i) => L.push(lightTerm(Number(type), i)));
|
|
188
|
-
L.push(` let envI = frame.sky.w;
|
|
189
|
-
var hemi = mix(frame.ground.rgb, frame.sky.rgb, N.y * 0.5 + 0.5) * envI + ambient;
|
|
190
|
-
let R = reflect(-V, N);
|
|
191
|
-
var envSpec = mix(frame.ground.rgb, frame.sky.rgb, smoothstep(-0.25 - rough, 0.25 + rough, R.y)) * envI;
|
|
192
|
-
envSpec = mix(envSpec, hemi, rough * rough);`);
|
|
193
|
-
if (occlusion) {
|
|
194
|
-
L.push(` let ao = mix(1.0, ${sample('occlusionTex')}.r, material.uvScale.z);
|
|
195
|
-
hemi = hemi * ao;
|
|
196
|
-
envSpec = envSpec * ao;`);
|
|
197
|
-
}
|
|
198
|
-
L.push(` color = color + diffuse * hemi + envSpec * envBRDF(f0, rough, NdotV);
|
|
199
|
-
color = color + emitted;
|
|
200
|
-
color = color * frame.ground.w;
|
|
201
|
-
return vec4f(linearToSrgb(aces(color)), base.a);
|
|
202
|
-
}`);
|
|
203
|
-
return L.join('\n');
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/** WGSL for light i of a known type (lighting.js: 0 directional, 1 point, 2 spot, 3 ambient, 4 hemisphere). */
|
|
207
|
-
function lightTerm(type, i) {
|
|
208
|
-
if (type === 3) return ` ambient = ambient + frame.lightColor[${i}].rgb;`;
|
|
209
|
-
if (type === 4) return ` ambient = ambient + mix(frame.lightExtra[${i}].yzw, frame.lightColor[${i}].rgb, N.y * 0.5 + 0.5);`;
|
|
210
|
-
if (type === 0) return ` color = color + shade(N, V, -frame.lightDir[${i}].xyz, frame.lightColor[${i}].rgb * PI, NdotV, f0, diffuse, a);`;
|
|
211
|
-
return ` {
|
|
212
|
-
let Lv = frame.lightPos[${i}].xyz - inp.world;
|
|
213
|
-
let d = length(Lv);
|
|
214
|
-
let L = Lv / max(d, 1e-5);
|
|
215
|
-
var att = 1.0 / max(d * d, 1e-4);
|
|
216
|
-
let range = frame.lightDir[${i}].w;
|
|
217
|
-
if (range > 0.0) {
|
|
218
|
-
let x = d / range;
|
|
219
|
-
let w = clamp(1.0 - x * x * x * x, 0.0, 1.0);
|
|
220
|
-
att = att * w * w;
|
|
221
|
-
}${type === 2 ? `\n att = att * smoothstep(frame.lightColor[${i}].w, frame.lightExtra[${i}].x, dot(-L, frame.lightDir[${i}].xyz));` : ''}
|
|
222
|
-
color = color + shade(N, V, L, frame.lightColor[${i}].rgb * PI * att, NdotV, f0, diffuse, a);
|
|
223
|
-
}`;
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
export const LINE_WGSL = /* wgsl */ `
|
|
227
|
-
${STRUCTS}
|
|
228
|
-
@vertex
|
|
229
|
-
fn vs_line(inp: VSIn) -> @builtin(position) vec4f {
|
|
230
|
-
let M = mat4x4f(inp.m0, inp.m1, inp.m2, inp.m3);
|
|
231
|
-
let wp = M * vec4f(inp.position, 1.0);
|
|
232
|
-
return frame.viewProj * vec4f(mix(wp.xyz, frame.cameraPos.xyz, frame.params.z), 1.0);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
@vertex
|
|
236
|
-
fn vs_normals(inp: VSIn) -> @builtin(position) vec4f {
|
|
237
|
-
let M = mat4x4f(inp.m0, inp.m1, inp.m2, inp.m3);
|
|
238
|
-
var wp = M * vec4f(inp.position, 1.0);
|
|
239
|
-
let n = normalize(worldNormal(inp));
|
|
240
|
-
wp = vec4f(wp.xyz + n * inp.uv.x * frame.params.y, 1.0);
|
|
241
|
-
return frame.viewProj * vec4f(mix(wp.xyz, frame.cameraPos.xyz, frame.params.z), 1.0);
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
@fragment
|
|
245
|
-
fn fs_line() -> @location(0) vec4f {
|
|
246
|
-
let flags = u32(material.params.z + 0.5);
|
|
247
|
-
if ((flags & 16u) != 0u) {
|
|
248
|
-
return material.overlay;
|
|
249
|
-
}
|
|
250
|
-
return vec4f(linearToSrgb(material.baseColor.rgb + material.emissive.rgb), material.baseColor.a);
|
|
251
|
-
}
|
|
252
|
-
`;
|
|
1
|
+
// WGSL shaders for the WebGPU backend. They mirror shaders/glsl.js.
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
MAT_FLAG_MAP, MAT_FLAG_NORMAL_MAP, MAT_FLAG_UNLIT, MAT_FLAG_DOUBLE_SIDED, MAT_FLAG_MR_MAP,
|
|
5
|
+
MAT_FLAG_OCCLUSION, MAT_FLAG_EMISSIVE_MAP, MAT_FLAG_ALPHA_MASK,
|
|
6
|
+
} from '../lighting.js';
|
|
7
|
+
|
|
8
|
+
const STRUCTS = /* wgsl */ `
|
|
9
|
+
struct Frame {
|
|
10
|
+
viewProj: mat4x4f,
|
|
11
|
+
view: mat4x4f,
|
|
12
|
+
cameraPos: vec4f,
|
|
13
|
+
sky: vec4f,
|
|
14
|
+
ground: vec4f,
|
|
15
|
+
params: vec4f,
|
|
16
|
+
lightPos: array<vec4f, 16>,
|
|
17
|
+
lightDir: array<vec4f, 16>,
|
|
18
|
+
lightColor: array<vec4f, 16>,
|
|
19
|
+
lightExtra: array<vec4f, 16>,
|
|
20
|
+
};
|
|
21
|
+
struct Material {
|
|
22
|
+
baseColor: vec4f,
|
|
23
|
+
emissive: vec4f,
|
|
24
|
+
params: vec4f,
|
|
25
|
+
uvScale: vec4f,
|
|
26
|
+
overlay: vec4f,
|
|
27
|
+
};
|
|
28
|
+
@group(0) @binding(0) var<uniform> frame: Frame;
|
|
29
|
+
@group(1) @binding(0) var<uniform> material: Material;
|
|
30
|
+
@group(1) @binding(1) var samp: sampler;
|
|
31
|
+
@group(1) @binding(2) var baseTex: texture_2d<f32>;
|
|
32
|
+
@group(1) @binding(3) var normalTex: texture_2d<f32>;
|
|
33
|
+
@group(1) @binding(4) var mrTex: texture_2d<f32>;
|
|
34
|
+
@group(1) @binding(5) var occlusionTex: texture_2d<f32>;
|
|
35
|
+
@group(1) @binding(6) var emissiveTex: texture_2d<f32>;
|
|
36
|
+
|
|
37
|
+
struct VSIn {
|
|
38
|
+
@location(0) position: vec3f,
|
|
39
|
+
@location(1) normal: vec3f,
|
|
40
|
+
@location(2) uv: vec2f,
|
|
41
|
+
@location(3) m0: vec4f,
|
|
42
|
+
@location(4) m1: vec4f,
|
|
43
|
+
@location(5) m2: vec4f,
|
|
44
|
+
@location(6) m3: vec4f,
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const PI = 3.14159265359;
|
|
48
|
+
|
|
49
|
+
fn linearToSrgb(c0: vec3f) -> vec3f {
|
|
50
|
+
let c = max(c0, vec3f(0.0));
|
|
51
|
+
return select(1.055 * pow(c, vec3f(1.0 / 2.4)) - 0.055, c * 12.92, c <= vec3f(0.0031308));
|
|
52
|
+
}
|
|
53
|
+
fn aces(x: vec3f) -> vec3f {
|
|
54
|
+
return clamp((x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14), vec3f(0.0), vec3f(1.0));
|
|
55
|
+
}
|
|
56
|
+
fn worldNormal(inp: VSIn) -> vec3f {
|
|
57
|
+
let c0 = inp.m0.xyz;
|
|
58
|
+
let c1 = inp.m1.xyz;
|
|
59
|
+
let c2 = inp.m2.xyz;
|
|
60
|
+
let cof = mat3x3f(cross(c1, c2), cross(c2, c0), cross(c0, c1));
|
|
61
|
+
let det = dot(c0, cross(c1, c2));
|
|
62
|
+
return cof * inp.normal * select(1.0, -1.0, det < 0.0);
|
|
63
|
+
}
|
|
64
|
+
`;
|
|
65
|
+
|
|
66
|
+
/** Vertex stage of the mesh modules; texture coordinates only for textured materials. */
|
|
67
|
+
const meshVertex = (textured) => /* wgsl */ `
|
|
68
|
+
struct VSOut {
|
|
69
|
+
@builtin(position) pos: vec4f,
|
|
70
|
+
@location(0) world: vec3f,
|
|
71
|
+
@location(1) normal: vec3f,
|
|
72
|
+
${textured ? '@location(2) uv: vec2f,' : ''}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
@vertex
|
|
76
|
+
fn vs_main(inp: VSIn) -> VSOut {
|
|
77
|
+
let M = mat4x4f(inp.m0, inp.m1, inp.m2, inp.m3);
|
|
78
|
+
let wp = M * vec4f(inp.position, 1.0);
|
|
79
|
+
var out: VSOut;
|
|
80
|
+
out.pos = frame.viewProj * wp;
|
|
81
|
+
out.world = wp.xyz;
|
|
82
|
+
out.normal = worldNormal(inp);
|
|
83
|
+
${textured ? 'out.uv = inp.uv;' : ''}
|
|
84
|
+
return out;
|
|
85
|
+
}
|
|
86
|
+
`;
|
|
87
|
+
|
|
88
|
+
const BRDF = /* wgsl */ `
|
|
89
|
+
fn D_GGX(NdotH: f32, a: f32) -> f32 {
|
|
90
|
+
let a2 = a * a;
|
|
91
|
+
let d = NdotH * NdotH * (a2 - 1.0) + 1.0;
|
|
92
|
+
return a2 / (PI * d * d);
|
|
93
|
+
}
|
|
94
|
+
fn V_Smith(NdotV: f32, NdotL: f32, a: f32) -> f32 {
|
|
95
|
+
let a2 = a * a;
|
|
96
|
+
let gv = NdotL * sqrt(NdotV * NdotV * (1.0 - a2) + a2);
|
|
97
|
+
let gl = NdotV * sqrt(NdotL * NdotL * (1.0 - a2) + a2);
|
|
98
|
+
return 0.5 / max(gv + gl, 1e-5);
|
|
99
|
+
}
|
|
100
|
+
fn F_Schlick(f0: vec3f, VdotH: f32) -> vec3f {
|
|
101
|
+
return f0 + (vec3f(1.0) - f0) * pow(1.0 - VdotH, 5.0);
|
|
102
|
+
}
|
|
103
|
+
fn envBRDF(f0: vec3f, roughness: f32, NdotV: f32) -> vec3f {
|
|
104
|
+
let c0 = vec4f(-1.0, -0.0275, -0.572, 0.022);
|
|
105
|
+
let c1 = vec4f(1.0, 0.0425, 1.04, -0.04);
|
|
106
|
+
let r = roughness * c0 + c1;
|
|
107
|
+
let a004 = min(r.x * r.x, exp2(-9.28 * NdotV)) * r.x + r.y;
|
|
108
|
+
let AB = vec2f(-1.04, 1.04) * a004 + r.zw;
|
|
109
|
+
return f0 * AB.x + AB.y;
|
|
110
|
+
}
|
|
111
|
+
// one light's contribution, zero when it is behind the surface
|
|
112
|
+
fn shade(N: vec3f, V: vec3f, L: vec3f, radiance: vec3f, NdotV: f32, f0: vec3f, diffuse: vec3f, a: f32) -> vec3f {
|
|
113
|
+
let NdotL = dot(N, L);
|
|
114
|
+
if (NdotL <= 0.0) {
|
|
115
|
+
return vec3f(0.0);
|
|
116
|
+
}
|
|
117
|
+
let H = normalize(V + L);
|
|
118
|
+
let NdotH = max(dot(N, H), 0.0);
|
|
119
|
+
let VdotH = max(dot(V, H), 0.0);
|
|
120
|
+
let F = F_Schlick(f0, VdotH);
|
|
121
|
+
let spec = D_GGX(NdotH, a) * V_Smith(NdotV, NdotL, a) * F;
|
|
122
|
+
let kd = (vec3f(1.0) - F) * diffuse / PI;
|
|
123
|
+
return (kd + spec) * radiance * NdotL;
|
|
124
|
+
}
|
|
125
|
+
`;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Shader module for one material feature set (MAT_FLAG_* bits) and one light
|
|
129
|
+
* setup (lightTypes() of the frame), as meshFragmentShader() in
|
|
130
|
+
* glsl.js: everything the material does not use is compiled out, and the
|
|
131
|
+
* light loop is unrolled. Textures are sampled in uniform control flow (the
|
|
132
|
+
* double-sided normal flip is a select, not a branch); after an alpha-mask
|
|
133
|
+
* discard, lookups use explicit gradients taken before it.
|
|
134
|
+
*/
|
|
135
|
+
export function meshShader(flags, lights) {
|
|
136
|
+
const has = (f) => (flags & f) !== 0;
|
|
137
|
+
const map = has(MAT_FLAG_MAP), normalMap = has(MAT_FLAG_NORMAL_MAP), mrMap = has(MAT_FLAG_MR_MAP);
|
|
138
|
+
const occlusion = has(MAT_FLAG_OCCLUSION), emissiveMap = has(MAT_FLAG_EMISSIVE_MAP);
|
|
139
|
+
const textured = map || normalMap || mrMap || occlusion || emissiveMap;
|
|
140
|
+
const grad = has(MAT_FLAG_ALPHA_MASK);
|
|
141
|
+
const sample = (tex) => (grad ? `textureSampleGrad(${tex}, samp, uv, ddx, ddy)` : `textureSample(${tex}, samp, uv)`);
|
|
142
|
+
const L = [STRUCTS, meshVertex(textured)];
|
|
143
|
+
if (!has(MAT_FLAG_UNLIT)) L.push(BRDF);
|
|
144
|
+
L.push('@fragment', 'fn fs_main(inp: VSOut, @builtin(front_facing) frontFacing: bool) -> @location(0) vec4f {');
|
|
145
|
+
if (textured) L.push(' let uv = inp.uv * material.uvScale.xy;');
|
|
146
|
+
if ((textured && grad) || normalMap) L.push(' let ddx = dpdx(uv);', ' let ddy = dpdy(uv);');
|
|
147
|
+
if (normalMap) {
|
|
148
|
+
// WGSL derivatives use framebuffer y (down); negate dpdy to match GLSL's
|
|
149
|
+
// window-space dFdy (up) so the cotangent frame has the same handedness
|
|
150
|
+
L.push(' let dp1 = dpdx(inp.world);', ' let dp2 = -dpdy(inp.world);', ' let duv1 = ddx;', ' let duv2 = -ddy;');
|
|
151
|
+
}
|
|
152
|
+
L.push(' var base = material.baseColor;');
|
|
153
|
+
if (map) L.push(` base = base * ${sample('baseTex')};`);
|
|
154
|
+
if (grad) L.push(' if (base.a < material.params.w) {', ' discard;', ' }');
|
|
155
|
+
L.push(' var emitted = material.emissive.rgb;');
|
|
156
|
+
if (emissiveMap) L.push(` emitted = emitted * ${sample('emissiveTex')}.rgb;`);
|
|
157
|
+
if (has(MAT_FLAG_UNLIT)) {
|
|
158
|
+
L.push(' return vec4f(linearToSrgb(base.rgb + emitted), base.a);', '}');
|
|
159
|
+
return L.join('\n');
|
|
160
|
+
}
|
|
161
|
+
L.push(' var N = normalize(inp.normal);');
|
|
162
|
+
if (has(MAT_FLAG_DOUBLE_SIDED)) L.push(' N = select(-N, N, frontFacing);');
|
|
163
|
+
if (normalMap) {
|
|
164
|
+
L.push(` let dp2perp = cross(dp2, N);
|
|
165
|
+
let dp1perp = cross(N, dp1);
|
|
166
|
+
let T = dp2perp * duv1.x + dp1perp * duv2.x;
|
|
167
|
+
let B = dp2perp * duv1.y + dp1perp * duv2.y;
|
|
168
|
+
let invmax = inverseSqrt(max(max(dot(T, T), dot(B, B)), 1e-20));
|
|
169
|
+
let n0 = ${sample('normalTex')}.xyz * 2.0 - 1.0;
|
|
170
|
+
let n = vec3f(n0.xy * material.emissive.w, n0.z);
|
|
171
|
+
N = normalize(mat3x3f(T * invmax, B * invmax, N) * n);`);
|
|
172
|
+
}
|
|
173
|
+
L.push(` let V = normalize(frame.cameraPos.xyz - inp.world);
|
|
174
|
+
let NdotV = max(dot(N, V), 1e-4);
|
|
175
|
+
var rough = clamp(material.params.y, 0.045, 1.0);
|
|
176
|
+
var metal = clamp(material.params.x, 0.0, 1.0);`);
|
|
177
|
+
if (mrMap) {
|
|
178
|
+
L.push(` let mr = ${sample('mrTex')};
|
|
179
|
+
rough = clamp(rough * mr.g, 0.045, 1.0);
|
|
180
|
+
metal = clamp(metal * mr.b, 0.0, 1.0);`);
|
|
181
|
+
}
|
|
182
|
+
L.push(` let a = rough * rough;
|
|
183
|
+
let f0 = mix(vec3f(0.04), base.rgb, metal);
|
|
184
|
+
let diffuse = base.rgb * (1.0 - metal);
|
|
185
|
+
var color = vec3f(0.0);
|
|
186
|
+
var ambient = vec3f(0.0);`);
|
|
187
|
+
if (lights) lights.split(',').forEach((type, i) => L.push(lightTerm(Number(type), i)));
|
|
188
|
+
L.push(` let envI = frame.sky.w;
|
|
189
|
+
var hemi = mix(frame.ground.rgb, frame.sky.rgb, N.y * 0.5 + 0.5) * envI + ambient;
|
|
190
|
+
let R = reflect(-V, N);
|
|
191
|
+
var envSpec = mix(frame.ground.rgb, frame.sky.rgb, smoothstep(-0.25 - rough, 0.25 + rough, R.y)) * envI;
|
|
192
|
+
envSpec = mix(envSpec, hemi, rough * rough);`);
|
|
193
|
+
if (occlusion) {
|
|
194
|
+
L.push(` let ao = mix(1.0, ${sample('occlusionTex')}.r, material.uvScale.z);
|
|
195
|
+
hemi = hemi * ao;
|
|
196
|
+
envSpec = envSpec * ao;`);
|
|
197
|
+
}
|
|
198
|
+
L.push(` color = color + diffuse * hemi + envSpec * envBRDF(f0, rough, NdotV);
|
|
199
|
+
color = color + emitted;
|
|
200
|
+
color = color * frame.ground.w;
|
|
201
|
+
return vec4f(linearToSrgb(aces(color)), base.a);
|
|
202
|
+
}`);
|
|
203
|
+
return L.join('\n');
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/** WGSL for light i of a known type (lighting.js: 0 directional, 1 point, 2 spot, 3 ambient, 4 hemisphere). */
|
|
207
|
+
function lightTerm(type, i) {
|
|
208
|
+
if (type === 3) return ` ambient = ambient + frame.lightColor[${i}].rgb;`;
|
|
209
|
+
if (type === 4) return ` ambient = ambient + mix(frame.lightExtra[${i}].yzw, frame.lightColor[${i}].rgb, N.y * 0.5 + 0.5);`;
|
|
210
|
+
if (type === 0) return ` color = color + shade(N, V, -frame.lightDir[${i}].xyz, frame.lightColor[${i}].rgb * PI, NdotV, f0, diffuse, a);`;
|
|
211
|
+
return ` {
|
|
212
|
+
let Lv = frame.lightPos[${i}].xyz - inp.world;
|
|
213
|
+
let d = length(Lv);
|
|
214
|
+
let L = Lv / max(d, 1e-5);
|
|
215
|
+
var att = 1.0 / max(d * d, 1e-4);
|
|
216
|
+
let range = frame.lightDir[${i}].w;
|
|
217
|
+
if (range > 0.0) {
|
|
218
|
+
let x = d / range;
|
|
219
|
+
let w = clamp(1.0 - x * x * x * x, 0.0, 1.0);
|
|
220
|
+
att = att * w * w;
|
|
221
|
+
}${type === 2 ? `\n att = att * smoothstep(frame.lightColor[${i}].w, frame.lightExtra[${i}].x, dot(-L, frame.lightDir[${i}].xyz));` : ''}
|
|
222
|
+
color = color + shade(N, V, L, frame.lightColor[${i}].rgb * PI * att, NdotV, f0, diffuse, a);
|
|
223
|
+
}`;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export const LINE_WGSL = /* wgsl */ `
|
|
227
|
+
${STRUCTS}
|
|
228
|
+
@vertex
|
|
229
|
+
fn vs_line(inp: VSIn) -> @builtin(position) vec4f {
|
|
230
|
+
let M = mat4x4f(inp.m0, inp.m1, inp.m2, inp.m3);
|
|
231
|
+
let wp = M * vec4f(inp.position, 1.0);
|
|
232
|
+
return frame.viewProj * vec4f(mix(wp.xyz, frame.cameraPos.xyz, frame.params.z), 1.0);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
@vertex
|
|
236
|
+
fn vs_normals(inp: VSIn) -> @builtin(position) vec4f {
|
|
237
|
+
let M = mat4x4f(inp.m0, inp.m1, inp.m2, inp.m3);
|
|
238
|
+
var wp = M * vec4f(inp.position, 1.0);
|
|
239
|
+
let n = normalize(worldNormal(inp));
|
|
240
|
+
wp = vec4f(wp.xyz + n * inp.uv.x * frame.params.y, 1.0);
|
|
241
|
+
return frame.viewProj * vec4f(mix(wp.xyz, frame.cameraPos.xyz, frame.params.z), 1.0);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
@fragment
|
|
245
|
+
fn fs_line() -> @location(0) vec4f {
|
|
246
|
+
let flags = u32(material.params.z + 0.5);
|
|
247
|
+
if ((flags & 16u) != 0u) {
|
|
248
|
+
return material.overlay;
|
|
249
|
+
}
|
|
250
|
+
return vec4f(linearToSrgb(material.baseColor.rgb + material.emissive.rgb), material.baseColor.a);
|
|
251
|
+
}
|
|
252
|
+
`;
|