@zephyr3d/scene 0.9.21 → 0.9.23
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/joint_dynamics/collision.js +1 -7
- package/dist/animation/joint_dynamics/collision.js.map +1 -1
- package/dist/animation/joint_dynamics/constraints.js +2 -17
- package/dist/animation/joint_dynamics/constraints.js.map +1 -1
- package/dist/animation/joint_dynamics/controller.js +2 -20
- package/dist/animation/joint_dynamics/controller.js.map +1 -1
- package/dist/animation/joint_dynamics/joint_dynamics_system.js +1 -3
- package/dist/animation/joint_dynamics/joint_dynamics_system.js.map +1 -1
- package/dist/animation/joint_dynamics/solver.js +14 -60
- package/dist/animation/joint_dynamics/solver.js.map +1 -1
- package/dist/animation/joint_dynamics/types.js +3 -3
- package/dist/animation/joint_dynamics/types.js.map +1 -1
- package/dist/app/app.js +23 -0
- package/dist/app/app.js.map +1 -1
- package/dist/app/scriptregistry.js +16 -53
- package/dist/app/scriptregistry.js.map +1 -1
- package/dist/asset/assetmanager.js +2 -0
- package/dist/asset/assetmanager.js.map +1 -1
- package/dist/asset/model.js +22 -0
- package/dist/asset/model.js.map +1 -1
- package/dist/blitter/bilateralblur.js +3 -1
- package/dist/blitter/bilateralblur.js.map +1 -1
- package/dist/blitter/blitter.js +3 -3
- package/dist/blitter/blitter.js.map +1 -1
- package/dist/camera/camera.js +398 -45
- package/dist/camera/camera.js.map +1 -1
- package/dist/camera/perspectivecamera.js +69 -2
- package/dist/camera/perspectivecamera.js.map +1 -1
- package/dist/index.d.ts +9100 -8712
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/material/blinn.js +1 -1
- package/dist/material/blinn.js.map +1 -1
- package/dist/material/lambert.js +3 -3
- package/dist/material/lambert.js.map +1 -1
- package/dist/material/material.js +8 -5
- package/dist/material/material.js.map +1 -1
- package/dist/material/meshmaterial.js +17 -10
- package/dist/material/meshmaterial.js.map +1 -1
- package/dist/material/mixins/lightmodel/blinnphong.js +1 -1
- package/dist/material/mixins/lightmodel/blinnphong.js.map +1 -1
- package/dist/material/mixins/lightmodel/lambert.js +1 -1
- package/dist/material/mixins/lightmodel/lambert.js.map +1 -1
- package/dist/material/mixins/lightmodel/pbrblueprintmixin.js +21 -6
- package/dist/material/mixins/lightmodel/pbrblueprintmixin.js.map +1 -1
- package/dist/material/mixins/lightmodel/pbrmetallicroughness.js +1 -1
- package/dist/material/mixins/lightmodel/pbrmetallicroughness.js.map +1 -1
- package/dist/material/mixins/lightmodel/pbrspecularglossness.js +1 -1
- package/dist/material/mixins/lightmodel/pbrspecularglossness.js.map +1 -1
- package/dist/material/mixins/lit.js +33 -11
- package/dist/material/mixins/lit.js.map +1 -1
- package/dist/material/mixins/pbr/brdf.js +44 -12
- package/dist/material/mixins/pbr/brdf.js.map +1 -1
- package/dist/material/mixins/pbr/common.js +149 -14
- package/dist/material/mixins/pbr/common.js.map +1 -1
- package/dist/material/mtoon.js +7 -4
- package/dist/material/mtoon.js.map +1 -1
- package/dist/material/pbrblueprint.js +5 -1
- package/dist/material/pbrblueprint.js.map +1 -1
- package/dist/material/pbrmr.js +1 -1
- package/dist/material/pbrmr.js.map +1 -1
- package/dist/material/pbrsg.js +1 -1
- package/dist/material/pbrsg.js.map +1 -1
- package/dist/material/shader/helper.js +186 -11
- package/dist/material/shader/helper.js.map +1 -1
- package/dist/material/skin.js +2 -2
- package/dist/material/skin.js.map +1 -1
- package/dist/material/terrain-cm.js +5 -5
- package/dist/material/terrain-cm.js.map +1 -1
- package/dist/material/water.js +6 -4
- package/dist/material/water.js.map +1 -1
- package/dist/posteffect/bloom.js +54 -4
- package/dist/posteffect/bloom.js.map +1 -1
- package/dist/posteffect/compositor.js +41 -0
- package/dist/posteffect/compositor.js.map +1 -1
- package/dist/posteffect/posteffect.js +51 -0
- package/dist/posteffect/posteffect.js.map +1 -1
- package/dist/posteffect/sao.js +7 -6
- package/dist/posteffect/sao.js.map +1 -1
- package/dist/posteffect/skinsss.js +3 -3
- package/dist/posteffect/skinsss.js.map +1 -1
- package/dist/posteffect/ssgi.js +1196 -0
- package/dist/posteffect/ssgi.js.map +1 -0
- package/dist/posteffect/ssr.js +83 -45
- package/dist/posteffect/ssr.js.map +1 -1
- package/dist/posteffect/sss.js +137 -49
- package/dist/posteffect/sss.js.map +1 -1
- package/dist/posteffect/taa.js +5 -4
- package/dist/posteffect/taa.js.map +1 -1
- package/dist/posteffect/tonemap.js +15 -3
- package/dist/posteffect/tonemap.js.map +1 -1
- package/dist/render/abuffer_oit.js +2 -2
- package/dist/render/abuffer_oit.js.map +1 -1
- package/dist/render/cluster_light.js +12 -6
- package/dist/render/cluster_light.js.map +1 -1
- package/dist/render/drawable_mixin.js +8 -5
- package/dist/render/drawable_mixin.js.map +1 -1
- package/dist/render/dualdepthpeeling_oit.js +10 -5
- package/dist/render/dualdepthpeeling_oit.js.map +1 -1
- package/dist/render/envlight.js +298 -5
- package/dist/render/envlight.js.map +1 -1
- package/dist/render/gpu_picking.js +7 -5
- package/dist/render/gpu_picking.js.map +1 -1
- package/dist/render/hzb.js +28 -19
- package/dist/render/hzb.js.map +1 -1
- package/dist/render/lightpass.js +18 -4
- package/dist/render/lightpass.js.map +1 -1
- package/dist/render/render_queue.js +37 -4
- package/dist/render/render_queue.js.map +1 -1
- package/dist/render/renderer.js +8 -3
- package/dist/render/renderer.js.map +1 -1
- package/dist/render/rendergraph/blackboard.js +20 -48
- package/dist/render/rendergraph/blackboard.js.map +1 -1
- package/dist/render/rendergraph/device_pool_allocator.js +18 -58
- package/dist/render/rendergraph/device_pool_allocator.js.map +1 -1
- package/dist/render/rendergraph/executor.js +89 -115
- package/dist/render/rendergraph/executor.js.map +1 -1
- package/dist/render/rendergraph/forward_plus_builder.js +1003 -715
- package/dist/render/rendergraph/forward_plus_builder.js.map +1 -1
- package/dist/render/rendergraph/frame_graph_context.js +41 -0
- package/dist/render/rendergraph/frame_graph_context.js.map +1 -0
- package/dist/render/rendergraph/frame_resource_requirements.js +14 -0
- package/dist/render/rendergraph/frame_resource_requirements.js.map +1 -0
- package/dist/render/rendergraph/history_resource_manager.js +74 -140
- package/dist/render/rendergraph/history_resource_manager.js.map +1 -1
- package/dist/render/rendergraph/history_resources.js +7 -5
- package/dist/render/rendergraph/history_resources.js.map +1 -1
- package/dist/render/rendergraph/render_context.js +19 -0
- package/dist/render/rendergraph/render_context.js.map +1 -0
- package/dist/render/rendergraph/render_pipeline.js +315 -0
- package/dist/render/rendergraph/render_pipeline.js.map +1 -0
- package/dist/render/rendergraph/rendergraph.js +158 -141
- package/dist/render/rendergraph/rendergraph.js.map +1 -1
- package/dist/render/rendergraph/scene_render_context.js +267 -0
- package/dist/render/rendergraph/scene_render_context.js.map +1 -0
- package/dist/render/rendergraph/texture_affinity_cache.js +19 -0
- package/dist/render/rendergraph/texture_affinity_cache.js.map +1 -0
- package/dist/render/rendergraph/types.js +19 -31
- package/dist/render/rendergraph/types.js.map +1 -1
- package/dist/render/renderpass.js +3 -3
- package/dist/render/renderpass.js.map +1 -1
- package/dist/render/shadow_mask_pass.js +10 -6
- package/dist/render/shadow_mask_pass.js.map +1 -1
- package/dist/render/sky.js +197 -35
- package/dist/render/sky.js.map +1 -1
- package/dist/render/weightedblended_oit.js +4 -3
- package/dist/render/weightedblended_oit.js.map +1 -1
- package/dist/scene/basesprite.js +2 -0
- package/dist/scene/basesprite.js.map +1 -1
- package/dist/scene/batchgroup.js +10 -0
- package/dist/scene/batchgroup.js.map +1 -1
- package/dist/scene/environment.js +61 -2
- package/dist/scene/environment.js.map +1 -1
- package/dist/scene/light.js +178 -10
- package/dist/scene/light.js.map +1 -1
- package/dist/scene/meshdrawable.js +89 -2
- package/dist/scene/meshdrawable.js.map +1 -1
- package/dist/scene/msdftext.js +2 -0
- package/dist/scene/msdftext.js.map +1 -1
- package/dist/scene/msdftextsprite.js +2 -0
- package/dist/scene/msdftextsprite.js.map +1 -1
- package/dist/scene/particlesys.js +2 -0
- package/dist/scene/particlesys.js.map +1 -1
- package/dist/scene/scene.js +34 -1
- package/dist/scene/scene.js.map +1 -1
- package/dist/scene/terrain-cm/grass.js +2 -1
- package/dist/scene/terrain-cm/grass.js.map +1 -1
- package/dist/scene/terrain-cm/terrain-cm.js +2 -0
- package/dist/scene/terrain-cm/terrain-cm.js.map +1 -1
- package/dist/scene/water.js +2 -0
- package/dist/scene/water.js.map +1 -1
- package/dist/shaders/atmosphere.js +5 -4
- package/dist/shaders/atmosphere.js.map +1 -1
- package/dist/shaders/fog.js +10 -3
- package/dist/shaders/fog.js.map +1 -1
- package/dist/shaders/shadow.js +96 -29
- package/dist/shaders/shadow.js.map +1 -1
- package/dist/shaders/ssgi.js +63 -0
- package/dist/shaders/ssgi.js.map +1 -0
- package/dist/shaders/ssgi_reprojection.js +118 -0
- package/dist/shaders/ssgi_reprojection.js.map +1 -0
- package/dist/shaders/ssr.js +322 -154
- package/dist/shaders/ssr.js.map +1 -1
- package/dist/shadow/esm.js +13 -8
- package/dist/shadow/esm.js.map +1 -1
- package/dist/shadow/pcf_opt.js +15 -12
- package/dist/shadow/pcf_opt.js.map +1 -1
- package/dist/shadow/pcf_pd.js +15 -12
- package/dist/shadow/pcf_pd.js.map +1 -1
- package/dist/shadow/pcss.js +17 -11
- package/dist/shadow/pcss.js.map +1 -1
- package/dist/shadow/shadow_impl.js +20 -2
- package/dist/shadow/shadow_impl.js.map +1 -1
- package/dist/shadow/shadowmapper.js +10 -3
- package/dist/shadow/shadowmapper.js.map +1 -1
- package/dist/shadow/ssm.js +16 -13
- package/dist/shadow/ssm.js.map +1 -1
- package/dist/shadow/vsm.js +10 -8
- package/dist/shadow/vsm.js.map +1 -1
- package/dist/utility/physical.js +74 -0
- package/dist/utility/physical.js.map +1 -0
- package/dist/utility/rendermipmap.js +2 -2
- package/dist/utility/rendermipmap.js.map +1 -1
- package/dist/utility/serialization/manager.js +3 -1
- package/dist/utility/serialization/manager.js.map +1 -1
- package/dist/utility/serialization/scene/animation.js +3 -1
- package/dist/utility/serialization/scene/animation.js.map +1 -1
- package/dist/utility/serialization/scene/batch.js +3 -1
- package/dist/utility/serialization/scene/batch.js.map +1 -1
- package/dist/utility/serialization/scene/camera.js +598 -2
- package/dist/utility/serialization/scene/camera.js.map +1 -1
- package/dist/utility/serialization/scene/light.js +180 -9
- package/dist/utility/serialization/scene/light.js.map +1 -1
- package/dist/utility/serialization/scene/material.js +43 -3
- package/dist/utility/serialization/scene/material.js.map +1 -1
- package/dist/utility/serialization/scene/mesh.js +3 -1
- package/dist/utility/serialization/scene/mesh.js.map +1 -1
- package/dist/utility/serialization/scene/node.js +3 -1
- package/dist/utility/serialization/scene/node.js.map +1 -1
- package/dist/utility/serialization/scene/particle.js +3 -1
- package/dist/utility/serialization/scene/particle.js.map +1 -1
- package/dist/utility/serialization/scene/primitive.js +2 -0
- package/dist/utility/serialization/scene/primitive.js.map +1 -1
- package/dist/utility/serialization/scene/scene.js +70 -0
- package/dist/utility/serialization/scene/scene.js.map +1 -1
- package/dist/utility/serialization/scene/sprite.js +3 -1
- package/dist/utility/serialization/scene/sprite.js.map +1 -1
- package/dist/utility/serialization/scene/terrain.js +3 -1
- package/dist/utility/serialization/scene/terrain.js.map +1 -1
- package/dist/utility/serialization/scene/text.js +3 -0
- package/dist/utility/serialization/scene/text.js.map +1 -1
- package/dist/utility/serialization/scene/water.js +3 -1
- package/dist/utility/serialization/scene/water.js.map +1 -1
- package/dist/values.js +2 -3
- package/dist/values.js.map +1 -1
- package/package.json +3 -3
- package/dist/animation/joint_dynamics/convex_collider.js +0 -320
- package/dist/animation/joint_dynamics/convex_collider.js.map +0 -1
|
@@ -0,0 +1,1196 @@
|
|
|
1
|
+
import { Vector2, Matrix4x4, Vector4, DEPTH_FARTHEST } from '@zephyr3d/base';
|
|
2
|
+
import { FrameResources } from '../render/rendergraph/blackboard.js';
|
|
3
|
+
import { RGHistoryResources } from '../render/rendergraph/history_resources.js';
|
|
4
|
+
import { screenSpaceRayTracing_HiZ, screenSpaceRayTracing_Linear2D } from '../shaders/ssr.js';
|
|
5
|
+
import { ShaderHelper } from '../material/shader/helper.js';
|
|
6
|
+
import '../material/lambert.js';
|
|
7
|
+
import '../material/blinn.js';
|
|
8
|
+
import '../material/unlit.js';
|
|
9
|
+
import '../material/particle.js';
|
|
10
|
+
import '../material/subsurfaceprofile.js';
|
|
11
|
+
import '../material/skin.js';
|
|
12
|
+
import '@zephyr3d/device';
|
|
13
|
+
import '../values.js';
|
|
14
|
+
import '../material/meshmaterial.js';
|
|
15
|
+
import '../material/grassmaterial.js';
|
|
16
|
+
import '../material/terrain-cm.js';
|
|
17
|
+
import '../material/pbrmr.js';
|
|
18
|
+
import '../material/pbrsg.js';
|
|
19
|
+
import '../material/mtoon.js';
|
|
20
|
+
import '../material/pbrblueprint.js';
|
|
21
|
+
import '../material/sprite.js';
|
|
22
|
+
import '../material/sprite_std.js';
|
|
23
|
+
import '../utility/blueprint/material/texture.js';
|
|
24
|
+
import { linearToGamma } from '../shaders/misc.js';
|
|
25
|
+
import { fetchSampler } from '../utility/misc.js';
|
|
26
|
+
import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
|
|
27
|
+
|
|
28
|
+
const IRRADIANCE_FORMAT = 'rgba16f';
|
|
29
|
+
const SURFACE_FORMAT = 'rgba16f';
|
|
30
|
+
const MOMENTS_FORMAT = 'rgba16f';
|
|
31
|
+
// AO is scalar; r16f is available wherever SSGI runs.
|
|
32
|
+
const AO_FORMAT = 'r16f';
|
|
33
|
+
const HALF_FLOAT_MAX = 65504;
|
|
34
|
+
// Keep squared luminance below the rgba16f range.
|
|
35
|
+
const MOMENT_LUMINANCE_MAX = 255;
|
|
36
|
+
/**
|
|
37
|
+
* Screen-space diffuse global illumination.
|
|
38
|
+
*
|
|
39
|
+
* @public
|
|
40
|
+
*/ class SSGI extends AbstractPostEffect {
|
|
41
|
+
static _tracePrograms = {};
|
|
42
|
+
static _temporalPrograms = {};
|
|
43
|
+
static _atrousProgram = null;
|
|
44
|
+
static _surfaceProgram = null;
|
|
45
|
+
static _upsampleProgram = null;
|
|
46
|
+
static _compositePrograms = {};
|
|
47
|
+
_traceBindGroups = {};
|
|
48
|
+
_temporalBindGroups = {};
|
|
49
|
+
_atrousBindGroups = {};
|
|
50
|
+
_surfaceBindGroups = {};
|
|
51
|
+
_upsampleBindGroups = {};
|
|
52
|
+
_compositeBindGroups = {};
|
|
53
|
+
constructor(){
|
|
54
|
+
super();
|
|
55
|
+
this._layer = PostEffectLayer.opaque;
|
|
56
|
+
}
|
|
57
|
+
/** {@inheritDoc AbstractPostEffect.requireLinearDepthTexture} */ requireLinearDepthTexture(ctx) {
|
|
58
|
+
return !!ctx.SSGI;
|
|
59
|
+
}
|
|
60
|
+
/** {@inheritDoc AbstractPostEffect.requireMotionVectorTexture} */ requireMotionVectorTexture(ctx) {
|
|
61
|
+
return !!ctx.SSGI && ctx.device.type === 'webgpu';
|
|
62
|
+
}
|
|
63
|
+
/** {@inheritDoc AbstractPostEffect.requireHiZTexture} */ requireHiZTexture(ctx) {
|
|
64
|
+
return !!ctx.SSGI && ctx.device.type === 'webgpu';
|
|
65
|
+
}
|
|
66
|
+
/** {@inheritDoc AbstractPostEffect.requireSceneNormalTexture} */ requireSceneNormalTexture(ctx) {
|
|
67
|
+
return !!ctx.SSGI;
|
|
68
|
+
}
|
|
69
|
+
/** Build trace, temporal, denoise, upsample, history and pass-through stages. */ setup(s) {
|
|
70
|
+
const { graph, ctx, history, blackboard } = s;
|
|
71
|
+
if (!ctx.SSGI || !history) {
|
|
72
|
+
return s.input;
|
|
73
|
+
}
|
|
74
|
+
const linearDepthHandle = blackboard.get(FrameResources.LinearDepth);
|
|
75
|
+
const normalHandle = blackboard.get(FrameResources.SceneNormal);
|
|
76
|
+
const motionHandle = blackboard.get(FrameResources.MotionVector);
|
|
77
|
+
const hiZHandle = blackboard.get(FrameResources.HiZ);
|
|
78
|
+
// Traced hits read this as outgoing surface radiance, so it must exclude the
|
|
79
|
+
// fog the camera ray accumulated: that fog belongs to a different path than
|
|
80
|
+
// the one being integrated, and feeding it back would brighten every bounce.
|
|
81
|
+
// Absent when the scene has no fog, in which case SceneColor is already fog
|
|
82
|
+
// free. Both the sampled color and the committed history use it, otherwise
|
|
83
|
+
// the current and previous frames would disagree and flicker.
|
|
84
|
+
const noFogHandle = blackboard.get(FrameResources.SceneColorNoFog);
|
|
85
|
+
const sampleColorHandle = noFogHandle ?? s.input;
|
|
86
|
+
if (!linearDepthHandle || !normalHandle) {
|
|
87
|
+
return s.input;
|
|
88
|
+
}
|
|
89
|
+
const settings = ctx.camera.ssgiResolvedSettings;
|
|
90
|
+
const traceWidth = settings.halfRes ? Math.max(1, Math.ceil(s.width * 0.5)) : s.width;
|
|
91
|
+
const traceHeight = settings.halfRes ? Math.max(1, Math.ceil(s.height * 0.5)) : s.height;
|
|
92
|
+
const traceSize = {
|
|
93
|
+
width: traceWidth,
|
|
94
|
+
height: traceHeight
|
|
95
|
+
};
|
|
96
|
+
const fullSize = {
|
|
97
|
+
width: s.width,
|
|
98
|
+
height: s.height
|
|
99
|
+
};
|
|
100
|
+
const traceDesc = {
|
|
101
|
+
format: IRRADIANCE_FORMAT,
|
|
102
|
+
sizeMode: 'absolute',
|
|
103
|
+
width: traceWidth,
|
|
104
|
+
height: traceHeight
|
|
105
|
+
};
|
|
106
|
+
const fullIrradianceDesc = {
|
|
107
|
+
format: IRRADIANCE_FORMAT,
|
|
108
|
+
sizeMode: 'absolute',
|
|
109
|
+
width: s.width,
|
|
110
|
+
height: s.height
|
|
111
|
+
};
|
|
112
|
+
const surfaceDesc = {
|
|
113
|
+
format: SURFACE_FORMAT,
|
|
114
|
+
sizeMode: 'absolute',
|
|
115
|
+
width: s.width,
|
|
116
|
+
height: s.height
|
|
117
|
+
};
|
|
118
|
+
const momentsDesc = {
|
|
119
|
+
format: MOMENTS_FORMAT,
|
|
120
|
+
sizeMode: 'absolute',
|
|
121
|
+
width: traceWidth,
|
|
122
|
+
height: traceHeight
|
|
123
|
+
};
|
|
124
|
+
const aoTraceDesc = {
|
|
125
|
+
format: AO_FORMAT,
|
|
126
|
+
sizeMode: 'absolute',
|
|
127
|
+
width: traceWidth,
|
|
128
|
+
height: traceHeight
|
|
129
|
+
};
|
|
130
|
+
const fullAODesc = {
|
|
131
|
+
format: AO_FORMAT,
|
|
132
|
+
sizeMode: 'absolute',
|
|
133
|
+
width: s.width,
|
|
134
|
+
height: s.height
|
|
135
|
+
};
|
|
136
|
+
const findHistoryRead = (name)=>s.historyReads.find((binding)=>binding.name === name)?.handle ?? null;
|
|
137
|
+
const previousIrradianceHandle = findHistoryRead(RGHistoryResources.SSGI_IRRADIANCE);
|
|
138
|
+
const previousSurfaceHandle = findHistoryRead(RGHistoryResources.SSGI_SURFACE);
|
|
139
|
+
const previousMomentsHandle = history.importPreviousIfCompatible(graph, RGHistoryResources.SSGI_MOMENTS, momentsDesc, traceSize);
|
|
140
|
+
// Only this effect's own temporal pass reads the AO history, so it is
|
|
141
|
+
// imported directly instead of being routed through the light pass history
|
|
142
|
+
// read bindings the way irradiance and surface are.
|
|
143
|
+
const previousAOHandle = history.importPreviousIfCompatible(graph, RGHistoryResources.SSGI_AO, aoTraceDesc, traceSize);
|
|
144
|
+
const previousSceneColorHandle = ctx.device.type === 'webgpu' ? history.importPreviousIfCompatible(graph, RGHistoryResources.SSGI_SCENE_COLOR, {
|
|
145
|
+
format: s.colorFormat,
|
|
146
|
+
sizeMode: 'absolute',
|
|
147
|
+
width: s.width,
|
|
148
|
+
height: s.height
|
|
149
|
+
}, fullSize) : null;
|
|
150
|
+
const canSampleSceneHistory = !!(previousSceneColorHandle && previousSurfaceHandle && motionHandle);
|
|
151
|
+
const readFrameInputs = (builder)=>{
|
|
152
|
+
builder.read(linearDepthHandle);
|
|
153
|
+
builder.read(normalHandle);
|
|
154
|
+
if (motionHandle) {
|
|
155
|
+
builder.read(motionHandle);
|
|
156
|
+
}
|
|
157
|
+
if (hiZHandle) {
|
|
158
|
+
builder.read(hiZHandle);
|
|
159
|
+
}
|
|
160
|
+
for (const dep of s.dependencies){
|
|
161
|
+
builder.read(dep);
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
// Trace one or two cosine-weighted diffuse rays. A hit prefers the previous
|
|
165
|
+
// frame through previousHitUV = hitUV - motion(hitUV) for multi-bounce
|
|
166
|
+
// feedback and falls back to the current frame when that reprojection fails.
|
|
167
|
+
const traceResult = graph.addPass('SSGI:Trace', (builder)=>{
|
|
168
|
+
builder.read(sampleColorHandle);
|
|
169
|
+
readFrameInputs(builder);
|
|
170
|
+
if (canSampleSceneHistory) {
|
|
171
|
+
builder.read(previousSceneColorHandle);
|
|
172
|
+
builder.read(previousSurfaceHandle);
|
|
173
|
+
}
|
|
174
|
+
const out = builder.createTexture({
|
|
175
|
+
...traceDesc,
|
|
176
|
+
label: 'SSGI:rawIrradiance'
|
|
177
|
+
});
|
|
178
|
+
const ao = builder.createTexture({
|
|
179
|
+
...aoTraceDesc,
|
|
180
|
+
label: 'SSGI:rawAO'
|
|
181
|
+
});
|
|
182
|
+
const fb = builder.createFramebuffer({
|
|
183
|
+
label: 'SSGI:traceFB',
|
|
184
|
+
width: traceWidth,
|
|
185
|
+
height: traceHeight,
|
|
186
|
+
colorAttachments: [
|
|
187
|
+
out,
|
|
188
|
+
ao
|
|
189
|
+
],
|
|
190
|
+
depthAttachment: null,
|
|
191
|
+
ignoreDepthStencil: true
|
|
192
|
+
});
|
|
193
|
+
builder.setExecute((rg)=>{
|
|
194
|
+
const device = ctx.device;
|
|
195
|
+
device.pushDeviceStates();
|
|
196
|
+
try {
|
|
197
|
+
device.setFramebuffer(rg.getFramebuffer(fb));
|
|
198
|
+
this.trace(ctx, rg.getTexture(sampleColorHandle), canSampleSceneHistory ? rg.getTexture(previousSceneColorHandle) : null, rg.getTexture(linearDepthHandle), rg.getTexture(normalHandle), canSampleSceneHistory ? rg.getTexture(motionHandle) : null, canSampleSceneHistory ? rg.getTexture(previousSurfaceHandle) : null, hiZHandle ? rg.getTexture(hiZHandle) : null, traceWidth, traceHeight);
|
|
199
|
+
} finally{
|
|
200
|
+
device.popDeviceStates();
|
|
201
|
+
}
|
|
202
|
+
});
|
|
203
|
+
return {
|
|
204
|
+
irradiance: out,
|
|
205
|
+
ao
|
|
206
|
+
};
|
|
207
|
+
});
|
|
208
|
+
// Store the current full-resolution surface independently from irradiance;
|
|
209
|
+
// no albedo MRT is needed because the next lighting pass applies the BRDF.
|
|
210
|
+
const surfaceHandle = graph.addPass('SSGI:Surface', (builder)=>{
|
|
211
|
+
builder.read(linearDepthHandle);
|
|
212
|
+
builder.read(normalHandle);
|
|
213
|
+
const out = builder.createTexture({
|
|
214
|
+
...surfaceDesc,
|
|
215
|
+
label: 'SSGI:surface'
|
|
216
|
+
});
|
|
217
|
+
const fb = builder.createFramebuffer({
|
|
218
|
+
label: 'SSGI:surfaceFB',
|
|
219
|
+
width: s.width,
|
|
220
|
+
height: s.height,
|
|
221
|
+
colorAttachments: out,
|
|
222
|
+
depthAttachment: null,
|
|
223
|
+
ignoreDepthStencil: true
|
|
224
|
+
});
|
|
225
|
+
builder.setExecute((rg)=>{
|
|
226
|
+
const device = ctx.device;
|
|
227
|
+
device.pushDeviceStates();
|
|
228
|
+
try {
|
|
229
|
+
device.setFramebuffer(rg.getFramebuffer(fb));
|
|
230
|
+
this.writeSurface(ctx, rg.getTexture(linearDepthHandle), rg.getTexture(normalHandle));
|
|
231
|
+
} finally{
|
|
232
|
+
device.popDeviceStates();
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
return out;
|
|
236
|
+
});
|
|
237
|
+
const canTemporal = !!(ctx.device.type === 'webgpu' && ctx.camera.ssgiTemporal && motionHandle && previousIrradianceHandle && previousSurfaceHandle && previousMomentsHandle && previousAOHandle);
|
|
238
|
+
// Temporal pass always creates moments. On the first frame it simply
|
|
239
|
+
// initializes them; with valid history it rejects by depth and normal,
|
|
240
|
+
// neighborhood-clamps radiance, and accumulates first/second moments.
|
|
241
|
+
const temporalResult = graph.addPass('SSGI:Temporal', (builder)=>{
|
|
242
|
+
builder.read(traceResult.irradiance);
|
|
243
|
+
builder.read(traceResult.ao);
|
|
244
|
+
readFrameInputs(builder);
|
|
245
|
+
if (canTemporal) {
|
|
246
|
+
builder.read(previousIrradianceHandle);
|
|
247
|
+
builder.read(previousSurfaceHandle);
|
|
248
|
+
builder.read(previousMomentsHandle);
|
|
249
|
+
builder.read(previousAOHandle);
|
|
250
|
+
}
|
|
251
|
+
const irradiance = builder.createTexture({
|
|
252
|
+
...traceDesc,
|
|
253
|
+
label: 'SSGI:temporalIrradiance'
|
|
254
|
+
});
|
|
255
|
+
const moments = builder.createTexture({
|
|
256
|
+
...momentsDesc,
|
|
257
|
+
label: 'SSGI:moments'
|
|
258
|
+
});
|
|
259
|
+
const ao = builder.createTexture({
|
|
260
|
+
...aoTraceDesc,
|
|
261
|
+
label: 'SSGI:temporalAO'
|
|
262
|
+
});
|
|
263
|
+
const fb = builder.createFramebuffer({
|
|
264
|
+
label: 'SSGI:temporalFB',
|
|
265
|
+
width: traceWidth,
|
|
266
|
+
height: traceHeight,
|
|
267
|
+
colorAttachments: [
|
|
268
|
+
irradiance,
|
|
269
|
+
moments,
|
|
270
|
+
ao
|
|
271
|
+
],
|
|
272
|
+
depthAttachment: null,
|
|
273
|
+
ignoreDepthStencil: true
|
|
274
|
+
});
|
|
275
|
+
builder.setExecute((rg)=>{
|
|
276
|
+
const device = ctx.device;
|
|
277
|
+
device.pushDeviceStates();
|
|
278
|
+
try {
|
|
279
|
+
device.setFramebuffer(rg.getFramebuffer(fb));
|
|
280
|
+
this.temporal(ctx, rg.getTexture(traceResult.irradiance), rg.getTexture(traceResult.ao), rg.getTexture(linearDepthHandle), rg.getTexture(normalHandle), canTemporal ? rg.getTexture(motionHandle) : null, canTemporal ? rg.getTexture(previousIrradianceHandle) : null, canTemporal ? rg.getTexture(previousSurfaceHandle) : null, canTemporal ? rg.getTexture(previousMomentsHandle) : null, canTemporal ? rg.getTexture(previousAOHandle) : null, traceWidth, traceHeight);
|
|
281
|
+
} finally{
|
|
282
|
+
device.popDeviceStates();
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
return {
|
|
286
|
+
irradiance,
|
|
287
|
+
moments,
|
|
288
|
+
ao
|
|
289
|
+
};
|
|
290
|
+
});
|
|
291
|
+
// Variance-guided, cross-bilateral a-trous filtering. The increasing
|
|
292
|
+
// kernel step removes low-frequency Monte Carlo noise while depth/normal
|
|
293
|
+
// weights preserve discontinuities.
|
|
294
|
+
let filteredHandle = temporalResult.irradiance;
|
|
295
|
+
let filteredAOHandle = temporalResult.ao;
|
|
296
|
+
for(let pass = 0; pass < settings.denoisePasses; pass++){
|
|
297
|
+
const source = filteredHandle;
|
|
298
|
+
const aoSource = filteredAOHandle;
|
|
299
|
+
const filtered = graph.addPass(`SSGI:ATrous:${pass}`, (builder)=>{
|
|
300
|
+
builder.read(source);
|
|
301
|
+
builder.read(aoSource);
|
|
302
|
+
builder.read(temporalResult.moments);
|
|
303
|
+
builder.read(linearDepthHandle);
|
|
304
|
+
builder.read(normalHandle);
|
|
305
|
+
const out = builder.createTexture({
|
|
306
|
+
...traceDesc,
|
|
307
|
+
label: `SSGI:atrous${pass}`
|
|
308
|
+
});
|
|
309
|
+
const ao = builder.createTexture({
|
|
310
|
+
...aoTraceDesc,
|
|
311
|
+
label: `SSGI:atrousAO${pass}`
|
|
312
|
+
});
|
|
313
|
+
const fb = builder.createFramebuffer({
|
|
314
|
+
label: `SSGI:atrousFB${pass}`,
|
|
315
|
+
width: traceWidth,
|
|
316
|
+
height: traceHeight,
|
|
317
|
+
colorAttachments: [
|
|
318
|
+
out,
|
|
319
|
+
ao
|
|
320
|
+
],
|
|
321
|
+
depthAttachment: null,
|
|
322
|
+
ignoreDepthStencil: true
|
|
323
|
+
});
|
|
324
|
+
builder.setExecute((rg)=>{
|
|
325
|
+
const device = ctx.device;
|
|
326
|
+
device.pushDeviceStates();
|
|
327
|
+
try {
|
|
328
|
+
device.setFramebuffer(rg.getFramebuffer(fb));
|
|
329
|
+
this.atrous(ctx, rg.getTexture(source), rg.getTexture(aoSource), rg.getTexture(temporalResult.moments), rg.getTexture(linearDepthHandle), rg.getTexture(normalHandle), traceWidth, traceHeight, 1 << pass, canTemporal);
|
|
330
|
+
} finally{
|
|
331
|
+
device.popDeviceStates();
|
|
332
|
+
}
|
|
333
|
+
});
|
|
334
|
+
return {
|
|
335
|
+
irradiance: out,
|
|
336
|
+
ao
|
|
337
|
+
};
|
|
338
|
+
});
|
|
339
|
+
filteredHandle = filtered.irradiance;
|
|
340
|
+
filteredAOHandle = filtered.ao;
|
|
341
|
+
}
|
|
342
|
+
// Half-resolution presets use a joint bilateral upsample before history is
|
|
343
|
+
// consumed by full-resolution materials on the next frame.
|
|
344
|
+
let finalIrradianceHandle = filteredHandle;
|
|
345
|
+
let finalAOHandle = filteredAOHandle;
|
|
346
|
+
if (settings.halfRes) {
|
|
347
|
+
const source = filteredHandle;
|
|
348
|
+
const aoSource = filteredAOHandle;
|
|
349
|
+
const upsampled = graph.addPass('SSGI:Upsample', (builder)=>{
|
|
350
|
+
builder.read(source);
|
|
351
|
+
builder.read(aoSource);
|
|
352
|
+
builder.read(linearDepthHandle);
|
|
353
|
+
builder.read(normalHandle);
|
|
354
|
+
const out = builder.createTexture({
|
|
355
|
+
...fullIrradianceDesc,
|
|
356
|
+
label: 'SSGI:upsampledIrradiance'
|
|
357
|
+
});
|
|
358
|
+
const ao = builder.createTexture({
|
|
359
|
+
...fullAODesc,
|
|
360
|
+
label: 'SSGI:upsampledAO'
|
|
361
|
+
});
|
|
362
|
+
const fb = builder.createFramebuffer({
|
|
363
|
+
label: 'SSGI:upsampleFB',
|
|
364
|
+
width: s.width,
|
|
365
|
+
height: s.height,
|
|
366
|
+
colorAttachments: [
|
|
367
|
+
out,
|
|
368
|
+
ao
|
|
369
|
+
],
|
|
370
|
+
depthAttachment: null,
|
|
371
|
+
ignoreDepthStencil: true
|
|
372
|
+
});
|
|
373
|
+
builder.setExecute((rg)=>{
|
|
374
|
+
const device = ctx.device;
|
|
375
|
+
device.pushDeviceStates();
|
|
376
|
+
try {
|
|
377
|
+
device.setFramebuffer(rg.getFramebuffer(fb));
|
|
378
|
+
this.upsample(ctx, rg.getTexture(source), rg.getTexture(aoSource), rg.getTexture(linearDepthHandle), rg.getTexture(normalHandle));
|
|
379
|
+
} finally{
|
|
380
|
+
device.popDeviceStates();
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
return {
|
|
384
|
+
irradiance: out,
|
|
385
|
+
ao
|
|
386
|
+
};
|
|
387
|
+
});
|
|
388
|
+
finalIrradianceHandle = upsampled.irradiance;
|
|
389
|
+
finalAOHandle = upsampled.ao;
|
|
390
|
+
}
|
|
391
|
+
// Pinned before the commit closure captures it: the a-trous loop above
|
|
392
|
+
// reassigns the handle, and the AO history has to be the trace-resolution
|
|
393
|
+
// one regardless of whether an upsample ran.
|
|
394
|
+
const traceResolutionAOHandle = filteredAOHandle;
|
|
395
|
+
// SSGI history is independent from TAA/SSR history. Commit the opaque HDR
|
|
396
|
+
// SceneColor input (before SSR/transparency/tone-map/AA), irradiance,
|
|
397
|
+
// surface and moments, then pass SceneColor through unchanged.
|
|
398
|
+
return graph.addPass('SSGI:Commit', (builder)=>{
|
|
399
|
+
builder.read(s.input);
|
|
400
|
+
if (noFogHandle) {
|
|
401
|
+
builder.read(noFogHandle);
|
|
402
|
+
}
|
|
403
|
+
builder.read(finalIrradianceHandle);
|
|
404
|
+
builder.read(finalAOHandle);
|
|
405
|
+
// The trace-resolution AO is what goes back into history, so it has to be
|
|
406
|
+
// read here too even when the upsampled one is what gets composited.
|
|
407
|
+
builder.read(traceResolutionAOHandle);
|
|
408
|
+
builder.read(surfaceHandle);
|
|
409
|
+
builder.read(temporalResult.moments);
|
|
410
|
+
const output = s.createOutput(builder);
|
|
411
|
+
builder.setExecute((rg)=>{
|
|
412
|
+
const device = ctx.device;
|
|
413
|
+
device.pushDeviceStates();
|
|
414
|
+
try {
|
|
415
|
+
device.setFramebuffer(output.framebuffer ? rg.getFramebuffer(output.framebuffer) : null);
|
|
416
|
+
const sceneColor = rg.getTexture(s.input);
|
|
417
|
+
const irradiance = rg.getTexture(finalIrradianceHandle);
|
|
418
|
+
const ao = rg.getTexture(finalAOHandle);
|
|
419
|
+
const filteredAO = rg.getTexture(traceResolutionAOHandle);
|
|
420
|
+
const surface = rg.getTexture(surfaceHandle);
|
|
421
|
+
const moments = rg.getTexture(temporalResult.moments);
|
|
422
|
+
if (ctx.camera.ssgiAOIntensity > 0) {
|
|
423
|
+
this.compositeAO(ctx, sceneColor, ao, output.srgbOutput);
|
|
424
|
+
} else {
|
|
425
|
+
this.passThrough(ctx, sceneColor, output.srgbOutput);
|
|
426
|
+
}
|
|
427
|
+
if (ctx.device.type === 'webgpu') {
|
|
428
|
+
// Commit the same fog-free color the trace sampled, so next frame's
|
|
429
|
+
// reprojected history is on the same footing as this frame's fallback.
|
|
430
|
+
this.commitHistory(history, RGHistoryResources.SSGI_SCENE_COLOR, rg.getTexture(sampleColorHandle));
|
|
431
|
+
}
|
|
432
|
+
this.commitHistory(history, RGHistoryResources.SSGI_IRRADIANCE, irradiance);
|
|
433
|
+
this.commitHistory(history, RGHistoryResources.SSGI_SURFACE, surface);
|
|
434
|
+
this.commitHistory(history, RGHistoryResources.SSGI_MOMENTS, moments);
|
|
435
|
+
// Committed at trace resolution: the temporal pass that consumes it
|
|
436
|
+
// runs before the upsample, so a full-resolution AO would fail the
|
|
437
|
+
// descriptor compatibility check and silently disable accumulation.
|
|
438
|
+
this.commitHistory(history, RGHistoryResources.SSGI_AO, filteredAO);
|
|
439
|
+
} finally{
|
|
440
|
+
device.popDeviceStates();
|
|
441
|
+
}
|
|
442
|
+
});
|
|
443
|
+
return output.color;
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
/** @internal */ commitHistory(history, name, texture) {
|
|
447
|
+
history.queueRetainedCommit(name, {
|
|
448
|
+
format: texture.format,
|
|
449
|
+
sizeMode: 'absolute',
|
|
450
|
+
width: texture.width,
|
|
451
|
+
height: texture.height
|
|
452
|
+
}, {
|
|
453
|
+
width: texture.width,
|
|
454
|
+
height: texture.height
|
|
455
|
+
}, texture);
|
|
456
|
+
}
|
|
457
|
+
/** @internal */ trace(ctx, currentColor, previousColor, depth, normal, motion, previousSurface, hiZ, width, height) {
|
|
458
|
+
const sampleHistory = !!(previousColor && motion && previousSurface);
|
|
459
|
+
const envHash = ctx.env.light.getHash();
|
|
460
|
+
const historyHash = sampleHistory ? `${previousColor.uid}:${motion.uid}:${previousSurface.uid}` : '';
|
|
461
|
+
const hizHash = hiZ ? `${hiZ.uid}` : '';
|
|
462
|
+
const programHash = `${sampleHistory ? '1' : '0'}:${hiZ ? '1' : '0'}:${ctx.camera.ssgiResolvedSettings.raysPerPixel}:${envHash}`;
|
|
463
|
+
const bindGroupHash = `${currentColor.uid}:${depth.uid}:${normal.uid}:(${historyHash}):(${hizHash}):${ctx.camera.ssgiResolvedSettings.raysPerPixel}:${envHash}`;
|
|
464
|
+
let program = SSGI._tracePrograms[programHash];
|
|
465
|
+
if (!program) {
|
|
466
|
+
program = this.createTraceProgram(ctx, !!hiZ, sampleHistory);
|
|
467
|
+
SSGI._tracePrograms[programHash] = program;
|
|
468
|
+
}
|
|
469
|
+
const colorSampler = fetchSampler(ctx.device.type === 'webgl' ? 'clamp_nearest_nomip' : 'clamp_linear_nomip');
|
|
470
|
+
let bindGroup = this._traceBindGroups[bindGroupHash];
|
|
471
|
+
if (!bindGroup) {
|
|
472
|
+
bindGroup = ctx.device.createBindGroup(program.bindGroupLayouts[0]);
|
|
473
|
+
bindGroup.setTexture('currentColorTex', currentColor, colorSampler);
|
|
474
|
+
bindGroup.setTexture('depthTex', depth, fetchSampler('clamp_nearest_nomip'));
|
|
475
|
+
bindGroup.setTexture('normalTex', normal, fetchSampler('clamp_nearest_nomip'));
|
|
476
|
+
if (sampleHistory) {
|
|
477
|
+
bindGroup.setTexture('previousColorTex', previousColor, colorSampler);
|
|
478
|
+
bindGroup.setTexture('motionTex', motion, fetchSampler('clamp_nearest_nomip'));
|
|
479
|
+
bindGroup.setTexture('previousSurfaceTex', previousSurface, fetchSampler('clamp_nearest_nomip'));
|
|
480
|
+
}
|
|
481
|
+
if (hiZ) {
|
|
482
|
+
bindGroup.setTexture('hizTex', hiZ, fetchSampler('clamp_nearest'));
|
|
483
|
+
bindGroup.setValue('depthMipLevels', hiZ.mipLevelCount);
|
|
484
|
+
}
|
|
485
|
+
this._traceBindGroups[bindGroupHash] = bindGroup;
|
|
486
|
+
}
|
|
487
|
+
if (sampleHistory) {
|
|
488
|
+
bindGroup.setValue('historyRejectParams', new Vector2(ctx.camera.ssgiDepthReject, ctx.camera.ssgiNormalReject));
|
|
489
|
+
}
|
|
490
|
+
bindGroup.setValue('cameraNearFar', new Vector2(ctx.camera.getNearPlane(), ctx.camera.getFarPlane()));
|
|
491
|
+
bindGroup.setValue('viewMatrix', ctx.camera.viewMatrix);
|
|
492
|
+
bindGroup.setValue('invViewMatrix', ctx.camera.worldMatrix);
|
|
493
|
+
bindGroup.setValue('projMatrix', ctx.camera.getProjectionMatrix());
|
|
494
|
+
bindGroup.setValue('invProjMatrix', Matrix4x4.invert(ctx.camera.getProjectionMatrix()));
|
|
495
|
+
bindGroup.setValue('traceParams', new Vector4(ctx.camera.ssgiMaxDistance, ctx.camera.ssgiThickness, ctx.camera.ssgiResolvedSettings.maxSteps, ctx.camera.ssgiStride));
|
|
496
|
+
bindGroup.setValue('targetSize', new Vector4(width, height, hiZ?.width ?? depth.width, hiZ?.height ?? depth.height));
|
|
497
|
+
bindGroup.setValue('radianceParams', new Vector4(ctx.camera.ssgiIntensity, ctx.camera.ssgiMaxRayIntensity, ShaderHelper.getEnvLightLuminance(ctx), ctx.device.frameInfo.frameCounter));
|
|
498
|
+
bindGroup.setValue('skyOcclusion', ctx.camera.ssgiSkyOcclusion);
|
|
499
|
+
bindGroup.setValue('flip', this.needFlip(ctx.device) ? 1 : 0);
|
|
500
|
+
ctx.env.light.envLight.updateBindGroup(bindGroup);
|
|
501
|
+
ctx.device.setProgram(program);
|
|
502
|
+
ctx.device.setBindGroup(0, bindGroup);
|
|
503
|
+
this.drawFullscreenQuad(AbstractPostEffect.getDefaultRenderState(ctx, 'always'));
|
|
504
|
+
}
|
|
505
|
+
/** @internal */ temporal(ctx, current, currentAO, depth, normal, motion, previousIrradiance, previousSurface, previousMoments, previousAO, width, height) {
|
|
506
|
+
const hasHistory = !!(motion && previousIrradiance && previousSurface && previousMoments && previousAO);
|
|
507
|
+
const hash = hasHistory ? `history:${current.uid}:${currentAO.uid}:${depth.uid}:${normal.uid}:${motion.uid}:${previousIrradiance.uid}:${previousSurface.uid}:${previousMoments.uid}:${previousAO.uid}` : `initialize:${current.uid}:${currentAO.uid}:${depth.uid}:${normal.uid}`;
|
|
508
|
+
let program = SSGI._temporalPrograms[hash];
|
|
509
|
+
if (!program) {
|
|
510
|
+
program = this.createTemporalProgram(ctx, hasHistory);
|
|
511
|
+
SSGI._temporalPrograms[hash] = program;
|
|
512
|
+
}
|
|
513
|
+
let bindGroup = this._temporalBindGroups[hash];
|
|
514
|
+
if (!bindGroup) {
|
|
515
|
+
bindGroup = ctx.device.createBindGroup(program.bindGroupLayouts[0]);
|
|
516
|
+
bindGroup.setTexture('currentTex', current, fetchSampler('clamp_nearest_nomip'));
|
|
517
|
+
bindGroup.setTexture('currentAOTex', currentAO, fetchSampler('clamp_nearest_nomip'));
|
|
518
|
+
bindGroup.setTexture('depthTex', depth, fetchSampler('clamp_nearest_nomip'));
|
|
519
|
+
bindGroup.setTexture('normalTex', normal, fetchSampler('clamp_nearest_nomip'));
|
|
520
|
+
if (hasHistory) {
|
|
521
|
+
bindGroup.setTexture('motionTex', motion, fetchSampler('clamp_nearest_nomip'));
|
|
522
|
+
bindGroup.setTexture('previousIrradianceTex', previousIrradiance, fetchSampler('clamp_linear_nomip'));
|
|
523
|
+
bindGroup.setTexture('previousSurfaceTex', previousSurface, fetchSampler('clamp_nearest_nomip'));
|
|
524
|
+
bindGroup.setTexture('previousMomentsTex', previousMoments, fetchSampler('clamp_linear_nomip'));
|
|
525
|
+
// Linear is safe here: this branch only runs on WebGPU, where r16f is
|
|
526
|
+
// unconditionally filterable, and the read is at a reprojected UV.
|
|
527
|
+
bindGroup.setTexture('previousAOTex', previousAO, fetchSampler('clamp_linear_nomip'));
|
|
528
|
+
}
|
|
529
|
+
this._temporalBindGroups[hash] = bindGroup;
|
|
530
|
+
}
|
|
531
|
+
bindGroup.setValue('targetSize', new Vector4(width, height, depth.width, depth.height));
|
|
532
|
+
bindGroup.setValue('temporalParams', new Vector4(ctx.camera.ssgiTemporalWeight, ctx.camera.ssgiDepthReject, ctx.camera.ssgiNormalReject, ctx.camera.getFarPlane()));
|
|
533
|
+
bindGroup.setValue('flip', this.needFlip(ctx.device) ? 1 : 0);
|
|
534
|
+
ctx.device.setProgram(program);
|
|
535
|
+
ctx.device.setBindGroup(0, bindGroup);
|
|
536
|
+
this.drawFullscreenQuad(AbstractPostEffect.getDefaultRenderState(ctx, 'always'));
|
|
537
|
+
}
|
|
538
|
+
/** @internal */ atrous(ctx, source, aoSource, moments, depth, normal, width, height, step, hasTemporalHistory) {
|
|
539
|
+
let program = SSGI._atrousProgram;
|
|
540
|
+
if (!program) {
|
|
541
|
+
program = this.createAtrousProgram(ctx);
|
|
542
|
+
SSGI._atrousProgram = program;
|
|
543
|
+
}
|
|
544
|
+
const hash = `atrous:${source.uid}:${aoSource.uid}:${moments.uid}:${depth.uid}:${normal.uid}`;
|
|
545
|
+
let bindGroup = this._atrousBindGroups[hash];
|
|
546
|
+
if (!bindGroup) {
|
|
547
|
+
bindGroup = ctx.device.createBindGroup(program.bindGroupLayouts[0]);
|
|
548
|
+
bindGroup.setTexture('sourceTex', source, fetchSampler('clamp_nearest_nomip'));
|
|
549
|
+
bindGroup.setTexture('aoSourceTex', aoSource, fetchSampler('clamp_nearest_nomip'));
|
|
550
|
+
bindGroup.setTexture('momentsTex', moments, fetchSampler('clamp_nearest_nomip'));
|
|
551
|
+
bindGroup.setTexture('depthTex', depth, fetchSampler('clamp_nearest_nomip'));
|
|
552
|
+
bindGroup.setTexture('normalTex', normal, fetchSampler('clamp_nearest_nomip'));
|
|
553
|
+
this._atrousBindGroups[hash] = bindGroup;
|
|
554
|
+
}
|
|
555
|
+
bindGroup.setValue('targetSize', new Vector4(width, height, depth.width, depth.height));
|
|
556
|
+
bindGroup.setValue('filterParams', new Vector4(step, Math.max(0.001, ctx.camera.ssgiDepthReject), Math.max(1, ctx.camera.ssgiNormalReject * 32), 4));
|
|
557
|
+
// Number of accumulated frames a pixel needs before its variance estimate is
|
|
558
|
+
// trusted. Zero disables the ramp for paths that never build history, where
|
|
559
|
+
// the stored length is pinned at 1 and would read as permanently new.
|
|
560
|
+
bindGroup.setValue('denoiseParams', new Vector4(hasTemporalHistory ? 8 : 0, 0, 0, 0));
|
|
561
|
+
bindGroup.setValue('cameraFar', ctx.camera.getFarPlane());
|
|
562
|
+
bindGroup.setValue('flip', this.needFlip(ctx.device) ? 1 : 0);
|
|
563
|
+
ctx.device.setProgram(program);
|
|
564
|
+
ctx.device.setBindGroup(0, bindGroup);
|
|
565
|
+
this.drawFullscreenQuad(AbstractPostEffect.getDefaultRenderState(ctx, 'always'));
|
|
566
|
+
}
|
|
567
|
+
/** @internal */ writeSurface(ctx, depth, normal) {
|
|
568
|
+
let program = SSGI._surfaceProgram;
|
|
569
|
+
if (!program) {
|
|
570
|
+
program = this.createSurfaceProgram(ctx);
|
|
571
|
+
SSGI._surfaceProgram = program;
|
|
572
|
+
}
|
|
573
|
+
const hash = `${depth.uid}:${normal.uid}`;
|
|
574
|
+
let bindGroup = this._surfaceBindGroups[hash];
|
|
575
|
+
if (!bindGroup) {
|
|
576
|
+
bindGroup = ctx.device.createBindGroup(program.bindGroupLayouts[0]);
|
|
577
|
+
bindGroup.setTexture('depthTex', depth, fetchSampler('clamp_nearest_nomip'));
|
|
578
|
+
bindGroup.setTexture('normalTex', normal, fetchSampler('clamp_nearest_nomip'));
|
|
579
|
+
bindGroup.setValue('targetSize', new Vector2(depth.width, depth.height));
|
|
580
|
+
this._surfaceBindGroups[hash] = bindGroup;
|
|
581
|
+
}
|
|
582
|
+
bindGroup.setValue('cameraFar', ctx.camera.getFarPlane());
|
|
583
|
+
bindGroup.setValue('flip', this.needFlip(ctx.device) ? 1 : 0);
|
|
584
|
+
ctx.device.setProgram(program);
|
|
585
|
+
ctx.device.setBindGroup(0, bindGroup);
|
|
586
|
+
this.drawFullscreenQuad(AbstractPostEffect.getDefaultRenderState(ctx, 'always'));
|
|
587
|
+
}
|
|
588
|
+
/** @internal */ upsample(ctx, source, aoSource, depth, normal) {
|
|
589
|
+
let program = SSGI._upsampleProgram;
|
|
590
|
+
if (!program) {
|
|
591
|
+
program = this.createUpsampleProgram(ctx);
|
|
592
|
+
SSGI._upsampleProgram = program;
|
|
593
|
+
}
|
|
594
|
+
const hash = `${source.uid}:${aoSource.uid}:${depth.uid}:${normal.uid}`;
|
|
595
|
+
let bindGroup = this._upsampleBindGroups[hash];
|
|
596
|
+
if (!bindGroup) {
|
|
597
|
+
bindGroup = ctx.device.createBindGroup(program.bindGroupLayouts[0]);
|
|
598
|
+
bindGroup.setTexture('sourceTex', source, fetchSampler('clamp_nearest_nomip'));
|
|
599
|
+
bindGroup.setTexture('aoSourceTex', aoSource, fetchSampler('clamp_nearest_nomip'));
|
|
600
|
+
bindGroup.setTexture('depthTex', depth, fetchSampler('clamp_nearest_nomip'));
|
|
601
|
+
bindGroup.setTexture('normalTex', normal, fetchSampler('clamp_nearest_nomip'));
|
|
602
|
+
bindGroup.setValue('targetSize', new Vector4(source.width, source.height, depth.width, depth.height));
|
|
603
|
+
this._upsampleBindGroups[hash] = bindGroup;
|
|
604
|
+
}
|
|
605
|
+
bindGroup.setValue('guideParams', new Vector4(Math.max(0.001, ctx.camera.ssgiDepthReject), Math.max(1, ctx.camera.ssgiNormalReject * 32), ctx.camera.getFarPlane(), 0));
|
|
606
|
+
bindGroup.setValue('flip', this.needFlip(ctx.device) ? 1 : 0);
|
|
607
|
+
ctx.device.setProgram(program);
|
|
608
|
+
ctx.device.setBindGroup(0, bindGroup);
|
|
609
|
+
this.drawFullscreenQuad(AbstractPostEffect.getDefaultRenderState(ctx, 'always'));
|
|
610
|
+
}
|
|
611
|
+
/** @internal */ createTraceProgram(ctx, useHiZ, sampleHistory) {
|
|
612
|
+
const raysPerPixel = ctx.camera.ssgiResolvedSettings.raysPerPixel;
|
|
613
|
+
const program = ctx.device.buildRenderProgram({
|
|
614
|
+
vertex (pb) {
|
|
615
|
+
this.flip = pb.int().uniform(0);
|
|
616
|
+
this.$inputs.pos = pb.vec2().attrib('position');
|
|
617
|
+
this.$outputs.uv = pb.vec2();
|
|
618
|
+
pb.main(function() {
|
|
619
|
+
this.$builtins.position = pb.vec4(this.$inputs.pos, DEPTH_FARTHEST, 1);
|
|
620
|
+
this.$outputs.uv = pb.add(pb.mul(this.$inputs.pos.xy, 0.5), pb.vec2(0.5));
|
|
621
|
+
this.$if(pb.notEqual(this.flip, 0), function() {
|
|
622
|
+
this.$builtins.position.y = pb.neg(this.$builtins.position.y);
|
|
623
|
+
});
|
|
624
|
+
});
|
|
625
|
+
},
|
|
626
|
+
fragment (pb) {
|
|
627
|
+
this.currentColorTex = pb.tex2D().uniform(0);
|
|
628
|
+
// Linear depth is r32f (or rg32f when thickness is enabled). These
|
|
629
|
+
// formats are not filterable without the optional float32-filterable
|
|
630
|
+
// feature, and all SSGI depth reads use a nearest sampler.
|
|
631
|
+
this.depthTex = pb.tex2D().sampleType('unfilterable-float').uniform(0);
|
|
632
|
+
this.normalTex = pb.tex2D().uniform(0);
|
|
633
|
+
if (sampleHistory) {
|
|
634
|
+
this.previousColorTex = pb.tex2D().uniform(0);
|
|
635
|
+
this.motionTex = pb.tex2D().uniform(0);
|
|
636
|
+
this.previousSurfaceTex = pb.tex2D().uniform(0);
|
|
637
|
+
this.historyRejectParams = pb.vec2().uniform(0);
|
|
638
|
+
}
|
|
639
|
+
if (useHiZ) {
|
|
640
|
+
// Hi-Z is stored as r32f. It is only read with a nearest sampler,
|
|
641
|
+
// so use the unfilterable sample type for adapters without the
|
|
642
|
+
// optional float32-filterable feature (notably some AMD iGPUs).
|
|
643
|
+
this.hizTex = pb.tex2D().sampleType('unfilterable-float').uniform(0);
|
|
644
|
+
this.depthMipLevels = pb.int().uniform(0);
|
|
645
|
+
}
|
|
646
|
+
this.cameraNearFar = pb.vec2().uniform(0);
|
|
647
|
+
this.viewMatrix = pb.mat4().uniform(0);
|
|
648
|
+
this.invViewMatrix = pb.mat4().uniform(0);
|
|
649
|
+
this.projMatrix = pb.mat4().uniform(0);
|
|
650
|
+
this.invProjMatrix = pb.mat4().uniform(0);
|
|
651
|
+
this.traceParams = pb.vec4().uniform(0);
|
|
652
|
+
this.targetSize = pb.vec4().uniform(0);
|
|
653
|
+
this.radianceParams = pb.vec4().uniform(0);
|
|
654
|
+
this.skyOcclusion = pb.float().uniform(0);
|
|
655
|
+
ctx.env.light.envLight.initShaderBindings(pb);
|
|
656
|
+
this.$outputs.outColor = pb.vec4();
|
|
657
|
+
this.$outputs.outAO = pb.vec4();
|
|
658
|
+
pb.func('SSGI_getPosition', [
|
|
659
|
+
pb.vec2('uv')
|
|
660
|
+
], function() {
|
|
661
|
+
this.$l.linearDepth = ShaderHelper.sampleLinearDepth(this, this.depthTex, this.uv, 0);
|
|
662
|
+
this.$l.nonLinearDepth = ShaderHelper.linearNormalizedToNonLinearDepth(this, this.linearDepth, this.cameraNearFar);
|
|
663
|
+
this.$l.clipPos = pb.vec4(pb.sub(pb.mul(this.uv, 2), pb.vec2(1)), ShaderHelper.deviceDepthToClipZ(this, pb.clamp(this.nonLinearDepth, 0, 1)), 1);
|
|
664
|
+
this.$l.viewPos = pb.mul(this.invProjMatrix, this.clipPos);
|
|
665
|
+
this.$return(pb.vec4(pb.div(this.viewPos.xyz, this.viewPos.w), this.linearDepth));
|
|
666
|
+
});
|
|
667
|
+
pb.func('SSGI_hash22', [
|
|
668
|
+
pb.vec2('p'),
|
|
669
|
+
pb.float('seed')
|
|
670
|
+
], function() {
|
|
671
|
+
this.$l.q = pb.vec2(pb.dot(this.p, pb.vec2(127.1, 311.7)), pb.dot(this.p, pb.vec2(269.5, 183.3)));
|
|
672
|
+
this.$return(pb.fract(pb.mul(pb.sin(pb.add(this.q, this.seed)), 43758.5453)));
|
|
673
|
+
});
|
|
674
|
+
pb.func('SSGI_cosineDirection', [
|
|
675
|
+
pb.vec3('n'),
|
|
676
|
+
pb.vec2('xi')
|
|
677
|
+
], function() {
|
|
678
|
+
this.$l.up = this.$choice(pb.lessThan(pb.abs(this.n.z), 0.999), pb.vec3(0, 0, 1), pb.vec3(1, 0, 0));
|
|
679
|
+
this.$l.tangent = pb.normalize(pb.cross(this.up, this.n));
|
|
680
|
+
this.$l.bitangent = pb.cross(this.n, this.tangent);
|
|
681
|
+
this.$l.r = pb.sqrt(this.xi.x);
|
|
682
|
+
this.$l.phi = pb.mul(2 * Math.PI, this.xi.y);
|
|
683
|
+
this.$l.local = pb.vec3(pb.mul(this.r, pb.cos(this.phi)), pb.mul(this.r, pb.sin(this.phi)), pb.sqrt(pb.max(0, pb.sub(1, this.xi.x))));
|
|
684
|
+
this.$return(pb.normalize(pb.add(pb.mul(this.tangent, this.local.x), pb.mul(this.bitangent, this.local.y), pb.mul(this.n, this.local.z))));
|
|
685
|
+
});
|
|
686
|
+
pb.main(function() {
|
|
687
|
+
this.$l.uv = pb.div(pb.vec2(this.$builtins.fragCoord.xy), this.targetSize.xy);
|
|
688
|
+
this.$l.pos = this.SSGI_getPosition(this.uv);
|
|
689
|
+
this.$if(pb.greaterThanEqual(this.pos.w, 1), function() {
|
|
690
|
+
this.$outputs.outColor = pb.vec4(0);
|
|
691
|
+
// Sky is unoccluded. Its irradiance validity is zero, so this value
|
|
692
|
+
// is never read as a weight, but it must not be a dark texel: the
|
|
693
|
+
// a-trous and upsample kernels reach across the silhouette.
|
|
694
|
+
this.$outputs.outAO = pb.vec4(1);
|
|
695
|
+
}).$else(function() {
|
|
696
|
+
this.$l.worldNormal = pb.normalize(pb.sub(pb.mul(pb.textureSampleLevel(this.normalTex, this.uv, 0).rgb, 2), pb.vec3(1)));
|
|
697
|
+
this.$l.viewNormal = pb.normalize(pb.mul(this.viewMatrix, pb.vec4(this.worldNormal, 0)).xyz);
|
|
698
|
+
// Use the SH-integrated diffuse IBL as an exact, noise-free
|
|
699
|
+
// baseline. Stochastic rays only estimate the screen-space
|
|
700
|
+
// replacement relative to the environment in the same direction.
|
|
701
|
+
//
|
|
702
|
+
// getIrradiance returns the cosine-convolved SH, which already
|
|
703
|
+
// carries the 1/PI of the Lambert BRDF: materials consume it as
|
|
704
|
+
// k_D * irradiance with no further division, matching punctual
|
|
705
|
+
// lights that apply 1/PI explicitly. The published history is mixed
|
|
706
|
+
// against that same value in the lighting pass, so this baseline
|
|
707
|
+
// must stay in those units and must not be scaled by PI.
|
|
708
|
+
this.$l.iblIrradiance = pb.mul(ctx.env.light.envLight.getIrradiance(this, this.worldNormal), this.radianceParams.z);
|
|
709
|
+
this.$l.correctionSum = pb.vec3(0);
|
|
710
|
+
// Rays whose outcome the depth buffer cannot resolve (they left the
|
|
711
|
+
// screen or ran out of iterations behind geometry) are excluded from
|
|
712
|
+
// the average instead of counted as unoccluded sky.
|
|
713
|
+
this.$l.determinateSum = pb.float(0);
|
|
714
|
+
this.$l.escapedSum = pb.float(0);
|
|
715
|
+
// Ambient occlusion reuses the very same visibility measurement the
|
|
716
|
+
// sky removal is built on, so the two can never disagree. No extra
|
|
717
|
+
// ray march: only this accumulator is new.
|
|
718
|
+
this.$l.occludedSum = pb.float(0);
|
|
719
|
+
this.$for(pb.float('rayIndex'), 0, raysPerPixel, function() {
|
|
720
|
+
this.$l.xi = this.SSGI_hash22(pb.add(pb.mul(this.uv, this.targetSize.xy), pb.vec2(this.rayIndex, pb.mul(this.rayIndex, 7))), pb.add(this.radianceParams.w, pb.mul(this.rayIndex, 17)));
|
|
721
|
+
this.$l.worldRay = this.SSGI_cosineDirection(this.worldNormal, this.xi);
|
|
722
|
+
this.$l.viewRay = pb.normalize(pb.mul(this.viewMatrix, pb.vec4(this.worldRay, 0)).xyz);
|
|
723
|
+
this.$l.rayOrigin = pb.add(this.pos.xyz, pb.mul(this.viewNormal, this.traceParams.y, 0.25));
|
|
724
|
+
// vec3(occluded, escaped, rawConfidence) - see screenSpaceRayTracing_Linear2D.
|
|
725
|
+
this.$l.giTrace = pb.vec3(0);
|
|
726
|
+
if (useHiZ) {
|
|
727
|
+
// UE5 SSGI: diffuse rays march with RayRoughness = 1 (fast mip
|
|
728
|
+
// ramp) and StepOffset = noise - 0.9. A dedicated hash keeps
|
|
729
|
+
// the jitter decorrelated from the direction sample.
|
|
730
|
+
this.$l.stepOffset = pb.sub(this.SSGI_hash22(pb.add(pb.mul(this.uv, this.targetSize.xy), pb.vec2(pb.mul(this.rayIndex, 23), 11)), pb.add(this.radianceParams.w, pb.mul(this.rayIndex, 29))).x, 0.9);
|
|
731
|
+
this.$l.hit = screenSpaceRayTracing_HiZ(this, this.rayOrigin, this.viewRay, this.viewMatrix, this.projMatrix, this.invProjMatrix, this.cameraNearFar, this.depthMipLevels, this.traceParams.z, this.traceParams.x, this.traceParams.y, this.targetSize, this.hizTex, this.normalTex, this.giTrace, 1, this.stepOffset);
|
|
732
|
+
} else {
|
|
733
|
+
this.$l.hit = screenSpaceRayTracing_Linear2D(this, this.rayOrigin, this.viewRay, this.viewMatrix, this.projMatrix, this.invProjMatrix, this.cameraNearFar, this.traceParams.x, this.traceParams.z, this.traceParams.y, this.traceParams.w, this.targetSize, this.depthTex, this.normalTex, false, this.giTrace);
|
|
734
|
+
}
|
|
735
|
+
// Grazing/projectively degenerate rays can return Inf or NaN.
|
|
736
|
+
// A zero weight is not sufficient because NaN * 0 remains NaN and
|
|
737
|
+
// the spatial passes then spread it into a dark block.
|
|
738
|
+
this.$l.hitFinite = pb.all(pb.lessThan(pb.abs(this.hit), pb.vec4(1e30)));
|
|
739
|
+
// Visibility and radiance are kept apart: whether the ray was
|
|
740
|
+
// blocked is independent of whether the blocker's outgoing
|
|
741
|
+
// radiance can be read back from the screen. Geometric certainty
|
|
742
|
+
// gates occlusion so a thin-object false positive cannot darken,
|
|
743
|
+
// while the hit's screen colour is always a usable radiance
|
|
744
|
+
// estimate because the marcher only ever stops on-screen.
|
|
745
|
+
this.$l.occluded = pb.mul(pb.float(this.hitFinite), pb.clamp(this.giTrace.x, 0, 1), pb.clamp(this.giTrace.z, 0, 1));
|
|
746
|
+
this.$l.escaped = pb.mul(pb.float(this.hitFinite), pb.clamp(this.giTrace.y, 0, 1));
|
|
747
|
+
this.$l.hitUV = this.$choice(pb.greaterThan(this.occluded, 0), pb.clamp(this.hit.xy, pb.vec2(0), pb.vec2(1)), this.uv);
|
|
748
|
+
this.$l.rawEnvRadiance = pb.mul(ctx.env.light.envLight.getRadiance(this, this.worldRay, pb.float(0)), this.radianceParams.z);
|
|
749
|
+
this.$l.envRadiance = this.$choice(pb.all(pb.lessThan(pb.abs(this.rawEnvRadiance), pb.vec3(1e30))), this.rawEnvRadiance, pb.vec3(0));
|
|
750
|
+
this.$l.currentRadiance = pb.textureSampleLevel(this.currentColorTex, this.hitUV, 0).rgb;
|
|
751
|
+
if (sampleHistory) {
|
|
752
|
+
this.$l.hitMotion = pb.textureSampleLevel(this.motionTex, this.hitUV, 0).xy;
|
|
753
|
+
this.$l.previousHitUV = pb.sub(this.hitUV, this.hitMotion);
|
|
754
|
+
this.$l.historyValid = pb.and(pb.all(pb.greaterThanEqual(this.previousHitUV, pb.vec2(0))), pb.all(pb.lessThanEqual(this.previousHitUV, pb.vec2(1))), pb.all(pb.lessThan(pb.abs(this.hitMotion), pb.vec2(5e4))));
|
|
755
|
+
this.$l.historyRadiance = pb.textureSampleLevel(this.previousColorTex, pb.clamp(this.previousHitUV, pb.vec2(0), pb.vec2(1)), 0).rgb;
|
|
756
|
+
this.$l.currentHitDepth = pb.mul(ShaderHelper.sampleLinearDepth(this, this.depthTex, this.hitUV, 0), this.cameraNearFar.y);
|
|
757
|
+
this.$l.currentHitNormal = pb.normalize(pb.sub(pb.mul(pb.textureSampleLevel(this.normalTex, this.hitUV, 0).rgb, 2), pb.vec3(1)));
|
|
758
|
+
this.$l.previousHitSurface = pb.textureSampleLevel(this.previousSurfaceTex, pb.clamp(this.previousHitUV, pb.vec2(0), pb.vec2(1)), 0);
|
|
759
|
+
this.$l.previousHitNormal = pb.normalize(pb.sub(pb.mul(this.previousHitSurface.rgb, 2), pb.vec3(1)));
|
|
760
|
+
this.historyValid = pb.and(this.historyValid, pb.lessThanEqual(pb.abs(pb.sub(this.previousHitSurface.a, this.currentHitDepth)), this.historyRejectParams.x), pb.greaterThanEqual(pb.dot(this.currentHitNormal, this.previousHitNormal), this.historyRejectParams.y));
|
|
761
|
+
// Prefer the reprojected previous frame for multi-bounce
|
|
762
|
+
// feedback, but fall back to this frame's already-shaded opaque
|
|
763
|
+
// colour rather than dropping the sample. Losing one bounce of
|
|
764
|
+
// feedback is a far smaller error than reverting to unoccluded
|
|
765
|
+
// IBL, which is what made newly disoccluded regions flash.
|
|
766
|
+
this.$l.screenRadiance = this.$choice(this.historyValid, this.historyRadiance, this.currentRadiance);
|
|
767
|
+
} else {
|
|
768
|
+
this.$l.screenRadiance = this.currentRadiance;
|
|
769
|
+
}
|
|
770
|
+
this.$l.screenRadianceFinite = pb.all(pb.lessThan(pb.abs(this.screenRadiance), pb.vec3(1e30)));
|
|
771
|
+
this.screenRadiance = this.$choice(this.screenRadianceFinite, this.screenRadiance, this.envRadiance);
|
|
772
|
+
this.$l.maxComponent = pb.max(pb.max(this.screenRadiance.r, this.screenRadiance.g), this.screenRadiance.b);
|
|
773
|
+
this.$l.fireflyScale = this.$choice(pb.greaterThan(this.maxComponent, this.radianceParams.y), pb.div(this.radianceParams.y, pb.max(this.maxComponent, 1e-5)), pb.float(1));
|
|
774
|
+
this.$l.clampedScreenRadiance = pb.mul(this.screenRadiance, this.fireflyScale);
|
|
775
|
+
// Split the correction into the bounce the blocker adds and the sky
|
|
776
|
+
// it takes away. The bounce is always kept: the hit is on-screen, so
|
|
777
|
+
// its colour is a real measurement. Only the removal is scaled by
|
|
778
|
+
// skyOcclusion, which lets the sky be dimmed less than physically
|
|
779
|
+
// implied without also discarding measured bounce light.
|
|
780
|
+
this.$l.bounceGain = pb.max(pb.sub(this.clampedScreenRadiance, this.envRadiance), pb.vec3(0));
|
|
781
|
+
this.$l.skyLoss = pb.mul(pb.max(pb.sub(this.envRadiance, this.clampedScreenRadiance), pb.vec3(0)), this.skyOcclusion);
|
|
782
|
+
// Cosine-weighted sampling makes the estimator of E/PI - the unit
|
|
783
|
+
// the baseline and the lighting pass both use - the plain mean of
|
|
784
|
+
// the sampled radiance, so no PI appears here either.
|
|
785
|
+
this.$l.correction = pb.mul(pb.sub(this.bounceGain, this.skyLoss), this.occluded, this.radianceParams.x);
|
|
786
|
+
this.correctionSum = pb.add(this.correctionSum, this.correction);
|
|
787
|
+
this.determinateSum = pb.add(this.determinateSum, pb.max(this.occluded, this.escaped));
|
|
788
|
+
this.escapedSum = pb.add(this.escapedSum, this.escaped);
|
|
789
|
+
this.occludedSum = pb.add(this.occludedSum, this.occluded);
|
|
790
|
+
});
|
|
791
|
+
// Average over resolved rays only. Dividing by raysPerPixel would
|
|
792
|
+
// implicitly treat every indeterminate ray as an unoccluded sky
|
|
793
|
+
// sample, which is the systematic sky leak this pass has to avoid.
|
|
794
|
+
this.$l.determinateCount = pb.max(this.determinateSum, 1);
|
|
795
|
+
this.$l.irradiance = pb.add(this.iblIrradiance, pb.div(this.correctionSum, this.determinateCount));
|
|
796
|
+
// radianceParams.x blends the traced estimate over the IBL baseline. A finite-ray
|
|
797
|
+
// correction can have much higher variance than the integrated physical sky and must
|
|
798
|
+
// not remove more than that blend permits (at the default 0.7, at least 30% remains).
|
|
799
|
+
// That reserved share is itself scaled by measured visibility, otherwise no amount of
|
|
800
|
+
// occlusion could ever darken an enclosed corner.
|
|
801
|
+
this.$l.minimumIrradiance = pb.mul(this.iblIrradiance, pb.max(0, pb.sub(1, this.radianceParams.x)), pb.mix(pb.float(1), pb.div(this.escapedSum, this.determinateCount), this.skyOcclusion));
|
|
802
|
+
// Clamp before the render-target conversion: a finite value that overflows becomes Inf
|
|
803
|
+
// in the texture, and every a-trous pass then expands the contaminated region by its
|
|
804
|
+
// kernel radius. Pre-exposed lighting makes this unlikely, but a bright emissive hit
|
|
805
|
+
// amplified by a low-probability ray can still reach it.
|
|
806
|
+
this.$l.boundedIrradiance = pb.clamp(pb.max(this.irradiance, this.minimumIrradiance), pb.vec3(0), pb.vec3(HALF_FLOAT_MAX));
|
|
807
|
+
this.$outputs.outColor = pb.vec4(this.boundedIrradiance, 1);
|
|
808
|
+
// Cosine-weighted sampling makes the mean of `occluded` over the
|
|
809
|
+
// resolved rays an unbiased estimate of cosine-weighted visibility.
|
|
810
|
+
// Averaging over the determinate set - not raysPerPixel - is what
|
|
811
|
+
// keeps an indeterminate ray from reading as unoccluded, the same
|
|
812
|
+
// reasoning the irradiance average uses above.
|
|
813
|
+
this.$outputs.outAO = pb.vec4(pb.clamp(pb.sub(1, pb.div(this.occludedSum, this.determinateCount)), 0, 1));
|
|
814
|
+
});
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
});
|
|
818
|
+
program.name = '@SSGI_Trace';
|
|
819
|
+
return program;
|
|
820
|
+
}
|
|
821
|
+
/** @internal */ createTemporalProgram(ctx, hasHistory) {
|
|
822
|
+
const program = ctx.device.buildRenderProgram({
|
|
823
|
+
vertex (pb) {
|
|
824
|
+
this.flip = pb.int().uniform(0);
|
|
825
|
+
this.$inputs.pos = pb.vec2().attrib('position');
|
|
826
|
+
this.$outputs.uv = pb.vec2();
|
|
827
|
+
pb.main(function() {
|
|
828
|
+
this.$builtins.position = pb.vec4(this.$inputs.pos, DEPTH_FARTHEST, 1);
|
|
829
|
+
this.$outputs.uv = pb.add(pb.mul(this.$inputs.pos.xy, 0.5), pb.vec2(0.5));
|
|
830
|
+
this.$if(pb.notEqual(this.flip, 0), function() {
|
|
831
|
+
this.$builtins.position.y = pb.neg(this.$builtins.position.y);
|
|
832
|
+
});
|
|
833
|
+
});
|
|
834
|
+
},
|
|
835
|
+
fragment (pb) {
|
|
836
|
+
this.currentTex = pb.tex2D().uniform(0);
|
|
837
|
+
this.currentAOTex = pb.tex2D().uniform(0);
|
|
838
|
+
this.depthTex = pb.tex2D().sampleType('unfilterable-float').uniform(0);
|
|
839
|
+
this.normalTex = pb.tex2D().uniform(0);
|
|
840
|
+
if (hasHistory) {
|
|
841
|
+
this.motionTex = pb.tex2D().uniform(0);
|
|
842
|
+
this.previousIrradianceTex = pb.tex2D().uniform(0);
|
|
843
|
+
this.previousSurfaceTex = pb.tex2D().uniform(0);
|
|
844
|
+
this.previousMomentsTex = pb.tex2D().uniform(0);
|
|
845
|
+
this.previousAOTex = pb.tex2D().uniform(0);
|
|
846
|
+
}
|
|
847
|
+
this.targetSize = pb.vec4().uniform(0);
|
|
848
|
+
this.temporalParams = pb.vec4().uniform(0);
|
|
849
|
+
this.$outputs.outIrradiance = pb.vec4();
|
|
850
|
+
this.$outputs.outMoments = pb.vec4();
|
|
851
|
+
this.$outputs.outAO = pb.vec4();
|
|
852
|
+
pb.func('SSGI_luminance', [
|
|
853
|
+
pb.vec3('c')
|
|
854
|
+
], function() {
|
|
855
|
+
this.$l.boundedLuminance = pb.clamp(pb.dot(this.c, pb.vec3(0.2126, 0.7152, 0.0722)), 0, MOMENT_LUMINANCE_MAX);
|
|
856
|
+
this.$return(this.boundedLuminance);
|
|
857
|
+
});
|
|
858
|
+
pb.main(function() {
|
|
859
|
+
this.$l.uv = pb.div(pb.vec2(this.$builtins.fragCoord.xy), this.targetSize.xy);
|
|
860
|
+
this.$l.current = pb.textureSampleLevel(this.currentTex, this.uv, 0);
|
|
861
|
+
this.$l.currentAO = pb.textureSampleLevel(this.currentAOTex, this.uv, 0).r;
|
|
862
|
+
this.$l.result = this.current.rgb;
|
|
863
|
+
this.$l.resultAO = this.currentAO;
|
|
864
|
+
this.$l.luminance = this.SSGI_luminance(this.current.rgb);
|
|
865
|
+
this.$l.moment = pb.vec4(this.luminance, pb.mul(this.luminance, this.luminance), 1, 0);
|
|
866
|
+
if (hasHistory) {
|
|
867
|
+
this.$l.motion = pb.textureSampleLevel(this.motionTex, this.uv, 0).xy;
|
|
868
|
+
this.$l.previousUV = pb.sub(this.uv, this.motion);
|
|
869
|
+
this.$l.validUV = pb.and(pb.all(pb.greaterThanEqual(this.previousUV, pb.vec2(0))), pb.all(pb.lessThanEqual(this.previousUV, pb.vec2(1))), pb.all(pb.lessThan(pb.abs(this.motion), pb.vec2(5e4))));
|
|
870
|
+
this.$l.currentDepth = pb.mul(ShaderHelper.sampleLinearDepth(this, this.depthTex, this.uv, 0), this.temporalParams.w);
|
|
871
|
+
this.$l.currentNormal = pb.normalize(pb.sub(pb.mul(pb.textureSampleLevel(this.normalTex, this.uv, 0).rgb, 2), pb.vec3(1)));
|
|
872
|
+
this.$l.previousSurface = pb.textureSampleLevel(this.previousSurfaceTex, pb.clamp(this.previousUV, pb.vec2(0), pb.vec2(1)), 0);
|
|
873
|
+
this.$l.previousNormal = pb.normalize(pb.sub(pb.mul(this.previousSurface.rgb, 2), pb.vec3(1)));
|
|
874
|
+
this.$l.surfaceValid = pb.and(pb.lessThanEqual(pb.abs(pb.sub(this.previousSurface.a, this.currentDepth)), this.temporalParams.y), pb.greaterThanEqual(pb.dot(this.currentNormal, this.previousNormal), this.temporalParams.z));
|
|
875
|
+
this.$l.neighborhoodMin = this.current.rgb;
|
|
876
|
+
this.$l.neighborhoodMax = this.current.rgb;
|
|
877
|
+
this.$l.aoNeighborhoodMin = this.currentAO;
|
|
878
|
+
this.$l.aoNeighborhoodMax = this.currentAO;
|
|
879
|
+
for(let y = -1; y <= 1; y++){
|
|
880
|
+
for(let x = -1; x <= 1; x++){
|
|
881
|
+
if (x === 0 && y === 0) {
|
|
882
|
+
continue;
|
|
883
|
+
}
|
|
884
|
+
this.$l.neighborUV = pb.add(this.uv, pb.div(pb.vec2(x, y), this.targetSize.xy));
|
|
885
|
+
this.$l.neighbor = pb.textureSampleLevel(this.currentTex, this.neighborUV, 0).rgb;
|
|
886
|
+
this.neighborhoodMin = pb.min(this.neighborhoodMin, this.neighbor);
|
|
887
|
+
this.neighborhoodMax = pb.max(this.neighborhoodMax, this.neighbor);
|
|
888
|
+
this.$l.aoNeighbor = pb.textureSampleLevel(this.currentAOTex, this.neighborUV, 0).r;
|
|
889
|
+
this.aoNeighborhoodMin = pb.min(this.aoNeighborhoodMin, this.aoNeighbor);
|
|
890
|
+
this.aoNeighborhoodMax = pb.max(this.aoNeighborhoodMax, this.aoNeighbor);
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
this.$l.previous = pb.textureSampleLevel(this.previousIrradianceTex, pb.clamp(this.previousUV, pb.vec2(0), pb.vec2(1)), 0);
|
|
894
|
+
this.$l.previousClamped = pb.clamp(this.previous.rgb, this.neighborhoodMin, this.neighborhoodMax);
|
|
895
|
+
this.$l.previousMoment = pb.textureSampleLevel(this.previousMomentsTex, pb.clamp(this.previousUV, pb.vec2(0), pb.vec2(1)), 0);
|
|
896
|
+
this.$l.validity = pb.mul(pb.float(pb.and(this.validUV, this.surfaceValid)), pb.clamp(this.previous.a, 0, 1));
|
|
897
|
+
// Start new histories as a running average, then cap their weight
|
|
898
|
+
// at the configured value once enough samples have accumulated.
|
|
899
|
+
this.$l.runningAverageWeight = pb.div(this.previousMoment.z, pb.add(this.previousMoment.z, 1));
|
|
900
|
+
this.$l.historyWeight = pb.min(this.temporalParams.x, this.runningAverageWeight);
|
|
901
|
+
this.$l.weight = pb.mul(this.historyWeight, this.validity);
|
|
902
|
+
this.result = pb.mix(this.current.rgb, this.previousClamped, this.weight);
|
|
903
|
+
// AO accumulates on exactly the same validity and weight as the
|
|
904
|
+
// irradiance: it was measured by the same rays against the same
|
|
905
|
+
// surface, so any disagreement between the two would be an artifact.
|
|
906
|
+
this.$l.previousAO = pb.textureSampleLevel(this.previousAOTex, pb.clamp(this.previousUV, pb.vec2(0), pb.vec2(1)), 0).r;
|
|
907
|
+
this.$l.previousAOClamped = pb.clamp(this.previousAO, this.aoNeighborhoodMin, this.aoNeighborhoodMax);
|
|
908
|
+
this.resultAO = pb.mix(this.currentAO, this.previousAOClamped, this.weight);
|
|
909
|
+
this.$l.momentXY = pb.mix(this.moment.xy, this.previousMoment.xy, this.weight);
|
|
910
|
+
this.$l.historyLength = pb.mix(pb.float(1), pb.min(32, pb.add(this.previousMoment.z, 1)), this.validity);
|
|
911
|
+
this.$l.variance = pb.max(0, pb.sub(this.momentXY.y, pb.mul(this.momentXY.x, this.momentXY.x)));
|
|
912
|
+
this.moment = pb.vec4(this.momentXY, this.historyLength, this.variance);
|
|
913
|
+
} else {
|
|
914
|
+
// The WebGL fallback has no motion-vector temporal resolve. Seed
|
|
915
|
+
// a-trous with spatial variance so its luminance weighting can
|
|
916
|
+
// still remove Monte Carlo noise instead of treating it as an edge.
|
|
917
|
+
this.$l.spatialLum = pb.float(0);
|
|
918
|
+
this.$l.spatialLum2 = pb.float(0);
|
|
919
|
+
for(let y = -1; y <= 1; y++){
|
|
920
|
+
for(let x = -1; x <= 1; x++){
|
|
921
|
+
this.$l.neighborLum = this.SSGI_luminance(pb.textureSampleLevel(this.currentTex, pb.add(this.uv, pb.div(pb.vec2(x, y), this.targetSize.xy)), 0).rgb);
|
|
922
|
+
this.spatialLum = pb.add(this.spatialLum, this.neighborLum);
|
|
923
|
+
this.spatialLum2 = pb.add(this.spatialLum2, pb.mul(this.neighborLum, this.neighborLum));
|
|
924
|
+
}
|
|
925
|
+
}
|
|
926
|
+
this.spatialLum = pb.div(this.spatialLum, 9);
|
|
927
|
+
this.spatialLum2 = pb.div(this.spatialLum2, 9);
|
|
928
|
+
this.$l.spatialVariance = pb.max(0, pb.sub(this.spatialLum2, pb.mul(this.spatialLum, this.spatialLum)));
|
|
929
|
+
this.moment = pb.vec4(this.moment.xyz, this.spatialVariance);
|
|
930
|
+
}
|
|
931
|
+
this.$outputs.outIrradiance = pb.vec4(this.result, this.current.a);
|
|
932
|
+
this.$outputs.outMoments = this.moment;
|
|
933
|
+
this.$outputs.outAO = pb.vec4(pb.clamp(this.resultAO, 0, 1));
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
});
|
|
937
|
+
program.name = hasHistory ? '@SSGI_Temporal' : '@SSGI_TemporalInitialize';
|
|
938
|
+
return program;
|
|
939
|
+
}
|
|
940
|
+
/** @internal */ createAtrousProgram(ctx) {
|
|
941
|
+
const program = ctx.device.buildRenderProgram({
|
|
942
|
+
vertex (pb) {
|
|
943
|
+
this.flip = pb.int().uniform(0);
|
|
944
|
+
this.$inputs.pos = pb.vec2().attrib('position');
|
|
945
|
+
this.$outputs.uv = pb.vec2();
|
|
946
|
+
pb.main(function() {
|
|
947
|
+
this.$builtins.position = pb.vec4(this.$inputs.pos, DEPTH_FARTHEST, 1);
|
|
948
|
+
this.$outputs.uv = pb.add(pb.mul(this.$inputs.pos.xy, 0.5), pb.vec2(0.5));
|
|
949
|
+
this.$if(pb.notEqual(this.flip, 0), function() {
|
|
950
|
+
this.$builtins.position.y = pb.neg(this.$builtins.position.y);
|
|
951
|
+
});
|
|
952
|
+
});
|
|
953
|
+
},
|
|
954
|
+
fragment (pb) {
|
|
955
|
+
this.sourceTex = pb.tex2D().uniform(0);
|
|
956
|
+
this.aoSourceTex = pb.tex2D().uniform(0);
|
|
957
|
+
this.momentsTex = pb.tex2D().uniform(0);
|
|
958
|
+
this.depthTex = pb.tex2D().sampleType('unfilterable-float').uniform(0);
|
|
959
|
+
this.normalTex = pb.tex2D().uniform(0);
|
|
960
|
+
this.targetSize = pb.vec4().uniform(0);
|
|
961
|
+
this.filterParams = pb.vec4().uniform(0);
|
|
962
|
+
this.denoiseParams = pb.vec4().uniform(0);
|
|
963
|
+
this.cameraFar = pb.float().uniform(0);
|
|
964
|
+
this.$outputs.outColor = pb.vec4();
|
|
965
|
+
this.$outputs.outAO = pb.vec4();
|
|
966
|
+
pb.func('SSGI_luminance', [
|
|
967
|
+
pb.vec3('c')
|
|
968
|
+
], function() {
|
|
969
|
+
this.$return(pb.max(0, pb.dot(this.c, pb.vec3(0.2126, 0.7152, 0.0722))));
|
|
970
|
+
});
|
|
971
|
+
pb.main(function() {
|
|
972
|
+
this.$l.uv = pb.div(pb.vec2(this.$builtins.fragCoord.xy), this.targetSize.xy);
|
|
973
|
+
this.$l.center = pb.textureSampleLevel(this.sourceTex, this.uv, 0);
|
|
974
|
+
this.$l.centerDepth = pb.mul(ShaderHelper.sampleLinearDepth(this, this.depthTex, this.uv, 0), this.cameraFar);
|
|
975
|
+
this.$l.centerNormal = pb.normalize(pb.sub(pb.mul(pb.textureSampleLevel(this.normalTex, this.uv, 0).rgb, 2), pb.vec3(1)));
|
|
976
|
+
this.$l.centerLum = this.SSGI_luminance(this.center.rgb);
|
|
977
|
+
this.$l.moments = pb.textureSampleLevel(this.momentsTex, this.uv, 0);
|
|
978
|
+
// A pixel with little accumulated history has an unreliable variance
|
|
979
|
+
// estimate, so the edge-stopping functions would preserve Monte Carlo
|
|
980
|
+
// noise as if it were detail. Widen the kernel and relax the luminance
|
|
981
|
+
// and normal tests until enough frames have converged, then hand back
|
|
982
|
+
// to the sharp weights (standard SVGF history-driven ramp).
|
|
983
|
+
//
|
|
984
|
+
// A path that never accumulates history (no temporal resolve, so
|
|
985
|
+
// moments.z is pinned at 1) is permanently unconverged, not converged:
|
|
986
|
+
// it is the noisiest input this filter ever sees. Sending it to the
|
|
987
|
+
// relaxed end is the whole point of the ramp - the sharp end would give
|
|
988
|
+
// it the narrowest kernel and one eighth the luminance tolerance.
|
|
989
|
+
this.$l.historyConfidence = this.$choice(pb.greaterThan(this.denoiseParams.x, 0), pb.clamp(pb.div(this.moments.z, this.denoiseParams.x), 0, 1), pb.float(0));
|
|
990
|
+
this.$l.stepScale = pb.mix(pb.float(2), pb.float(1), this.historyConfidence);
|
|
991
|
+
this.$l.effStep = pb.mul(this.filterParams.x, this.stepScale);
|
|
992
|
+
this.$l.normalPower = pb.mix(pb.max(1, pb.mul(this.filterParams.z, 0.25)), this.filterParams.z, this.historyConfidence);
|
|
993
|
+
this.$l.sigmaScale = pb.mix(pb.float(8), pb.float(1), this.historyConfidence);
|
|
994
|
+
this.$l.sigma = pb.mul(pb.add(pb.sqrt(pb.max(this.moments.w, 0)), 0.02), this.sigmaScale);
|
|
995
|
+
this.$l.centerAO = pb.textureSampleLevel(this.aoSourceTex, this.uv, 0).r;
|
|
996
|
+
this.$l.sum = pb.vec3(0);
|
|
997
|
+
this.$l.weightSum = pb.float(0);
|
|
998
|
+
this.$l.aoSum = pb.float(0);
|
|
999
|
+
this.$l.aoWeightSum = pb.float(0);
|
|
1000
|
+
const kernel = [
|
|
1001
|
+
1,
|
|
1002
|
+
2 / 3,
|
|
1003
|
+
1 / 6
|
|
1004
|
+
];
|
|
1005
|
+
for(let y = -2; y <= 2; y++){
|
|
1006
|
+
for(let x = -2; x <= 2; x++){
|
|
1007
|
+
const kernelWeight = kernel[Math.abs(x)] * kernel[Math.abs(y)];
|
|
1008
|
+
this.$l.sampleUV = pb.add(this.uv, pb.mul(pb.div(pb.vec2(x, y), this.targetSize.xy), this.effStep));
|
|
1009
|
+
this.$l.sampleValue = pb.textureSampleLevel(this.sourceTex, this.sampleUV, 0);
|
|
1010
|
+
this.$l.sampleDepth = pb.mul(ShaderHelper.sampleLinearDepth(this, this.depthTex, this.sampleUV, 0), this.cameraFar);
|
|
1011
|
+
this.$l.sampleNormal = pb.normalize(pb.sub(pb.mul(pb.textureSampleLevel(this.normalTex, this.sampleUV, 0).rgb, 2), pb.vec3(1)));
|
|
1012
|
+
this.$l.depthWeight = pb.exp(pb.neg(pb.div(pb.abs(pb.sub(this.sampleDepth, this.centerDepth)), pb.mul(this.filterParams.y, this.effStep))));
|
|
1013
|
+
this.$l.normalWeight = pb.pow(pb.max(0, pb.dot(this.centerNormal, this.sampleNormal)), this.normalPower);
|
|
1014
|
+
this.$l.colorWeight = pb.exp(pb.neg(pb.div(pb.abs(pb.sub(this.SSGI_luminance(this.sampleValue.rgb), this.centerLum)), pb.mul(this.sigma, this.filterParams.w))));
|
|
1015
|
+
this.$l.weight = pb.mul(kernelWeight, this.depthWeight, this.normalWeight, this.colorWeight, this.sampleValue.a);
|
|
1016
|
+
this.sum = pb.add(this.sum, pb.mul(this.sampleValue.rgb, this.weight));
|
|
1017
|
+
this.weightSum = pb.add(this.weightSum, this.weight);
|
|
1018
|
+
// AO deliberately drops colorWeight: that term is driven by the
|
|
1019
|
+
// moments' luminance variance, which describes the irradiance and
|
|
1020
|
+
// says nothing about AO. Leaving it out is a stronger smoothing,
|
|
1021
|
+
// which is what a per-ray binary quantity needs - with 1-2 rays per
|
|
1022
|
+
// pixel, single-frame AO is close to binary.
|
|
1023
|
+
this.$l.aoWeight = pb.mul(kernelWeight, this.depthWeight, this.normalWeight, this.sampleValue.a);
|
|
1024
|
+
this.$l.sampleAO = pb.textureSampleLevel(this.aoSourceTex, this.sampleUV, 0).r;
|
|
1025
|
+
this.aoSum = pb.add(this.aoSum, pb.mul(this.sampleAO, this.aoWeight));
|
|
1026
|
+
this.aoWeightSum = pb.add(this.aoWeightSum, this.aoWeight);
|
|
1027
|
+
}
|
|
1028
|
+
}
|
|
1029
|
+
this.$l.filtered = this.$choice(pb.greaterThan(this.weightSum, 1e-5), pb.div(this.sum, this.weightSum), this.center.rgb);
|
|
1030
|
+
this.$l.filteredAO = this.$choice(pb.greaterThan(this.aoWeightSum, 1e-5), pb.div(this.aoSum, this.aoWeightSum), this.centerAO);
|
|
1031
|
+
this.$outputs.outColor = pb.vec4(this.filtered, this.center.a);
|
|
1032
|
+
this.$outputs.outAO = pb.vec4(pb.clamp(this.filteredAO, 0, 1));
|
|
1033
|
+
});
|
|
1034
|
+
}
|
|
1035
|
+
});
|
|
1036
|
+
program.name = '@SSGI_ATrous';
|
|
1037
|
+
return program;
|
|
1038
|
+
}
|
|
1039
|
+
/** @internal */ createSurfaceProgram(ctx) {
|
|
1040
|
+
const program = ctx.device.buildRenderProgram({
|
|
1041
|
+
vertex (pb) {
|
|
1042
|
+
this.flip = pb.int().uniform(0);
|
|
1043
|
+
this.$inputs.pos = pb.vec2().attrib('position');
|
|
1044
|
+
this.$outputs.uv = pb.vec2();
|
|
1045
|
+
pb.main(function() {
|
|
1046
|
+
this.$builtins.position = pb.vec4(this.$inputs.pos, DEPTH_FARTHEST, 1);
|
|
1047
|
+
this.$outputs.uv = pb.add(pb.mul(this.$inputs.pos.xy, 0.5), pb.vec2(0.5));
|
|
1048
|
+
this.$if(pb.notEqual(this.flip, 0), function() {
|
|
1049
|
+
this.$builtins.position.y = pb.neg(this.$builtins.position.y);
|
|
1050
|
+
});
|
|
1051
|
+
});
|
|
1052
|
+
},
|
|
1053
|
+
fragment (pb) {
|
|
1054
|
+
this.depthTex = pb.tex2D().sampleType('unfilterable-float').uniform(0);
|
|
1055
|
+
this.normalTex = pb.tex2D().uniform(0);
|
|
1056
|
+
this.targetSize = pb.vec2().uniform(0);
|
|
1057
|
+
this.cameraFar = pb.float().uniform(0);
|
|
1058
|
+
this.$outputs.outColor = pb.vec4();
|
|
1059
|
+
pb.main(function() {
|
|
1060
|
+
this.$l.uv = pb.div(pb.vec2(this.$builtins.fragCoord.xy), this.targetSize);
|
|
1061
|
+
this.$l.normal = pb.textureSampleLevel(this.normalTex, this.uv, 0).rgb;
|
|
1062
|
+
this.$l.depth = pb.mul(ShaderHelper.sampleLinearDepth(this, this.depthTex, this.uv, 0), this.cameraFar);
|
|
1063
|
+
this.$outputs.outColor = pb.vec4(this.normal, this.depth);
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
});
|
|
1067
|
+
program.name = '@SSGI_Surface';
|
|
1068
|
+
return program;
|
|
1069
|
+
}
|
|
1070
|
+
/**
|
|
1071
|
+
* Multiply the traced ambient occlusion into the scene color.
|
|
1072
|
+
*
|
|
1073
|
+
* This is the same multiply-into-final-color semantics the standalone SAO post
|
|
1074
|
+
* effect applies, so enabling both double darkens.
|
|
1075
|
+
*
|
|
1076
|
+
* @internal
|
|
1077
|
+
*/ compositeAO(ctx, sceneColor, ao, srgbOutput) {
|
|
1078
|
+
const key = srgbOutput ? 'srgb' : 'linear';
|
|
1079
|
+
let program = SSGI._compositePrograms[key];
|
|
1080
|
+
if (!program) {
|
|
1081
|
+
program = this.createCompositeProgram(ctx, srgbOutput);
|
|
1082
|
+
SSGI._compositePrograms[key] = program;
|
|
1083
|
+
}
|
|
1084
|
+
const hash = `${key}:${sceneColor.uid}:${ao.uid}`;
|
|
1085
|
+
let bindGroup = this._compositeBindGroups[hash];
|
|
1086
|
+
if (!bindGroup) {
|
|
1087
|
+
bindGroup = ctx.device.createBindGroup(program.bindGroupLayouts[0]);
|
|
1088
|
+
bindGroup.setTexture('colorTex', sceneColor, fetchSampler('clamp_nearest_nomip'));
|
|
1089
|
+
// Nearest, not linear: the AO handed to this pass is already at full
|
|
1090
|
+
// resolution, so filtering would only add a dependency on the WebGL
|
|
1091
|
+
// half-float linear filtering cap for no gain.
|
|
1092
|
+
bindGroup.setTexture('aoTex', ao, fetchSampler('clamp_nearest_nomip'));
|
|
1093
|
+
this._compositeBindGroups[hash] = bindGroup;
|
|
1094
|
+
}
|
|
1095
|
+
bindGroup.setValue('aoParams', new Vector2(ctx.camera.ssgiAOIntensity, Math.max(1e-3, ctx.camera.ssgiAOPower)));
|
|
1096
|
+
bindGroup.setValue('flip', this.needFlip(ctx.device) ? 1 : 0);
|
|
1097
|
+
ctx.device.setProgram(program);
|
|
1098
|
+
ctx.device.setBindGroup(0, bindGroup);
|
|
1099
|
+
this.drawFullscreenQuad(AbstractPostEffect.getDefaultRenderState(ctx, 'always'));
|
|
1100
|
+
}
|
|
1101
|
+
/** @internal */ createCompositeProgram(ctx, srgbOutput) {
|
|
1102
|
+
const program = ctx.device.buildRenderProgram({
|
|
1103
|
+
vertex (pb) {
|
|
1104
|
+
this.flip = pb.int().uniform(0);
|
|
1105
|
+
this.$inputs.pos = pb.vec2().attrib('position');
|
|
1106
|
+
this.$outputs.uv = pb.vec2();
|
|
1107
|
+
pb.main(function() {
|
|
1108
|
+
this.$builtins.position = pb.vec4(this.$inputs.pos, DEPTH_FARTHEST, 1);
|
|
1109
|
+
this.$outputs.uv = pb.add(pb.mul(this.$inputs.pos.xy, 0.5), pb.vec2(0.5));
|
|
1110
|
+
this.$if(pb.notEqual(this.flip, 0), function() {
|
|
1111
|
+
this.$builtins.position.y = pb.neg(this.$builtins.position.y);
|
|
1112
|
+
});
|
|
1113
|
+
});
|
|
1114
|
+
},
|
|
1115
|
+
fragment (pb) {
|
|
1116
|
+
this.colorTex = pb.tex2D().uniform(0);
|
|
1117
|
+
this.aoTex = pb.tex2D().uniform(0);
|
|
1118
|
+
this.aoParams = pb.vec2().uniform(0);
|
|
1119
|
+
this.$outputs.outColor = pb.vec4();
|
|
1120
|
+
pb.main(function() {
|
|
1121
|
+
this.$l.color = pb.textureSampleLevel(this.colorTex, this.$inputs.uv, 0);
|
|
1122
|
+
this.$l.ao = pb.clamp(pb.textureSampleLevel(this.aoTex, this.$inputs.uv, 0).r, 0, 1);
|
|
1123
|
+
// mix() toward 1 rather than a plain multiply: intensity then doubles as
|
|
1124
|
+
// the fallback for noisy or unresolved texels, which matters most on the
|
|
1125
|
+
// WebGL path where there is no temporal accumulation to converge them.
|
|
1126
|
+
this.$l.shapedAO = pb.pow(this.ao, this.aoParams.y);
|
|
1127
|
+
this.$l.finalAO = pb.mix(pb.float(1), this.shapedAO, this.aoParams.x);
|
|
1128
|
+
this.$l.occludedColor = pb.mul(this.color.rgb, this.finalAO);
|
|
1129
|
+
this.$outputs.outColor = pb.vec4(srgbOutput ? linearToGamma(this, this.occludedColor) : this.occludedColor, this.color.a);
|
|
1130
|
+
});
|
|
1131
|
+
}
|
|
1132
|
+
});
|
|
1133
|
+
program.name = srgbOutput ? '@SSGI_CompositeAO_sRGB' : '@SSGI_CompositeAO';
|
|
1134
|
+
return program;
|
|
1135
|
+
}
|
|
1136
|
+
/** @internal */ createUpsampleProgram(ctx) {
|
|
1137
|
+
const program = ctx.device.buildRenderProgram({
|
|
1138
|
+
vertex (pb) {
|
|
1139
|
+
this.flip = pb.int().uniform(0);
|
|
1140
|
+
this.$inputs.pos = pb.vec2().attrib('position');
|
|
1141
|
+
this.$outputs.uv = pb.vec2();
|
|
1142
|
+
pb.main(function() {
|
|
1143
|
+
this.$builtins.position = pb.vec4(this.$inputs.pos, DEPTH_FARTHEST, 1);
|
|
1144
|
+
this.$outputs.uv = pb.add(pb.mul(this.$inputs.pos.xy, 0.5), pb.vec2(0.5));
|
|
1145
|
+
this.$if(pb.notEqual(this.flip, 0), function() {
|
|
1146
|
+
this.$builtins.position.y = pb.neg(this.$builtins.position.y);
|
|
1147
|
+
});
|
|
1148
|
+
});
|
|
1149
|
+
},
|
|
1150
|
+
fragment (pb) {
|
|
1151
|
+
this.sourceTex = pb.tex2D().uniform(0);
|
|
1152
|
+
this.aoSourceTex = pb.tex2D().uniform(0);
|
|
1153
|
+
this.depthTex = pb.tex2D().sampleType('unfilterable-float').uniform(0);
|
|
1154
|
+
this.normalTex = pb.tex2D().uniform(0);
|
|
1155
|
+
this.targetSize = pb.vec4().uniform(0);
|
|
1156
|
+
this.guideParams = pb.vec4().uniform(0);
|
|
1157
|
+
this.$outputs.outColor = pb.vec4();
|
|
1158
|
+
this.$outputs.outAO = pb.vec4();
|
|
1159
|
+
pb.main(function() {
|
|
1160
|
+
this.$l.uv = pb.div(pb.vec2(this.$builtins.fragCoord.xy), this.targetSize.zw);
|
|
1161
|
+
this.$l.centerDepth = pb.mul(ShaderHelper.sampleLinearDepth(this, this.depthTex, this.uv, 0), this.guideParams.z);
|
|
1162
|
+
this.$l.centerNormal = pb.normalize(pb.sub(pb.mul(pb.textureSampleLevel(this.normalTex, this.uv, 0).rgb, 2), pb.vec3(1)));
|
|
1163
|
+
this.$l.sum = pb.vec3(0);
|
|
1164
|
+
this.$l.weightSum = pb.float(0);
|
|
1165
|
+
this.$l.aoSum = pb.float(0);
|
|
1166
|
+
for(let y = -1; y <= 1; y++){
|
|
1167
|
+
for(let x = -1; x <= 1; x++){
|
|
1168
|
+
this.$l.sampleUV = pb.add(this.uv, pb.div(pb.vec2(x, y), this.targetSize.xy));
|
|
1169
|
+
this.$l.sampleValue = pb.textureSampleLevel(this.sourceTex, this.sampleUV, 0);
|
|
1170
|
+
this.$l.sampleAO = pb.textureSampleLevel(this.aoSourceTex, this.sampleUV, 0).r;
|
|
1171
|
+
this.$l.sampleDepth = pb.mul(ShaderHelper.sampleLinearDepth(this, this.depthTex, this.sampleUV, 0), this.guideParams.z);
|
|
1172
|
+
this.$l.sampleNormal = pb.normalize(pb.sub(pb.mul(pb.textureSampleLevel(this.normalTex, this.sampleUV, 0).rgb, 2), pb.vec3(1)));
|
|
1173
|
+
this.$l.depthWeight = pb.exp(pb.neg(pb.div(pb.abs(pb.sub(this.sampleDepth, this.centerDepth)), this.guideParams.x)));
|
|
1174
|
+
this.$l.normalWeight = pb.pow(pb.max(0, pb.dot(this.centerNormal, this.sampleNormal)), this.guideParams.y);
|
|
1175
|
+
this.$l.weight = pb.mul(this.depthWeight, this.normalWeight, this.sampleValue.a);
|
|
1176
|
+
this.sum = pb.add(this.sum, pb.mul(this.sampleValue.rgb, this.weight));
|
|
1177
|
+
this.weightSum = pb.add(this.weightSum, this.weight);
|
|
1178
|
+
// Same joint bilateral weights as the irradiance, so the upsampled
|
|
1179
|
+
// AO cannot cross a depth or normal edge the irradiance respects.
|
|
1180
|
+
this.aoSum = pb.add(this.aoSum, pb.mul(this.sampleAO, this.weight));
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
this.$l.result = this.$choice(pb.greaterThan(this.weightSum, 1e-5), pb.div(this.sum, this.weightSum), pb.textureSampleLevel(this.sourceTex, this.uv, 0).rgb);
|
|
1184
|
+
this.$l.resultAO = this.$choice(pb.greaterThan(this.weightSum, 1e-5), pb.div(this.aoSum, this.weightSum), pb.textureSampleLevel(this.aoSourceTex, this.uv, 0).r);
|
|
1185
|
+
this.$outputs.outColor = pb.vec4(this.result, pb.float(pb.greaterThan(this.weightSum, 1e-5)));
|
|
1186
|
+
this.$outputs.outAO = pb.vec4(pb.clamp(this.resultAO, 0, 1));
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
});
|
|
1190
|
+
program.name = '@SSGI_Upsample';
|
|
1191
|
+
return program;
|
|
1192
|
+
}
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
export { SSGI };
|
|
1196
|
+
//# sourceMappingURL=ssgi.js.map
|