@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/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
- let material = v.override ?? mesh.material
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