lecodes-cli 0.20.0 → 0.20.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/dist/index.js +1065 -577
- package/package.json +7 -7
- package/runtime/materials/decal-relief.mat +178 -0
- package/runtime/materials/decal.mat +172 -0
- package/runtime/materials/lightmap-baked-lite.mat +175 -0
- package/runtime/materials/lightmap-baked.mat +176 -0
- package/runtime/materials/lightmap.mat +181 -0
- package/runtime/materials/lit.mat +43 -0
- package/runtime/materials/particles-quad.mat +179 -0
- package/runtime/materials/particles.mat +129 -0
- package/runtime/materials/shadow.mat +21 -0
- package/runtime/materials/terrain.mat +134 -0
- package/runtime/materials/unlit-transparent.mat +42 -0
- package/runtime/materials/unlit.mat +44 -0
- package/runtime/materials/video.mat +31 -0
- package/runtime/scene-harness.json +1 -1
- package/runtime/sdk-types.json +1 -1
- package/runtime/web/assets/{createViewerLite-BvHobVhp.js → createViewerLite-Ct_PZdof.js} +22 -22
- package/runtime/web/assets/{index-CfNCCBjN.js → index-BMt7AnC5.js} +1 -1
- package/runtime/web/embed.js +2 -2
- package/src/cmgenTool.ts +17 -9
- package/src/commands/appDesktop.ts +12 -2
- package/src/commands/appShared.ts +48 -0
- package/src/commands/assets.ts +3 -7
- package/src/commands/design.ts +9 -26
- package/src/commands/desktop.ts +3 -0
- package/src/commands/dev.ts +42 -0
- package/src/commands/projectTemplates.ts +13 -1
- package/src/commands/render.ts +4 -8
- package/src/commands/scene.ts +2 -6
- package/src/commands/shaders.ts +17 -1
- package/src/commands/shadersNew.ts +133 -0
- package/src/commands/thumbs.ts +3 -6
- package/src/commands/update.ts +42 -21
- package/src/desktopRenderer.ts +17 -0
- package/src/desktopScript.ts +1 -1
- package/src/dev/androidDev.ts +154 -0
- package/src/dev/devServer.ts +8 -2
- package/src/index.ts +21 -4
- package/src/matcTool.ts +31 -19
- package/src/peerInstall.ts +160 -0
- package/src/peers.ts +48 -14
- package/src/projectEnv.ts +4 -0
- package/src/releases.ts +16 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// lightmap-baked: the lightmap material's CHEAP twin (SDK `Material.lightmapShading = "baked"`, a
|
|
2
|
+
// graphics-quality tier). Same parameters, same atlas / volume reads, the same picture on a level whose
|
|
3
|
+
// light is baked — but an UNLIT shading model with the lighting written out by hand, so the fragment
|
|
4
|
+
// skips filament's lit path (prefiltered-cubemap IBL specular + DFG LUT, the froxel loop over dynamic
|
|
5
|
+
// lights, sun shadows, energy compensation, multi-bounce AO).
|
|
6
|
+
//
|
|
7
|
+
// Measured on fps-demo's shooting range (2146 lightmapped props, 1280x720, integrated GPU): the lit
|
|
8
|
+
// material renders at ~62 fps, this one at ~91 — the same +45 % a no-shading stub gave, so the lit
|
|
9
|
+
// path was the cost. Filtering the light variants out of the lit material (variantFilter) gave only
|
|
10
|
+
// +10 %: the IBL evaluation is the expensive half, not the light loops. Mean frame brightness matches
|
|
11
|
+
// the lit material within 1 %.
|
|
12
|
+
//
|
|
13
|
+
// What it drops, deliberately:
|
|
14
|
+
// - real-time point lights: a muzzle flash / fireball no longer lights the level (the baked lamps
|
|
15
|
+
// still do — they come from the atlas / volume, not the light list);
|
|
16
|
+
// - the sun is unshadowed Lambert × the atlas' sun mask (R) — on a DYNAMIC object no shadows;
|
|
17
|
+
// - IBL specular is approximated: the SH irradiance at the reflected direction × an analytic DFG
|
|
18
|
+
// (Karis' EnvBRDFApprox) — right for rough surfaces, a blurred reflection on mirrors.
|
|
19
|
+
// Two traps, learnt the hard way (probes in the 2026-09-07 session):
|
|
20
|
+
// - unlit has NO vertex tangent frame (MATERIAL_NEEDS_TBN is lit-only): getWorldTangentFrame() returns
|
|
21
|
+
// garbage, NoV collapses to 0 and the specular lobe explodes to white. The frame is built here from
|
|
22
|
+
// screen-space derivatives of position and uv0 (Schueler's cotangent frame);
|
|
23
|
+
// - the SH coefficients are NOT pre-exposed (the CPU stores them raw); filament multiplies its Fd/Fr by
|
|
24
|
+
// iblLuminance in the shader, so this one does too.
|
|
25
|
+
// Keep the parameter list IDENTICAL to lightmap.mat: creator-gl's lightmapSetOptions / bindVolume set
|
|
26
|
+
// every one of them on every lightmap-material instance, and an undeclared uniform is a panic.
|
|
27
|
+
|
|
28
|
+
material {
|
|
29
|
+
name : lightmap_baked,
|
|
30
|
+
shadingModel : unlit,
|
|
31
|
+
doubleSided : true,
|
|
32
|
+
flipUV : false,
|
|
33
|
+
requires : [ uv0, uv1, tangents ],
|
|
34
|
+
parameters : [
|
|
35
|
+
{ type : float4, name : baseColorFactor },
|
|
36
|
+
{ type : sampler2d, name : baseColorMap },
|
|
37
|
+
{ type : float, name : metallicFactor },
|
|
38
|
+
{ type : float, name : roughnessFactor },
|
|
39
|
+
{ type : float3, name : emissiveFactor },
|
|
40
|
+
{ type : float, name : normalScale },
|
|
41
|
+
{ type : float, name : aoStrength },
|
|
42
|
+
{ type : sampler2d, name : normalMap },
|
|
43
|
+
{ type : float, name : hasNormalMap },
|
|
44
|
+
{ type : sampler2d, name : metallicRoughnessMap },
|
|
45
|
+
{ type : sampler2d, name : occlusionMap },
|
|
46
|
+
{ type : sampler2d, name : emissiveMap },
|
|
47
|
+
{ type : sampler2d, name : lightmap },
|
|
48
|
+
{ type : sampler2d, name : lightmapLight },
|
|
49
|
+
{ type : float, name : lightScale },
|
|
50
|
+
{ type : float, name : lightGamma },
|
|
51
|
+
{ type : float4, name : lightmapST },
|
|
52
|
+
{ type : float, name : ambientScale },
|
|
53
|
+
{ type : float, name : sunStrength },
|
|
54
|
+
{ type : float, name : bakedAo },
|
|
55
|
+
{ type : sampler3d, name : probeVolume },
|
|
56
|
+
{ type : sampler3d, name : probeLight },
|
|
57
|
+
{ type : float4, name : probeMin },
|
|
58
|
+
{ type : float4, name : probeInvSize }
|
|
59
|
+
]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
fragment {
|
|
63
|
+
// filament's SH irradiance (Lambert baked into the coefficients, like Irradiance_SphericalHarmonics)
|
|
64
|
+
vec3 shIrradiance(const vec3 n) {
|
|
65
|
+
return max(
|
|
66
|
+
frameUniforms.iblSH[0].xyz
|
|
67
|
+
+ frameUniforms.iblSH[1].xyz * n.y + frameUniforms.iblSH[2].xyz * n.z + frameUniforms.iblSH[3].xyz * n.x
|
|
68
|
+
+ frameUniforms.iblSH[4].xyz * (n.y * n.x) + frameUniforms.iblSH[5].xyz * (n.y * n.z)
|
|
69
|
+
+ frameUniforms.iblSH[6].xyz * (3.0 * n.z * n.z - 1.0) + frameUniforms.iblSH[7].xyz * (n.z * n.x)
|
|
70
|
+
+ frameUniforms.iblSH[8].xyz * (n.x * n.x - n.y * n.y), 0.0);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Karis' analytic DFG (UE4 mobile): the split-sum's E term without the LUT
|
|
74
|
+
vec3 envBrdfApprox(const vec3 f0, float roughness, float NoV) {
|
|
75
|
+
vec4 c0 = vec4(-1.0, -0.0275, -0.572, 0.022);
|
|
76
|
+
vec4 c1 = vec4(1.0, 0.0425, 1.04, -0.04);
|
|
77
|
+
vec4 r = roughness * c0 + c1;
|
|
78
|
+
float a004 = min(r.x * r.x, exp2(-9.28 * NoV)) * r.x + r.y;
|
|
79
|
+
vec2 ab = vec2(-1.04, 1.04) * a004 + r.zw;
|
|
80
|
+
return f0 * ab.x + ab.y;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
void material(inout MaterialInputs material) {
|
|
84
|
+
prepareMaterial(material);
|
|
85
|
+
vec4 base = texture(materialParams_baseColorMap, getUV0()) * materialParams.baseColorFactor;
|
|
86
|
+
vec2 mr = texture(materialParams_metallicRoughnessMap, getUV0()).bg;
|
|
87
|
+
float metallic = materialParams.metallicFactor * mr.x;
|
|
88
|
+
float roughness = materialParams.roughnessFactor * mr.y;
|
|
89
|
+
vec3 glow = texture(materialParams_emissiveMap, getUV0()).rgb * materialParams.emissiveFactor;
|
|
90
|
+
float occ = 1.0 + materialParams.aoStrength * (texture(materialParams_occlusionMap, getUV0()).r - 1.0);
|
|
91
|
+
vec3 n = getWorldGeometricNormalVector();
|
|
92
|
+
vec3 ns = n;
|
|
93
|
+
if (materialParams.hasNormalMap > 0.5) {
|
|
94
|
+
vec3 tn = texture(materialParams_normalMap, getUV0()).xyz * 2.0 - 1.0;
|
|
95
|
+
tn.xy *= materialParams.normalScale;
|
|
96
|
+
// unlit has no vertex tangent frame (MATERIAL_NEEDS_TBN is lit-only): build one from the
|
|
97
|
+
// screen-space derivatives of position and uv0 (Schueler's cotangent frame)
|
|
98
|
+
highp vec3 p = getWorldPosition();
|
|
99
|
+
highp vec2 uv = getUV0();
|
|
100
|
+
highp vec3 dp1 = dFdx(p), dp2 = dFdy(p);
|
|
101
|
+
highp vec2 duv1 = dFdx(uv), duv2 = dFdy(uv);
|
|
102
|
+
highp vec3 dp2perp = cross(dp2, n), dp1perp = cross(n, dp1);
|
|
103
|
+
highp vec3 T = dp2perp * duv1.x + dp1perp * duv2.x;
|
|
104
|
+
highp vec3 B = dp2perp * duv1.y + dp1perp * duv2.y;
|
|
105
|
+
highp float invmax = inversesqrt(max(dot(T, T), dot(B, B)));
|
|
106
|
+
ns = normalize(mat3(T * invmax, B * invmax, n) * tn);
|
|
107
|
+
}
|
|
108
|
+
vec3 v = getWorldViewVector();
|
|
109
|
+
float NoV = max(dot(ns, v), 1e-4);
|
|
110
|
+
vec3 luma = vec3(0.2126, 0.7152, 0.0722);
|
|
111
|
+
float lg = materialParams.lightGamma > 0.0 ? materialParams.lightGamma : 1.0;
|
|
112
|
+
|
|
113
|
+
// the baked lamps: the volume for a dynamic object, the atlas for a static — as lightmap.mat
|
|
114
|
+
vec2 lm;
|
|
115
|
+
vec3 baked;
|
|
116
|
+
vec3 l;
|
|
117
|
+
if (materialParams.probeInvSize.w > 0.5) {
|
|
118
|
+
vec3 p = getUserWorldPosition() + n * materialParams.probeMin.w;
|
|
119
|
+
vec3 uvw = (p - materialParams.probeMin.xyz) * materialParams.probeInvSize.xyz;
|
|
120
|
+
lm = texture(materialParams_probeVolume, uvw).rg;
|
|
121
|
+
float nz = materialParams.probeInvSize.w;
|
|
122
|
+
float wz = clamp(uvw.z, 0.5 / nz, 1.0 - 0.5 / nz);
|
|
123
|
+
vec3 fx = vec3(uvw.xy, ((ns.x >= 0.0 ? 0.0 : 1.0) + wz) / 6.0);
|
|
124
|
+
vec3 fy = vec3(uvw.xy, ((ns.y >= 0.0 ? 2.0 : 3.0) + wz) / 6.0);
|
|
125
|
+
vec3 fz = vec3(uvw.xy, ((ns.z >= 0.0 ? 4.0 : 5.0) + wz) / 6.0);
|
|
126
|
+
vec3 ex = pow(texture(materialParams_probeLight, fx).rgb, vec3(lg));
|
|
127
|
+
vec3 ey = pow(texture(materialParams_probeLight, fy).rgb, vec3(lg));
|
|
128
|
+
vec3 ez = pow(texture(materialParams_probeLight, fz).rgb, vec3(lg));
|
|
129
|
+
vec3 w2 = ns * ns;
|
|
130
|
+
baked = (w2.x * ex + w2.y * ey + w2.z * ez) * materialParams.lightScale;
|
|
131
|
+
vec3 dirE = vec3(sign(ns.x) * dot(ex, luma), sign(ns.y) * dot(ey, luma), sign(ns.z) * dot(ez, luma));
|
|
132
|
+
l = dot(dirE, dirE) > 1e-12 ? normalize(dirE) : ns;
|
|
133
|
+
} else {
|
|
134
|
+
vec2 luv = getUV1() * materialParams.lightmapST.xy + materialParams.lightmapST.zw;
|
|
135
|
+
lm = texture(materialParams_lightmap, luv).rg;
|
|
136
|
+
baked = pow(texture(materialParams_lightmapLight, luv).rgb, vec3(lg)) * materialParams.lightScale;
|
|
137
|
+
l = ns;
|
|
138
|
+
}
|
|
139
|
+
lm.g = mix(1.0, lm.g, materialParams.bakedAo);
|
|
140
|
+
float ao = lm.g * occ;
|
|
141
|
+
float vis = mix(1.0, lm.r, materialParams.sunStrength);
|
|
142
|
+
|
|
143
|
+
vec3 diffuseColor = base.rgb * (1.0 - metallic);
|
|
144
|
+
vec3 f0 = mix(vec3(0.04), base.rgb, metallic);
|
|
145
|
+
vec3 E = envBrdfApprox(f0, roughness, NoV);
|
|
146
|
+
|
|
147
|
+
vec3 ambD = diffuseColor * shIrradiance(ns) * (1.0 - E) * ao * frameUniforms.iblLuminance;
|
|
148
|
+
vec3 ambS = E * shIrradiance(reflect(-v, ns)) * ao * frameUniforms.iblLuminance;
|
|
149
|
+
vec3 color = (ambD + ambS) * materialParams.ambientScale;
|
|
150
|
+
|
|
151
|
+
// the sun (a level with one): Lambert × the atlas' visibility mask; pre-exposed like filament's own
|
|
152
|
+
float sunW = frameUniforms.lightColorIntensity.w;
|
|
153
|
+
if (sunW > 0.0) {
|
|
154
|
+
float NoL = max(dot(ns, frameUniforms.lightDirection), 0.0);
|
|
155
|
+
color += diffuseColor * frameUniforms.lightColorIntensity.rgb * (sunW * NoL * vis / PI);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// the baked lamps: diffuse + the cheap specular lobe lightmap.mat also uses (physical units × exposure)
|
|
159
|
+
{
|
|
160
|
+
vec3 h = normalize(v + l);
|
|
161
|
+
float NoL = max(dot(ns, l), 0.0);
|
|
162
|
+
float NoH = max(dot(ns, h), 0.0);
|
|
163
|
+
float LoH = max(dot(l, h), 0.0);
|
|
164
|
+
float pr = max(roughness, 0.5);
|
|
165
|
+
float a2 = pr * pr;
|
|
166
|
+
a2 *= a2;
|
|
167
|
+
float dd = NoH * NoH * (a2 - 1.0) + 1.0;
|
|
168
|
+
float D = a2 / (PI * dd * dd);
|
|
169
|
+
float V = 0.5 / max(NoL * sqrt(NoV * NoV * (1.0 - a2) + a2) + NoV * sqrt(NoL * NoL * (1.0 - a2) + a2), 1e-4);
|
|
170
|
+
vec3 F = f0 + (1.0 - f0) * pow(1.0 - LoH, 5.0);
|
|
171
|
+
color += (diffuseColor / PI + D * V * F) * baked * getExposure();
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
material.baseColor = vec4(color + glow, 1.0);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
// lightmap — a lit PBR material that multiplies a BAKED shadow mask into the result and adds the
|
|
2
|
+
// BAKED direct light of the level's point lights (docs/lightmap-plan.md §3). gltfio hands GLB
|
|
3
|
+
// primitives this material instead of the ubershader when the SDK loads a model with
|
|
4
|
+
// `lightmap: true` (LightmapMaterialProvider in creator-gl/src/glb.cpp), so the base-colour
|
|
5
|
+
// parameters use gltfio's own names and the AssetLoader fills them itself. Mesh receivers
|
|
6
|
+
// (Mesh.plane) get it from the SDK directly.
|
|
7
|
+
//
|
|
8
|
+
// Atlas channels: R = sun visibility, G = ambient occlusion. G goes to ambientOcclusion, which
|
|
9
|
+
// filament applies to the IBL term exactly (diffuse + specular AO). R attenuates the sun through
|
|
10
|
+
// postLightingColor (multiply) — it scales the WHOLE lit result, so `k` keeps the ambient share
|
|
11
|
+
// alive in shadow: k = (ambient·G + sun·NoL·vis) / (ambient·G + sun·NoL), diffuse-exact.
|
|
12
|
+
// The real-time sun still lights this surface, so dynamic casters keep their shadow-map shadows.
|
|
13
|
+
//
|
|
14
|
+
// The LIGHT atlas (lightmapLight, same rects) holds the point lights' direct irradiance E as
|
|
15
|
+
// sRGB(E / lightScale): the bake traced colour × candela × filament's own falloff window × NoL ×
|
|
16
|
+
// shadow rays, so the baked lamp matches the real-time one it replaces (the SDK switches the
|
|
17
|
+
// real-time light off once the bake applies). It reaches the pixel as emissive = diffuseColor ×
|
|
18
|
+
// E / π, exposure-weighted — the diffuse term filament's own punctual path would have produced.
|
|
19
|
+
// No specular from baked lights (v1).
|
|
20
|
+
//
|
|
21
|
+
// Sampler budget: feature level 1 allows 9 per material (16 minus filament's 7 for a lit shader);
|
|
22
|
+
// this one uses all 9 — nothing else can be added without dropping one.
|
|
23
|
+
material {
|
|
24
|
+
name : lightmap,
|
|
25
|
+
shadingModel : lit,
|
|
26
|
+
doubleSided : true,
|
|
27
|
+
// glTF's UV origin is top-left, like gltfio's ubershader (base.mat.in): no flip — filament's
|
|
28
|
+
// default (true) would mirror both the props' base colour and the atlas rects vertically.
|
|
29
|
+
flipUV : false,
|
|
30
|
+
requires : [ uv0, uv1 ],
|
|
31
|
+
postLightingBlending : multiply,
|
|
32
|
+
parameters : [
|
|
33
|
+
{ type : float4, name : baseColorFactor },
|
|
34
|
+
{ type : sampler2d, name : baseColorMap },
|
|
35
|
+
{ type : float, name : metallicFactor },
|
|
36
|
+
{ type : float, name : roughnessFactor },
|
|
37
|
+
{ type : float3, name : emissiveFactor },
|
|
38
|
+
// set unconditionally by gltfio's AssetLoader (a missing uniform is a PANIC, not a no-op)
|
|
39
|
+
{ type : float, name : normalScale },
|
|
40
|
+
{ type : float, name : aoStrength },
|
|
41
|
+
// glTF normal map on UV0 (the AssetLoader binds it by name); hasNormalMap gates the read —
|
|
42
|
+
// the provider sets it from the material key, Mesh receivers leave it 0
|
|
43
|
+
{ type : sampler2d, name : normalMap },
|
|
44
|
+
{ type : float, name : hasNormalMap },
|
|
45
|
+
// glTF metal-rough (B = metallic, G = roughness), occlusion (R) and emissive maps on UV0 —
|
|
46
|
+
// bound by the AssetLoader by name; absent = the shared 1x1 white, which is each map's
|
|
47
|
+
// identity under its factor (metallic 0 x 1, roughness x 1, AO 1, emissive 0 x 1)
|
|
48
|
+
{ type : sampler2d, name : metallicRoughnessMap },
|
|
49
|
+
{ type : sampler2d, name : occlusionMap },
|
|
50
|
+
{ type : sampler2d, name : emissiveMap },
|
|
51
|
+
{ type : sampler2d, name : lightmap },
|
|
52
|
+
// the point lights' baked irradiance, sRGB(E / lightScale) on the same rects; lightScale 0 = none
|
|
53
|
+
{ type : sampler2d, name : lightmapLight },
|
|
54
|
+
{ type : float, name : lightScale },
|
|
55
|
+
// the light store's curve: texel = sRGB(pow(E / lightScale, 1 / lightGamma)). 0/1 = the linear v2
|
|
56
|
+
// store, 2 = the square-root store bakes write since 2026-08-31 (headroom for the 1/d² peaks)
|
|
57
|
+
{ type : float, name : lightGamma },
|
|
58
|
+
// uv1 * st.xy + st.zw = the instance's rectangle of the atlas
|
|
59
|
+
{ type : float4, name : lightmapST },
|
|
60
|
+
// multiplier on the ambient (IBL) share in the shadow math: 1 = filament's own shadow
|
|
61
|
+
// darkness, >1 lighter shadows, <1 deeper
|
|
62
|
+
{ type : float, name : ambientScale },
|
|
63
|
+
// 1 = baked sun shadows at full strength, 0 = R ignored (AO only)
|
|
64
|
+
{ type : float, name : sunStrength },
|
|
65
|
+
// how much of the baked ambient occlusion applies: 1 = all of it, 0 = none (an interior lit
|
|
66
|
+
// by its ambient probe alone can need less than the geometric truth)
|
|
67
|
+
{ type : float, name : bakedAo },
|
|
68
|
+
// The light VOLUME for dynamic objects (docs/lightmap-plan.md §9): the same channels on a 3D
|
|
69
|
+
// grid over the level, sampled at the fragment's world position + probeMin.w along the normal.
|
|
70
|
+
// probeInvSize.w = the grid's depth nz (>= 1) selects it over the atlas (creator-gl binds it; an
|
|
71
|
+
// atlas rect resets it to 0). probeLight = the point lights' irradiance as an AMBIENT CUBE: six
|
|
72
|
+
// face slabs (+X −X +Y −Y +Z −Z) stacked along z, depth nz * 6, blended by the normal's n²
|
|
73
|
+
{ type : sampler3d, name : probeVolume },
|
|
74
|
+
{ type : sampler3d, name : probeLight },
|
|
75
|
+
{ type : float4, name : probeMin },
|
|
76
|
+
{ type : float4, name : probeInvSize }
|
|
77
|
+
]
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
fragment {
|
|
81
|
+
void material(inout MaterialInputs material) {
|
|
82
|
+
if (materialParams.hasNormalMap > 0.5) {
|
|
83
|
+
vec3 tn = texture(materialParams_normalMap, getUV0()).xyz * 2.0 - 1.0;
|
|
84
|
+
tn.xy *= materialParams.normalScale;
|
|
85
|
+
material.normal = tn;
|
|
86
|
+
}
|
|
87
|
+
prepareMaterial(material);
|
|
88
|
+
|
|
89
|
+
material.baseColor = texture(materialParams_baseColorMap, getUV0()) * materialParams.baseColorFactor;
|
|
90
|
+
vec2 mr = texture(materialParams_metallicRoughnessMap, getUV0()).bg;
|
|
91
|
+
material.metallic = materialParams.metallicFactor * mr.x;
|
|
92
|
+
material.roughness = materialParams.roughnessFactor * mr.y;
|
|
93
|
+
vec3 glow = texture(materialParams_emissiveMap, getUV0()).rgb * materialParams.emissiveFactor;
|
|
94
|
+
float occ = 1.0 + materialParams.aoStrength * (texture(materialParams_occlusionMap, getUV0()).r - 1.0);
|
|
95
|
+
|
|
96
|
+
vec3 n = getWorldGeometricNormalVector();
|
|
97
|
+
vec3 ns = getWorldNormalVector(); // the shading normal (normal map applied)
|
|
98
|
+
vec3 luma = vec3(0.2126, 0.7152, 0.0722);
|
|
99
|
+
float lg = materialParams.lightGamma > 0.0 ? materialParams.lightGamma : 1.0;
|
|
100
|
+
vec2 lm;
|
|
101
|
+
vec3 baked; // the point lights' irradiance E(n) on this pixel, physical units
|
|
102
|
+
vec3 l; // the direction that light is taken to arrive from (the specular term below)
|
|
103
|
+
if (materialParams.probeInvSize.w > 0.5) {
|
|
104
|
+
// a dynamic object: the light volume at this pixel (trilinear, half a cell off the surface)
|
|
105
|
+
vec3 p = getUserWorldPosition() + n * materialParams.probeMin.w;
|
|
106
|
+
vec3 uvw = (p - materialParams.probeMin.xyz) * materialParams.probeInvSize.xyz;
|
|
107
|
+
lm = texture(materialParams_probeVolume, uvw).rg;
|
|
108
|
+
// the ambient cube: the face slab of each axis' sign, z clamped to the slab's own cell
|
|
109
|
+
// centres so the trilinear fetch never blends into the neighbouring face; decode each face
|
|
110
|
+
// (the store is a square root), then blend in linear irradiance by n²
|
|
111
|
+
float nz = materialParams.probeInvSize.w;
|
|
112
|
+
float wz = clamp(uvw.z, 0.5 / nz, 1.0 - 0.5 / nz);
|
|
113
|
+
vec3 fx = vec3(uvw.xy, ((ns.x >= 0.0 ? 0.0 : 1.0) + wz) / 6.0);
|
|
114
|
+
vec3 fy = vec3(uvw.xy, ((ns.y >= 0.0 ? 2.0 : 3.0) + wz) / 6.0);
|
|
115
|
+
vec3 fz = vec3(uvw.xy, ((ns.z >= 0.0 ? 4.0 : 5.0) + wz) / 6.0);
|
|
116
|
+
vec3 ex = pow(texture(materialParams_probeLight, fx).rgb, vec3(lg));
|
|
117
|
+
vec3 ey = pow(texture(materialParams_probeLight, fy).rgb, vec3(lg));
|
|
118
|
+
vec3 ez = pow(texture(materialParams_probeLight, fz).rgb, vec3(lg));
|
|
119
|
+
vec3 w2 = ns * ns;
|
|
120
|
+
baked = (w2.x * ex + w2.y * ey + w2.z * ez) * materialParams.lightScale;
|
|
121
|
+
// the dominant direction within the normal's octant: each face's energy along its axis
|
|
122
|
+
vec3 dirE = vec3(sign(ns.x) * dot(ex, luma), sign(ns.y) * dot(ey, luma), sign(ns.z) * dot(ez, luma));
|
|
123
|
+
l = dot(dirE, dirE) > 1e-12 ? normalize(dirE) : ns;
|
|
124
|
+
} else {
|
|
125
|
+
vec2 luv = getUV1() * materialParams.lightmapST.xy + materialParams.lightmapST.zw;
|
|
126
|
+
lm = texture(materialParams_lightmap, luv).rg;
|
|
127
|
+
baked = pow(texture(materialParams_lightmapLight, luv).rgb, vec3(lg)) * materialParams.lightScale;
|
|
128
|
+
l = ns; // the atlas keeps no direction: a static's lamp light is taken along its normal
|
|
129
|
+
}
|
|
130
|
+
lm.g = mix(1.0, lm.g, materialParams.bakedAo);
|
|
131
|
+
material.ambientOcclusion = lm.g * occ;
|
|
132
|
+
|
|
133
|
+
// Baked point lights on the pixel, exposed here (emissive.w = 0 adds it as-is) so the glTF
|
|
134
|
+
// emissive can share the slot. Diffuse: albedo × (1 − metallic) × E / π — filament's own punctual
|
|
135
|
+
// diffuse. Specular: the same E through a GGX lobe from `l`, so METALS take baked light too — their
|
|
136
|
+
// diffuse is zero, and a rough steel table used to render darker than the floor it stands on because
|
|
137
|
+
// only the ambient probe reached it. L_out = f_spec · E_⊥ · NoL, and E(n) = E_⊥ · NoL is what the
|
|
138
|
+
// store holds. The lobe is kept no tighter than roughness 0.5: the stored E is light gathered from
|
|
139
|
+
// a room, not one bulb, and a mirror-sharp lobe would paint it as a highlight over the whole surface.
|
|
140
|
+
vec3 diffuseColor = material.baseColor.rgb * (1.0 - material.metallic);
|
|
141
|
+
vec3 specBrdf;
|
|
142
|
+
{
|
|
143
|
+
vec3 v = getWorldViewVector();
|
|
144
|
+
vec3 h = normalize(v + l);
|
|
145
|
+
float NoL = saturate(dot(ns, l));
|
|
146
|
+
float NoV = max(dot(ns, v), 1e-4);
|
|
147
|
+
float NoH = saturate(dot(ns, h));
|
|
148
|
+
float LoH = saturate(dot(l, h));
|
|
149
|
+
float pr = max(material.roughness, 0.5);
|
|
150
|
+
float a2 = pr * pr;
|
|
151
|
+
a2 *= a2;
|
|
152
|
+
float dd = NoH * NoH * (a2 - 1.0) + 1.0;
|
|
153
|
+
float D = a2 / (PI * dd * dd);
|
|
154
|
+
float V = 0.5 / max(NoL * sqrt(NoV * NoV * (1.0 - a2) + a2) + NoV * sqrt(NoL * NoL * (1.0 - a2) + a2), 1e-4);
|
|
155
|
+
vec3 f0 = mix(vec3(0.04), material.baseColor.rgb, material.metallic);
|
|
156
|
+
vec3 F = f0 + (1.0 - f0) * pow(1.0 - LoH, 5.0);
|
|
157
|
+
specBrdf = D * V * F;
|
|
158
|
+
}
|
|
159
|
+
material.emissive = vec4(glow + (diffuseColor / PI + specBrdf) * baked * getExposure(), 0.0);
|
|
160
|
+
|
|
161
|
+
float vis = mix(1.0, lm.r, materialParams.sunStrength);
|
|
162
|
+
// The ambient share, from the SAME per-frame uniforms filament shades this pixel with
|
|
163
|
+
// (frameUniforms: the directional light + the IBL's SH, both exposure-premultiplied), so
|
|
164
|
+
// the floor is exactly the IBL-only brightness filament's own shadow map would leave.
|
|
165
|
+
vec3 sh = frameUniforms.iblSH[0].xyz
|
|
166
|
+
+ frameUniforms.iblSH[1].xyz * n.y + frameUniforms.iblSH[2].xyz * n.z + frameUniforms.iblSH[3].xyz * n.x
|
|
167
|
+
+ frameUniforms.iblSH[4].xyz * (n.y * n.x) + frameUniforms.iblSH[5].xyz * (n.y * n.z)
|
|
168
|
+
+ frameUniforms.iblSH[6].xyz * (3.0 * n.z * n.z - 1.0) + frameUniforms.iblSH[7].xyz * (n.z * n.x)
|
|
169
|
+
+ frameUniforms.iblSH[8].xyz * (n.x * n.x - n.y * n.y);
|
|
170
|
+
// diffuse terms in filament's units: IBL = albedo * E_sh * iblLuminance; sun = albedo * (color * intensity) * NoL / PI
|
|
171
|
+
float amb = max(dot(max(sh, 0.0), luma), 0.0) * frameUniforms.iblLuminance * materialParams.ambientScale * lm.g;
|
|
172
|
+
float nol = max(dot(n, frameUniforms.lightDirection), 0.0);
|
|
173
|
+
float sun = dot(frameUniforms.lightColorIntensity.rgb, luma) * frameUniforms.lightColorIntensity.w * nol / PI;
|
|
174
|
+
// the post-lighting multiply also scales the emissive slot the baked lamps ride in, so their
|
|
175
|
+
// share (same units: exposed irradiance / π) sits on both sides — a lamp in the sun's shadow
|
|
176
|
+
// is not dimmed by it
|
|
177
|
+
float lamp = dot(baked, luma) * (getExposure() / PI);
|
|
178
|
+
float k = (amb + sun * vis + lamp) / max(amb + sun + lamp, 1e-6);
|
|
179
|
+
material.postLightingColor = vec4(k, k, k, 1.0);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
|
|
2
|
+
material {
|
|
3
|
+
name : lit,
|
|
4
|
+
shadingModel : lit,
|
|
5
|
+
parameters : [
|
|
6
|
+
// The color must be passed in linear space, not sRGB.
|
|
7
|
+
// baseColor multiplies baseColorMap (glTF-style factor x map). The host primes
|
|
8
|
+
// baseColor to white and binds a 1x1 white texture to an unset baseColorMap
|
|
9
|
+
// (createMaterialInstance in creator-gl/src/material.cpp), so either half alone
|
|
10
|
+
// shows through unchanged — no useBaseColorMap branch, no unset-sampler warning.
|
|
11
|
+
{
|
|
12
|
+
type : float3,
|
|
13
|
+
name : baseColor
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
type : sampler2d,
|
|
17
|
+
name : baseColorMap
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
type : float,
|
|
21
|
+
name : roughness
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
type : float,
|
|
25
|
+
name : metallic
|
|
26
|
+
}
|
|
27
|
+
],
|
|
28
|
+
requires: [
|
|
29
|
+
uv0
|
|
30
|
+
]
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
fragment {
|
|
34
|
+
void material(inout MaterialInputs material) {
|
|
35
|
+
prepareMaterial(material);
|
|
36
|
+
|
|
37
|
+
material.baseColor = texture(materialParams_baseColorMap, getUV0());
|
|
38
|
+
material.baseColor.rgb *= materialParams.baseColor;
|
|
39
|
+
|
|
40
|
+
material.roughness = materialParams.roughness;
|
|
41
|
+
material.metallic = materialParams.metallic;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// Quad particle material — real camera-facing quads for particle systems and ribbon trails
|
|
2
|
+
// (SDK: Material.particles({ render: 'quad' | 'stretch' | 'ribbon' }); native RenderMode 1/2
|
|
3
|
+
// builds the matching vertex stream in particles.cpp). The point-sprite particles.mat stays the
|
|
4
|
+
// default: this variant exists because the vertex FORMAT changes — the fragment look (sheet /
|
|
5
|
+
// emissive / additive / softness) is identical and driven by the same uniforms.
|
|
6
|
+
//
|
|
7
|
+
// Per-vertex inputs (QuadVertex in particles.h, one interleaved buffer):
|
|
8
|
+
// COLOR — tint, multiplied into the texture
|
|
9
|
+
// CUSTOM0 — x: size (world units), y: rotation (radians, billboard only), z: opacity, w: frame
|
|
10
|
+
// CUSTOM1 — aux: velocity (stretch axis, mode 1) or strip tangent (mode 2), sim space
|
|
11
|
+
// CUSTOM2 — corner: quad ±0.5 square; ribbon (u01 - 0.5, ±0.5 side)
|
|
12
|
+
// Mode uniforms:
|
|
13
|
+
// mode — 0 camera billboard, 1 velocity-stretched, 2 ribbon strip
|
|
14
|
+
// stretch — (mode 1) extra length in world units per unit of speed: length = size + stretch·|v|
|
|
15
|
+
// Look uniforms beyond the point-sprite set:
|
|
16
|
+
// emissiveMap / emissiveColor — a second sheet (same grid) ADDED as emissiveMap.rgb × emissiveColor,
|
|
17
|
+
// keyed by the base sheet's alpha but NOT tinted by the particle colour (Unity's
|
|
18
|
+
// Particles/Standard _EmissionMap contract: a fireball is a smoke albedo that tints to
|
|
19
|
+
// black over life plus an emission sheet that keeps glowing until alpha runs out).
|
|
20
|
+
// emissiveColor defaults to 0 → the unset (white dummy) sampler adds nothing.
|
|
21
|
+
// frameBlend — 1: crossfade each frame with the next one by the fractional index (Unity's
|
|
22
|
+
// blended flipbook); 0: snap. The partner frame never wraps past the last cell.
|
|
23
|
+
// depthFade — soft particles: metres over which a sprite fades out as it cuts into the scene
|
|
24
|
+
// geometry behind it (0 = off). Reads Filament's structure (depth) buffer, see the
|
|
25
|
+
// fragment stage + creator-gl patch 0017.
|
|
26
|
+
// The offset is added to worldPosition AFTER the model transform, so size stays in world units
|
|
27
|
+
// regardless of entity scale — matching the point-sprite path. aux is stored in sim space
|
|
28
|
+
// (invWorld-multiplied when the system is world-space) and restored here by the model matrix.
|
|
29
|
+
|
|
30
|
+
material {
|
|
31
|
+
name : particles_quad,
|
|
32
|
+
shadingModel : unlit,
|
|
33
|
+
parameters : [
|
|
34
|
+
{ type : sampler2d, name : baseColorMap },
|
|
35
|
+
{ type : float2, name : sheetSize, precision : high },
|
|
36
|
+
{ type : float, name : emissive },
|
|
37
|
+
{ type : float, name : additive },
|
|
38
|
+
{ type : float, name : softness },
|
|
39
|
+
{ type : float, name : mode },
|
|
40
|
+
{ type : float, name : stretch },
|
|
41
|
+
{ type : sampler2d, name : emissiveMap },
|
|
42
|
+
{ type : float3, name : emissiveColor },
|
|
43
|
+
{ type : float, name : frameBlend },
|
|
44
|
+
{ type : float, name : depthFade }
|
|
45
|
+
],
|
|
46
|
+
// Both varyings feed the atlas uv (`custom.w` is the frame index, `quaduv.xy` the in-cell uv),
|
|
47
|
+
// so they must stay full precision — a mediump varying is already quantized on arrival, before
|
|
48
|
+
// any highp math in the fragment could help. See the note in the fragment stage.
|
|
49
|
+
variables : [
|
|
50
|
+
{ name : custom, precision : high },
|
|
51
|
+
{ name : quaduv, precision : high }
|
|
52
|
+
],
|
|
53
|
+
blending : fade,
|
|
54
|
+
culling : none,
|
|
55
|
+
requires: [ color, custom0, custom1, custom2 ]
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
vertex {
|
|
59
|
+
void materialVertex(inout MaterialVertexInputs material) {
|
|
60
|
+
float4 c0 = getCustom0();
|
|
61
|
+
float3 aux = (getWorldFromModelMatrix() * float4(getCustom1().xyz, 0.0)).xyz;
|
|
62
|
+
float2 corner = getCustom2().xy;
|
|
63
|
+
|
|
64
|
+
float3 camX = getWorldFromViewMatrix()[0].xyz;
|
|
65
|
+
float3 camY = getWorldFromViewMatrix()[1].xyz;
|
|
66
|
+
float3 camZ = getWorldFromViewMatrix()[2].xyz; // toward the camera
|
|
67
|
+
|
|
68
|
+
float m = materialParams.mode;
|
|
69
|
+
float3 offset;
|
|
70
|
+
float2 suv = corner; // softness/uv basis (pre-rotation)
|
|
71
|
+
if (m < 0.5) {
|
|
72
|
+
// camera billboard — rotate the corner, texture rides along (no fragment discard)
|
|
73
|
+
float co = cos(c0.y);
|
|
74
|
+
float si = sin(c0.y);
|
|
75
|
+
float2 rc = float2(co * corner.x - si * corner.y, si * corner.x + co * corner.y);
|
|
76
|
+
offset = (camX * rc.x + camY * rc.y) * c0.x;
|
|
77
|
+
} else if (m < 1.5) {
|
|
78
|
+
// stretched along velocity, projected to the view plane
|
|
79
|
+
float speed = length(aux);
|
|
80
|
+
float3 dir = speed > 1e-4 ? aux / speed : camY;
|
|
81
|
+
// Project the axis onto the view plane; its length (0..1 for a unit dir) is how much of
|
|
82
|
+
// the streak the viewer can see — a streak flying straight at / away from the camera
|
|
83
|
+
// must collapse to a dot, not be drawn full length along the (numerically random)
|
|
84
|
+
// residual direction.
|
|
85
|
+
float3 y = dir - camZ * dot(dir, camZ);
|
|
86
|
+
float yl = length(y);
|
|
87
|
+
y = yl > 1e-4 ? y / yl : camY;
|
|
88
|
+
float3 x = normalize(cross(y, camZ));
|
|
89
|
+
offset = x * corner.x * c0.x + y * corner.y * (c0.x + materialParams.stretch * speed * yl);
|
|
90
|
+
} else {
|
|
91
|
+
// ribbon: extrude sideways from the strip tangent; corner.x carries u along the trail
|
|
92
|
+
float3 x = cross(aux, camZ);
|
|
93
|
+
float xl = length(x);
|
|
94
|
+
x = xl > 1e-4 ? x / xl : camX;
|
|
95
|
+
offset = x * corner.y * c0.x;
|
|
96
|
+
suv = float2(0.0, corner.y); // soft falloff across the width only
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
material.worldPosition.xyz += offset;
|
|
100
|
+
material.custom = c0;
|
|
101
|
+
material.quaduv = float4(corner.x + 0.5, 0.5 - corner.y, suv.x, suv.y);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
fragment {
|
|
106
|
+
void material(inout MaterialInputs material) {
|
|
107
|
+
prepareMaterial(material);
|
|
108
|
+
|
|
109
|
+
// `highp` throughout the atlas-uv path: matc's `-p mobile` profile lowers unqualified floats
|
|
110
|
+
// to `mediump`, which Apple GPUs execute as real fp16 (desktop GL/browsers run mediump at
|
|
111
|
+
// fp32, hiding it). In fp16 `cell + uv` for the last column sits in [7,8) where the ULP is
|
|
112
|
+
// 0.0039 — ~1 texel of a 2048px sheet after the divide — so sprites stair-step, worse the
|
|
113
|
+
// higher the cell index.
|
|
114
|
+
highp float2 uv = variable_quaduv.xy;
|
|
115
|
+
|
|
116
|
+
highp float2 sheet = materialParams.sheetSize;
|
|
117
|
+
highp float total = sheet.x * sheet.y;
|
|
118
|
+
highp float fidx = mod(variable_custom.w, total);
|
|
119
|
+
highp float frame = floor(fidx);
|
|
120
|
+
// Integer cell math: `floor(frame / sheet.x)` in float is evaluated as frame * (1/sheet.x) on
|
|
121
|
+
// the GPU and 6 * 0.16666667 floors to 0 — every row start sampled the PREVIOUS row's first
|
|
122
|
+
// cell for a few frames (a fireball flashing a bright fire cell mid fade-out, five times per
|
|
123
|
+
// life: the "flicker").
|
|
124
|
+
int sx = int(sheet.x + 0.5);
|
|
125
|
+
int fi = int(frame + 0.5);
|
|
126
|
+
highp float2 cell = float2(float(fi - (fi / sx) * sx), float(fi / sx));
|
|
127
|
+
highp float2 tuv = (cell + uv) / sheet;
|
|
128
|
+
|
|
129
|
+
float4 tex = texture(materialParams_baseColorMap, tuv);
|
|
130
|
+
float3 em = texture(materialParams_emissiveMap, tuv).rgb;
|
|
131
|
+
if (materialParams.frameBlend > 0.5) {
|
|
132
|
+
// the next cell, clamped to the last one (a wrap would flash frame 0 at the end of life)
|
|
133
|
+
int ni = min(fi + 1, int(total + 0.5) - 1);
|
|
134
|
+
highp float2 cell2 = float2(float(ni - (ni / sx) * sx), float(ni / sx));
|
|
135
|
+
highp float2 tuv2 = (cell2 + uv) / sheet;
|
|
136
|
+
float f = fidx - frame;
|
|
137
|
+
tex = mix(tex, texture(materialParams_baseColorMap, tuv2), f);
|
|
138
|
+
em = mix(em, texture(materialParams_emissiveMap, tuv2).rgb, f);
|
|
139
|
+
}
|
|
140
|
+
float4 tint = getColor();
|
|
141
|
+
|
|
142
|
+
float radial = 1.0 - smoothstep(0.0, 0.5, length(variable_quaduv.zw));
|
|
143
|
+
float shape = mix(1.0, radial, saturate(materialParams.softness));
|
|
144
|
+
|
|
145
|
+
float alpha = tex.a * tint.a * variable_custom.z * shape;
|
|
146
|
+
|
|
147
|
+
// Soft particles (depthFade > 0, metres): fade the sprite out where it cuts into the scene
|
|
148
|
+
// geometry behind it. sampler0_structure is Filament's depth ("structure") buffer — bound
|
|
149
|
+
// to the colour pass by patch 0017 while creator-gl sees a drawn system with depthFade > 0.
|
|
150
|
+
// With the structure pass OFF (setDepthEffects(false) = the LOW graphics tier, or a scene
|
|
151
|
+
// nothing asked it for) filament binds a 1x1 dummy that reads 1.0 — in reversed-Z that is
|
|
152
|
+
// the NEAR plane, i.e. "the scene is in front of every sprite", so the fade would take the
|
|
153
|
+
// whole system to alpha 0 and it would VANISH instead of turning hard. Real geometry never
|
|
154
|
+
// reaches 1.0 (the buffer clears to 0 = far, and the near plane is clipped away), so
|
|
155
|
+
// `sceneDepth < 1.0` is the "this depth buffer is real" test.
|
|
156
|
+
// The fragment is reprojected to render-target uv the way Filament's own SSR does
|
|
157
|
+
// (surface_light_indirect.fs), and both depths are linearized with the SSR linearizeDepth
|
|
158
|
+
// formula (reversed-Z, far at infinity).
|
|
159
|
+
// (not in the depth/shadow/picking variants: their per-view layout has no structure sampler)
|
|
160
|
+
#if !defined(VARIANT_DEPTH)
|
|
161
|
+
if (materialParams.depthFade > 0.0) {
|
|
162
|
+
highp vec3 wp = getWorldPosition();
|
|
163
|
+
highp vec4 clip = getClipFromWorldMatrix() * vec4(wp, 1.0);
|
|
164
|
+
highp vec2 suv = clip.xy * (0.5 / clip.w) + 0.5;
|
|
165
|
+
highp float sceneDepth = textureLod(sampler0_structure, uvToRenderTargetUV(suv), 0.0).r;
|
|
166
|
+
if (sceneDepth < 1.0) {
|
|
167
|
+
highp mat4 p = getViewFromClipMatrix();
|
|
168
|
+
highp float sceneZ = (sceneDepth * p[2].z + p[3].z) / max(sceneDepth * p[2].w + p[3].w, 1.0 / 16777216.0);
|
|
169
|
+
highp float fragZ = (getViewFromWorldMatrix() * vec4(wp, 1.0)).z;
|
|
170
|
+
// both are view-space z (negative ahead): fragZ - sceneZ = how far the scene is behind
|
|
171
|
+
alpha *= saturate((fragZ - sceneZ) / materialParams.depthFade);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
#endif
|
|
175
|
+
float3 rgb = tex.rgb * tint.rgb * (1.0 + materialParams.emissive) + em * materialParams.emissiveColor;
|
|
176
|
+
|
|
177
|
+
material.baseColor = float4(rgb * alpha, alpha * (1.0 - saturate(materialParams.additive)));
|
|
178
|
+
}
|
|
179
|
+
}
|