@driftengine/core 4.11.0 → 4.11.1
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 +2 -2
- package/dist/index.js +1 -1
- package/dist/render/ambientOcclusionPass.d.ts +3 -2
- package/dist/render/ambientOcclusionPass.js +4 -2
- package/dist/render/backend/api.d.ts +21 -1
- package/dist/render/backend/webgl2/renderer.d.ts +45 -4
- package/dist/render/backend/webgl2/renderer.js +112 -19
- package/dist/render/backend/webgpu/renderer.d.ts +52 -1
- package/dist/render/backend/webgpu/renderer.js +237 -89
- package/dist/render/instances.d.ts +13 -1
- package/dist/render/instances.js +10 -0
- package/dist/render/occlusionFade.d.ts +21 -0
- package/dist/render/occlusionFade.js +27 -0
- package/dist/render/sceneTarget.d.ts +2 -0
- package/dist/render/sceneTarget.js +1 -1
- package/dist/render/shaders/ambientOcclusion.js +12 -1
- package/dist/render/shaders/flat/index.js +9 -2
- package/dist/render/shaders/flat/layered.d.ts +15 -7
- package/dist/render/shaders/flat/layered.js +114 -31
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +10 -3
- package/dist/render/shaders/flat/normalMap.d.ts +1 -1
- package/dist/render/shaders/flat/normalMap.js +11 -2
- package/dist/render/shaders/generated/ambientOcclusion.wgsl.d.ts +6 -1
- package/dist/render/shaders/generated/ambientOcclusion.wgsl.js +6 -1
- package/dist/render/shaders/generated/flat.wgsl.d.ts +48 -48
- package/dist/render/shaders/generated/flat.wgsl.js +71 -71
- package/dist/render/surfaceLayers.d.ts +83 -15
- package/dist/render/surfaceLayers.js +69 -13
- package/dist/render/surfaceTexture.d.ts +13 -4
- package/dist/render/transmission.d.ts +21 -0
- package/dist/render/transmission.js +21 -0
- package/dist/ui/splash.d.ts +42 -2
- package/dist/ui/splash.js +78 -13
- package/package.json +6 -6
- package/src/index.ts +2 -0
- package/src/render/ambientOcclusionPass.ts +4 -1
- package/src/render/backend/api.ts +31 -0
- package/src/render/backend/webgl2/renderer.ts +130 -20
- package/src/render/backend/webgpu/renderer.ts +371 -139
- package/src/render/instances.ts +18 -1
- package/src/render/occlusionFade.ts +29 -0
- package/src/render/sceneTarget.ts +3 -0
- package/src/render/shaders/ambientOcclusion.ts +12 -1
- package/src/render/shaders/flat/index.ts +9 -2
- package/src/render/shaders/flat/layered.ts +114 -31
- package/src/render/shaders/flat/main.ts +10 -3
- package/src/render/shaders/flat/normalMap.ts +11 -2
- package/src/render/shaders/generated/ambientOcclusion.wgsl.ts +6 -1
- package/src/render/shaders/generated/flat.wgsl.ts +71 -71
- package/src/render/surfaceLayers.ts +138 -19
- package/src/render/surfaceTexture.ts +13 -4
- package/src/render/transmission.ts +27 -0
- package/src/ui/splash.ts +83 -14
|
@@ -63,8 +63,10 @@ import { GpuClothBinding, GpuClothParticles, clothStandIn } from './clothTexture
|
|
|
63
63
|
import { MODEL_PARAM_FLOATS, eyeAxisInWorld, packModel, skinProfileDistance, } from '../../surfaceModel.js';
|
|
64
64
|
import { OVERLAY_FLOATS, overlayLays, packSurfaceOverlay, } from '../../surfaceOverlay.js';
|
|
65
65
|
import { PROJECTION_FLOATS, packSurfaceProjection } from '../../surfaceProjection.js';
|
|
66
|
+
import { resolveTransmission } from '../../transmission.js';
|
|
67
|
+
import { resolveOcclusionFade } from '../../occlusionFade.js';
|
|
66
68
|
import { orthographicDepthSpan } from '../../lightMatrix.js';
|
|
67
|
-
import { LAYER_FLOATS, packSurfaceLayers } from '../../surfaceLayers.js';
|
|
69
|
+
import { LAYER_FLOATS, layerMaskMap, layeredProjection, layersFitBeside, packSurfaceLayers, } from '../../surfaceLayers.js';
|
|
68
70
|
import { frameReflectionsRefused, resolveFrameReflections, } from '../../frameReflections.js';
|
|
69
71
|
import { REFLECTION_SURFACE_FORMAT, ReflectionSurfacePass } from './reflectionSurfacePass.js';
|
|
70
72
|
import { validateClothBinding } from '../../clothBindingData.js';
|
|
@@ -228,6 +230,11 @@ const MAX_OVERLAYS = 64;
|
|
|
228
230
|
* and passes nobody has written yet; the cost is sixteen kilobytes.
|
|
229
231
|
*/
|
|
230
232
|
const MAX_SKY_DRAWS = 64;
|
|
233
|
+
/** The draw a preparation asks pipelines for: every option at its default. See `prepareMesh`. */
|
|
234
|
+
const PREPARED_DRAW = {};
|
|
235
|
+
/** A preparation's morph states for a mesh with no targets, and for one with some. */
|
|
236
|
+
const NOT_MORPHED = [false];
|
|
237
|
+
const MORPHED_OR_NOT = [false, true];
|
|
231
238
|
/**
|
|
232
239
|
* Whether the environment probe's permutation fits this device's per-stage binding limits.
|
|
233
240
|
*
|
|
@@ -1646,6 +1653,8 @@ export class WebGPURenderer {
|
|
|
1646
1653
|
temporalHistoryUsable = false;
|
|
1647
1654
|
aoStaging = new ArrayBuffer(AO_FRAG_SIZE);
|
|
1648
1655
|
aoFloats = new Float32Array(this.aoStaging);
|
|
1656
|
+
/** Where occlusion fades out with distance, `uFade`: none until a caller sets one. */
|
|
1657
|
+
aoFade = new Float32Array([-1, 0]);
|
|
1649
1658
|
aoBindGroup = null;
|
|
1650
1659
|
aoBlurLayout;
|
|
1651
1660
|
/**
|
|
@@ -2247,7 +2256,13 @@ export class WebGPURenderer {
|
|
|
2247
2256
|
projection = new Float32Array(PROJECTION_FLOATS);
|
|
2248
2257
|
/** `uLayers`, the material's layers packed by `packSurfaceLayers`. */
|
|
2249
2258
|
layerFloats = new Float32Array(LAYER_FLOATS);
|
|
2259
|
+
/**
|
|
2260
|
+
* `uGlassTint` as the material leaves it, its light from behind and that light's colour, which a
|
|
2261
|
+
* pane overwrites for its own draw and `releaseSeeThrough` puts back. See `transmission.ts`.
|
|
2262
|
+
*/
|
|
2263
|
+
transmission = new Float32Array([-1, -1, -1, 0]);
|
|
2250
2264
|
warnedLayersModelMap = false;
|
|
2265
|
+
warnedLayersTriplanar = false;
|
|
2251
2266
|
/** Its numbers, `uModelParams`, packed by `packModel`. */
|
|
2252
2267
|
modelParams = new Float32Array(MODEL_PARAM_FLOATS);
|
|
2253
2268
|
/**
|
|
@@ -2649,9 +2664,9 @@ export class WebGPURenderer {
|
|
|
2649
2664
|
const orm = (material?.orm ?? null);
|
|
2650
2665
|
const emissiveMap = (material?.emissive ?? null);
|
|
2651
2666
|
const ownModelMap = (material?.modelMap ?? null);
|
|
2652
|
-
/* A layered material's mask goes where a model's
|
|
2667
|
+
/* A layered material's mask, where it is a map, goes where a model's would: surfaceLayers.ts. */
|
|
2653
2668
|
const layers = this.layersOf(material);
|
|
2654
|
-
const modelMap = ownModelMap ?? (layers
|
|
2669
|
+
const modelMap = ownModelMap ?? layerMaskMap(layers);
|
|
2655
2670
|
this.materialModel = material?.model ?? null;
|
|
2656
2671
|
const reflectivity = material?.reflectivity;
|
|
2657
2672
|
const environmentGain = material?.environmentGain;
|
|
@@ -2715,11 +2730,14 @@ export class WebGPURenderer {
|
|
|
2715
2730
|
if (material?.physicalSpecular === true)
|
|
2716
2731
|
this.enableLit('PHYSICAL_SPECULAR');
|
|
2717
2732
|
/* Maps placed by the world: the first material that asks is when lit pipelines carry it. */
|
|
2718
|
-
|
|
2733
|
+
const projection = layeredProjection(material?.projection, layers);
|
|
2734
|
+
if (projection !== (material?.projection ?? null))
|
|
2735
|
+
this.warnLayersTriplanar();
|
|
2736
|
+
packSurfaceProjection(projection, this.projection);
|
|
2719
2737
|
f.set(this.projection, this.materialField('uWorldUv'));
|
|
2720
|
-
/* How much light from behind it lets through,
|
|
2721
|
-
|
|
2722
|
-
|
|
2738
|
+
/* How much light from behind it lets through, and its colour: transmission.ts. */
|
|
2739
|
+
resolveTransmission(material, this.transmission);
|
|
2740
|
+
f.set(this.transmission, this.materialField('uGlassTint'));
|
|
2723
2741
|
if (this.projection[0] > 0)
|
|
2724
2742
|
this.enableLit('WORLD_UVS');
|
|
2725
2743
|
/* And its layers, the first time a material blends any: layered.ts. */
|
|
@@ -5078,15 +5096,24 @@ export class WebGPURenderer {
|
|
|
5078
5096
|
const layers = material?.layers ?? null;
|
|
5079
5097
|
if (layers === null || layers.repeats.length === 0)
|
|
5080
5098
|
return null;
|
|
5081
|
-
if ((material?.modelMap
|
|
5099
|
+
if (layersFitBeside(layers, material?.modelMap))
|
|
5082
5100
|
return layers;
|
|
5083
5101
|
if (!this.warnedLayersModelMap) {
|
|
5084
5102
|
this.warnedLayersModelMap = true;
|
|
5085
|
-
console.warn("WebGPU: a material's layers are drawn as one: its mask is read where a model's map
|
|
5086
|
-
|
|
5103
|
+
console.warn("WebGPU: a material's layers are drawn as one: its mask is a map, read where a model's map " +
|
|
5104
|
+
"goes, and this material carries a `modelMap` too. A mask in the ORM array (`mask: 'orm'`) " +
|
|
5105
|
+
"or the vertex colour (`mask: 'vertex'`) leaves the slot free.");
|
|
5087
5106
|
}
|
|
5088
5107
|
return null;
|
|
5089
5108
|
}
|
|
5109
|
+
/** Says once that a layered material's triplanar projection is laid on the horizontal plane. */
|
|
5110
|
+
warnLayersTriplanar() {
|
|
5111
|
+
if (this.warnedLayersTriplanar)
|
|
5112
|
+
return;
|
|
5113
|
+
this.warnedLayersTriplanar = true;
|
|
5114
|
+
console.warn("WebGPU: a layered material's triplanar projection is laid on the horizontal plane: every " +
|
|
5115
|
+
"layer's maps on three planes would be three times the reads. See `SurfaceMaterial.layers`.");
|
|
5116
|
+
}
|
|
5090
5117
|
/**
|
|
5091
5118
|
* What the sun's moving layer is read through: the matrix its shadow pass was last drawn with,
|
|
5092
5119
|
* where the lit pipelines carry one (`MOVING_SUN`); the environment's otherwise, which every caller
|
|
@@ -7155,7 +7182,8 @@ export class WebGPURenderer {
|
|
|
7155
7182
|
/* And no projection: maps follow the mesh's coordinates until a material asks otherwise. */
|
|
7156
7183
|
f.fill(0, at('uWorldUv'), at('uWorldUv') + 4);
|
|
7157
7184
|
/* Nor light through from behind. See `SurfaceMaterial.diffuseTransmission`. */
|
|
7158
|
-
|
|
7185
|
+
resolveTransmission(null, this.transmission);
|
|
7186
|
+
f.set(this.transmission, at('uGlassTint'));
|
|
7159
7187
|
/* Nor layers: one layer until a material asks for more. */
|
|
7160
7188
|
f.fill(0, at('uLayers'), at('uLayers') + LAYER_FLOATS);
|
|
7161
7189
|
f[at('uCutout')] = 0;
|
|
@@ -7880,6 +7908,153 @@ export class WebGPURenderer {
|
|
|
7880
7908
|
`TranslucentMeshOptions.tint` for why, and for the fade it was added for. */
|
|
7881
7909
|
this.submitMesh(mesh, model, options.tint ?? null, Math.min(opacity, 1), true, options);
|
|
7882
7910
|
}
|
|
7911
|
+
/**
|
|
7912
|
+
* Compile off the main thread every pipeline a draw of `mesh` in `material` takes. **The other
|
|
7913
|
+
* backend's `prepareMesh` carries the contract**; here a draw's key is `meshShape`'s, so a prepared
|
|
7914
|
+
* key is exactly the one the draw asks for, and the compiles are started as a skipping draw starts
|
|
7915
|
+
* them (`PipelineCache.lit`) whatever `pipelineCompile` says, into the frame's own caches.
|
|
7916
|
+
*/
|
|
7917
|
+
prepareMesh(mesh, material, options = {}) {
|
|
7918
|
+
if (this.surface.lost)
|
|
7919
|
+
return;
|
|
7920
|
+
this.preparing(material, (into, blend) => {
|
|
7921
|
+
const skinned = mesh.isSkinned;
|
|
7922
|
+
const eight = skinned && mesh.isSkinnedEight;
|
|
7923
|
+
for (const morphed of mesh.morph === null ? NOT_MORPHED : MORPHED_OR_NOT) {
|
|
7924
|
+
this.meshShape(mesh, blend, PREPARED_DRAW, skinned, morphed, eight, false);
|
|
7925
|
+
this.meshPipeline(blend, false, skinned, morphed, eight, false, into);
|
|
7926
|
+
}
|
|
7927
|
+
}, options);
|
|
7928
|
+
}
|
|
7929
|
+
/** `prepareMesh` for an instanced batch. See the other backend's `prepareInstanced`. */
|
|
7930
|
+
prepareInstanced(batch, material, options = {}) {
|
|
7931
|
+
if (this.surface.lost)
|
|
7932
|
+
return;
|
|
7933
|
+
this.preparing(material, (into, blend) => {
|
|
7934
|
+
const coverage = !blend && this.cutoutResolveStaged === 'coverage';
|
|
7935
|
+
this.instancedPipeline(batch.mesh, batch.animation !== null, blend, false, PREPARED_DRAW, true, 0, coverage, into);
|
|
7936
|
+
}, options);
|
|
7937
|
+
}
|
|
7938
|
+
/**
|
|
7939
|
+
* `material` set, every cache a draw in it may build into passed to `start` with whether that draw
|
|
7940
|
+
* blends, compiles started rather than built (`PipelineCache.lit`), and then no material set and
|
|
7941
|
+
* each cache back to what `pipelineCompile` says.
|
|
7942
|
+
*/
|
|
7943
|
+
preparing(material, start, options) {
|
|
7944
|
+
this.setMaterial(material);
|
|
7945
|
+
this.skipCompiling(true);
|
|
7946
|
+
try {
|
|
7947
|
+
start(this.pipelines, false);
|
|
7948
|
+
if (options.translucent === true) {
|
|
7949
|
+
start(this.pipelines, true);
|
|
7950
|
+
/* A blended draw in a reconstructing frame lands in the late pass, whose cache is its own. */
|
|
7951
|
+
if (this.latePipelines !== this.pipelines)
|
|
7952
|
+
start(this.latePipelines, true);
|
|
7953
|
+
}
|
|
7954
|
+
}
|
|
7955
|
+
finally {
|
|
7956
|
+
this.skipCompiling(this.quality.pipelineCompile === 'skip');
|
|
7957
|
+
this.setMaterial(null);
|
|
7958
|
+
}
|
|
7959
|
+
}
|
|
7960
|
+
/** What `meshShape` worked out for the mesh draw at hand. Reused, so a draw allocates no record. */
|
|
7961
|
+
drawShape = {
|
|
7962
|
+
base: '',
|
|
7963
|
+
key: '',
|
|
7964
|
+
depthWrite: true,
|
|
7965
|
+
layer: 0,
|
|
7966
|
+
adds: false,
|
|
7967
|
+
coverage: false,
|
|
7968
|
+
splits: false,
|
|
7969
|
+
modelKind: null,
|
|
7970
|
+
};
|
|
7971
|
+
/**
|
|
7972
|
+
* The key a mesh draw's pipeline is cached under, and the pipeline state that key encodes, under
|
|
7973
|
+
* the current material and these options, into `drawShape`. **The one statement of it**:
|
|
7974
|
+
* `submitMesh` draws by it and `prepareMesh` compiles by it, so the two cannot disagree about a key.
|
|
7975
|
+
*/
|
|
7976
|
+
meshShape(mesh, blend, options, skinned, morphed, eight, clothed) {
|
|
7977
|
+
const modelKind = this.materialModel?.kind ?? null;
|
|
7978
|
+
const base = mesh.key ?? 'flat:s0:u0';
|
|
7979
|
+
const keyed = `${base}${morphed ? '|morph' : ''}${skinned ? (eight ? '|skin8' : '|skin') : ''}` +
|
|
7980
|
+
`${clothed ? '|cloth' : ''}`;
|
|
7981
|
+
/*
|
|
7982
|
+
* Depth writing and the overlay layer are pipeline state on this backend, so they belong in
|
|
7983
|
+
* the key: two draws of one mesh differing only in these would otherwise share a cached
|
|
7984
|
+
* pipeline and the second would silently take the first one's depth behaviour.
|
|
7985
|
+
*
|
|
7986
|
+
* **Both suffixes are empty for the defaults**, which is what keeps the keys `createMesh`
|
|
7987
|
+
* warms — `fullKey` and `${'`'}${'$'}{fullKey}|blend${'`'}` — the ones an ordinary draw still asks for. A
|
|
7988
|
+
* caller that opts in pays one compile on its first draw, the same bargain the overlay
|
|
7989
|
+
* pipelines above already make.
|
|
7990
|
+
*/
|
|
7991
|
+
const depthWrite = options.depthWrite ?? true;
|
|
7992
|
+
const layer = Math.min(Math.max(Math.round(options.depthLayer ?? 0), 0), MAX_DEPTH_LAYER);
|
|
7993
|
+
/* Added rather than blended over: its own blend state, so its own pipeline. */
|
|
7994
|
+
const adds = blend && options.additive === true;
|
|
7995
|
+
const coverage = !blend && this.cutoutResolveStaged === 'coverage';
|
|
7996
|
+
/* Skin under the screen-space blur draws its two halves; see `keepSkinDiffuse`. */
|
|
7997
|
+
const splits = modelKind === 'skin' && !blend && this.skinSplits();
|
|
7998
|
+
const key = `${keyed}${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
7999
|
+
`${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
|
|
8000
|
+
/* The two order-independent buffers are two formats and two blend states, so a draw into
|
|
8001
|
+
one must never share a cached pipeline with the same mesh drawn into the other. */
|
|
8002
|
+
`${this.oitMode === 'none' ? '' : `|oit${this.oitMode}`}` +
|
|
8003
|
+
/* Culling is pipeline state here: a two-sided surface culls nothing. */
|
|
8004
|
+
`${this.materialDoubleSided ? '|2s' : ''}` +
|
|
8005
|
+
/* And so is alpha-to-coverage, for a dithered cutout in a multisampled frame. */
|
|
8006
|
+
`${coverage ? '|a2c' : ''}` +
|
|
8007
|
+
/* And the material's shading model, which is the fragment stage's own switch. */
|
|
8008
|
+
`${modelKind === null ? '' : `|m:${modelKind}`}` +
|
|
8009
|
+
/* And which half of a skin it draws, under the screen-space blur. */
|
|
8010
|
+
`${splits ? '|ss' : ''}`;
|
|
8011
|
+
const shape = this.drawShape;
|
|
8012
|
+
shape.base = base;
|
|
8013
|
+
shape.key = key;
|
|
8014
|
+
shape.depthWrite = depthWrite;
|
|
8015
|
+
shape.layer = layer;
|
|
8016
|
+
shape.adds = adds;
|
|
8017
|
+
shape.coverage = coverage;
|
|
8018
|
+
shape.splits = splits;
|
|
8019
|
+
shape.modelKind = modelKind;
|
|
8020
|
+
return shape;
|
|
8021
|
+
}
|
|
8022
|
+
/**
|
|
8023
|
+
* The pipeline `drawShape` names: the one built, one built here, or none where draws skip a
|
|
8024
|
+
* compile and this one has not landed (`PipelineCache.lit`) — and none for a split skin until all
|
|
8025
|
+
* of its halves have.
|
|
8026
|
+
*/
|
|
8027
|
+
meshPipeline(blend, lands, skinned, morphed, eight, clothed,
|
|
8028
|
+
/** Which cache to build into: the one the draw's target reads, unless a preparation says. */
|
|
8029
|
+
into = null) {
|
|
8030
|
+
const { base, key, depthWrite, layer, adds, coverage, splits, modelKind } = this.drawShape;
|
|
8031
|
+
/*
|
|
8032
|
+
* **Asynchronous by default, synchronous if it has to be, and never absent.**
|
|
8033
|
+
*
|
|
8034
|
+
* `createMesh` compiles off the main thread, so a draw can legitimately arrive before the
|
|
8035
|
+
* driver has finished — a consumer that awaits `ready()` never sees this, and one that
|
|
8036
|
+
* draws immediately gets the old behaviour rather than an exception. Correctness cannot be
|
|
8037
|
+
* allowed to depend on which of those happened, so the miss builds the pipeline here and
|
|
8038
|
+
* pays the compile it was trying to avoid. That is a slow frame; the alternative is a
|
|
8039
|
+
* thrown error in the middle of one.
|
|
8040
|
+
*/
|
|
8041
|
+
const pipelines = into ?? (blend ? this.blendedPipelines(lands) : this.targetPipelines());
|
|
8042
|
+
let pipeline = pipelines.peek(key) ?? null;
|
|
8043
|
+
if (pipeline === null) {
|
|
8044
|
+
const present = this.meshPresent.get(base);
|
|
8045
|
+
if (present === undefined) {
|
|
8046
|
+
throw new Error(`WebGPU: no pipeline for ${key}; createMesh builds it`);
|
|
8047
|
+
}
|
|
8048
|
+
pipeline = flatPipeline(pipelines, this.surface.device, this.flatLayoutFor(skinned, morphed), this.variant, key, present, adds ? 'additive' : blend, skinned, morphed, depthWrite, layer, false, this.oitMode, this.materialDoubleSided, coverage, eight, clothed, modelKind, splits ? 'scene' : 'whole');
|
|
8049
|
+
}
|
|
8050
|
+
/* A split skin draws its three halves or none of them. */
|
|
8051
|
+
if (pipeline !== null &&
|
|
8052
|
+
splits &&
|
|
8053
|
+
!this.skinHalvesReady(key, base, skinned, morphed, layer, eight, clothed)) {
|
|
8054
|
+
pipeline = null;
|
|
8055
|
+
}
|
|
8056
|
+
return pipeline;
|
|
8057
|
+
}
|
|
7883
8058
|
/**
|
|
7884
8059
|
* @param blend Which of the two entry points above this came through, and **not** something to
|
|
7885
8060
|
* infer from `opacity`.
|
|
@@ -7999,7 +8174,6 @@ export class WebGPURenderer {
|
|
|
7999
8174
|
const modelKind = this.materialModel?.kind ?? null;
|
|
8000
8175
|
if (modelKind === 'eye')
|
|
8001
8176
|
this.writeEyeAxis(model, this.skinPaletteSlot >= 0 && mesh.isSkinned);
|
|
8002
|
-
const base = mesh.key ?? 'flat:s0:u0';
|
|
8003
8177
|
/*
|
|
8004
8178
|
* A rigged mesh takes the skinned vertex variant, an unrigged one the plain variant, and the
|
|
8005
8179
|
* key carries the flag so the two never share a cached pipeline. A rigged mesh whose caller
|
|
@@ -8014,38 +8188,6 @@ export class WebGPURenderer {
|
|
|
8014
8188
|
/* A cloth set and a skinned draw: the pipeline built with CLOTH_BOUND on, warmed when the
|
|
8015
8189
|
binding was made. See `createClothBinding`. */
|
|
8016
8190
|
const clothed = skinned && this.cloth !== null;
|
|
8017
|
-
const keyed = `${base}${morphed ? '|morph' : ''}${skinned ? (eight ? '|skin8' : '|skin') : ''}` +
|
|
8018
|
-
`${clothed ? '|cloth' : ''}`;
|
|
8019
|
-
/*
|
|
8020
|
-
* Depth writing and the overlay layer are pipeline state on this backend, so they belong in
|
|
8021
|
-
* the key: two draws of one mesh differing only in these would otherwise share a cached
|
|
8022
|
-
* pipeline and the second would silently take the first one's depth behaviour.
|
|
8023
|
-
*
|
|
8024
|
-
* **Both suffixes are empty for the defaults**, which is what keeps the keys `createMesh`
|
|
8025
|
-
* warms — `fullKey` and `${'`'}${'$'}{fullKey}|blend${'`'}` — the ones an ordinary draw still asks for. A
|
|
8026
|
-
* caller that opts in pays one compile on its first draw, the same bargain the overlay
|
|
8027
|
-
* pipelines above already make.
|
|
8028
|
-
*/
|
|
8029
|
-
const depthWrite = options.depthWrite ?? true;
|
|
8030
|
-
const layer = Math.min(Math.max(Math.round(options.depthLayer ?? 0), 0), MAX_DEPTH_LAYER);
|
|
8031
|
-
/* Added rather than blended over: its own blend state, so its own pipeline. */
|
|
8032
|
-
const adds = blend && options.additive === true;
|
|
8033
|
-
const coverage = !blend && this.cutoutResolveStaged === 'coverage';
|
|
8034
|
-
/* Skin under the screen-space blur draws its two halves; see `keepSkinDiffuse`. */
|
|
8035
|
-
const splits = modelKind === 'skin' && !blend && this.skinSplits();
|
|
8036
|
-
const key = `${keyed}${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
8037
|
-
`${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
|
|
8038
|
-
/* The two order-independent buffers are two formats and two blend states, so a draw into
|
|
8039
|
-
one must never share a cached pipeline with the same mesh drawn into the other. */
|
|
8040
|
-
`${this.oitMode === 'none' ? '' : `|oit${this.oitMode}`}` +
|
|
8041
|
-
/* Culling is pipeline state here: a two-sided surface culls nothing. */
|
|
8042
|
-
`${this.materialDoubleSided ? '|2s' : ''}` +
|
|
8043
|
-
/* And so is alpha-to-coverage, for a dithered cutout in a multisampled frame. */
|
|
8044
|
-
`${coverage ? '|a2c' : ''}` +
|
|
8045
|
-
/* And the material's shading model, which is the fragment stage's own switch. */
|
|
8046
|
-
`${modelKind === null ? '' : `|m:${modelKind}`}` +
|
|
8047
|
-
/* And which half of a skin it draws, under the screen-space blur. */
|
|
8048
|
-
`${splits ? '|ss' : ''}`;
|
|
8049
8191
|
/*
|
|
8050
8192
|
* The morph uniforms, written only for a morphed draw and read from the *morphed* variant's
|
|
8051
8193
|
* field table. `FLAT_VERT_FIELDS` is the plain variant's and does not name them at all — every
|
|
@@ -8058,31 +8200,9 @@ export class WebGPURenderer {
|
|
|
8058
8200
|
this.perDraw.writeInt(slot, fields.uMorphTargetCount.offset, mesh.morph.targetCount);
|
|
8059
8201
|
this.perDraw.writeInt(slot, fields.uMorphTextureWidth.offset, mesh.morph.width);
|
|
8060
8202
|
}
|
|
8061
|
-
/*
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
* `createMesh` compiles off the main thread, so a draw can legitimately arrive before the
|
|
8065
|
-
* driver has finished — a consumer that awaits `ready()` never sees this, and one that
|
|
8066
|
-
* draws immediately gets the old behaviour rather than an exception. Correctness cannot be
|
|
8067
|
-
* allowed to depend on which of those happened, so the miss builds the pipeline here and
|
|
8068
|
-
* pays the compile it was trying to avoid. That is a slow frame; the alternative is a
|
|
8069
|
-
* thrown error in the middle of one.
|
|
8070
|
-
*/
|
|
8071
|
-
const pipelines = blend ? this.blendedPipelines(lands) : this.targetPipelines();
|
|
8072
|
-
let pipeline = pipelines.peek(key) ?? null;
|
|
8073
|
-
if (pipeline === null) {
|
|
8074
|
-
const present = this.meshPresent.get(base);
|
|
8075
|
-
if (present === undefined) {
|
|
8076
|
-
throw new Error(`WebGPU: no pipeline for ${key}; createMesh builds it`);
|
|
8077
|
-
}
|
|
8078
|
-
pipeline = flatPipeline(pipelines, this.surface.device, this.flatLayoutFor(skinned, morphed), this.variant, key, present, adds ? 'additive' : blend, skinned, morphed, depthWrite, layer, false, this.oitMode, this.materialDoubleSided, coverage, eight, clothed, modelKind, splits ? 'scene' : 'whole');
|
|
8079
|
-
}
|
|
8080
|
-
/* A split skin draws its three halves or none of them. */
|
|
8081
|
-
if (pipeline !== null &&
|
|
8082
|
-
splits &&
|
|
8083
|
-
!this.skinHalvesReady(key, base, skinned, morphed, layer, eight, clothed)) {
|
|
8084
|
-
pipeline = null;
|
|
8085
|
-
}
|
|
8203
|
+
/* Its pipeline, by the key a prepared draw compiled too: see `meshShape`. */
|
|
8204
|
+
const { base, key, layer, depthWrite, splits } = this.meshShape(mesh, blend, options, skinned, morphed, eight, clothed);
|
|
8205
|
+
let pipeline = this.meshPipeline(blend, lands, skinned, morphed, eight, clothed);
|
|
8086
8206
|
/*
|
|
8087
8207
|
* Guards the draw call only, not the restore below.
|
|
8088
8208
|
*
|
|
@@ -8388,6 +8508,8 @@ export class WebGPURenderer {
|
|
|
8388
8508
|
const seeAt = this.materialField('uSeeThrough');
|
|
8389
8509
|
this.perFrameFloats[seeAt] = 0;
|
|
8390
8510
|
this.perFrameFloats[seeAt + 2] = 0;
|
|
8511
|
+
/* The material's light through, over the pane's tint: transmission.ts. */
|
|
8512
|
+
this.perFrameFloats.set(this.transmission, this.materialField('uGlassTint'));
|
|
8391
8513
|
}
|
|
8392
8514
|
/**
|
|
8393
8515
|
* One draw, one material slot, one uniform slot, however many instances.
|
|
@@ -8401,6 +8523,41 @@ export class WebGPURenderer {
|
|
|
8401
8523
|
* **What it gives up** against `drawMesh` is skinning and morphing, refused where the variant
|
|
8402
8524
|
* is built, and a per-instance opacity: `opacity` here dims the whole batch.
|
|
8403
8525
|
*/
|
|
8526
|
+
/**
|
|
8527
|
+
* An instanced draw's pipeline under the current material and these options: the one built, one
|
|
8528
|
+
* built here, or none while it compiles (`PipelineCache.lit`), its key, base and model left in
|
|
8529
|
+
* `drawShape`. **The one statement of an instanced key**: `submitInstanced` draws by it and
|
|
8530
|
+
* `prepareInstanced` compiles by it.
|
|
8531
|
+
*/
|
|
8532
|
+
instancedPipeline(mesh, animated, blend, lands, options, depthWrite, layer, coverage,
|
|
8533
|
+
/** Which cache to build into: the one the draw's target reads, unless a preparation says. */
|
|
8534
|
+
into = null) {
|
|
8535
|
+
/* A batch shades by its material's model too; an eye's axis is a placement's, not a batch's. */
|
|
8536
|
+
const modelKind = this.materialModel?.kind ?? null;
|
|
8537
|
+
const base = mesh.key ?? 'flat:s0:u0';
|
|
8538
|
+
/* `|inst` in the key, for the reason every other suffix is there: an instanced pipeline binds
|
|
8539
|
+
a third vertex buffer and a different vertex module, and sharing a cache entry with the
|
|
8540
|
+
plain one would hand a draw the wrong layout. */
|
|
8541
|
+
const adds = blend && options.additive === true;
|
|
8542
|
+
const key = `${base}|inst${!animated ? '' : '|anim'}${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
8543
|
+
`${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
|
|
8544
|
+
`${this.materialDoubleSided ? '|2s' : ''}${coverage ? '|a2c' : ''}` +
|
|
8545
|
+
`${modelKind === null ? '' : `|m:${modelKind}`}`;
|
|
8546
|
+
const shape = this.drawShape;
|
|
8547
|
+
shape.base = base;
|
|
8548
|
+
shape.key = key;
|
|
8549
|
+
shape.modelKind = modelKind;
|
|
8550
|
+
const pipelines = into ?? (blend ? this.blendedPipelines(lands) : this.targetPipelines());
|
|
8551
|
+
let pipeline = pipelines.peek(key) ?? null;
|
|
8552
|
+
if (pipeline === null) {
|
|
8553
|
+
const present = this.meshPresent.get(base);
|
|
8554
|
+
if (present === undefined) {
|
|
8555
|
+
throw new Error(`WebGPU: no mesh attributes recorded for ${base}; createMesh records them`);
|
|
8556
|
+
}
|
|
8557
|
+
pipeline = flatPipeline(pipelines, this.surface.device, !animated ? this.flatLayoutFor(false, false) : this.animatedBindGroupLayout, this.variant, key, present, adds ? 'additive' : blend, false, false, depthWrite, layer, true, 'none', this.materialDoubleSided, coverage, false, false, modelKind, 'whole', false, animated);
|
|
8558
|
+
}
|
|
8559
|
+
return pipeline;
|
|
8560
|
+
}
|
|
8404
8561
|
/**
|
|
8405
8562
|
* The next of a batch's cull slots this frame, with its cull queued against the current view; null
|
|
8406
8563
|
* when the batch has drawn from `MAX_CULL_SLOTS` views already or the frame's job ring is full,
|
|
@@ -8504,26 +8661,9 @@ export class WebGPURenderer {
|
|
|
8504
8661
|
const own = ownsMaterial({ opacity, lit, fog, toneMapped, refracting }, this.surfaceFog) || ditheredBlend;
|
|
8505
8662
|
if (own)
|
|
8506
8663
|
this.materials.dirty();
|
|
8507
|
-
/*
|
|
8508
|
-
const
|
|
8509
|
-
const base =
|
|
8510
|
-
/* `|inst` in the key, for the reason every other suffix is there: an instanced pipeline binds
|
|
8511
|
-
a third vertex buffer and a different vertex module, and sharing a cache entry with the
|
|
8512
|
-
plain one would hand a draw the wrong layout. */
|
|
8513
|
-
const adds = blend && options.additive === true;
|
|
8514
|
-
const key = `${base}|inst${animation === null ? '' : '|anim'}${blend ? (adds ? '|add' : '|blend') : ''}` +
|
|
8515
|
-
`${depthWrite ? '' : '|nodepth'}${layer > 0 ? `|dl${layer}` : ''}` +
|
|
8516
|
-
`${this.materialDoubleSided ? '|2s' : ''}${coverage ? '|a2c' : ''}` +
|
|
8517
|
-
`${modelKind === null ? '' : `|m:${modelKind}`}`;
|
|
8518
|
-
const pipelines = blend ? this.blendedPipelines(lands) : this.targetPipelines();
|
|
8519
|
-
let pipeline = pipelines.peek(key) ?? null;
|
|
8520
|
-
if (pipeline === null) {
|
|
8521
|
-
const present = this.meshPresent.get(base);
|
|
8522
|
-
if (present === undefined) {
|
|
8523
|
-
throw new Error(`WebGPU: no mesh attributes recorded for ${base}; createMesh records them`);
|
|
8524
|
-
}
|
|
8525
|
-
pipeline = flatPipeline(pipelines, this.surface.device, animation === null ? this.flatLayoutFor(false, false) : this.animatedBindGroupLayout, this.variant, key, present, adds ? 'additive' : blend, false, false, depthWrite, layer, true, 'none', this.materialDoubleSided, coverage, false, false, modelKind, 'whole', false, animation !== null);
|
|
8526
|
-
}
|
|
8664
|
+
/* Its pipeline, by the key a prepared batch compiled too: see `instancedPipeline`. */
|
|
8665
|
+
const pipeline = this.instancedPipeline(mesh, animation !== null, blend, lands, options, depthWrite, layer, coverage);
|
|
8666
|
+
const { base, key, modelKind } = this.drawShape;
|
|
8527
8667
|
/* A batch whose pipeline is still compiling is left out, as a mesh is. */
|
|
8528
8668
|
const material = pipeline === null ? null : this.materialSlotForDraw();
|
|
8529
8669
|
if (pipeline !== null && material !== null) {
|
|
@@ -11581,6 +11721,7 @@ export class WebGPURenderer {
|
|
|
11581
11721
|
const f = this.aoFloats;
|
|
11582
11722
|
const at = (name) => this.postField(AO_FRAG_FIELDS, name);
|
|
11583
11723
|
f[at('uRadius')] = this.quality.ambientOcclusionRadius;
|
|
11724
|
+
f.set(this.aoFade, at('uFade'));
|
|
11584
11725
|
f.set(this.aoProjScale, at('uProjScale'));
|
|
11585
11726
|
f.set(this.aoInvProjection, at('uInvProjection'));
|
|
11586
11727
|
device.queue.writeBuffer(this.aoUniforms, 0, this.aoStaging);
|
|
@@ -14172,6 +14313,13 @@ export class WebGPURenderer {
|
|
|
14172
14313
|
this.exposurePass = new ExposurePass(this.surface.device, this.pipelines, this.postSampler);
|
|
14173
14314
|
this.rebuildRushBindGroup();
|
|
14174
14315
|
}
|
|
14316
|
+
/**
|
|
14317
|
+
* Where ambient occlusion fades out with distance. **The other backend's
|
|
14318
|
+
* `setAmbientOcclusionFade` carries the reasoning**; this is the same setter over the same state.
|
|
14319
|
+
*/
|
|
14320
|
+
setAmbientOcclusionFade(distance, radius) {
|
|
14321
|
+
resolveOcclusionFade(distance, radius, this.aoFade);
|
|
14322
|
+
}
|
|
14175
14323
|
/**
|
|
14176
14324
|
* Where this frame's lens is focused, how deep the sharp zone is, and how much of the ceiling
|
|
14177
14325
|
* to take. **The other backend's `setDepthOfField` carries the reasoning**, whole: the
|
|
@@ -46,9 +46,16 @@ export interface MeshInstances {
|
|
|
46
46
|
* read by a draw whose material is a `lightmapModel` — instances of one mesh in one material sit
|
|
47
47
|
* in different regions of one page, each applied before the material's own region. **They ride
|
|
48
48
|
* the tint and the opacity**, which a lightmapped instance gives up (see `lightmap.ts`): where
|
|
49
|
-
* these are given,
|
|
49
|
+
* these are given, the tints are not read. **A lightmapped batch needs them**, since a bake is
|
|
50
50
|
* different light at each instance; one drawn without them reads its tints as its regions, and
|
|
51
51
|
* either mismatch is said once on the console.
|
|
52
|
+
*
|
|
53
|
+
* **The opacity survives, in sixty-fourths**, so a lightmapped instance fades by
|
|
54
|
+
* `setDitherOpacity` as any other does: its transparency travels as whole steps of 2 added to the
|
|
55
|
+
* region's U offset, which is a place in a page and so under 1, and the vertex stage takes them
|
|
56
|
+
* back off (`LIGHTMAP_OPACITY_STEPS`). Sixty-four is the screen door's own resolution, an 8x8
|
|
57
|
+
* pattern. **What it gives up**: a fading instance's offset keeps 16 bits of fraction rather than
|
|
58
|
+
* 23, a sixteenth of a texel on a 4,096 page; an opaque one is written to the bit as before.
|
|
52
59
|
*/
|
|
53
60
|
readonly lightmapRegions?: Float32Array;
|
|
54
61
|
/**
|
|
@@ -98,6 +105,11 @@ export declare function createMeshInstances(capacity: number): MeshInstances;
|
|
|
98
105
|
* as one four-float attribute, so the opacity cost no attribute and no byte.
|
|
99
106
|
*/
|
|
100
107
|
export declare const INSTANCE_FLOATS = 20;
|
|
108
|
+
/**
|
|
109
|
+
* How finely a lightmapped instance's opacity travels in its region: transparency in steps of
|
|
110
|
+
* 1/64, each 2 on the U offset. See `MeshInstances.lightmapRegions`; the vertex stage reads it back.
|
|
111
|
+
*/
|
|
112
|
+
export declare const LIGHTMAP_OPACITY_STEPS = 64;
|
|
101
113
|
/** Bytes one instance occupies. See `INSTANCE_FLOATS`. */
|
|
102
114
|
export declare const INSTANCE_STRIDE: number;
|
|
103
115
|
/**
|
package/dist/render/instances.js
CHANGED
|
@@ -26,6 +26,11 @@ export function createMeshInstances(capacity) {
|
|
|
26
26
|
* as one four-float attribute, so the opacity cost no attribute and no byte.
|
|
27
27
|
*/
|
|
28
28
|
export const INSTANCE_FLOATS = 20;
|
|
29
|
+
/**
|
|
30
|
+
* How finely a lightmapped instance's opacity travels in its region: transparency in steps of
|
|
31
|
+
* 1/64, each 2 on the U offset. See `MeshInstances.lightmapRegions`; the vertex stage reads it back.
|
|
32
|
+
*/
|
|
33
|
+
export const LIGHTMAP_OPACITY_STEPS = 64;
|
|
29
34
|
/** Bytes one instance occupies. See `INSTANCE_FLOATS`. */
|
|
30
35
|
export const INSTANCE_STRIDE = INSTANCE_FLOATS * 4;
|
|
31
36
|
/**
|
|
@@ -56,6 +61,11 @@ export function packInstances(instances, out) {
|
|
|
56
61
|
/* The region in the tint's and the opacity's place. See `lightmapRegions`. */
|
|
57
62
|
for (let c = 0; c < 4; c += 1)
|
|
58
63
|
out[at + 16 + c] = lightmapRegions[i * 4 + c];
|
|
64
|
+
/* And the opacity's transparency in whole steps of 2 on the U offset, none where opaque. */
|
|
65
|
+
const alpha = Math.min(Math.max(alphas?.[i] ?? 1, 0), 1);
|
|
66
|
+
const steps = Math.round((1 - alpha) * LIGHTMAP_OPACITY_STEPS);
|
|
67
|
+
if (steps > 0)
|
|
68
|
+
out[at + 18] = out[at + 18] + 2 * steps;
|
|
59
69
|
continue;
|
|
60
70
|
}
|
|
61
71
|
const t = i * 3;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where ambient occlusion fades out with distance: `setAmbientOcclusionFade`'s two numbers as the
|
|
3
|
+
* estimate reads them, `uFade` in `shaders/ambientOcclusion.ts`.
|
|
4
|
+
*
|
|
5
|
+
* **Occlusion is whole up to `distance` metres from the eye and gone `radius` metres past it.** A
|
|
6
|
+
* screen-space estimate reaches as far as the depth does, and far off the depth buffer's steps are
|
|
7
|
+
* metres apart: a sky dome or a mountain range kilometres away is shaded in rings that follow
|
|
8
|
+
* those steps, and nothing there is in contact with anything the estimate could be right about.
|
|
9
|
+
* A pixel past the fade skips the estimate's walk, as the sky does, so it costs less as well.
|
|
10
|
+
*
|
|
11
|
+
* **What it gives up**: distant contact shade that is real — a doorway a hundred metres off — fades
|
|
12
|
+
* with the rest, and the distance is the eye's and not the subject's, so a long lens fades what it
|
|
13
|
+
* frames close up. What would make it wrong is a scene whose far depth is precise enough to shade,
|
|
14
|
+
* which is a reason to fade further, not to leave the fade off.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* `distance` and `radius` into `out`, `[distance, radius]`. A distance that is not a finite number
|
|
18
|
+
* at or above zero is no fade, `[−1, 0]`, so `Infinity` reads as "never fades". A radius below zero
|
|
19
|
+
* or not a number is 0, which is a cut at `distance` rather than a ramp.
|
|
20
|
+
*/
|
|
21
|
+
export declare function resolveOcclusionFade(distance: number, radius: number, out: Float32Array): void;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Where ambient occlusion fades out with distance: `setAmbientOcclusionFade`'s two numbers as the
|
|
3
|
+
* estimate reads them, `uFade` in `shaders/ambientOcclusion.ts`.
|
|
4
|
+
*
|
|
5
|
+
* **Occlusion is whole up to `distance` metres from the eye and gone `radius` metres past it.** A
|
|
6
|
+
* screen-space estimate reaches as far as the depth does, and far off the depth buffer's steps are
|
|
7
|
+
* metres apart: a sky dome or a mountain range kilometres away is shaded in rings that follow
|
|
8
|
+
* those steps, and nothing there is in contact with anything the estimate could be right about.
|
|
9
|
+
* A pixel past the fade skips the estimate's walk, as the sky does, so it costs less as well.
|
|
10
|
+
*
|
|
11
|
+
* **What it gives up**: distant contact shade that is real — a doorway a hundred metres off — fades
|
|
12
|
+
* with the rest, and the distance is the eye's and not the subject's, so a long lens fades what it
|
|
13
|
+
* frames close up. What would make it wrong is a scene whose far depth is precise enough to shade,
|
|
14
|
+
* which is a reason to fade further, not to leave the fade off.
|
|
15
|
+
*/
|
|
16
|
+
/** `uFade` for no fade: a distance below zero, which the estimate reads as "never". */
|
|
17
|
+
const NO_FADE = -1;
|
|
18
|
+
/**
|
|
19
|
+
* `distance` and `radius` into `out`, `[distance, radius]`. A distance that is not a finite number
|
|
20
|
+
* at or above zero is no fade, `[−1, 0]`, so `Infinity` reads as "never fades". A radius below zero
|
|
21
|
+
* or not a number is 0, which is a cut at `distance` rather than a ramp.
|
|
22
|
+
*/
|
|
23
|
+
export function resolveOcclusionFade(distance, radius, out) {
|
|
24
|
+
const fades = Number.isFinite(distance) && distance >= 0;
|
|
25
|
+
out[0] = fades ? distance : NO_FADE;
|
|
26
|
+
out[1] = fades && Number.isFinite(radius) ? Math.max(radius, 0) : 0;
|
|
27
|
+
}
|
|
@@ -313,6 +313,8 @@ export declare class SceneTarget {
|
|
|
313
313
|
/** The projection's x and y scales, which turn a world radius into a screen one. */
|
|
314
314
|
readonly projScale: Float32Array;
|
|
315
315
|
readonly invProjection: Float32Array;
|
|
316
|
+
/** Where it fades out with distance, `uFade`: see `occlusionFade.ts`. */
|
|
317
|
+
readonly fade: Float32Array;
|
|
316
318
|
/** Where the frame's jitter put the occlusion, in uv: see the shader's `uAoOffset`. */
|
|
317
319
|
readonly offset?: Float32Array;
|
|
318
320
|
}, bloom?: {
|
|
@@ -771,7 +771,7 @@ export class SceneTarget {
|
|
|
771
771
|
let aoTexture = null;
|
|
772
772
|
if (ao !== undefined && !this.depthBlitRefused && this.depth !== null) {
|
|
773
773
|
this.ao ??= new AmbientOcclusionPass(gl);
|
|
774
|
-
aoTexture = this.ao.run(this.depth, this.width, this.height, ao.radius, ao.projScale, ao.invProjection);
|
|
774
|
+
aoTexture = this.ao.run(this.depth, this.width, this.height, ao.radius, ao.projScale, ao.invProjection, ao.fade);
|
|
775
775
|
}
|
|
776
776
|
/*
|
|
777
777
|
* Bloom beside occlusion rather than after the composite, because the composite is what
|
|
@@ -75,6 +75,11 @@ uniform vec2 uProjScale;
|
|
|
75
75
|
uniform mat4 uInvProjection;
|
|
76
76
|
/** How far the sampling reaches, in metres of the world. */
|
|
77
77
|
uniform float uRadius;
|
|
78
|
+
/**
|
|
79
|
+
* Where occlusion fades out: whole up to x metres from the eye, gone y metres past it, and no fade
|
|
80
|
+
* where x is below zero. See occlusionFade.ts.
|
|
81
|
+
*/
|
|
82
|
+
uniform vec2 uFade;
|
|
78
83
|
|
|
79
84
|
out float fragColor;
|
|
80
85
|
|
|
@@ -167,6 +172,12 @@ void main() {
|
|
|
167
172
|
fragColor = 1.0;
|
|
168
173
|
return;
|
|
169
174
|
}
|
|
175
|
+
/* How far past the caller's fade this pixel is, and open with no walk where it is all the way. */
|
|
176
|
+
float faded = uFade.x < 0.0 ? 0.0 : clamp((length(p) - uFade.x) / max(uFade.y, 1e-4), 0.0, 1.0);
|
|
177
|
+
if (faded >= 1.0) {
|
|
178
|
+
fragColor = AO_STORE;
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
170
181
|
|
|
171
182
|
/*
|
|
172
183
|
* At a silhouette, the quad derivative above is replaced by the one-sided difference that
|
|
@@ -307,7 +318,7 @@ void main() {
|
|
|
307
318
|
visible += projectedLength * gtaoArc(h0, h1, normalAngle);
|
|
308
319
|
}
|
|
309
320
|
|
|
310
|
-
fragColor = clamp(visible / float(AO_SLICES) * AO_STORE, 0.0, 1.0);
|
|
321
|
+
fragColor = clamp(mix(visible / float(AO_SLICES), 1.0, faded) * AO_STORE, 0.0, 1.0);
|
|
311
322
|
}
|
|
312
323
|
`;
|
|
313
324
|
/**
|
|
@@ -18,6 +18,7 @@ import { modelsGlsl } from './models.js';
|
|
|
18
18
|
import { AREA_LIGHT_GLSL } from './areaLight.js';
|
|
19
19
|
import { NORMAL_MAP_GLSL } from './normalMap.js';
|
|
20
20
|
import { DRIFT_LIGHT_GLSL } from './driftLight.js';
|
|
21
|
+
import { LIGHTMAP_OPACITY_STEPS } from '../../instances.js';
|
|
21
22
|
import { SURFACE_EFFECTS_GLSL } from './surfaceEffects.js';
|
|
22
23
|
import { overlayGlsl } from './overlay.js';
|
|
23
24
|
import { reflectionSurfaceGlsl } from './reflectionSurface.js';
|
|
@@ -341,10 +342,16 @@ void main() {
|
|
|
341
342
|
vAlpha = aInstanceTint.w;
|
|
342
343
|
vThickness = 1.0;
|
|
343
344
|
if (LIGHTMAP_REGIONS) {
|
|
345
|
+
/*
|
|
346
|
+
* The opacity's transparency rides the U offset as whole steps of 2, the offset itself under 1
|
|
347
|
+
* (\`MeshInstances.lightmapRegions\`): taken off, and none where the instance is opaque.
|
|
348
|
+
*/
|
|
349
|
+
float steps = floor(aInstanceTint.z * 0.5);
|
|
350
|
+
float offsetU = aInstanceTint.z - 2.0 * steps;
|
|
344
351
|
/* Out of the lane's \`-1 - uv\`, through the region, and back into it. See lightmap.ts. */
|
|
345
|
-
vGrain = -1.0 - ((-1.0 - aGrain) * aInstanceTint.x +
|
|
352
|
+
vGrain = -1.0 - ((-1.0 - aGrain) * aInstanceTint.x + offsetU);
|
|
346
353
|
vRelief = -1.0 - ((-1.0 - aRelief) * aInstanceTint.y + aInstanceTint.w);
|
|
347
|
-
vAlpha = 1.0;
|
|
354
|
+
vAlpha = 1.0 - steps / ${LIGHTMAP_OPACITY_STEPS.toFixed(1)};
|
|
348
355
|
}
|
|
349
356
|
#else
|
|
350
357
|
vSkyDirect = aChannel.y;
|