@es3d/es3d 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -201
- package/README.md +95 -86
- package/docs/SPEC.md +426 -417
- package/package.json +1 -1
- package/scripts/es3d-cli.mjs +390 -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 +132 -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-pack.js +1461 -0
- 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 +604 -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 +365 -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,285 +1,285 @@
|
|
|
1
|
-
// GLSL ES 3.00 shaders for the WebGL2 backend. They mirror shaders/wgsl.js.
|
|
2
|
-
//
|
|
3
|
-
// Instancing: the per-instance world matrix arrives as four vec4 attributes.
|
|
4
|
-
// Normals are transformed with the cofactor matrix of the instance matrix
|
|
5
|
-
// (proportional to the inverse transpose), so no normal matrix is uploaded and
|
|
6
|
-
// non-uniform and mirrored scales are handled correctly.
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
MAT_FLAG_MAP, MAT_FLAG_NORMAL_MAP, MAT_FLAG_UNLIT, MAT_FLAG_DOUBLE_SIDED, MAT_FLAG_MR_MAP,
|
|
10
|
-
MAT_FLAG_OCCLUSION, MAT_FLAG_EMISSIVE_MAP, MAT_FLAG_ALPHA_MASK,
|
|
11
|
-
} from '../lighting.js';
|
|
12
|
-
|
|
13
|
-
const FRAME_BLOCK = /* glsl */ `
|
|
14
|
-
layout(std140) uniform Frame {
|
|
15
|
-
mat4 uViewProj;
|
|
16
|
-
mat4 uView;
|
|
17
|
-
vec4 uCameraPos;
|
|
18
|
-
vec4 uSky;
|
|
19
|
-
vec4 uGround;
|
|
20
|
-
vec4 uParams;
|
|
21
|
-
vec4 uLightPos[16];
|
|
22
|
-
vec4 uLightDir[16];
|
|
23
|
-
vec4 uLightColor[16];
|
|
24
|
-
vec4 uLightExtra[16];
|
|
25
|
-
};
|
|
26
|
-
`;
|
|
27
|
-
|
|
28
|
-
const MATERIAL_BLOCK = /* glsl */ `
|
|
29
|
-
layout(std140) uniform Material {
|
|
30
|
-
vec4 mBaseColor;
|
|
31
|
-
vec4 mEmissive;
|
|
32
|
-
vec4 mParams;
|
|
33
|
-
vec4 mUv;
|
|
34
|
-
vec4 mOverlay;
|
|
35
|
-
};
|
|
36
|
-
`;
|
|
37
|
-
|
|
38
|
-
const INSTANCE_INPUTS = /* glsl */ `
|
|
39
|
-
layout(location = 0) in vec3 aPosition;
|
|
40
|
-
layout(location = 1) in vec3 aNormal;
|
|
41
|
-
layout(location = 2) in vec2 aUv;
|
|
42
|
-
layout(location = 3) in vec4 iM0;
|
|
43
|
-
layout(location = 4) in vec4 iM1;
|
|
44
|
-
layout(location = 5) in vec4 iM2;
|
|
45
|
-
layout(location = 6) in vec4 iM3;
|
|
46
|
-
`;
|
|
47
|
-
|
|
48
|
-
const COMMON_FS = /* glsl */ `
|
|
49
|
-
const float PI = 3.14159265359;
|
|
50
|
-
vec3 linearToSrgb(vec3 c) {
|
|
51
|
-
c = max(c, vec3(0.0));
|
|
52
|
-
return mix(c * 12.92, 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, step(vec3(0.0031308), c));
|
|
53
|
-
}
|
|
54
|
-
vec3 aces(vec3 x) {
|
|
55
|
-
return clamp((x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14), 0.0, 1.0);
|
|
56
|
-
}
|
|
57
|
-
`;
|
|
58
|
-
|
|
59
|
-
/** Vertex shader of the mesh programs; texture coordinates only for textured materials. */
|
|
60
|
-
export const meshVertexShader = (textured) => /* glsl */ `#version 300 es
|
|
61
|
-
precision highp float;
|
|
62
|
-
${FRAME_BLOCK}
|
|
63
|
-
${INSTANCE_INPUTS}
|
|
64
|
-
out vec3 vWorld;
|
|
65
|
-
out vec3 vNormal;
|
|
66
|
-
${textured ? 'out vec2 vUv;' : ''}
|
|
67
|
-
void main() {
|
|
68
|
-
mat4 M = mat4(iM0, iM1, iM2, iM3);
|
|
69
|
-
vec4 wp = M * vec4(aPosition, 1.0);
|
|
70
|
-
vec3 c0 = iM0.xyz;
|
|
71
|
-
vec3 c1 = iM1.xyz;
|
|
72
|
-
vec3 c2 = iM2.xyz;
|
|
73
|
-
mat3 cof = mat3(cross(c1, c2), cross(c2, c0), cross(c0, c1));
|
|
74
|
-
float det = dot(c0, cross(c1, c2));
|
|
75
|
-
vNormal = cof * aNormal * (det < 0.0 ? -1.0 : 1.0);
|
|
76
|
-
vWorld = wp.xyz;
|
|
77
|
-
${textured ? 'vUv = aUv;' : ''}
|
|
78
|
-
gl_Position = uViewProj * wp;
|
|
79
|
-
}
|
|
80
|
-
`;
|
|
81
|
-
|
|
82
|
-
/** Material flags that need texture coordinates. */
|
|
83
|
-
export const TEXTURE_FLAGS = MAT_FLAG_MAP | MAT_FLAG_NORMAL_MAP | MAT_FLAG_MR_MAP | MAT_FLAG_OCCLUSION | MAT_FLAG_EMISSIVE_MAP;
|
|
84
|
-
|
|
85
|
-
/**
|
|
86
|
-
* Fragment shader for one material feature set (MAT_FLAG_* bits) and one
|
|
87
|
-
* light setup (lightTypes() of the frame: its light types, in order). Everything else is
|
|
88
|
-
* compiled out, as a renderer that specialises its programs does: textures
|
|
89
|
-
* are sampled only when the material has them, `discard` exists only for
|
|
90
|
-
* alpha-masked materials (its mere presence disables early depth testing on
|
|
91
|
-
* many GPUs), and the light loop is unrolled with each light's type known.
|
|
92
|
-
*/
|
|
93
|
-
export function meshFragmentShader(flags, lights) {
|
|
94
|
-
const has = (f) => (flags & f) !== 0;
|
|
95
|
-
const map = has(MAT_FLAG_MAP), normalMap = has(MAT_FLAG_NORMAL_MAP), mrMap = has(MAT_FLAG_MR_MAP);
|
|
96
|
-
const occlusion = has(MAT_FLAG_OCCLUSION), emissiveMap = has(MAT_FLAG_EMISSIVE_MAP);
|
|
97
|
-
const textured = map || normalMap || mrMap || occlusion || emissiveMap;
|
|
98
|
-
// explicit gradients only where a discard could leave later lookups without them
|
|
99
|
-
const grad = has(MAT_FLAG_ALPHA_MASK);
|
|
100
|
-
const sample = (sampler) => (grad ? `textureGrad(${sampler}, uv, duv1, duv2)` : `texture(${sampler}, uv)`);
|
|
101
|
-
const L = [];
|
|
102
|
-
L.push(`#version 300 es
|
|
103
|
-
precision highp float;
|
|
104
|
-
precision highp int;
|
|
105
|
-
${FRAME_BLOCK}
|
|
106
|
-
${MATERIAL_BLOCK}`);
|
|
107
|
-
if (map) L.push('uniform sampler2D uMap;');
|
|
108
|
-
if (normalMap) L.push('uniform sampler2D uNormalMap;');
|
|
109
|
-
if (mrMap) L.push('uniform sampler2D uMrMap;');
|
|
110
|
-
if (occlusion) L.push('uniform sampler2D uOcclusionMap;');
|
|
111
|
-
if (emissiveMap) L.push('uniform sampler2D uEmissiveMap;');
|
|
112
|
-
L.push(`in vec3 vWorld;
|
|
113
|
-
in vec3 vNormal;
|
|
114
|
-
${textured ? 'in vec2 vUv;' : ''}
|
|
115
|
-
out vec4 outColor;
|
|
116
|
-
${COMMON_FS}`);
|
|
117
|
-
if (!has(MAT_FLAG_UNLIT)) L.push(BRDF);
|
|
118
|
-
L.push('void main() {');
|
|
119
|
-
if (textured) L.push(' vec2 uv = vUv * mUv.xy;');
|
|
120
|
-
// derivatives before any discard, so every lookup after it still has them
|
|
121
|
-
if ((textured && grad) || normalMap) L.push(' vec2 duv1 = dFdx(uv);', ' vec2 duv2 = dFdy(uv);');
|
|
122
|
-
L.push(' vec4 base = mBaseColor;');
|
|
123
|
-
if (map) L.push(` base *= ${sample('uMap')};`);
|
|
124
|
-
if (has(MAT_FLAG_ALPHA_MASK)) L.push(' if (base.a < mParams.w) discard;');
|
|
125
|
-
L.push(' vec3 emitted = mEmissive.rgb;');
|
|
126
|
-
if (emissiveMap) L.push(` emitted *= ${sample('uEmissiveMap')}.rgb;`);
|
|
127
|
-
if (has(MAT_FLAG_UNLIT)) {
|
|
128
|
-
L.push(' outColor = vec4(linearToSrgb(base.rgb + emitted), base.a);', '}');
|
|
129
|
-
return L.join('\n');
|
|
130
|
-
}
|
|
131
|
-
L.push(' vec3 N = normalize(vNormal);');
|
|
132
|
-
if (has(MAT_FLAG_DOUBLE_SIDED)) L.push(' N = gl_FrontFacing ? N : -N;');
|
|
133
|
-
if (normalMap) {
|
|
134
|
-
L.push(` vec3 dp1 = dFdx(vWorld);
|
|
135
|
-
vec3 dp2 = dFdy(vWorld);
|
|
136
|
-
vec3 dp2perp = cross(dp2, N);
|
|
137
|
-
vec3 dp1perp = cross(N, dp1);
|
|
138
|
-
vec3 T = dp2perp * duv1.x + dp1perp * duv2.x;
|
|
139
|
-
vec3 B = dp2perp * duv1.y + dp1perp * duv2.y;
|
|
140
|
-
float invmax = inversesqrt(max(max(dot(T, T), dot(B, B)), 1e-20));
|
|
141
|
-
vec3 n = ${sample('uNormalMap')}.xyz * 2.0 - 1.0;
|
|
142
|
-
n.xy *= mEmissive.w;
|
|
143
|
-
N = normalize(mat3(T * invmax, B * invmax, N) * n);`);
|
|
144
|
-
}
|
|
145
|
-
L.push(` vec3 V = normalize(uCameraPos.xyz - vWorld);
|
|
146
|
-
float NdotV = max(dot(N, V), 1e-4);
|
|
147
|
-
float rough = clamp(mParams.y, 0.045, 1.0);
|
|
148
|
-
float metal = clamp(mParams.x, 0.0, 1.0);`);
|
|
149
|
-
if (mrMap) {
|
|
150
|
-
L.push(` vec4 mr = ${sample('uMrMap')};
|
|
151
|
-
rough = clamp(rough * mr.g, 0.045, 1.0);
|
|
152
|
-
metal = clamp(metal * mr.b, 0.0, 1.0);`);
|
|
153
|
-
}
|
|
154
|
-
L.push(` float a = rough * rough;
|
|
155
|
-
vec3 f0 = mix(vec3(0.04), base.rgb, metal);
|
|
156
|
-
vec3 diffuse = base.rgb * (1.0 - metal);
|
|
157
|
-
vec3 color = vec3(0.0);
|
|
158
|
-
vec3 ambient = vec3(0.0);`);
|
|
159
|
-
if (lights) lights.split(',').forEach((type, i) => L.push(lightTerm(Number(type), i)));
|
|
160
|
-
L.push(` float envI = uSky.w;
|
|
161
|
-
vec3 hemi = mix(uGround.rgb, uSky.rgb, N.y * 0.5 + 0.5) * envI + ambient;
|
|
162
|
-
vec3 R = reflect(-V, N);
|
|
163
|
-
vec3 envSpec = mix(uGround.rgb, uSky.rgb, smoothstep(-0.25 - rough, 0.25 + rough, R.y)) * envI;
|
|
164
|
-
envSpec = mix(envSpec, hemi, rough * rough);`);
|
|
165
|
-
if (occlusion) {
|
|
166
|
-
L.push(` float ao = mix(1.0, ${sample('uOcclusionMap')}.r, mUv.z);
|
|
167
|
-
hemi *= ao;
|
|
168
|
-
envSpec *= ao;`);
|
|
169
|
-
}
|
|
170
|
-
L.push(` color += diffuse * hemi + envSpec * envBRDF(f0, rough, NdotV);
|
|
171
|
-
color += emitted;
|
|
172
|
-
color *= uGround.w;
|
|
173
|
-
outColor = vec4(linearToSrgb(aces(color)), base.a);
|
|
174
|
-
}`);
|
|
175
|
-
return L.join('\n');
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const BRDF = /* glsl */ `
|
|
179
|
-
float D_GGX(float NdotH, float a) {
|
|
180
|
-
float a2 = a * a;
|
|
181
|
-
float d = NdotH * NdotH * (a2 - 1.0) + 1.0;
|
|
182
|
-
return a2 / (PI * d * d);
|
|
183
|
-
}
|
|
184
|
-
float V_Smith(float NdotV, float NdotL, float a) {
|
|
185
|
-
float a2 = a * a;
|
|
186
|
-
float gv = NdotL * sqrt(NdotV * NdotV * (1.0 - a2) + a2);
|
|
187
|
-
float gl = NdotV * sqrt(NdotL * NdotL * (1.0 - a2) + a2);
|
|
188
|
-
return 0.5 / max(gv + gl, 1e-5);
|
|
189
|
-
}
|
|
190
|
-
vec3 F_Schlick(vec3 f0, float VdotH) {
|
|
191
|
-
return f0 + (vec3(1.0) - f0) * pow(1.0 - VdotH, 5.0);
|
|
192
|
-
}
|
|
193
|
-
vec3 envBRDF(vec3 f0, float roughness, float NdotV) {
|
|
194
|
-
const vec4 c0 = vec4(-1.0, -0.0275, -0.572, 0.022);
|
|
195
|
-
const vec4 c1 = vec4(1.0, 0.0425, 1.04, -0.04);
|
|
196
|
-
vec4 r = roughness * c0 + c1;
|
|
197
|
-
float a004 = min(r.x * r.x, exp2(-9.28 * NdotV)) * r.x + r.y;
|
|
198
|
-
vec2 AB = vec2(-1.04, 1.04) * a004 + r.zw;
|
|
199
|
-
return f0 * AB.x + AB.y;
|
|
200
|
-
}
|
|
201
|
-
// one light's contribution, zero when it is behind the surface
|
|
202
|
-
vec3 shade(vec3 N, vec3 V, vec3 L, vec3 radiance, float NdotV, vec3 f0, vec3 diffuse, float a) {
|
|
203
|
-
float NdotL = dot(N, L);
|
|
204
|
-
if (NdotL <= 0.0) return vec3(0.0);
|
|
205
|
-
vec3 H = normalize(V + L);
|
|
206
|
-
float NdotH = max(dot(N, H), 0.0);
|
|
207
|
-
float VdotH = max(dot(V, H), 0.0);
|
|
208
|
-
vec3 F = F_Schlick(f0, VdotH);
|
|
209
|
-
vec3 spec = D_GGX(NdotH, a) * V_Smith(NdotV, NdotL, a) * F;
|
|
210
|
-
vec3 kd = (vec3(1.0) - F) * diffuse / PI;
|
|
211
|
-
return (kd + spec) * radiance * NdotL;
|
|
212
|
-
}
|
|
213
|
-
`;
|
|
214
|
-
|
|
215
|
-
/** GLSL for light i of a known type (lighting.js: 0 directional, 1 point, 2 spot, 3 ambient, 4 hemisphere). */
|
|
216
|
-
function lightTerm(type, i) {
|
|
217
|
-
if (type === 3) return ` ambient += uLightColor[${i}].rgb;`;
|
|
218
|
-
if (type === 4) return ` ambient += mix(uLightExtra[${i}].yzw, uLightColor[${i}].rgb, N.y * 0.5 + 0.5);`;
|
|
219
|
-
if (type === 0) return ` color += shade(N, V, -uLightDir[${i}].xyz, uLightColor[${i}].rgb * PI, NdotV, f0, diffuse, a);`;
|
|
220
|
-
return ` {
|
|
221
|
-
vec3 Lv = uLightPos[${i}].xyz - vWorld;
|
|
222
|
-
float d = length(Lv);
|
|
223
|
-
vec3 L = Lv / max(d, 1e-5);
|
|
224
|
-
float att = 1.0 / max(d * d, 1e-4);
|
|
225
|
-
float range = uLightDir[${i}].w;
|
|
226
|
-
if (range > 0.0) {
|
|
227
|
-
float x = d / range;
|
|
228
|
-
float w = clamp(1.0 - x * x * x * x, 0.0, 1.0);
|
|
229
|
-
att *= w * w;
|
|
230
|
-
}${type === 2 ? `\n att *= smoothstep(uLightColor[${i}].w, uLightExtra[${i}].x, dot(-L, uLightDir[${i}].xyz));` : ''}
|
|
231
|
-
color += shade(N, V, L, uLightColor[${i}].rgb * PI * att, NdotV, f0, diffuse, a);
|
|
232
|
-
}`;
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/** Lines: scene polylines (unlit material color), wireframe and bounding boxes. */
|
|
236
|
-
export const LINE_VS = /* glsl */ `#version 300 es
|
|
237
|
-
precision highp float;
|
|
238
|
-
${FRAME_BLOCK}
|
|
239
|
-
${INSTANCE_INPUTS}
|
|
240
|
-
void main() {
|
|
241
|
-
mat4 M = mat4(iM0, iM1, iM2, iM3);
|
|
242
|
-
vec4 wp = M * vec4(aPosition, 1.0);
|
|
243
|
-
// pull lines slightly toward the camera so they win the depth test against
|
|
244
|
-
// the surfaces they outline (a relative offset works at every distance)
|
|
245
|
-
wp.xyz = mix(wp.xyz, uCameraPos.xyz, uParams.z);
|
|
246
|
-
gl_Position = uViewProj * wp;
|
|
247
|
-
}
|
|
248
|
-
`;
|
|
249
|
-
|
|
250
|
-
/** Normal visualisation: the tip vertex (uv.x = 1) is offset along the world normal. */
|
|
251
|
-
export const NORMAL_VS = /* glsl */ `#version 300 es
|
|
252
|
-
precision highp float;
|
|
253
|
-
${FRAME_BLOCK}
|
|
254
|
-
${INSTANCE_INPUTS}
|
|
255
|
-
void main() {
|
|
256
|
-
mat4 M = mat4(iM0, iM1, iM2, iM3);
|
|
257
|
-
vec4 wp = M * vec4(aPosition, 1.0);
|
|
258
|
-
vec3 c0 = iM0.xyz;
|
|
259
|
-
vec3 c1 = iM1.xyz;
|
|
260
|
-
vec3 c2 = iM2.xyz;
|
|
261
|
-
mat3 cof = mat3(cross(c1, c2), cross(c2, c0), cross(c0, c1));
|
|
262
|
-
float det = dot(c0, cross(c1, c2));
|
|
263
|
-
vec3 n = normalize(cof * aNormal * (det < 0.0 ? -1.0 : 1.0));
|
|
264
|
-
wp.xyz += n * aUv.x * uParams.y;
|
|
265
|
-
wp.xyz = mix(wp.xyz, uCameraPos.xyz, uParams.z);
|
|
266
|
-
gl_Position = uViewProj * wp;
|
|
267
|
-
}
|
|
268
|
-
`;
|
|
269
|
-
|
|
270
|
-
export const LINE_FS = /* glsl */ `#version 300 es
|
|
271
|
-
precision highp float;
|
|
272
|
-
precision highp int;
|
|
273
|
-
${FRAME_BLOCK}
|
|
274
|
-
${MATERIAL_BLOCK}
|
|
275
|
-
out vec4 outColor;
|
|
276
|
-
${COMMON_FS}
|
|
277
|
-
void main() {
|
|
278
|
-
int flags = int(mParams.z + 0.5);
|
|
279
|
-
if ((flags & 16) != 0) {
|
|
280
|
-
outColor = mOverlay;
|
|
281
|
-
return;
|
|
282
|
-
}
|
|
283
|
-
outColor = vec4(linearToSrgb(mBaseColor.rgb + mEmissive.rgb), mBaseColor.a);
|
|
284
|
-
}
|
|
285
|
-
`;
|
|
1
|
+
// GLSL ES 3.00 shaders for the WebGL2 backend. They mirror shaders/wgsl.js.
|
|
2
|
+
//
|
|
3
|
+
// Instancing: the per-instance world matrix arrives as four vec4 attributes.
|
|
4
|
+
// Normals are transformed with the cofactor matrix of the instance matrix
|
|
5
|
+
// (proportional to the inverse transpose), so no normal matrix is uploaded and
|
|
6
|
+
// non-uniform and mirrored scales are handled correctly.
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
MAT_FLAG_MAP, MAT_FLAG_NORMAL_MAP, MAT_FLAG_UNLIT, MAT_FLAG_DOUBLE_SIDED, MAT_FLAG_MR_MAP,
|
|
10
|
+
MAT_FLAG_OCCLUSION, MAT_FLAG_EMISSIVE_MAP, MAT_FLAG_ALPHA_MASK,
|
|
11
|
+
} from '../lighting.js';
|
|
12
|
+
|
|
13
|
+
const FRAME_BLOCK = /* glsl */ `
|
|
14
|
+
layout(std140) uniform Frame {
|
|
15
|
+
mat4 uViewProj;
|
|
16
|
+
mat4 uView;
|
|
17
|
+
vec4 uCameraPos;
|
|
18
|
+
vec4 uSky;
|
|
19
|
+
vec4 uGround;
|
|
20
|
+
vec4 uParams;
|
|
21
|
+
vec4 uLightPos[16];
|
|
22
|
+
vec4 uLightDir[16];
|
|
23
|
+
vec4 uLightColor[16];
|
|
24
|
+
vec4 uLightExtra[16];
|
|
25
|
+
};
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
const MATERIAL_BLOCK = /* glsl */ `
|
|
29
|
+
layout(std140) uniform Material {
|
|
30
|
+
vec4 mBaseColor;
|
|
31
|
+
vec4 mEmissive;
|
|
32
|
+
vec4 mParams;
|
|
33
|
+
vec4 mUv;
|
|
34
|
+
vec4 mOverlay;
|
|
35
|
+
};
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
const INSTANCE_INPUTS = /* glsl */ `
|
|
39
|
+
layout(location = 0) in vec3 aPosition;
|
|
40
|
+
layout(location = 1) in vec3 aNormal;
|
|
41
|
+
layout(location = 2) in vec2 aUv;
|
|
42
|
+
layout(location = 3) in vec4 iM0;
|
|
43
|
+
layout(location = 4) in vec4 iM1;
|
|
44
|
+
layout(location = 5) in vec4 iM2;
|
|
45
|
+
layout(location = 6) in vec4 iM3;
|
|
46
|
+
`;
|
|
47
|
+
|
|
48
|
+
const COMMON_FS = /* glsl */ `
|
|
49
|
+
const float PI = 3.14159265359;
|
|
50
|
+
vec3 linearToSrgb(vec3 c) {
|
|
51
|
+
c = max(c, vec3(0.0));
|
|
52
|
+
return mix(c * 12.92, 1.055 * pow(c, vec3(1.0 / 2.4)) - 0.055, step(vec3(0.0031308), c));
|
|
53
|
+
}
|
|
54
|
+
vec3 aces(vec3 x) {
|
|
55
|
+
return clamp((x * (2.51 * x + 0.03)) / (x * (2.43 * x + 0.59) + 0.14), 0.0, 1.0);
|
|
56
|
+
}
|
|
57
|
+
`;
|
|
58
|
+
|
|
59
|
+
/** Vertex shader of the mesh programs; texture coordinates only for textured materials. */
|
|
60
|
+
export const meshVertexShader = (textured) => /* glsl */ `#version 300 es
|
|
61
|
+
precision highp float;
|
|
62
|
+
${FRAME_BLOCK}
|
|
63
|
+
${INSTANCE_INPUTS}
|
|
64
|
+
out vec3 vWorld;
|
|
65
|
+
out vec3 vNormal;
|
|
66
|
+
${textured ? 'out vec2 vUv;' : ''}
|
|
67
|
+
void main() {
|
|
68
|
+
mat4 M = mat4(iM0, iM1, iM2, iM3);
|
|
69
|
+
vec4 wp = M * vec4(aPosition, 1.0);
|
|
70
|
+
vec3 c0 = iM0.xyz;
|
|
71
|
+
vec3 c1 = iM1.xyz;
|
|
72
|
+
vec3 c2 = iM2.xyz;
|
|
73
|
+
mat3 cof = mat3(cross(c1, c2), cross(c2, c0), cross(c0, c1));
|
|
74
|
+
float det = dot(c0, cross(c1, c2));
|
|
75
|
+
vNormal = cof * aNormal * (det < 0.0 ? -1.0 : 1.0);
|
|
76
|
+
vWorld = wp.xyz;
|
|
77
|
+
${textured ? 'vUv = aUv;' : ''}
|
|
78
|
+
gl_Position = uViewProj * wp;
|
|
79
|
+
}
|
|
80
|
+
`;
|
|
81
|
+
|
|
82
|
+
/** Material flags that need texture coordinates. */
|
|
83
|
+
export const TEXTURE_FLAGS = MAT_FLAG_MAP | MAT_FLAG_NORMAL_MAP | MAT_FLAG_MR_MAP | MAT_FLAG_OCCLUSION | MAT_FLAG_EMISSIVE_MAP;
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Fragment shader for one material feature set (MAT_FLAG_* bits) and one
|
|
87
|
+
* light setup (lightTypes() of the frame: its light types, in order). Everything else is
|
|
88
|
+
* compiled out, as a renderer that specialises its programs does: textures
|
|
89
|
+
* are sampled only when the material has them, `discard` exists only for
|
|
90
|
+
* alpha-masked materials (its mere presence disables early depth testing on
|
|
91
|
+
* many GPUs), and the light loop is unrolled with each light's type known.
|
|
92
|
+
*/
|
|
93
|
+
export function meshFragmentShader(flags, lights) {
|
|
94
|
+
const has = (f) => (flags & f) !== 0;
|
|
95
|
+
const map = has(MAT_FLAG_MAP), normalMap = has(MAT_FLAG_NORMAL_MAP), mrMap = has(MAT_FLAG_MR_MAP);
|
|
96
|
+
const occlusion = has(MAT_FLAG_OCCLUSION), emissiveMap = has(MAT_FLAG_EMISSIVE_MAP);
|
|
97
|
+
const textured = map || normalMap || mrMap || occlusion || emissiveMap;
|
|
98
|
+
// explicit gradients only where a discard could leave later lookups without them
|
|
99
|
+
const grad = has(MAT_FLAG_ALPHA_MASK);
|
|
100
|
+
const sample = (sampler) => (grad ? `textureGrad(${sampler}, uv, duv1, duv2)` : `texture(${sampler}, uv)`);
|
|
101
|
+
const L = [];
|
|
102
|
+
L.push(`#version 300 es
|
|
103
|
+
precision highp float;
|
|
104
|
+
precision highp int;
|
|
105
|
+
${FRAME_BLOCK}
|
|
106
|
+
${MATERIAL_BLOCK}`);
|
|
107
|
+
if (map) L.push('uniform sampler2D uMap;');
|
|
108
|
+
if (normalMap) L.push('uniform sampler2D uNormalMap;');
|
|
109
|
+
if (mrMap) L.push('uniform sampler2D uMrMap;');
|
|
110
|
+
if (occlusion) L.push('uniform sampler2D uOcclusionMap;');
|
|
111
|
+
if (emissiveMap) L.push('uniform sampler2D uEmissiveMap;');
|
|
112
|
+
L.push(`in vec3 vWorld;
|
|
113
|
+
in vec3 vNormal;
|
|
114
|
+
${textured ? 'in vec2 vUv;' : ''}
|
|
115
|
+
out vec4 outColor;
|
|
116
|
+
${COMMON_FS}`);
|
|
117
|
+
if (!has(MAT_FLAG_UNLIT)) L.push(BRDF);
|
|
118
|
+
L.push('void main() {');
|
|
119
|
+
if (textured) L.push(' vec2 uv = vUv * mUv.xy;');
|
|
120
|
+
// derivatives before any discard, so every lookup after it still has them
|
|
121
|
+
if ((textured && grad) || normalMap) L.push(' vec2 duv1 = dFdx(uv);', ' vec2 duv2 = dFdy(uv);');
|
|
122
|
+
L.push(' vec4 base = mBaseColor;');
|
|
123
|
+
if (map) L.push(` base *= ${sample('uMap')};`);
|
|
124
|
+
if (has(MAT_FLAG_ALPHA_MASK)) L.push(' if (base.a < mParams.w) discard;');
|
|
125
|
+
L.push(' vec3 emitted = mEmissive.rgb;');
|
|
126
|
+
if (emissiveMap) L.push(` emitted *= ${sample('uEmissiveMap')}.rgb;`);
|
|
127
|
+
if (has(MAT_FLAG_UNLIT)) {
|
|
128
|
+
L.push(' outColor = vec4(linearToSrgb(base.rgb + emitted), base.a);', '}');
|
|
129
|
+
return L.join('\n');
|
|
130
|
+
}
|
|
131
|
+
L.push(' vec3 N = normalize(vNormal);');
|
|
132
|
+
if (has(MAT_FLAG_DOUBLE_SIDED)) L.push(' N = gl_FrontFacing ? N : -N;');
|
|
133
|
+
if (normalMap) {
|
|
134
|
+
L.push(` vec3 dp1 = dFdx(vWorld);
|
|
135
|
+
vec3 dp2 = dFdy(vWorld);
|
|
136
|
+
vec3 dp2perp = cross(dp2, N);
|
|
137
|
+
vec3 dp1perp = cross(N, dp1);
|
|
138
|
+
vec3 T = dp2perp * duv1.x + dp1perp * duv2.x;
|
|
139
|
+
vec3 B = dp2perp * duv1.y + dp1perp * duv2.y;
|
|
140
|
+
float invmax = inversesqrt(max(max(dot(T, T), dot(B, B)), 1e-20));
|
|
141
|
+
vec3 n = ${sample('uNormalMap')}.xyz * 2.0 - 1.0;
|
|
142
|
+
n.xy *= mEmissive.w;
|
|
143
|
+
N = normalize(mat3(T * invmax, B * invmax, N) * n);`);
|
|
144
|
+
}
|
|
145
|
+
L.push(` vec3 V = normalize(uCameraPos.xyz - vWorld);
|
|
146
|
+
float NdotV = max(dot(N, V), 1e-4);
|
|
147
|
+
float rough = clamp(mParams.y, 0.045, 1.0);
|
|
148
|
+
float metal = clamp(mParams.x, 0.0, 1.0);`);
|
|
149
|
+
if (mrMap) {
|
|
150
|
+
L.push(` vec4 mr = ${sample('uMrMap')};
|
|
151
|
+
rough = clamp(rough * mr.g, 0.045, 1.0);
|
|
152
|
+
metal = clamp(metal * mr.b, 0.0, 1.0);`);
|
|
153
|
+
}
|
|
154
|
+
L.push(` float a = rough * rough;
|
|
155
|
+
vec3 f0 = mix(vec3(0.04), base.rgb, metal);
|
|
156
|
+
vec3 diffuse = base.rgb * (1.0 - metal);
|
|
157
|
+
vec3 color = vec3(0.0);
|
|
158
|
+
vec3 ambient = vec3(0.0);`);
|
|
159
|
+
if (lights) lights.split(',').forEach((type, i) => L.push(lightTerm(Number(type), i)));
|
|
160
|
+
L.push(` float envI = uSky.w;
|
|
161
|
+
vec3 hemi = mix(uGround.rgb, uSky.rgb, N.y * 0.5 + 0.5) * envI + ambient;
|
|
162
|
+
vec3 R = reflect(-V, N);
|
|
163
|
+
vec3 envSpec = mix(uGround.rgb, uSky.rgb, smoothstep(-0.25 - rough, 0.25 + rough, R.y)) * envI;
|
|
164
|
+
envSpec = mix(envSpec, hemi, rough * rough);`);
|
|
165
|
+
if (occlusion) {
|
|
166
|
+
L.push(` float ao = mix(1.0, ${sample('uOcclusionMap')}.r, mUv.z);
|
|
167
|
+
hemi *= ao;
|
|
168
|
+
envSpec *= ao;`);
|
|
169
|
+
}
|
|
170
|
+
L.push(` color += diffuse * hemi + envSpec * envBRDF(f0, rough, NdotV);
|
|
171
|
+
color += emitted;
|
|
172
|
+
color *= uGround.w;
|
|
173
|
+
outColor = vec4(linearToSrgb(aces(color)), base.a);
|
|
174
|
+
}`);
|
|
175
|
+
return L.join('\n');
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const BRDF = /* glsl */ `
|
|
179
|
+
float D_GGX(float NdotH, float a) {
|
|
180
|
+
float a2 = a * a;
|
|
181
|
+
float d = NdotH * NdotH * (a2 - 1.0) + 1.0;
|
|
182
|
+
return a2 / (PI * d * d);
|
|
183
|
+
}
|
|
184
|
+
float V_Smith(float NdotV, float NdotL, float a) {
|
|
185
|
+
float a2 = a * a;
|
|
186
|
+
float gv = NdotL * sqrt(NdotV * NdotV * (1.0 - a2) + a2);
|
|
187
|
+
float gl = NdotV * sqrt(NdotL * NdotL * (1.0 - a2) + a2);
|
|
188
|
+
return 0.5 / max(gv + gl, 1e-5);
|
|
189
|
+
}
|
|
190
|
+
vec3 F_Schlick(vec3 f0, float VdotH) {
|
|
191
|
+
return f0 + (vec3(1.0) - f0) * pow(1.0 - VdotH, 5.0);
|
|
192
|
+
}
|
|
193
|
+
vec3 envBRDF(vec3 f0, float roughness, float NdotV) {
|
|
194
|
+
const vec4 c0 = vec4(-1.0, -0.0275, -0.572, 0.022);
|
|
195
|
+
const vec4 c1 = vec4(1.0, 0.0425, 1.04, -0.04);
|
|
196
|
+
vec4 r = roughness * c0 + c1;
|
|
197
|
+
float a004 = min(r.x * r.x, exp2(-9.28 * NdotV)) * r.x + r.y;
|
|
198
|
+
vec2 AB = vec2(-1.04, 1.04) * a004 + r.zw;
|
|
199
|
+
return f0 * AB.x + AB.y;
|
|
200
|
+
}
|
|
201
|
+
// one light's contribution, zero when it is behind the surface
|
|
202
|
+
vec3 shade(vec3 N, vec3 V, vec3 L, vec3 radiance, float NdotV, vec3 f0, vec3 diffuse, float a) {
|
|
203
|
+
float NdotL = dot(N, L);
|
|
204
|
+
if (NdotL <= 0.0) return vec3(0.0);
|
|
205
|
+
vec3 H = normalize(V + L);
|
|
206
|
+
float NdotH = max(dot(N, H), 0.0);
|
|
207
|
+
float VdotH = max(dot(V, H), 0.0);
|
|
208
|
+
vec3 F = F_Schlick(f0, VdotH);
|
|
209
|
+
vec3 spec = D_GGX(NdotH, a) * V_Smith(NdotV, NdotL, a) * F;
|
|
210
|
+
vec3 kd = (vec3(1.0) - F) * diffuse / PI;
|
|
211
|
+
return (kd + spec) * radiance * NdotL;
|
|
212
|
+
}
|
|
213
|
+
`;
|
|
214
|
+
|
|
215
|
+
/** GLSL for light i of a known type (lighting.js: 0 directional, 1 point, 2 spot, 3 ambient, 4 hemisphere). */
|
|
216
|
+
function lightTerm(type, i) {
|
|
217
|
+
if (type === 3) return ` ambient += uLightColor[${i}].rgb;`;
|
|
218
|
+
if (type === 4) return ` ambient += mix(uLightExtra[${i}].yzw, uLightColor[${i}].rgb, N.y * 0.5 + 0.5);`;
|
|
219
|
+
if (type === 0) return ` color += shade(N, V, -uLightDir[${i}].xyz, uLightColor[${i}].rgb * PI, NdotV, f0, diffuse, a);`;
|
|
220
|
+
return ` {
|
|
221
|
+
vec3 Lv = uLightPos[${i}].xyz - vWorld;
|
|
222
|
+
float d = length(Lv);
|
|
223
|
+
vec3 L = Lv / max(d, 1e-5);
|
|
224
|
+
float att = 1.0 / max(d * d, 1e-4);
|
|
225
|
+
float range = uLightDir[${i}].w;
|
|
226
|
+
if (range > 0.0) {
|
|
227
|
+
float x = d / range;
|
|
228
|
+
float w = clamp(1.0 - x * x * x * x, 0.0, 1.0);
|
|
229
|
+
att *= w * w;
|
|
230
|
+
}${type === 2 ? `\n att *= smoothstep(uLightColor[${i}].w, uLightExtra[${i}].x, dot(-L, uLightDir[${i}].xyz));` : ''}
|
|
231
|
+
color += shade(N, V, L, uLightColor[${i}].rgb * PI * att, NdotV, f0, diffuse, a);
|
|
232
|
+
}`;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Lines: scene polylines (unlit material color), wireframe and bounding boxes. */
|
|
236
|
+
export const LINE_VS = /* glsl */ `#version 300 es
|
|
237
|
+
precision highp float;
|
|
238
|
+
${FRAME_BLOCK}
|
|
239
|
+
${INSTANCE_INPUTS}
|
|
240
|
+
void main() {
|
|
241
|
+
mat4 M = mat4(iM0, iM1, iM2, iM3);
|
|
242
|
+
vec4 wp = M * vec4(aPosition, 1.0);
|
|
243
|
+
// pull lines slightly toward the camera so they win the depth test against
|
|
244
|
+
// the surfaces they outline (a relative offset works at every distance)
|
|
245
|
+
wp.xyz = mix(wp.xyz, uCameraPos.xyz, uParams.z);
|
|
246
|
+
gl_Position = uViewProj * wp;
|
|
247
|
+
}
|
|
248
|
+
`;
|
|
249
|
+
|
|
250
|
+
/** Normal visualisation: the tip vertex (uv.x = 1) is offset along the world normal. */
|
|
251
|
+
export const NORMAL_VS = /* glsl */ `#version 300 es
|
|
252
|
+
precision highp float;
|
|
253
|
+
${FRAME_BLOCK}
|
|
254
|
+
${INSTANCE_INPUTS}
|
|
255
|
+
void main() {
|
|
256
|
+
mat4 M = mat4(iM0, iM1, iM2, iM3);
|
|
257
|
+
vec4 wp = M * vec4(aPosition, 1.0);
|
|
258
|
+
vec3 c0 = iM0.xyz;
|
|
259
|
+
vec3 c1 = iM1.xyz;
|
|
260
|
+
vec3 c2 = iM2.xyz;
|
|
261
|
+
mat3 cof = mat3(cross(c1, c2), cross(c2, c0), cross(c0, c1));
|
|
262
|
+
float det = dot(c0, cross(c1, c2));
|
|
263
|
+
vec3 n = normalize(cof * aNormal * (det < 0.0 ? -1.0 : 1.0));
|
|
264
|
+
wp.xyz += n * aUv.x * uParams.y;
|
|
265
|
+
wp.xyz = mix(wp.xyz, uCameraPos.xyz, uParams.z);
|
|
266
|
+
gl_Position = uViewProj * wp;
|
|
267
|
+
}
|
|
268
|
+
`;
|
|
269
|
+
|
|
270
|
+
export const LINE_FS = /* glsl */ `#version 300 es
|
|
271
|
+
precision highp float;
|
|
272
|
+
precision highp int;
|
|
273
|
+
${FRAME_BLOCK}
|
|
274
|
+
${MATERIAL_BLOCK}
|
|
275
|
+
out vec4 outColor;
|
|
276
|
+
${COMMON_FS}
|
|
277
|
+
void main() {
|
|
278
|
+
int flags = int(mParams.z + 0.5);
|
|
279
|
+
if ((flags & 16) != 0) {
|
|
280
|
+
outColor = mOverlay;
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
outColor = vec4(linearToSrgb(mBaseColor.rgb + mEmissive.rgb), mBaseColor.a);
|
|
284
|
+
}
|
|
285
|
+
`;
|