lecodes-cli 2.5.1 → 2.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +20618 -19047
- package/package.json +15 -18
- package/runtime/materials/foliage-cards.mat +16 -2
- package/runtime/materials/foliage-impostor.mat +103 -26
- package/runtime/materials/foliage.mat +12 -1
- package/runtime/materials/grass.mat +6 -3
- package/runtime/sdk-types.json +1 -1
- package/runtime/web/VERSION.json +3 -3
- package/runtime/web/assets/{materials-Cs_z1zLi.bin → materials-DE8QZ7Hn.bin} +0 -0
- package/runtime/web/assets/materials-world-CGNyYz_-.bin +0 -0
- package/runtime/web/player.js +16 -16
- package/runtime/web-lite/lite.js +2 -2
- package/src/commands/call.ts +96 -0
- package/src/commands/compile.ts +8 -4
- package/src/commands/desktop.ts +5 -4
- package/src/commands/dev.ts +42 -3
- package/src/commands/index.ts +2 -0
- package/src/commands/init/templates.ts +67 -3
- package/src/commands/plugin.ts +1 -1
- package/src/commands/render.ts +31 -22
- package/src/commands/test.ts +90 -24
- package/src/commands/web.ts +13 -4
- package/src/compile/headlessBundle.ts +5 -6
- package/src/compile/projectCompile.ts +35 -5
- package/src/declarations/lecodes-headless.d.ts +51 -2
- package/src/dev/backendCall.ts +44 -0
- package/src/dev/backendWorker.ts +60 -7
- package/src/dev/bots.ts +133 -0
- package/src/dev/devLink.ts +136 -0
- package/src/dev/devServer.ts +19 -6
- package/src/dev/localBackend.ts +57 -9
- package/src/dev/projectBackend.ts +227 -49
- package/src/hosts/desktopRenderer.ts +8 -2
- package/src/hosts/liteRunner.ts +1 -0
- package/src/plugin/generate.ts +5 -3
- package/runtime/web/assets/materials-world-BtoAfznA.bin +0 -0
package/package.json
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lecodes-cli",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.8.0",
|
|
4
4
|
"dependencies": {
|
|
5
5
|
"@letary/chisel": "^0.15.0",
|
|
6
6
|
"jimp": "^1.6.1",
|
|
7
|
-
"lecodes-sdk": "2.
|
|
8
|
-
"selfsigned": "^2.4.1"
|
|
7
|
+
"lecodes-sdk": "2.8.0",
|
|
8
|
+
"selfsigned": "^2.4.1",
|
|
9
|
+
"typescript": "^5.8.3"
|
|
9
10
|
},
|
|
10
11
|
"devDependencies": {
|
|
11
12
|
"lecodes-design": "https://storage.yandexcloud.net/le-codes/releases/packages/lecodes-design/lecodes-design-0.11.0.tgz",
|
|
12
|
-
"lecodes-headless": "2.
|
|
13
|
+
"lecodes-headless": "2.8.0",
|
|
13
14
|
"qrcode-terminal": "^0.12.0",
|
|
14
15
|
"lecodes-3d-editor": "https://storage.yandexcloud.net/le-codes/releases/packages/lecodes-3d-editor/lecodes-3d-editor-0.9.0.tgz",
|
|
15
|
-
"lecodes-assets": "2.
|
|
16
|
+
"lecodes-assets": "2.8.0",
|
|
16
17
|
"lecodes-creator-nav": "0.19.0",
|
|
17
|
-
"lecodes-gl-lite": "2.
|
|
18
|
-
"lecodes-web-frame": "2.
|
|
19
|
-
"lecodes-web-player": "2.
|
|
20
|
-
"lecodes-web-shared": "2.
|
|
18
|
+
"lecodes-gl-lite": "2.8.0",
|
|
19
|
+
"lecodes-web-frame": "2.8.0",
|
|
20
|
+
"lecodes-web-player": "2.8.0",
|
|
21
|
+
"lecodes-web-shared": "2.8.0",
|
|
21
22
|
"lecodes-web-lite": "2.0.7",
|
|
22
23
|
"heic-decode": "^2.1.0",
|
|
23
|
-
"marcidb-embedded": "^0.
|
|
24
|
+
"marcidb-embedded": "^0.18.0",
|
|
24
25
|
"sharp": "^0.35.2"
|
|
25
26
|
},
|
|
26
27
|
"peerDependencies": {
|
|
27
28
|
"lecodes-design": "^0.11.0",
|
|
28
|
-
"lecodes-headless": "^2.
|
|
29
|
+
"lecodes-headless": "^2.8.0",
|
|
29
30
|
"lecodes-3d-editor": "^0.9.0",
|
|
30
|
-
"lecodes-assets": "^2.
|
|
31
|
+
"lecodes-assets": "^2.8.0",
|
|
31
32
|
"heic-decode": "^2.1.0",
|
|
32
|
-
"marcidb-embedded": "^0.
|
|
33
|
-
"sharp": "^0.35.2"
|
|
34
|
-
"typescript": "^5.8.3"
|
|
33
|
+
"marcidb-embedded": "^0.18.0",
|
|
34
|
+
"sharp": "^0.35.2"
|
|
35
35
|
},
|
|
36
36
|
"bin": {
|
|
37
37
|
"lecodes": "dist/index.js"
|
|
@@ -64,9 +64,6 @@
|
|
|
64
64
|
"lecodes-assets": {
|
|
65
65
|
"optional": true
|
|
66
66
|
},
|
|
67
|
-
"typescript": {
|
|
68
|
-
"optional": true
|
|
69
|
-
},
|
|
70
67
|
"marcidb-embedded": {
|
|
71
68
|
"optional": true
|
|
72
69
|
},
|
|
@@ -55,7 +55,8 @@ material {
|
|
|
55
55
|
{ type : sampler2d, name : windNoise },
|
|
56
56
|
{ type : float4, name : plant },
|
|
57
57
|
{ type : float4[8], name : benders },
|
|
58
|
-
{ type : float4[8], name : benderMotion }
|
|
58
|
+
{ type : float4[8], name : benderMotion },
|
|
59
|
+
{ type : float, name : debugMode } // the impostor lab's DEBUG VIEW (glb.cpp CREATOR_FOLIAGE_DEBUG; 0 = off): 1 albedo, 2 hull normal as colour, 3 the sky term on white — the sun off in all three
|
|
59
60
|
]
|
|
60
61
|
}
|
|
61
62
|
|
|
@@ -85,7 +86,10 @@ vertex {
|
|
|
85
86
|
// (`leaf.z`, narrowed toward the view line into the light; less deep in the crown), one scalar the fragment
|
|
86
87
|
// multiplies by the light's colour, intensity and SHADOW
|
|
87
88
|
float ao = mix(1.0, f.vao, clamp(materialParams.leaf.w, 0.0, 1.0));
|
|
88
|
-
|
|
89
|
+
// + THE SKY THROUGH THE LEAF (look.x, Foliage `skyThrough`): a card below the crown's middle faces down and would
|
|
90
|
+
// read the environment's ground alone - a tenth of the zenith under an overcast sky, the lower half of every plant
|
|
91
|
+
// black (2026-11-04) - so the thin leaf adds that share of the sky on its other side
|
|
92
|
+
vec3 ambient = (shIrradianceV(f.hull) + materialParams.look.x * shIrradianceV(-f.hull)) * frameUniforms.iblLuminance * ao;
|
|
89
93
|
vec3 l = frameUniforms.lightDirection;
|
|
90
94
|
vec3 v = normalize(getWorldCameraPosition() - f.wp);
|
|
91
95
|
float wrap = clamp(materialParams.sway.w, 0.0, 1.0);
|
|
@@ -125,6 +129,15 @@ fragment {
|
|
|
125
129
|
material.reflectance = 0.0;
|
|
126
130
|
material.ambientOcclusion = 0.0; // Filament's IBL off: the ambient is the vertex shader's, below
|
|
127
131
|
material.emissive = vec4(base.rgb * variable_lightv.rgb, 0.0);
|
|
132
|
+
// THE DEBUG VIEW (debugMode, the impostor lab): the alpha path above stays, only the colour changes; the sun is
|
|
133
|
+
// off (surfaceShading returns 0). 1 = the albedo, 2 = the hull normal (world) ×½+½, 3 = the sky term on white
|
|
134
|
+
float dbg = materialParams.debugMode;
|
|
135
|
+
if (dbg > 0.5) {
|
|
136
|
+
vec3 hn = variable_radial.xyz;
|
|
137
|
+
float hl = length(hn);
|
|
138
|
+
hn = hl > 1e-5 ? hn / hl : vec3(0.0, 1.0, 0.0);
|
|
139
|
+
material.emissive = vec4(dbg < 1.5 ? base.rgb : dbg < 2.5 ? hn * 0.5 + 0.5 : variable_lightv.rgb, 0.0);
|
|
140
|
+
}
|
|
128
141
|
}
|
|
129
142
|
|
|
130
143
|
// PER LIGHT (the sun first): the vertex shader's wrapped Lambert + translucency scalar, times the light and its
|
|
@@ -132,6 +145,7 @@ fragment {
|
|
|
132
145
|
// from the crown's own card-on-card one, and a young pine at full strength went black (2026-10-18). A lamp gets the
|
|
133
146
|
// sun's scalar too — the lean tier is for cards seen from a distance, where a lamp's own angle on a leaf is not read
|
|
134
147
|
vec3 surfaceShading(const MaterialInputs materialInputs, const ShadingData shadingData, const LightData lightData) {
|
|
148
|
+
if (materialParams.debugMode > 0.5) return vec3(0.0); // the debug view: no sun
|
|
135
149
|
float lit = mix(1.0, lightData.visibility, clamp(materialParams.leaf.y, 0.0, 1.0));
|
|
136
150
|
float reach = lightData.colorIntensity.w * lightData.attenuation * lit;
|
|
137
151
|
return shadingData.diffuseColor * lightData.colorIntensity.rgb * (reach * (variable_lightv.w + variable_leafv.x) / PI);
|
|
@@ -24,14 +24,22 @@
|
|
|
24
24
|
// A point p seen in a frame at a = ½ + ½·dot(p - c, right)/r, b = ½ + ½·dot(p - c, up)/r (a right, b UP) lands at
|
|
25
25
|
// the atlas uv ((i + a)/A, (k + 1 - b)/E) on the grid, ((i + a)/N, (j + 1 - b)/N) on the octahedron — image row 0
|
|
26
26
|
// at the top, the glTF convention (flipUV false, getUV0() raw).
|
|
27
|
-
// • baseColorMap: rgb = albedo (sRGB), a =
|
|
28
|
-
//
|
|
29
|
-
//
|
|
27
|
+
// • baseColorMap: rgb = albedo (sRGB), a = COVERAGE, fractional: the baker takes S×S samples a texel (default 8×8) and
|
|
28
|
+
// stores covered / S². metallicRoughnessMap (linear): rg = the SHADING normal in OBJECT space — a card sample's the
|
|
29
|
+
// crown's HULL normal (what the cards shade by: from 0.6 of the plant's height over its origin to the surface,
|
|
30
|
+
// foliage-wind.vs f.hull), a bark sample's the MESH normal (the interpolated vertex NORMAL turned toward the frame's
|
|
31
|
+
// viewer: what foliage.mat lights the bark by), averaged over the texel's samples —
|
|
32
|
+
// packed with the FULL-sphere octahedral map ×½+½ (normalDecode below, the baker's normalEncode —
|
|
30
33
|
// not the frame layout's hemi impEncode/impDecode; its seam pole at the corners is object −Y): a far-side leaf keeps a normal that looks away, which is the
|
|
31
34
|
// crown's shading; b = the DEPTH along the frame's view axis, z = dot(p - c, d)/r stored z·½+½ (1 = the point
|
|
32
35
|
// nearest this frame's viewer, ½ = the quad plane, 0 = the farthest); a = the baked sky visibility (vertexAo.ts'
|
|
33
|
-
// COLOR_0.r).
|
|
34
|
-
//
|
|
36
|
+
// COLOR_0.r). occlusionMap (linear, NOT an occlusion): r = the BARK fraction of the texel's covered samples (an
|
|
37
|
+
// opaque glTF material is bark, a MASK one a card), g = 0, b = the BARK's OCCLUSION: what foliage.mat darkens the bark's
|
|
38
|
+
// ambient by beyond the sky visibility (its baked cavity x its occlusion map x its normal map's cavity), 1/255 at least
|
|
39
|
+
// - b = 0 is an atlas from before the channel and reads 1. g = 0 marks the map present: the engine's default for
|
|
40
|
+
// an unbound sampler is a 1×1 white (g = 1), so the shader reads bark = r·(1 - g) and an older atlas reads none.
|
|
41
|
+
// An uncovered texel carries its nearest covered neighbour's data, albedo and bark (the dilation): a depth read just
|
|
42
|
+
// outside a silhouette continues the surface, while the coverage there stays 0.
|
|
35
43
|
//
|
|
36
44
|
// THE QUAD: 4 vertices whose POSITION is ignored (the baker writes the sphere's box corners there only so gltfio's
|
|
37
45
|
// culling box is right); TEXCOORD_0 is the corner id, corner = uv·2-1 (x right, y up). The vertex shader takes the
|
|
@@ -43,11 +51,20 @@
|
|
|
43
51
|
// (i+1,k+1) (i,k+1) (i+1,k) with (t.x+t.y-1, 1-t.x, 1-t.y), so the picture morphs between neighbouring views instead of
|
|
44
52
|
// popping. On the grid i+1 wraps mod A and k+1 stops at the top row (min(k+1, E-1)); on the octahedron both stop at N-1.
|
|
45
53
|
//
|
|
54
|
+
// THE WIND (2026-11-04): the quad sways with its tree. Each corner's world position is bent by the cards' own plant-level
|
|
55
|
+
// lean (foliage-wind.vs foliageField + foliageBend, included under FOLIAGE_BEND_ONLY): the wind field and the gust at the
|
|
56
|
+
// plant's root, the rotation about the root across the wind growing with the corner's distance from it, by the same
|
|
57
|
+
// `wind` / `sway` / `profile` the engine writes to every foliage material. The leaves' shiver and the benders' touch stay
|
|
58
|
+
// out (below a far tree's pixel; the touch is grass-scale). Only the drawn position moves: p, the frame pick and the
|
|
59
|
+
// lighting data describe the still tree. The depth variants run the same vertex code, so the shadow leans too. The A/B is
|
|
60
|
+
// `Foliage.configure({ wind: false })` (a zero field: no axis to bend about).
|
|
61
|
+
//
|
|
46
62
|
// THE VARYINGS (Filament caps custom variables at 5; four used):
|
|
47
63
|
// impFrames = (frame0, frame1, frame2, hash) — a frame packed as i·E + k (grid) / i·N + j (octahedron); hash = the
|
|
48
64
|
// cards' per-instance tint hash
|
|
49
65
|
// impPos = (p.xyz, w0) — p = the fragment's offset from c in object space (linear over the quad)
|
|
50
|
-
// impW = (w1, alive,
|
|
66
|
+
// impW = (w1, alive, lodEst, 0) — w2 = 1 - w0 - w1; alive = the distance fade (`fade`, as the cards);
|
|
67
|
+
// lodEst = the coverage boost's mip term, max(mip - 1, 0) (THE FRAGMENT)
|
|
51
68
|
// impRot = the model's rotation as a quaternion (its columns normalised): getWorldFromModelMatrix() exists in the
|
|
52
69
|
// vertex stage only, and the fragment needs it to take the atlas' object-space normal to the world
|
|
53
70
|
// The frame choice and the weights depend on the CENTRE's direction alone, so they are the same at the four corners:
|
|
@@ -55,12 +72,28 @@
|
|
|
55
72
|
//
|
|
56
73
|
// THE FRAGMENT: per frame, unpack its index (grid: i = floor(packed/E), k = packed - i·E; octahedron: by N), decode its
|
|
57
74
|
// direction (the grid's d(az_i, el_k) / the octahedron's decode), build its basis, project p onto it (no parallax — see the note at the
|
|
58
|
-
// sample) and read
|
|
59
|
-
//
|
|
75
|
+
// sample) and read the three maps; blend by the weights. Alpha = Σ w·a (× the distance fade), the FRACTIONAL coverage of
|
|
76
|
+
// a porous crown: the glTF's mask threshold cuts it like a card's, and under MSAA alphaToCoverage turns the fraction into
|
|
77
|
+
// samples. Before the cut, the colour pass takes a COVERAGE BOOST: alpha × (1 + look.w × max(mip − 1, 0)), capped at 1,
|
|
78
|
+
// look.w = the same Foliage `coverage` the cards use. mip = log2(frameTexels / quadPixels), the atlas mip the quad
|
|
79
|
+
// actually reads: frameTexels = a frame's height in texels (the atlas' height / its frame rows), quadPixels = the quad's
|
|
80
|
+
// projected height (2r × P[1][1] / the eye's distance to the sphere's centre × the viewport's height / 2). Not the cards'
|
|
81
|
+
// rule (log2(dist / 8 m): a proxy for THEIR texture mip, whose blur and cut lose a card's coverage) — the atlas stores the
|
|
82
|
+
// TRUE coverage, exact until a frame texel shrinks under a screen pixel and its mips average a sparse crown down past
|
|
83
|
+
// the cut, so the boost starts only at the atlas' second mip. Measured 2026-11-04: the cards' rule made a far quad
|
|
84
|
+
// denser than the mesh (both / mesh only / quad only 57 / 4 / 39 against 56 / 26 / 18 without). coverage 0 = the A/B.
|
|
85
|
+
// The depth variants keep their own rule, no boost, as the cards'. (A dither of the fraction was built and rejected, 2026-11-04: the mesh over-covers its thin needles under its
|
|
86
|
+
// hard mask - mip alpha, the cards' distance boost - and against it the honest coverage read sparse, a ghost tree; the
|
|
87
|
+
// cut matched the mesh's coverage.) The depth variants (the shadow map, the prepass) keep the cards' hard rule — a
|
|
88
|
+
// texel past half the cutoff casts —, so the shadow stays solid. The normal: normalDecode(data.rg) per frame — already
|
|
60
89
|
// in object space, no basis —, blended, normalised, turned to the world by the quaternion. THE LIGHT is
|
|
61
90
|
// foliage-cards.mat's, per pixel: the ambient = the sky's SH on that normal × the exposure × the baked AO (by leaf.w) as
|
|
62
91
|
// emissive (Filament's IBL off), the sun = a wrapped Lambert (sway.w) + the back-lit translucency (leaf.z) in one scalar
|
|
63
|
-
// (gSun) that surfaceShading() multiplies by the light, its attenuation and its SHADOW (by leaf.y).
|
|
92
|
+
// (gSun) that surfaceShading() multiplies by the light, its attenuation and its SHADOW (by leaf.y). THE BARK (b = the bark
|
|
93
|
+
// map blended like the data): the sun = mix(that leaf scalar, max(n·l, 0) — no wrap, no translucency —, b), the shadow
|
|
94
|
+
// share of the map and of the self-shadow mix(leaf.y, 1, b): bark takes a shadow in full, a card its leaf.y share (as
|
|
95
|
+
// foliage.mat against foliage-cards.mat); the ambient stays SH(n) × AO, n already the bark's own normal there. The
|
|
96
|
+
// per-copy tint covers both (foliage.mat tints its bark too). The per-pixel normal
|
|
64
97
|
// also becomes Filament's shading normal, the one its lights loop tests (dot(n, l) > 0) before it reads the shadow map.
|
|
65
98
|
//
|
|
66
99
|
// THE SELF-SHADOW: a flat quad never shades itself through the shadow map, and at these distances the mesh's own
|
|
@@ -81,8 +114,8 @@
|
|
|
81
114
|
// multiplied by 1 - occl. The term is 1 with the sun under the horizon; `impostorSelfShadow` mixes it in (1 = on;
|
|
82
115
|
// glb.cpp writes the 1, CREATOR_IMPOSTOR_SELFSHADOW=0 a 0). The depth variants (the shadow map, the prepass) skip it.
|
|
83
116
|
//
|
|
84
|
-
// The engine treats it as foliage (it declares `benders`) and writes the whole foliage block; the wind
|
|
85
|
-
//
|
|
117
|
+
// The engine treats it as foliage (it declares `benders`) and writes the whole foliage block; of the wind only the
|
|
118
|
+
// plant-level bend applies (THE WIND above). `Material.foliageImpostor` in the SDK; Model.load({ foliage: 'tree' }) arms it
|
|
86
119
|
// with the bark and the cards, and the GLB provider picks it for a material whose extras carry `lecodes.impostor`
|
|
87
120
|
// ([A, cx, cy, cz, r, E] → impostorGrid = (A, E) / impostor; E = 0 or missing = the octahedron, N = the first number).
|
|
88
121
|
|
|
@@ -131,11 +164,16 @@ material {
|
|
|
131
164
|
// ---- the impostor (the GLB material's extras `lecodes.impostor` = [A, cx, cy, cz, r, E]) ----
|
|
132
165
|
{ type : float4, name : impostor, precision : high }, // (c.xyz, r): the level-0 bounding sphere, object space
|
|
133
166
|
{ type : float2, name : impostorGrid, precision : high }, // (A, E): A azimuths × E rows; (N, 0): N×N octahedron
|
|
134
|
-
{ type : float, name : impostorSelfShadow }
|
|
167
|
+
{ type : float, name : impostorSelfShadow }, // the self-shadow from the atlas: 1 on, 0 off (glb.cpp writes 1)
|
|
168
|
+
{ type : float, name : debugMode } // the impostor lab's DEBUG VIEW (glb.cpp CREATOR_FOLIAGE_DEBUG; 0 = off): 1 albedo, 2 hull normal as colour, 3 the sky term on white — the sun off in all three
|
|
135
169
|
]
|
|
136
170
|
}
|
|
137
171
|
|
|
138
172
|
vertex {
|
|
173
|
+
// the wind's plant-level bend (foliageField / foliageBend) without the cards' per-vertex half (a quad has no COLOR_0)
|
|
174
|
+
#define FOLIAGE_BEND_ONLY
|
|
175
|
+
#include "foliage-wind.vs"
|
|
176
|
+
|
|
139
177
|
// the hemi-octahedral map, [0,1]² <-> a unit direction with y ≥ 0 (the baker's TS is the same formula)
|
|
140
178
|
highp vec3 impDecode(highp vec2 uv) {
|
|
141
179
|
highp float u = uv.x * 2.0 - 1.0;
|
|
@@ -198,6 +236,11 @@ vertex {
|
|
|
198
236
|
highp vec3 up = cross(d, right);
|
|
199
237
|
highp vec3 p = (corner.x * right + corner.y * up) * r;
|
|
200
238
|
material.worldPosition.xyz = (model * vec4(c + p, 1.0)).xyz;
|
|
239
|
+
// THE WIND: the corner leans by the cards' own plant-level bend at its place (foliage-wind.vs foliageBend: the
|
|
240
|
+
// field at the root, the rotation growing with the distance from it) - the drawn quad only; p, the frame pick and
|
|
241
|
+
// the lighting data keep the still tree
|
|
242
|
+
highp vec3 uOrigin = (getUserWorldFromWorldMatrix() * vec4(model[3].xyz, 1.0)).xyz;
|
|
243
|
+
material.worldPosition.xyz = foliageBend(material.worldPosition.xyz, model[3].xyz, foliageField(uOrigin, getUserTime().x));
|
|
201
244
|
#if defined(HAS_ATTRIBUTE_TANGENTS)
|
|
202
245
|
// the quad's geometric normal faces the eye (the shadow lookup's normal bias reads it; the mesh's NORMAL is a dummy)
|
|
203
246
|
highp float dwl = length(dW);
|
|
@@ -238,7 +281,6 @@ vertex {
|
|
|
238
281
|
}
|
|
239
282
|
|
|
240
283
|
// the cards' per-instance tint hash, bit for bit (foliage-wind.vs: the USER world origin of the instance)
|
|
241
|
-
highp vec3 uOrigin = (getUserWorldFromWorldMatrix() * vec4(model[3].xyz, 1.0)).xyz;
|
|
242
284
|
float hash = fract(sin(dot(uOrigin.xz, vec2(12.9898, 78.233))) * 43758.5453);
|
|
243
285
|
// the distance fade (foliage-wind.vs `alive`, measured to the sphere's centre)
|
|
244
286
|
float fadeEnd = materialParams.fade.y;
|
|
@@ -247,15 +289,23 @@ vertex {
|
|
|
247
289
|
// packed i·E + k (grid) / i·N + j (octahedron: E = N)
|
|
248
290
|
material.impFrames = vec4(f0.x * E + f0.y, f1.x * E + f1.y, f2.x * E + f2.y, hash);
|
|
249
291
|
material.impPos = vec4(p, w.x);
|
|
250
|
-
|
|
292
|
+
// the coverage boost's term: the atlas mip the quad reads, past the first (the header) — a frame's texels against
|
|
293
|
+
// the quad's projected height (2r in the world, the model's scale included, at the eye's distance to the centre)
|
|
294
|
+
highp float frameTexels = float(textureSize(materialParams_baseColorMap, 0).y) / E;
|
|
295
|
+
highp float rW = r * length(model[1].xyz);
|
|
296
|
+
highp float quadPixels = 2.0 * rW * getClipFromViewMatrix()[1][1] / max(length(dW), 1e-3) * frameUniforms.resolution.y * 0.5;
|
|
297
|
+
float lodEst = max(log2(frameTexels / max(quadPixels, 1e-3)) - 1.0, 0.0);
|
|
298
|
+
material.impW = vec4(w.y, alive, lodEst, 0.0);
|
|
251
299
|
mat3 R = mat3(normalize(model[0].xyz), normalize(model[1].xyz), normalize(model[2].xyz));
|
|
252
300
|
material.impRot = impQuat(R);
|
|
253
301
|
}
|
|
254
302
|
}
|
|
255
303
|
|
|
256
304
|
fragment {
|
|
257
|
-
// from material() for surfaceShading(): the sun's scalar at this pixel — the wrapped Lambert + the translucency
|
|
305
|
+
// from material() for surfaceShading(): the sun's scalar at this pixel — the wrapped Lambert + the translucency (the
|
|
306
|
+
// bark's plain Lambert by its share) — and the bark fraction (the shadow map's share: mix(leaf.y, 1, gBark))
|
|
258
307
|
float gSun = 0.0;
|
|
308
|
+
float gBark = 0.0;
|
|
259
309
|
|
|
260
310
|
highp vec3 impDecode(highp vec2 uv) {
|
|
261
311
|
highp float u = uv.x * 2.0 - 1.0;
|
|
@@ -322,15 +372,26 @@ fragment {
|
|
|
322
372
|
// PARALLAX (the next step, not built): the data map's depth (b) exists now — the self-shadow reads it; offsetting p
|
|
323
373
|
// along the view by it before the projection would line the three frames up and cut the ghosting between them.
|
|
324
374
|
void impFrame(highp float packed, bool isGrid, highp vec2 cells, highp float r, highp vec3 p, highp vec2 inset,
|
|
325
|
-
out vec4 base, out vec4 data, out highp vec3 dk, out highp vec3 rk, out highp vec3 uk) {
|
|
375
|
+
out vec4 base, out vec4 data, out highp vec2 uv, out highp vec3 dk, out highp vec3 rk, out highp vec3 uk) {
|
|
326
376
|
highp float fi = floor(packed / cells.y + 0.5 / cells.y);
|
|
327
377
|
highp float fk = packed - fi * cells.y;
|
|
328
378
|
impBasis(fi, fk, isGrid, cells, dk, rk, uk);
|
|
329
|
-
|
|
379
|
+
uv = impUv(fi, fk, cells, dot(p, rk) / r, dot(p, uk) / r, inset);
|
|
330
380
|
base = texture(materialParams_baseColorMap, uv);
|
|
331
381
|
data = texture(materialParams_metallicRoughnessMap, uv);
|
|
332
382
|
}
|
|
333
383
|
|
|
384
|
+
// a frame's bark fraction (the occlusion map's r; g = 0 says the map is there — the default white reads 0)
|
|
385
|
+
float impBark(highp vec2 uv) {
|
|
386
|
+
vec2 o = texture(materialParams_occlusionMap, uv).rg;
|
|
387
|
+
return o.r * (1.0 - o.g);
|
|
388
|
+
}
|
|
389
|
+
// a frame's bark occlusion (the occlusion map's b; 0 = an atlas without the channel, and the default white, read 1)
|
|
390
|
+
float impBarkOcc(highp vec2 uv) {
|
|
391
|
+
float b = texture(materialParams_occlusionMap, uv).b;
|
|
392
|
+
return b > 0.0 ? b : 1.0;
|
|
393
|
+
}
|
|
394
|
+
|
|
334
395
|
// the sky's SH irradiance on n (foliage-cards.mat's shIrradianceV; Filament's own is not in every variant's fragment)
|
|
335
396
|
vec3 impSky(const vec3 n) {
|
|
336
397
|
return max(
|
|
@@ -386,9 +447,10 @@ fragment {
|
|
|
386
447
|
|
|
387
448
|
vec4 b0, b1, b2, t0, t1, t2;
|
|
388
449
|
highp vec3 d0, d1, d2, r0, r1, r2, u0, u1, u2;
|
|
389
|
-
|
|
390
|
-
impFrame(fr.
|
|
391
|
-
impFrame(fr.
|
|
450
|
+
highp vec2 uv0, uv1, uv2;
|
|
451
|
+
impFrame(fr.x, isGrid, cells, r, p, inset, b0, t0, uv0, d0, r0, u0);
|
|
452
|
+
impFrame(fr.y, isGrid, cells, r, p, inset, b1, t1, uv1, d1, r1, u1);
|
|
453
|
+
impFrame(fr.z, isGrid, cells, r, p, inset, b2, t2, uv2, d2, r2, u2);
|
|
392
454
|
vec4 base = (b0 * w.x + b1 * w.y + b2 * w.z) * materialParams.baseColorFactor;
|
|
393
455
|
// the distance fade thins the tree like the cards (the mask threshold does the cutting)
|
|
394
456
|
base.a *= variable_impW.y;
|
|
@@ -398,7 +460,12 @@ fragment {
|
|
|
398
460
|
if (base.a >= 0.5 * cut) base.a = max(base.a, cut);
|
|
399
461
|
material.baseColor = base;
|
|
400
462
|
#else
|
|
463
|
+
// the coverage boost (the header: the atlas' own mip past the first, the cards' `look.w`): a far crown keeps its share
|
|
464
|
+
base.a = min(base.a * (1.0 + materialParams.look.w * variable_impW.z), 1.0);
|
|
401
465
|
vec4 data = t0 * w.x + t1 * w.y + t2 * w.z;
|
|
466
|
+
float bark = clamp(impBark(uv0) * w.x + impBark(uv1) * w.y + impBark(uv2) * w.z, 0.0, 1.0);
|
|
467
|
+
gBark = bark;
|
|
468
|
+
float barkOcc = impBarkOcc(uv0) * w.x + impBarkOcc(uv1) * w.y + impBarkOcc(uv2) * w.z;
|
|
402
469
|
highp vec3 nObj = impNormal(t0) * w.x + impNormal(t1) * w.y + impNormal(t2) * w.z;
|
|
403
470
|
highp float nl = length(nObj);
|
|
404
471
|
nObj = nl > 1e-5 ? nObj / nl : d0;
|
|
@@ -417,8 +484,11 @@ fragment {
|
|
|
417
484
|
// THE LIGHT, foliage-cards.mat's per-vertex model at the pixel: the sky's SH on the crown's normal darkened by the
|
|
418
485
|
// baked sky visibility (leaf.w), and the sun's wrapped Lambert + translucency for surfaceShading()
|
|
419
486
|
float vao = data.a;
|
|
420
|
-
|
|
421
|
-
|
|
487
|
+
// the bark's share also takes its own occlusion in full (foliage.mat: occ x cavity on the ambient, the sky
|
|
488
|
+
// visibility alone by leaf.w) - measured 2026-11-04 on a Chernobyl pine: without it the quad's trunk read 15 % brighter
|
|
489
|
+
float ao = mix(1.0, vao, clamp(materialParams.leaf.w, 0.0, 1.0)) * mix(1.0, barkOcc, bark);
|
|
490
|
+
// + the sky through the leaf on the cards' share (foliage-cards.mat look.x), the bark's share opaque
|
|
491
|
+
vec3 ambient = (impSky(n) + materialParams.look.x * (1.0 - bark) * impSky(-n)) * frameUniforms.iblLuminance * ao;
|
|
422
492
|
highp vec3 l = frameUniforms.lightDirection;
|
|
423
493
|
highp vec3 v = getWorldViewVector();
|
|
424
494
|
float wrap = clamp(materialParams.sway.w, 0.0, 1.0);
|
|
@@ -426,7 +496,8 @@ fragment {
|
|
|
426
496
|
float back = max(-dot(n, l), 0.0);
|
|
427
497
|
float vl = pow(max(dot(-v, l), 0.0), 4.0);
|
|
428
498
|
float through = clamp(materialParams.leaf.z, 0.0, 1.0) * back * (0.5 + 0.5 * vl) * mix(1.0, vao, 0.5);
|
|
429
|
-
|
|
499
|
+
// the bark's share: a plain Lambert on its own normal (foliage.mat's opaque variant: no wrap, no translucency)
|
|
500
|
+
gSun = mix(NoL + through, max(dot(n, l), 0.0), bark);
|
|
430
501
|
|
|
431
502
|
// THE SELF-SHADOW from the atlas: the frames around the sun are the tree's own shadow map (the header). The
|
|
432
503
|
// pixel's surface point (offset from c, object space), the three view frames' points blended by their weights:
|
|
@@ -463,8 +534,8 @@ fragment {
|
|
|
463
534
|
bool inSun = lo.y >= 0.0; // the sun above the horizon
|
|
464
535
|
float selfShadow = inSun ? 1.0 - occl : 1.0;
|
|
465
536
|
// by the cards' share of the shadow map (leaf.y, foliage-cards.mat surfaceShading: a crown never goes black under its
|
|
466
|
-
// own shadow)
|
|
467
|
-
selfShadow = mix(1.0, selfShadow, materialParams.impostorSelfShadow * clamp(materialParams.leaf.y, 0.0, 1.0));
|
|
537
|
+
// own shadow) — the bark's in full —, and the knob
|
|
538
|
+
selfShadow = mix(1.0, selfShadow, materialParams.impostorSelfShadow * mix(clamp(materialParams.leaf.y, 0.0, 1.0), 1.0, bark));
|
|
468
539
|
gSun *= selfShadow;
|
|
469
540
|
|
|
470
541
|
material.baseColor = base;
|
|
@@ -473,12 +544,18 @@ fragment {
|
|
|
473
544
|
material.reflectance = 0.0;
|
|
474
545
|
material.ambientOcclusion = 0.0; // Filament's IBL off: the ambient is ours, as emissive
|
|
475
546
|
material.emissive = vec4(base.rgb * ambient, 0.0);
|
|
547
|
+
// THE DEBUG VIEW (foliage-cards.mat's): 1 = the albedo, 2 = the hull normal (world) ×½+½, 3 = the sky term on
|
|
548
|
+
// white; the sun off (surfaceShading returns 0), the alpha untouched
|
|
549
|
+
float dbg = materialParams.debugMode;
|
|
550
|
+
if (dbg > 0.5) material.emissive = vec4(dbg < 1.5 ? base.rgb : dbg < 2.5 ? n * 0.5 + 0.5 : ambient, 0.0);
|
|
476
551
|
#endif
|
|
477
552
|
}
|
|
478
553
|
|
|
479
554
|
// PER LIGHT (the sun first): the pixel's sun scalar times the light and its SHADOW, by leaf.y — foliage-cards.mat's rule
|
|
555
|
+
// — on the cards' share, in full on the bark's (foliage.mat)
|
|
480
556
|
vec3 surfaceShading(const MaterialInputs materialInputs, const ShadingData shadingData, const LightData lightData) {
|
|
481
|
-
|
|
557
|
+
if (materialParams.debugMode > 0.5) return vec3(0.0); // the debug view: no sun
|
|
558
|
+
float lit = mix(1.0, lightData.visibility, mix(clamp(materialParams.leaf.y, 0.0, 1.0), 1.0, gBark));
|
|
482
559
|
float reach = lightData.colorIntensity.w * lightData.attenuation * lit;
|
|
483
560
|
return shadingData.diffuseColor * lightData.colorIntensity.rgb * (reach * gSun / PI);
|
|
484
561
|
}
|
|
@@ -97,7 +97,10 @@ material {
|
|
|
97
97
|
// per bender: xy = travel direction on the ground (unit, or 0 when still), z = speed weight 0..1
|
|
98
98
|
// (creator-gl smooths the velocity over ~0.2 s) — near a plant's centre the lean follows the
|
|
99
99
|
// travel direction instead of the radial one, so crossing a bush never flips it (2026-09-12)
|
|
100
|
-
{ type : float4[8], name : benderMotion }
|
|
100
|
+
{ type : float4[8], name : benderMotion },
|
|
101
|
+
// the impostor lab's DEBUG VIEW (glb.cpp CREATOR_FOLIAGE_DEBUG; 0 = off): 1 albedo, 2 the crown's hull normal as
|
|
102
|
+
// colour (what the impostor atlas stores for every texel, bark included), 3 the sky term on white — the sun off
|
|
103
|
+
{ type : float, name : debugMode }
|
|
101
104
|
]
|
|
102
105
|
}
|
|
103
106
|
|
|
@@ -192,6 +195,13 @@ fragment {
|
|
|
192
195
|
material.reflectance = 0.35; // a faint sheen on the bark under grazing light (f0 ≈ 0.02; the lobe is in surfaceShading)
|
|
193
196
|
material.ambientOcclusion = 0.0; // Filament's IBL off (the header of lightmap.mat)
|
|
194
197
|
material.emissive = vec4(color + glow, 0.0);
|
|
198
|
+
float dbg = materialParams.debugMode;
|
|
199
|
+
if (dbg > 0.5) {
|
|
200
|
+
vec3 hn = variable_radial.xyz;
|
|
201
|
+
float hl = length(hn);
|
|
202
|
+
hn = hl > 1e-5 ? hn / hl : vec3(0.0, 1.0, 0.0);
|
|
203
|
+
material.emissive = vec4(dbg < 1.5 ? base.rgb : dbg < 2.5 ? hn * 0.5 + 0.5 : shIrradiance(ns) * ao * frameUniforms.iblLuminance, 0.0);
|
|
204
|
+
}
|
|
195
205
|
}
|
|
196
206
|
|
|
197
207
|
// PER LIGHT (Filament's loop: the sun first, then the lamps in range), with the light's shadow in `visibility` -
|
|
@@ -201,6 +211,7 @@ fragment {
|
|
|
201
211
|
// faces the viewer, double-sided) narrowed toward the view line into the light, as HDRP's thin-surface transmission
|
|
202
212
|
// does.
|
|
203
213
|
vec3 surfaceShading(const MaterialInputs materialInputs, const ShadingData shadingData, const LightData lightData) {
|
|
214
|
+
if (materialParams.debugMode > 0.5) return vec3(0.0); // the debug view: no sun
|
|
204
215
|
vec3 ns = gNs;
|
|
205
216
|
vec3 v = getWorldViewVector();
|
|
206
217
|
vec3 l = lightData.l;
|
|
@@ -144,9 +144,12 @@ vertex {
|
|
|
144
144
|
// beside it (a 1.5 m bush with the open sky's SH on its top was a lamp in the shade - the owner, 2026-10-18);
|
|
145
145
|
// the open sky is reached at the crowns' level, CANOPY metres up
|
|
146
146
|
up = clamp((f.uwp.y - f.uOrigin.y) / CANOPY, 0.0, 1.0);
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
147
|
+
// the ground's share takes NO vertex AO: the terrain texel under the plant already carries the plant's own
|
|
148
|
+
// baked shadow (a fern's centre read its self-occlusion three times over - the ground's E, the down-facing
|
|
149
|
+
// hull's quarter and its own vao - and went black under an overcast sky, 2026-11-04); the open sky's share
|
|
150
|
+
// keeps it, that is the vertex's own depth in the plant
|
|
151
|
+
ambient = mix(groundAmbient, skyAmbient * ao, up);
|
|
152
|
+
} else ambient *= ao;
|
|
150
153
|
vec3 l = frameUniforms.lightDirection;
|
|
151
154
|
vec3 v = normalize(getWorldCameraPosition() - f.wp);
|
|
152
155
|
float wrap = clamp(materialParams.sway.w, 0.0, 1.0);
|