@solidrt/3d 0.0.53 → 0.0.55
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/AGENTS.md +121 -28
- package/examples/README.md +7 -0
- package/examples/cascades.tsx +21 -3
- package/examples/fog.tsx +174 -0
- package/package.json +3 -3
- package/src/components.tsx +11 -1
- package/src/glsl.ts +47 -0
- package/src/gltf.ts +22 -4
- package/src/index.ts +1 -1
- package/src/material.ts +248 -99
- package/src/model.ts +17 -3
- package/src/scene.ts +91 -1
package/src/scene.ts
CHANGED
|
@@ -373,8 +373,50 @@ export type CameraUpdate = {
|
|
|
373
373
|
ortho?: OrthoExtent | null
|
|
374
374
|
}
|
|
375
375
|
|
|
376
|
+
/**
|
|
377
|
+
* Scene fog, by RADIAL distance from the camera, in one of two forms:
|
|
378
|
+
* linear (Three's `Fog`, Unity's linear mode) fades toward `color` from
|
|
379
|
+
* `near` to `far` world units and is fully fogged past `far`; exp2
|
|
380
|
+
* (Three's `FogExp2`, Unity's default) thickens as
|
|
381
|
+
* `1 - exp(-(distance * density)^2)`, no start band, never quite opaque
|
|
382
|
+
* - `density` 0.01 is about 63% fog at 100 units, 98% at 200. Either
|
|
383
|
+
* form takes the height attenuation (Godot's fog height, Unreal's
|
|
384
|
+
* height falloff): the fog is full at and below `height` (world y) and
|
|
385
|
+
* thins above it by `exp(-(y - height) * heightFalloff)` - a valley
|
|
386
|
+
* fills while the hilltops and the sky stay clear; `heightFalloff` 0.1
|
|
387
|
+
* halves the fog every ~7 units of climb. Match
|
|
388
|
+
* `color` to the clearColor or background - the background is not
|
|
389
|
+
* fogged, so a mismatch shows as a band at the horizon - and, for the
|
|
390
|
+
* linear form, set `far` at or inside the camera's far plane to hide
|
|
391
|
+
* the clip.
|
|
392
|
+
*/
|
|
393
|
+
export type FogOptions = (
|
|
394
|
+
| {
|
|
395
|
+
/** Distance where the fade starts, world units; negative puts the
|
|
396
|
+
* camera itself part-way into the fog (Three allows the same). */
|
|
397
|
+
near: number
|
|
398
|
+
/** Distance where the fade completes, world units; must exceed near. */
|
|
399
|
+
far: number
|
|
400
|
+
}
|
|
401
|
+
| {
|
|
402
|
+
/** Exp2 thickness per world unit, > 0 (0.005 haze .. 0.05 pea soup). */
|
|
403
|
+
density: number
|
|
404
|
+
}
|
|
405
|
+
) & {
|
|
406
|
+
/** Straight [r, g, b], 0..1. */
|
|
407
|
+
color: [number, number, number]
|
|
408
|
+
/** World y at and below which the fog is full; default 0. Only acts
|
|
409
|
+
* with a `heightFalloff`. */
|
|
410
|
+
height?: number
|
|
411
|
+
/** How fast the fog thins above `height`, per world unit (e-fold rate,
|
|
412
|
+
* >= 0); default 0, no height attenuation. */
|
|
413
|
+
heightFalloff?: number
|
|
414
|
+
}
|
|
415
|
+
|
|
376
416
|
export type SceneOptions = {
|
|
377
417
|
clearColor?: [number, number, number, number]
|
|
418
|
+
/** Scene-wide fog; see setFog. */
|
|
419
|
+
fog?: FogOptions
|
|
378
420
|
/** Fragment GLSL drawn behind the meshes, inside the scene's own pass -
|
|
379
421
|
* see setBackground. */
|
|
380
422
|
background?: string
|
|
@@ -458,6 +500,19 @@ export type Scene = {
|
|
|
458
500
|
* declare a name simply skips it. Frame-rate-safe like setTransform.
|
|
459
501
|
*/
|
|
460
502
|
setParams(params: ShaderParams): void
|
|
503
|
+
/**
|
|
504
|
+
* Set, replace, or remove (null) the scene's fog, linear
|
|
505
|
+
* (`{ color, near, far }`) or exp2 (`{ color, density }`), either with
|
|
506
|
+
* the optional `height`/`heightFalloff`. One shared-params write
|
|
507
|
+
* (`uFogColor`, `uFogNear`, `uFogInv`, `uFogDensity`, `uFogHeight`,
|
|
508
|
+
* `uFogHeightFalloff` - the set FOG in `@solidrt/3d/glsl` declares;
|
|
509
|
+
* the form not in use is written 0), fanned out to every view like
|
|
510
|
+
* setParams, so however many meshes fog it costs nothing per frame.
|
|
511
|
+
* Every standard material (unlit, lit, sprite) composes it unless
|
|
512
|
+
* created with `fog: false`; a shaderMaterial opts in by composing FOG.
|
|
513
|
+
* The background is not fogged: match colors (see FogOptions).
|
|
514
|
+
*/
|
|
515
|
+
setFog(fog: FogOptions | null): void
|
|
461
516
|
/**
|
|
462
517
|
* Set, replace, or remove (null) the scene's background: fragment GLSL
|
|
463
518
|
* drawn as the FIRST entry of the scene's own pass - one target, no
|
|
@@ -1537,7 +1592,10 @@ export function createScene(width: number, height: number, opts?: SceneOptions):
|
|
|
1537
1592
|
let inst = mesh._instances
|
|
1538
1593
|
if (v.override !== null && inst !== null) return
|
|
1539
1594
|
if (v.filter !== null && !v.filter(mesh)) return
|
|
1540
|
-
|
|
1595
|
+
// A shadow view (the filtered kind) lets a caster's material pick its
|
|
1596
|
+
// own depth variant (its cull side); any other override view draws
|
|
1597
|
+
// exactly what it was given.
|
|
1598
|
+
let material = v.override !== null ? (v.filter !== null ? (mesh.material.shadow ?? v.override) : v.override) : mesh.material
|
|
1541
1599
|
let bufs = mesh._buffers!
|
|
1542
1600
|
let entry = addDraw(v.texture, material.pipeline(mesh.geometry.layout), entrySeed(material, v.override !== null ? null : mesh._params), {
|
|
1543
1601
|
buffer: bufs.buffer,
|
|
@@ -2116,6 +2174,32 @@ export function createScene(width: number, height: number, opts?: SceneOptions):
|
|
|
2116
2174
|
setTargetParams(texture, params)
|
|
2117
2175
|
for (let v of views) setTargetParams(v.texture, params)
|
|
2118
2176
|
},
|
|
2177
|
+
setFog(fog) {
|
|
2178
|
+
if (fog === null) {
|
|
2179
|
+
scene.setParams({ uFogInv: 0, uFogDensity: 0, uFogHeightFalloff: 0 })
|
|
2180
|
+
return
|
|
2181
|
+
}
|
|
2182
|
+
let color = [fog.color[0], fog.color[1], fog.color[2]]
|
|
2183
|
+
let height = fog.height ?? 0
|
|
2184
|
+
let falloff = fog.heightFalloff ?? 0
|
|
2185
|
+
if (!Number.isFinite(height) || !Number.isFinite(falloff) || falloff < 0) {
|
|
2186
|
+
throw new Error(`setFog: height must be finite and heightFalloff finite and >= 0, got ${height} / ${falloff}`)
|
|
2187
|
+
}
|
|
2188
|
+
let params: ShaderParams = { uFogColor: color, uFogHeight: height, uFogHeightFalloff: falloff }
|
|
2189
|
+
if ("density" in fog) {
|
|
2190
|
+
let { density } = fog
|
|
2191
|
+
if (!Number.isFinite(density) || density <= 0) {
|
|
2192
|
+
throw new Error(`setFog: density must be finite and > 0, got ${density}`)
|
|
2193
|
+
}
|
|
2194
|
+
scene.setParams({ ...params, uFogInv: 0, uFogDensity: density })
|
|
2195
|
+
return
|
|
2196
|
+
}
|
|
2197
|
+
let { near, far } = fog
|
|
2198
|
+
if (!(Number.isFinite(near) && Number.isFinite(far)) || far <= near) {
|
|
2199
|
+
throw new Error(`setFog: near/far must be finite with near < far, got ${near}..${far}`)
|
|
2200
|
+
}
|
|
2201
|
+
scene.setParams({ ...params, uFogNear: near, uFogInv: 1 / (far - near), uFogDensity: 0 })
|
|
2202
|
+
},
|
|
2119
2203
|
setBackground(source) {
|
|
2120
2204
|
if (disposed) return
|
|
2121
2205
|
if (background !== null) {
|
|
@@ -2270,6 +2354,12 @@ export function createScene(width: number, height: number, opts?: SceneOptions):
|
|
|
2270
2354
|
}
|
|
2271
2355
|
},
|
|
2272
2356
|
}
|
|
2357
|
+
// The fog set starts at "none" (uFogInv and uFogDensity 0 are factor 0,
|
|
2358
|
+
// uFogHeightFalloff 0 is no attenuation) so every material that declares
|
|
2359
|
+
// it has coverage from the first frame; a target tolerates the names
|
|
2360
|
+
// when nothing declares them.
|
|
2361
|
+
scene.setParams({ uFogColor: [0, 0, 0], uFogNear: 0, uFogInv: 0, uFogDensity: 0, uFogHeight: 0, uFogHeightFalloff: 0 })
|
|
2362
|
+
if (opts?.fog !== undefined) scene.setFog(opts.fog)
|
|
2273
2363
|
if (opts?.background !== undefined) scene.setBackground(opts.background)
|
|
2274
2364
|
if (opts?.autoFree !== false && getOwner()) onCleanup(() => scene.dispose())
|
|
2275
2365
|
return scene
|