@driftengine/core 4.12.0 → 4.13.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/CHANGELOG.json +66 -0
- package/README.md +18 -9
- package/dist/geometry/meshBuilder.d.ts +10 -0
- package/dist/geometry/meshBuilder.js +11 -2
- package/dist/index.d.ts +3 -2
- package/dist/index.js +3 -2
- package/dist/math/color.d.ts +25 -0
- package/dist/math/color.js +37 -0
- package/dist/render/backend/webgl2/renderer.d.ts +17 -4
- package/dist/render/backend/webgl2/renderer.js +82 -47
- package/dist/render/backend/webgl2/skinScatter.d.ts +3 -1
- package/dist/render/backend/webgl2/skinScatter.js +5 -1
- 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/flockPass.d.ts +1 -1
- package/dist/render/backend/webgpu/giFieldPass.d.ts +3 -1
- package/dist/render/backend/webgpu/gpuDrivenPass.d.ts +10 -1
- package/dist/render/backend/webgpu/gpuDrivenPass.js +28 -11
- package/dist/render/backend/webgpu/panelPass.d.ts +1 -1
- package/dist/render/backend/webgpu/postPass.d.ts +2 -2
- package/dist/render/backend/webgpu/renderer.d.ts +19 -8
- package/dist/render/backend/webgpu/renderer.js +94 -38
- package/dist/render/backend/webgpu/scatterPass.d.ts +1 -1
- package/dist/render/backend/webgpu/skinScatterPass.d.ts +5 -1
- package/dist/render/backend/webgpu/skinScatterPass.js +9 -3
- package/dist/render/backend/webgpu/skyPass.d.ts +11 -1
- package/dist/render/backend/webgpu/waterPass.d.ts +1 -1
- package/dist/render/backend/webgpu/windStreakPass.d.ts +1 -1
- package/dist/render/causticsRenderer.d.ts +3 -1
- package/dist/render/causticsRenderer.js +5 -1
- package/dist/render/decalPass.d.ts +3 -1
- package/dist/render/decalPass.js +4 -1
- package/dist/render/flockRenderer.d.ts +1 -1
- package/dist/render/flockRenderer.js +4 -1
- package/dist/render/globalMediumPass.d.ts +3 -1
- package/dist/render/globalMediumPass.js +5 -1
- package/dist/render/gradeColor.d.ts +21 -0
- package/dist/render/gradeColor.js +64 -0
- package/dist/render/lightMatrix.d.ts +13 -0
- package/dist/render/lightMatrix.js +17 -0
- package/dist/render/passGrade.d.ts +13 -0
- package/dist/render/passGrade.js +47 -0
- package/dist/render/plumeRenderer.d.ts +1 -1
- package/dist/render/plumeRenderer.js +4 -1
- package/dist/render/renderQuality.d.ts +57 -18
- package/dist/render/renderQuality.js +3 -3
- package/dist/render/sceneTarget.d.ts +2 -0
- package/dist/render/sceneTarget.js +1 -0
- package/dist/render/sdfTextRenderer.d.ts +1 -1
- package/dist/render/sdfTextRenderer.js +1 -1
- package/dist/render/shaders/arcane.d.ts +1 -1
- package/dist/render/shaders/arcane.js +5 -1
- package/dist/render/shaders/bolt.d.ts +1 -1
- package/dist/render/shaders/bolt.js +4 -1
- package/dist/render/shaders/caustics.d.ts +1 -1
- package/dist/render/shaders/caustics.js +4 -1
- package/dist/render/shaders/decalProject.js +12 -2
- package/dist/render/shaders/film.d.ts +1 -1
- package/dist/render/shaders/film.js +22 -9
- package/dist/render/shaders/fire.d.ts +1 -1
- package/dist/render/shaders/fire.js +4 -1
- package/dist/render/shaders/flat/layerLooks.js +11 -19
- package/dist/render/shaders/flat/layered.js +33 -18
- package/dist/render/shaders/flock.d.ts +1 -1
- package/dist/render/shaders/flock.js +4 -1
- package/dist/render/shaders/generated/arcane.wgsl.d.ts +11 -1
- package/dist/render/shaders/generated/arcane.wgsl.js +11 -1
- package/dist/render/shaders/generated/bolt.wgsl.d.ts +12 -2
- package/dist/render/shaders/generated/bolt.wgsl.js +12 -2
- package/dist/render/shaders/generated/caustics.wgsl.d.ts +12 -2
- package/dist/render/shaders/generated/caustics.wgsl.js +12 -2
- package/dist/render/shaders/generated/decalProject.wgsl.d.ts +6 -1
- package/dist/render/shaders/generated/decalProject.wgsl.js +6 -1
- package/dist/render/shaders/generated/film.wgsl.d.ts +12 -2
- package/dist/render/shaders/generated/film.wgsl.js +12 -2
- package/dist/render/shaders/generated/fire.wgsl.d.ts +11 -1
- package/dist/render/shaders/generated/fire.wgsl.js +11 -1
- package/dist/render/shaders/generated/flat.wgsl.js +23 -23
- package/dist/render/shaders/generated/flock.wgsl.d.ts +12 -2
- package/dist/render/shaders/generated/flock.wgsl.js +12 -2
- package/dist/render/shaders/generated/globalMedium.wgsl.d.ts +12 -2
- package/dist/render/shaders/generated/globalMedium.wgsl.js +12 -2
- package/dist/render/shaders/generated/lightVolume.wgsl.d.ts +36 -16
- package/dist/render/shaders/generated/lightVolume.wgsl.js +39 -19
- package/dist/render/shaders/generated/panel.wgsl.d.ts +12 -2
- package/dist/render/shaders/generated/panel.wgsl.js +12 -2
- package/dist/render/shaders/generated/rush.wgsl.d.ts +31 -26
- package/dist/render/shaders/generated/rush.wgsl.js +31 -26
- package/dist/render/shaders/generated/scatter.wgsl.d.ts +12 -2
- package/dist/render/shaders/generated/scatter.wgsl.js +12 -2
- package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +11 -1
- package/dist/render/shaders/generated/skinBlur.wgsl.js +11 -1
- package/dist/render/shaders/generated/sky.wgsl.d.ts +12 -2
- package/dist/render/shaders/generated/sky.wgsl.js +12 -2
- package/dist/render/shaders/generated/smoke.wgsl.d.ts +11 -1
- package/dist/render/shaders/generated/smoke.wgsl.js +11 -1
- package/dist/render/shaders/generated/text.wgsl.d.ts +11 -1
- package/dist/render/shaders/generated/text.wgsl.js +11 -1
- package/dist/render/shaders/generated/water.wgsl.d.ts +12 -2
- package/dist/render/shaders/generated/water.wgsl.js +12 -2
- package/dist/render/shaders/generated/windStreaks.wgsl.d.ts +12 -2
- package/dist/render/shaders/generated/windStreaks.wgsl.js +12 -2
- package/dist/render/shaders/globalMedium.js +14 -1
- package/dist/render/shaders/gpudriven/present.wgsl.d.ts +15 -6
- package/dist/render/shaders/gpudriven/present.wgsl.js +71 -8
- package/dist/render/shaders/lightVolume.js +4 -1
- package/dist/render/shaders/line.d.ts +1 -1
- package/dist/render/shaders/outputTransform.d.ts +8 -2
- package/dist/render/shaders/outputTransform.js +18 -11
- package/dist/render/shaders/panel.d.ts +1 -1
- package/dist/render/shaders/panel.js +5 -1
- package/dist/render/shaders/particle.d.ts +3 -3
- package/dist/render/shaders/rush.js +8 -0
- package/dist/render/shaders/scatter.d.ts +1 -1
- package/dist/render/shaders/scatter.js +4 -1
- package/dist/render/shaders/sdfText.d.ts +1 -1
- package/dist/render/shaders/skinBlur.js +5 -1
- package/dist/render/shaders/sky.d.ts +1 -1
- package/dist/render/shaders/sky.js +5 -1
- package/dist/render/shaders/smoke.d.ts +1 -1
- package/dist/render/shaders/smoke.js +4 -1
- package/dist/render/shaders/text.d.ts +1 -1
- package/dist/render/shaders/text.js +4 -1
- package/dist/render/shaders/water.d.ts +1 -1
- package/dist/render/shaders/water.js +12 -2
- package/dist/render/shaders/windStreaks.d.ts +1 -1
- package/dist/render/shaders/windStreaks.js +4 -1
- package/dist/render/surfaceLayerLooks.d.ts +16 -4
- package/dist/render/surfaceLayerLooks.js +33 -14
- package/dist/render/surfaceLayers.d.ts +2 -2
- package/dist/render/textRenderer.d.ts +3 -1
- package/dist/render/textRenderer.js +7 -1
- package/dist/render/vertexDefaults.d.ts +1 -1
- package/dist/render/vertexDefaults.js +1 -1
- package/dist/render/waterRenderer.d.ts +1 -1
- package/dist/render/waterRenderer.js +4 -1
- package/dist/render/windStreakRenderer.d.ts +3 -1
- package/dist/render/windStreakRenderer.js +5 -1
- package/package.json +6 -6
- package/src/geometry/meshBuilder.ts +21 -2
- package/src/index.ts +11 -2
- package/src/math/color.ts +39 -0
- package/src/render/backend/webgl2/renderer.ts +134 -44
- package/src/render/backend/webgl2/skinScatter.ts +5 -0
- package/src/render/backend/webgpu/giFieldPass.ts +3 -1
- package/src/render/backend/webgpu/gpuDrivenPass.ts +29 -13
- package/src/render/backend/webgpu/renderer.ts +104 -37
- package/src/render/backend/webgpu/skinScatterPass.ts +17 -1
- package/src/render/causticsRenderer.ts +5 -0
- package/src/render/decalPass.ts +3 -0
- package/src/render/flockRenderer.ts +5 -0
- package/src/render/globalMediumPass.ts +10 -1
- package/src/render/gradeColor.ts +90 -0
- package/src/render/lightMatrix.ts +32 -0
- package/src/render/passGrade.ts +58 -0
- package/src/render/plumeRenderer.ts +5 -0
- package/src/render/renderQuality.ts +60 -21
- package/src/render/sceneTarget.ts +3 -0
- package/src/render/sdfTextRenderer.ts +1 -1
- package/src/render/shaders/arcane.ts +5 -1
- package/src/render/shaders/bolt.ts +4 -1
- package/src/render/shaders/caustics.ts +4 -1
- package/src/render/shaders/decalProject.ts +12 -2
- package/src/render/shaders/film.ts +22 -9
- package/src/render/shaders/fire.ts +4 -1
- package/src/render/shaders/flat/layerLooks.ts +11 -19
- package/src/render/shaders/flat/layered.ts +33 -18
- package/src/render/shaders/flock.ts +4 -1
- package/src/render/shaders/generated/arcane.wgsl.ts +11 -1
- package/src/render/shaders/generated/bolt.wgsl.ts +12 -2
- package/src/render/shaders/generated/caustics.wgsl.ts +12 -2
- package/src/render/shaders/generated/decalProject.wgsl.ts +6 -1
- package/src/render/shaders/generated/film.wgsl.ts +12 -2
- package/src/render/shaders/generated/fire.wgsl.ts +11 -1
- package/src/render/shaders/generated/flat.wgsl.ts +23 -23
- package/src/render/shaders/generated/flock.wgsl.ts +12 -2
- package/src/render/shaders/generated/globalMedium.wgsl.ts +12 -2
- package/src/render/shaders/generated/lightVolume.wgsl.ts +39 -19
- package/src/render/shaders/generated/panel.wgsl.ts +12 -2
- package/src/render/shaders/generated/rush.wgsl.ts +31 -26
- package/src/render/shaders/generated/scatter.wgsl.ts +12 -2
- package/src/render/shaders/generated/skinBlur.wgsl.ts +11 -1
- package/src/render/shaders/generated/sky.wgsl.ts +12 -2
- package/src/render/shaders/generated/smoke.wgsl.ts +11 -1
- package/src/render/shaders/generated/text.wgsl.ts +11 -1
- package/src/render/shaders/generated/water.wgsl.ts +12 -2
- package/src/render/shaders/generated/windStreaks.wgsl.ts +12 -2
- package/src/render/shaders/globalMedium.ts +14 -1
- package/src/render/shaders/gpudriven/present.wgsl.ts +72 -8
- package/src/render/shaders/lightVolume.ts +4 -1
- package/src/render/shaders/outputTransform.ts +19 -11
- package/src/render/shaders/panel.ts +5 -1
- package/src/render/shaders/rush.ts +8 -0
- package/src/render/shaders/scatter.ts +4 -1
- package/src/render/shaders/skinBlur.ts +5 -1
- package/src/render/shaders/sky.ts +5 -1
- package/src/render/shaders/smoke.ts +4 -1
- package/src/render/shaders/text.ts +4 -1
- package/src/render/shaders/water.ts +12 -2
- package/src/render/shaders/windStreaks.ts +4 -1
- package/src/render/surfaceLayerLooks.ts +49 -18
- package/src/render/surfaceLayers.ts +2 -2
- package/src/render/textRenderer.ts +6 -0
- package/src/render/vertexDefaults.ts +1 -1
- package/src/render/waterRenderer.ts +5 -0
- package/src/render/windStreakRenderer.ts +5 -0
|
@@ -253,8 +253,9 @@ import {
|
|
|
253
253
|
OUTPUT_TRANSFORM_CODE,
|
|
254
254
|
RUSH_REACH_UV,
|
|
255
255
|
clampMotionShare,
|
|
256
|
-
forwardTransformCode,
|
|
257
256
|
} from '../../vertexDefaults.ts';
|
|
257
|
+
import { gradeColorInto } from '../../gradeColor.ts';
|
|
258
|
+
import { interfaceGrade, passGradeCode, passGradeExposure } from '../../passGrade.ts';
|
|
258
259
|
import { FRINGE_FLOATS, resolveFringe } from '../../shaders/fringe.ts';
|
|
259
260
|
import { displayHeadroom, type DisplayRange } from '../../displayRange.ts';
|
|
260
261
|
import {
|
|
@@ -1512,7 +1513,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
1512
1513
|
* **Resolved by the caller and held, rather than defaulted per use.** Every shadow term
|
|
1513
1514
|
* below reads it, and the constants they replaced were `DEFAULT_RENDER_QUALITY` copies —
|
|
1514
1515
|
* which measured wrong the moment a scene overrode one. `demo/dayClock` resolves
|
|
1515
|
-
* `directionalShadowMaxSlope` to 9 where the default
|
|
1516
|
+
* `directionalShadowMaxSlope` to 9 where the default was 3 (60 since 4.13.0).
|
|
1516
1517
|
*/
|
|
1517
1518
|
readonly quality: Readonly<RenderQuality>;
|
|
1518
1519
|
|
|
@@ -2226,6 +2227,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2226
2227
|
private readonly mediumUpsampleUniforms: GPUBuffer;
|
|
2227
2228
|
private readonly mediumUpsampleStaging = new ArrayBuffer(MEDIUM_UPSAMPLE_SIZE);
|
|
2228
2229
|
private readonly mediumUpsampleFloats = new Float32Array(this.mediumUpsampleStaging);
|
|
2230
|
+
private readonly mediumUpsampleInts = new Int32Array(this.mediumUpsampleStaging);
|
|
2229
2231
|
private mediumUpsampleGroup: GPUBindGroup | null = null;
|
|
2230
2232
|
/** The inverse projection the composite linearises a depth with, built once per frame. */
|
|
2231
2233
|
private readonly mediumInvProjection = mat4.create();
|
|
@@ -2476,6 +2478,12 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2476
2478
|
*/
|
|
2477
2479
|
private readonly skyUniforms: UniformRing;
|
|
2478
2480
|
private readonly skyFloats = new Float32Array(SKY_UNIFORM_SIZE / 4);
|
|
2481
|
+
/** The same block's ints: the grade the sky draws with. */
|
|
2482
|
+
private readonly skyInts = new Int32Array(this.skyFloats.buffer);
|
|
2483
|
+
/** `clearOf`'s scratch: a clear colour as the open target takes it. */
|
|
2484
|
+
private readonly gradedClear: Vec3 = [0, 0, 0];
|
|
2485
|
+
/** An inset's clear as its quad's uniform, rewritten in place: the colour and an alpha of 1. */
|
|
2486
|
+
private readonly insetClear = new Float32Array([0, 0, 0, 1]);
|
|
2479
2487
|
private readonly skyBindGroup: GPUBindGroup;
|
|
2480
2488
|
/** The camera's inverse with the clip correction undone, rebuilt once a frame. */
|
|
2481
2489
|
private readonly skyInvViewProj = new Float32Array(16);
|
|
@@ -2560,6 +2568,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2560
2568
|
private decalUniforms!: GPUBuffer;
|
|
2561
2569
|
private readonly decalStaging = new ArrayBuffer(DECAL_SLOT * MAX_DRAWN_DECALS);
|
|
2562
2570
|
private readonly decalFloats = new Float32Array(this.decalStaging);
|
|
2571
|
+
/** The same slots' ints: the grade the frame under the marks was drawn with. */
|
|
2572
|
+
private readonly decalInts = new Int32Array(this.decalStaging);
|
|
2563
2573
|
private decalBindGroup: GPUBindGroup | null = null;
|
|
2564
2574
|
/** Said once where a mark cannot be drawn at all, rather than dropped in silence. */
|
|
2565
2575
|
private decalsRefused = false;
|
|
@@ -2743,9 +2753,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2743
2753
|
* still owed.
|
|
2744
2754
|
*/
|
|
2745
2755
|
this.pass = null;
|
|
2746
|
-
|
|
2747
|
-
this.mirrorClearColor[
|
|
2748
|
-
this.mirrorClearColor[
|
|
2756
|
+
const mirrorClear = this.clearOf(clearColor);
|
|
2757
|
+
this.mirrorClearColor[0] = mirrorClear[0];
|
|
2758
|
+
this.mirrorClearColor[1] = mirrorClear[1];
|
|
2759
|
+
this.mirrorClearColor[2] = mirrorClear[2];
|
|
2749
2760
|
this.pendingClearMask |= MIRROR_TARGET;
|
|
2750
2761
|
this.reflectionPassActive = true;
|
|
2751
2762
|
/* Closed in `endPlanarReflection`, matching `renderer.ts:2054`. */
|
|
@@ -2916,6 +2927,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
2916
2927
|
i[atF('uClipEnabled')] = this.reflectionPassActive ? 1 : 0;
|
|
2917
2928
|
f.set(this.reflection?.clipPlane ?? NO_CLIP_PLANE, atF('uClipPlane'));
|
|
2918
2929
|
f[atF('uSheen')] = sheen;
|
|
2930
|
+
i[atF('uOutputTransform')] = this.passGrade();
|
|
2931
|
+
f[atF('uOutputExposure')] = this.passGradeExposure();
|
|
2919
2932
|
f[atF('uFilmRoughness')] = Math.min(1, Math.max(0, options.roughness ?? 0));
|
|
2920
2933
|
f[atF('uFilmRoughnessCycles')] = Math.max(0.01, options.roughnessCyclesPerMetre ?? 60);
|
|
2921
2934
|
|
|
@@ -3888,6 +3901,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
3888
3901
|
const fragAt = (name: string): number => PANEL_FRAG_FIELDS[name]?.offset ?? 0;
|
|
3889
3902
|
this.panelFrags.writeFloats(fragmentSlot, fragAt('uColor'), color);
|
|
3890
3903
|
this.panelFrags.writeFloat(fragmentSlot, fragAt('uAlpha'), alpha);
|
|
3904
|
+
/* An interface: the screen encode, never the curve. See `interfaceGrade`. */
|
|
3905
|
+
this.panelFrags.writeInt(
|
|
3906
|
+
fragmentSlot,
|
|
3907
|
+
fragAt('uOutputTransform'),
|
|
3908
|
+
interfaceGrade(this.passGrade()),
|
|
3909
|
+
);
|
|
3910
|
+
this.panelFrags.writeFloat(fragmentSlot, fragAt('uOutputExposure'), 1);
|
|
3891
3911
|
|
|
3892
3912
|
const panelPipe = panelPipeline(this.targetPipelines(), this.surface.device, this.panelLayout);
|
|
3893
3913
|
/* `allocate` already returns a byte offset, not an index. Multiplying by the stride
|
|
@@ -4021,8 +4041,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4021
4041
|
font.atlasWidth,
|
|
4022
4042
|
font.atlasHeight,
|
|
4023
4043
|
]);
|
|
4024
|
-
this.sdfTextFrags.writeInt(fragmentSlot, fragAt('uOutputTransform'), this.
|
|
4025
|
-
this.sdfTextFrags.writeFloat(fragmentSlot, fragAt('uOutputExposure'), this.
|
|
4044
|
+
this.sdfTextFrags.writeInt(fragmentSlot, fragAt('uOutputTransform'), this.passGrade());
|
|
4045
|
+
this.sdfTextFrags.writeFloat(fragmentSlot, fragAt('uOutputExposure'), this.passGradeExposure());
|
|
4026
4046
|
|
|
4027
4047
|
const sdfPipe = sdfTextPipeline(
|
|
4028
4048
|
this.blendedPipelines(),
|
|
@@ -4143,6 +4163,13 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4143
4163
|
this.textFrags.writeFloats(fragmentSlot, fragAt('uColor'), style.color);
|
|
4144
4164
|
this.textFrags.writeFloat(fragmentSlot, fragAt('uGlow'), style.glow);
|
|
4145
4165
|
this.textFrags.writeFloat(fragmentSlot, fragAt('uAlpha'), style.alpha);
|
|
4166
|
+
/* An interface: the screen encode, never the curve. See `interfaceGrade`. */
|
|
4167
|
+
this.textFrags.writeInt(
|
|
4168
|
+
fragmentSlot,
|
|
4169
|
+
fragAt('uOutputTransform'),
|
|
4170
|
+
interfaceGrade(this.passGrade()),
|
|
4171
|
+
);
|
|
4172
|
+
this.textFrags.writeFloat(fragmentSlot, fragAt('uOutputExposure'), 1);
|
|
4146
4173
|
|
|
4147
4174
|
const pass = this.openTextPass();
|
|
4148
4175
|
if (pass === null) return;
|
|
@@ -4505,20 +4532,29 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4505
4532
|
* array allocated with the batch, so the frame loop still allocates nothing.
|
|
4506
4533
|
*/
|
|
4507
4534
|
/**
|
|
4508
|
-
* The
|
|
4509
|
-
*
|
|
4510
|
-
* The
|
|
4511
|
-
* one verb with two implementations, and a frame that grades on one backend and not the other
|
|
4512
|
-
* is the silent kind of disagreement. See that method for what the gate is protecting.
|
|
4535
|
+
* The grade a pass applies to what it draws into the target open now, and the exposure it
|
|
4536
|
+
* applies it at: the WebGPU half of `Renderer.passGrade`, and it has to agree with it draw for
|
|
4537
|
+
* draw. The decision is `passGrade.ts`'s, so both halves only gather the state it is taken from.
|
|
4513
4538
|
*/
|
|
4514
|
-
private
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4539
|
+
private passGrade(): number {
|
|
4540
|
+
const storing = this.probePassActive || this.capturePassActive;
|
|
4541
|
+
const transform = this.quality.outputTransform;
|
|
4542
|
+
return passGradeCode(storing, this.framePresented, this.resolveGrades(), transform);
|
|
4543
|
+
}
|
|
4544
|
+
|
|
4545
|
+
private passGradeExposure(): number {
|
|
4546
|
+
const storing = this.probePassActive || this.capturePassActive;
|
|
4547
|
+
return passGradeExposure(storing, this.framePresented, this.resolveGrades(), this.exposure);
|
|
4548
|
+
}
|
|
4549
|
+
|
|
4550
|
+
/** Whether the frame's resolve applies the transform, so the passes before it draw linear. */
|
|
4551
|
+
private resolveGrades(): boolean {
|
|
4552
|
+
return this.hasComposite && this.quality.hdrScene;
|
|
4518
4553
|
}
|
|
4519
4554
|
|
|
4520
|
-
|
|
4521
|
-
|
|
4555
|
+
/** A clear colour as the open target takes it, graded as a pass would. See `gradeColor.ts`. */
|
|
4556
|
+
private clearOf(color: Readonly<Vec3>): Vec3 {
|
|
4557
|
+
return gradeColorInto(this.passGrade(), this.passGradeExposure(), color, this.gradedClear);
|
|
4522
4558
|
}
|
|
4523
4559
|
|
|
4524
4560
|
drawParticles(
|
|
@@ -4641,8 +4677,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4641
4677
|
if (has('uFogEnabled')) i[atF('uFogEnabled')] = batch.fog ? 1 : 0;
|
|
4642
4678
|
/* Every material's fragment stage declares these, so they are unconditional like
|
|
4643
4679
|
`uCameraPos` rather than guarded like the material-specific fields above. */
|
|
4644
|
-
i[atF('uOutputTransform')] = this.
|
|
4645
|
-
f[atF('uOutputExposure')] = this.
|
|
4680
|
+
i[atF('uOutputTransform')] = this.passGrade();
|
|
4681
|
+
f[atF('uOutputExposure')] = this.passGradeExposure();
|
|
4646
4682
|
|
|
4647
4683
|
const spriteGroup = batch.material === 'sprite' ? this.spriteFades(batch, camera, f) : null;
|
|
4648
4684
|
|
|
@@ -4896,6 +4932,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
4896
4932
|
frags.writeInt(fragmentSlot, atF('uFogMode'), medium.fogMode);
|
|
4897
4933
|
frags.writeFloat(fragmentSlot, atF('uFogNear'), medium.fogNear);
|
|
4898
4934
|
frags.writeFloat(fragmentSlot, atF('uFogFar'), medium.fogFar);
|
|
4935
|
+
frags.writeInt(fragmentSlot, atF('uOutputTransform'), this.passGrade());
|
|
4936
|
+
frags.writeFloat(fragmentSlot, atF('uOutputExposure'), this.passGradeExposure());
|
|
4899
4937
|
/*
|
|
4900
4938
|
* **Written for every material, not only the one that reads it.** `arcane` is the only
|
|
4901
4939
|
* plume whose block declares `uTint`, and the slot it lands in is past the end of fire's
|
|
@@ -5096,6 +5134,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5096
5134
|
/* No reflection pass yet: the shader's own "off" path, not an improvised one. */
|
|
5097
5135
|
i[atF('uReflectionEnabled')] = mirror === null ? 0 : 1;
|
|
5098
5136
|
i[atF('uReflectionFilterTaps')] = water.reflectionFilterTaps;
|
|
5137
|
+
i[atF('uOutputTransform')] = this.passGrade();
|
|
5138
|
+
f[atF('uOutputExposure')] = this.passGradeExposure();
|
|
5099
5139
|
f[atF('uReflectionTexelSize')] = mirror === null ? 1 : 1 / mirror.width;
|
|
5100
5140
|
f[atF('uReflectionTexelSize') + 1] = mirror === null ? 1 : 1 / mirror.height;
|
|
5101
5141
|
|
|
@@ -5248,6 +5288,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5248
5288
|
ring.writeFloats(fragmentSlot, at('uDustOffset'), options.driftM ?? NO_DRIFT);
|
|
5249
5289
|
ring.writeFloat(fragmentSlot, at('uNear'), options.nearM ?? 0);
|
|
5250
5290
|
ring.writeInt(fragmentSlot, at('uSamples'), this.quality.lightVolumeSamples);
|
|
5291
|
+
ring.writeInt(fragmentSlot, at('uOutputTransform'), this.passGrade());
|
|
5292
|
+
ring.writeFloat(fragmentSlot, at('uOutputExposure'), this.passGradeExposure());
|
|
5251
5293
|
|
|
5252
5294
|
/*
|
|
5253
5295
|
* The clamp that makes a beam end on the floor, and the snapshot it reads.
|
|
@@ -5471,6 +5513,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5471
5513
|
v[at('uTime')] = timeSeconds;
|
|
5472
5514
|
|
|
5473
5515
|
this.windStreakFragFloats.set(tint, (WIND_STREAK_FRAG_FIELDS.uTint.offset ?? 0) / 4);
|
|
5516
|
+
this.windStreakFragBlock[WIND_STREAK_FRAG_FIELDS.uOutputTransform.offset / 4] =
|
|
5517
|
+
this.passGrade();
|
|
5518
|
+
this.windStreakFragFloats[WIND_STREAK_FRAG_FIELDS.uOutputExposure.offset / 4] =
|
|
5519
|
+
this.passGradeExposure();
|
|
5474
5520
|
|
|
5475
5521
|
this.windStreakVerts.writeBlock(vertexSlot, this.windStreakVertBlock);
|
|
5476
5522
|
this.windStreakFrags.writeBlock(fragmentSlot, this.windStreakFragBlock);
|
|
@@ -5576,6 +5622,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5576
5622
|
v[at('uWind') + 1] = windZ;
|
|
5577
5623
|
|
|
5578
5624
|
this.flockFragFloats.set(tint, (FLOCK_FRAG_FIELDS.uTint.offset ?? 0) / 4);
|
|
5625
|
+
this.flockFragBlock[FLOCK_FRAG_FIELDS.uOutputTransform.offset / 4] = this.passGrade();
|
|
5626
|
+
this.flockFragFloats[FLOCK_FRAG_FIELDS.uOutputExposure.offset / 4] = this.passGradeExposure();
|
|
5579
5627
|
|
|
5580
5628
|
this.flockVerts.writeBlock(vertexSlot, this.flockVertBlock);
|
|
5581
5629
|
this.flockFrags.writeBlock(fragmentSlot, this.flockFragBlock);
|
|
@@ -5687,6 +5735,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5687
5735
|
f.set(core, atF('uCoreColor'));
|
|
5688
5736
|
f.set(edge, atF('uEdgeColor'));
|
|
5689
5737
|
f[atF('uCoreGain')] = coreGain;
|
|
5738
|
+
i[atF('uOutputTransform')] = this.passGrade();
|
|
5739
|
+
f[atF('uOutputExposure')] = this.passGradeExposure();
|
|
5690
5740
|
f.set(camera.position, atF('uCameraPos'));
|
|
5691
5741
|
/* The medium, through the same resolver every other pass here binds from. */
|
|
5692
5742
|
const medium = resolveAtmosphere(
|
|
@@ -5823,8 +5873,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
5823
5873
|
this.lineFrags.writeFloat(fragmentSlot, atF('uOpacity'), opacity);
|
|
5824
5874
|
this.lineFrags.writeFloats(fragmentSlot, atF('uCameraPos'), camera.position);
|
|
5825
5875
|
this.lineFrags.writeFloat(fragmentSlot, atF('uSoftness'), softness);
|
|
5826
|
-
this.lineFrags.writeInt(fragmentSlot, atF('uOutputTransform'), this.
|
|
5827
|
-
this.lineFrags.writeFloat(fragmentSlot, atF('uOutputExposure'), this.
|
|
5876
|
+
this.lineFrags.writeInt(fragmentSlot, atF('uOutputTransform'), this.passGrade());
|
|
5877
|
+
this.lineFrags.writeFloat(fragmentSlot, atF('uOutputExposure'), this.passGradeExposure());
|
|
5828
5878
|
/* The medium, through the same resolver every other pass here binds from. */
|
|
5829
5879
|
const medium = resolveAtmosphere(
|
|
5830
5880
|
env,
|
|
@@ -8482,9 +8532,10 @@ export class WebGPURenderer implements RendererApi {
|
|
|
8482
8532
|
* at 92 MB a frame at 824x1830. `ensurePass` opens it on the first draw that wants it, which
|
|
8483
8533
|
* for such a scene is after the last mirror, so it is opened once and cleared once.
|
|
8484
8534
|
*/
|
|
8485
|
-
|
|
8486
|
-
this.pendingClearColor[
|
|
8487
|
-
this.pendingClearColor[
|
|
8535
|
+
const frameClear = this.clearOf(clearColor);
|
|
8536
|
+
this.pendingClearColor[0] = frameClear[0];
|
|
8537
|
+
this.pendingClearColor[1] = frameClear[1];
|
|
8538
|
+
this.pendingClearColor[2] = frameClear[2];
|
|
8488
8539
|
this.pendingClearMask = SCENE_TARGET;
|
|
8489
8540
|
if (this.quality.deferFramePass) {
|
|
8490
8541
|
this.pass = null;
|
|
@@ -9241,8 +9292,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
9241
9292
|
* `screenEffects` off, or on without `hdrScene`, the mesh pass writes what reaches the
|
|
9242
9293
|
* canvas and grades as it always did.
|
|
9243
9294
|
*/
|
|
9244
|
-
i[at('uOutputTransform')] = this.
|
|
9245
|
-
f[at('uOutputExposure')] = this.
|
|
9295
|
+
i[at('uOutputTransform')] = this.passGrade();
|
|
9296
|
+
f[at('uOutputExposure')] = this.passGradeExposure();
|
|
9246
9297
|
/* The projection occlusion needs, settled by the pass that has a camera. */
|
|
9247
9298
|
this.frameProjection = camera.projection as Float32Array;
|
|
9248
9299
|
|
|
@@ -10096,7 +10147,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10096
10147
|
/* `1` is sRGB alone: the conversion without the curve. `Math.min` rather than a literal, so
|
|
10097
10148
|
a renderer asked for `none` stays at none. See `TranslucentMeshOptions.toneMapped`. */
|
|
10098
10149
|
if (!toneMapped) {
|
|
10099
|
-
this.perFrameInts[this.materialField('uOutputTransform')] = Math.min(this.
|
|
10150
|
+
this.perFrameInts[this.materialField('uOutputTransform')] = Math.min(this.passGrade(), 1);
|
|
10100
10151
|
}
|
|
10101
10152
|
/*
|
|
10102
10153
|
* **Refraction, and the snapshot it reads is taken here or the draw does not refract.**
|
|
@@ -10268,7 +10319,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10268
10319
|
shared, so a strength left set is worn by everything drawn after it. */
|
|
10269
10320
|
if (refracting) this.releaseSeeThrough();
|
|
10270
10321
|
if (!toneMapped) {
|
|
10271
|
-
this.perFrameInts[this.materialField('uOutputTransform')] = this.
|
|
10322
|
+
this.perFrameInts[this.materialField('uOutputTransform')] = this.passGrade();
|
|
10272
10323
|
}
|
|
10273
10324
|
if (ditheredBlend) {
|
|
10274
10325
|
this.perFrameFloats[this.materialField('uCutout') + 1] =
|
|
@@ -10736,7 +10787,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10736
10787
|
this.perFrameInts[this.materialField('uFogEnabled')] = fog;
|
|
10737
10788
|
}
|
|
10738
10789
|
if (!toneMapped) {
|
|
10739
|
-
this.perFrameInts[this.materialField('uOutputTransform')] = Math.min(this.
|
|
10790
|
+
this.perFrameInts[this.materialField('uOutputTransform')] = Math.min(this.passGrade(), 1);
|
|
10740
10791
|
}
|
|
10741
10792
|
|
|
10742
10793
|
const depthWrite = options.depthWrite ?? true;
|
|
@@ -10846,7 +10897,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10846
10897
|
this.perFrameInts[this.materialField('uFogEnabled')] = this.surfaceFog;
|
|
10847
10898
|
}
|
|
10848
10899
|
if (!toneMapped) {
|
|
10849
|
-
this.perFrameInts[this.materialField('uOutputTransform')] = this.
|
|
10900
|
+
this.perFrameInts[this.materialField('uOutputTransform')] = this.passGrade();
|
|
10850
10901
|
}
|
|
10851
10902
|
if (refracting) this.releaseSeeThrough();
|
|
10852
10903
|
if (ditheredBlend) {
|
|
@@ -10963,6 +11014,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
10963
11014
|
i[atF('uFogMode')] = medium.fogMode;
|
|
10964
11015
|
f[atF('uFogNear')] = medium.fogNear;
|
|
10965
11016
|
f[atF('uFogFar')] = medium.fogFar;
|
|
11017
|
+
i[atF('uOutputTransform')] = this.passGrade();
|
|
11018
|
+
f[atF('uOutputExposure')] = this.passGradeExposure();
|
|
10966
11019
|
|
|
10967
11020
|
const fragmentSlot = this.scatterFrags.allocate();
|
|
10968
11021
|
if (fragmentSlot === null) return;
|
|
@@ -12583,6 +12636,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
12583
12636
|
);
|
|
12584
12637
|
f.set(medium.underwaterColor, at('uUnderwaterColor'));
|
|
12585
12638
|
f[at('uUnderwaterFactor')] = medium.underwaterFactor;
|
|
12639
|
+
this.skyInts[at('uOutputTransform')] = this.passGrade();
|
|
12640
|
+
f[at('uOutputExposure')] = this.passGradeExposure();
|
|
12586
12641
|
|
|
12587
12642
|
const slot = this.skyUniforms.allocate();
|
|
12588
12643
|
if (slot === null) {
|
|
@@ -12747,6 +12802,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
12747
12802
|
? (OUTPUT_TRANSFORM_CODE[this.quality.outputTransform] ?? 0)
|
|
12748
12803
|
: 0;
|
|
12749
12804
|
f[at('uOutputExposure')] = graded ? this.exposure : 1;
|
|
12805
|
+
/* The occlusion multiplied in as an encoded pixel takes it, where the passes graded the scene. */
|
|
12806
|
+
i[at('uSceneGraded')] = this.passGrade() !== 0 ? 1 : 0;
|
|
12750
12807
|
f.set(this.filmA, at('uFilmA'));
|
|
12751
12808
|
f.set(this.filmB, at('uFilmB'));
|
|
12752
12809
|
f[at('uDisplayHeadroom')] = this.headroom;
|
|
@@ -13376,8 +13433,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
13376
13433
|
});
|
|
13377
13434
|
const context = this.passContext;
|
|
13378
13435
|
context.pass = pass;
|
|
13379
|
-
context.outputTransform = this.
|
|
13380
|
-
context.outputExposure = this.
|
|
13436
|
+
context.outputTransform = this.passGrade();
|
|
13437
|
+
context.outputExposure = this.passGradeExposure();
|
|
13381
13438
|
context.jitter = this.latePassJitter;
|
|
13382
13439
|
context.format = RECON_HISTORY_FORMAT;
|
|
13383
13440
|
context.depthFormat = DEPTH_FORMAT;
|
|
@@ -14026,6 +14083,7 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14026
14083
|
f[base + at('uDecalOpacity')] = decal.opacity;
|
|
14027
14084
|
f[base + at('uDecalFacingCos')] = decal.facingCos;
|
|
14028
14085
|
f[base + at('uDecalSoftness')] = decal.softness;
|
|
14086
|
+
this.decalInts[base + at('uOutputTransform')] = this.passGrade();
|
|
14029
14087
|
slot++;
|
|
14030
14088
|
});
|
|
14031
14089
|
device.queue.writeBuffer(this.decalUniforms, 0, this.decalStaging, 0, slot * DECAL_SLOT);
|
|
@@ -14272,6 +14330,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14272
14330
|
g[upsampleAt('uDepthToViewZ') + 1] = inv[14] ?? 0;
|
|
14273
14331
|
g[upsampleAt('uDepthToViewZ') + 2] = inv[11] ?? 0;
|
|
14274
14332
|
g[upsampleAt('uDepthToViewZ') + 3] = inv[15] ?? 1;
|
|
14333
|
+
this.mediumUpsampleInts[upsampleAt('uOutputTransform')] = this.passGrade();
|
|
14334
|
+
g[upsampleAt('uOutputExposure')] = this.passGradeExposure();
|
|
14275
14335
|
device.queue.writeBuffer(this.mediumUpsampleUniforms, 0, this.mediumUpsampleStaging);
|
|
14276
14336
|
|
|
14277
14337
|
const march = encoder.beginRenderPass({
|
|
@@ -14997,6 +15057,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
14997
15057
|
projection,
|
|
14998
15058
|
this.skinInverseProjection,
|
|
14999
15059
|
() => this.gpuTimer.writesFor(),
|
|
15060
|
+
this.passGrade(),
|
|
15061
|
+
this.passGradeExposure(),
|
|
15000
15062
|
);
|
|
15001
15063
|
this.pass = encoder.beginRenderPass({
|
|
15002
15064
|
label: 'skin.scattered',
|
|
@@ -16207,8 +16269,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
16207
16269
|
const context = this.passContext;
|
|
16208
16270
|
context.pass = pass;
|
|
16209
16271
|
/* The frame's grade, so a contributed pass can obey the 2026-08-17 rule. See `PassContext`. */
|
|
16210
|
-
context.outputTransform = this.
|
|
16211
|
-
context.outputExposure = this.
|
|
16272
|
+
context.outputTransform = this.passGrade();
|
|
16273
|
+
context.outputExposure = this.passGradeExposure();
|
|
16212
16274
|
context.format = target.format;
|
|
16213
16275
|
context.samples = target.sampleCount;
|
|
16214
16276
|
return context as PassContext;
|
|
@@ -16705,8 +16767,11 @@ export class WebGPURenderer implements RendererApi {
|
|
|
16705
16767
|
const slot = this.insetUniforms.allocate();
|
|
16706
16768
|
if (slot !== null) {
|
|
16707
16769
|
/* Only the colour: the quad covers the viewport set above, so it needs no geometry. */
|
|
16708
|
-
const color = clearColor ?? BLACK_CLEAR;
|
|
16709
|
-
this.
|
|
16770
|
+
const color = this.clearOf(clearColor ?? BLACK_CLEAR);
|
|
16771
|
+
this.insetClear[0] = color[0];
|
|
16772
|
+
this.insetClear[1] = color[1];
|
|
16773
|
+
this.insetClear[2] = color[2];
|
|
16774
|
+
this.insetUniforms.writeFloats(slot, 0, this.insetClear);
|
|
16710
16775
|
const insetPipe = insetPipeline(
|
|
16711
16776
|
this.targetPipelines(),
|
|
16712
16777
|
this.surface.device,
|
|
@@ -17368,6 +17433,8 @@ export class WebGPURenderer implements RendererApi {
|
|
|
17368
17433
|
f[atF('uTime')] = timeSeconds;
|
|
17369
17434
|
f[atF('uMaxDrop')] = CAUSTICS_MAX_DROP_M;
|
|
17370
17435
|
f[atF('uStrength')] = strength;
|
|
17436
|
+
i[atF('uOutputTransform')] = this.passGrade();
|
|
17437
|
+
f[atF('uOutputExposure')] = this.passGradeExposure();
|
|
17371
17438
|
f.set(env.directionalDir, atF('uLightDir'));
|
|
17372
17439
|
|
|
17373
17440
|
/*
|
|
@@ -41,6 +41,8 @@ export class SkinScatterPass {
|
|
|
41
41
|
private readonly layout: GPUBindGroupLayout;
|
|
42
42
|
private readonly uniforms: GPUBuffer;
|
|
43
43
|
private readonly staging = new Float32Array((SLOT * 2) / 4);
|
|
44
|
+
/** The same blocks' ints: the frame's grade, which the second axis applies. */
|
|
45
|
+
private readonly stagingInts = new Int32Array(this.staging.buffer);
|
|
44
46
|
private readonly linear: GPUSampler;
|
|
45
47
|
private readonly nearest: GPUSampler;
|
|
46
48
|
private readonly commands: CommandPool = createCommandPool(16);
|
|
@@ -212,6 +214,9 @@ export class SkinScatterPass {
|
|
|
212
214
|
projection: ArrayLike<number>,
|
|
213
215
|
inverseProjection: ArrayLike<number>,
|
|
214
216
|
timestampWrites: () => GPURenderPassTimestampWrites | undefined,
|
|
217
|
+
/** The frame's grade, which the second axis applies to the light it adds. */
|
|
218
|
+
outputTransform = 0,
|
|
219
|
+
outputExposure = 1,
|
|
215
220
|
): void {
|
|
216
221
|
const diffuse = this.diffuse;
|
|
217
222
|
if (diffuse === null || this.diffuseView === null || this.acrossView === null) return;
|
|
@@ -221,7 +226,14 @@ export class SkinScatterPass {
|
|
|
221
226
|
this.groupDown = this.group('skin.blurDown', this.acrossView, depth);
|
|
222
227
|
this.groupsDepth = depth;
|
|
223
228
|
}
|
|
224
|
-
this.writeUniforms(
|
|
229
|
+
this.writeUniforms(
|
|
230
|
+
diffuse.width,
|
|
231
|
+
diffuse.height,
|
|
232
|
+
projection,
|
|
233
|
+
inverseProjection,
|
|
234
|
+
outputTransform,
|
|
235
|
+
outputExposure,
|
|
236
|
+
);
|
|
225
237
|
|
|
226
238
|
const across = postPipeline(
|
|
227
239
|
pipelines,
|
|
@@ -316,6 +328,8 @@ export class SkinScatterPass {
|
|
|
316
328
|
height: number,
|
|
317
329
|
projection: ArrayLike<number>,
|
|
318
330
|
inverse: ArrayLike<number>,
|
|
331
|
+
outputTransform: number,
|
|
332
|
+
outputExposure: number,
|
|
319
333
|
): void {
|
|
320
334
|
const f = this.staging;
|
|
321
335
|
const fields = BINDINGS.fields;
|
|
@@ -335,6 +349,8 @@ export class SkinScatterPass {
|
|
|
335
349
|
f.set(this.profiles, base + fields.uProfiles.offset / 4);
|
|
336
350
|
/* The colour is applied once, on the axis that adds into the frame. */
|
|
337
351
|
f[base + fields.uApplyAlbedo.offset / 4] = axis;
|
|
352
|
+
this.stagingInts[base + fields.uOutputTransform.offset / 4] = outputTransform;
|
|
353
|
+
f[base + fields.uOutputExposure.offset / 4] = outputExposure;
|
|
338
354
|
}
|
|
339
355
|
this.device.queue.writeBuffer(this.uniforms, 0, f);
|
|
340
356
|
}
|
|
@@ -116,10 +116,15 @@ export class CausticsRenderer {
|
|
|
116
116
|
windX = 0,
|
|
117
117
|
windZ = 0,
|
|
118
118
|
strength = 1,
|
|
119
|
+
/** The renderer's `passGrade`: the frame's, but none in a probe's face or a capture. */
|
|
120
|
+
outputTransform = 0,
|
|
121
|
+
outputExposure = 1,
|
|
119
122
|
): boolean {
|
|
120
123
|
if (this.vertexCount === 0) return false;
|
|
121
124
|
const u = this.uniforms;
|
|
122
125
|
gl.useProgram(this.program);
|
|
126
|
+
gl.uniform1i(u['uOutputTransform'] ?? null, outputTransform);
|
|
127
|
+
gl.uniform1f(u['uOutputExposure'] ?? null, outputExposure);
|
|
123
128
|
gl.uniformMatrix4fv(u['uViewProj'] ?? null, false, camera.viewProjection);
|
|
124
129
|
gl.uniform3fv(u['uCameraPos'] ?? null, camera.position);
|
|
125
130
|
gl.uniform1f(u['uTime'] ?? null, timeSeconds);
|
package/src/render/decalPass.ts
CHANGED
|
@@ -53,6 +53,8 @@ export class DecalPass {
|
|
|
53
53
|
eye: ArrayLike<number>,
|
|
54
54
|
width: number,
|
|
55
55
|
height: number,
|
|
56
|
+
/** The renderer's `passGrade`: how the frame under the marks was graded, 0 for not yet. */
|
|
57
|
+
outputTransform = 0,
|
|
56
58
|
): void {
|
|
57
59
|
if (queue.length === 0) return;
|
|
58
60
|
const { gl } = this;
|
|
@@ -62,6 +64,7 @@ export class DecalPass {
|
|
|
62
64
|
mat4.multiply(this.depthToWorld, this.depthToWorld, DEPTH_01_TO_CLIP);
|
|
63
65
|
|
|
64
66
|
gl.useProgram(this.program);
|
|
67
|
+
gl.uniform1i(u['uOutputTransform'] ?? null, outputTransform);
|
|
65
68
|
gl.bindVertexArray(this.vao);
|
|
66
69
|
gl.disable(gl.DEPTH_TEST);
|
|
67
70
|
gl.depthMask(false);
|
|
@@ -74,9 +74,14 @@ export class FlockRenderer {
|
|
|
74
74
|
tint: Vec3,
|
|
75
75
|
windX = 0,
|
|
76
76
|
windZ = 0,
|
|
77
|
+
outputTransform = 0,
|
|
78
|
+
outputExposure = 1,
|
|
77
79
|
): void {
|
|
78
80
|
const u = this.uniforms;
|
|
79
81
|
gl.useProgram(this.program);
|
|
82
|
+
/* The renderer's `passGrade`: the frame's, but none in a probe's face or a capture. */
|
|
83
|
+
gl.uniform1i(u['uOutputTransform'] ?? null, outputTransform);
|
|
84
|
+
gl.uniform1f(u['uOutputExposure'] ?? null, outputExposure);
|
|
80
85
|
gl.uniformMatrix4fv(u['uViewProj'] ?? null, false, camera.viewProjection);
|
|
81
86
|
/*
|
|
82
87
|
* No `uCameraPos` here, and the shader no longer declares one. It fed the banking that
|
|
@@ -268,7 +268,14 @@ export class GlobalMediumPass {
|
|
|
268
268
|
* `(ONE, SRC_ALPHA)` on colour is `dst * transmittance + inscatter`; alpha is left alone with
|
|
269
269
|
* `(ZERO, ONE)`, because the frame's own alpha is not the medium's to spend.
|
|
270
270
|
*/
|
|
271
|
-
compositeMedium(
|
|
271
|
+
compositeMedium(
|
|
272
|
+
depth: WebGLTexture,
|
|
273
|
+
frameWidth: number,
|
|
274
|
+
frameHeight: number,
|
|
275
|
+
/** The renderer's `passGrade`: how the frame under the medium was graded. */
|
|
276
|
+
outputTransform = 0,
|
|
277
|
+
outputExposure = 1,
|
|
278
|
+
): void {
|
|
272
279
|
if (this.target === null || this.unavailable) return;
|
|
273
280
|
const { gl } = this;
|
|
274
281
|
const u = this.upsampleUniforms;
|
|
@@ -292,6 +299,8 @@ export class GlobalMediumPass {
|
|
|
292
299
|
this.mediumTexel[1] = 1 / Math.max(1, this.height);
|
|
293
300
|
gl.uniform2fv(u['uMediumTexel'] ?? null, this.mediumTexel);
|
|
294
301
|
gl.uniform4fv(u['uDepthToViewZ'] ?? null, this.depthToViewZ);
|
|
302
|
+
gl.uniform1i(u['uOutputTransform'] ?? null, outputTransform);
|
|
303
|
+
gl.uniform1f(u['uOutputExposure'] ?? null, outputExposure);
|
|
295
304
|
|
|
296
305
|
gl.drawArrays(gl.TRIANGLES, 0, 3);
|
|
297
306
|
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The forward grade on the CPU: what `applyOutputTransform` in `shaders/outputTransform.ts` does to
|
|
3
|
+
* a colour, for a colour no shader draws — a clear colour, written to a target before any pass runs.
|
|
4
|
+
*
|
|
5
|
+
* **Why a clear colour is graded at all.** Every pass grades what it draws when nothing after it
|
|
6
|
+
* will, so a colour handed to the renderer means one thing wherever it lands: linear light. A clear
|
|
7
|
+
* left as it was given is the one colour in the frame read as a display value, so a scene whose fog
|
|
8
|
+
* fades to its background colour fades to a different colour than the background it meets; with
|
|
9
|
+
* `hdrScene` the resolve grades the clear with everything else, and a frame without it has to agree.
|
|
10
|
+
*
|
|
11
|
+
* **A second copy of the curve, and what holds the two together** is the hand-derived values in
|
|
12
|
+
* `gradeColor.test.ts`, the same ones the shader's own constants give: the sRGB encode's two
|
|
13
|
+
* segments, the ACES fit through its two matrices, and the highlight shoulder. Code 4, the filmic
|
|
14
|
+
* curve, is the composite's alone, so a forward code never carries it (`forwardTransformCode`).
|
|
15
|
+
*/
|
|
16
|
+
import type { Vec3 } from '../math/color.ts';
|
|
17
|
+
|
|
18
|
+
/* Columns as the shader's `mat3` reads them, written here as rows of the product. */
|
|
19
|
+
const IN_R = [0.59719, 0.35458, 0.04823];
|
|
20
|
+
const IN_G = [0.076, 0.90834, 0.01566];
|
|
21
|
+
const IN_B = [0.0284, 0.13383, 0.83777];
|
|
22
|
+
const OUT_R = [1.60475, -0.53108, -0.07367];
|
|
23
|
+
const OUT_G = [-0.10208, 1.10813, -0.00605];
|
|
24
|
+
const OUT_B = [-0.00327, -0.07276, 1.07602];
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* `color` graded by forward code `code` at `exposure` into `out`, which may be `color`: 0 leaves it
|
|
28
|
+
* as it is, 1 encodes it as sRGB, 2 runs ACES first and 3 the highlight shoulder. Allocates nothing.
|
|
29
|
+
*/
|
|
30
|
+
export function gradeColorInto(
|
|
31
|
+
code: number,
|
|
32
|
+
exposure: number,
|
|
33
|
+
color: Readonly<Vec3>,
|
|
34
|
+
out: Vec3,
|
|
35
|
+
): Vec3 {
|
|
36
|
+
let r = color[0];
|
|
37
|
+
let g = color[1];
|
|
38
|
+
let b = color[2];
|
|
39
|
+
if (code === 0) {
|
|
40
|
+
out[0] = r;
|
|
41
|
+
out[1] = g;
|
|
42
|
+
out[2] = b;
|
|
43
|
+
return out;
|
|
44
|
+
}
|
|
45
|
+
if (code === 2) {
|
|
46
|
+
const x = r * exposure;
|
|
47
|
+
const y = g * exposure;
|
|
48
|
+
const z = b * exposure;
|
|
49
|
+
const ir = fit(dot(IN_R, x, y, z));
|
|
50
|
+
const ig = fit(dot(IN_G, x, y, z));
|
|
51
|
+
const ib = fit(dot(IN_B, x, y, z));
|
|
52
|
+
r = clamp01(dot(OUT_R, ir, ig, ib));
|
|
53
|
+
g = clamp01(dot(OUT_G, ir, ig, ib));
|
|
54
|
+
b = clamp01(dot(OUT_B, ir, ig, ib));
|
|
55
|
+
} else if (code === 3) {
|
|
56
|
+
r *= exposure;
|
|
57
|
+
g *= exposure;
|
|
58
|
+
b *= exposure;
|
|
59
|
+
const m = Math.max(r, g, b);
|
|
60
|
+
if (m > 0.8) {
|
|
61
|
+
const e = m - 0.8;
|
|
62
|
+
const k = (0.8 + (0.2 * e) / (e + 0.2)) / m;
|
|
63
|
+
r *= k;
|
|
64
|
+
g *= k;
|
|
65
|
+
b *= k;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
out[0] = encode(r);
|
|
69
|
+
out[1] = encode(g);
|
|
70
|
+
out[2] = encode(b);
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function dot(row: readonly number[], x: number, y: number, z: number): number {
|
|
75
|
+
return (row[0] as number) * x + (row[1] as number) * y + (row[2] as number) * z;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** The RRT and ODT fit, `rrtAndOdtFit` in the shader. */
|
|
79
|
+
function fit(v: number): number {
|
|
80
|
+
return (v * (v + 0.0245786) - 0.000090537) / (v * (0.983729 * v + 0.432951) + 0.238081);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Linear to sRGB, the piecewise form the shader uses. */
|
|
84
|
+
function encode(c: number): number {
|
|
85
|
+
return c <= 0.0031308 ? c * 12.92 : 1.055 * Math.max(c, 0) ** (1 / 2.4) - 0.055;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function clamp01(v: number): number {
|
|
89
|
+
return v < 0 ? 0 : v > 1 ? 1 : v;
|
|
90
|
+
}
|
|
@@ -68,6 +68,38 @@ export function computeLightMatrix(
|
|
|
68
68
|
return radius * 6;
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* A directional light's matrix covering a box of known size whole: a model, a level, a room. The
|
|
73
|
+
* sphere around the box, at its centre, through `computeLightMatrix`, widened by the half texel the
|
|
74
|
+
* centre's snap may move it so a corner on the sphere stays inside. Returns the depth span, as that
|
|
75
|
+
* does.
|
|
76
|
+
*
|
|
77
|
+
* **For a scene whose extent is known rather than one that follows a viewer.** A radius chosen by
|
|
78
|
+
* eye around a guessed centre leaves part of the model outside the map, where it casts nothing; the
|
|
79
|
+
* bounds are the answer, and a loader has them. **What it gives up** is resolution where the box is
|
|
80
|
+
* large: the map's texels are spread over all of it, so a building's shadows are coarser than a
|
|
81
|
+
* focus on the viewer would draw them, which is what `computeLightMatrix` around the viewer is for.
|
|
82
|
+
*/
|
|
83
|
+
export function computeLightMatrixForBounds(
|
|
84
|
+
lightDir: Vec3,
|
|
85
|
+
min: Readonly<Vec3>,
|
|
86
|
+
max: Readonly<Vec3>,
|
|
87
|
+
shadowMapSize: number,
|
|
88
|
+
out: mat4,
|
|
89
|
+
): number {
|
|
90
|
+
const half = Math.hypot(max[0] - min[0], max[1] - min[1], max[2] - min[2]) / 2;
|
|
91
|
+
const radius = Math.max(half, 1e-3) * (1 + 2 / Math.max(shadowMapSize, 1));
|
|
92
|
+
return computeLightMatrix(
|
|
93
|
+
lightDir,
|
|
94
|
+
(min[0] + max[0]) / 2,
|
|
95
|
+
(min[1] + max[1]) / 2,
|
|
96
|
+
(min[2] + max[2]) / 2,
|
|
97
|
+
radius,
|
|
98
|
+
shadowMapSize,
|
|
99
|
+
out,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
|
|
71
103
|
/**
|
|
72
104
|
* The depth an orthographic light matrix spans, in metres: what `computeLightMatrix` returns, read
|
|
73
105
|
* back off any such matrix. Its depth row is the light's axis scaled by two over the span, so the
|