@zephyr3d/scene 0.9.25 → 0.9.27
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/dist/animation/animationset.js +92 -30
- package/dist/animation/animationset.js.map +1 -1
- package/dist/animation/hair/gpu_hair_simulation.js +1090 -0
- package/dist/animation/hair/gpu_hair_simulation.js.map +1 -0
- package/dist/animation/joint_dynamics/controller.js +7 -5
- package/dist/animation/joint_dynamics/controller.js.map +1 -1
- package/dist/animation/joint_dynamics/convex_collider.js +320 -0
- package/dist/animation/joint_dynamics/convex_collider.js.map +1 -0
- package/dist/animation/joint_dynamics/joint_dynamics_system.js +81 -5
- package/dist/animation/joint_dynamics/joint_dynamics_system.js.map +1 -1
- package/dist/animation/morphtrack.js +3 -4
- package/dist/animation/morphtrack.js.map +1 -1
- package/dist/animation/skeleton.js +79 -25
- package/dist/animation/skeleton.js.map +1 -1
- package/dist/animation/spring/multi_chain_spring_system.js +577 -179
- package/dist/animation/spring/multi_chain_spring_system.js.map +1 -1
- package/dist/animation/spring/spring_collider.js +4 -3
- package/dist/animation/spring/spring_collider.js.map +1 -1
- package/dist/animation/spring/spring_node_pose_tracker.js +40 -0
- package/dist/animation/spring/spring_node_pose_tracker.js.map +1 -0
- package/dist/animation/spring/spring_particle.js +2 -0
- package/dist/animation/spring/spring_particle.js.map +1 -1
- package/dist/animation/spring/spring_system.js +11 -3
- package/dist/animation/spring/spring_system.js.map +1 -1
- package/dist/animation/spring_modifier.js +10 -0
- package/dist/animation/spring_modifier.js.map +1 -1
- package/dist/app/engine.js +53 -43
- package/dist/app/engine.js.map +1 -1
- package/dist/app/scriptingsystem.js +119 -6
- package/dist/app/scriptingsystem.js.map +1 -1
- package/dist/app/scriptregistry.js +3 -2
- package/dist/app/scriptregistry.js.map +1 -1
- package/dist/asset/assetmanager.js +284 -67
- package/dist/asset/assetmanager.js.map +1 -1
- package/dist/asset/loaders/zhair/zhair_format.js +288 -0
- package/dist/asset/loaders/zhair/zhair_format.js.map +1 -0
- package/dist/asset/loaders/zhair/zhair_loader.js +221 -0
- package/dist/asset/loaders/zhair/zhair_loader.js.map +1 -0
- package/dist/asset/model.js +184 -69
- package/dist/asset/model.js.map +1 -1
- package/dist/camera/camera.js +268 -44
- package/dist/camera/camera.js.map +1 -1
- package/dist/camera/perspectivecamera.js +3 -1
- package/dist/camera/perspectivecamera.js.map +1 -1
- package/dist/index.d.ts +9044 -5139
- package/dist/index.js +17 -6
- package/dist/index.js.map +1 -1
- package/dist/material/eye.js +932 -0
- package/dist/material/eye.js.map +1 -0
- package/dist/material/hair.js +917 -0
- package/dist/material/hair.js.map +1 -0
- package/dist/material/hairstrand.js +842 -0
- package/dist/material/hairstrand.js.map +1 -0
- package/dist/material/hairstrand_data.js +162 -0
- package/dist/material/hairstrand_data.js.map +1 -0
- package/dist/material/lambert.js +5 -2
- package/dist/material/lambert.js.map +1 -1
- package/dist/material/material.js +48 -10
- package/dist/material/material.js.map +1 -1
- package/dist/material/meshmaterial.js +120 -19
- package/dist/material/meshmaterial.js.map +1 -1
- package/dist/material/mixins/albedocolor.js +1 -1
- package/dist/material/mixins/albedocolor.js.map +1 -1
- package/dist/material/mixins/lightmodel/blinnphong.js +5 -3
- package/dist/material/mixins/lightmodel/blinnphong.js.map +1 -1
- package/dist/material/mixins/lightmodel/lambert.js +5 -4
- package/dist/material/mixins/lightmodel/lambert.js.map +1 -1
- package/dist/material/mixins/lightmodel/pbrblueprintmixin.js +3 -3
- package/dist/material/mixins/lightmodel/pbrblueprintmixin.js.map +1 -1
- package/dist/material/mixins/lightmodel/pbrmetallicroughness.js +5 -5
- package/dist/material/mixins/lightmodel/pbrmetallicroughness.js.map +1 -1
- package/dist/material/mixins/lightmodel/pbrspecularglossness.js +3 -3
- package/dist/material/mixins/lightmodel/pbrspecularglossness.js.map +1 -1
- package/dist/material/mixins/lit.js +106 -19
- package/dist/material/mixins/lit.js.map +1 -1
- package/dist/material/mixins/pbr/common.js +4 -4
- package/dist/material/mixins/pbr/common.js.map +1 -1
- package/dist/material/mixins/texture.js +48 -11
- package/dist/material/mixins/texture.js.map +1 -1
- package/dist/material/mtoon.js +6 -5
- package/dist/material/mtoon.js.map +1 -1
- package/dist/material/pbrblueprint.js +146 -28
- package/dist/material/pbrblueprint.js.map +1 -1
- package/dist/material/pbrblueprint_instance.js +501 -0
- package/dist/material/pbrblueprint_instance.js.map +1 -0
- package/dist/material/pbrmr.js +15 -16
- package/dist/material/pbrmr.js.map +1 -1
- package/dist/material/shader/helper.js +734 -23
- package/dist/material/shader/helper.js.map +1 -1
- package/dist/material/shader/terrain_blend.js +55 -0
- package/dist/material/shader/terrain_blend.js.map +1 -0
- package/dist/material/skin.js +192 -32
- package/dist/material/skin.js.map +1 -1
- package/dist/material/spriteblueprint.js +16 -1
- package/dist/material/spriteblueprint.js.map +1 -1
- package/dist/material/subsurfaceprofile.js +86 -23
- package/dist/material/subsurfaceprofile.js.map +1 -1
- package/dist/material/water.js +2059 -72
- package/dist/material/water.js.map +1 -1
- package/dist/posteffect/bloom.js +167 -7
- package/dist/posteffect/bloom.js.map +1 -1
- package/dist/posteffect/coloradjust.js +2 -1
- package/dist/posteffect/coloradjust.js.map +1 -1
- package/dist/posteffect/compositor.js.map +1 -1
- package/dist/posteffect/fxaa.js +2 -1
- package/dist/posteffect/fxaa.js.map +1 -1
- package/dist/posteffect/grayscale.js +3 -1
- package/dist/posteffect/grayscale.js.map +1 -1
- package/dist/posteffect/motionblur.js +664 -56
- package/dist/posteffect/motionblur.js.map +1 -1
- package/dist/posteffect/posteffect.js +12 -1
- package/dist/posteffect/posteffect.js.map +1 -1
- package/dist/posteffect/sao.js +46 -44
- package/dist/posteffect/sao.js.map +1 -1
- package/dist/posteffect/skinsss.js +368 -69
- package/dist/posteffect/skinsss.js.map +1 -1
- package/dist/posteffect/ssgi.js +16 -9
- package/dist/posteffect/ssgi.js.map +1 -1
- package/dist/posteffect/ssr.js +3 -0
- package/dist/posteffect/ssr.js.map +1 -1
- package/dist/posteffect/sss.js +121 -36
- package/dist/posteffect/sss.js.map +1 -1
- package/dist/posteffect/taa.js.map +1 -1
- package/dist/posteffect/tonemap.js +44 -1
- package/dist/posteffect/tonemap.js.map +1 -1
- package/dist/render/abuffer_oit.js +8 -2
- package/dist/render/abuffer_oit.js.map +1 -1
- package/dist/render/clipmap.js +156 -5
- package/dist/render/clipmap.js.map +1 -1
- package/dist/render/cull_visitor.js +20 -0
- package/dist/render/cull_visitor.js.map +1 -1
- package/dist/render/depthpass.js +36 -2
- package/dist/render/depthpass.js.map +1 -1
- package/dist/render/drawable_mixin.js +26 -1
- package/dist/render/drawable_mixin.js.map +1 -1
- package/dist/render/dualdepthpeeling_oit.js +3 -2
- package/dist/render/dualdepthpeeling_oit.js.map +1 -1
- package/dist/render/envlight.js +1 -1
- package/dist/render/envlight.js.map +1 -1
- package/dist/render/fbm_wavegenerator.js +6 -3
- package/dist/render/fbm_wavegenerator.js.map +1 -1
- package/dist/render/fft_wavegenerator.js +57 -15
- package/dist/render/fft_wavegenerator.js.map +1 -1
- package/dist/render/gerstner_wavegenerator.js +130 -20
- package/dist/render/gerstner_wavegenerator.js.map +1 -1
- package/dist/render/globalbindgroup_allocator.js +3 -0
- package/dist/render/globalbindgroup_allocator.js.map +1 -1
- package/dist/render/hzb.js +80 -22
- package/dist/render/hzb.js.map +1 -1
- package/dist/render/lightpass.js +6 -3
- package/dist/render/lightpass.js.map +1 -1
- package/dist/render/oit.js +29 -0
- package/dist/render/oit.js.map +1 -0
- package/dist/render/render_queue.js +39 -0
- package/dist/render/render_queue.js.map +1 -1
- package/dist/render/renderer.js +4 -0
- package/dist/render/renderer.js.map +1 -1
- package/dist/render/rendergraph/blackboard.js +1 -0
- package/dist/render/rendergraph/blackboard.js.map +1 -1
- package/dist/render/rendergraph/device_pool_allocator.js.map +1 -1
- package/dist/render/rendergraph/executor.js +2 -0
- package/dist/render/rendergraph/executor.js.map +1 -1
- package/dist/render/rendergraph/forward_plus_builder.js +379 -10
- package/dist/render/rendergraph/forward_plus_builder.js.map +1 -1
- package/dist/render/rendergraph/frame_resource_requirements.js +1 -0
- package/dist/render/rendergraph/frame_resource_requirements.js.map +1 -1
- package/dist/render/rendergraph/history_resources.js +8 -1
- package/dist/render/rendergraph/history_resources.js.map +1 -1
- package/dist/render/rendergraph/rendergraph.js.map +1 -1
- package/dist/render/rendergraph/texture_affinity_cache.js +1 -1
- package/dist/render/rendergraph/texture_affinity_cache.js.map +1 -1
- package/dist/render/rendergraph/types.js.map +1 -1
- package/dist/render/shadow_mask_pass.js +73 -8
- package/dist/render/shadow_mask_pass.js.map +1 -1
- package/dist/render/sky.js +166 -126
- package/dist/render/sky.js.map +1 -1
- package/dist/render/underwater.js +624 -0
- package/dist/render/underwater.js.map +1 -0
- package/dist/render/water_caustics.js +1380 -0
- package/dist/render/water_caustics.js.map +1 -0
- package/dist/render/weightedblended_oit.js +3 -2
- package/dist/render/weightedblended_oit.js.map +1 -1
- package/dist/scene/basesprite.js +8 -0
- package/dist/scene/basesprite.js.map +1 -1
- package/dist/scene/batchgroup.js +3 -0
- package/dist/scene/batchgroup.js.map +1 -1
- package/dist/scene/environment.js +4 -1
- package/dist/scene/environment.js.map +1 -1
- package/dist/scene/hair.js +963 -0
- package/dist/scene/hair.js.map +1 -0
- package/dist/scene/light.js +5 -1
- package/dist/scene/light.js.map +1 -1
- package/dist/scene/mesh.js +481 -47
- package/dist/scene/mesh.js.map +1 -1
- package/dist/scene/meshdrawable.js +6 -0
- package/dist/scene/meshdrawable.js.map +1 -1
- package/dist/scene/msdftext.js +3 -0
- package/dist/scene/msdftext.js.map +1 -1
- package/dist/scene/msdftextsprite.js +3 -0
- package/dist/scene/msdftextsprite.js.map +1 -1
- package/dist/scene/particlesys.js +6 -0
- package/dist/scene/particlesys.js.map +1 -1
- package/dist/scene/scene.js +3 -0
- package/dist/scene/scene.js.map +1 -1
- package/dist/scene/scene_node.js +6 -1
- package/dist/scene/scene_node.js.map +1 -1
- package/dist/scene/terrain-cm/grass.js +3 -0
- package/dist/scene/terrain-cm/grass.js.map +1 -1
- package/dist/scene/terrain-cm/grassmaterial.js +3 -0
- package/dist/scene/terrain-cm/grassmaterial.js.map +1 -1
- package/dist/scene/terrain-cm/terrain-cm.js +6 -0
- package/dist/scene/terrain-cm/terrain-cm.js.map +1 -1
- package/dist/scene/terrain-cm/texture_cache.js +309 -0
- package/dist/scene/terrain-cm/texture_cache.js.map +1 -0
- package/dist/scene/water.js +546 -4
- package/dist/scene/water.js.map +1 -1
- package/dist/shaders/atmosphere.js.map +1 -1
- package/dist/shaders/diffusion.js +54 -0
- package/dist/shaders/diffusion.js.map +1 -0
- package/dist/shaders/shadow.js +33 -21
- package/dist/shaders/shadow.js.map +1 -1
- package/dist/shaders/temporal.js +71 -33
- package/dist/shaders/temporal.js.map +1 -1
- package/dist/shaders/water.js +22 -19
- package/dist/shaders/water.js.map +1 -1
- package/dist/shaders/water_medium.js +56 -0
- package/dist/shaders/water_medium.js.map +1 -0
- package/dist/shadow/dom.js +546 -0
- package/dist/shadow/dom.js.map +1 -0
- package/dist/shadow/pcf_opt.js +3 -5
- package/dist/shadow/pcf_opt.js.map +1 -1
- package/dist/shadow/pcf_pd.js +1 -1
- package/dist/shadow/pcf_pd.js.map +1 -1
- package/dist/shadow/receiver_bias.js +41 -0
- package/dist/shadow/receiver_bias.js.map +1 -0
- package/dist/shadow/shader.js +20 -60
- package/dist/shadow/shader.js.map +1 -1
- package/dist/shadow/shadow_impl.js +89 -0
- package/dist/shadow/shadow_impl.js.map +1 -1
- package/dist/shadow/shadow_region.js +1 -1
- package/dist/shadow/shadow_region.js.map +1 -1
- package/dist/shadow/shadowmapper.js +150 -37
- package/dist/shadow/shadowmapper.js.map +1 -1
- package/dist/shapes/shape.js +11 -2
- package/dist/shapes/shape.js.map +1 -1
- package/dist/shapes/sphere.js +50 -9
- package/dist/shapes/sphere.js.map +1 -1
- package/dist/utility/blueprint/common/constants.js +112 -2
- package/dist/utility/blueprint/common/constants.js.map +1 -1
- package/dist/utility/blueprint/material/func.js +142 -49
- package/dist/utility/blueprint/material/func.js.map +1 -1
- package/dist/utility/blueprint/material/ir.js +490 -25
- package/dist/utility/blueprint/material/ir.js.map +1 -1
- package/dist/utility/blueprint/material/texture.js +898 -4
- package/dist/utility/blueprint/material/texture.js.map +1 -1
- package/dist/utility/serialization/manager.js +434 -101
- package/dist/utility/serialization/manager.js.map +1 -1
- package/dist/utility/serialization/scene/animation.js +401 -5
- package/dist/utility/serialization/scene/animation.js.map +1 -1
- package/dist/utility/serialization/scene/batch.js +4 -0
- package/dist/utility/serialization/scene/batch.js.map +1 -1
- package/dist/utility/serialization/scene/camera.js +186 -26
- package/dist/utility/serialization/scene/camera.js.map +1 -1
- package/dist/utility/serialization/scene/common.js +26 -5
- package/dist/utility/serialization/scene/common.js.map +1 -1
- package/dist/utility/serialization/scene/hair.js +1141 -0
- package/dist/utility/serialization/scene/hair.js.map +1 -0
- package/dist/utility/serialization/scene/light.js +84 -4
- package/dist/utility/serialization/scene/light.js.map +1 -1
- package/dist/utility/serialization/scene/material.js +1867 -166
- package/dist/utility/serialization/scene/material.js.map +1 -1
- package/dist/utility/serialization/scene/mesh.js +231 -14
- package/dist/utility/serialization/scene/mesh.js.map +1 -1
- package/dist/utility/serialization/scene/node.js +141 -7
- package/dist/utility/serialization/scene/node.js.map +1 -1
- package/dist/utility/serialization/scene/particle.js +4 -0
- package/dist/utility/serialization/scene/particle.js.map +1 -1
- package/dist/utility/serialization/scene/primitive.js +18 -0
- package/dist/utility/serialization/scene/primitive.js.map +1 -1
- package/dist/utility/serialization/scene/sprite.js +4 -0
- package/dist/utility/serialization/scene/sprite.js.map +1 -1
- package/dist/utility/serialization/scene/terrain.js +4 -0
- package/dist/utility/serialization/scene/terrain.js.map +1 -1
- package/dist/utility/serialization/scene/text.js +4 -0
- package/dist/utility/serialization/scene/text.js.map +1 -1
- package/dist/utility/serialization/scene/water.js +1105 -21
- package/dist/utility/serialization/scene/water.js.map +1 -1
- package/dist/utility/serialization/types.js.map +1 -1
- package/dist/utility/shprojector.js +9 -8
- package/dist/utility/shprojector.js.map +1 -1
- package/dist/values.js +49 -2
- package/dist/values.js.map +1 -1
- package/package.json +3 -3
- package/dist/render/rendergraph/frame_graph_context.js +0 -41
- package/dist/render/rendergraph/frame_graph_context.js.map +0 -1
- package/dist/shaders/ssgi.js +0 -63
- package/dist/shaders/ssgi.js.map +0 -1
- package/dist/shaders/ssgi_reprojection.js +0 -118
- package/dist/shaders/ssgi_reprojection.js.map +0 -1
|
@@ -1,17 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Vector4, DEPTH_CLEAR_VALUE, DEPTH_COMPARE_DEFAULT } from '@zephyr3d/base';
|
|
2
2
|
import { RenderQueue } from '../render_queue.js';
|
|
3
3
|
import { LightPass } from '../lightpass.js';
|
|
4
4
|
import { ShadowMapPass } from '../shadowmap_pass.js';
|
|
5
5
|
import { DepthPass } from '../depthpass.js';
|
|
6
6
|
import { ClusteredLight } from '../cluster_light.js';
|
|
7
7
|
import { ShadowMaskRenderer } from '../shadow_mask_pass.js';
|
|
8
|
-
import { buildHiZ } from '../hzb.js';
|
|
8
|
+
import { getHiZFormat, buildHiZ } from '../hzb.js';
|
|
9
9
|
import '../primitive.js';
|
|
10
10
|
import '@zephyr3d/device';
|
|
11
11
|
import { CopyBlitter } from '../../blitter/copy.js';
|
|
12
12
|
import { fetchSampler } from '../../utility/misc.js';
|
|
13
13
|
import { MaterialVaryingFlags } from '../../values.js';
|
|
14
|
-
import { ShaderHelper } from '../../material/shader/helper.js';
|
|
14
|
+
import { ShaderHelper, MAX_CAUSTIC_WATERS } from '../../material/shader/helper.js';
|
|
15
|
+
import { REFRACT_BLUR_MAX_LOD } from '../../material/water.js';
|
|
15
16
|
import { PostEffectLayer, AbstractPostEffect } from '../../posteffect/posteffect.js';
|
|
16
17
|
import { RenderGraph } from './rendergraph.js';
|
|
17
18
|
import { RenderGraphExecutor } from './executor.js';
|
|
@@ -31,12 +32,16 @@ import '../../shapes/torus.js';
|
|
|
31
32
|
import '../../shapes/plane.js';
|
|
32
33
|
import '../../shapes/sphere.js';
|
|
33
34
|
import '../../shapes/tetrahedron.js';
|
|
35
|
+
import { WaterCausticsRenderer } from '../water_caustics.js';
|
|
36
|
+
import { UnderwaterRenderer, selectUnderwaterSource } from '../underwater.js';
|
|
34
37
|
|
|
35
38
|
const _scenePass = new LightPass();
|
|
36
39
|
const _depthPass = new DepthPass();
|
|
37
40
|
const _shadowMapPass = new ShadowMapPass();
|
|
38
41
|
const _clusters = [];
|
|
39
42
|
const _shadowMaskRenderer = new ShadowMaskRenderer();
|
|
43
|
+
const _waterCausticsRenderer = new WaterCausticsRenderer();
|
|
44
|
+
const _underwaterRenderer = new UnderwaterRenderer();
|
|
40
45
|
const _devicePoolAllocator = new DevicePoolAllocator();
|
|
41
46
|
const _textureAffinityCaches = new WeakMap();
|
|
42
47
|
function getTextureAffinityCache(camera, device) {
|
|
@@ -210,6 +215,21 @@ function getSSSLightingTextureFormat(ctx, attachmentCount, surfaceAttachmentCoun
|
|
|
210
215
|
function getFullMipLevelCount(width, height) {
|
|
211
216
|
return Math.max(1, Math.floor(Math.log2(Math.max(1, width, height))) + 1);
|
|
212
217
|
}
|
|
218
|
+
/**
|
|
219
|
+
* Levels the scene colour copy is allocated with.
|
|
220
|
+
*
|
|
221
|
+
* Deep enough to serve the refraction blur, which clamps its own LOD, and no
|
|
222
|
+
* deeper: the coarsest levels of a full chain are averages of the whole frame,
|
|
223
|
+
* which is not a plausible answer for "what is behind this water", and every
|
|
224
|
+
* level costs bandwidth to generate on each grab. Capped against the full chain
|
|
225
|
+
* so a small render target stays legal.
|
|
226
|
+
*
|
|
227
|
+
* Derived from the water material's clamp rather than restated, so the two
|
|
228
|
+
* cannot drift: a level count one short of it would leave the sampler asking for
|
|
229
|
+
* a level that does not exist.
|
|
230
|
+
*/ function getSceneColorMipLevelCount(width, height) {
|
|
231
|
+
return Math.min(REFRACT_BLUR_MAX_LOD + 1, getFullMipLevelCount(width, height));
|
|
232
|
+
}
|
|
213
233
|
function hasSurfaceMRT(ctx) {
|
|
214
234
|
return !!(ctx.materialFlags & SURFACE_MRT_FLAGS);
|
|
215
235
|
}
|
|
@@ -244,10 +264,16 @@ function getLightPassColorAttachments(ctx, colorAttachment) {
|
|
|
244
264
|
const skinSSS = camera.skinSSS && renderQueueHasActiveSkinSSS(renderQueue);
|
|
245
265
|
const needSceneColor = renderQueue.needSceneColor();
|
|
246
266
|
const needSceneColorWithDepth = renderQueue.needSceneColorWithDepth();
|
|
267
|
+
// Requested by materials rather than by a post effect, so it is resolved here
|
|
268
|
+
// instead of in resolveFrameResourceRequirements. WebGL1 has no Hi-Z at all,
|
|
269
|
+
// and a material asking for one there would turn a missing feature into a
|
|
270
|
+
// thrown frame; the material is expected to shade without it.
|
|
271
|
+
const needHiZNearest = (renderQueue.needHiZNearest() || camera.HiZNearest) && deviceType !== 'webgl';
|
|
247
272
|
return {
|
|
248
273
|
depthPrepass: true,
|
|
249
274
|
motionVectors: false,
|
|
250
|
-
hiZ:
|
|
275
|
+
hiZ: needHiZNearest,
|
|
276
|
+
hiZNearest: needHiZNearest,
|
|
251
277
|
sceneNormal: false,
|
|
252
278
|
sceneRoughness: false,
|
|
253
279
|
shadowMask: false,
|
|
@@ -266,7 +292,10 @@ function getLightPassColorAttachments(ctx, colorAttachment) {
|
|
|
266
292
|
function resolveFrameResourceRequirements(ctx, options, requirements) {
|
|
267
293
|
const deviceType = ctx.device.type;
|
|
268
294
|
options.motionVectors ||= !!requirements.motionVector;
|
|
269
|
-
options.
|
|
295
|
+
options.hiZNearest ||= !!requirements.hiZNearest;
|
|
296
|
+
// The nearest channel lives on the same pyramid, so asking for it is asking
|
|
297
|
+
// for the pyramid.
|
|
298
|
+
options.hiZ ||= !!requirements.hiZ || options.hiZNearest;
|
|
270
299
|
options.sceneNormal ||= !!requirements.sceneNormal;
|
|
271
300
|
options.sceneRoughness ||= !!requirements.sceneRoughness;
|
|
272
301
|
options.shadowMask ||= !!requirements.shadowMask;
|
|
@@ -401,6 +430,152 @@ function validateProducedFrameResources(blackboard, options, renderQueue) {
|
|
|
401
430
|
});
|
|
402
431
|
}
|
|
403
432
|
};
|
|
433
|
+
/**
|
|
434
|
+
* Picks the water surface and light the caustic map is built from.
|
|
435
|
+
*
|
|
436
|
+
* Only one pair is handled per frame: the caustics ride on a single light's
|
|
437
|
+
* additive pass, and a second water body would need a second map. The largest
|
|
438
|
+
* visible water surface wins, which is the one a camera near several of them is
|
|
439
|
+
* most likely to be looking through.
|
|
440
|
+
* @internal
|
|
441
|
+
*/ function selectWaterCausticSource(ctx, renderQueue) {
|
|
442
|
+
if (ctx.device.type === 'webgl' || renderQueue.waters.length === 0) {
|
|
443
|
+
return null;
|
|
444
|
+
}
|
|
445
|
+
// The caustic light must own an additive shadow pass, because that is the pass
|
|
446
|
+
// the caustic uniforms are declared and bound on.
|
|
447
|
+
const candidate = renderQueue.sunLight ?? renderQueue.primaryDirectionalLight;
|
|
448
|
+
const light = candidate && candidate.castShadow && renderQueue.shadowedLights.indexOf(candidate) >= 0 ? candidate : null;
|
|
449
|
+
if (!light) {
|
|
450
|
+
return null;
|
|
451
|
+
}
|
|
452
|
+
const waters = renderQueue.waters.filter((water)=>WaterCausticsRenderer.canRender(water, light));
|
|
453
|
+
if (waters.length === 0) {
|
|
454
|
+
return null;
|
|
455
|
+
}
|
|
456
|
+
// Largest first, which decides both what survives the cap and which body's
|
|
457
|
+
// settings describe the shared map. Area is a stand-in for "the one the viewer
|
|
458
|
+
// is most likely to be over"; a camera-distance sort would swap the map's
|
|
459
|
+
// whole configuration as the viewer walks between two pools, and swapping it
|
|
460
|
+
// discards the temporal history every time.
|
|
461
|
+
waters.sort((a, b)=>{
|
|
462
|
+
const ra = a.material.region;
|
|
463
|
+
const rb = b.material.region;
|
|
464
|
+
const areaA = Math.max(0, ra.z - ra.x) * Math.max(0, ra.w - ra.y);
|
|
465
|
+
const areaB = Math.max(0, rb.z - rb.x) * Math.max(0, rb.w - rb.y);
|
|
466
|
+
return areaB - areaA;
|
|
467
|
+
});
|
|
468
|
+
return {
|
|
469
|
+
waters: waters.slice(0, MAX_CAUSTIC_WATERS),
|
|
470
|
+
light
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
/** @internal */ const WaterCausticsModule = {
|
|
474
|
+
type: 'WaterCaustics',
|
|
475
|
+
writes: [
|
|
476
|
+
FrameResources.WaterCaustics
|
|
477
|
+
],
|
|
478
|
+
prepare: ({ ctx, renderQueue })=>({
|
|
479
|
+
enabled: !!selectWaterCausticSource(ctx, renderQueue)
|
|
480
|
+
}),
|
|
481
|
+
setup (fg) {
|
|
482
|
+
const { graph, ctx, renderQueue, ordering, blackboard } = fg;
|
|
483
|
+
const source = selectWaterCausticSource(ctx, renderQueue);
|
|
484
|
+
// Map-wide settings come from the largest body; see selectWaterCausticSource.
|
|
485
|
+
const material = source.waters[0].material;
|
|
486
|
+
const size = material.causticsResolution;
|
|
487
|
+
const format = WaterCausticsRenderer.getMapFormat(ctx.device);
|
|
488
|
+
const history = fg.history;
|
|
489
|
+
const temporal = material.causticsTemporalStrength > 0;
|
|
490
|
+
const mapDesc = {
|
|
491
|
+
format,
|
|
492
|
+
sizeMode: 'absolute',
|
|
493
|
+
width: size,
|
|
494
|
+
height: size
|
|
495
|
+
};
|
|
496
|
+
const mapSize = {
|
|
497
|
+
width: size,
|
|
498
|
+
height: size
|
|
499
|
+
};
|
|
500
|
+
// Null on the first frame, after a resize, and whenever the map format
|
|
501
|
+
// changes; the renderer falls back to the current frame alone.
|
|
502
|
+
const previousHandle = temporal && history ? history.importPreviousIfCompatible(graph, RGHistoryResources.WATER_CAUSTICS, mapDesc, mapSize) : null;
|
|
503
|
+
const result = graph.addPass('WaterCaustics', (builder)=>{
|
|
504
|
+
// The wave textures the splat pass samples are updated by the scene, not by
|
|
505
|
+
// the graph, so chain after the passes that have already run rather than
|
|
506
|
+
// declaring a resource dependency on them.
|
|
507
|
+
ordering.chainInto(builder);
|
|
508
|
+
ordering.emit(builder, 'WaterCausticsDone');
|
|
509
|
+
// The map leaves the graph through the draw context rather than through a
|
|
510
|
+
// handle a later pass samples, so nothing would otherwise keep this alive.
|
|
511
|
+
builder.sideEffect();
|
|
512
|
+
// Fixed-size, unlike almost every other graph texture: the map covers a
|
|
513
|
+
// world-space footprint, not the screen.
|
|
514
|
+
const mapHandle = builder.createTexture({
|
|
515
|
+
format,
|
|
516
|
+
sizeMode: 'absolute',
|
|
517
|
+
width: size,
|
|
518
|
+
height: size,
|
|
519
|
+
label: 'waterCaustics',
|
|
520
|
+
allocationKey: 'ForwardPlus.WaterCaustics'
|
|
521
|
+
});
|
|
522
|
+
const scratchHandle = builder.createTexture({
|
|
523
|
+
format,
|
|
524
|
+
sizeMode: 'absolute',
|
|
525
|
+
width: size,
|
|
526
|
+
height: size,
|
|
527
|
+
label: 'waterCausticsScratch',
|
|
528
|
+
allocationKey: 'ForwardPlus.WaterCausticsScratch'
|
|
529
|
+
});
|
|
530
|
+
// The displaced surface height along each map ray, which the receiver
|
|
531
|
+
// gates its depth on. Same slice and size as the map so it shares the
|
|
532
|
+
// map's uv; four channels, one per water slot.
|
|
533
|
+
const heightHandle = builder.createTexture({
|
|
534
|
+
format: WaterCausticsRenderer.getHeightMapFormat(),
|
|
535
|
+
sizeMode: 'absolute',
|
|
536
|
+
width: size,
|
|
537
|
+
height: size,
|
|
538
|
+
label: 'waterCausticHeights',
|
|
539
|
+
allocationKey: 'ForwardPlus.WaterCausticHeights'
|
|
540
|
+
});
|
|
541
|
+
if (previousHandle) {
|
|
542
|
+
builder.read(previousHandle);
|
|
543
|
+
}
|
|
544
|
+
builder.setExecute((rgCtx)=>{
|
|
545
|
+
const map = rgCtx.getTexture(mapHandle);
|
|
546
|
+
const scratch = rgCtx.getTexture(scratchHandle);
|
|
547
|
+
const heights = rgCtx.getTexture(heightHandle);
|
|
548
|
+
const previous = previousHandle ? rgCtx.getTexture(previousHandle) : null;
|
|
549
|
+
const resolved = _waterCausticsRenderer.render(ctx, source.waters, source.light, map, scratch, heights, previous, (texture)=>rgCtx.createFramebuffer({
|
|
550
|
+
width: texture.width,
|
|
551
|
+
height: texture.height,
|
|
552
|
+
colorAttachments: texture,
|
|
553
|
+
depthAttachment: null
|
|
554
|
+
}));
|
|
555
|
+
if (temporal && history) {
|
|
556
|
+
// Commit whichever texture the renderer settled on, so the next frame
|
|
557
|
+
// reprojects the map that was actually shown rather than the
|
|
558
|
+
// pre-resolve one.
|
|
559
|
+
history.queueRetainedCommit(RGHistoryResources.WATER_CAUSTICS, mapDesc, mapSize, resolved);
|
|
560
|
+
}
|
|
561
|
+
// Publish only after the map exists: the light pass keys its shader
|
|
562
|
+
// variant on this flag and binds the texture in the same step.
|
|
563
|
+
ctx.waterCaustics = true;
|
|
564
|
+
ctx.waterCausticLight = source.light;
|
|
565
|
+
ctx.waterCausticTexture = resolved;
|
|
566
|
+
ctx.waterCausticHeightTexture = heights;
|
|
567
|
+
ctx.waterCausticUniforms = _waterCausticsRenderer.uniforms;
|
|
568
|
+
});
|
|
569
|
+
return {
|
|
570
|
+
mapHandle,
|
|
571
|
+
scratchHandle
|
|
572
|
+
};
|
|
573
|
+
});
|
|
574
|
+
// Which target holds the result depends on whether the resolve ran. Both are
|
|
575
|
+
// declared by this pass, so either is a valid handle for consumers to read.
|
|
576
|
+
blackboard.set(FrameResources.WaterCaustics, temporal && previousHandle ? result.scratchHandle : result.mapHandle);
|
|
577
|
+
}
|
|
578
|
+
};
|
|
404
579
|
/** @internal */ const DepthPrepassModule = {
|
|
405
580
|
type: 'DepthPrepass',
|
|
406
581
|
writes: [
|
|
@@ -462,6 +637,11 @@ function validateProducedFrameResources(blackboard, options, renderQueue) {
|
|
|
462
637
|
builder.addSubpass('SkyMotionVectors', (rgCtx)=>{
|
|
463
638
|
renderSkyMotionVectors(ctx, rgCtx, skyMotionVectorFramebufferHandle);
|
|
464
639
|
});
|
|
640
|
+
// After the sky, which writes at the far plane: a blended surface in
|
|
641
|
+
// front of empty background has to replace what the sky put there.
|
|
642
|
+
builder.addSubpass('TransparentMotionVectors', (rgCtx)=>{
|
|
643
|
+
renderTransparentMotionVectors(frame, rgCtx, skyMotionVectorFramebufferHandle);
|
|
644
|
+
});
|
|
465
645
|
}
|
|
466
646
|
return {
|
|
467
647
|
depthHandle,
|
|
@@ -575,7 +755,7 @@ function validateProducedFrameResources(blackboard, options, renderQueue) {
|
|
|
575
755
|
enabled: options.hiZ
|
|
576
756
|
}),
|
|
577
757
|
setup (fg) {
|
|
578
|
-
const { graph, ctx, frame, blackboard } = fg;
|
|
758
|
+
const { graph, ctx, frame, blackboard, options } = fg;
|
|
579
759
|
const depthPassResult = requireBuildState(fg, 'depth', 'DepthPrepass', 'HiZ');
|
|
580
760
|
const preLightTransmissionDepthToken = fg.state.preLightTransmissionDepthToken;
|
|
581
761
|
//const hiZWidth = nextPowerOf2(ctx.renderWidth);
|
|
@@ -588,11 +768,14 @@ function validateProducedFrameResources(blackboard, options, renderQueue) {
|
|
|
588
768
|
builder.read(preLightTransmissionDepthToken);
|
|
589
769
|
}
|
|
590
770
|
hiZHandle = builder.createTexture({
|
|
591
|
-
format:
|
|
771
|
+
format: getHiZFormat(options.hiZNearest),
|
|
592
772
|
label: 'hiZ',
|
|
593
773
|
sizeMode: 'backbuffer-relative',
|
|
594
774
|
mipLevels: getFullMipLevelCount(ctx.renderWidth, ctx.renderHeight),
|
|
595
|
-
|
|
775
|
+
// Keyed on the channel count: a one- and a two-channel pyramid are
|
|
776
|
+
// different textures, and pooling them under one key would hand a
|
|
777
|
+
// consumer a pyramid with no nearest channel in it.
|
|
778
|
+
allocationKey: `ForwardPlus.HiZ${options.hiZNearest ? '.MinMax' : ''}`
|
|
596
779
|
});
|
|
597
780
|
const hiZFramebufferHandle = builder.createFramebuffer({
|
|
598
781
|
label: 'HiZFramebuffer',
|
|
@@ -628,6 +811,7 @@ function validateProducedFrameResources(blackboard, options, renderQueue) {
|
|
|
628
811
|
const depthPassResult = requireBuildState(fg, 'depth', 'DepthPrepass', 'SSSProfile');
|
|
629
812
|
const preLightTransmissionDepthToken = fg.state.preLightTransmissionDepthToken;
|
|
630
813
|
const shadowMaskHandle = blackboard.get(FrameResources.ShadowMask);
|
|
814
|
+
const waterCausticsHandle = blackboard.get(FrameResources.WaterCaustics);
|
|
631
815
|
const renderDepthAttachment = fg.state.renderDepthAttachment;
|
|
632
816
|
const sssProfileResult = graph.addPass('SSSProfile', (builder)=>{
|
|
633
817
|
builder.read(blackboard.expect(FrameResources.LinearDepth));
|
|
@@ -638,6 +822,12 @@ function validateProducedFrameResources(blackboard, options, renderQueue) {
|
|
|
638
822
|
if (shadowMaskHandle) {
|
|
639
823
|
builder.read(shadowMaskHandle);
|
|
640
824
|
}
|
|
825
|
+
if (waterCausticsHandle) {
|
|
826
|
+
// Same reason as the shadow mask: the map reaches the shader through
|
|
827
|
+
// ctx.waterCausticTexture, so only a declared read stops the executor
|
|
828
|
+
// from recycling it while this pass is still sampling it.
|
|
829
|
+
builder.read(waterCausticsHandle);
|
|
830
|
+
}
|
|
641
831
|
const profileHandle = builder.createTexture({
|
|
642
832
|
format: 'rgba16f',
|
|
643
833
|
label: 'sssProfile',
|
|
@@ -686,6 +876,7 @@ function validateProducedFrameResources(blackboard, options, renderQueue) {
|
|
|
686
876
|
const depthPassResult = requireBuildState(fg, 'depth', 'DepthPrepass', 'SceneColorGrab');
|
|
687
877
|
const preLightTransmissionDepthToken = fg.state.preLightTransmissionDepthToken;
|
|
688
878
|
const shadowMaskHandle = blackboard.get(FrameResources.ShadowMask);
|
|
879
|
+
const waterCausticsHandle = blackboard.get(FrameResources.WaterCaustics);
|
|
689
880
|
const renderDepthAttachment = fg.state.renderDepthAttachment;
|
|
690
881
|
const grabResult = graph.addPass('SceneColorGrab', (builder)=>{
|
|
691
882
|
builder.read(blackboard.expect(FrameResources.LinearDepth));
|
|
@@ -696,9 +887,34 @@ function validateProducedFrameResources(blackboard, options, renderQueue) {
|
|
|
696
887
|
if (shadowMaskHandle) {
|
|
697
888
|
builder.read(shadowMaskHandle);
|
|
698
889
|
}
|
|
890
|
+
if (waterCausticsHandle) {
|
|
891
|
+
// Same reason as the shadow mask: the map reaches the shader through
|
|
892
|
+
// ctx.waterCausticTexture, so only a declared read stops the executor
|
|
893
|
+
// from recycling it while this pass is still sampling it.
|
|
894
|
+
builder.read(waterCausticsHandle);
|
|
895
|
+
}
|
|
896
|
+
// A few mip levels, because the refraction background is sampled at a LOD
|
|
897
|
+
// and not only at mip 0: water picks one from how deep and how turbid the
|
|
898
|
+
// column is, so a stone under clear shallows stays sharp while the same
|
|
899
|
+
// stone under metres of silt smears. With a single-level texture the
|
|
900
|
+
// sampler returned mip 0 whatever LOD was asked for, and the blur did
|
|
901
|
+
// nothing.
|
|
902
|
+
//
|
|
903
|
+
// Nothing here generates the chain - both backends do it when the
|
|
904
|
+
// framebuffer is unbound, for any colour attachment with more than one
|
|
905
|
+
// level (WebGL `framebuffer_webgl.ts` unbind, WebGPU `renderpass_webgpu.ts`
|
|
906
|
+
// end), and `generateMipmaps` defaults to true on colour attachments. The
|
|
907
|
+
// level count is the whole precondition.
|
|
908
|
+
//
|
|
909
|
+
// Not the full chain down to 1x1. The consumer clamps its LOD, and the
|
|
910
|
+
// coarsest levels of a full chain average across the whole frame - a
|
|
911
|
+
// sample from one stops being "what is behind the water" and becomes the
|
|
912
|
+
// mean of the image. Generating them would cost bandwidth every frame to
|
|
913
|
+
// produce levels nothing may read.
|
|
699
914
|
const copyHandle = builder.createTexture({
|
|
700
915
|
format: ctx.colorFormat,
|
|
701
916
|
label: 'sceneColorCopy',
|
|
917
|
+
mipLevels: getSceneColorMipLevelCount(ctx.renderWidth, ctx.renderHeight),
|
|
702
918
|
allocationKey: 'ForwardPlus.SceneColorCopy'
|
|
703
919
|
});
|
|
704
920
|
// Isolate depth when SSR inserts transmission depth before LightPass.
|
|
@@ -739,6 +955,7 @@ function validateProducedFrameResources(blackboard, options, renderQueue) {
|
|
|
739
955
|
const { graph, ctx, frame, blackboard, options, backbuffer } = fg;
|
|
740
956
|
const depthPassResult = requireBuildState(fg, 'depth', 'DepthPrepass', 'LightPass');
|
|
741
957
|
const shadowMaskHandle = blackboard.get(FrameResources.ShadowMask);
|
|
958
|
+
const waterCausticsHandle = blackboard.get(FrameResources.WaterCaustics);
|
|
742
959
|
const preLightTransmissionDepthToken = fg.state.preLightTransmissionDepthToken;
|
|
743
960
|
const hiZHandle = blackboard.get(FrameResources.HiZ);
|
|
744
961
|
const sssProfileHandle = blackboard.get(FrameResources.SSSProfile);
|
|
@@ -809,6 +1026,12 @@ function validateProducedFrameResources(blackboard, options, renderQueue) {
|
|
|
809
1026
|
if (shadowMaskHandle) {
|
|
810
1027
|
builder.read(shadowMaskHandle);
|
|
811
1028
|
}
|
|
1029
|
+
if (waterCausticsHandle) {
|
|
1030
|
+
// Same reason as the shadow mask: the map reaches the shader through
|
|
1031
|
+
// ctx.waterCausticTexture, so only a declared read stops the executor
|
|
1032
|
+
// from recycling it while this pass is still sampling it.
|
|
1033
|
+
builder.read(waterCausticsHandle);
|
|
1034
|
+
}
|
|
812
1035
|
if (preLightTransmissionDepthToken) {
|
|
813
1036
|
builder.read(preLightTransmissionDepthToken);
|
|
814
1037
|
}
|
|
@@ -996,6 +1219,68 @@ function validateProducedFrameResources(blackboard, options, renderQueue) {
|
|
|
996
1219
|
}
|
|
997
1220
|
}
|
|
998
1221
|
};
|
|
1222
|
+
/** @internal */ const UnderwaterModule = {
|
|
1223
|
+
type: 'Underwater',
|
|
1224
|
+
reads: [
|
|
1225
|
+
{
|
|
1226
|
+
resource: FrameResources.SceneColor,
|
|
1227
|
+
version: 'current'
|
|
1228
|
+
}
|
|
1229
|
+
],
|
|
1230
|
+
writes: [
|
|
1231
|
+
FrameResources.SceneColor
|
|
1232
|
+
],
|
|
1233
|
+
prepare: ({ ctx })=>({
|
|
1234
|
+
enabled: !!ctx.underwater
|
|
1235
|
+
}),
|
|
1236
|
+
setup (fg) {
|
|
1237
|
+
const { graph, ctx, frame, blackboard } = fg;
|
|
1238
|
+
const depthPassResult = requireBuildState(fg, 'depth', 'DepthPrepass', 'Underwater');
|
|
1239
|
+
const lightPassResult = requireBuildState(fg, 'lightPass', 'LightPass', 'Underwater');
|
|
1240
|
+
const sceneColorHandle = blackboard.expect(FrameResources.SceneColor);
|
|
1241
|
+
const waterCausticsHandle = blackboard.get(FrameResources.WaterCaustics);
|
|
1242
|
+
const state = ctx.underwater;
|
|
1243
|
+
// After the sky, before the transparent pass: the medium has to cover the
|
|
1244
|
+
// sky - that is what the horizon fades into underwater - and the opaque
|
|
1245
|
+
// scene is the only thing the depth buffer describes, so it is also the only
|
|
1246
|
+
// thing this can reach. Transparent geometry is on its own, exactly as it is
|
|
1247
|
+
// for atmospheric fog.
|
|
1248
|
+
const underwaterColorHandle = graph.addPass('Underwater', (builder)=>{
|
|
1249
|
+
builder.read(sceneColorHandle);
|
|
1250
|
+
builder.read(depthPassResult.depthFramebufferHandle);
|
|
1251
|
+
if (lightPassResult.sceneColorFramebufferHandle) {
|
|
1252
|
+
builder.read(lightPassResult.sceneColorFramebufferHandle);
|
|
1253
|
+
}
|
|
1254
|
+
if (waterCausticsHandle) {
|
|
1255
|
+
// The light shafts sample the caustic map, which reaches the shader
|
|
1256
|
+
// through ctx.waterCausticTexture rather than through a handle. Same
|
|
1257
|
+
// lifetime argument as the shadow mask in CompositeTail: only a declared
|
|
1258
|
+
// read stops the executor returning it to the pool after LightPass.
|
|
1259
|
+
builder.read(waterCausticsHandle);
|
|
1260
|
+
}
|
|
1261
|
+
const out = builder.write(sceneColorHandle);
|
|
1262
|
+
builder.setExecute((rgCtx)=>{
|
|
1263
|
+
const colorTexture = rgCtx.getTexture(sceneColorHandle);
|
|
1264
|
+
const depthTexture = frame.depthFramebuffer?.getDepthAttachment();
|
|
1265
|
+
if (!colorTexture || !depthTexture) {
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
// Color only. The scene depth is sampled by this pass, which it cannot
|
|
1269
|
+
// be while it is still attached to the bound framebuffer.
|
|
1270
|
+
const colorFramebuffer = rgCtx.createFramebuffer({
|
|
1271
|
+
width: colorTexture.width,
|
|
1272
|
+
height: colorTexture.height,
|
|
1273
|
+
colorAttachments: colorTexture,
|
|
1274
|
+
depthAttachment: null
|
|
1275
|
+
});
|
|
1276
|
+
_underwaterRenderer.render(ctx, state, colorFramebuffer, depthTexture);
|
|
1277
|
+
});
|
|
1278
|
+
return out;
|
|
1279
|
+
});
|
|
1280
|
+
lightPassResult.sceneColorHandle = underwaterColorHandle;
|
|
1281
|
+
blackboard.set(FrameResources.SceneColor, underwaterColorHandle);
|
|
1282
|
+
}
|
|
1283
|
+
};
|
|
999
1284
|
/** @internal */ const CompositeTailModule = {
|
|
1000
1285
|
type: 'CompositeTail',
|
|
1001
1286
|
writes: [
|
|
@@ -1011,6 +1296,8 @@ function validateProducedFrameResources(blackboard, options, renderQueue) {
|
|
|
1011
1296
|
const depthPassResult = requireBuildState(fg, 'depth', 'DepthPrepass', 'CompositeTail');
|
|
1012
1297
|
const hiZHandle = blackboard.get(FrameResources.HiZ);
|
|
1013
1298
|
const sceneColorCopyHandle = blackboard.get(FrameResources.SceneColorCopy);
|
|
1299
|
+
const shadowMaskHandle = blackboard.get(FrameResources.ShadowMask);
|
|
1300
|
+
const waterCausticsHandle = blackboard.get(FrameResources.WaterCaustics);
|
|
1014
1301
|
const lightPassResult = requireBuildState(fg, 'lightPass', 'LightPass', 'CompositeTail');
|
|
1015
1302
|
const renderDepthAttachment = fg.state.renderDepthAttachment;
|
|
1016
1303
|
const useFinalFramebufferAsIntermediate = fg.state.useFinalFramebufferAsIntermediate;
|
|
@@ -1074,9 +1361,27 @@ function validateProducedFrameResources(blackboard, options, renderQueue) {
|
|
|
1074
1361
|
if (sceneColorCopyHandle) {
|
|
1075
1362
|
builder.read(sceneColorCopyHandle);
|
|
1076
1363
|
}
|
|
1364
|
+
if (shadowMaskHandle) {
|
|
1365
|
+
// Transparent geometry goes through the same clustered-light bind group
|
|
1366
|
+
// as the opaque pass, which binds ctx.shadowMaskTexture whenever
|
|
1367
|
+
// screenSpaceShadowMask is on (ShaderHelper, UNIFORM_NAME_SHADOW_MASK).
|
|
1368
|
+
// Declaring the read is what keeps the mask alive: the executor derives
|
|
1369
|
+
// each resource's lastUse from declared reads and returns the texture to
|
|
1370
|
+
// the pool immediately after, so without this the mask would be recycled
|
|
1371
|
+
// after LightPass while this pass is still sampling it.
|
|
1372
|
+
builder.read(shadowMaskHandle);
|
|
1373
|
+
}
|
|
1374
|
+
if (waterCausticsHandle) {
|
|
1375
|
+
// Same lifetime argument as the shadow mask above.
|
|
1376
|
+
builder.read(waterCausticsHandle);
|
|
1377
|
+
}
|
|
1077
1378
|
builder.read(opaqueChainResult.color);
|
|
1078
1379
|
const out = builder.write(opaqueChainResult.color);
|
|
1079
1380
|
builder.setExecute((rgCtx)=>{
|
|
1381
|
+
// Pulled here rather than inherited from LightPass's execute. The value
|
|
1382
|
+
// is the same, but relying on the leftover would make this pass depend
|
|
1383
|
+
// on execution order instead of on its own declared inputs.
|
|
1384
|
+
ctx.shadowMaskTexture = shadowMaskHandle ? rgCtx.getTexture(shadowMaskHandle) : null;
|
|
1080
1385
|
renderTransparentScenePass(frame, rgCtx, opaqueChainRan ? opaqueChainResult.color : null, lightPassResult.sceneColorFramebufferHandle);
|
|
1081
1386
|
});
|
|
1082
1387
|
return out;
|
|
@@ -1185,6 +1490,7 @@ function validateProducedFrameResources(blackboard, options, renderQueue) {
|
|
|
1185
1490
|
ClusterLights: ClusterLightsModule,
|
|
1186
1491
|
GPUPicking: GPUPickingModule,
|
|
1187
1492
|
ShadowMaps: ShadowMapsModule,
|
|
1493
|
+
WaterCaustics: WaterCausticsModule,
|
|
1188
1494
|
DepthPrepass: DepthPrepassModule,
|
|
1189
1495
|
ShadowMask: ShadowMaskModule,
|
|
1190
1496
|
TransmissionDepthForSSR: TransmissionDepthForSSRModule,
|
|
@@ -1193,6 +1499,7 @@ function validateProducedFrameResources(blackboard, options, renderQueue) {
|
|
|
1193
1499
|
SceneColorGrab: SceneColorGrabModule,
|
|
1194
1500
|
LightPass: LightPassModule,
|
|
1195
1501
|
SkyPass: SkyPassModule,
|
|
1502
|
+
Underwater: UnderwaterModule,
|
|
1196
1503
|
CompositeTail: CompositeTailModule
|
|
1197
1504
|
};
|
|
1198
1505
|
/** The default module order assembled by {@link createForwardPlusPipeline}. */ const DEFAULT_FORWARD_PLUS_MODULES = [
|
|
@@ -1200,6 +1507,7 @@ function validateProducedFrameResources(blackboard, options, renderQueue) {
|
|
|
1200
1507
|
ClusterLightsModule,
|
|
1201
1508
|
GPUPickingModule,
|
|
1202
1509
|
ShadowMapsModule,
|
|
1510
|
+
WaterCausticsModule,
|
|
1203
1511
|
DepthPrepassModule,
|
|
1204
1512
|
ShadowMaskModule,
|
|
1205
1513
|
TransmissionDepthForSSRModule,
|
|
@@ -1208,6 +1516,7 @@ function validateProducedFrameResources(blackboard, options, renderQueue) {
|
|
|
1208
1516
|
SceneColorGrabModule,
|
|
1209
1517
|
LightPassModule,
|
|
1210
1518
|
SkyPassModule,
|
|
1519
|
+
UnderwaterModule,
|
|
1211
1520
|
CompositeTailModule
|
|
1212
1521
|
];
|
|
1213
1522
|
/** @internal */ let _defaultForwardPlusPipeline = null;
|
|
@@ -1235,6 +1544,13 @@ function buildForwardPlusGraphInternal(graph, ctx, renderQueue, options) {
|
|
|
1235
1544
|
ctx.SkinSSSTexture = null;
|
|
1236
1545
|
// ShadowMask sets this only when it produces a texture.
|
|
1237
1546
|
ctx.shadowMaskTexture = null;
|
|
1547
|
+
// WaterCaustics turns these on together once it has produced a map; they are
|
|
1548
|
+
// read by every light pass, so they must start cleared each frame.
|
|
1549
|
+
ctx.waterCaustics = false;
|
|
1550
|
+
ctx.waterCausticLight = null;
|
|
1551
|
+
ctx.waterCausticTexture = null;
|
|
1552
|
+
ctx.waterCausticHeightTexture = null;
|
|
1553
|
+
ctx.waterCausticUniforms = null;
|
|
1238
1554
|
const blackboard = new RGBlackboard();
|
|
1239
1555
|
const frame = {
|
|
1240
1556
|
ctx,
|
|
@@ -1485,7 +1801,47 @@ function cleanupFrame(frame) {
|
|
|
1485
1801
|
let _skyMVProgram = null;
|
|
1486
1802
|
let _skyMVBindGroup = null;
|
|
1487
1803
|
let _skyMVBox = null;
|
|
1488
|
-
/** @internal */
|
|
1804
|
+
/** @internal */ /**
|
|
1805
|
+
* Fills in motion vectors for blended geometry that opted into them.
|
|
1806
|
+
*
|
|
1807
|
+
* @remarks
|
|
1808
|
+
* The transparent queue never reaches the ordinary prepass, so without this a
|
|
1809
|
+
* blended surface carries whatever velocity belongs to what is behind it. The
|
|
1810
|
+
* framebuffer here holds the motion vector attachment alone, so nothing touches
|
|
1811
|
+
* the linear depth the light pass and the screen-space effects read, and the
|
|
1812
|
+
* materials run with depth test on and depth write off, so the scene depth is
|
|
1813
|
+
* left as the opaque pass built it.
|
|
1814
|
+
*
|
|
1815
|
+
* Materials that have not opted in discard, which leaves the velocity beneath
|
|
1816
|
+
* them intact. See {@link MeshMaterial.transparentMotionVector} for why that is
|
|
1817
|
+
* the right default and where the approximation breaks.
|
|
1818
|
+
* @internal
|
|
1819
|
+
*/ function renderTransparentMotionVectors(frame, rgCtx, framebufferHandle) {
|
|
1820
|
+
const ctx = frame.ctx;
|
|
1821
|
+
const renderQueue = frame.renderQueue;
|
|
1822
|
+
if (!ctx.motionVectors || !renderQueue.itemList?.transparent) {
|
|
1823
|
+
return;
|
|
1824
|
+
}
|
|
1825
|
+
const device = ctx.device;
|
|
1826
|
+
device.pushDeviceStates();
|
|
1827
|
+
try {
|
|
1828
|
+
device.setFramebuffer(rgCtx.getFramebuffer(framebufferHandle));
|
|
1829
|
+
_depthPass.motionVectorOnly = true;
|
|
1830
|
+
_depthPass.encodeDepth = false;
|
|
1831
|
+
_depthPass.transmission = false;
|
|
1832
|
+
_depthPass.renderBackface = false;
|
|
1833
|
+
// Nothing is cleared: this pass only replaces the pixels its own geometry
|
|
1834
|
+
// covers, and everything else in the target is already correct.
|
|
1835
|
+
_depthPass.clearColor = null;
|
|
1836
|
+
_depthPass.clearDepth = null;
|
|
1837
|
+
_depthPass.clearStencil = null;
|
|
1838
|
+
_depthPass.render(ctx, null, null, renderQueue);
|
|
1839
|
+
} finally{
|
|
1840
|
+
_depthPass.motionVectorOnly = false;
|
|
1841
|
+
device.popDeviceStates();
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
function renderSkyMotionVectors(ctx, rgCtx, framebufferHandle) {
|
|
1489
1845
|
if (!ctx.motionVectorTexture) {
|
|
1490
1846
|
return;
|
|
1491
1847
|
}
|
|
@@ -1680,7 +2036,11 @@ function blitToCurrentColorAttachment(ctx, source) {
|
|
|
1680
2036
|
device.setViewport(null);
|
|
1681
2037
|
device.setScissor(null);
|
|
1682
2038
|
ctx.scene.env.sky.renderSky(ctx);
|
|
1683
|
-
|
|
2039
|
+
// Not while submerged: the medium between the eye and the scene is water,
|
|
2040
|
+
// and the Underwater module composites it. The sky itself is still drawn -
|
|
2041
|
+
// that pass then extinguishes it along with everything else at the far
|
|
2042
|
+
// plane, which is what makes the horizon read as water rather than as air.
|
|
2043
|
+
if (ctx.scene.env.sky.fogPresents && !ctx.underwater) {
|
|
1684
2044
|
// Snapshot between sky and fog. Sky belongs in the copy - a ray that hits
|
|
1685
2045
|
// the sky should read the sky - while fog does not, because the fog a
|
|
1686
2046
|
// screen-space pass would sample lies along the camera ray rather than the
|
|
@@ -1762,8 +2122,17 @@ function blitToCurrentColorAttachment(ctx, source) {
|
|
|
1762
2122
|
let motionVectorFrameCommitted = false;
|
|
1763
2123
|
try {
|
|
1764
2124
|
renderQueue = _scenePass.cullScene(ctx, ctx.camera);
|
|
2125
|
+
// Before the options are derived, because being submerged changes them: the
|
|
2126
|
+
// atmosphere is not the medium the camera is looking through, so its fog is
|
|
2127
|
+
// suppressed for the frame.
|
|
2128
|
+
ctx.underwater = selectUnderwaterSource(ctx);
|
|
1765
2129
|
const options = deriveForwardPlusOptions(ctx.scene, ctx.camera, device.type, renderQueue);
|
|
1766
2130
|
options.ssgi &&= supportsSSGIRenderTargets(ctx);
|
|
2131
|
+
// Atmospheric fog describes a column of air, which is not what a submerged
|
|
2132
|
+
// camera is looking through. The Underwater module owns the medium instead.
|
|
2133
|
+
// Clearing the flag here also stops SkyPass reserving a fog-free snapshot
|
|
2134
|
+
// for a frame that composites no fog.
|
|
2135
|
+
options.fogPresents &&= !ctx.underwater;
|
|
1767
2136
|
ctx.SSS = options.sss;
|
|
1768
2137
|
historyManager = ctx.camera.getHistoryResourceManager();
|
|
1769
2138
|
if (!historyManager) {
|