@driftengine/core 4.8.5 → 4.8.7
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 +4392 -4250
- package/README.md +1 -1
- package/dist/index.d.ts +12 -4
- package/dist/index.js +12 -2
- package/dist/render/ambientHarmonics.d.ts +46 -0
- package/dist/render/ambientHarmonics.js +80 -0
- package/dist/render/anisotropicLobe.d.ts +10 -0
- package/dist/render/anisotropicLobe.js +19 -0
- package/dist/render/areaLights.d.ts +54 -0
- package/dist/render/areaLights.js +55 -0
- package/dist/render/backend/api.d.ts +43 -2
- package/dist/render/backend/webgl2/instanced.d.ts +2 -0
- package/dist/render/backend/webgl2/instanced.js +11 -2
- package/dist/render/backend/webgl2/renderer.d.ts +120 -11
- package/dist/render/backend/webgl2/renderer.js +356 -53
- package/dist/render/backend/webgl2/sceneCaptureTarget.d.ts +29 -0
- package/dist/render/backend/webgl2/sceneCaptureTarget.js +96 -0
- package/dist/render/backend/webgl2/skinScatter.d.ts +14 -0
- package/dist/render/backend/webgl2/skinScatter.js +88 -26
- package/dist/render/backend/webgpu/buffers.js +2 -1
- package/dist/render/backend/webgpu/clusterBinner.js +3 -2
- package/dist/render/backend/webgpu/flatPass.d.ts +7 -5
- package/dist/render/backend/webgpu/flatPass.js +15 -13
- package/dist/render/backend/webgpu/particlePass.d.ts +37 -4
- package/dist/render/backend/webgpu/particlePass.js +81 -12
- package/dist/render/backend/webgpu/postPass.d.ts +6 -1
- package/dist/render/backend/webgpu/postPass.js +7 -2
- package/dist/render/backend/webgpu/renderer.d.ts +93 -8
- package/dist/render/backend/webgpu/renderer.js +483 -93
- package/dist/render/backend/webgpu/sceneCaptureTarget.d.ts +24 -0
- package/dist/render/backend/webgpu/sceneCaptureTarget.js +59 -0
- package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.d.ts +1 -1
- package/dist/render/backend/webgpu/shaders/clusterBin.wgsl.js +41 -1
- package/dist/render/backend/webgpu/skinPaletteRing.d.ts +40 -28
- package/dist/render/backend/webgpu/skinPaletteRing.js +69 -32
- package/dist/render/backend/webgpu/skinScatterPass.d.ts +10 -2
- package/dist/render/backend/webgpu/skinScatterPass.js +59 -9
- package/dist/render/backend/webgpu/surfaceTexturePass.d.ts +5 -1
- package/dist/render/backend/webgpu/surfaceTexturePass.js +45 -5
- package/dist/render/backend/webgpu/waterPass.d.ts +1 -1
- package/dist/render/bloomChain.d.ts +44 -0
- package/dist/render/bloomChain.js +28 -0
- package/dist/render/bloomPass.d.ts +3 -1
- package/dist/render/bloomPass.js +16 -2
- package/dist/render/clusteredLights.d.ts +78 -6
- package/dist/render/clusteredLights.js +214 -13
- package/dist/render/driftLight/bake.d.ts +2 -0
- package/dist/render/driftLight/bake.js +1 -1
- package/dist/render/driftLight/falloff.d.ts +4 -2
- package/dist/render/driftLight/falloff.js +8 -2
- package/dist/render/fixtureAtlas.js +1 -1
- package/dist/render/gi/bounceLights.d.ts +3 -2
- package/dist/render/gi/bounceLights.js +8 -3
- package/dist/render/instanceCull.js +12 -0
- package/dist/render/instances.d.ts +47 -11
- package/dist/render/instances.js +24 -9
- package/dist/render/lightBudget.d.ts +38 -2
- package/dist/render/lightBudget.js +48 -5
- package/dist/render/lightmap.d.ts +116 -0
- package/dist/render/lightmap.js +140 -0
- package/dist/render/mesh.js +2 -1
- package/dist/render/particleBatch.d.ts +59 -3
- package/dist/render/particleBatch.js +53 -2
- package/dist/render/particleMaterial.d.ts +6 -1
- package/dist/render/particleMaterial.js +10 -2
- package/dist/render/particlePool.d.ts +32 -0
- package/dist/render/particlePool.js +9 -0
- package/dist/render/particleSort.d.ts +34 -0
- package/dist/render/particleSort.js +96 -0
- package/dist/render/pointLightSelection.d.ts +13 -0
- package/dist/render/pointLightSelection.js +3 -1
- package/dist/render/renderQuality.d.ts +8 -7
- package/dist/render/rendererHarness.js +3 -0
- package/dist/render/rgb9e5.d.ts +22 -0
- package/dist/render/rgb9e5.js +47 -0
- package/dist/render/sceneCapture.d.ts +29 -0
- package/dist/render/sceneCapture.js +36 -0
- package/dist/render/sceneTarget.d.ts +5 -0
- package/dist/render/sceneTarget.js +8 -1
- package/dist/render/screenSpaceReflection.d.ts +27 -1
- package/dist/render/screenSpaceReflection.js +9 -0
- package/dist/render/shaders/bloom.d.ts +8 -4
- package/dist/render/shaders/bloom.js +20 -3
- package/dist/render/shaders/bolt.d.ts +1 -1
- package/dist/render/shaders/bolt.js +7 -1
- package/dist/render/shaders/depth.js +11 -0
- package/dist/render/shaders/flat/anisotropic.d.ts +1 -1
- package/dist/render/shaders/flat/anisotropic.js +37 -3
- package/dist/render/shaders/flat/areaLight.d.ts +10 -0
- package/dist/render/shaders/flat/areaLight.js +142 -0
- package/dist/render/shaders/flat/glassTint.d.ts +3 -2
- package/dist/render/shaders/flat/glassTint.js +3 -1
- package/dist/render/shaders/flat/index.d.ts +5 -0
- package/dist/render/shaders/flat/index.js +46 -6
- package/dist/render/shaders/flat/lobes.d.ts +6 -0
- package/dist/render/shaders/flat/lobes.js +49 -43
- package/dist/render/shaders/flat/main.d.ts +1 -1
- package/dist/render/shaders/flat/main.js +158 -121
- package/dist/render/shaders/flat/models.d.ts +21 -6
- package/dist/render/shaders/flat/models.js +161 -17
- package/dist/render/shaders/flat/normalMap.d.ts +10 -0
- package/dist/render/shaders/flat/normalMap.js +25 -0
- package/dist/render/shaders/flat/preamble.js +84 -40
- package/dist/render/shaders/flat/skin.js +6 -5
- package/dist/render/shaders/generated/bloom.wgsl.d.ts +13 -3
- package/dist/render/shaders/generated/bloom.wgsl.js +13 -3
- package/dist/render/shaders/generated/bolt.wgsl.d.ts +1 -1
- package/dist/render/shaders/generated/bolt.wgsl.js +1 -1
- package/dist/render/shaders/generated/depth.wgsl.d.ts +4 -4
- package/dist/render/shaders/generated/depth.wgsl.js +4 -4
- package/dist/render/shaders/generated/flat.wgsl.d.ts +1647 -2044
- package/dist/render/shaders/generated/flat.wgsl.js +1685 -2082
- package/dist/render/shaders/generated/particle.wgsl.d.ts +115 -1
- package/dist/render/shaders/generated/particle.wgsl.js +115 -1
- package/dist/render/shaders/generated/skinBlur.wgsl.d.ts +12 -2
- package/dist/render/shaders/generated/skinBlur.wgsl.js +12 -2
- package/dist/render/shaders/generated/ssrTrace.wgsl.d.ts +11 -6
- package/dist/render/shaders/generated/ssrTrace.wgsl.js +11 -6
- package/dist/render/shaders/generated/water.wgsl.d.ts +23 -16
- package/dist/render/shaders/generated/water.wgsl.js +23 -16
- package/dist/render/shaders/gi/probeBake.wgsl.js +9 -5
- package/dist/render/shaders/particle.d.ts +17 -1
- package/dist/render/shaders/particle.js +133 -1
- package/dist/render/shaders/recon/motion.wgsl.d.ts +1 -1
- package/dist/render/shaders/recon/motion.wgsl.js +13 -2
- package/dist/render/shaders/skinBlur.js +25 -3
- package/dist/render/shaders/ssrTrace.js +20 -1
- package/dist/render/shaders/water.d.ts +1 -1
- package/dist/render/shaders/water.js +12 -3
- package/dist/render/skinBlur.d.ts +8 -5
- package/dist/render/skinBlur.js +8 -5
- package/dist/render/skinScatter.d.ts +3 -1
- package/dist/render/skinScatter.js +3 -1
- package/dist/render/ssrPass.js +1 -0
- package/dist/render/surfaceModel.d.ts +49 -8
- package/dist/render/surfaceModel.js +54 -8
- package/dist/render/surfaceTexture.d.ts +38 -1
- package/dist/render/surfaceTexture.js +37 -2
- package/package.json +6 -6
- package/src/index.ts +26 -3
- package/src/render/ambientHarmonics.ts +92 -0
- package/src/render/anisotropicLobe.ts +29 -0
- package/src/render/areaLights.ts +97 -0
- package/src/render/backend/api.ts +61 -1
- package/src/render/backend/webgl2/instanced.ts +12 -2
- package/src/render/backend/webgl2/renderer.ts +470 -59
- package/src/render/backend/webgl2/sceneCaptureTarget.ts +130 -0
- package/src/render/backend/webgl2/skinScatter.ts +120 -43
- package/src/render/backend/webgpu/buffers.ts +2 -1
- package/src/render/backend/webgpu/clusterBinner.ts +3 -1
- package/src/render/backend/webgpu/flatPass.ts +18 -13
- package/src/render/backend/webgpu/particlePass.ts +119 -12
- package/src/render/backend/webgpu/postPass.ts +7 -2
- package/src/render/backend/webgpu/renderer.ts +589 -113
- package/src/render/backend/webgpu/sceneCaptureTarget.ts +63 -0
- package/src/render/backend/webgpu/shaders/clusterBin.wgsl.ts +41 -1
- package/src/render/backend/webgpu/skinPaletteRing.ts +65 -33
- package/src/render/backend/webgpu/skinScatterPass.ts +59 -9
- package/src/render/backend/webgpu/surfaceTexturePass.ts +57 -6
- package/src/render/bloomChain.ts +69 -0
- package/src/render/bloomPass.ts +26 -2
- package/src/render/clusteredLights.ts +259 -12
- package/src/render/driftLight/bake.ts +3 -1
- package/src/render/driftLight/falloff.ts +13 -2
- package/src/render/fixtureAtlas.ts +1 -1
- package/src/render/gi/bounceLights.ts +12 -3
- package/src/render/instanceCull.ts +9 -0
- package/src/render/instances.ts +61 -12
- package/src/render/lightBudget.ts +88 -4
- package/src/render/lightmap.ts +243 -0
- package/src/render/mesh.ts +2 -1
- package/src/render/particleBatch.ts +95 -4
- package/src/render/particleMaterial.ts +11 -1
- package/src/render/particlePool.ts +36 -0
- package/src/render/particleSort.ts +133 -0
- package/src/render/pointLightSelection.ts +20 -1
- package/src/render/renderQuality.ts +8 -7
- package/src/render/rendererHarness.ts +3 -0
- package/src/render/rgb9e5.ts +52 -0
- package/src/render/sceneCapture.ts +56 -0
- package/src/render/sceneTarget.ts +18 -1
- package/src/render/screenSpaceReflection.ts +33 -1
- package/src/render/shaders/bloom.ts +20 -3
- package/src/render/shaders/bolt.ts +7 -1
- package/src/render/shaders/depth.ts +11 -0
- package/src/render/shaders/flat/anisotropic.ts +37 -3
- package/src/render/shaders/flat/areaLight.ts +142 -0
- package/src/render/shaders/flat/glassTint.ts +9 -2
- package/src/render/shaders/flat/index.ts +51 -6
- package/src/render/shaders/flat/lobes.ts +50 -43
- package/src/render/shaders/flat/main.ts +158 -121
- package/src/render/shaders/flat/models.ts +170 -21
- package/src/render/shaders/flat/normalMap.ts +25 -0
- package/src/render/shaders/flat/preamble.ts +84 -40
- package/src/render/shaders/flat/skin.ts +6 -5
- package/src/render/shaders/generated/bloom.wgsl.ts +13 -3
- package/src/render/shaders/generated/bolt.wgsl.ts +1 -1
- package/src/render/shaders/generated/depth.wgsl.ts +4 -4
- package/src/render/shaders/generated/flat.wgsl.ts +1685 -2082
- package/src/render/shaders/generated/particle.wgsl.ts +116 -1
- package/src/render/shaders/generated/skinBlur.wgsl.ts +12 -2
- package/src/render/shaders/generated/ssrTrace.wgsl.ts +11 -6
- package/src/render/shaders/generated/water.wgsl.ts +23 -16
- package/src/render/shaders/gi/probeBake.wgsl.ts +9 -5
- package/src/render/shaders/particle.ts +134 -1
- package/src/render/shaders/recon/motion.wgsl.ts +13 -2
- package/src/render/shaders/skinBlur.ts +25 -3
- package/src/render/shaders/ssrTrace.ts +20 -1
- package/src/render/shaders/water.ts +12 -3
- package/src/render/skinBlur.ts +8 -5
- package/src/render/skinScatter.ts +3 -1
- package/src/render/ssrPass.ts +1 -0
- package/src/render/surfaceModel.ts +82 -10
- package/src/render/surfaceTexture.ts +101 -3
|
@@ -23,7 +23,9 @@ import { PickableSet } from '../../pickable.js';
|
|
|
23
23
|
import { GpuTimestamps } from './gpuTimestamps.js';
|
|
24
24
|
import { isWeakGpuFamily } from '../../gpuCapability.js';
|
|
25
25
|
import { resolveAtmosphere } from '../../atmosphere.js';
|
|
26
|
-
import {
|
|
26
|
+
import { AMBIENT_SH_FLOATS, packAmbientSH } from '../../ambientHarmonics.js';
|
|
27
|
+
import { lightmapTexels, warnRegionsWithoutPage, warnUnpagedLightmap, withLightmapUvs, } from '../../lightmap.js';
|
|
28
|
+
import { BLOOM_FILTER_RADIUS_UV, BLOOM_LEVELS, bloomLevelSizes, bloomThresholdOf, bloomProfileWarning, defaultBloomResponse, resolveBloomResponse, } from '../../bloomChain.js';
|
|
27
29
|
import { createResolvedWater, resolveWater, waterAppearance, } from '../../waterDraw.js';
|
|
28
30
|
import { createResolvedLightVolume, DEPTH_01_TO_CLIP_Y_DOWN, resolveLightVolume, volumeMediumGain, } from '../../lightVolumeDraw.js';
|
|
29
31
|
import { WIND_STREAK_DEFAULTS, createResolvedWindStreaks, resolveWindStreaks, } from '../../windStreakDraw.js';
|
|
@@ -42,7 +44,7 @@ import { COOKIE_TILE } from '../../lightBudget.js';
|
|
|
42
44
|
import { FULL_LIGHT_BUDGET } from '../../uniformVectorBudget.js';
|
|
43
45
|
import { toHalfFloats } from '../../halfFloat.js';
|
|
44
46
|
import { DEFAULT_SOURCE_RADIUS, FACE_COUNT, createResolvedPointShadows, } from '../../pointShadowImage.js';
|
|
45
|
-
import { MAX_AREA_LIGHTS } from '../../areaLights.js';
|
|
47
|
+
import { MAX_AREA_LIGHTS, packAreaSizesAndDoors } from '../../areaLights.js';
|
|
46
48
|
import { areaShadowLayerCount, castingRange, createResolvedAreaShadows, firstAreaShadowLayer, AreaShadowSet, } from '../../areaShadowSet.js';
|
|
47
49
|
import { createBakeScratch, runPointShadowBakes, selectCastingLights, } from '../../pointShadowBudget.js';
|
|
48
50
|
import { POINT_SHADOW_SAMPLER, PointShadowSystem, } from '../../pointShadowSystem.js';
|
|
@@ -52,9 +54,11 @@ import { createPanelBindGroup, createPanelBindGroupLayout, createPanelCorners, p
|
|
|
52
54
|
import { createGpuTextCube, createTextBindGroup, createTextBindGroupLayout, GpuText, textPipeline, TEXT_FRAG_FIELDS, TEXT_FRAG_SIZE, TEXT_VERT_FIELDS, TEXT_VERT_SIZE, } from './textPass.js';
|
|
53
55
|
import { deviceSnappedCellSize, deviceSnappedOrigin } from '../../textLayout.js';
|
|
54
56
|
import { GpuSurfaceTexture } from './surfaceTexturePass.js';
|
|
57
|
+
import { GpuSceneCaptureTarget } from './sceneCaptureTarget.js';
|
|
58
|
+
import { sceneCaptureTexels } from '../../sceneCapture.js';
|
|
55
59
|
import { gpuCompressedFormats } from './compressedUpload.js';
|
|
56
60
|
import { GpuClothBinding, GpuClothParticles, clothStandIn } from './clothTextures.js';
|
|
57
|
-
import { MODEL_PARAM_FLOATS, eyeAxisInWorld, packModel } from '../../surfaceModel.js';
|
|
61
|
+
import { MODEL_PARAM_FLOATS, eyeAxisInWorld, packModel, skinProfileDistance, } from '../../surfaceModel.js';
|
|
58
62
|
import { validateClothBinding } from '../../clothBindingData.js';
|
|
59
63
|
import { createSdfTextBindGroup, createSdfTextBindGroupLayout, GpuSdfText, sdfTextPipeline, SDF_TEXT_FRAG_FIELDS, SDF_TEXT_FRAG_SIZE, SDF_TEXT_VERT_FIELDS, SDF_TEXT_VERT_SIZE, } from './sdfTextPass.js';
|
|
60
64
|
import { createFilmBindGroup, createFilmBindGroupLayout, filmPipeline, FILM_FRAG_FIELDS, FILM_FRAG_SIZE, FILM_VERT_FIELDS, FILM_VERT_SIZE, } from './filmPass.js';
|
|
@@ -65,7 +69,7 @@ import { ExposurePass } from './exposurePass.js';
|
|
|
65
69
|
import { AO_STORE } from '../../shaders/ambientOcclusion.js';
|
|
66
70
|
import { clampAutoExposure } from '../../shaders/exposure.js';
|
|
67
71
|
import { clampLocalExposure } from '../../shaders/localExposure.js';
|
|
68
|
-
import { createDepthResolveLayout, createPostStageLayout, depthResolvePipeline, postPipeline, postPipelineTargets, sceneColorFormat, AO_BLUR_FIELDS, AO_BLUR_FRAG_WGSL, AO_BLUR_SIZE, AO_BLUR_TEXTURES, AO_BLUR_UNIFORMS, AO_FRAG_FIELDS, AO_FRAG_SIZE, AO_FRAG_WGSL, AO_TEXTURES, AO_UNIFORMS,
|
|
72
|
+
import { createDepthResolveLayout, createPostStageLayout, depthResolvePipeline, postPipeline, postPipelineTargets, sceneColorFormat, AO_BLUR_FIELDS, AO_BLUR_FRAG_WGSL, AO_BLUR_SIZE, AO_BLUR_TEXTURES, AO_BLUR_UNIFORMS, AO_FRAG_FIELDS, AO_FRAG_SIZE, AO_FRAG_WGSL, AO_TEXTURES, AO_UNIFORMS, BLOOM_DOWNSAMPLE_FIELDS, BLOOM_DOWNSAMPLE_FRAG_WGSL, BLOOM_PREFILTER_FIELDS, BLOOM_PREFILTER_FRAG_WGSL, BLOOM_STAGE_SIZE, BLOOM_TEXTURES, BLOOM_UNIFORMS, BLOOM_UPSAMPLE_FIELDS, TINTED_ADD_BLEND, BLOOM_UPSAMPLE_FRAG_WGSL, RESOLVED_DEPTH_FORMAT, RUSH_FRAG_FIELDS, RUSH_FRAG_SIZE, RUSH_FRAG_WGSL, RUSH_TEXTURES, RUSH_UNIFORMS, TAA_FRAG_FIELDS, TAA_FRAG_SIZE, TAA_TEXTURES, TAA_UNIFORMS, TEMPORAL_RESOLVE_FRAG_WGSL, MEDIUM_FRAG_FIELDS, MEDIUM_FRAG_SIZE, MEDIUM_FRAG_WGSL, MEDIUM_TEXTURES, MEDIUM_TRANSMIT_BLEND, MEDIUM_UNIFORMS, MEDIUM_UPSAMPLE_FIELDS, MEDIUM_UPSAMPLE_FRAG_WGSL, MEDIUM_UPSAMPLE_SIZE, MEDIUM_UPSAMPLE_TEXTURES, MEDIUM_UPSAMPLE_UNIFORMS, OIT_COMPOSITE_BLEND, OIT_RESOLVE_FRAG_WGSL, OIT_TEXTURES, DECAL_FRAG_FIELDS, DECAL_FRAG_SIZE, DECAL_MULTIPLY_BLEND, DECAL_PROJECT_FRAG_WGSL, DECAL_TEXTURES, DECAL_UNIFORMS, PREMULTIPLIED_OVER_BLEND, REFLECTION_OVER_BLEND, SSR_FRAG_FIELDS, SSR_FRAG_SIZE, SSR_RESOLVE_FRAG_WGSL, SSR_RESOLVE_TEXTURES, SSR_TEXTURES, SSR_TRACE_FRAG_WGSL, SSR_UNIFORMS, createOitResolveLayout, createSsrResolveLayout, } from './postPass.js';
|
|
69
73
|
import { GRADE_PLACEHOLDER_SIZE, identityGradeLut, validateGradeLut, } from '../../colourGrade.js';
|
|
70
74
|
import { FILM_LOOK_WITHOUT_COMPOSITE, clampGrain, clampVignette, grainSeed, } from '../../shaders/filmLook.js';
|
|
71
75
|
import { createProbeMipBindGroupLayout, createProbePrefilterBindGroupLayout, PREFILTER_UNIFORM_OFFSETS, probeLevels, probeMipPipeline, probePrefilterPipeline, PROBE_FACES, PROBE_FACE_BASIS, PROBE_FAR_M, PROBE_FOV_DEG, PROBE_MIP_UNIFORM_SIZE, PROBE_NEAR_M, } from './probePass.js';
|
|
@@ -75,6 +79,7 @@ import { seaStateForWind } from '../../seaState.js';
|
|
|
75
79
|
import { causticsPipeline, createCausticsBindGroup, createCausticsBindGroupLayout, createGpuCaustics, CAUSTICS_FRAG_FIELDS, CAUSTICS_FRAG_SIZE, CAUSTICS_VERT_FIELDS, CAUSTICS_VERT_SIZE, } from './causticsPass.js';
|
|
76
80
|
import { createInsetBindGroup, createInsetBindGroupLayout, insetPipeline, INSET_UNIFORM_SIZE, } from './insetPass.js';
|
|
77
81
|
import { buildPlumeGeometry } from '../../plumeGeometry.js';
|
|
82
|
+
import { facingCode } from '../../particleBatch.js';
|
|
78
83
|
import { PARTICLE_INDICES } from '../../shaders/particle.js';
|
|
79
84
|
import { createScatterDeform, resolveScatterDeform, } from '../../scatterDeform.js';
|
|
80
85
|
import { resolvePointLights } from '../../lightBudget.js';
|
|
@@ -96,7 +101,8 @@ import { SKY_FIELDS, SKY_UNIFORM_SIZE, createSkyBindGroup, createSkyBindGroupLay
|
|
|
96
101
|
import { SCATTER_DEPTH_FIELDS, SCATTER_DEPTH_SIZE, SCATTER_FRAG_FIELDS, SCATTER_FRAG_SIZE, SCATTER_VERT_FIELDS, SCATTER_VERT_SIZE, createGpuScatter, createScatterBindGroup, createScatterBindGroupLayout, createScatterDepthBindGroup, createScatterDepthBindGroupLayout, scatterDepthPipeline, scatterPipeline, } from './scatterPass.js';
|
|
97
102
|
import { WATER_FRAG_FIELDS, WATER_FRAG_SIZE, WATER_VERT_FIELDS, WATER_VERT_SIZE, createGpuWater, createWaterBindGroup, createWaterBindGroupLayout, waterPipeline, } from './waterPass.js';
|
|
98
103
|
import { PLUME_FRAG_FIELDS, PLUME_FRAG_SIZE, PLUME_VERT_FIELDS, PLUME_VERT_SIZE, createGpuPlumes, createPlumeBindGroup, createPlumeBindGroupLayout, plumePipeline, } from './plumePass.js';
|
|
99
|
-
import { PARTICLE_VERT_FIELDS, createGpuParticles, createParticleBindGroupLayout, packInstances, particleFragBindings, particlePipeline, } from './particlePass.js';
|
|
104
|
+
import { PARTICLE_INSTANCE_FLOATS, PARTICLE_VERT_FIELDS, createGpuParticles, createParticleBindGroupLayout, createSpriteBindGroup, packInstances, particleFragBindings, particlePipeline, } from './particlePass.js';
|
|
105
|
+
import { sortBackToFront } from '../../particleSort.js';
|
|
100
106
|
import { WIND_STREAK_FRAG_FIELDS, WIND_STREAK_FRAG_SIZE, WIND_STREAK_VERT_FIELDS, WIND_STREAK_VERT_SIZE, createGpuWindStreaks, createWindStreakBindGroup, createWindStreakBindGroupLayout, windStreakPipeline, } from './windStreakPass.js';
|
|
101
107
|
import { FLOCK_FRAG_FIELDS, FLOCK_FRAG_SIZE, FLOCK_VERT_FIELDS, FLOCK_VERT_SIZE, createFlockBindGroup, createFlockBindGroupLayout, createGpuFlock, flockPipeline, } from './flockPass.js';
|
|
102
108
|
import { BOLT_FRAG_FIELDS, BOLT_FRAG_SIZE, BOLT_VERT_FIELDS, BOLT_VERT_SIZE, boltPipeline, createBoltBindGroup, createBoltBindGroupLayout, createGpuBolts, } from './boltPass.js';
|
|
@@ -127,6 +133,8 @@ import { fitProbeGrid } from '../../gi/probeGridFit.js';
|
|
|
127
133
|
const INDIRECT_PROBE_SPACING = 2;
|
|
128
134
|
/** What the trace is given when a frame never said what was lighting it. */
|
|
129
135
|
const NO_SUN = [0, 0, 0];
|
|
136
|
+
/** The halves of a skin draw the spread keeps, in the order they are drawn. See `keepSkinDiffuse`. */
|
|
137
|
+
const SKIN_KEPT_HALVES = ['diffuse', 'albedo'];
|
|
130
138
|
import { CLIP_Y_FLIP, MAX_REFLECTIVE_SURFACES, REFLECTION_EDGE_FADE, ReflectionQueue, } from '../../screenSpaceReflection.js';
|
|
131
139
|
import { decalScissor } from '../../decalProjector.js';
|
|
132
140
|
import { createCommandPool, resetPool, takeCommand } from './drawCommand.js';
|
|
@@ -138,7 +146,7 @@ import { OcclusionBuffer } from '../../occlusion.js';
|
|
|
138
146
|
import { boundsVisible } from '../../visibility.js';
|
|
139
147
|
import { createPassRegistry, drainRegistry, passAt, registerIn, unregisterIn } from '../../pass.js';
|
|
140
148
|
import { ClusterBinner } from './clusterBinner.js';
|
|
141
|
-
import { clusteredMode, createClusterLightSet, fillClusterLightSet, } from '../../clusteredLights.js';
|
|
149
|
+
import { clusteredMode, createClusterLightSet, clusteredLightTotal, fillClusterLightSet, lightChannelsOf, } from '../../clusteredLights.js';
|
|
142
150
|
import { computeAt, createComputeRegistry, } from '../../compute.js';
|
|
143
151
|
import { GpuInstancedBatch } from './instanced.js';
|
|
144
152
|
import { cutoutOf } from '../../cutoutCaster.js';
|
|
@@ -688,11 +696,16 @@ const DECAL_SLOT = DYNAMIC_UNIFORM_ALIGNMENT;
|
|
|
688
696
|
*/
|
|
689
697
|
const SSR_SLOT = Math.ceil(SSR_FRAG_SIZE / DYNAMIC_UNIFORM_ALIGNMENT) * DYNAMIC_UNIFORM_ALIGNMENT;
|
|
690
698
|
const BLOOM_SLOT = DYNAMIC_UNIFORM_ALIGNMENT;
|
|
691
|
-
const BLOOM_SLOTS = BLOOM_LEVELS +
|
|
699
|
+
const BLOOM_SLOTS = BLOOM_LEVELS + 2;
|
|
692
700
|
/** One slot per blur axis, aligned like every other dynamic-offset block here. */
|
|
693
701
|
const AO_BLUR_SLOT = DYNAMIC_UNIFORM_ALIGNMENT;
|
|
694
|
-
/**
|
|
702
|
+
/**
|
|
703
|
+
* The tent radius every upsample draw binds, with white as the level it reads is multiplied by;
|
|
704
|
+
* and the same for the first draw up, which multiplies the deepest level by that level's tint.
|
|
705
|
+
* Every other level's tint is the blend constant of the draw adding onto it. See `BloomResponse`.
|
|
706
|
+
*/
|
|
695
707
|
const BLOOM_UPSAMPLE_SLOT = BLOOM_LEVELS;
|
|
708
|
+
const BLOOM_UPSAMPLE_DEEPEST_SLOT = BLOOM_LEVELS + 1;
|
|
696
709
|
/** The tint a draw gets when it asks for none, matching the WebGL2 path's reset-to-white. */
|
|
697
710
|
const WHITE = new Float32Array([1, 1, 1]);
|
|
698
711
|
/** A dust field standing still, for a light volume that asked for dust and not for wind. */
|
|
@@ -849,7 +862,7 @@ export class WebGPURenderer {
|
|
|
849
862
|
* Held rather than passed, like every other per-draw state here: `setSkinPalette` chooses and
|
|
850
863
|
* the following draws use it, which is the shape `setMaterial` already has.
|
|
851
864
|
*/
|
|
852
|
-
skinPalettes
|
|
865
|
+
skinPalettes;
|
|
853
866
|
skinnedBindGroupLayout;
|
|
854
867
|
morphedBindGroupLayout;
|
|
855
868
|
bothBindGroupLayout;
|
|
@@ -1279,6 +1292,17 @@ export class WebGPURenderer {
|
|
|
1279
1292
|
/** Whether this frame has spread its skin: once, at its first blended draw or before its end. */
|
|
1280
1293
|
skinScattered = false;
|
|
1281
1294
|
skinInverseProjection = mat4.create();
|
|
1295
|
+
/** Scratch for a sprite's depth conversion, so a particle draw allocates nothing. */
|
|
1296
|
+
spriteInverseProjection = mat4.create();
|
|
1297
|
+
/** Said once: a sprite asked for a soft edge where no depth copy could be had. */
|
|
1298
|
+
warnedSpriteDepth = false;
|
|
1299
|
+
/** Said once: more rectangles than the fixed arm shades, and no froxel table for the rest. */
|
|
1300
|
+
warnedAreaOverflow = false;
|
|
1301
|
+
/** What a sprite's depth binding holds where no copy was taken. See `spriteDepthStandIn`. */
|
|
1302
|
+
spriteDepthEmpty = null;
|
|
1303
|
+
/** The samplers every sprite batch binds: its image filtered, the depth copy texel for texel. */
|
|
1304
|
+
spriteLinearSampler = null;
|
|
1305
|
+
spriteNearestSampler = null;
|
|
1282
1306
|
sceneColorView = null;
|
|
1283
1307
|
/** The multisampled twin, when the profile asks for samples. Resolved into `sceneColor`. */
|
|
1284
1308
|
sceneColorMsaa = null;
|
|
@@ -2198,6 +2222,104 @@ export class WebGPURenderer {
|
|
|
2198
2222
|
createSurfaceTextureArray(sources, options = {}) {
|
|
2199
2223
|
return new GpuSurfaceTexture(this.surface.device, this.pipelines, sources, options, this.compressedFormats);
|
|
2200
2224
|
}
|
|
2225
|
+
/** A baked lightmap page, as a `lightmapModel` material's `modelMap`. See `RendererApi`. */
|
|
2226
|
+
createLightmap(page) {
|
|
2227
|
+
return new GpuSurfaceTexture(this.surface.device, this.pipelines, lightmapTexels(page), {
|
|
2228
|
+
wrap: 'clamp',
|
|
2229
|
+
mipmap: false,
|
|
2230
|
+
});
|
|
2231
|
+
}
|
|
2232
|
+
/**
|
|
2233
|
+
* A texture the scene can be drawn into, for a material to show. See `RendererApi` and
|
|
2234
|
+
* `sceneCapture.ts`. At the world's own format, which under `hdrScene` is half floats.
|
|
2235
|
+
*/
|
|
2236
|
+
createSceneCapture(width, height) {
|
|
2237
|
+
const texture = new GpuSurfaceTexture(this.surface.device, this.pipelines, sceneCaptureTexels(width, height, this.quality.hdrScene), { wrap: 'clamp', mipmap: false });
|
|
2238
|
+
this.captureTargets.set(texture, new GpuSceneCaptureTarget(this.surface.device, width, height));
|
|
2239
|
+
return texture;
|
|
2240
|
+
}
|
|
2241
|
+
/** Each capture's attachments, beside the texture a material binds. */
|
|
2242
|
+
captureTargets = new WeakMap();
|
|
2243
|
+
/**
|
|
2244
|
+
* Whether a scene capture is being drawn: an off-screen pass as a probe bake is, so the frame's
|
|
2245
|
+
* camera, jitter, motion, refraction copy, skin's spread and late routing are left alone, but
|
|
2246
|
+
* lit by the probes and DriftLight as the frame is. See `captureScene`.
|
|
2247
|
+
*/
|
|
2248
|
+
capturePassActive = false;
|
|
2249
|
+
/**
|
|
2250
|
+
* Draw the scene into a capture from `camera`. See `RendererApi.captureScene`.
|
|
2251
|
+
*
|
|
2252
|
+
* **Its own encoder, as a probe bake's**: the frame's pass is set aside and restored, the rings
|
|
2253
|
+
* are marked and rewound around the submit, and the frame graph is flushed at both boundaries so
|
|
2254
|
+
* no recorded draw crosses one. **Radiance**: the output transform and exposure are held off.
|
|
2255
|
+
*/
|
|
2256
|
+
captureScene(capture, camera, clearColor, draw) {
|
|
2257
|
+
if (this.surface.lost)
|
|
2258
|
+
return false;
|
|
2259
|
+
const target = this.captureTargets.get(capture);
|
|
2260
|
+
const resolve = capture.layerView();
|
|
2261
|
+
if (target === undefined || resolve === null)
|
|
2262
|
+
return false;
|
|
2263
|
+
camera.updateMatrices(target.width / target.height);
|
|
2264
|
+
const { device } = this.surface;
|
|
2265
|
+
const { depth, colorMsaa } = target.attachments(this.samples, this.pipelines.format);
|
|
2266
|
+
const savedPass = this.pass;
|
|
2267
|
+
if (this.quality.frameGraph)
|
|
2268
|
+
this.flushGraph();
|
|
2269
|
+
const savedEncoder = this.encoder;
|
|
2270
|
+
this.markRings();
|
|
2271
|
+
const skinMark = this.skinPalettes.mark();
|
|
2272
|
+
const frameEpoch = this.encoderEpoch;
|
|
2273
|
+
this.encoderEpoch = ++this.encoderEpochs;
|
|
2274
|
+
const encoder = device.createCommandEncoder({ label: 'capture' });
|
|
2275
|
+
this.encoder = encoder;
|
|
2276
|
+
this.capturePassActive = true;
|
|
2277
|
+
const heldGrade = this.perFrameInts[this.materialField('uOutputTransform')] ?? 0;
|
|
2278
|
+
const heldExposure = this.perFrameFloats[this.materialField('uOutputExposure')] ?? 1;
|
|
2279
|
+
this.perFrameInts[this.materialField('uOutputTransform')] = 0;
|
|
2280
|
+
this.perFrameFloats[this.materialField('uOutputExposure')] = 1;
|
|
2281
|
+
this.materials.dirty();
|
|
2282
|
+
const pass = encoder.beginRenderPass({
|
|
2283
|
+
label: 'capture',
|
|
2284
|
+
colorAttachments: [
|
|
2285
|
+
{
|
|
2286
|
+
view: colorMsaa ?? resolve,
|
|
2287
|
+
resolveTarget: colorMsaa === null ? undefined : resolve,
|
|
2288
|
+
loadOp: 'clear',
|
|
2289
|
+
storeOp: resolvedStoreOp(colorMsaa !== null, true, this.quality.discardResolvedAttachments),
|
|
2290
|
+
clearValue: { r: clearColor[0], g: clearColor[1], b: clearColor[2], a: 1 },
|
|
2291
|
+
},
|
|
2292
|
+
],
|
|
2293
|
+
depthStencilAttachment: {
|
|
2294
|
+
view: depth,
|
|
2295
|
+
depthClearValue: DEPTH_CLEAR,
|
|
2296
|
+
depthLoadOp: 'clear',
|
|
2297
|
+
depthStoreOp: 'discard',
|
|
2298
|
+
},
|
|
2299
|
+
});
|
|
2300
|
+
this.pass = pass;
|
|
2301
|
+
try {
|
|
2302
|
+
draw(camera);
|
|
2303
|
+
/* The callback's draws replayed into this pass before it ends: see `bakeProbe`. */
|
|
2304
|
+
if (this.quality.frameGraph)
|
|
2305
|
+
this.flushGraph();
|
|
2306
|
+
}
|
|
2307
|
+
finally {
|
|
2308
|
+
pass.end();
|
|
2309
|
+
this.pass = null;
|
|
2310
|
+
this.flushRings();
|
|
2311
|
+
device.queue.submit([encoder.finish()]);
|
|
2312
|
+
this.rewindRings(skinMark);
|
|
2313
|
+
this.encoderEpoch = frameEpoch;
|
|
2314
|
+
this.encoder = savedEncoder;
|
|
2315
|
+
this.pass = savedPass;
|
|
2316
|
+
this.perFrameInts[this.materialField('uOutputTransform')] = heldGrade;
|
|
2317
|
+
this.perFrameFloats[this.materialField('uOutputExposure')] = heldExposure;
|
|
2318
|
+
this.materials.dirty();
|
|
2319
|
+
this.capturePassActive = false;
|
|
2320
|
+
}
|
|
2321
|
+
return true;
|
|
2322
|
+
}
|
|
2201
2323
|
/**
|
|
2202
2324
|
* GPU textures an `updateSurfaceTexture` replaced, destroyed when the next frame begins.
|
|
2203
2325
|
*
|
|
@@ -2251,6 +2373,8 @@ export class WebGPURenderer {
|
|
|
2251
2373
|
this.modelMap = null;
|
|
2252
2374
|
this.forgetBindingsOf(texture);
|
|
2253
2375
|
texture.dispose();
|
|
2376
|
+
this.captureTargets.get(texture)?.dispose();
|
|
2377
|
+
this.captureTargets.delete(texture);
|
|
2254
2378
|
}
|
|
2255
2379
|
/**
|
|
2256
2380
|
* Drop every bind group that may hold `texture`'s view, and rebuild the ones bound now.
|
|
@@ -2427,8 +2551,9 @@ export class WebGPURenderer {
|
|
|
2427
2551
|
this.materialModel = material?.model ?? null;
|
|
2428
2552
|
/* What the blur spreads this skin by: the last material to name a profile says. */
|
|
2429
2553
|
const model = material?.model;
|
|
2554
|
+
warnUnpagedLightmap(model, modelMap);
|
|
2430
2555
|
if (model?.kind === 'skin' && this.skinScatter !== null) {
|
|
2431
|
-
this.skinScatter.setProfile(model.profile, model
|
|
2556
|
+
this.skinScatter.setProfile(model.profile, skinProfileDistance(model, 0), skinProfileDistance(model, 1), skinProfileDistance(model, 2));
|
|
2432
2557
|
}
|
|
2433
2558
|
this.material((f, i) => {
|
|
2434
2559
|
/* The material's four switches, one row: albedo, ORM map, emissive map, two-sided. */
|
|
@@ -2444,6 +2569,8 @@ export class WebGPURenderer {
|
|
|
2444
2569
|
f[cut] = cutoff;
|
|
2445
2570
|
f[cut + 1] = CUTOUT_RESOLVE_CODE[this.cutoutResolveStaged];
|
|
2446
2571
|
f[cut + 2] = this.cutoutFrame;
|
|
2572
|
+
/* The material's lighting channels, which the lamps' are compared with. See `uCutout`. */
|
|
2573
|
+
f[cut + 3] = lightChannelsOf(material?.lightChannels);
|
|
2447
2574
|
i[flags + 3] = material?.doubleSided === true ? 1 : 0;
|
|
2448
2575
|
/* 0 is no map, and gates the whole block in the shader. See `uNormalStrength`. */
|
|
2449
2576
|
f[this.materialField('uNormalStrength')] =
|
|
@@ -2471,7 +2598,10 @@ export class WebGPURenderer {
|
|
|
2471
2598
|
f[emissive + 1] = asked?.[1] ?? 1;
|
|
2472
2599
|
f[emissive + 2] = asked?.[2] ?? 1;
|
|
2473
2600
|
/* The model's numbers and whether its map is bound. See `packModel`. */
|
|
2474
|
-
packModel(material?.model ?? null, modelMap !== null, this.modelParams);
|
|
2601
|
+
packModel(material?.model ?? null, modelMap !== null, this.modelParams, material?.physicalSpecular === true);
|
|
2602
|
+
/* The first physical highlight asked for is when the lit pipelines start carrying one. */
|
|
2603
|
+
if (material?.physicalSpecular === true)
|
|
2604
|
+
this.enableLit('PHYSICAL_SPECULAR');
|
|
2475
2605
|
f.set(this.modelParams, this.materialField('uModelParams'));
|
|
2476
2606
|
});
|
|
2477
2607
|
/*
|
|
@@ -2583,16 +2713,26 @@ export class WebGPURenderer {
|
|
|
2583
2713
|
console.warn('WebGPU: setEnvironmentAmbient no longer does anything. A baked probe now lights a ' +
|
|
2584
2714
|
'surface through projected irradiance, and there is nothing left to tune.');
|
|
2585
2715
|
}
|
|
2716
|
+
/**
|
|
2717
|
+
* The ambient the following draws take, nine spherical-harmonic coefficients or null for the
|
|
2718
|
+
* frame's. See `Renderer.setAmbientSH`: material state, so the next draw takes a slot of its own.
|
|
2719
|
+
*/
|
|
2720
|
+
setAmbientSH(coefficients) {
|
|
2721
|
+
if (this.surface.lost)
|
|
2722
|
+
return;
|
|
2723
|
+
packAmbientSH(coefficients, this.ambientSH);
|
|
2724
|
+
this.material((f) => f.set(this.ambientSH, this.materialField('uAmbientSH')));
|
|
2725
|
+
}
|
|
2586
2726
|
/** How reflective the surfaces drawn next are. Clamped, as `renderer.ts` clamps it. */
|
|
2587
2727
|
setSurfaceReflectivity(amount) {
|
|
2588
2728
|
this.material((f) => {
|
|
2589
|
-
f[this.materialField('
|
|
2729
|
+
f[this.materialField('uEnvironmentDials') + 1] = Math.min(1, Math.max(0, amount));
|
|
2590
2730
|
});
|
|
2591
2731
|
}
|
|
2592
2732
|
/** How bright the environment the surfaces drawn next reflect is. See `renderer.ts`. */
|
|
2593
2733
|
setEnvironmentGain(gain) {
|
|
2594
2734
|
this.material((f) => {
|
|
2595
|
-
f[this.materialField('
|
|
2735
|
+
f[this.materialField('uEnvironmentDials') + 2] = Math.max(0, gain);
|
|
2596
2736
|
});
|
|
2597
2737
|
}
|
|
2598
2738
|
setSurfaceGrain(amount) {
|
|
@@ -2629,7 +2769,7 @@ export class WebGPURenderer {
|
|
|
2629
2769
|
setSurfaceRelief(amount, cyclesPerMetre = 60) {
|
|
2630
2770
|
this.material((f) => {
|
|
2631
2771
|
f[this.materialField('uRelief')] = Math.min(1, Math.max(0, amount));
|
|
2632
|
-
f[this.materialField('
|
|
2772
|
+
f[this.materialField('uRelief') + 1] = Math.max(0.01, cyclesPerMetre);
|
|
2633
2773
|
});
|
|
2634
2774
|
}
|
|
2635
2775
|
/**
|
|
@@ -2647,7 +2787,7 @@ export class WebGPURenderer {
|
|
|
2647
2787
|
/** Scale the emissive of the following draws. See `Renderer.setEmissiveGain` for why. */
|
|
2648
2788
|
setEmissiveGain(gain) {
|
|
2649
2789
|
this.material((f) => {
|
|
2650
|
-
f[this.materialField('
|
|
2790
|
+
f[this.materialField('uEmission')] = gain;
|
|
2651
2791
|
});
|
|
2652
2792
|
}
|
|
2653
2793
|
/* -- Overlays ------------------------------------------------------------------------ */
|
|
@@ -3220,7 +3360,19 @@ export class WebGPURenderer {
|
|
|
3220
3360
|
*/
|
|
3221
3361
|
createParticles(capacity, options) {
|
|
3222
3362
|
const layout = this.particleLayoutFor(options.material);
|
|
3223
|
-
|
|
3363
|
+
const texture = (options.texture ?? null);
|
|
3364
|
+
if (options.material === 'sprite' && texture === null) {
|
|
3365
|
+
throw new Error("createParticles: a 'sprite' draws an image, and was given no `texture`");
|
|
3366
|
+
}
|
|
3367
|
+
return createGpuParticles(this.surface.device, capacity, options.material, options.blend, options.stretchSec ?? 0, options.facing ?? 'camera', options.erosion ?? 0, options.coreGain ?? 1, options.fog ?? false, layout.layout, layout.fields, layout.fragSize, {
|
|
3368
|
+
texture: texture?.view ?? null,
|
|
3369
|
+
cells: options.cells ?? [1, 1],
|
|
3370
|
+
blendCells: options.blendCells === true,
|
|
3371
|
+
softDepth: Math.max(0, options.softDepth ?? 0),
|
|
3372
|
+
cameraFade: Math.max(0, options.cameraFade ?? 0),
|
|
3373
|
+
cameraOffset: options.cameraOffset ?? 0,
|
|
3374
|
+
sort: options.sort === true,
|
|
3375
|
+
});
|
|
3224
3376
|
}
|
|
3225
3377
|
disposeParticles(batch) {
|
|
3226
3378
|
batch.dispose();
|
|
@@ -3260,18 +3412,45 @@ export class WebGPURenderer {
|
|
|
3260
3412
|
return;
|
|
3261
3413
|
}
|
|
3262
3414
|
batch.drawnInEncoder = this.encoderEpoch;
|
|
3263
|
-
|
|
3415
|
+
/* Farthest first where the batch asked; see `particleSort.ts`. */
|
|
3416
|
+
const drawn = batch.order === null ? data : sortBackToFront(data, camera.position, batch.order);
|
|
3417
|
+
const live = packInstances(batch, drawn);
|
|
3264
3418
|
if (live === 0)
|
|
3265
3419
|
return;
|
|
3266
3420
|
const device = this.surface.device;
|
|
3267
|
-
device.queue.writeBuffer(batch.instances, 0, batch.staging, 0, live *
|
|
3421
|
+
device.queue.writeBuffer(batch.instances, 0, batch.staging, 0, live * PARTICLE_INSTANCE_FLOATS);
|
|
3422
|
+
this.drawParticleInstances(batch, batch.instances, live, camera, env, timeSeconds);
|
|
3423
|
+
}
|
|
3424
|
+
/**
|
|
3425
|
+
* Draw particles a caller's own compute wrote, where they lie. See `DeviceParticles`.
|
|
3426
|
+
*
|
|
3427
|
+
* **The pool's draw with its instance stream swapped**: the same material, blend, flipbook, soft
|
|
3428
|
+
* edge and fog, reading the caller's buffer in place of the one the pool uploads into — so nothing
|
|
3429
|
+
* crosses back to the CPU. Not sorted, and a count past what the buffer holds is cut to it rather
|
|
3430
|
+
* than handed to a draw the device would refuse with the frame.
|
|
3431
|
+
*/
|
|
3432
|
+
drawDeviceParticles(batch, particles, camera, env, timeSeconds) {
|
|
3433
|
+
const fits = Math.floor(particles.buffer.size / (PARTICLE_INSTANCE_FLOATS * 4));
|
|
3434
|
+
const live = Math.min(Math.max(0, Math.floor(particles.count)), fits);
|
|
3435
|
+
/* Not above zero covers a NaN as well: a count or a size that is no number draws nothing. */
|
|
3436
|
+
if (!this.canDraw() || !(live > 0))
|
|
3437
|
+
return;
|
|
3438
|
+
if (batch.drawnInEncoder === this.encoderEpoch)
|
|
3439
|
+
return;
|
|
3440
|
+
batch.drawnInEncoder = this.encoderEpoch;
|
|
3441
|
+
this.drawParticleInstances(batch, particles.buffer, live, camera, env, timeSeconds);
|
|
3442
|
+
}
|
|
3443
|
+
/** A pool's uniforms for this view, and its draw from `instances`: both entry points' tail. */
|
|
3444
|
+
drawParticleInstances(batch, instances, live, camera, env, timeSeconds) {
|
|
3445
|
+
const device = this.surface.device;
|
|
3268
3446
|
const v = batch.vertFloats;
|
|
3269
3447
|
const atV = (name) => (PARTICLE_VERT_FIELDS[name]?.offset ?? -4) / 4;
|
|
3270
3448
|
mat4.multiply(this.correctedViewProj, CLIP_CORRECTION, camera.viewProjection);
|
|
3271
3449
|
v.set(this.correctedViewProj, atV('uViewProj'));
|
|
3272
3450
|
v.set(camera.position, atV('uCameraPos'));
|
|
3273
3451
|
v[atV('uStretchSec')] = batch.stretchSec;
|
|
3274
|
-
v[atV('uCameraFacing')] = batch.facing
|
|
3452
|
+
v[atV('uCameraFacing')] = facingCode(batch.facing);
|
|
3453
|
+
v[atV('uCameraOffset')] = batch.cameraOffset;
|
|
3275
3454
|
const f = batch.fragFloats;
|
|
3276
3455
|
const i = batch.fragInts;
|
|
3277
3456
|
const fields = batch.fields;
|
|
@@ -3322,16 +3501,20 @@ export class WebGPURenderer {
|
|
|
3322
3501
|
`uCameraPos` rather than guarded like the material-specific fields above. */
|
|
3323
3502
|
i[atF('uOutputTransform')] = this.gradeCode();
|
|
3324
3503
|
f[atF('uOutputExposure')] = this.gradeExposure();
|
|
3504
|
+
const spriteGroup = batch.material === 'sprite' ? this.spriteFades(batch, camera, f) : null;
|
|
3325
3505
|
device.queue.writeBuffer(batch.vertUniforms, 0, batch.vertStaging);
|
|
3326
3506
|
device.queue.writeBuffer(batch.fragUniforms, 0, batch.fragStaging);
|
|
3507
|
+
const group = spriteGroup ?? batch.bindGroup;
|
|
3508
|
+
if (group === null)
|
|
3509
|
+
return;
|
|
3327
3510
|
const layout = this.particleLayoutFor(batch.material).layout;
|
|
3328
3511
|
const particlePipe = particlePipeline(this.blendedPipelines(), device, layout, batch.material, batch.blend);
|
|
3329
3512
|
const particleCommand = this.recordBlended(0);
|
|
3330
3513
|
if (particleCommand !== null) {
|
|
3331
3514
|
particleCommand.pipeline = particlePipe;
|
|
3332
|
-
particleCommand.bindGroup =
|
|
3515
|
+
particleCommand.bindGroup = group;
|
|
3333
3516
|
particleCommand.vertexBuffers[0] = batch.corners;
|
|
3334
|
-
particleCommand.vertexBuffers[1] =
|
|
3517
|
+
particleCommand.vertexBuffers[1] = instances;
|
|
3335
3518
|
particleCommand.vertexCount = 2;
|
|
3336
3519
|
particleCommand.indexBuffer = batch.indexBuffer;
|
|
3337
3520
|
particleCommand.indexed = true;
|
|
@@ -3343,13 +3526,100 @@ export class WebGPURenderer {
|
|
|
3343
3526
|
if (pass === null)
|
|
3344
3527
|
return;
|
|
3345
3528
|
pass.setPipeline(particlePipe);
|
|
3346
|
-
pass.setBindGroup(0,
|
|
3529
|
+
pass.setBindGroup(0, group);
|
|
3347
3530
|
pass.setVertexBuffer(0, batch.corners);
|
|
3348
|
-
pass.setVertexBuffer(1,
|
|
3531
|
+
pass.setVertexBuffer(1, instances);
|
|
3349
3532
|
pass.setIndexBuffer(batch.indexBuffer, 'uint32');
|
|
3350
3533
|
pass.drawIndexed(PARTICLE_INDICES, live);
|
|
3351
3534
|
}
|
|
3352
3535
|
}
|
|
3536
|
+
/**
|
|
3537
|
+
* A sprite's flipbook and fades into its fragment block `f`, and the bind group holding the
|
|
3538
|
+
* frame's depth copy its soft edge reads — built again only when that copy is replaced.
|
|
3539
|
+
*
|
|
3540
|
+
* **The copy is the volume's**: `takeVolumeDepth` ends the frame's pass, resolves the opaque
|
|
3541
|
+
* depth and reopens, once a frame whoever asks. Only in the frame's own picture: in a mirror or
|
|
3542
|
+
* a probe's face it would be another view's depth, and the edge stays hard there, as on WebGL2.
|
|
3543
|
+
* Without a composite there is no copy, and the renderer says so once.
|
|
3544
|
+
*/
|
|
3545
|
+
spriteFades(batch, camera, f) {
|
|
3546
|
+
const fields = batch.fields;
|
|
3547
|
+
const atF = (name) => (fields[name]?.offset ?? -4) / 4;
|
|
3548
|
+
let depth = null;
|
|
3549
|
+
if (batch.softDepth > 0 &&
|
|
3550
|
+
!this.reflectionPassActive &&
|
|
3551
|
+
!this.probePassActive &&
|
|
3552
|
+
!this.capturePassActive) {
|
|
3553
|
+
if (this.openPass() !== null && this.takeVolumeDepth())
|
|
3554
|
+
depth = this.resolvedDepthView;
|
|
3555
|
+
if (depth === null && !this.warnedSpriteDepth) {
|
|
3556
|
+
this.warnedSpriteDepth = true;
|
|
3557
|
+
console.warn("WebGPU: a 'sprite' asked for a soft edge, which reads a copy of the frame's depth, and " +
|
|
3558
|
+
'this profile keeps none — screenEffects is off. The edge is hard.');
|
|
3559
|
+
}
|
|
3560
|
+
}
|
|
3561
|
+
f[atF('uCells')] = batch.cells[0];
|
|
3562
|
+
f[atF('uCells') + 1] = batch.cells[1];
|
|
3563
|
+
f[atF('uCells') + 2] = batch.blendCells ? 1 : 0;
|
|
3564
|
+
f[atF('uFade')] = batch.softDepth;
|
|
3565
|
+
f[atF('uFade') + 1] = batch.cameraFade;
|
|
3566
|
+
f[atF('uFade') + 2] = depth === null ? 0 : 1;
|
|
3567
|
+
mat4.invert(this.spriteInverseProjection, camera.projection);
|
|
3568
|
+
const inverse = this.spriteInverseProjection;
|
|
3569
|
+
f[atF('uDepthToViewZ')] = inverse[10] ?? 0;
|
|
3570
|
+
f[atF('uDepthToViewZ') + 1] = inverse[14] ?? 0;
|
|
3571
|
+
f[atF('uDepthToViewZ') + 2] = inverse[11] ?? 0;
|
|
3572
|
+
f[atF('uDepthToViewZ') + 3] = inverse[15] ?? 1;
|
|
3573
|
+
/*
|
|
3574
|
+
* The pixels a fragment's own coordinate counts in: the output's where blended draws land
|
|
3575
|
+
* after reconstruction, the scene's otherwise. The copy is sampled by the fraction, so its own
|
|
3576
|
+
* size does not matter. Read off the canvas rather than `sceneSize`, which builds an object.
|
|
3577
|
+
*/
|
|
3578
|
+
const { canvas } = this.surface;
|
|
3579
|
+
const ratio = this.drawsLate() || !this.reconstructionWanted ? 1 : this.quality.reconstruction;
|
|
3580
|
+
/* `reconRenderSize`'s own rounding — up, from the floored output — without its object. */
|
|
3581
|
+
const width = Math.max(1, Math.floor(canvas.width));
|
|
3582
|
+
const height = Math.max(1, Math.floor(canvas.height));
|
|
3583
|
+
f[atF('uViewport')] = ratio > 1 ? Math.max(1, Math.ceil(width / ratio)) : width;
|
|
3584
|
+
f[atF('uViewport') + 1] = ratio > 1 ? Math.max(1, Math.ceil(height / ratio)) : height;
|
|
3585
|
+
/* Something single-sampled and unfilterable stands in where there is no copy to read. */
|
|
3586
|
+
const bound = depth ?? this.spriteDepthStandIn();
|
|
3587
|
+
if (batch.spriteGroup === null || batch.spriteGroupDepth !== bound) {
|
|
3588
|
+
batch.spriteGroup = createSpriteBindGroup(this.surface.device, this.particleLayoutFor('sprite').layout, batch, this.particleLayoutFor('sprite').fragSize, bound, this.spriteSampler(true), this.spriteSampler(false));
|
|
3589
|
+
batch.spriteGroupDepth = bound;
|
|
3590
|
+
}
|
|
3591
|
+
return batch.spriteGroup;
|
|
3592
|
+
}
|
|
3593
|
+
/** A sprite's image sampler, repeating nothing and filtered, or its depth's, nearest. */
|
|
3594
|
+
spriteSampler(linear) {
|
|
3595
|
+
const device = this.surface.device;
|
|
3596
|
+
if (linear) {
|
|
3597
|
+
this.spriteLinearSampler ??= device.createSampler({
|
|
3598
|
+
label: 'particle.sprite',
|
|
3599
|
+
magFilter: 'linear',
|
|
3600
|
+
minFilter: 'linear',
|
|
3601
|
+
mipmapFilter: 'linear',
|
|
3602
|
+
addressModeU: 'clamp-to-edge',
|
|
3603
|
+
addressModeV: 'clamp-to-edge',
|
|
3604
|
+
});
|
|
3605
|
+
return this.spriteLinearSampler;
|
|
3606
|
+
}
|
|
3607
|
+
this.spriteNearestSampler ??= device.createSampler({ label: 'particle.depth' });
|
|
3608
|
+
return this.spriteNearestSampler;
|
|
3609
|
+
}
|
|
3610
|
+
/** A one-texel `r32float` for a sprite's depth binding where no copy was taken. */
|
|
3611
|
+
spriteDepthStandIn() {
|
|
3612
|
+
if (this.spriteDepthEmpty === null) {
|
|
3613
|
+
const texture = this.surface.device.createTexture({
|
|
3614
|
+
label: 'particle.depthStandIn',
|
|
3615
|
+
size: [1, 1],
|
|
3616
|
+
format: 'r32float',
|
|
3617
|
+
usage: 0x04 | 0x02,
|
|
3618
|
+
});
|
|
3619
|
+
this.spriteDepthEmpty = texture.createView();
|
|
3620
|
+
}
|
|
3621
|
+
return this.spriteDepthEmpty;
|
|
3622
|
+
}
|
|
3353
3623
|
/**
|
|
3354
3624
|
* The bind group layout for one particle material, built on first use.
|
|
3355
3625
|
*
|
|
@@ -3367,7 +3637,7 @@ export class WebGPURenderer {
|
|
|
3367
3637
|
const device = this.surface.device;
|
|
3368
3638
|
const frag = particleFragBindings(material);
|
|
3369
3639
|
const built = {
|
|
3370
|
-
layout: createParticleBindGroupLayout(device, frag.size),
|
|
3640
|
+
layout: createParticleBindGroupLayout(device, frag.size, material === 'sprite'),
|
|
3371
3641
|
fields: frag.fields,
|
|
3372
3642
|
fragSize: frag.size,
|
|
3373
3643
|
};
|
|
@@ -3572,6 +3842,7 @@ export class WebGPURenderer {
|
|
|
3572
3842
|
scatterInto(f, WATER_FRAG_FIELDS['uLightRadius'], lights.radii, 1);
|
|
3573
3843
|
scatterInto(f, WATER_FRAG_FIELDS['uLightSourceRadius'], lights.sourceRadii, 1);
|
|
3574
3844
|
scatterInto(f, WATER_FRAG_FIELDS['uLightWeight'], lights.weights, 1);
|
|
3845
|
+
scatterInto(f, WATER_FRAG_FIELDS['uLightCone'], lights.conesAndFalloffs, 4);
|
|
3575
3846
|
const medium = resolveAtmosphere(env, camera.position[1] ?? 0, this.quality.underwaterAtmosphere, this.medium);
|
|
3576
3847
|
f.set(medium.fogColor, atF('uFogColor'));
|
|
3577
3848
|
f[atF('uFogDensity')] = medium.fogDensity;
|
|
@@ -4363,8 +4634,12 @@ export class WebGPURenderer {
|
|
|
4363
4634
|
iesAxes: new Float32Array(0),
|
|
4364
4635
|
cookies: new Float32Array(0),
|
|
4365
4636
|
heldSourceRadii: new Float32Array(MAX_POINT_LIGHTS),
|
|
4637
|
+
axesAndChannels: new Float32Array(MAX_POINT_LIGHTS * 4),
|
|
4638
|
+
conesAndFalloffs: new Float32Array(MAX_POINT_LIGHTS * 4),
|
|
4366
4639
|
heldWeights: new Float32Array(MAX_POINT_LIGHTS),
|
|
4367
4640
|
};
|
|
4641
|
+
/** The fixed arm's rectangles as `uAreaLightSize` holds them. See `packAreaSizesAndDoors`. */
|
|
4642
|
+
areaSizesAndDoors = new Float32Array(MAX_AREA_LIGHTS * 4);
|
|
4368
4643
|
/** Refilled per frame rather than allocated; see `resolveAtmosphere`. */
|
|
4369
4644
|
medium = {
|
|
4370
4645
|
fogColor: new Float32Array(3),
|
|
@@ -5034,6 +5309,8 @@ export class WebGPURenderer {
|
|
|
5034
5309
|
this.materialBudget.limit(quality.materialChangesPerFrame);
|
|
5035
5310
|
this.drawBudget.limit(quality.drawsPerFrame);
|
|
5036
5311
|
this.shadowDrawBudget.limit(quality.drawsPerFrame);
|
|
5312
|
+
/* A palette per draw at most, which is the most a frame can bind. See `SkinPaletteRing`. */
|
|
5313
|
+
this.skinPalettes = new SkinPaletteRing(quality.drawsPerFrame);
|
|
5037
5314
|
this.maxDrawingBufferPixels = quality.maxDrawingBufferPixels;
|
|
5038
5315
|
this.exposure = quality.outputExposure;
|
|
5039
5316
|
this.flushSchedule =
|
|
@@ -6324,7 +6601,9 @@ export class WebGPURenderer {
|
|
|
6324
6601
|
keyed.key = fullKey;
|
|
6325
6602
|
return keyed;
|
|
6326
6603
|
}
|
|
6327
|
-
createMesh(
|
|
6604
|
+
createMesh(given, options = {}) {
|
|
6605
|
+
/* The streams it is drawn with, before anything records which it has. See `withLightmapUvs`. */
|
|
6606
|
+
const data = withLightmapUvs(given);
|
|
6328
6607
|
const fullKey = this.warmFlatPipelines(data, options.dynamic === true);
|
|
6329
6608
|
return this.keyed(createGpuMesh(this.surface.device, data, options.dynamic === true), fullKey);
|
|
6330
6609
|
}
|
|
@@ -6337,7 +6616,8 @@ export class WebGPURenderer {
|
|
|
6337
6616
|
* straddle one, and going on writing to a dead device would raise validation errors a consumer
|
|
6338
6617
|
* can do nothing about. Done with `complete` still false is the signal that it was abandoned.
|
|
6339
6618
|
*/
|
|
6340
|
-
createMeshIncremental(
|
|
6619
|
+
createMeshIncremental(given, options = {}) {
|
|
6620
|
+
const data = withLightmapUvs(given);
|
|
6341
6621
|
const fullKey = this.warmFlatPipelines(data, options.dynamic === true);
|
|
6342
6622
|
const { mesh, upload } = createGpuMeshIncremental(this.surface.device, data, options.dynamic === true);
|
|
6343
6623
|
const surface = this.surface;
|
|
@@ -6398,6 +6678,15 @@ export class WebGPURenderer {
|
|
|
6398
6678
|
bindMeshPass(camera, env) {
|
|
6399
6679
|
if (this.surface.lost)
|
|
6400
6680
|
return;
|
|
6681
|
+
/* Rectangles past the fixed arm's are the froxel table's, and without it nobody shades them. */
|
|
6682
|
+
if (!this.warnedAreaOverflow &&
|
|
6683
|
+
!this.quality.clusteredLights &&
|
|
6684
|
+
(env.areaLights?.count ?? 0) > this.shadedAreaLights) {
|
|
6685
|
+
this.warnedAreaOverflow = true;
|
|
6686
|
+
console.warn(`WebGPU: ${env.areaLights?.count ?? 0} rectangular lights and ${this.shadedAreaLights} ` +
|
|
6687
|
+
'shaded without clusteredLights: the rest are not drawn. Turn clusteredLights on, and the ' +
|
|
6688
|
+
'froxel table shades every one past the fixed ones, unshadowed.');
|
|
6689
|
+
}
|
|
6401
6690
|
/* Corrected once a frame, not once a draw: every draw copies the same matrix. */
|
|
6402
6691
|
mat4.multiply(this.correctedViewProj, CLIP_CORRECTION, camera.viewProjection);
|
|
6403
6692
|
/*
|
|
@@ -6406,7 +6695,7 @@ export class WebGPURenderer {
|
|
|
6406
6695
|
* the viewer sees, and bounding it through a mirrored camera puts it in the wrong half of the
|
|
6407
6696
|
* screen.
|
|
6408
6697
|
*/
|
|
6409
|
-
if (!this.reflectionPassActive) {
|
|
6698
|
+
if (!this.reflectionPassActive && !this.capturePassActive) {
|
|
6410
6699
|
mat4.copy(this.frameRawViewProj, camera.viewProjection);
|
|
6411
6700
|
/* The eye in world space, which the resolve's normals are turned to face. */
|
|
6412
6701
|
this.reconEye.set(camera.position);
|
|
@@ -6440,7 +6729,8 @@ export class WebGPURenderer {
|
|
|
6440
6729
|
* second temporal frame stepped the sequence twice a frame, so the world saw every other
|
|
6441
6730
|
* position and the anti-flicker's period never started.
|
|
6442
6731
|
*/
|
|
6443
|
-
|
|
6732
|
+
/* A capture has no resolve to undo a jitter and is no frame of the sequence. */
|
|
6733
|
+
if (this.framePresented || this.capturePassActive)
|
|
6444
6734
|
this.viewProj = this.correctedViewProj;
|
|
6445
6735
|
else
|
|
6446
6736
|
this.jitterFrame();
|
|
@@ -6561,16 +6851,18 @@ export class WebGPURenderer {
|
|
|
6561
6851
|
* switched grain off. That is the same failure the missing properties had — silence that
|
|
6562
6852
|
* reads as a setting rather than as an omission.
|
|
6563
6853
|
*
|
|
6564
|
-
* `
|
|
6854
|
+
* `uRelief.y` is 60 because `renderer.ts` says 60. Nothing in either file explains the
|
|
6565
6855
|
* number, so this is a copy and is marked as one; the two must move together.
|
|
6566
6856
|
*/
|
|
6567
6857
|
f[at('uGrain')] = 1;
|
|
6568
|
-
f[at('
|
|
6858
|
+
f[at('uEnvironmentDials') + 1] = 0;
|
|
6569
6859
|
/* No weighting and no dither until a pass or a crossfade asks. See `setDitherFade`. */
|
|
6570
6860
|
f[at('uWriteMode')] = 0;
|
|
6571
6861
|
f[at('uWriteMode') + 1] = 0;
|
|
6572
6862
|
/* 1, not 0: a multiplier's identity is one. See `setEnvironmentGain`. */
|
|
6573
|
-
f[at('
|
|
6863
|
+
f[at('uEnvironmentDials') + 2] = 1;
|
|
6864
|
+
/* The frame's own ambient until a draw asks for its own. See `setAmbientSH`. */
|
|
6865
|
+
f.fill(0, at('uAmbientSH'), at('uAmbientSH') + AMBIENT_SH_FLOATS);
|
|
6574
6866
|
/*
|
|
6575
6867
|
* **Only where the variant declares it.** The irradiance block lives inside the probe's own
|
|
6576
6868
|
* `#if`, because it exists to be filled from a cube only that permutation has, so `at` throws
|
|
@@ -6624,7 +6916,7 @@ export class WebGPURenderer {
|
|
|
6624
6916
|
f[base + 2] = sets[2] ?? 0;
|
|
6625
6917
|
}
|
|
6626
6918
|
/* Which chain is bound, because the level and the weight both depend on it. */
|
|
6627
|
-
f[at('
|
|
6919
|
+
f[at('uEnvironmentDials')] = this.quality.environmentPrefilter ? 1 : 0;
|
|
6628
6920
|
}
|
|
6629
6921
|
/* DriftLight, or zeros that switch it off: see `driftLight/uniforms.ts`. */
|
|
6630
6922
|
this.uploadLightField();
|
|
@@ -6650,10 +6942,10 @@ export class WebGPURenderer {
|
|
|
6650
6942
|
f[emissiveScale + 1] = 1;
|
|
6651
6943
|
f[emissiveScale + 2] = 1;
|
|
6652
6944
|
f[at('uRelief')] = 0;
|
|
6653
|
-
f[at('
|
|
6945
|
+
f[at('uRelief') + 1] = 60;
|
|
6654
6946
|
f[at('uTextureRelief')] = 0;
|
|
6655
|
-
f[at('
|
|
6656
|
-
f[at('
|
|
6947
|
+
f[at('uEmission')] = env.emissiveGain;
|
|
6948
|
+
f[at('uEmission') + 1] = env.nightFactor;
|
|
6657
6949
|
/*
|
|
6658
6950
|
* **Only where the variant declares it**, because `at` throws on a name the permutation does
|
|
6659
6951
|
* not hold and this term is compiled in by `nightEmissive` alone.
|
|
@@ -6671,7 +6963,8 @@ export class WebGPURenderer {
|
|
|
6671
6963
|
f[at('uNightEmissive')] = env.nightEmissive ?? 0;
|
|
6672
6964
|
f.set(env.highlightMin, at('uHighlightMin'));
|
|
6673
6965
|
f.set(env.highlightMax, at('uHighlightMax'));
|
|
6674
|
-
|
|
6966
|
+
/* The gain in the far corner's w. See `uHighlightMax`. */
|
|
6967
|
+
f[at('uHighlightMax') + 3] = env.highlightGain;
|
|
6675
6968
|
/*
|
|
6676
6969
|
* No grading here. `renderer.ts` grades in this pass only when there is no composite to do
|
|
6677
6970
|
* it in; this backend has no composite yet, and adding one is its own row. Exposure is 1
|
|
@@ -6765,13 +7058,16 @@ export class WebGPURenderer {
|
|
|
6765
7058
|
* uniform added on one backend is bound on both in the same change.
|
|
6766
7059
|
*/
|
|
6767
7060
|
scatterInto(f, this.fragment.fields['uLightDir'], lights.directions, 3);
|
|
6768
|
-
|
|
7061
|
+
/* The cone and, in z, the light's own falloff exponent. See `conesAndFalloffs`. */
|
|
7062
|
+
scatterInto(f, this.fragment.fields['uLightCone'], lights.conesAndFalloffs, 4);
|
|
6769
7063
|
scatterInto(f, this.fragment.fields['uLightIesProfile'], lights.iesProfiles, 1);
|
|
6770
|
-
|
|
7064
|
+
/* The azimuth and, in w, the light's channels. See `axesAndChannels`. */
|
|
7065
|
+
scatterInto(f, this.fragment.fields['uLightIesAxis'], lights.axesAndChannels, 4);
|
|
6771
7066
|
scatterInto(f, this.fragment.fields['uLightCookie'], lights.cookies, 1);
|
|
6772
|
-
|
|
6773
|
-
f[at('
|
|
6774
|
-
f[at('
|
|
7067
|
+
/* The fixture atlas's shape, three numbers in one row: profile rows, planes, cookie tiles. */
|
|
7068
|
+
f[at('uFixtureShape')] = this.iesRows;
|
|
7069
|
+
f[at('uFixtureShape') + 1] = this.iesPlanes;
|
|
7070
|
+
f[at('uFixtureShape') + 2] = this.cookieTiles;
|
|
6775
7071
|
/*
|
|
6776
7072
|
* The rectangular emitters. Bound on both backends in the same change, per the 2026-08-13
|
|
6777
7073
|
* rule's fourth clause — and the count is what makes the whole thing free to a scene with
|
|
@@ -6784,7 +7080,9 @@ export class WebGPURenderer {
|
|
|
6784
7080
|
scatterInto(f, this.fragment.fields['uAreaLightColor'], area.colors, 3);
|
|
6785
7081
|
scatterInto(f, this.fragment.fields['uAreaLightRight'], area.right, 3);
|
|
6786
7082
|
scatterInto(f, this.fragment.fields['uAreaLightUp'], area.up, 3);
|
|
6787
|
-
|
|
7083
|
+
/* The half extents and, in zw, the barn doors. See `packAreaSizesAndDoors`. */
|
|
7084
|
+
packAreaSizesAndDoors(area, this.areaSizesAndDoors);
|
|
7085
|
+
scatterInto(f, this.fragment.fields['uAreaLightSize'], this.areaSizesAndDoors, 4);
|
|
6788
7086
|
scatterInto(f, this.fragment.fields['uAreaLightTwoSided'], area.twoSided, 1);
|
|
6789
7087
|
}
|
|
6790
7088
|
/*
|
|
@@ -6916,8 +7214,20 @@ export class WebGPURenderer {
|
|
|
6916
7214
|
* same terms here, which is why a pass cannot inherit a material from the one before it.
|
|
6917
7215
|
*/
|
|
6918
7216
|
this.materials.dirty();
|
|
6919
|
-
/*
|
|
7217
|
+
/*
|
|
7218
|
+
* A pass starts with no material, exactly as `renderer.ts` resets these terms — **every map,
|
|
7219
|
+
* not the albedo alone**. `setMaterial` skips the lookup when the maps it is given are the ones
|
|
7220
|
+
* held, so a pass that kept the last pass's normal, ORM, emissive and model maps here while
|
|
7221
|
+
* binding the blank group made the next `setMaterial` naming those same maps a no-op: the draw
|
|
7222
|
+
* kept the blank group and read the stand-ins while its flags said the maps were bound. A scene
|
|
7223
|
+
* whose first material in a pass was the last of the pass before — any scene of one material —
|
|
7224
|
+
* lost its maps from the second frame on, on this backend alone. Until 4.8.6.
|
|
7225
|
+
*/
|
|
6920
7226
|
this.albedo = null;
|
|
7227
|
+
this.normalMap = null;
|
|
7228
|
+
this.ormMap = null;
|
|
7229
|
+
this.emissiveMap = null;
|
|
7230
|
+
this.modelMap = null;
|
|
6921
7231
|
this.bindGroup = this.blankAlbedoBindGroup;
|
|
6922
7232
|
this.uvScale[0] = 1;
|
|
6923
7233
|
this.uvScale[1] = 1;
|
|
@@ -7548,6 +7858,7 @@ export class WebGPURenderer {
|
|
|
7548
7858
|
submitInstanced(batch, data, opacity, blend, options) {
|
|
7549
7859
|
if (!this.canDraw() || this.viewProj === null)
|
|
7550
7860
|
return;
|
|
7861
|
+
warnRegionsWithoutPage(this.materialModel, data.lightmapRegions);
|
|
7551
7862
|
const mesh = batch.mesh;
|
|
7552
7863
|
/* Nor a batch of a mesh with nothing in it: see `GpuMesh.indexCount`. */
|
|
7553
7864
|
if (!mesh.complete || mesh.indexCount === 0)
|
|
@@ -9453,7 +9764,7 @@ export class WebGPURenderer {
|
|
|
9453
9764
|
drawsLate() {
|
|
9454
9765
|
return (this.latePass !== null &&
|
|
9455
9766
|
this.oitEncoder === null &&
|
|
9456
|
-
routesLate(this.reconstructing, this.reflectionPassActive, this.probePassActive, this.framePresented));
|
|
9767
|
+
routesLate(this.reconstructing, this.reflectionPassActive, this.probePassActive || this.capturePassActive, this.framePresented));
|
|
9457
9768
|
}
|
|
9458
9769
|
/**
|
|
9459
9770
|
* The command a blended draw records into: the late pool while reconstructing, the frame graph
|
|
@@ -9515,7 +9826,7 @@ export class WebGPURenderer {
|
|
|
9515
9826
|
}
|
|
9516
9827
|
/** Whether this draw lands in the reconstructed picture. See `lateRouting.ts`. */
|
|
9517
9828
|
tracksMotionNow() {
|
|
9518
|
-
return tracksMotion(this.reconstructing, this.reflectionPassActive, this.probePassActive, this.framePresented);
|
|
9829
|
+
return tracksMotion(this.reconstructing, this.reflectionPassActive, this.probePassActive || this.capturePassActive, this.framePresented);
|
|
9519
9830
|
}
|
|
9520
9831
|
/**
|
|
9521
9832
|
* What this draw's motion is, recorded for the motion pass.
|
|
@@ -9916,6 +10227,8 @@ export class WebGPURenderer {
|
|
|
9916
10227
|
f.set(surface.axis, base + at('uSsrAxis'));
|
|
9917
10228
|
f.set(surface.tint, base + at('uSsrTint'));
|
|
9918
10229
|
f[base + at('uSsrStrength')] = surface.strength;
|
|
10230
|
+
f[base + at('uSsrFresnel')] = surface.fresnel ? 1 : 0;
|
|
10231
|
+
f[base + at('uSsrFresnel') + 1] = surface.roughness;
|
|
9919
10232
|
f[base + at('uSsrFacingCos')] = surface.facingCos;
|
|
9920
10233
|
f[base + at('uSsrReach')] = surface.reachM;
|
|
9921
10234
|
f[base + at('uSsrThickness')] = surface.thicknessM;
|
|
@@ -10362,9 +10675,9 @@ export class WebGPURenderer {
|
|
|
10362
10675
|
*
|
|
10363
10676
|
* `load` rather than `clear` wherever there is a blend, and that is the other half of what
|
|
10364
10677
|
* makes the sum of the octaves a sum: a cleared attachment discards the level the downsample
|
|
10365
|
-
* wrote and adds the octave to nothing. See `
|
|
10678
|
+
* wrote and adds the octave to nothing. See `TINTED_ADD_BLEND`.
|
|
10366
10679
|
*/
|
|
10367
|
-
const stage = (label, key, fragment, target, source, slot, blend) => {
|
|
10680
|
+
const stage = (label, key, fragment, target, source, slot, blend, constant) => {
|
|
10368
10681
|
const group = this.bloomGroups[source];
|
|
10369
10682
|
if (group === undefined)
|
|
10370
10683
|
return;
|
|
@@ -10382,6 +10695,8 @@ export class WebGPURenderer {
|
|
|
10382
10695
|
});
|
|
10383
10696
|
pass.setPipeline(postPipeline(this.pipelines, this.surface.device, this.bloomLayout, key, fragment, format, blend));
|
|
10384
10697
|
pass.setBindGroup(0, group, [slot * BLOOM_SLOT]);
|
|
10698
|
+
if (constant !== undefined)
|
|
10699
|
+
pass.setBlendConstant(constant);
|
|
10385
10700
|
pass.draw(3);
|
|
10386
10701
|
pass.end();
|
|
10387
10702
|
};
|
|
@@ -10395,13 +10710,22 @@ export class WebGPURenderer {
|
|
|
10395
10710
|
break;
|
|
10396
10711
|
stage('post.bloomDown', 'post.bloomDownsample', BLOOM_DOWNSAMPLE_FRAG_WGSL, target, index, index);
|
|
10397
10712
|
}
|
|
10398
|
-
/*
|
|
10399
|
-
|
|
10713
|
+
/*
|
|
10714
|
+
* And back up. Every draw reads the same tent radius, so they share one slot rather than
|
|
10715
|
+
* holding five copies of one constant — but for the first, which multiplies the deepest level
|
|
10716
|
+
* by its tint. Each level being added onto takes its own tint from the blend constant.
|
|
10717
|
+
*/
|
|
10718
|
+
const response = this.bloomResponse;
|
|
10400
10719
|
for (let index = levels.length - 1; index > 0; index--) {
|
|
10401
10720
|
const target = levels[index - 1];
|
|
10402
|
-
|
|
10721
|
+
const constant = this.bloomConstants[index - 1];
|
|
10722
|
+
if (target === undefined || constant === undefined)
|
|
10403
10723
|
break;
|
|
10404
|
-
|
|
10724
|
+
const into = 1 + (index - 1) * 3;
|
|
10725
|
+
constant[0] = response[into] ?? 1;
|
|
10726
|
+
constant[1] = response[into + 1] ?? 1;
|
|
10727
|
+
constant[2] = response[into + 2] ?? 1;
|
|
10728
|
+
stage('post.bloomUp', 'post.bloomUpsample', BLOOM_UPSAMPLE_FRAG_WGSL, target, index + 1, index === levels.length - 1 ? BLOOM_UPSAMPLE_DEEPEST_SLOT : BLOOM_UPSAMPLE_SLOT, TINTED_ADD_BLEND, constant);
|
|
10405
10729
|
}
|
|
10406
10730
|
}
|
|
10407
10731
|
/**
|
|
@@ -10465,7 +10789,7 @@ export class WebGPURenderer {
|
|
|
10465
10789
|
* glass, and a frame that refracts nothing breaks no pass at all.
|
|
10466
10790
|
*/
|
|
10467
10791
|
takeRefractSnapshot() {
|
|
10468
|
-
if (this.reflectionPassActive || this.probePassActive)
|
|
10792
|
+
if (this.reflectionPassActive || this.probePassActive || this.capturePassActive)
|
|
10469
10793
|
return false;
|
|
10470
10794
|
if (this.refractSnapshotTaken)
|
|
10471
10795
|
return true;
|
|
@@ -10548,14 +10872,16 @@ export class WebGPURenderer {
|
|
|
10548
10872
|
!this.skinScattered &&
|
|
10549
10873
|
!this.reflectionPassActive &&
|
|
10550
10874
|
!this.probePassActive &&
|
|
10875
|
+
!this.capturePassActive &&
|
|
10551
10876
|
!this.framePresented &&
|
|
10552
10877
|
!this.overlayActive &&
|
|
10553
10878
|
this.oitMode === 'none');
|
|
10554
10879
|
}
|
|
10555
10880
|
/**
|
|
10556
|
-
* A skin draw's diffuse
|
|
10557
|
-
* buffers and bind group, through the
|
|
10558
|
-
*
|
|
10881
|
+
* A skin draw's diffuse and colour halves, kept for the spread: the frame's draw again, twice,
|
|
10882
|
+
* with the same slots, buffers and bind group, through the pipelines that write the diffuse's
|
|
10883
|
+
* light alone and its colour alone into the blur's targets and test for the depth its frame half
|
|
10884
|
+
* wrote.
|
|
10559
10885
|
*/
|
|
10560
10886
|
keepSkinDiffuse(mesh, frameKey, base, skinned, morphed, deltas, layer, eight, clothed, slot, material) {
|
|
10561
10887
|
const skin = this.skinScatter;
|
|
@@ -10563,24 +10889,28 @@ export class WebGPURenderer {
|
|
|
10563
10889
|
const present = this.meshPresent.get(base);
|
|
10564
10890
|
if (skin === null || pipelines === null || present === undefined)
|
|
10565
10891
|
return;
|
|
10566
|
-
const
|
|
10567
|
-
|
|
10568
|
-
|
|
10569
|
-
|
|
10570
|
-
|
|
10571
|
-
|
|
10572
|
-
|
|
10573
|
-
|
|
10574
|
-
|
|
10575
|
-
|
|
10576
|
-
command.
|
|
10577
|
-
|
|
10578
|
-
|
|
10579
|
-
|
|
10580
|
-
|
|
10581
|
-
|
|
10582
|
-
|
|
10583
|
-
|
|
10892
|
+
const bindGroup = this.flatBindGroupFor(skinned, morphed, deltas);
|
|
10893
|
+
/* The light alone, which the blur spreads, then the colour alone, which it is multiplied by. */
|
|
10894
|
+
for (const half of SKIN_KEPT_HALVES) {
|
|
10895
|
+
const key = `${frameKey}|${half === 'diffuse' ? 'sd' : 'sa'}`;
|
|
10896
|
+
const pipeline = pipelines.peek(key) ??
|
|
10897
|
+
flatPipeline(pipelines, this.surface.device, this.flatLayoutFor(skinned, morphed), this.variant, key, present, false, skinned, morphed, true, layer, false, 'none', this.materialDoubleSided, false, eight, clothed, 'skin', half);
|
|
10898
|
+
const command = half === 'diffuse' ? skin.take() : skin.takeAlbedo();
|
|
10899
|
+
command.pipeline = pipeline;
|
|
10900
|
+
command.bindGroup = bindGroup;
|
|
10901
|
+
command.offsetA = slot;
|
|
10902
|
+
command.offsetB = material;
|
|
10903
|
+
command.offsetCount = 2;
|
|
10904
|
+
for (let index = 0; index < mesh.vertexBuffers.length; index++) {
|
|
10905
|
+
command.vertexBuffers[index] = mesh.vertexBuffers[index];
|
|
10906
|
+
}
|
|
10907
|
+
command.vertexCount = mesh.vertexBuffers.length;
|
|
10908
|
+
command.indexBuffer = mesh.indexBuffer;
|
|
10909
|
+
command.indexed = true;
|
|
10910
|
+
command.count = mesh.indexCount;
|
|
10911
|
+
command.instances = 1;
|
|
10912
|
+
command.indirect = null;
|
|
10913
|
+
}
|
|
10584
10914
|
}
|
|
10585
10915
|
/**
|
|
10586
10916
|
* Spread the frame's skin and add it back: once, at the first blended draw or before the frame's
|
|
@@ -10600,6 +10930,8 @@ export class WebGPURenderer {
|
|
|
10600
10930
|
return;
|
|
10601
10931
|
if (this.reflectionPassActive || this.probePassActive || this.oitReplaying)
|
|
10602
10932
|
return;
|
|
10933
|
+
if (this.capturePassActive)
|
|
10934
|
+
return;
|
|
10603
10935
|
const encoder = this.encoder;
|
|
10604
10936
|
const depth = this.depthView;
|
|
10605
10937
|
const resolved = this.resolvedDepthView;
|
|
@@ -10657,7 +10989,7 @@ export class WebGPURenderer {
|
|
|
10657
10989
|
* own terms: `resolvedDepthView` holds the *frame's* depth, and clamping a mirrored beam
|
|
10658
10990
|
* against it would stop it at geometry that is not in front of it.
|
|
10659
10991
|
*/
|
|
10660
|
-
if (this.reflectionPassActive)
|
|
10992
|
+
if (this.reflectionPassActive || this.capturePassActive)
|
|
10661
10993
|
return false;
|
|
10662
10994
|
if (this.resolvedDepthView === null)
|
|
10663
10995
|
return false;
|
|
@@ -11214,8 +11546,9 @@ export class WebGPURenderer {
|
|
|
11214
11546
|
* any future branch added to one belongs in the other.
|
|
11215
11547
|
*/
|
|
11216
11548
|
targetPipelines() {
|
|
11217
|
-
if (this.probePassActive || this.reflectionPassActive)
|
|
11549
|
+
if (this.probePassActive || this.reflectionPassActive || this.capturePassActive) {
|
|
11218
11550
|
return this.pipelines;
|
|
11551
|
+
}
|
|
11219
11552
|
return this.overlayActive || this.framePresented ? this.overlayPipelines : this.pipelines;
|
|
11220
11553
|
}
|
|
11221
11554
|
/** End and submit a reopened pass. Safe to call when there is none. */
|
|
@@ -11963,10 +12296,11 @@ export class WebGPURenderer {
|
|
|
11963
12296
|
f[frustumAt + 2] = tanHalfFovY;
|
|
11964
12297
|
f[frustumAt + 3] = aspect;
|
|
11965
12298
|
f.set(camera.view, at('uView'));
|
|
11966
|
-
|
|
12299
|
+
/* The rectangles past the fixed arm's go into the table. See `ClusterLightSet.areas`. */
|
|
12300
|
+
const set = fillClusterLightSet(env, this.clusterLights, this.shadedAreaLights);
|
|
11967
12301
|
/* A slot is read only under `POINT_SHADOWS`; see `lightHasFixture`. */
|
|
11968
12302
|
const shadowSlots = this.quality.pointShadows ? MAX_POINT_LIGHTS : 0;
|
|
11969
|
-
i[at('uClustered')] = clusteredMode(set, set
|
|
12303
|
+
i[at('uClustered')] = clusteredMode(set, clusteredLightTotal(set), shadowSlots);
|
|
11970
12304
|
binner.setFrame({
|
|
11971
12305
|
lights: set,
|
|
11972
12306
|
view: camera.view,
|
|
@@ -12281,6 +12615,16 @@ export class WebGPURenderer {
|
|
|
12281
12615
|
* -----------------------------------------------------------------------------------------
|
|
12282
12616
|
*/
|
|
12283
12617
|
bloomScale = 1;
|
|
12618
|
+
/** The draw's own ambient, as `packAmbientSH` writes it; zeros are the frame's. */
|
|
12619
|
+
ambientSH = new Float32Array(AMBIENT_SH_FLOATS);
|
|
12620
|
+
/** The ramp and each level's tint, as `resolveBloomResponse` writes them; the subtraction and white. */
|
|
12621
|
+
bloomResponse = defaultBloomResponse();
|
|
12622
|
+
/** The response before the last `setBloom`, to tell whether the blocks need writing again. */
|
|
12623
|
+
bloomResponseNext = defaultBloomResponse();
|
|
12624
|
+
/** Each level's tint as a blend constant, rewritten in place: `setBlendConstant` takes a colour. */
|
|
12625
|
+
bloomConstants = Array.from({ length: BLOOM_LEVELS }, () => [
|
|
12626
|
+
1, 1, 1, 1,
|
|
12627
|
+
]);
|
|
12284
12628
|
/** In scene units, once `setBloom` has moved it; null reads the profile's. */
|
|
12285
12629
|
bloomThresholdSet = null;
|
|
12286
12630
|
rushStrength = 0;
|
|
@@ -12411,16 +12755,23 @@ export class WebGPURenderer {
|
|
|
12411
12755
|
this.gradeView = this.gradeTexture.createView({ dimension: '3d' });
|
|
12412
12756
|
this.gradeSize = lut.size;
|
|
12413
12757
|
}
|
|
12414
|
-
setBloom(scale, threshold) {
|
|
12758
|
+
setBloom(scale, threshold, response) {
|
|
12415
12759
|
this.bloomScale = Math.min(Math.max(scale, 0), 1);
|
|
12416
|
-
|
|
12417
|
-
|
|
12418
|
-
|
|
12419
|
-
|
|
12420
|
-
|
|
12421
|
-
|
|
12422
|
-
|
|
12423
|
-
|
|
12760
|
+
let moved = false;
|
|
12761
|
+
if (threshold !== undefined) {
|
|
12762
|
+
const next = bloomThresholdOf(threshold);
|
|
12763
|
+
moved = next !== this.bloomThresholdSet;
|
|
12764
|
+
this.bloomThresholdSet = next;
|
|
12765
|
+
}
|
|
12766
|
+
if (response !== undefined) {
|
|
12767
|
+
this.bloomResponseNext.set(this.bloomResponse);
|
|
12768
|
+
resolveBloomResponse(response, this.bloomResponse);
|
|
12769
|
+
for (let i = 0; i < this.bloomResponse.length && !moved; i++) {
|
|
12770
|
+
moved = this.bloomResponse[i] !== this.bloomResponseNext[i];
|
|
12771
|
+
}
|
|
12772
|
+
}
|
|
12773
|
+
/* The blocks are written once per size, so a moved threshold or response rewrites them now. */
|
|
12774
|
+
if (moved && this.bloomLevels.length > 0)
|
|
12424
12775
|
this.writeBloomBlocks();
|
|
12425
12776
|
}
|
|
12426
12777
|
/**
|
|
@@ -13152,6 +13503,43 @@ export class WebGPURenderer {
|
|
|
13152
13503
|
if (!this.setProbeGrid({ origin: WORLD_ORIGIN, spacing: UNIT_STEP, counts: SINGLE_PROBE })) {
|
|
13153
13504
|
return false;
|
|
13154
13505
|
}
|
|
13506
|
+
this.uploadProbeFaces(probe, image);
|
|
13507
|
+
const encoder = device.createCommandEncoder({ label: 'probe.environmentImage' });
|
|
13508
|
+
this.buildProbeChain(encoder, probe, 0);
|
|
13509
|
+
device.queue.submit([encoder.finish()]);
|
|
13510
|
+
this.markProbeFilled(0);
|
|
13511
|
+
this.probeAmbient = options?.irradiance ?? true;
|
|
13512
|
+
this.rebuildFlatBindGroup();
|
|
13513
|
+
return true;
|
|
13514
|
+
}
|
|
13515
|
+
/** One layer of the declared grid from an image. See `Renderer.setProbeLayerImage`. */
|
|
13516
|
+
setProbeLayerImage(layer, image, options) {
|
|
13517
|
+
if (this.surface.lost)
|
|
13518
|
+
return false;
|
|
13519
|
+
const probe = this.probe;
|
|
13520
|
+
const grid = this.probes;
|
|
13521
|
+
if (probe === null || grid === null || this.probeArray === null) {
|
|
13522
|
+
console.warn('WebGpuRenderer: setProbeLayerImage needs a reflection probe (`reflectionProbeSize`) and ' +
|
|
13523
|
+
'a grid declared with setProbeGrid; the image was ignored.');
|
|
13524
|
+
return false;
|
|
13525
|
+
}
|
|
13526
|
+
if (!Number.isInteger(layer) || layer < 0 || layer >= grid.layers) {
|
|
13527
|
+
console.warn(`WebGpuRenderer: setProbeLayerImage was given layer ${layer}, and the grid has ${grid.layers}.`);
|
|
13528
|
+
return false;
|
|
13529
|
+
}
|
|
13530
|
+
this.uploadProbeFaces(probe, image);
|
|
13531
|
+
const { device } = this.surface;
|
|
13532
|
+
const encoder = device.createCommandEncoder({ label: 'probe.layerImage' });
|
|
13533
|
+
this.buildProbeChain(encoder, probe, this.probeSweeps?.writeLayer(layer) ?? layer);
|
|
13534
|
+
device.queue.submit([encoder.finish()]);
|
|
13535
|
+
this.markProbeFilled(layer);
|
|
13536
|
+
this.probeAmbient = options?.irradiance ?? true;
|
|
13537
|
+
this.rebuildFlatBindGroup();
|
|
13538
|
+
return true;
|
|
13539
|
+
}
|
|
13540
|
+
/** An equirectangular image onto the capture cube's six faces, in the cube's own format. */
|
|
13541
|
+
uploadProbeFaces(probe, image) {
|
|
13542
|
+
const { device } = this.surface;
|
|
13155
13543
|
const faces = equirectToCubeFaces(image, this.probeSize);
|
|
13156
13544
|
const format = this.pipelines.format;
|
|
13157
13545
|
for (let face = 0; face < faces.length; face++) {
|
|
@@ -13178,13 +13566,6 @@ export class WebGPURenderer {
|
|
|
13178
13566
|
const converted = environmentTexels(pixels, format);
|
|
13179
13567
|
device.queue.writeTexture({ texture: probe, mipLevel: 0, origin: { x: 0, y: 0, z: face } }, converted.data, { bytesPerRow: this.probeSize * converted.bytesPerTexel, rowsPerImage: this.probeSize }, { width: this.probeSize, height: this.probeSize, depthOrArrayLayers: 1 });
|
|
13180
13568
|
}
|
|
13181
|
-
const encoder = device.createCommandEncoder({ label: 'probe.environmentImage' });
|
|
13182
|
-
this.buildProbeChain(encoder, probe, 0);
|
|
13183
|
-
device.queue.submit([encoder.finish()]);
|
|
13184
|
-
this.markProbeFilled(0);
|
|
13185
|
-
this.probeAmbient = options?.irradiance ?? true;
|
|
13186
|
-
this.rebuildFlatBindGroup();
|
|
13187
|
-
return true;
|
|
13188
13569
|
}
|
|
13189
13570
|
/**
|
|
13190
13571
|
* Upload the photometric profiles a consumer loaded, and rebuild the bind group that holds them.
|
|
@@ -14029,6 +14410,15 @@ export class WebGPURenderer {
|
|
|
14029
14410
|
}
|
|
14030
14411
|
const up = (name) => this.postField(BLOOM_UPSAMPLE_FIELDS, name);
|
|
14031
14412
|
f[slot(BLOOM_UPSAMPLE_SLOT) + up('uRadius')] = BLOOM_FILTER_RADIUS_UV;
|
|
14413
|
+
f[slot(BLOOM_UPSAMPLE_DEEPEST_SLOT) + up('uRadius')] = BLOOM_FILTER_RADIUS_UV;
|
|
14414
|
+
/* White on every draw up but the first, which reads the deepest level and carries its tint. */
|
|
14415
|
+
const response = this.bloomResponse;
|
|
14416
|
+
const deepest = 1 + (levels.length - 1) * 3;
|
|
14417
|
+
for (let c = 0; c < 3; c++) {
|
|
14418
|
+
f[slot(BLOOM_UPSAMPLE_SLOT) + up('uScale') + c] = 1;
|
|
14419
|
+
f[slot(BLOOM_UPSAMPLE_DEEPEST_SLOT) + up('uScale') + c] = response[deepest + c] ?? 1;
|
|
14420
|
+
}
|
|
14421
|
+
f[slot(0) + prefilter('uRamp')] = response[0] ?? 0;
|
|
14032
14422
|
this.surface.device.queue.writeBuffer(this.bloomUniforms, 0, this.bloomStaging);
|
|
14033
14423
|
}
|
|
14034
14424
|
ensureReflection(width, height) {
|