@zephyr3d/scene 0.9.22 → 0.9.24
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 +9113 -8740
- package/dist/index.js +10 -2
- 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 +512 -334
- 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 +35 -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 +988 -436
- package/dist/posteffect/ssgi.js.map +1 -1
- package/dist/posteffect/ssr.js +83 -45
- package/dist/posteffect/ssr.js.map +1 -1
- package/dist/posteffect/sss.js +18 -15
- 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 +22 -16
- 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 +1 -1
- package/dist/render/rendergraph/frame_graph_context.js.map +1 -1
- 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 +7 -6
- package/dist/render/shadow_mask_pass.js.map +1 -1
- package/dist/render/sky.js +257 -95
- 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 +149 -40
- package/dist/shaders/shadow.js.map +1 -1
- 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 +10 -7
- package/dist/shadow/esm.js.map +1 -1
- package/dist/shadow/pcf_opt.js +12 -12
- package/dist/shadow/pcf_opt.js.map +1 -1
- package/dist/shadow/pcf_pd.js +12 -12
- package/dist/shadow/pcf_pd.js.map +1 -1
- package/dist/shadow/pcss.js +16 -11
- package/dist/shadow/pcss.js.map +1 -1
- package/dist/shadow/shadow_impl.js +29 -3
- package/dist/shadow/shadow_impl.js.map +1 -1
- package/dist/shadow/shadowmapper.js +5 -2
- package/dist/shadow/shadowmapper.js.map +1 -1
- package/dist/shadow/ssm.js +13 -13
- package/dist/shadow/ssm.js.map +1 -1
- package/dist/shadow/vsm.js +7 -7
- 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 +59 -65
- package/dist/values.js.map +1 -1
- package/package.json +3 -3
package/dist/posteffect/ssgi.js
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
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';
|
|
3
4
|
import { screenSpaceRayTracing_HiZ, screenSpaceRayTracing_Linear2D } from '../shaders/ssr.js';
|
|
4
|
-
import { SSGI_rand2, SSGI_cosineSampleHemisphere } from '../shaders/ssgi.js';
|
|
5
|
-
import { Vector2, Matrix4x4, Vector4 } from '@zephyr3d/base';
|
|
6
|
-
import { copyTexture, fetchSampler } from '../utility/misc.js';
|
|
7
|
-
import { BilateralBlurBlitter } from '../blitter/bilateralblur.js';
|
|
8
5
|
import { ShaderHelper } from '../material/shader/helper.js';
|
|
9
6
|
import '../material/lambert.js';
|
|
10
7
|
import '../material/blinn.js';
|
|
@@ -24,92 +21,203 @@ import '../material/pbrblueprint.js';
|
|
|
24
21
|
import '../material/sprite.js';
|
|
25
22
|
import '../material/sprite_std.js';
|
|
26
23
|
import '../utility/blueprint/material/texture.js';
|
|
27
|
-
import {
|
|
28
|
-
import {
|
|
24
|
+
import { linearToGamma } from '../shaders/misc.js';
|
|
25
|
+
import { fetchSampler } from '../utility/misc.js';
|
|
26
|
+
import { AbstractPostEffect, PostEffectLayer } from './posteffect.js';
|
|
29
27
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
*
|
|
40
|
-
*
|
|
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
|
|
41
40
|
*/ class SSGI extends AbstractPostEffect {
|
|
42
41
|
static _tracePrograms = {};
|
|
43
|
-
static
|
|
44
|
-
static
|
|
45
|
-
static
|
|
46
|
-
static
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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(){
|
|
53
54
|
super();
|
|
54
55
|
this._layer = PostEffectLayer.opaque;
|
|
55
|
-
this._traceBindGroups = {};
|
|
56
|
-
this._combineBindGroup = null;
|
|
57
|
-
this._temporalBindGroup = null;
|
|
58
56
|
}
|
|
59
|
-
/** {@inheritDoc AbstractPostEffect.requireLinearDepthTexture} */ requireLinearDepthTexture() {
|
|
60
|
-
return
|
|
61
|
-
}
|
|
62
|
-
/** {@inheritDoc AbstractPostEffect.requireDepthAttachment} */ requireDepthAttachment() {
|
|
63
|
-
return true;
|
|
57
|
+
/** {@inheritDoc AbstractPostEffect.requireLinearDepthTexture} */ requireLinearDepthTexture(ctx) {
|
|
58
|
+
return !!ctx.SSGI;
|
|
64
59
|
}
|
|
65
60
|
/** {@inheritDoc AbstractPostEffect.requireMotionVectorTexture} */ requireMotionVectorTexture(ctx) {
|
|
66
|
-
return !!ctx.
|
|
61
|
+
return !!ctx.SSGI && ctx.device.type === 'webgpu';
|
|
62
|
+
}
|
|
63
|
+
/** {@inheritDoc AbstractPostEffect.requireHiZTexture} */ requireHiZTexture(ctx) {
|
|
64
|
+
return !!ctx.SSGI && ctx.device.type === 'webgpu';
|
|
67
65
|
}
|
|
68
|
-
/**
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const { graph, ctx, history } = s;
|
|
73
|
-
|
|
74
|
-
// available on WebGL1. Pass the input through untouched in that case.
|
|
75
|
-
if (ctx.device.type === 'webgl' || !s.blackboard.get(FrameResources.SSRNormal)) {
|
|
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) {
|
|
76
72
|
return s.input;
|
|
77
73
|
}
|
|
78
|
-
const
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
const
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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,
|
|
85
102
|
sizeMode: 'absolute',
|
|
86
103
|
width: traceWidth,
|
|
87
104
|
height: traceHeight
|
|
88
105
|
};
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
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);
|
|
92
159
|
}
|
|
93
|
-
// Normal MRT, HiZ and the scene color are reached through DrawContext
|
|
94
|
-
// fields; the dependency list carries their handles.
|
|
95
160
|
for (const dep of s.dependencies){
|
|
96
161
|
builder.read(dep);
|
|
97
162
|
}
|
|
98
163
|
};
|
|
99
|
-
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
const
|
|
103
|
-
builder.read(
|
|
104
|
-
|
|
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
|
+
}
|
|
105
174
|
const out = builder.createTexture({
|
|
106
|
-
...
|
|
107
|
-
label: 'SSGI:
|
|
175
|
+
...traceDesc,
|
|
176
|
+
label: 'SSGI:rawIrradiance'
|
|
177
|
+
});
|
|
178
|
+
const ao = builder.createTexture({
|
|
179
|
+
...aoTraceDesc,
|
|
180
|
+
label: 'SSGI:rawAO'
|
|
108
181
|
});
|
|
109
182
|
const fb = builder.createFramebuffer({
|
|
110
183
|
label: 'SSGI:traceFB',
|
|
111
184
|
width: traceWidth,
|
|
112
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,
|
|
113
221
|
colorAttachments: out,
|
|
114
222
|
depthAttachment: null,
|
|
115
223
|
ignoreDepthStencil: true
|
|
@@ -119,52 +227,97 @@ import { FrameResources } from '../render/rendergraph/blackboard.js';
|
|
|
119
227
|
device.pushDeviceStates();
|
|
120
228
|
try {
|
|
121
229
|
device.setFramebuffer(rg.getFramebuffer(fb));
|
|
122
|
-
this.
|
|
230
|
+
this.writeSurface(ctx, rg.getTexture(linearDepthHandle), rg.getTexture(normalHandle));
|
|
123
231
|
} finally{
|
|
124
232
|
device.popDeviceStates();
|
|
125
233
|
}
|
|
126
234
|
});
|
|
127
235
|
return out;
|
|
128
236
|
});
|
|
129
|
-
|
|
130
|
-
//
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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);
|
|
159
305
|
const out = builder.createTexture({
|
|
160
|
-
...
|
|
161
|
-
label:
|
|
306
|
+
...traceDesc,
|
|
307
|
+
label: `SSGI:atrous${pass}`
|
|
308
|
+
});
|
|
309
|
+
const ao = builder.createTexture({
|
|
310
|
+
...aoTraceDesc,
|
|
311
|
+
label: `SSGI:atrousAO${pass}`
|
|
162
312
|
});
|
|
163
313
|
const fb = builder.createFramebuffer({
|
|
164
|
-
label:
|
|
314
|
+
label: `SSGI:atrousFB${pass}`,
|
|
165
315
|
width: traceWidth,
|
|
166
316
|
height: traceHeight,
|
|
167
|
-
colorAttachments:
|
|
317
|
+
colorAttachments: [
|
|
318
|
+
out,
|
|
319
|
+
ao
|
|
320
|
+
],
|
|
168
321
|
depthAttachment: null,
|
|
169
322
|
ignoreDepthStencil: true
|
|
170
323
|
});
|
|
@@ -172,44 +325,48 @@ import { FrameResources } from '../render/rendergraph/blackboard.js';
|
|
|
172
325
|
const device = ctx.device;
|
|
173
326
|
device.pushDeviceStates();
|
|
174
327
|
try {
|
|
175
|
-
|
|
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);
|
|
176
330
|
} finally{
|
|
177
331
|
device.popDeviceStates();
|
|
178
332
|
}
|
|
179
333
|
});
|
|
180
|
-
return
|
|
334
|
+
return {
|
|
335
|
+
irradiance: out,
|
|
336
|
+
ao
|
|
337
|
+
};
|
|
181
338
|
});
|
|
339
|
+
filteredHandle = filtered.irradiance;
|
|
340
|
+
filteredAOHandle = filtered.ao;
|
|
182
341
|
}
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
if (
|
|
188
|
-
const
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
});
|
|
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);
|
|
196
354
|
const out = builder.createTexture({
|
|
197
|
-
...
|
|
198
|
-
label: 'SSGI:
|
|
355
|
+
...fullIrradianceDesc,
|
|
356
|
+
label: 'SSGI:upsampledIrradiance'
|
|
199
357
|
});
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
height: traceHeight,
|
|
204
|
-
colorAttachments: middle,
|
|
205
|
-
depthAttachment: null,
|
|
206
|
-
ignoreDepthStencil: true
|
|
358
|
+
const ao = builder.createTexture({
|
|
359
|
+
...fullAODesc,
|
|
360
|
+
label: 'SSGI:upsampledAO'
|
|
207
361
|
});
|
|
208
|
-
const
|
|
209
|
-
label: 'SSGI:
|
|
210
|
-
width:
|
|
211
|
-
height:
|
|
212
|
-
colorAttachments:
|
|
362
|
+
const fb = builder.createFramebuffer({
|
|
363
|
+
label: 'SSGI:upsampleFB',
|
|
364
|
+
width: s.width,
|
|
365
|
+
height: s.height,
|
|
366
|
+
colorAttachments: [
|
|
367
|
+
out,
|
|
368
|
+
ao
|
|
369
|
+
],
|
|
213
370
|
depthAttachment: null,
|
|
214
371
|
ignoreDepthStencil: true
|
|
215
372
|
});
|
|
@@ -217,73 +374,68 @@ import { FrameResources } from '../render/rendergraph/blackboard.js';
|
|
|
217
374
|
const device = ctx.device;
|
|
218
375
|
device.pushDeviceStates();
|
|
219
376
|
try {
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
this.blurPass(ctx, blitterH, depthTex, rg.getTexture(blurInputHandle), rg.getFramebuffer(middleFB));
|
|
223
|
-
const blitterV = SSGI._blurBlitterV = SSGI._blurBlitterV ?? new BilateralBlurBlitter(true);
|
|
224
|
-
this.blurPass(ctx, blitterV, depthTex, rg.getTexture(middle), rg.getFramebuffer(outFB));
|
|
377
|
+
device.setFramebuffer(rg.getFramebuffer(fb));
|
|
378
|
+
this.upsample(ctx, rg.getTexture(source), rg.getTexture(aoSource), rg.getTexture(linearDepthHandle), rg.getTexture(normalHandle));
|
|
225
379
|
} finally{
|
|
226
380
|
device.popDeviceStates();
|
|
227
381
|
}
|
|
228
382
|
});
|
|
229
|
-
return
|
|
383
|
+
return {
|
|
384
|
+
irradiance: out,
|
|
385
|
+
ao
|
|
386
|
+
};
|
|
230
387
|
});
|
|
388
|
+
finalIrradianceHandle = upsampled.irradiance;
|
|
389
|
+
finalAOHandle = upsampled.ao;
|
|
231
390
|
}
|
|
232
|
-
//
|
|
233
|
-
//
|
|
234
|
-
|
|
235
|
-
|
|
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)=>{
|
|
236
399
|
builder.read(s.input);
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
builder.read(accumulatedHandle);
|
|
240
|
-
}
|
|
241
|
-
if (motionVectorHandle) {
|
|
242
|
-
builder.read(motionVectorHandle);
|
|
400
|
+
if (noFogHandle) {
|
|
401
|
+
builder.read(noFogHandle);
|
|
243
402
|
}
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
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);
|
|
248
411
|
builder.setExecute((rg)=>{
|
|
249
412
|
const device = ctx.device;
|
|
250
413
|
device.pushDeviceStates();
|
|
251
414
|
try {
|
|
252
415
|
device.setFramebuffer(output.framebuffer ? rg.getFramebuffer(output.framebuffer) : null);
|
|
253
|
-
const
|
|
254
|
-
const
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
history.
|
|
267
|
-
|
|
268
|
-
sizeMode: 'absolute',
|
|
269
|
-
width: accumulatedTex.width,
|
|
270
|
-
height: accumulatedTex.height
|
|
271
|
-
}, {
|
|
272
|
-
width: accumulatedTex.width,
|
|
273
|
-
height: accumulatedTex.height
|
|
274
|
-
}, accumulatedTex);
|
|
275
|
-
if (ctx.motionVectorTexture) {
|
|
276
|
-
history.queueRetainedCommit(RGHistoryResources.SSGI_MOTION_VECTOR, {
|
|
277
|
-
format: ctx.motionVectorTexture.format,
|
|
278
|
-
sizeMode: 'absolute',
|
|
279
|
-
width: ctx.motionVectorTexture.width,
|
|
280
|
-
height: ctx.motionVectorTexture.height
|
|
281
|
-
}, {
|
|
282
|
-
width: ctx.motionVectorTexture.width,
|
|
283
|
-
height: ctx.motionVectorTexture.height
|
|
284
|
-
}, ctx.motionVectorTexture);
|
|
285
|
-
}
|
|
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));
|
|
286
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);
|
|
287
439
|
} finally{
|
|
288
440
|
device.popDeviceStates();
|
|
289
441
|
}
|
|
@@ -291,116 +443,180 @@ import { FrameResources } from '../render/rendergraph/blackboard.js';
|
|
|
291
443
|
return output.color;
|
|
292
444
|
});
|
|
293
445
|
}
|
|
294
|
-
/** @internal */
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
blitter.srgbOut = false;
|
|
305
|
-
blitter.blit(srcTex, fbTo, fetchSampler('clamp_linear_nomip'));
|
|
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);
|
|
306
456
|
}
|
|
307
|
-
/** @internal */ trace(ctx,
|
|
308
|
-
const
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
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'));
|
|
315
480
|
}
|
|
316
|
-
|
|
317
|
-
|
|
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));
|
|
318
489
|
}
|
|
319
|
-
|
|
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];
|
|
320
514
|
if (!bindGroup) {
|
|
321
|
-
bindGroup = device.createBindGroup(program.bindGroupLayouts[0]);
|
|
322
|
-
|
|
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;
|
|
323
530
|
}
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
bindGroup.setTexture('
|
|
343
|
-
bindGroup.
|
|
344
|
-
bindGroup.
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
bindGroup.setValue('targetSize', new Vector4(traceWidth, traceHeight, sceneDepthTexture.width, sceneDepthTexture.height));
|
|
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;
|
|
348
554
|
}
|
|
349
|
-
bindGroup.setValue('
|
|
350
|
-
|
|
351
|
-
|
|
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);
|
|
352
565
|
this.drawFullscreenQuad(AbstractPostEffect.getDefaultRenderState(ctx, 'always'));
|
|
353
566
|
}
|
|
354
|
-
/** @internal */
|
|
355
|
-
|
|
356
|
-
let program = SSGI._temporalProgram;
|
|
567
|
+
/** @internal */ writeSurface(ctx, depth, normal) {
|
|
568
|
+
let program = SSGI._surfaceProgram;
|
|
357
569
|
if (!program) {
|
|
358
|
-
program = this.
|
|
359
|
-
SSGI.
|
|
570
|
+
program = this.createSurfaceProgram(ctx);
|
|
571
|
+
SSGI._surfaceProgram = program;
|
|
360
572
|
}
|
|
361
|
-
|
|
362
|
-
|
|
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;
|
|
363
581
|
}
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
this._temporalBindGroup.setValue('flip', this.needFlip(device) ? 1 : 0);
|
|
369
|
-
this._temporalBindGroup.setValue('texSize', new Vector2(currentRadianceTex.width, currentRadianceTex.height));
|
|
370
|
-
this._temporalBindGroup.setValue('temporalWeight', ctx.camera.ssgiTemporalWeight);
|
|
371
|
-
device.setFramebuffer(outFramebuffer);
|
|
372
|
-
device.setProgram(program);
|
|
373
|
-
device.setBindGroup(0, this._temporalBindGroup);
|
|
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);
|
|
374
586
|
this.drawFullscreenQuad(AbstractPostEffect.getDefaultRenderState(ctx, 'always'));
|
|
375
587
|
}
|
|
376
|
-
/** @internal */
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
SSGI._combineProgram = program;
|
|
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;
|
|
382
593
|
}
|
|
383
|
-
|
|
384
|
-
|
|
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;
|
|
385
604
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
this.
|
|
391
|
-
this._combineBindGroup.setValue('srgbOut', srgbOut ? 1 : 0);
|
|
392
|
-
device.setProgram(program);
|
|
393
|
-
device.setBindGroup(0, this._combineBindGroup);
|
|
394
|
-
this.drawFullscreenQuad(AbstractPostEffect.getDefaultRenderState(ctx, 'gt'));
|
|
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'));
|
|
395
610
|
}
|
|
396
|
-
/** @internal */
|
|
611
|
+
/** @internal */ createTraceProgram(ctx, useHiZ, sampleHistory) {
|
|
612
|
+
const raysPerPixel = ctx.camera.ssgiResolvedSettings.raysPerPixel;
|
|
397
613
|
const program = ctx.device.buildRenderProgram({
|
|
398
614
|
vertex (pb) {
|
|
399
615
|
this.flip = pb.int().uniform(0);
|
|
400
616
|
this.$inputs.pos = pb.vec2().attrib('position');
|
|
401
617
|
this.$outputs.uv = pb.vec2();
|
|
402
618
|
pb.main(function() {
|
|
403
|
-
this.$builtins.position = pb.vec4(this.$inputs.pos,
|
|
619
|
+
this.$builtins.position = pb.vec4(this.$inputs.pos, DEPTH_FARTHEST, 1);
|
|
404
620
|
this.$outputs.uv = pb.add(pb.mul(this.$inputs.pos.xy, 0.5), pb.vec2(0.5));
|
|
405
621
|
this.$if(pb.notEqual(this.flip, 0), function() {
|
|
406
622
|
this.$builtins.position.y = pb.neg(this.$builtins.position.y);
|
|
@@ -408,100 +624,208 @@ import { FrameResources } from '../render/rendergraph/blackboard.js';
|
|
|
408
624
|
});
|
|
409
625
|
},
|
|
410
626
|
fragment (pb) {
|
|
411
|
-
this.
|
|
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);
|
|
412
632
|
this.normalTex = pb.tex2D().uniform(0);
|
|
413
|
-
|
|
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
|
+
}
|
|
414
646
|
this.cameraNearFar = pb.vec2().uniform(0);
|
|
647
|
+
this.viewMatrix = pb.mat4().uniform(0);
|
|
648
|
+
this.invViewMatrix = pb.mat4().uniform(0);
|
|
415
649
|
this.projMatrix = pb.mat4().uniform(0);
|
|
416
650
|
this.invProjMatrix = pb.mat4().uniform(0);
|
|
417
|
-
this.
|
|
418
|
-
// (radius, maxIterations, thickness, numRays)
|
|
419
|
-
this.ssgiParams = pb.vec4().uniform(0);
|
|
420
|
-
this.frameIndex = pb.float().uniform(0);
|
|
651
|
+
this.traceParams = pb.vec4().uniform(0);
|
|
421
652
|
this.targetSize = pb.vec4().uniform(0);
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
} else {
|
|
426
|
-
this.ssgiStride = pb.float().uniform(0);
|
|
427
|
-
}
|
|
653
|
+
this.radianceParams = pb.vec4().uniform(0);
|
|
654
|
+
this.skyOcclusion = pb.float().uniform(0);
|
|
655
|
+
ctx.env.light.envLight.initShaderBindings(pb);
|
|
428
656
|
this.$outputs.outColor = pb.vec4();
|
|
429
|
-
pb.
|
|
430
|
-
|
|
431
|
-
pb.
|
|
657
|
+
this.$outputs.outAO = pb.vec4();
|
|
658
|
+
pb.func('SSGI_getPosition', [
|
|
659
|
+
pb.vec2('uv')
|
|
432
660
|
], function() {
|
|
433
661
|
this.$l.linearDepth = ShaderHelper.sampleLinearDepth(this, this.depthTex, this.uv, 0);
|
|
434
|
-
this.$l.nonLinearDepth =
|
|
435
|
-
this.$l.
|
|
436
|
-
this.$l.
|
|
437
|
-
this.$return(pb.vec4(pb.div(this.
|
|
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))));
|
|
438
685
|
});
|
|
439
686
|
pb.main(function() {
|
|
440
|
-
this.$l.
|
|
441
|
-
this.$l.
|
|
442
|
-
this.$
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
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)));
|
|
446
697
|
this.$l.viewNormal = pb.normalize(pb.mul(this.viewMatrix, pb.vec4(this.worldNormal, 0)).xyz);
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
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);
|
|
455
732
|
} else {
|
|
456
|
-
this.
|
|
457
|
-
// built with SSR thickness computation enabled.
|
|
458
|
-
!!ctx.SSRCalcThickness);
|
|
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);
|
|
459
734
|
}
|
|
460
|
-
|
|
461
|
-
//
|
|
462
|
-
//
|
|
463
|
-
|
|
464
|
-
//
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
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);
|
|
475
790
|
});
|
|
476
|
-
//
|
|
477
|
-
//
|
|
478
|
-
|
|
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));
|
|
479
814
|
});
|
|
480
|
-
// Reinhard-compress into [0, 1) (mirrors the SSR intersect pass).
|
|
481
|
-
// The bilateral blur weights neighbors by color distance; in linear
|
|
482
|
-
// HDR a firefly is "far" from every neighbor and survives the blur
|
|
483
|
-
// untouched. Compressed, bright outliers stay within blurring range
|
|
484
|
-
// and the denoiser can actually converge them. combine() inverts
|
|
485
|
-
// this mapping.
|
|
486
|
-
this.color = pb.div(this.color, pb.add(this.color, pb.vec3(1)));
|
|
487
|
-
this.$outputs.outColor = pb.vec4(this.color, 1);
|
|
488
815
|
});
|
|
489
816
|
}
|
|
490
817
|
});
|
|
491
|
-
if (!program) {
|
|
492
|
-
return null;
|
|
493
|
-
}
|
|
494
818
|
program.name = '@SSGI_Trace';
|
|
495
819
|
return program;
|
|
496
820
|
}
|
|
497
|
-
/** @internal */
|
|
821
|
+
/** @internal */ createTemporalProgram(ctx, hasHistory) {
|
|
498
822
|
const program = ctx.device.buildRenderProgram({
|
|
499
823
|
vertex (pb) {
|
|
500
824
|
this.flip = pb.int().uniform(0);
|
|
501
825
|
this.$inputs.pos = pb.vec2().attrib('position');
|
|
502
826
|
this.$outputs.uv = pb.vec2();
|
|
503
827
|
pb.main(function() {
|
|
504
|
-
this.$builtins.position = pb.vec4(this.$inputs.pos,
|
|
828
|
+
this.$builtins.position = pb.vec4(this.$inputs.pos, DEPTH_FARTHEST, 1);
|
|
505
829
|
this.$outputs.uv = pb.add(pb.mul(this.$inputs.pos.xy, 0.5), pb.vec2(0.5));
|
|
506
830
|
this.$if(pb.notEqual(this.flip, 0), function() {
|
|
507
831
|
this.$builtins.position.y = pb.neg(this.$builtins.position.y);
|
|
@@ -509,95 +833,279 @@ import { FrameResources } from '../render/rendergraph/blackboard.js';
|
|
|
509
833
|
});
|
|
510
834
|
},
|
|
511
835
|
fragment (pb) {
|
|
512
|
-
this.
|
|
513
|
-
this.
|
|
514
|
-
this.
|
|
515
|
-
this.
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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
|
+
});
|
|
519
858
|
pb.main(function() {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
this.$
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
}).$else(function() {
|
|
545
|
-
this.$l.historySample = pb.textureSampleLevel(this.historyColorTex, this.reprojectedUV, 0);
|
|
546
|
-
this.$l.historyColor = this.historySample.rgb;
|
|
547
|
-
this.$l.historyCount = pb.max(this.historySample.a, 1);
|
|
548
|
-
// 3x3 neighborhood statistics of the current stochastic estimate
|
|
549
|
-
this.$l.m1 = pb.vec3(0);
|
|
550
|
-
this.$l.m2 = pb.vec3(0);
|
|
551
|
-
for(let i = -1; i <= 1; i++){
|
|
552
|
-
for(let j = -1; j <= 1; j++){
|
|
553
|
-
const c = `c${(i + 1) * 3 + j + 1}`;
|
|
554
|
-
this.$l[c] = pb.textureSampleLevel(this.currentColorTex, pb.add(this.screenUV, pb.div(pb.vec2(i, j), this.texSize)), 0).rgb;
|
|
555
|
-
this.m1 = pb.add(this.m1, this[c]);
|
|
556
|
-
this.m2 = pb.add(this.m2, pb.mul(this[c], this[c]));
|
|
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;
|
|
557
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);
|
|
558
891
|
}
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
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);
|
|
586
1050
|
});
|
|
587
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
|
+
});
|
|
588
1065
|
}
|
|
589
1066
|
});
|
|
590
|
-
program.name = '@
|
|
1067
|
+
program.name = '@SSGI_Surface';
|
|
591
1068
|
return program;
|
|
592
1069
|
}
|
|
593
|
-
/**
|
|
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) {
|
|
594
1102
|
const program = ctx.device.buildRenderProgram({
|
|
595
1103
|
vertex (pb) {
|
|
596
1104
|
this.flip = pb.int().uniform(0);
|
|
597
1105
|
this.$inputs.pos = pb.vec2().attrib('position');
|
|
598
1106
|
this.$outputs.uv = pb.vec2();
|
|
599
1107
|
pb.main(function() {
|
|
600
|
-
this.$builtins.position = pb.vec4(this.$inputs.pos,
|
|
1108
|
+
this.$builtins.position = pb.vec4(this.$inputs.pos, DEPTH_FARTHEST, 1);
|
|
601
1109
|
this.$outputs.uv = pb.add(pb.mul(this.$inputs.pos.xy, 0.5), pb.vec2(0.5));
|
|
602
1110
|
this.$if(pb.notEqual(this.flip, 0), function() {
|
|
603
1111
|
this.$builtins.position.y = pb.neg(this.$builtins.position.y);
|
|
@@ -606,36 +1114,80 @@ import { FrameResources } from '../render/rendergraph/blackboard.js';
|
|
|
606
1114
|
},
|
|
607
1115
|
fragment (pb) {
|
|
608
1116
|
this.colorTex = pb.tex2D().uniform(0);
|
|
609
|
-
this.
|
|
610
|
-
this.
|
|
611
|
-
this.ssgiIntensity = pb.float().uniform(0);
|
|
612
|
-
this.srgbOut = pb.int().uniform(0);
|
|
1117
|
+
this.aoTex = pb.tex2D().uniform(0);
|
|
1118
|
+
this.aoParams = pb.vec2().uniform(0);
|
|
613
1119
|
this.$outputs.outColor = pb.vec4();
|
|
614
1120
|
pb.main(function() {
|
|
615
|
-
this.$l.
|
|
616
|
-
this.$l.
|
|
617
|
-
//
|
|
618
|
-
//
|
|
619
|
-
//
|
|
620
|
-
this.$l.
|
|
621
|
-
this.$l.
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
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);
|
|
634
1147
|
});
|
|
635
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
|
+
});
|
|
636
1188
|
}
|
|
637
1189
|
});
|
|
638
|
-
program.name = '@
|
|
1190
|
+
program.name = '@SSGI_Upsample';
|
|
639
1191
|
return program;
|
|
640
1192
|
}
|
|
641
1193
|
}
|