lecodes-cli 1.0.0 → 2.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/dist/index.js +6211 -3649
- package/package.json +12 -8
- package/runtime/materials/lightmap.mat +115 -139
- package/runtime/materials/particles-quad.mat +6 -2
- package/runtime/materials/terrain-lightmap.mat +123 -0
- package/runtime/materials/terrain.mat +36 -55
- package/runtime/sdk-types.json +1 -1
- package/runtime/web/VERSION.json +5 -0
- package/runtime/web/assets/camera-5XOM8eHl.js +1 -0
- package/runtime/web/assets/creator-2d-D9WKYM6h.js +1 -0
- package/runtime/web/assets/creator-2d-ukvEmNEp.wasm +0 -0
- package/runtime/web/assets/creator-full-BBXzQlhq.js +1 -0
- package/runtime/web/assets/creator-full-D2dSDdDj.wasm +0 -0
- package/runtime/web/assets/creator-full.js-BYswGsqR.symbols +8124 -0
- package/runtime/web/assets/creator-ui-DIc92ion.wasm +0 -0
- package/runtime/web/assets/creator-ui-_1Z_AX2M.js +1 -0
- package/runtime/web/assets/geolocation-BjTKN9_P.js +1 -0
- package/runtime/web/assets/inspect-C7x78PNs.js +2 -0
- package/runtime/web/assets/map-CyBxOSV0.js +1 -0
- package/runtime/web/assets/map-XzoOFt0E.js +2 -0
- package/runtime/web/assets/mapImpl-bxvQ2h1Z.js +823 -0
- package/runtime/web/assets/maplibre-gl-worker-B7LXXUkR.js +8 -0
- package/runtime/web/assets/materials-uri4FvPE.bin +0 -0
- package/runtime/web/assets/materials-world-CQWt8TAm.bin +0 -0
- package/runtime/web/assets/qr-scanner-CK_cYoJR.js +1 -0
- package/runtime/web/assets/uberarchive-DLH-kBhM.bin +0 -0
- package/runtime/web/index.html +36 -0
- package/runtime/web/player.js +21 -0
- package/runtime/web-page/index.html +41 -0
- package/runtime/web-page/page.css +1 -0
- package/runtime/web-page/page.js +31 -0
- package/src/cli/help.ts +25 -0
- package/src/cli/index.ts +7 -7
- package/src/commands/app/android.ts +3 -1
- package/src/commands/app/desktop.ts +1 -1
- package/src/commands/app/desktopMac.ts +10 -9
- package/src/commands/app/icon.ts +9 -6
- package/src/commands/app/index.ts +3 -1
- package/src/commands/app/shared.ts +13 -5
- package/src/commands/app/templates/android.ts +10 -11
- package/src/commands/app/templates/ios.ts +1 -1
- package/src/commands/assets.ts +1 -1
- package/src/commands/compile.ts +15 -2
- package/src/commands/design/index.ts +4 -4
- package/src/commands/design/snapshot.ts +3 -3
- package/src/commands/desktop.ts +5 -1
- package/src/commands/dev.ts +82 -29
- package/src/commands/index.ts +64 -60
- package/src/commands/init/index.ts +11 -15
- package/src/commands/init/templates.ts +64 -21
- package/src/commands/lightmap.ts +459 -200
- package/src/commands/navmesh.ts +2 -2
- package/src/commands/plugin.ts +46 -0
- package/src/commands/render.ts +22 -16
- package/src/commands/scene.ts +6 -1
- package/src/commands/serve.ts +58 -0
- package/src/commands/shaders/new.ts +1 -1
- package/src/commands/test.ts +402 -398
- package/src/commands/update/index.ts +3 -3
- package/src/compile/designCompile.ts +1 -1
- package/src/compile/headlessBundle.ts +133 -133
- package/src/compile/nativeStack.ts +1 -1
- package/src/compile/projectCompile.ts +10 -2
- package/src/compile/sceneCompile.ts +8 -22
- package/src/declarations/lecodes-3d-editor.d.ts +4 -0
- package/src/declarations/lecodes-assets.d.ts +1 -1
- package/src/declarations/{lecodes-renderer.d.ts → lecodes-headless.d.ts} +3 -3
- package/src/dev/clientTemplates.ts +18 -14
- package/src/dev/devServer.ts +75 -38
- package/src/dev/iosDev.ts +180 -0
- package/src/dev/webRunner.ts +82 -14
- package/src/hosts/cmgenTool.ts +1 -1
- package/src/hosts/desktopRenderer.ts +98 -27
- package/src/hosts/desktopScript.ts +150 -35
- package/src/hosts/distRoot.ts +1 -1
- package/src/hosts/matcTool.ts +1 -1
- package/src/hosts/peerInstall.ts +5 -5
- package/src/hosts/peers.ts +2 -2
- package/src/index.ts +4 -1
- package/src/plugin/contract.ts +438 -0
- package/src/plugin/emitKotlin.ts +357 -0
- package/src/plugin/emitSdk.ts +259 -0
- package/src/plugin/emitSwift.ts +336 -0
- package/src/plugin/emitWeb.ts +383 -0
- package/src/plugin/generate.ts +157 -0
- package/src/plugin/lines.ts +28 -0
- package/src/plugin/lower.ts +231 -0
- package/src/plugin/manifest.ts +192 -0
- package/src/project/ignore.ts +164 -163
- package/runtime/materials/lightmap-baked-lite.mat +0 -180
- package/runtime/materials/lightmap-baked.mat +0 -181
- package/runtime/scene-harness.json +0 -1
- package/runtime/web/assets/__vite-browser-external-BIHI7g3E.js +0 -1
- package/runtime/web/assets/basis-C64VHDVD.js +0 -1
- package/runtime/web/assets/basis_transcoder-VXdx5NbI.wasm +0 -0
- package/runtime/web/assets/createViewerLite-DHqTFhWB.js +0 -866
- package/runtime/web/assets/draco-BiISTFcR.js +0 -118
- package/runtime/web/assets/draco_decoder-DsQ12WqX.wasm +0 -0
- package/runtime/web/assets/index-Q06c6oHx.js +0 -2
- package/runtime/web/assets/mapViewImpl-B2JcES8l.js +0 -810
- package/runtime/web/assets/maplibre-gl-worker-CJfwIrte.js +0 -8
- package/runtime/web/assets/worker-Caf-yYEI.js +0 -2
- package/runtime/web/embed.html +0 -32
- package/runtime/web/embed.js +0 -171
package/package.json
CHANGED
|
@@ -1,23 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lecodes-cli",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"dependencies": {
|
|
5
|
-
"@letary/chisel": "^0.
|
|
5
|
+
"@letary/chisel": "^0.13.0",
|
|
6
6
|
"jimp": "^1.6.1",
|
|
7
|
-
"lecodes-sdk": "
|
|
7
|
+
"lecodes-sdk": "2.0.1"
|
|
8
8
|
},
|
|
9
9
|
"devDependencies": {
|
|
10
10
|
"lecodes-design": "^0.9.0",
|
|
11
|
-
"lecodes-
|
|
11
|
+
"lecodes-headless": "2.0.1",
|
|
12
12
|
"qrcode-terminal": "^0.12.0",
|
|
13
13
|
"lecodes-3d-editor": "^0.7.0",
|
|
14
14
|
"lecodes-assets": "0.2.1",
|
|
15
15
|
"lecodes-creator-nav": "0.19.0",
|
|
16
|
-
"lecodes-
|
|
16
|
+
"lecodes-gl-lite": "2.0.1",
|
|
17
|
+
"lecodes-web-frame": "2.0.1",
|
|
18
|
+
"lecodes-web-player": "2.0.1",
|
|
19
|
+
"lecodes-web-shared": "2.0.1"
|
|
17
20
|
},
|
|
18
21
|
"peerDependencies": {
|
|
19
22
|
"lecodes-design": "^0.9.0",
|
|
20
|
-
"lecodes-
|
|
23
|
+
"lecodes-headless": "^2.0.1",
|
|
21
24
|
"lecodes-3d-editor": "^0.7.0",
|
|
22
25
|
"lecodes-assets": "^0.2.1"
|
|
23
26
|
},
|
|
@@ -43,7 +46,7 @@
|
|
|
43
46
|
"lecodes-design": {
|
|
44
47
|
"optional": true
|
|
45
48
|
},
|
|
46
|
-
"lecodes-
|
|
49
|
+
"lecodes-headless": {
|
|
47
50
|
"optional": true
|
|
48
51
|
},
|
|
49
52
|
"lecodes-3d-editor": {
|
|
@@ -57,9 +60,10 @@
|
|
|
57
60
|
"icon": "bun scripts/make-icon.ts",
|
|
58
61
|
"vendor-runtime": "bun scripts/vendor-runtime.ts",
|
|
59
62
|
"sync-peers": "bun scripts/sync-peer-versions.ts",
|
|
60
|
-
"build": "bun scripts/vendor-runtime.ts && bun build src/index.ts --target=node --outfile dist/index.js --external @letary/chisel --external lecodes-
|
|
63
|
+
"build": "bun scripts/vendor-runtime.ts && bun build src/index.ts --target=node --outfile dist/index.js --external @letary/chisel --external lecodes-headless --external lecodes-design --external lecodes-3d-editor --external lecodes-assets --banner=\"#!/usr/bin/env node\"",
|
|
61
64
|
"build:bin": "bun scripts/build-bin.ts",
|
|
62
65
|
"typecheck": "tsc --noEmit",
|
|
66
|
+
"test": "bun test",
|
|
63
67
|
"docs": "bun scripts/gen-docs.ts",
|
|
64
68
|
"docs:check": "bun scripts/gen-docs.ts --check",
|
|
65
69
|
"dev": "bun run src/index.ts",
|
|
@@ -1,43 +1,89 @@
|
|
|
1
|
-
// lightmap —
|
|
2
|
-
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
// parameters use gltfio's own names and the AssetLoader fills them itself. Mesh receivers
|
|
6
|
-
// (Mesh.plane) get it from the SDK directly.
|
|
1
|
+
// lightmap — the material of a BAKED static (creator-bake, phase 1). gltfio hands a GLB's primitives this
|
|
2
|
+
// material instead of the ubershader when the SDK loads a model with `lightmap: true` (the provider in
|
|
3
|
+
// creator-gl/src/glb.cpp), so the glTF-side parameters use gltfio's own names and the AssetLoader fills
|
|
4
|
+
// them itself. Mesh receivers get it from the SDK directly.
|
|
7
5
|
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
6
|
+
// A static takes ALL of its diffuse light from the atlas: `lightmapLight` holds the irradiance E in lux
|
|
7
|
+
// (BC6H, times lightScale) that the bake traced onto the surface — the sun, the sky and the lamps
|
|
8
|
+
// together — and the pixel's diffuse is albedo x E / pi, Filament's own punctual diffuse. Nothing
|
|
9
|
+
// real-time is added on top: the engine keeps the sun and the lamps off these renderables (light channels,
|
|
10
|
+
// lightmapApply) and Filament's IBL — diffuse and specular — is switched off exactly (ambientOcclusion = 0,
|
|
11
|
+
// specularAmbientOcclusion simple). The SPECULAR comes from the bake too: with the level's REFLECTION PROBES
|
|
12
|
+
// bound (phase 4, `lightmapProbe` + the probe map under the aux page: prefiltered radiance traced from points of the
|
|
13
|
+
// level, creator-bake/src/reflect.h) it is the split-sum response to the radiance the probes this texel SEES (baked)
|
|
14
|
+
// hold along the reflection — blended per pixel, parallax-corrected by each probe's room box, at the material's
|
|
15
|
+
// roughness — held down to this texel's own light where the probes see more than the surface does (a specular
|
|
16
|
+
// occlusion from the bake, see the fragment); without them, the baked light as light from all over plus the
|
|
17
|
+
// sky's SH radiance along the reflection through the baked sky visibility. No
|
|
18
|
+
// highlight from the baked direction (fireflies, see the fragment). Plus the glTF emissive. So the picture on
|
|
19
|
+
// a static is the bake, and a wrong picture is a wrong bake or a wrong material, never a shading estimate.
|
|
13
20
|
//
|
|
14
|
-
// The
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
//
|
|
21
|
+
// The aux atlas (`lightmapAux`, RGBA8 linear, the same rects): RGB = share x d (x 0.5 + 0.5) — the
|
|
22
|
+
// directional SHARE of the baked light times its direction d — and A = sky visibility (phase 3,
|
|
23
|
+
// creator-bake/src/direction.h). The
|
|
24
|
+
// bake split every texel's light into an ambient part (uniform over the hemisphere) and one direction, so
|
|
25
|
+
// a normal map's shading normal ns gets the irradiance those two would give it:
|
|
26
|
+
// E(ns) = E x [ (1 - share) (1 + n.ns) / 2 + share max(0, d.ns) / (d.n) ]
|
|
27
|
+
// exact for both parts, and E itself where the normal is flat. Under a grazing light (d.n < 0.1) the bake
|
|
28
|
+
// fades the share out and the slope is capped. `reconstructIrradiance` in direction.h is the same formula;
|
|
29
|
+
// bake-check tests it.
|
|
20
30
|
//
|
|
21
|
-
//
|
|
22
|
-
//
|
|
31
|
+
// DEBUG VIEWS (`debugMode`, level-wide through lightmapDebug): the material writes DATA to the pixel
|
|
32
|
+
// and turns the lit path off (black base colour, no reflectance), so what is on screen is the number —
|
|
33
|
+
// look at it under `toneMapping: 'linear'` for a faithful reading.
|
|
34
|
+
// 1 lighting only: a white diffuse material under the camera's exposure 2 albedo (base colour, linear)
|
|
35
|
+
// 3 geometric normal (x 0.5 + 0.5) 4 shading normal (the normal map applied)
|
|
36
|
+
// 5 the directional share (0 = ambient, 1 = one direction) 6 sky visibility
|
|
37
|
+
// 7 the atlas: a texel checker, tinted per instance 8 the baked light's direction (x 0.5 + 0.5)
|
|
38
|
+
// 9 false-colour lux: log10 over [debugParams.y, debugParams.z] 10 relief: E(ns) / E, grey 0.5 = 1 (flat)
|
|
39
|
+
// 13 probe: the radiance the specular reflects (the probe along the reflection at the material's roughness, occluded;
|
|
40
|
+
// or the sky + baked light without a probe), under the camera's exposure like view 1
|
|
41
|
+
// 14 the probe map: grey = the probes' share Wp of the reflection, tinted by the texel's probe pair (magenta = no entry)
|
|
42
|
+
// 15 the sky's path of the reflection alone 16 the probes' path alone, after the occlusion (black = no probe)
|
|
43
|
+
// On desktop both atlases are fetched bicubically (textureBicubic: a cubic B-spline through four bilinear taps) —
|
|
44
|
+
// the texel grid stops showing on the diagonals of a shadow's edge; the bake's gutters (4 texels, chart-aware fill)
|
|
45
|
+
// cover the wider 4x4 footprint. Mobile and iOS take the plain bilinear fetch (matc -TLIGHTMAP_FETCH, materials.json);
|
|
46
|
+
// macOS (`materials-macos`) takes the desktop flags: the same MOBILE shader model as iOS, the desktop's quality.
|
|
47
|
+
//
|
|
48
|
+
// PRECISION: everything in LUX is `highp`, explicitly. The mobile shader model (matc -p mobile: the `materials` and
|
|
49
|
+
// `materials-ios` targets) makes mediump the fragment default, and Filament's Metal path turns mediump into 16-bit
|
|
50
|
+
// half (filamat GLSLPostProcessor: CreateConvertRelaxedToHalfPass) — max 65504. A sunlit texel is 100 000-180 000 lux
|
|
51
|
+
// once the atlas is multiplied back by lightScale, so on macOS / iOS every baked static went infinite (flat white,
|
|
52
|
+
// and bloom smeared it over the frame: the yard, 2026-09-17) while the desktop target (highp by default) was right.
|
|
53
|
+
// The parameters in lux are `precision : high` for the same reason. Ratios and exposed values are O(1) and may stay.
|
|
54
|
+
// The same for ATLAS COORDINATES, TEXEL INDICES, PROBE INDICES and WORLD POSITIONS: a half has 11 mantissa bits, so a
|
|
55
|
+
// uv near 1 steps by 1/2048 (two texels of a 4096 page — the irradiance read as blocks on macOS, 2026-09-17), an
|
|
56
|
+
// integer above 2048 cannot be odd (the texel checker, view 7, went flat), and 65534 / 65535 (the sky / none probe
|
|
57
|
+
// sentinels) are not representable at all. `luv`, the bicubic taps, mapU16 and the probe ids are highp for that.
|
|
23
58
|
material {
|
|
24
59
|
name : lightmap,
|
|
25
60
|
shadingModel : lit,
|
|
26
61
|
doubleSided : true,
|
|
27
62
|
// opaque or masked: matc -TBLENDING (materials.json `variants`) — the masked twin is what the
|
|
28
|
-
//
|
|
63
|
+
// provider hands a glTF MASK material; BLEND materials (glass) are not lightmapped at all
|
|
29
64
|
blending : ${BLENDING},
|
|
30
|
-
// glTF's UV origin is top-left, like gltfio's ubershader (base.mat.in): no flip
|
|
31
|
-
// default (true) would mirror both the props' base colour and the atlas rects vertically.
|
|
65
|
+
// glTF's UV origin is top-left, like gltfio's ubershader (base.mat.in): no flip
|
|
32
66
|
flipUV : false,
|
|
33
67
|
requires : [ uv0, uv1 ],
|
|
34
|
-
|
|
68
|
+
// Filament's own specular occlusion, fed ambientOcclusion = 0: the IBL — diffuse AND specular — is off
|
|
69
|
+
// on a baked surface. Occluding the specular through f0 alone (2026-09-16, phase 1) leaked the sky at
|
|
70
|
+
// grazing angles: Schlick's (1 - NoV)^5 term does not care about f0, so a basement floor mirrored the
|
|
71
|
+
// sky IBL whenever the camera looked along it. The sky reflection is the material's own now (skyRadianceSH
|
|
72
|
+
// x skyVis in the fragment); sharp interior reflections need local probes (a later phase).
|
|
73
|
+
specularAmbientOcclusion : simple,
|
|
35
74
|
parameters : [
|
|
36
75
|
{ type : float4, name : baseColorFactor },
|
|
37
76
|
{ type : sampler2d, name : baseColorMap },
|
|
38
77
|
{ type : float, name : metallicFactor },
|
|
39
78
|
{ type : float, name : roughnessFactor },
|
|
40
79
|
{ type : float3, name : emissiveFactor },
|
|
80
|
+
// KHR_materials_emissive_strength (gltfio sets it, 1 without the extension): an HDR panel is factor 1 x strength 10
|
|
81
|
+
{ type : float, name : emissiveStrength },
|
|
82
|
+
// THE LEVEL'S RADIANCE OF EMISSION 1.0, cd / m2 (the bake's emissiveNits, level-wide): over 0 the glow is drawn
|
|
83
|
+
// SCENE-referred - emission x nits through the camera's exposure, the luminance the bake lit the room with - so a
|
|
84
|
+
// panel looks as bright as the light it gives. 0 = screen-referred as before (emission 1.0 = white at any exposure;
|
|
85
|
+
// an imported strength of 10 then blooms over the whole ceiling).
|
|
86
|
+
{ type : float, name : emissiveNits, precision : high },
|
|
41
87
|
// set unconditionally by gltfio's AssetLoader (a missing uniform is a PANIC, not a no-op)
|
|
42
88
|
{ type : float, name : normalScale },
|
|
43
89
|
{ type : float, name : aoStrength },
|
|
@@ -46,139 +92,69 @@ material {
|
|
|
46
92
|
{ type : sampler2d, name : normalMap },
|
|
47
93
|
{ type : float, name : hasNormalMap },
|
|
48
94
|
// glTF metal-rough (B = metallic, G = roughness), occlusion (R) and emissive maps on UV0 —
|
|
49
|
-
// bound by the AssetLoader by name; absent = the shared 1x1 white,
|
|
50
|
-
// identity under its factor (metallic 0 x 1, roughness x 1, AO 1, emissive 0 x 1)
|
|
95
|
+
// bound by the AssetLoader by name; absent = the shared 1x1 white, each map's identity
|
|
51
96
|
{ type : sampler2d, name : metallicRoughnessMap },
|
|
52
97
|
{ type : sampler2d, name : occlusionMap },
|
|
53
98
|
{ type : sampler2d, name : emissiveMap },
|
|
54
|
-
|
|
55
|
-
// the point lights' baked irradiance, sRGB(E / lightScale) on the same rects; lightScale 0 = none
|
|
99
|
+
// the bake: irradiance (lux / lightScale) and the aux page, on uv1 * lightmapST.xy + lightmapST.zw
|
|
56
100
|
{ type : sampler2d, name : lightmapLight },
|
|
57
|
-
{ type :
|
|
58
|
-
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
//
|
|
62
|
-
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
{ type :
|
|
66
|
-
|
|
67
|
-
{ type :
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
//
|
|
74
|
-
// atlas
|
|
75
|
-
|
|
76
|
-
{ type :
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
101
|
+
{ type : sampler2d, name : lightmapAux },
|
|
102
|
+
{ type : float4, name : lightmapST, precision : high },
|
|
103
|
+
// the level's reflection probes (phase 4, creator-bake/src/reflect.h): the octahedral ATLAS of every probe's
|
|
104
|
+
// roughness levels (radiance in lux / lightScale per steradian; level l = perceptual roughness l / (levels - 1)).
|
|
105
|
+
// WHICH probes a pixel reflects is baked: under every aux page sits its PROBE MAP (rows past the square page,
|
|
106
|
+
// read by texelFetch — see probeGather). probeParams = (bound, levels - 1, oct = level 0's tile edge, probes
|
|
107
|
+
// per atlas row); probeGrid.xyz / probeGridN.xyz = the origin / size of the range the map's 16-bit probe records
|
|
108
|
+
// are shares of. probeBall = k + 1 shows probe k alone, unoccluded (the debug mirror balls). Level-wide.
|
|
109
|
+
{ type : sampler2d, name : lightmapProbe },
|
|
110
|
+
{ type : float4, name : probeParams },
|
|
111
|
+
{ type : float4, name : probeGrid, precision : high },
|
|
112
|
+
{ type : float4, name : probeGridN, precision : high },
|
|
113
|
+
{ type : float, name : probeBall },
|
|
114
|
+
// A SMALL STATIC the atlas left out (the bake's minSize; creator-bake/src/objects.h): no rect — its light was baked
|
|
115
|
+
// where it stands, as an AMBIENT CUBE: objectCube[+x -x +y -y +z -z] = (the irradiance rgb in lux a surface facing
|
|
116
|
+
// that way gets at that side of the object — sun, sky, lamps, bounce — and its sky visibility). objectProbes =
|
|
117
|
+
// (probe a, probe b, a's weight — what the probe map's byte holds for a texel; 65535 none, 65534 sky — and 1 = this
|
|
118
|
+
// mode is on). The atlas samplers stay bound to one of the level's pages: the probes' records sit under its aux image.
|
|
119
|
+
{ type : float4[6], name : objectCube, precision : high },
|
|
120
|
+
{ type : float4, name : objectProbes, precision : high },
|
|
121
|
+
// A MOVER'S SUN SHADOW on the baked surface: sunDir = TO the sun (w = 1: the aux pages end in the SUN MAP - its
|
|
122
|
+
// levels' rows, level l of edge s = S >> l in s / 4 rows: texel (x, y) = channel x & 3 of pixel ((x >> 2) + (y & 3) s / 4,
|
|
123
|
+
// base(l) + (y >> 2)), the texel's baked share of the sun's disc; lightmap-shading.fs sunMapAt), sunLight = the sun's
|
|
124
|
+
// illuminance in lux, rgb, .w = the sun map's level count (the shader picks the level by the pixel's footprint). Where the real-time
|
|
125
|
+
// shadow map (movers only: the baked statics cast nothing) says "in shadow", the sun's part leaves the baked
|
|
126
|
+
// light - no double shadow: a texel the bake already shaded has no sun to lose.
|
|
127
|
+
{ type : float4, name : sunDir },
|
|
128
|
+
{ type : float4, name : sunLight, precision : high },
|
|
129
|
+
{ type : float, name : lightScale, precision : high },
|
|
130
|
+
{ type : float, name : debugMode },
|
|
131
|
+
{ type : float4, name : debugParams }
|
|
80
132
|
]
|
|
81
133
|
}
|
|
82
134
|
|
|
83
135
|
fragment {
|
|
136
|
+
#include "lightmap-shading.fs"
|
|
137
|
+
|
|
84
138
|
void material(inout MaterialInputs material) {
|
|
139
|
+
vec3 n = getWorldGeometricNormalVector();
|
|
140
|
+
vec3 ns = n;
|
|
85
141
|
if (materialParams.hasNormalMap > 0.5) {
|
|
86
142
|
vec3 tn = texture(materialParams_normalMap, getUV0()).xyz * 2.0 - 1.0;
|
|
87
143
|
tn.xy *= materialParams.normalScale;
|
|
88
144
|
material.normal = tn;
|
|
145
|
+
ns = normalize(getWorldTangentFrame() * tn);
|
|
89
146
|
}
|
|
90
147
|
prepareMaterial(material);
|
|
91
148
|
|
|
92
|
-
|
|
149
|
+
vec4 base = texture(materialParams_baseColorMap, getUV0()) * materialParams.baseColorFactor;
|
|
93
150
|
vec2 mr = texture(materialParams_metallicRoughnessMap, getUV0()).bg;
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
vec3 ns = getWorldNormalVector(); // the shading normal (normal map applied)
|
|
101
|
-
vec3 luma = vec3(0.2126, 0.7152, 0.0722);
|
|
102
|
-
float lg = materialParams.lightGamma > 0.0 ? materialParams.lightGamma : 1.0;
|
|
103
|
-
vec2 lm;
|
|
104
|
-
vec3 baked; // the point lights' irradiance E(n) on this pixel, physical units
|
|
105
|
-
vec3 l; // the direction that light is taken to arrive from (the specular term below)
|
|
106
|
-
if (materialParams.probeInvSize.w > 0.5) {
|
|
107
|
-
// a dynamic object: the light volume at this pixel (trilinear, half a cell off the surface)
|
|
108
|
-
vec3 p = getUserWorldPosition() + n * materialParams.probeMin.w;
|
|
109
|
-
vec3 uvw = (p - materialParams.probeMin.xyz) * materialParams.probeInvSize.xyz;
|
|
110
|
-
lm = texture(materialParams_probeVolume, uvw).rg;
|
|
111
|
-
// the ambient cube: the face slab of each axis' sign, z clamped to the slab's own cell
|
|
112
|
-
// centres so the trilinear fetch never blends into the neighbouring face; decode each face
|
|
113
|
-
// (the store is a square root), then blend in linear irradiance by n²
|
|
114
|
-
float nz = materialParams.probeInvSize.w;
|
|
115
|
-
float wz = clamp(uvw.z, 0.5 / nz, 1.0 - 0.5 / nz);
|
|
116
|
-
vec3 fx = vec3(uvw.xy, ((ns.x >= 0.0 ? 0.0 : 1.0) + wz) / 6.0);
|
|
117
|
-
vec3 fy = vec3(uvw.xy, ((ns.y >= 0.0 ? 2.0 : 3.0) + wz) / 6.0);
|
|
118
|
-
vec3 fz = vec3(uvw.xy, ((ns.z >= 0.0 ? 4.0 : 5.0) + wz) / 6.0);
|
|
119
|
-
vec3 ex = pow(texture(materialParams_probeLight, fx).rgb, vec3(lg));
|
|
120
|
-
vec3 ey = pow(texture(materialParams_probeLight, fy).rgb, vec3(lg));
|
|
121
|
-
vec3 ez = pow(texture(materialParams_probeLight, fz).rgb, vec3(lg));
|
|
122
|
-
vec3 w2 = ns * ns;
|
|
123
|
-
baked = (w2.x * ex + w2.y * ey + w2.z * ez) * materialParams.lightScale;
|
|
124
|
-
// the dominant direction within the normal's octant: each face's energy along its axis
|
|
125
|
-
vec3 dirE = vec3(sign(ns.x) * dot(ex, luma), sign(ns.y) * dot(ey, luma), sign(ns.z) * dot(ez, luma));
|
|
126
|
-
l = dot(dirE, dirE) > 1e-12 ? normalize(dirE) : ns;
|
|
127
|
-
} else {
|
|
128
|
-
vec2 luv = getUV1() * materialParams.lightmapST.xy + materialParams.lightmapST.zw;
|
|
129
|
-
lm = texture(materialParams_lightmap, luv).rg;
|
|
130
|
-
baked = pow(texture(materialParams_lightmapLight, luv).rgb, vec3(lg)) * materialParams.lightScale;
|
|
131
|
-
l = ns; // the atlas keeps no direction: a static's lamp light is taken along its normal
|
|
132
|
-
}
|
|
133
|
-
lm.g = mix(1.0, lm.g, materialParams.bakedAo);
|
|
134
|
-
material.ambientOcclusion = lm.g * occ;
|
|
135
|
-
|
|
136
|
-
// Baked point lights on the pixel, exposed here (emissive.w = 0 adds it as-is) so the glTF
|
|
137
|
-
// emissive can share the slot. Diffuse: albedo × (1 − metallic) × E / π — filament's own punctual
|
|
138
|
-
// diffuse. Specular: the same E through a GGX lobe from `l`, so METALS take baked light too — their
|
|
139
|
-
// diffuse is zero, and a rough steel table used to render darker than the floor it stands on because
|
|
140
|
-
// only the ambient probe reached it. L_out = f_spec · E_⊥ · NoL, and E(n) = E_⊥ · NoL is what the
|
|
141
|
-
// store holds. The lobe is kept no tighter than roughness 0.5: the stored E is light gathered from
|
|
142
|
-
// a room, not one bulb, and a mirror-sharp lobe would paint it as a highlight over the whole surface.
|
|
143
|
-
vec3 diffuseColor = material.baseColor.rgb * (1.0 - material.metallic);
|
|
144
|
-
vec3 specBrdf;
|
|
145
|
-
{
|
|
146
|
-
vec3 v = getWorldViewVector();
|
|
147
|
-
vec3 h = normalize(v + l);
|
|
148
|
-
float NoL = saturate(dot(ns, l));
|
|
149
|
-
float NoV = max(dot(ns, v), 1e-4);
|
|
150
|
-
float NoH = saturate(dot(ns, h));
|
|
151
|
-
float LoH = saturate(dot(l, h));
|
|
152
|
-
float pr = max(material.roughness, 0.5);
|
|
153
|
-
float a2 = pr * pr;
|
|
154
|
-
a2 *= a2;
|
|
155
|
-
float dd = NoH * NoH * (a2 - 1.0) + 1.0;
|
|
156
|
-
float D = a2 / (PI * dd * dd);
|
|
157
|
-
float V = 0.5 / max(NoL * sqrt(NoV * NoV * (1.0 - a2) + a2) + NoV * sqrt(NoL * NoL * (1.0 - a2) + a2), 1e-4);
|
|
158
|
-
vec3 f0 = mix(vec3(0.04), material.baseColor.rgb, material.metallic);
|
|
159
|
-
vec3 F = f0 + (1.0 - f0) * pow(1.0 - LoH, 5.0);
|
|
160
|
-
specBrdf = D * V * F;
|
|
161
|
-
}
|
|
162
|
-
material.emissive = vec4(glow + (diffuseColor / PI + specBrdf) * baked * getExposure(), 0.0);
|
|
151
|
+
float metallic = materialParams.metallicFactor * mr.x;
|
|
152
|
+
float roughness = materialParams.roughnessFactor * mr.y;
|
|
153
|
+
// highp, and nits x exposure FIRST (the PRECISION note in the header): the camera's own reading of emission 1.0 is
|
|
154
|
+
// 1.2 x 2^EV100 = 39 000 nits outdoors - times a strength of 10 it is past a half's 65 504 before the exposure
|
|
155
|
+
highp vec3 glow = texture(materialParams_emissiveMap, getUV0()).rgb * (materialParams.emissiveFactor * materialParams.emissiveStrength);
|
|
156
|
+
if (materialParams.emissiveNits > 0.0) glow *= (materialParams.emissiveNits * getExposure());
|
|
163
157
|
|
|
164
|
-
|
|
165
|
-
// The ambient share, from the SAME per-frame uniforms filament shades this pixel with
|
|
166
|
-
// (frameUniforms: the directional light + the IBL's SH, both exposure-premultiplied), so
|
|
167
|
-
// the floor is exactly the IBL-only brightness filament's own shadow map would leave.
|
|
168
|
-
vec3 sh = frameUniforms.iblSH[0].xyz
|
|
169
|
-
+ frameUniforms.iblSH[1].xyz * n.y + frameUniforms.iblSH[2].xyz * n.z + frameUniforms.iblSH[3].xyz * n.x
|
|
170
|
-
+ frameUniforms.iblSH[4].xyz * (n.y * n.x) + frameUniforms.iblSH[5].xyz * (n.y * n.z)
|
|
171
|
-
+ frameUniforms.iblSH[6].xyz * (3.0 * n.z * n.z - 1.0) + frameUniforms.iblSH[7].xyz * (n.z * n.x)
|
|
172
|
-
+ frameUniforms.iblSH[8].xyz * (n.x * n.x - n.y * n.y);
|
|
173
|
-
// diffuse terms in filament's units: IBL = albedo * E_sh * iblLuminance; sun = albedo * (color * intensity) * NoL / PI
|
|
174
|
-
float amb = max(dot(max(sh, 0.0), luma), 0.0) * frameUniforms.iblLuminance * materialParams.ambientScale * lm.g;
|
|
175
|
-
float nol = max(dot(n, frameUniforms.lightDirection), 0.0);
|
|
176
|
-
float sun = dot(frameUniforms.lightColorIntensity.rgb, luma) * frameUniforms.lightColorIntensity.w * nol / PI;
|
|
177
|
-
// the post-lighting multiply also scales the emissive slot the baked lamps ride in, so their
|
|
178
|
-
// share (same units: exposed irradiance / π) sits on both sides — a lamp in the sun's shadow
|
|
179
|
-
// is not dimmed by it
|
|
180
|
-
float lamp = dot(baked, luma) * (getExposure() / PI);
|
|
181
|
-
float k = (amb + sun * vis + lamp) / max(amb + sun + lamp, 1e-6);
|
|
182
|
-
material.postLightingColor = vec4(k, k, k, 1.0);
|
|
158
|
+
lightmapShade(material, n, ns, base, metallic, roughness, glow);
|
|
183
159
|
}
|
|
184
160
|
}
|
|
@@ -88,8 +88,12 @@ vertex {
|
|
|
88
88
|
float3 x = normalize(cross(y, camZ));
|
|
89
89
|
offset = x * corner.x * c0.x + y * corner.y * (c0.x + materialParams.stretch * speed * yl);
|
|
90
90
|
} else {
|
|
91
|
-
// ribbon: extrude sideways
|
|
92
|
-
|
|
91
|
+
// ribbon: extrude sideways across the strip; corner.x carries u along the trail.
|
|
92
|
+
// c0.y (the billboard's rotation, meaningless here) says which kind of aux this is:
|
|
93
|
+
// 0 — the strip TANGENT, crossed with the view so the strip faces the camera
|
|
94
|
+
// 1 — the width axis already resolved on the CPU (an oriented / blade-aligned strip,
|
|
95
|
+
// blended back toward the camera as it goes edge-on; it owes the view nothing)
|
|
96
|
+
float3 x = c0.y > 0.5 ? aux : cross(aux, camZ);
|
|
93
97
|
float xl = length(x);
|
|
94
98
|
x = xl > 1e-4 ? x / xl : camX;
|
|
95
99
|
offset = x * corner.y * c0.x;
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// terrain-lightmap — THE BAKED TERRAIN: terrain.mat's surface (four splat layers blended by the control map, see its
|
|
2
|
+
// header) shaded like lightmap.mat (its header is the model: the whole diffuse light is the baked irradiance atlas on
|
|
3
|
+
// UV1, no real-time sun, no diffuse IBL; the sun's share comes back out where a mover's real-time shadow falls). The
|
|
4
|
+
// shading is lightmap-shading.fs, the very code lightmap.mat runs — a terrain next to a baked wall cannot drift from it.
|
|
5
|
+
//
|
|
6
|
+
// UV1 is the terrain's global unit square (creator-gl/src/terrain.cpp): ONE atlas rect for the whole terrain, which
|
|
7
|
+
// lightmapApply addresses through `lightmapST` on the terrain's single material instance.
|
|
8
|
+
//
|
|
9
|
+
// WHO USES IT: a scene file's `terrain:` node that is a lightmap static (sdk scene/defineScene.ts) is created with this
|
|
10
|
+
// material instead of terrain.mat, like a model is loaded with lightmap.mat. Without a bound bake it is black, like
|
|
11
|
+
// any lightmapped static.
|
|
12
|
+
//
|
|
13
|
+
// NO REFLECTION PROBES (LIGHTMAP_NO_PROBES): feature level 1 gives a material 9 samplers and the layers take 7 of them,
|
|
14
|
+
// the atlas 2. Ground is rough and mostly under the sky: it reflects the sky's path (SH radiance x the baked sky
|
|
15
|
+
// visibility), which is what lightmap.mat does wherever no probe is seen.
|
|
16
|
+
material {
|
|
17
|
+
name : terrain_lightmap,
|
|
18
|
+
shadingModel : lit,
|
|
19
|
+
// geometric specular AA (Kaplanyan): the ground is a 1024² normal map at ×1.5–2 tiled every 2 m under a
|
|
20
|
+
// 100 000 lux sun — without this every minified texel is a sub-pixel mirror and the sand sparkles
|
|
21
|
+
// (Mountain, 2026-09-23; A/B with normalScale 0 showed nothing but the normal map does it)
|
|
22
|
+
specularAntiAliasing : true,
|
|
23
|
+
blending : ${BLENDING},
|
|
24
|
+
flipUV : false,
|
|
25
|
+
requires : [ uv0, uv1 ],
|
|
26
|
+
specularAmbientOcclusion : simple,
|
|
27
|
+
parameters : [
|
|
28
|
+
// ---- the surface: terrain.mat's
|
|
29
|
+
{ type : sampler2d, name : control },
|
|
30
|
+
{ type : sampler2d, name : albedo0 },
|
|
31
|
+
{ type : sampler2d, name : albedo1 },
|
|
32
|
+
{ type : sampler2d, name : albedo2 },
|
|
33
|
+
{ type : sampler2d, name : albedo3 },
|
|
34
|
+
{ type : sampler2d, name : normals01 },
|
|
35
|
+
{ type : sampler2d, name : normals23 },
|
|
36
|
+
{ type : float4, name : tiling },
|
|
37
|
+
{ type : float4, name : roughness },
|
|
38
|
+
{ type : float4, name : normalScale },
|
|
39
|
+
{ type : float4, name : triplanar },
|
|
40
|
+
{ type : float2, name : gridSize },
|
|
41
|
+
{ type : float, name : cellSize },
|
|
42
|
+
{ type : float3, name : tint },
|
|
43
|
+
// ---- the bake: lightmap.mat's (the comments are there), without the probe atlas
|
|
44
|
+
{ type : sampler2d, name : lightmapLight },
|
|
45
|
+
{ type : sampler2d, name : lightmapAux },
|
|
46
|
+
{ type : float4, name : lightmapST, precision : high },
|
|
47
|
+
{ type : float4, name : probeParams },
|
|
48
|
+
{ type : float4, name : probeGrid, precision : high },
|
|
49
|
+
{ type : float4, name : probeGridN, precision : high },
|
|
50
|
+
{ type : float, name : probeBall },
|
|
51
|
+
{ type : float4[6], name : objectCube, precision : high },
|
|
52
|
+
{ type : float4, name : objectProbes, precision : high },
|
|
53
|
+
{ type : float4, name : sunDir },
|
|
54
|
+
{ type : float4, name : sunLight, precision : high },
|
|
55
|
+
{ type : float, name : lightScale, precision : high },
|
|
56
|
+
{ type : float, name : debugMode },
|
|
57
|
+
{ type : float4, name : debugParams }
|
|
58
|
+
]
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
fragment {
|
|
62
|
+
#define LIGHTMAP_NO_PROBES
|
|
63
|
+
#include "lightmap-shading.fs"
|
|
64
|
+
|
|
65
|
+
vec4 triplanarSample(sampler2D tex, highp vec3 p, vec3 bw, float s) {
|
|
66
|
+
return texture(tex, p.zy / s) * bw.x + texture(tex, p.xz / s) * bw.y + texture(tex, p.xy / s) * bw.z;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// `slot` 0 = this layer's normal XY sits in the pack's RG, 1 = in its BA
|
|
70
|
+
void splatLayer(sampler2D alb, sampler2D pack, float slot, float tiling, float nscale, float tri, float w,
|
|
71
|
+
highp vec2 uv, highp vec3 wp, vec3 bw, float rf, inout vec3 albedo, inout vec3 nrm, inout float rough) {
|
|
72
|
+
if (w <= 0.0) return;
|
|
73
|
+
float s = max(tiling, 1e-3);
|
|
74
|
+
if (tri > 0.5) {
|
|
75
|
+
vec4 c = triplanarSample(alb, wp, bw, s);
|
|
76
|
+
albedo += c.rgb * w;
|
|
77
|
+
rough += rf * c.a * w;
|
|
78
|
+
nrm += vec3(0.0, 0.0, 1.0) * w;
|
|
79
|
+
} else {
|
|
80
|
+
highp vec2 luv = uv / s;
|
|
81
|
+
vec4 c = texture(alb, luv);
|
|
82
|
+
albedo += c.rgb * w;
|
|
83
|
+
rough += rf * c.a * w;
|
|
84
|
+
if (nscale > 0.0) {
|
|
85
|
+
vec4 p = texture(pack, luv);
|
|
86
|
+
vec2 xy = (slot > 0.5 ? p.ba : p.rg) * 2.0 - 1.0;
|
|
87
|
+
xy *= nscale;
|
|
88
|
+
float z = sqrt(max(1.0 - dot(xy, xy), 0.0));
|
|
89
|
+
nrm += vec3(xy, z) * w;
|
|
90
|
+
} else {
|
|
91
|
+
nrm += vec3(0.0, 0.0, 1.0) * w;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
void material(inout MaterialInputs material) {
|
|
97
|
+
// metres and world positions are highp (lightmap.mat's PRECISION note: a half steps by centimetres at 100 m)
|
|
98
|
+
highp vec2 uv = getUV0();
|
|
99
|
+
highp vec2 cuv = (uv / max(materialParams.cellSize, 1e-4) + 0.5) / max(materialParams.gridSize, vec2(1.0));
|
|
100
|
+
vec4 w = texture(materialParams_control, cuv);
|
|
101
|
+
float ws = w.r + w.g + w.b + w.a;
|
|
102
|
+
w = ws > 1e-4 ? w / ws : vec4(1.0, 0.0, 0.0, 0.0);
|
|
103
|
+
|
|
104
|
+
highp vec3 wp = getUserWorldPosition();
|
|
105
|
+
vec3 gn = getWorldGeometricNormalVector();
|
|
106
|
+
vec3 bw = abs(gn);
|
|
107
|
+
bw = bw / max(bw.x + bw.y + bw.z, 1e-4);
|
|
108
|
+
|
|
109
|
+
vec3 albedo = vec3(0.0);
|
|
110
|
+
vec3 nrm = vec3(0.0);
|
|
111
|
+
float rough = 0.0;
|
|
112
|
+
splatLayer(materialParams_albedo0, materialParams_normals01, 0.0, materialParams.tiling.x, materialParams.normalScale.x, materialParams.triplanar.x, w.r, uv, wp, bw, materialParams.roughness.x, albedo, nrm, rough);
|
|
113
|
+
splatLayer(materialParams_albedo1, materialParams_normals01, 1.0, materialParams.tiling.y, materialParams.normalScale.y, materialParams.triplanar.y, w.g, uv, wp, bw, materialParams.roughness.y, albedo, nrm, rough);
|
|
114
|
+
splatLayer(materialParams_albedo2, materialParams_normals23, 0.0, materialParams.tiling.z, materialParams.normalScale.z, materialParams.triplanar.z, w.b, uv, wp, bw, materialParams.roughness.z, albedo, nrm, rough);
|
|
115
|
+
splatLayer(materialParams_albedo3, materialParams_normals23, 1.0, materialParams.tiling.w, materialParams.normalScale.w, materialParams.triplanar.w, w.a, uv, wp, bw, materialParams.roughness.w, albedo, nrm, rough);
|
|
116
|
+
vec3 tn = normalize(nrm);
|
|
117
|
+
material.normal = tn;
|
|
118
|
+
vec3 ns = normalize(getWorldTangentFrame() * tn);
|
|
119
|
+
prepareMaterial(material);
|
|
120
|
+
|
|
121
|
+
lightmapShade(material, gn, ns, vec4(albedo * materialParams.tint, 1.0), 0.0, rough, vec3(0.0));
|
|
122
|
+
}
|
|
123
|
+
}
|