@driftengine/core 4.13.4 → 4.13.5
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/CHANGELOG.json +36 -0
- package/README.md +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/render/anisotropicLobe.d.ts +5 -0
- package/dist/render/anisotropicLobe.js +13 -0
- package/dist/render/atmosphere.d.ts +79 -1
- package/dist/render/atmosphere.js +66 -19
- package/dist/render/backend/webgl2/renderer.d.ts +34 -2
- package/dist/render/backend/webgl2/renderer.js +44 -12
- package/dist/render/backend/webgpu/boltPass.d.ts +1 -1
- package/dist/render/backend/webgpu/causticsPass.d.ts +1 -1
- package/dist/render/backend/webgpu/filmPass.d.ts +1 -1
- package/dist/render/backend/webgpu/gpuDrivenPass.js +20 -3
- package/dist/render/backend/webgpu/linePass.d.ts +1 -1
- package/dist/render/backend/webgpu/mediumFields.d.ts +17 -0
- package/dist/render/backend/webgpu/mediumFields.js +74 -0
- package/dist/render/backend/webgpu/plumePass.d.ts +3 -3
- package/dist/render/backend/webgpu/postPass.d.ts +7 -2
- package/dist/render/backend/webgpu/renderer.d.ts +13 -3
- package/dist/render/backend/webgpu/renderer.js +86 -150
- package/dist/render/backend/webgpu/scatterPass.d.ts +1 -1
- package/dist/render/backend/webgpu/waterPass.d.ts +1 -1
- package/dist/render/environmentMixing.d.ts +18 -0
- package/dist/render/environmentMixing.js +40 -0
- package/dist/render/fog.d.ts +43 -1
- package/dist/render/fog.js +87 -22
- package/dist/render/globalMedium.d.ts +112 -10
- package/dist/render/globalMedium.js +96 -1
- package/dist/render/globalMediumPass.d.ts +7 -3
- package/dist/render/globalMediumPass.js +22 -1
- package/dist/render/hairLobes.d.ts +5 -3
- package/dist/render/hairLobes.js +11 -7
- package/dist/render/lightBudget.d.ts +12 -0
- package/dist/render/lightBudget.js +24 -0
- package/dist/render/sceneTarget.d.ts +5 -3
- package/dist/render/sceneTarget.js +14 -8
- package/dist/render/shaders/arcane.d.ts +1 -1
- package/dist/render/shaders/bolt.d.ts +1 -1
- package/dist/render/shaders/caustics.d.ts +1 -1
- package/dist/render/shaders/film.d.ts +1 -1
- package/dist/render/shaders/film.js +4 -1
- package/dist/render/shaders/fire.d.ts +1 -1
- package/dist/render/shaders/flat/anisotropic.d.ts +8 -1
- package/dist/render/shaders/flat/anisotropic.js +25 -3
- package/dist/render/shaders/flat/hair.js +31 -7
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +20 -1
- package/dist/render/shaders/flat/models.js +1 -1
- package/dist/render/shaders/flat/preamble.js +7 -0
- package/dist/render/shaders/fog.d.ts +4 -4
- package/dist/render/shaders/fog.js +81 -32
- package/dist/render/shaders/generated/arcane.wgsl.d.ts +29 -39
- package/dist/render/shaders/generated/arcane.wgsl.js +29 -39
- package/dist/render/shaders/generated/bolt.wgsl.d.ts +26 -36
- package/dist/render/shaders/generated/bolt.wgsl.js +26 -36
- package/dist/render/shaders/generated/caustics.wgsl.d.ts +25 -35
- package/dist/render/shaders/generated/caustics.wgsl.js +25 -35
- package/dist/render/shaders/generated/film.wgsl.d.ts +28 -38
- package/dist/render/shaders/generated/film.wgsl.js +28 -38
- package/dist/render/shaders/generated/fire.wgsl.d.ts +28 -38
- package/dist/render/shaders/generated/fire.wgsl.js +28 -38
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1466 -1546
- package/dist/render/shaders/generated/flat.wgsl.js +1489 -1569
- package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +98 -12
- package/dist/render/shaders/generated/globalMedium.wgsl.js +98 -12
- package/dist/render/shaders/generated/line.wgsl.d.ts +25 -35
- package/dist/render/shaders/generated/line.wgsl.js +25 -35
- package/dist/render/shaders/generated/particle.wgsl.d.ts +100 -140
- package/dist/render/shaders/generated/particle.wgsl.js +100 -140
- package/dist/render/shaders/generated/rush.wgsl.d.ts +20 -10
- package/dist/render/shaders/generated/rush.wgsl.js +20 -10
- package/dist/render/shaders/generated/scatter.wgsl.d.ts +25 -35
- package/dist/render/shaders/generated/scatter.wgsl.js +25 -35
- package/dist/render/shaders/generated/smoke.wgsl.d.ts +28 -38
- package/dist/render/shaders/generated/smoke.wgsl.js +28 -38
- package/dist/render/shaders/generated/water.wgsl.d.ts +29 -39
- package/dist/render/shaders/generated/water.wgsl.js +29 -39
- package/dist/render/shaders/globalMedium.js +114 -10
- package/dist/render/shaders/gpudriven/blendRaster.wgsl.js +2 -1
- package/dist/render/shaders/gpudriven/fog.wgsl.d.ts +10 -4
- package/dist/render/shaders/gpudriven/fog.wgsl.js +77 -19
- package/dist/render/shaders/gpudriven/frameBlock.wgsl.d.ts +1 -1
- package/dist/render/shaders/gpudriven/frameBlock.wgsl.js +12 -1
- package/dist/render/shaders/gpudriven/shade.wgsl.js +3 -1
- package/dist/render/shaders/line.d.ts +1 -1
- package/dist/render/shaders/line.js +4 -2
- package/dist/render/shaders/localExposure.d.ts +42 -1
- package/dist/render/shaders/localExposure.js +73 -6
- package/dist/render/shaders/particle.d.ts +3 -3
- package/dist/render/shaders/particle.js +10 -6
- package/dist/render/shaders/scatter.d.ts +1 -1
- package/dist/render/shaders/scatter.js +4 -2
- package/dist/render/shaders/smoke.d.ts +1 -1
- package/dist/render/shaders/water.d.ts +1 -1
- package/dist/render/shaders/water.js +2 -1
- package/dist/render/surfaceModel.d.ts +23 -4
- package/dist/render/surfaceModel.js +12 -2
- package/package.json +6 -6
- package/src/index.ts +12 -2
- package/src/render/anisotropicLobe.ts +20 -0
- package/src/render/atmosphere.ts +153 -20
- package/src/render/backend/webgl2/renderer.ts +63 -13
- package/src/render/backend/webgpu/gpuDrivenPass.ts +20 -3
- package/src/render/backend/webgpu/mediumFields.ts +87 -0
- package/src/render/backend/webgpu/renderer.ts +109 -157
- package/src/render/environmentMixing.ts +48 -0
- package/src/render/fog.ts +125 -25
- package/src/render/globalMedium.ts +214 -11
- package/src/render/globalMediumPass.ts +29 -4
- package/src/render/hairLobes.ts +35 -4
- package/src/render/lightBudget.ts +32 -0
- package/src/render/sceneTarget.ts +15 -11
- package/src/render/shaders/film.ts +4 -1
- package/src/render/shaders/flat/anisotropic.ts +25 -3
- package/src/render/shaders/flat/hair.ts +31 -7
- package/src/render/shaders/flat/main.ts +20 -1
- package/src/render/shaders/flat/models.ts +1 -1
- package/src/render/shaders/flat/preamble.ts +7 -0
- package/src/render/shaders/fog.ts +81 -32
- package/src/render/shaders/generated/arcane.wgsl.ts +29 -39
- package/src/render/shaders/generated/bolt.wgsl.ts +26 -36
- package/src/render/shaders/generated/caustics.wgsl.ts +25 -35
- package/src/render/shaders/generated/film.wgsl.ts +28 -38
- package/src/render/shaders/generated/fire.wgsl.ts +28 -38
- package/src/render/shaders/generated/flat.wgsl.ts +1489 -1569
- package/src/render/shaders/generated/globalMedium.wgsl.ts +98 -12
- package/src/render/shaders/generated/line.wgsl.ts +25 -35
- package/src/render/shaders/generated/particle.wgsl.ts +100 -140
- package/src/render/shaders/generated/rush.wgsl.ts +20 -10
- package/src/render/shaders/generated/scatter.wgsl.ts +25 -35
- package/src/render/shaders/generated/smoke.wgsl.ts +28 -38
- package/src/render/shaders/generated/water.wgsl.ts +29 -39
- package/src/render/shaders/globalMedium.ts +114 -10
- package/src/render/shaders/gpudriven/blendRaster.wgsl.ts +2 -1
- package/src/render/shaders/gpudriven/fog.wgsl.ts +78 -19
- package/src/render/shaders/gpudriven/frameBlock.wgsl.ts +12 -1
- package/src/render/shaders/gpudriven/shade.wgsl.ts +3 -1
- package/src/render/shaders/line.ts +4 -2
- package/src/render/shaders/localExposure.ts +115 -6
- package/src/render/shaders/particle.ts +10 -6
- package/src/render/shaders/scatter.ts +4 -2
- package/src/render/shaders/water.ts +2 -1
- package/src/render/surfaceModel.ts +41 -6
|
@@ -427,6 +427,13 @@ export interface PointLightSet {
|
|
|
427
427
|
* falls off as it did. See `PointLightSource.falloffExponent`.
|
|
428
428
|
*/
|
|
429
429
|
readonly lightFalloffExponents?: Float32Array;
|
|
430
|
+
/**
|
|
431
|
+
* One per light: how strongly it scatters in the global medium, as a share of how strongly it
|
|
432
|
+
* lights a surface, beside `setGlobalMedium`'s `lampScatter` for every lamp at once. **Absent, or
|
|
433
|
+
* a short array, is 1 for every light.** A lamp turned up here lights a fog bank brighter than it
|
|
434
|
+
* lights the floor in it, which is how a lamp authored for its shaft rather than its pool reads.
|
|
435
|
+
*/
|
|
436
|
+
readonly lightMediumScatter?: Float32Array;
|
|
430
437
|
}
|
|
431
438
|
|
|
432
439
|
/** Weights are optional per consumer; a short array means every light is fully present. */
|
|
@@ -637,6 +644,31 @@ export interface ResolvedPointLights {
|
|
|
637
644
|
readonly heldWeights: Float32Array;
|
|
638
645
|
}
|
|
639
646
|
|
|
647
|
+
/**
|
|
648
|
+
* A target for `resolvePointLights`, made once outside the frame loop. Its arrays are references
|
|
649
|
+
* the resolve points at the caller's own or at the defaults, but for the four it owns.
|
|
650
|
+
*/
|
|
651
|
+
export function createResolvedPointLights(): ResolvedPointLights {
|
|
652
|
+
return {
|
|
653
|
+
count: 0,
|
|
654
|
+
falloff: 0,
|
|
655
|
+
positions: NO_POSITIONS,
|
|
656
|
+
colors: NO_COLORS,
|
|
657
|
+
radii: POINT_SOURCES,
|
|
658
|
+
sourceRadii: POINT_SOURCES,
|
|
659
|
+
weights: FULLY_PRESENT,
|
|
660
|
+
directions: NO_DIRECTIONS,
|
|
661
|
+
coneCos: OPEN_CONES,
|
|
662
|
+
iesProfiles: NO_PROFILES,
|
|
663
|
+
iesAxes: NO_IES_AXES,
|
|
664
|
+
cookies: NO_COOKIES,
|
|
665
|
+
axesAndChannels: new Float32Array(MAX_POINT_LIGHTS * 4),
|
|
666
|
+
conesAndFalloffs: new Float32Array(MAX_POINT_LIGHTS * 4),
|
|
667
|
+
heldSourceRadii: new Float32Array(MAX_POINT_LIGHTS),
|
|
668
|
+
heldWeights: new Float32Array(MAX_POINT_LIGHTS),
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
|
|
640
672
|
/** One per GL binder, refilled per call: holds references, allocates nothing. */
|
|
641
673
|
const glLights: ResolvedPointLights = {
|
|
642
674
|
count: 0,
|
|
@@ -4,7 +4,8 @@ import { FRINGE_FLOATS, resolveFringe } from './shaders/fringe.ts';
|
|
|
4
4
|
import { AmbientOcclusionPass } from './ambientOcclusionPass.ts';
|
|
5
5
|
import { ExposurePass } from './exposurePass.ts';
|
|
6
6
|
import { clampAutoExposure } from './shaders/exposure.ts';
|
|
7
|
-
import {
|
|
7
|
+
import { LOCAL_EXPOSURE_FLOATS, resolveLocalExposure } from './shaders/localExposure.ts';
|
|
8
|
+
import type { LocalExposureOptions } from './shaders/localExposure.ts';
|
|
8
9
|
import { BloomPass } from './bloomPass.ts';
|
|
9
10
|
import {
|
|
10
11
|
type ColourGradeLut,
|
|
@@ -107,8 +108,9 @@ export class SceneTarget {
|
|
|
107
108
|
/** Eye adaptation: how far, and this frame's time step. See `Renderer.setAutoExposure`. */
|
|
108
109
|
private autoExposure = 0;
|
|
109
110
|
private autoExposureDt = 0;
|
|
110
|
-
/**
|
|
111
|
-
private localExposure =
|
|
111
|
+
/** Whether regions move, and how, as `resolveLocalExposure` packs it. See `setLocalExposure`. */
|
|
112
|
+
private localExposure = false;
|
|
113
|
+
private readonly localExposureShape = new Float32Array(LOCAL_EXPOSURE_FLOATS);
|
|
112
114
|
private exposure: ExposurePass | null = null;
|
|
113
115
|
private width = 0;
|
|
114
116
|
private height = 0;
|
|
@@ -722,9 +724,9 @@ export class SceneTarget {
|
|
|
722
724
|
this.autoExposureDt = dtSec;
|
|
723
725
|
}
|
|
724
726
|
|
|
725
|
-
/** How
|
|
726
|
-
setLocalExposure(
|
|
727
|
-
this.localExposure =
|
|
727
|
+
/** How regions are brought toward the frame. See `Renderer.setLocalExposure`. */
|
|
728
|
+
setLocalExposure(setting: number | LocalExposureOptions): void {
|
|
729
|
+
this.localExposure = resolveLocalExposure(setting, this.localExposureShape);
|
|
728
730
|
}
|
|
729
731
|
|
|
730
732
|
/** A new shot: the eye meters it afresh rather than easing in from the last one. */
|
|
@@ -1011,7 +1013,7 @@ export class SceneTarget {
|
|
|
1011
1013
|
* reason: the composite consumes it. Off at zero strength, where nothing is allocated or drawn.
|
|
1012
1014
|
*/
|
|
1013
1015
|
let heldExposure: WebGLTexture | null = null;
|
|
1014
|
-
const local = this.localExposure
|
|
1016
|
+
const local = this.localExposure;
|
|
1015
1017
|
if ((this.autoExposure > 0 || local) && scene !== null) {
|
|
1016
1018
|
this.exposure ??= new ExposurePass(gl);
|
|
1017
1019
|
heldExposure = this.exposure.run(scene, this.autoExposureDt, local);
|
|
@@ -1136,10 +1138,12 @@ export class SceneTarget {
|
|
|
1136
1138
|
gl.activeTexture(gl.TEXTURE6);
|
|
1137
1139
|
gl.bindTexture(gl.TEXTURE_2D, localGrid ?? scene);
|
|
1138
1140
|
gl.uniform1i(this.uniforms['uExposureLocal'] ?? null, 6);
|
|
1139
|
-
gl.uniform1f(
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1141
|
+
gl.uniform1f(this.uniforms['uLocalExposure'] ?? null, localGrid === null ? 0 : 1);
|
|
1142
|
+
/* Its shape only where it is on: off, the shader returns before reading it. */
|
|
1143
|
+
if (localGrid !== null) {
|
|
1144
|
+
gl.uniform4fv(this.uniforms['uLocalShape'] ?? null, this.localExposureShape, 0, 4);
|
|
1145
|
+
gl.uniform4fv(this.uniforms['uLocalPivot'] ?? null, this.localExposureShape, 4, 4);
|
|
1146
|
+
}
|
|
1143
1147
|
/* Each object's own motion is WebGPU's under a reconstruction; here the scene stands in on
|
|
1144
1148
|
unit 7 and the flag says not to read it, so the camera's motion alone blurs. */
|
|
1145
1149
|
gl.activeTexture(gl.TEXTURE7);
|
|
@@ -211,8 +211,11 @@ void main() {
|
|
|
211
211
|
// it was not: this ran its own curve while claiming otherwise, so a slick receded
|
|
212
212
|
// at a different rate than the deck it was lying on.
|
|
213
213
|
float fog = mediumFog(length(uCameraPos - vWorld), vWorld.y);
|
|
214
|
+
vec3 glow = mediumGlow(length(uCameraPos - vWorld), vWorld.y, normalize(vWorld - uCameraPos));
|
|
215
|
+
/* The air the mirror recedes into takes no glow: the mirror is mixed toward it by the fog, which
|
|
216
|
+
would lay the glow by the fog's share a second time over the surface's own. */
|
|
214
217
|
vec3 gradedAir = applyOutputTransform(mediumColor());
|
|
215
|
-
vec3 graded = applyOutputTransform(mix(color, mediumColor(), fog));
|
|
218
|
+
vec3 graded = applyOutputTransform(mix(color, mediumColor(), fog) + glow);
|
|
216
219
|
|
|
217
220
|
/*
|
|
218
221
|
* The mirrored scene, under the sheen rather than instead of it.
|
|
@@ -11,20 +11,30 @@
|
|
|
11
11
|
*
|
|
12
12
|
* Lamps widen both of the lobe's widths by the source's angle, as `sphereLobe` widens the standard
|
|
13
13
|
* lobe, with the energy kept; at strength 0 that is `sphereLobe` exactly.
|
|
14
|
+
*
|
|
15
|
+
* **A lift**, where the model asks for one: the distribution raised where it is bright, `D +
|
|
16
|
+
* min(3, D^1.5) · k`, which is how a brushed finish that reads as polished in its streak is
|
|
17
|
+
* authored. `k` is the model's `lift`, times the model map's alpha where one is bound, so it can
|
|
18
|
+
* vary texel by texel. Applied as a factor of the distribution at the half vector, so the
|
|
19
|
+
* peak-normalised lobe and the physical one lift alike. **What it gives up** is energy: a lifted
|
|
20
|
+
* lobe returns more than arrives, which is the look it is for.
|
|
14
21
|
*/
|
|
15
22
|
export const ANISOTROPIC_GLSL = /* glsl */ `
|
|
16
|
-
/* The stretch's frame and
|
|
23
|
+
/* The stretch's frame, strength and lift, gathered once with the surface. */
|
|
17
24
|
vec3 aAlong;
|
|
18
25
|
vec3 aAcross;
|
|
19
26
|
float aStrength;
|
|
27
|
+
float aLift;
|
|
20
28
|
|
|
21
29
|
void anisotropicSurface() {
|
|
22
30
|
vec2 direction = vec2(1.0, 0.0);
|
|
23
31
|
float strength = uModelParams[0].x;
|
|
32
|
+
aLift = uModelParams[0].w;
|
|
24
33
|
if (uModelParams[1].w > 0.0) {
|
|
25
34
|
vec2 mapped = mMap.rg * 2.0 - 1.0;
|
|
26
35
|
direction = dot(mapped, mapped) > 1e-8 ? normalize(mapped) : direction;
|
|
27
36
|
strength *= mMap.b;
|
|
37
|
+
aLift *= mMap.a;
|
|
28
38
|
}
|
|
29
39
|
vec2 turned = vec2(
|
|
30
40
|
direction.x * uModelParams[0].y - direction.y * uModelParams[0].z,
|
|
@@ -44,6 +54,17 @@ void anisotropicSurface() {
|
|
|
44
54
|
aStrength = strength;
|
|
45
55
|
}
|
|
46
56
|
|
|
57
|
+
/**
|
|
58
|
+
* How much the lift brightens the lobe at a half vector, from the stretched distribution there, D,
|
|
59
|
+
* at widths \`along\` and \`across\` and \`d\` its denominator's root: \`(D + min(3, D^1.5) k) / D\`.
|
|
60
|
+
* anisotropicLiftFactor in anisotropicLobe.ts.
|
|
61
|
+
*/
|
|
62
|
+
float anisotropicLift(float along, float across, float d) {
|
|
63
|
+
if (aLift <= 0.0) return 1.0;
|
|
64
|
+
float distribution = 1.0 / max(3.14159265 * along * across * d * d, 1e-8);
|
|
65
|
+
return 1.0 + aLift * min(3.0 / distribution, sqrt(distribution));
|
|
66
|
+
}
|
|
67
|
+
|
|
47
68
|
/** The stretched lobe at a half vector, 1 at the mirror direction: anisotropicLobe.ts. */
|
|
48
69
|
float anisotropicLobe(vec3 h, float sourceRadius, float dist) {
|
|
49
70
|
float alpha = max(mRoughness * mRoughness, MIN_LOBE_ALPHA);
|
|
@@ -56,7 +77,7 @@ float anisotropicLobe(vec3 h, float sourceRadius, float dist) {
|
|
|
56
77
|
float y = dot(aAcross, h) / acrossWide;
|
|
57
78
|
float z = dot(mNormal, h);
|
|
58
79
|
float d = x * x + y * y + z * z;
|
|
59
|
-
return energy / max(d * d, 1e-8);
|
|
80
|
+
return energy / max(d * d, 1e-8) * anisotropicLift(alongWide, acrossWide, d);
|
|
60
81
|
}
|
|
61
82
|
|
|
62
83
|
/**
|
|
@@ -74,7 +95,8 @@ float anisotropicPhysical(vec3 l, vec3 h, float ndl, float sourceRadius, float d
|
|
|
74
95
|
float y = dot(aAcross, h) / across;
|
|
75
96
|
float z = dot(mNormal, h);
|
|
76
97
|
float d = x * x + y * y + z * z;
|
|
77
|
-
float distribution =
|
|
98
|
+
float distribution =
|
|
99
|
+
1.0 / max(3.14159265 * along * across * d * d, 1e-8) * anisotropicLift(along, across, d);
|
|
78
100
|
float ndv = max(dot(mNormal, mToEye), 1e-4);
|
|
79
101
|
float viewed = ndl * length(vec3(along * dot(aAlong, mToEye), across * dot(aAcross, mToEye), ndv));
|
|
80
102
|
float lit = ndv * length(vec3(along * dot(aAlong, l), across * dot(aAcross, l), ndl));
|
|
@@ -3,10 +3,19 @@
|
|
|
3
3
|
* their tilted peaks, the transmitted lobe to the backlight, the sum to what a white strand
|
|
4
4
|
* receives, and every term to a finite answer where the geometry degenerates.
|
|
5
5
|
*
|
|
6
|
-
* **The strand is the tangent, root to tip
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* **The strand is the tangent, root to tip**, unless the model map says otherwise. Its red is the
|
|
7
|
+
* strand's specular, which scales the reflected lobe by twice it, so 0.5 is the lobe as drawn and 0
|
|
8
|
+
* a root that reflects nothing; its green turns each strand's tilt by up to half of it either way
|
|
9
|
+
* (0.5 is none); and its blue is occlusion, which light from every side takes and the lamps do not
|
|
10
|
+
* — their shadows are theirs to cast. A colour from root to tip is the albedo map's to carry.
|
|
11
|
+
*
|
|
12
|
+
* **A strand's direction, texel by texel**, where the model has a \`directionOffset\`: that many
|
|
13
|
+
* layers past the one the other channels are read at, a direction in the surface's tangent frame —
|
|
14
|
+
* red along the tangent, green along the bitangent and blue out of the surface along its normal,
|
|
15
|
+
* each −1 to 1 as a normal map packs them — which the strand runs along instead of the tangent. So
|
|
16
|
+
* a card's strands can lean out of its plane, each its own way, as the strands it stands for do.
|
|
17
|
+
* **What it gives up** is the frame's own precision: eight bits a component turns a strand in
|
|
18
|
+
* steps of about half a degree, which the lobes, a few degrees wide, do not resolve.
|
|
10
19
|
*
|
|
11
20
|
* **Light from every side** — the sky, the probes, DriftLight — goes through the lobes rather than
|
|
12
21
|
* past them: they are evaluated once, for a light along the eye flattened across the strand, every
|
|
@@ -30,8 +39,10 @@ const float HAIR_F0 = ${HAIR_F0.toFixed(9)};
|
|
|
30
39
|
const float HAIR_MIN_BETA = ${HAIR_MIN_BETA.toFixed(3)};
|
|
31
40
|
const float HAIR_AREA = ${HAIR_AREA.toFixed(3)};
|
|
32
41
|
|
|
33
|
-
/* This texel's tilt, and the share of light from every side it returns: gathered
|
|
42
|
+
/* This texel's tilt, its specular, and the share of light from every side it returns: gathered
|
|
43
|
+
with the surface. */
|
|
34
44
|
float hShift;
|
|
45
|
+
float hSpecular;
|
|
35
46
|
vec3 hAround;
|
|
36
47
|
|
|
37
48
|
/** A longitudinal lobe: a normalised Gaussian of width \`beta\` at \`x\` from its centre. */
|
|
@@ -69,7 +80,7 @@ void hairLobes(vec3 l, float grow, float backlit) {
|
|
|
69
80
|
float x = sinI + sinO;
|
|
70
81
|
|
|
71
82
|
float r = hairLongitudinal(beta + grow, x + 2.0 * hShift) * 0.25 * cosHalfPhi
|
|
72
|
-
* hairFresnel(sqrt(clamp(0.5 + 0.5 * dot(l, mToEye), 0.0, 1.0)));
|
|
83
|
+
* hairFresnel(sqrt(clamp(0.5 + 0.5 * dot(l, mToEye), 0.0, 1.0))) * hSpecular;
|
|
73
84
|
|
|
74
85
|
float a = 1.0 / (1.19 / cosD + 0.36 * cosD);
|
|
75
86
|
float h = cosHalfPhi * (1.0 + a * (0.6 - 0.8 * cosPhi));
|
|
@@ -116,8 +127,21 @@ vec3 hairIndirect() {
|
|
|
116
127
|
return min(vec3(1.0), 2.0 * (mSpecular + mDiffuse));
|
|
117
128
|
}
|
|
118
129
|
|
|
119
|
-
|
|
130
|
+
/**
|
|
131
|
+
* The strand at this texel: its tilt and specular from the model map, its direction from the layer
|
|
132
|
+
* \`directionOffset\` names where there is one, and then its share of light from every side, which
|
|
133
|
+
* reads all three. On a branch of uniforms alone, so the read's derivatives are in uniform flow; a
|
|
134
|
+
* direction that decodes to nothing keeps the tangent.
|
|
135
|
+
*/
|
|
136
|
+
void hairSurface(vec3 at) {
|
|
120
137
|
hShift = uModelParams[0].x * (0.5 + mMap.g);
|
|
138
|
+
hSpecular = 2.0 * mMap.r;
|
|
139
|
+
if (uModelParams[0].w > 0.0 && uModelParams[1].w > 0.0) {
|
|
140
|
+
vec3 d = texture(uModelMap, vec3(at.xy, at.z + uModelParams[0].w)).rgb * 2.0 - 1.0;
|
|
141
|
+
vec3 strand = mTangent * d.x + mBitangent * d.y + mNormal * d.z;
|
|
142
|
+
float length2 = dot(strand, strand);
|
|
143
|
+
if (length2 > 1e-4) mTangent = strand * inversesqrt(length2);
|
|
144
|
+
}
|
|
121
145
|
hAround = hairIndirect();
|
|
122
146
|
}
|
|
123
147
|
|
|
@@ -835,6 +835,19 @@ export const MAIN_GLSL = `void main() {
|
|
|
835
835
|
* caller sets this.
|
|
836
836
|
*/
|
|
837
837
|
environment *= mix(1.0, uEnvironmentDials.z, metal);
|
|
838
|
+
/*
|
|
839
|
+
* **And brought to the light where the surface stands**, metal or not: a probe is captured
|
|
840
|
+
* at one place and seen from many, so a rough surface standing in light twice what the probe
|
|
841
|
+
* saw takes twice its reflection, and a mirror keeps the capture as it is. The weight is the
|
|
842
|
+
* caller's, the place's indirect light over the probe's average; mixed in by roughness, with
|
|
843
|
+
* the smoothstep written out because GLSL leaves one over an empty range undefined. 0, the
|
|
844
|
+
* weight less one, is exactly the reflection there was.
|
|
845
|
+
*/
|
|
846
|
+
if (uEnvironmentMixing.x != 0.0) {
|
|
847
|
+
float span = max(uEnvironmentMixing.z - uEnvironmentMixing.y, 1e-4);
|
|
848
|
+
float rough = clamp((surfaceRoughness - uEnvironmentMixing.y) / span, 0.0, 1.0);
|
|
849
|
+
environment *= 1.0 + uEnvironmentMixing.x * rough * rough * (3.0 - 2.0 * rough);
|
|
850
|
+
}
|
|
838
851
|
float facing = 1.0 - max(dot(n, toEye), 0.0);
|
|
839
852
|
/*
|
|
840
853
|
* **A metal's Fresnel base is close to its albedo, not a dielectric's four percent.** Schlick
|
|
@@ -2090,8 +2103,14 @@ export const MAIN_GLSL = `void main() {
|
|
|
2090
2103
|
* the medium's colour and adding it puts the haze into the frame twice — a glow by day drew as
|
|
2091
2104
|
* a grey shape of itself, greyer the thicker the air.
|
|
2092
2105
|
*/
|
|
2106
|
+
/* And the sun's glow in the haze, which a surface takes after its fog and an added light never
|
|
2107
|
+
does: what is behind it already has it. */
|
|
2108
|
+
vec3 glow = vec3(0.0);
|
|
2109
|
+
if (uFogEnabled == 1) {
|
|
2110
|
+
glow = mediumGlow(distance(vWorldPos, uCameraPos), vWorldPos.y, normalize(vWorldPos - uCameraPos));
|
|
2111
|
+
}
|
|
2093
2112
|
vec3 shaded = applyOutputTransform(
|
|
2094
|
-
uFogEnabled == 2 ? lit * (1.0 - fog) : mix(lit, mediumColor(), fog));
|
|
2113
|
+
uFogEnabled == 2 ? lit * (1.0 - fog) : mix(lit, mediumColor(), fog) + glow);
|
|
2095
2114
|
|
|
2096
2115
|
/*
|
|
2097
2116
|
* **Refraction: the scene behind this surface, bent and absorbed.**
|
|
@@ -236,7 +236,7 @@ void modelSurface(vec3 n, vec3 albedo, float roughness, float metal, vec3 at) {
|
|
|
236
236
|
mMap = vec4(0.5, 0.5, 1.0, 1.0);
|
|
237
237
|
if (uModelParams[1].w > 0.0) mMap = texture(uModelMap, at);
|
|
238
238
|
if (MODEL_ANISOTROPIC) anisotropicSurface();
|
|
239
|
-
if (MODEL_HAIR) hairSurface();
|
|
239
|
+
if (MODEL_HAIR) hairSurface(at);
|
|
240
240
|
if (MODEL_SKIN) skinSurface();
|
|
241
241
|
if (MODEL_EYE) eyeSurface(at);
|
|
242
242
|
}
|
|
@@ -289,6 +289,13 @@ uniform vec3 uOrmScale; // wgsl:material
|
|
|
289
289
|
*/
|
|
290
290
|
uniform vec3 uEnvironmentDials; // wgsl:material
|
|
291
291
|
|
|
292
|
+
/**
|
|
293
|
+
* The environment a surface reflects brought to the light where it stands, for every surface:
|
|
294
|
+
* \`setEnvironmentMixing\`. x is the weight less one, so a program nothing wrote reflects as it did;
|
|
295
|
+
* y and z the roughnesses the weight is mixed in over, smooth to rough.
|
|
296
|
+
*/
|
|
297
|
+
uniform vec4 uEnvironmentMixing; // wgsl:material
|
|
298
|
+
|
|
292
299
|
/**
|
|
293
300
|
* Where a surface glows, and in what colour.
|
|
294
301
|
*
|
|
@@ -31,37 +31,77 @@
|
|
|
31
31
|
* at the eye times `(1 − e⁻ᵗ)/t`, with `t` the height climbed in scale heights. The eye
|
|
32
32
|
* density and `1/H` arrive precomputed from `bindAtmosphere`.
|
|
33
33
|
*
|
|
34
|
-
* `
|
|
35
|
-
* never needed the camera position in their fragment stage, and a block
|
|
36
|
-
* everything it uses can be pasted anywhere.
|
|
34
|
+
* `uFogMedium.z`, the eye's height, rather than `uCameraPos.y`, because two of the seven
|
|
35
|
+
* programs (the plumes) never needed the camera position in their fragment stage, and a block
|
|
36
|
+
* that declares everything it uses can be pasted anywhere.
|
|
37
37
|
*/
|
|
38
38
|
export const FOG_GLSL = `
|
|
39
39
|
uniform vec3 uFogColor;
|
|
40
|
-
/**
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
40
|
+
/**
|
|
41
|
+
* The haze: its extinction per metre **at the camera's own height**, not at the base; its
|
|
42
|
+
* reciprocal scale height per metre, 0 for a uniform medium; the camera's height, so a fragment's
|
|
43
|
+
* own height is one subtraction away; and the water column's own density.
|
|
44
|
+
*
|
|
45
|
+
* **Four numbers in one vector rather than four uniforms**, and the reason is a phone: a scalar
|
|
46
|
+
* costs a whole row of the uniform grid, and the lit stage at its eight-light rung must fit the 256
|
|
47
|
+
* rows an Adreno 740 offers (uniformVectorBudget.ts). Packed, the haze's eight scalars cost two
|
|
48
|
+
* rows where they cost eight, which is what paid for the second layer and the sun's glow below.
|
|
49
|
+
*/
|
|
50
|
+
uniform vec4 uFogMedium;
|
|
51
|
+
/** Where haze begins, where a linear ramp is total, and the mode: 0 a medium, 1 a ramp. */
|
|
52
|
+
uniform vec4 uFogRange;
|
|
46
53
|
uniform vec3 uUnderwaterColor;
|
|
47
|
-
uniform float uUnderwaterFogDensity;
|
|
48
54
|
uniform float uUnderwaterFactor;
|
|
49
|
-
/**
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
55
|
+
/**
|
|
56
|
+
* The second layer's extinction per metre at the camera's height and its falloff; then the least of
|
|
57
|
+
* a surface the haze must leave, Atmosphere.fogMaxOpacity's complement, and the distance past which
|
|
58
|
+
* it hides nothing (0 is none). **Every lane is none at zero**, and so are uFogGlow's: a program a
|
|
59
|
+
* binder never wrote these into draws the haze it drew before they existed, rather than none.
|
|
60
|
+
*/
|
|
61
|
+
uniform vec4 uFogLayer2;
|
|
62
|
+
/** The sun's glow in the haze: its colour, black for none, and the exponent of its lobe. */
|
|
63
|
+
uniform vec4 uFogGlow;
|
|
64
|
+
/** Surface → the glow's light, and how far past the haze's start the glow begins. */
|
|
65
|
+
uniform vec4 uFogGlowDir;
|
|
54
66
|
|
|
55
67
|
/** What distance converges toward: the day's haze, or the water column. */
|
|
56
68
|
vec3 mediumColor() {
|
|
57
69
|
return mix(uFogColor, uUnderwaterColor, uUnderwaterFactor);
|
|
58
70
|
}
|
|
59
71
|
|
|
72
|
+
/**
|
|
73
|
+
* The haze's optical depth from where it begins along the ray to a point at height pointY, dist
|
|
74
|
+
* metres away: each layer's density at the eye carried over what is left of the ray, with the
|
|
75
|
+
* second layer only where there is one. The fog and the sun's glow both integrate it.
|
|
76
|
+
*/
|
|
77
|
+
float hazeDepth(float dist, float pointY) {
|
|
78
|
+
// Where the medium begins along the ray: uFogRange.x, the linear mode's slot, which this mode
|
|
79
|
+
// never read before Atmosphere.fogStart. The ray is integrated from there on, at the density
|
|
80
|
+
// of that point; at a start of 0 every term is the one it always was. See render/fog.ts.
|
|
81
|
+
float start = min(uFogRange.x, dist);
|
|
82
|
+
float before = dist > 0.0 ? start / dist : 0.0;
|
|
83
|
+
float t = (pointY - uFogMedium.z) * uFogMedium.y;
|
|
84
|
+
float rest = t * (1.0 - before);
|
|
85
|
+
// (1 - e^-t)/t, which tends to 1 as the ray levels out. Guarded rather than
|
|
86
|
+
// branched: at t = 0 the quotient is 0/0, and 1e-4 of a scale height is 8 mm.
|
|
87
|
+
float denom = abs(rest) < 1e-4 ? 1e-4 : rest;
|
|
88
|
+
float depth = uFogMedium.x * exp(-t * before) * (dist - start) * (1.0 - exp(-denom)) / denom;
|
|
89
|
+
if (uFogLayer2.x > 0.0) {
|
|
90
|
+
float t2 = (pointY - uFogMedium.z) * uFogLayer2.y;
|
|
91
|
+
float rest2 = t2 * (1.0 - before);
|
|
92
|
+
float denom2 = abs(rest2) < 1e-4 ? 1e-4 : rest2;
|
|
93
|
+
depth += uFogLayer2.x * exp(-t2 * before) * (dist - start) * (1.0 - exp(-denom2)) / denom2;
|
|
94
|
+
}
|
|
95
|
+
return depth;
|
|
96
|
+
}
|
|
97
|
+
|
|
60
98
|
/**
|
|
61
99
|
* How much of a surface standing at height pointY, dist metres away, is lost to
|
|
62
100
|
* the medium in between. 0 is perfectly clear, 1 is the medium's own colour.
|
|
63
101
|
*/
|
|
64
102
|
float mediumFog(float dist, float pointY) {
|
|
103
|
+
/* Past the cutoff the air hides nothing, in either mode; the water column keeps its own law. */
|
|
104
|
+
bool cut = uFogLayer2.w > 0.0 && dist > uFogLayer2.w;
|
|
65
105
|
/*
|
|
66
106
|
* A depth ramp rather than a medium, for worlds authored against one.
|
|
67
107
|
*
|
|
@@ -77,30 +117,39 @@ float mediumFog(float dist, float pointY) {
|
|
|
77
117
|
* Height falloff has no meaning here and is ignored: a ramp is defined on distance
|
|
78
118
|
* alone.
|
|
79
119
|
*/
|
|
80
|
-
if (
|
|
81
|
-
float span = max(
|
|
82
|
-
float ramp = clamp((dist -
|
|
120
|
+
if (uFogRange.z == 1.0) {
|
|
121
|
+
float span = max(uFogRange.y - uFogRange.x, 1e-4);
|
|
122
|
+
float ramp = cut ? 0.0 : min(clamp((dist - uFogRange.x) / span, 0.0, 1.0), 1.0 - uFogLayer2.z);
|
|
83
123
|
if (uUnderwaterFactor <= 0.0) return ramp;
|
|
84
|
-
float wetLinear =
|
|
124
|
+
float wetLinear = uFogMedium.w * dist;
|
|
85
125
|
return mix(ramp, 1.0 - exp2(-wetLinear * wetLinear * 1.442695), uUnderwaterFactor);
|
|
86
126
|
}
|
|
87
127
|
|
|
88
|
-
//
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
float start = min(uFogNear, dist);
|
|
92
|
-
float before = dist > 0.0 ? start / dist : 0.0;
|
|
93
|
-
float t = (pointY - uFogEyeY) * uFogHeightFalloff;
|
|
94
|
-
float rest = t * (1.0 - before);
|
|
95
|
-
// (1 - e^-t)/t, which tends to 1 as the ray levels out. Guarded rather than
|
|
96
|
-
// branched: at t = 0 the quotient is 0/0, and 1e-4 of a scale height is 8 mm.
|
|
97
|
-
float denom = abs(rest) < 1e-4 ? 1e-4 : rest;
|
|
98
|
-
float air =
|
|
99
|
-
1.0 - exp(-uFogDensity * exp(-t * before) * (dist - start) * (1.0 - exp(-denom)) / denom);
|
|
128
|
+
// Beer-Lambert through both layers, leaving at least uFogLayer2.z of the surface: a floor on
|
|
129
|
+
// what survives rather than a ceiling on the depth, so the curve keeps its shape above it.
|
|
130
|
+
float air = cut ? 0.0 : 1.0 - max(exp(-hazeDepth(dist, pointY)), uFogLayer2.z);
|
|
100
131
|
if (uUnderwaterFactor <= 0.0) return air;
|
|
101
132
|
// Water is its own medium and keeps its own curve, so crossing the surface
|
|
102
133
|
// changes nothing about how the water column reads.
|
|
103
|
-
float wet =
|
|
134
|
+
float wet = uFogMedium.w * dist;
|
|
104
135
|
return mix(air, 1.0 - exp2(-wet * wet * 1.442695), uUnderwaterFactor);
|
|
105
136
|
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The sun's light the haze scatters toward the eye along a ray to a point at height pointY, dist
|
|
140
|
+
* metres away in the direction toPoint: added to a surface after its fog, never mixed. A lobe of
|
|
141
|
+
* cos^exponent about the light, by as much of the ray past the glow's own start as the haze hides —
|
|
142
|
+
* the haze's depth per metre carried over that stretch. None in a linear ramp, past the cutoff, or
|
|
143
|
+
* under water, which keeps its own medium.
|
|
144
|
+
*/
|
|
145
|
+
vec3 mediumGlow(float dist, float pointY, vec3 toPoint) {
|
|
146
|
+
if (uFogRange.z == 1.0 || max(max(uFogGlow.r, uFogGlow.g), uFogGlow.b) <= 0.0) return vec3(0.0);
|
|
147
|
+
if (uFogLayer2.w > 0.0 && dist > uFogLayer2.w) return vec3(0.0);
|
|
148
|
+
float span = dist - min(uFogRange.x, dist);
|
|
149
|
+
if (span <= 0.0) return vec3(0.0);
|
|
150
|
+
float perMetre = hazeDepth(dist, pointY) / span;
|
|
151
|
+
float hidden = 1.0 - exp(-perMetre * max(span - uFogGlowDir.w, 0.0));
|
|
152
|
+
float lobe = pow(clamp(dot(toPoint, uFogGlowDir.xyz), 0.0, 1.0), uFogGlow.a);
|
|
153
|
+
return uFogGlow.rgb * (lobe * hidden * (1.0 - uUnderwaterFactor));
|
|
154
|
+
}
|
|
106
155
|
`;
|