@woosh/meep-engine 3.54.0 → 3.55.0

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.
Files changed (78) hide show
  1. package/package.json +1 -1
  2. package/src/shade/RENDERER_CONTRACT.md +5 -2
  3. package/src/shade/device/SHADE_DEVICE_FLOOR.d.ts.map +1 -1
  4. package/src/shade/device/SHADE_DEVICE_FLOOR.js +2 -0
  5. package/src/shade/renderer/Renderer.d.ts.map +1 -1
  6. package/src/shade/renderer/Renderer.js +1 -1
  7. package/src/shade/renderer/hiz/generate_downsampler_code.js +1 -1
  8. package/src/shade/renderer/hiz/shader_hzb_reduce_mip0_clip.js +1 -1
  9. package/src/shade/renderer/postprocess/denoise/fragment_shader_denoise_ao.d.ts.map +1 -1
  10. package/src/shade/renderer/postprocess/denoise/fragment_shader_denoise_ao.js +154 -147
  11. package/src/shade/renderer/postprocess/gtao/GTAO.d.ts +2 -1
  12. package/src/shade/renderer/postprocess/gtao/GTAO.d.ts.map +1 -1
  13. package/src/shade/renderer/postprocess/gtao/GTAO.js +5 -1
  14. package/src/shade/renderer/postprocess/gtao/GTAO_CONSTANTS.d.ts +8 -0
  15. package/src/shade/renderer/postprocess/gtao/GTAO_CONSTANTS.d.ts.map +1 -1
  16. package/src/shade/renderer/postprocess/gtao/GTAO_CONSTANTS.js +10 -1
  17. package/src/shade/renderer/postprocess/gtao/ao_reproject_shader.d.ts.map +1 -1
  18. package/src/shade/renderer/postprocess/gtao/ao_reproject_shader.js +5 -0
  19. package/src/shade/renderer/postprocess/gtao/chunk_gtao_compute_mip_level.d.ts +22 -0
  20. package/src/shade/renderer/postprocess/gtao/chunk_gtao_compute_mip_level.d.ts.map +1 -0
  21. package/src/shade/renderer/postprocess/gtao/chunk_gtao_compute_mip_level.js +33 -0
  22. package/src/shade/renderer/postprocess/gtao/chunk_gtao_resolve_hzb_depth.d.ts +22 -0
  23. package/src/shade/renderer/postprocess/gtao/chunk_gtao_resolve_hzb_depth.d.ts.map +1 -0
  24. package/src/shade/renderer/postprocess/gtao/chunk_gtao_resolve_hzb_depth.js +56 -0
  25. package/src/shade/renderer/postprocess/gtao/chunk_gtao_sample_depth.d.ts +13 -0
  26. package/src/shade/renderer/postprocess/gtao/chunk_gtao_sample_depth.d.ts.map +1 -0
  27. package/src/shade/renderer/postprocess/gtao/chunk_gtao_sample_depth.js +42 -0
  28. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao.d.ts +2 -1
  29. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao.d.ts.map +1 -1
  30. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao.js +382 -357
  31. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao_with_irradiance.d.ts.map +1 -1
  32. package/src/shade/renderer/postprocess/gtao/fragment_shader_gtao_with_irradiance.js +414 -410
  33. package/src/shade/renderer/postprocess/ssr/prefilter/NOTES.md +0 -7
  34. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_local_light.d.ts +10 -5
  35. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_local_light.d.ts.map +1 -1
  36. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_local_light.js +30 -14
  37. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_sample.d.ts.map +1 -1
  38. package/src/shade/renderer/volumetrics/irradiance_clipmap/chunk_irradiance_clipmap_sample.js +17 -3
  39. package/src/engine/graphics3/decal/DecalAtlas.d.ts +0 -43
  40. package/src/engine/graphics3/decal/DecalAtlas.d.ts.map +0 -1
  41. package/src/engine/graphics3/decal/DecalAtlasFill.d.ts +0 -12
  42. package/src/engine/graphics3/decal/DecalAtlasFill.d.ts.map +0 -1
  43. package/src/engine/graphics3/decal/decal_atlas_border.d.ts +0 -52
  44. package/src/engine/graphics3/decal/decal_atlas_border.d.ts.map +0 -1
  45. package/src/shade/renderer/postprocess/gtao/graph_build_depth_mipmap.d.ts +0 -26
  46. package/src/shade/renderer/postprocess/gtao/graph_build_depth_mipmap.d.ts.map +0 -1
  47. package/src/shade/renderer/postprocess/gtao/graph_build_depth_mipmap.js +0 -268
  48. package/src/shade/renderer/postprocess/ssr/copy_shader_f32.d.ts +0 -3
  49. package/src/shade/renderer/postprocess/ssr/copy_shader_f32.d.ts.map +0 -1
  50. package/src/shade/renderer/postprocess/ssr/copy_shader_f32.js +0 -32
  51. package/src/shade/renderer/postprocess/ssr/depth_max_shader.d.ts +0 -17
  52. package/src/shade/renderer/postprocess/ssr/depth_max_shader.d.ts.map +0 -1
  53. package/src/shade/renderer/postprocess/ssr/depth_max_shader.js +0 -98
  54. package/src/shade/renderer/postprocess/ssr/graph_build_depth_max_pyramid.d.ts +0 -23
  55. package/src/shade/renderer/postprocess/ssr/graph_build_depth_max_pyramid.d.ts.map +0 -1
  56. package/src/shade/renderer/postprocess/ssr/graph_build_depth_max_pyramid.js +0 -118
  57. package/src/shade/renderer/volumetrics/chunk_volumetrics_froxel_sample.d.ts +0 -24
  58. package/src/shade/renderer/volumetrics/chunk_volumetrics_froxel_sample.d.ts.map +0 -1
  59. package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_froxel_centre_ws.d.ts +0 -15
  60. package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_froxel_centre_ws.d.ts.map +0 -1
  61. package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi.d.ts +0 -17
  62. package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi.d.ts.map +0 -1
  63. package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi_sample.d.ts +0 -35
  64. package/src/shade/renderer/volumetrics/gi/chunk_volumetrics_gi_sample.d.ts.map +0 -1
  65. package/src/shade/renderer/volumetrics/gi/graph_build_froxel_gi.d.ts +0 -35
  66. package/src/shade/renderer/volumetrics/gi/graph_build_froxel_gi.d.ts.map +0 -1
  67. package/src/shade/renderer/volumetrics/gi/shader_volumetrics_gi.d.ts +0 -10
  68. package/src/shade/renderer/volumetrics/gi/shader_volumetrics_gi.d.ts.map +0 -1
  69. package/src/shade/renderer/volumetrics/gi/volumetrics_gi_resources.d.ts +0 -28
  70. package/src/shade/renderer/volumetrics/gi/volumetrics_gi_resources.d.ts.map +0 -1
  71. package/src/shade/renderer/volumetrics/graph_build_froxel_irradiance.d.ts +0 -48
  72. package/src/shade/renderer/volumetrics/graph_build_froxel_irradiance.d.ts.map +0 -1
  73. package/src/shade/renderer/volumetrics/shader_volumetrics_build_irradiance.d.ts +0 -28
  74. package/src/shade/renderer/volumetrics/shader_volumetrics_build_irradiance.d.ts.map +0 -1
  75. package/src/shade/renderer/volumetrics/shader_volumetrics_classify_froxels.d.ts +0 -16
  76. package/src/shade/renderer/volumetrics/shader_volumetrics_classify_froxels.d.ts.map +0 -1
  77. package/src/shade/renderer/volumetrics/shader_volumetrics_lighting_regimes.d.ts +0 -23
  78. package/src/shade/renderer/volumetrics/shader_volumetrics_lighting_regimes.d.ts.map +0 -1
@@ -1,357 +1,382 @@
1
- import { BlendState } from "../../../descriptor/blend/BlendState.js";
2
- import { CAMERA_UNIFORM_STRUCT } from "../../camera/CAMERA_UNIFORM_STRUCT.js";
3
- import { DEPTH_STENCIL_FOREGROUND_ONLY } from "../../depth/DEPTH_STENCIL_FOREGROUND_ONLY.js";
4
- import { chunk_project_position_from_depth } from "../../shader/chunk/camera/chunk_project_position_from_depth.js";
5
- import { chunk_project_view_position_from_depth } from "../../shader/chunk/camera/chunk_project_view_position_from_depth.js";
6
- import { chunk_get_view_space_depth } from "../../shader/chunk/depth/chunk_get_view_space_depth.js";
7
- import { chunk_mat4_extract_position } from "../../shader/chunk/geometry/mat4/chunk_mat4_extract_position.js";
8
- import { chunk_decode_g_buffer_normal } from "../../shader/chunk/geometry/normal/chunk_decode_g_buffer_normal.js";
9
- import { chunk_encode_g_buffer_normal } from "../../shader/chunk/geometry/normal/chunk_encode_g_buffer_normal.js";
10
- import { chunk_v3_matrix4_rotate } from "../../shader/chunk/geometry/vec3/chunk_v3_matrix4_rotate.js";
11
- import { chunk_fast_acos } from "../../shader/chunk/math/fast/chunk_fast_acos.js";
12
- import { PI_DECLARATION } from "../../shader/chunk/math/PI_DECLARATION.js";
13
- import { PI_HALF_DECLARATION } from "../../shader/chunk/math/PI_HALF_DECLARATION.js";
14
- import { chunk_spatio_temporal_noise_r2_64 } from "../../shader/chunk/noise/chunk_spatio_temporal_noise_r2_64.js";
15
- import { CodeChunk } from "../../shader/compiler/CodeChunk.js";
16
- import { ImageShader } from "../../shader/ImageShader.js";
17
- import { ShaderResourceSetDescriptor } from "../../shader/resource/ShaderResourceSetDescriptor.js";
18
- import { ShaderDescriptor } from "../../shader/ShaderDescriptor.js";
19
- import { HILBERT_R2_INDEX_TEXTURE } from "../../texture/noise/HILBERT_R2_INDEX_TEXTURE.js";
20
- import { SAMPLER_NEAREST } from "../../texture/SAMPLER_LINEAR_UPSCALE.js";
21
- import { chunk_integrate_bent_normal_fast } from "./chunk_integrate_bent_normal_fast.js";
22
- import {
23
- GTAO_DEPTH_MIP_LEVELS,
24
- GTAO_EFFECT_FALLOFF_RANGE,
25
- GTAO_EFFECT_RADIUS,
26
- GTAO_QUALITY_PROFILES,
27
- GTAO_QUALITY_TYPE
28
- } from "./GTAO_CONSTANTS.js";
29
- import { GTAO_RENDER_SETTINGS } from "./GTAO_RENDER_SETTINGS.js";
30
-
31
- const resources = new ShaderResourceSetDescriptor();
32
-
33
- // per-pass, four bytes, read-only: immediate data rather than a uniform buffer.
34
- //
35
- // The uniform carried a `frame_index: 0` default, which does not travel: immediate data has no
36
- // default, and a dispatch that never wrote its slots is refused rather than reading zero. GTAO
37
- // passes frame_index on every dispatch, so nothing relied on it.
38
- resources.setImmediate("settings", GTAO_RENDER_SETTINGS);
39
- resources.createGroup()
40
- .addTexture("tDepth")
41
- .addSampler("sPoint", "non-filtering", SAMPLER_NEAREST)
42
- .addTexture("tNormal", "uint")
43
- .addTexture("tNoise", "uint", "2d", false, HILBERT_R2_INDEX_TEXTURE)
44
- .addUniform("camera", CAMERA_UNIFORM_STRUCT)
45
- ;
46
-
47
- const QUALITY_SETTINGS = GTAO_QUALITY_PROFILES[GTAO_QUALITY_TYPE.Medium];
48
-
49
- // the import paths these already had, kept working
50
- export { GTAO_DEPTH_MIP_LEVELS, GTAO_EFFECT_FALLOFF_RANGE, GTAO_EFFECT_RADIUS };
51
-
52
- /*
53
- based on https://github.com/GameTechDev/XeGTAO/tree/e7698f874e90f2516fca26c696ec3cd2c70e505a
54
-
55
- The horizon search reconstructs its samples in view space (2026-10-16). Measured on an RTX 4090 (Chrome,
56
- D3D12), the VSM playground's courtyard, Medium quality, TAA on. Arms swapped by pipeline every 4 frames
57
- inside one capture, 3 captures of 6 s each. GTAO's time, and the old world-space path against the new:
58
-
59
- near view (walls close) overview (geometry far)
60
- 1898 x 982 185 µs, old/new 1.00 84 µs, old/new 1.055 (p10 1.049)
61
- 2538 x 1342 364 µs, old/new 1.00 179 µs, old/new 1.041 (p10 1.048-1.055)
62
-
63
- Up close a 1 m radius spreads the 16 samples over hundreds of pixels and the pass waits on depth fetches:
64
- deleting the reconstruction outright bought nothing there (1.008, 1.010). At a distance the fetches hit
65
- cache and the arithmetic shows: deleting it bought 9.4% and 6.6%, and the view-space path recovers half.
66
- The same frame replayed through both: visibility differs in 0.1-0.18% of pixels, by one f16 step
67
- (0.00098); bent normals in 3-7% of pixels, by at most 0.014 degrees.
68
- */
69
- const body = CodeChunk.from(
70
- //language=WGSL
71
- `
72
- const scale = 1.0;
73
-
74
- // small crevices more important than big surfaces
75
- const sampleDistributionPower = 2.0;
76
-
77
- const SLICE_COUNT = ${QUALITY_SETTINGS.slices};
78
-
79
- const STEPS = ${QUALITY_SETTINGS.steps};
80
-
81
- struct Output{
82
- @location(0) visibility: vec2<f32>, // first 2 moment
83
- @location(1) bent_normal: vec2<u32>, // octahedral encoding
84
- }
85
-
86
- @fragment
87
- fn main(
88
- @builtin(position) coord: vec4<f32>,
89
- @location(0) uv: vec2<f32>,
90
- ) -> Output {
91
-
92
- // fadeout precompute optimisation
93
- const falloffRange = ${GTAO_EFFECT_FALLOFF_RANGE} * ${GTAO_EFFECT_RADIUS};
94
-
95
- const falloffFrom = ${GTAO_EFFECT_RADIUS} * (1.0 - ${GTAO_EFFECT_FALLOFF_RANGE});
96
- const falloffMul = -1.0 / ( falloffRange );
97
- const falloffAdd = falloffFrom / ( falloffRange ) + 1.0;
98
-
99
- let texel_position_u32 = vec2<u32>(coord.xy);
100
-
101
- let ViewportSize = textureDimensions(tDepth);
102
- let ViewportPixelSize = 1. / vec2<f32>(ViewportSize);
103
-
104
- var device_depth = textureLoad(tDepth, texel_position_u32, 0).r;
105
-
106
- // NOTE: no need to reject based on depth, already handled via depthStencil
107
-
108
- var viewspaceZ = get_view_space_depth(device_depth, camera);
109
-
110
- // Move center pixel slightly towards camera to avoid imprecision artifacts due to depth buffer imprecision; offset depends on depth texture format used
111
- viewspaceZ *= 0.99999; // this is good for FP32 depth buffer
112
-
113
- let position_ws = project_position_from_depth(uv, device_depth, camera.view_projection_matrix_inverse );
114
- let view_position_ws = mat4_extract_position(camera.view_matrix_inverse);
115
- let view_direction_ws = normalize(view_position_ws - position_ws);
116
-
117
- // The horizon search below only measures distances and angles, which the view rotation preserves, so it
118
- // runs in view space: a sample there is 4 multiply-adds from the projection's inverse instead of 16
119
- // from the view-projection's. Slices and the bent normal stay in world space.
120
- let position_vs = project_view_position_from_depth(uv, device_depth, camera.projection_matrix_inverse);
121
- let view_direction_vs = normalize(-position_vs);
122
-
123
- let view_normal_ws = decode_g_buffer_normal(textureLoad(tNormal, texel_position_u32, 0).xy); // shading normal
124
-
125
- let noiseTexel = spatio_temporal_noise_r2_64(texel_position_u32, settings.frame_index, tNoise);
126
-
127
- let noiseSample = noiseTexel.x;
128
- let noiseSlice = noiseTexel.y;
129
-
130
- // quality settings / tweaks / hacks
131
- const pixelTooCloseThreshold = 1.3; // if the offset is under approx pixel size (pixelTooCloseThreshold), push it out to the minimum distance
132
-
133
- // approx viewspace pixel size at pixCoord; approximation of NDCToViewspace( normalizedScreenPos.xy + consts.ViewportPixelSize.xy, pixCenterPos.z ).xy - pixCenterPos.xy;
134
- // device_depth_to_view_space.z = 1/a = aspect * tan(fovY/2) = tan(fovX/2), pairs with horizontal pixel size.
135
- // factor of 2 converts a single uv-step (= 1 pixel) into the matching ndc-step (range 2 wide).
136
- let NDCToViewMul_x_PixelSize = 2.0 * camera.device_depth_to_view_space.z * ViewportPixelSize.x;
137
- let pixelDirRBViewspaceSizeAtCenterZ = viewspaceZ * NDCToViewMul_x_PixelSize;
138
-
139
- let screenspaceRadius = abs( ${GTAO_EFFECT_RADIUS} / pixelDirRBViewspaceSizeAtCenterZ);
140
-
141
- var visibility = 0.0;
142
- var bent_normal:vec3<f32>;
143
-
144
- // this is the min distance to start sampling from to avoid sampling from the center pixel (no useful data obtained from sampling center pixel)
145
- let minS = pixelTooCloseThreshold / screenspaceRadius;
146
-
147
- const INV_SLICE_COUNT = 1.0 / f32(SLICE_COUNT);
148
-
149
- for (var slice = 0; slice < SLICE_COUNT; slice++) {
150
-
151
- let sliceK = ( f32(slice) + noiseSlice ) * INV_SLICE_COUNT;
152
-
153
- // lines 5,6 from the paper
154
- let phi = sliceK * PI; // slice angle
155
-
156
- let cosPhi = cos(phi);
157
- let sinPhi = sin(phi);
158
-
159
- let omega = vec2(cosPhi, -sinPhi) * screenspaceRadius;
160
-
161
- // line 8 from the paper
162
- let sliceViewDir = vec3(cosPhi, sinPhi, 0.0);
163
- let sliceWorldDir = v3_matrix4_rotate(sliceViewDir, camera.view_matrix_inverse);
164
-
165
- // line 9 from the paper
166
- // NOTE: optimized with FMA, readability suffers
167
- let orthoWorldDir = fma( vec3( -dot(sliceWorldDir, view_direction_ws) ), view_direction_ws, sliceWorldDir );
168
-
169
- // line 10 from the paper
170
- // axisVec is orthogonal to sliceViewDir and viewVec, used to define projectedNormal
171
- let axisVec = normalize( cross(orthoWorldDir, view_direction_ws) );
172
-
173
- // line 11 from paper
174
- let projectedWorldNormal = fma( -axisVec, vec3( dot(view_normal_ws, axisVec) ), view_normal_ws );
175
-
176
- // line 13 from the paper
177
- let signNorm = sign( dot( orthoWorldDir, projectedWorldNormal ) );
178
-
179
- // line 14 from the paper
180
- var projectedNormalVecLength = length(projectedWorldNormal);
181
- let norm_cos = saturate( dot( projectedWorldNormal, view_direction_ws ) / projectedNormalVecLength );
182
-
183
- // line 15 from the paper
184
- let n = signNorm * fast_acos(norm_cos);
185
-
186
- // this is a lower weight target; not using -1 as in the original paper because it is under horizon, so a 'weight' has different meaning based on the normal
187
- let lowHorizonCos0 = cos(n + PI_HALF);
188
- let lowHorizonCos1 = -lowHorizonCos0; // cos(n - PI_HALF);
189
-
190
- // lines 17, 18 from the paper, manually unrolled the 'side' loop
191
- var horizonCos0 = lowHorizonCos0; // -1
192
- var horizonCos1 = lowHorizonCos1; // -1
193
-
194
- const INV_STEPS = 1.0 / f32(STEPS);
195
-
196
- for (var step_index = 0; step_index < STEPS; step_index++) {
197
-
198
- // R1 sequence (http://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/)
199
- // Standard multiplier is 0.6180339887498948482 = ( 1 / GOLDEN_RATION )
200
- let stepBaseNoise = f32(slice + step_index * STEPS) * 0.6180339887498948482;
201
-
202
- let stepNoise = fract(noiseSample + stepBaseNoise);
203
-
204
- // approx line 20 from the paper, with added noise
205
- var s = ( f32(step_index) + stepNoise ) * INV_STEPS;
206
-
207
- // additional distribution modifier
208
- s = pow( s, sampleDistributionPower );
209
-
210
- // avoid sampling center pixel
211
- s += minS;
212
-
213
- // approx lines 21-22 from the paper, unrolled
214
- var sampleOffset = s * omega;
215
-
216
- // note: when sampling, using point_point_point or point_point_linear sampler works, but linear_linear_linear will cause unwanted interpolation between neighbouring depth values on the same MIP level!
217
- const mipLevel = 0; // Disabled currently
218
-
219
- // Snap to pixel center (more correct direction math, avoids artifacts due to sampling pos not matching depth texel center - messes up slope - but adds other
220
- // artifacts due to them being pushed off the slice). Also use full precision for high res cases.
221
- sampleOffset = round( sampleOffset ) * ViewportPixelSize;
222
-
223
- let sampleScreenPos0 = uv + sampleOffset;
224
-
225
- let SZ0 = textureSampleLevel(tDepth, sPoint, sampleScreenPos0, mipLevel).x;
226
- let samplePos0 = project_view_position_from_depth(sampleScreenPos0, SZ0, camera.projection_matrix_inverse);
227
-
228
- let sampleScreenPos1 = uv - sampleOffset;
229
- let SZ1 = textureSampleLevel(tDepth, sPoint, sampleScreenPos1, mipLevel).x;
230
- let samplePos1 = project_view_position_from_depth(sampleScreenPos1, SZ1, camera.projection_matrix_inverse);
231
-
232
- let sampleDelta0 = (samplePos0 - position_vs); // using lpfloat for sampleDelta causes precision issues
233
- let sampleDelta1 = (samplePos1 - position_vs); // using lpfloat for sampleDelta causes precision issues
234
-
235
- let sampleDist0 = length( sampleDelta0 );
236
- let sampleDist1 = length( sampleDelta1 );
237
-
238
- // approx lines 23, 24 from the paper, unrolled
239
- let sampleHorizonVec0 = sampleDelta0 / sampleDist0;
240
- let sampleHorizonVec1 = sampleDelta1 / sampleDist1;
241
-
242
- let weight0 = saturate( fma(sampleDist0, falloffMul, falloffAdd) );
243
- let weight1 = saturate( fma(sampleDist1, falloffMul, falloffAdd) );
244
-
245
- // sample horizon cos
246
- var shc0 = dot( sampleHorizonVec0, view_direction_vs );
247
- var shc1 = dot( sampleHorizonVec1, view_direction_vs );
248
-
249
- // discard unwanted samples
250
- shc0 = mix( lowHorizonCos0, shc0, weight0 ); // this would be more correct but too expensive: cos(lerp( acos(lowHorizonCos0), acos(shc0), weight0 ));
251
- shc1 = mix( lowHorizonCos1, shc1, weight1 ); // this would be more correct but too expensive: cos(lerp( acos(lowHorizonCos1), acos(shc1), weight1 ));
252
-
253
- // this is a version where thicknessHeuristic is completely disabled
254
- horizonCos0 = max( horizonCos0, shc0 );
255
- horizonCos1 = max( horizonCos1, shc1 );
256
-
257
- }
258
-
259
- // line ~27, unrolled
260
- let h0 = -fast_acos(horizonCos1);
261
- let h1 = fast_acos(horizonCos0);
262
-
263
- let norm_sin = sin(n);
264
- let norm_sin2 = 2.0 * norm_sin;
265
-
266
- let iarc0 = ( fma( h0 , norm_sin2, norm_cos ) - cos( fma( 2.0, h0, - n)) ) * 0.25;
267
- let iarc1 = ( fma( h1 , norm_sin2, norm_cos ) - cos( fma( 2.0, h1, - n)) ) * 0.25;
268
-
269
- // Slight overdarkening on high slopes is compensated by tilting projected length toward 1.
270
- // In the training set, 0.05 is close (PSNR 21.34) to disabled (PSNR 21.45).
271
- // Scoped to the visibility branch so bent normals keep their unfudged slice weighting.
272
- let visibilityProjLength = mix( projectedNormalVecLength, 1, 0.05 );
273
- let localVisibility = visibilityProjLength * ( iarc0 + iarc1 );
274
-
275
- visibility += localVisibility;
276
-
277
-
278
- // see "Algorithm 2 Extension that computes bent normals b."
279
- let slice_tangent_ws = normalize(orthoWorldDir);
280
- let local_bent_ts = integrate_bent_normal(
281
- horizonCos1,
282
- horizonCos0,
283
- norm_cos,
284
- norm_sin,
285
- );
286
-
287
- let t0 = local_bent_ts.x;
288
- let t1 = local_bent_ts.y;
289
-
290
- var slice_bent_normal_ws = slice_tangent_ws * t0 + view_direction_ws * t1;
291
-
292
- bent_normal = fma(slice_bent_normal_ws, vec3(projectedNormalVecLength), bent_normal);
293
- }
294
-
295
- visibility *= INV_SLICE_COUNT;
296
-
297
- visibility = pow(visibility, scale);
298
-
299
- // disallow total occlusion (which wouldn't make any sense anyhow since pixel is visible but also helps with packing bent normals)
300
- visibility = max(0.03, visibility);
301
-
302
- bent_normal = normalize(bent_normal);
303
-
304
- var out: Output;
305
- out.visibility = vec2(visibility, visibility * visibility);
306
- out.bent_normal = encode_g_buffer_normal( bent_normal );
307
-
308
- return out;
309
- }
310
- `, [
311
- chunk_get_view_space_depth,
312
- PI_DECLARATION,
313
- PI_HALF_DECLARATION,
314
-
315
- chunk_decode_g_buffer_normal,
316
- chunk_encode_g_buffer_normal,
317
- chunk_spatio_temporal_noise_r2_64,
318
-
319
- chunk_fast_acos,
320
-
321
- chunk_mat4_extract_position,
322
- chunk_project_position_from_depth,
323
- chunk_project_view_position_from_depth,
324
- chunk_v3_matrix4_rotate,
325
- chunk_integrate_bent_normal_fast,
326
- ]);
327
-
328
- export const fragment_shader_gtao = new ImageShader({
329
- descriptor: ShaderDescriptor.from(
330
- {
331
- label: "GTAO",
332
- resources,
333
- body
334
- },
335
- ),
336
- depthStencil: DEPTH_STENCIL_FOREGROUND_ONLY,
337
- targets: [
338
- {
339
- label: "Occlusion",
340
- // first 2 moments of visibility
341
- format: "rg16float",
342
- clearValue: [1, 1], // Stores visibility, so 1 == not occluded
343
- blend: BlendState.from({
344
- color: {
345
- operation: "add",
346
- srcFactor: "one",
347
- dstFactor: "zero",
348
- }
349
- })
350
- },
351
- {
352
- label: "Bent Normals",
353
- // Direction is encoded with octahedral
354
- format: "rg16uint",
355
- },
356
- ]
357
- });
1
+ import { BlendState } from "../../../descriptor/blend/BlendState.js";
2
+ import { CAMERA_UNIFORM_STRUCT } from "../../camera/CAMERA_UNIFORM_STRUCT.js";
3
+ import { DEPTH_STENCIL_FOREGROUND_ONLY } from "../../depth/DEPTH_STENCIL_FOREGROUND_ONLY.js";
4
+ import { chunk_project_position_from_depth } from "../../shader/chunk/camera/chunk_project_position_from_depth.js";
5
+ import { chunk_project_view_position_from_depth } from "../../shader/chunk/camera/chunk_project_view_position_from_depth.js";
6
+ import { chunk_get_view_space_depth } from "../../shader/chunk/depth/chunk_get_view_space_depth.js";
7
+ import { chunk_mat4_extract_position } from "../../shader/chunk/geometry/mat4/chunk_mat4_extract_position.js";
8
+ import { chunk_decode_g_buffer_normal } from "../../shader/chunk/geometry/normal/chunk_decode_g_buffer_normal.js";
9
+ import { chunk_encode_g_buffer_normal } from "../../shader/chunk/geometry/normal/chunk_encode_g_buffer_normal.js";
10
+ import { chunk_v3_matrix4_rotate } from "../../shader/chunk/geometry/vec3/chunk_v3_matrix4_rotate.js";
11
+ import { chunk_fast_acos } from "../../shader/chunk/math/fast/chunk_fast_acos.js";
12
+ import { PI_DECLARATION } from "../../shader/chunk/math/PI_DECLARATION.js";
13
+ import { PI_HALF_DECLARATION } from "../../shader/chunk/math/PI_HALF_DECLARATION.js";
14
+ import { chunk_spatio_temporal_noise_r2_64 } from "../../shader/chunk/noise/chunk_spatio_temporal_noise_r2_64.js";
15
+ import { CodeChunk } from "../../shader/compiler/CodeChunk.js";
16
+ import { ImageShader } from "../../shader/ImageShader.js";
17
+ import { ShaderResourceSetDescriptor } from "../../shader/resource/ShaderResourceSetDescriptor.js";
18
+ import { ShaderDescriptor } from "../../shader/ShaderDescriptor.js";
19
+ import { HILBERT_R2_INDEX_TEXTURE } from "../../texture/noise/HILBERT_R2_INDEX_TEXTURE.js";
20
+ import { SAMPLER_NEAREST } from "../../texture/SAMPLER_LINEAR_UPSCALE.js";
21
+ import { chunk_gtao_compute_mip_level } from "./chunk_gtao_compute_mip_level.js";
22
+ import { chunk_gtao_sample_depth } from "./chunk_gtao_sample_depth.js";
23
+ import { chunk_integrate_bent_normal_fast } from "./chunk_integrate_bent_normal_fast.js";
24
+ import {
25
+ GTAO_DEPTH_MIP_LEVELS,
26
+ GTAO_DEPTH_MIP_SAMPLING_OFFSET,
27
+ GTAO_EFFECT_FALLOFF_RANGE,
28
+ GTAO_EFFECT_RADIUS,
29
+ GTAO_QUALITY_PROFILES,
30
+ GTAO_QUALITY_TYPE
31
+ } from "./GTAO_CONSTANTS.js";
32
+ import { GTAO_RENDER_SETTINGS } from "./GTAO_RENDER_SETTINGS.js";
33
+
34
+ const resources = new ShaderResourceSetDescriptor();
35
+
36
+ // per-pass, four bytes, read-only: immediate data rather than a uniform buffer.
37
+ //
38
+ // The uniform carried a `frame_index: 0` default, which does not travel: immediate data has no
39
+ // default, and a dispatch that never wrote its slots is refused rather than reading zero. GTAO
40
+ // passes frame_index on every dispatch, so nothing relied on it.
41
+ resources.setImmediate("settings", GTAO_RENDER_SETTINGS);
42
+ resources.createGroup()
43
+ .addTexture("tDepth")
44
+ .addTexture("tHZB")
45
+ .addSampler("sPoint", "non-filtering", SAMPLER_NEAREST)
46
+ .addTexture("tNormal", "uint")
47
+ .addTexture("tNoise", "uint", "2d", false, HILBERT_R2_INDEX_TEXTURE)
48
+ .addUniform("camera", CAMERA_UNIFORM_STRUCT)
49
+ ;
50
+
51
+ const QUALITY_SETTINGS = GTAO_QUALITY_PROFILES[GTAO_QUALITY_TYPE.Medium];
52
+
53
+ // the import paths these already had, kept working
54
+ export { GTAO_DEPTH_MIP_LEVELS, GTAO_DEPTH_MIP_SAMPLING_OFFSET, GTAO_EFFECT_FALLOFF_RANGE, GTAO_EFFECT_RADIUS };
55
+
56
+ /*
57
+ based on https://github.com/GameTechDev/XeGTAO/tree/e7698f874e90f2516fca26c696ec3cd2c70e505a
58
+
59
+ The horizon search reconstructs its samples in view space (2026-10-16). Measured on an RTX 4090 (Chrome,
60
+ D3D12), the VSM playground's courtyard, Medium quality, TAA on. Arms swapped by pipeline every 4 frames
61
+ inside one capture, 3 captures of 6 s each. GTAO's time, and the old world-space path against the new:
62
+
63
+ near view (walls close) overview (geometry far)
64
+ 1898 x 982 185 µs, old/new 1.00 84 µs, old/new 1.055 (p10 1.049)
65
+ 2538 x 1342 364 µs, old/new 1.00 179 µs, old/new 1.041 (p10 1.048-1.055)
66
+
67
+ Up close a 1 m radius spreads the 16 samples over hundreds of pixels and the pass waits on depth fetches:
68
+ deleting the reconstruction outright bought nothing there (1.008, 1.010). At a distance the fetches hit
69
+ cache and the arithmetic shows: deleting it bought 9.4% and 6.6%, and the view-space path recovers half.
70
+ The same frame replayed through both: visibility differs in 0.1-0.18% of pixels, by one f16 step
71
+ (0.00098); bent normals in 3-7% of pixels, by at most 0.014 degrees.
72
+
73
+ HZB depth mipmap sampling (2026-10-23, `chunk_gtao_compute_mip_level` + `chunk_gtao_sample_depth`), same
74
+ courtyard setup, 3 captures of 60 frames each, arms swapped by pipeline every 4 frames. `mip0` (all taps
75
+ from `tDepth` mip 0) against `hzb_slope` (`gtao_resolve_hzb_depth` slope-aware filter) and `hzb_max`
76
+ (`tHZB.y` only):
77
+
78
+ near view (walls close) overview (geometry far)
79
+ 1898 x 982 mip0 192.5 µs -> hzb_slope 72.7 µs (2.65x) mip0 91.1 µs -> hzb_slope 88.1 µs (1.03x)
80
+ -> hzb_max 66.6 µs (2.89x) -> hzb_max 79.9 µs (1.14x)
81
+ 2538 x 1342 mip0 362.5 µs -> hzb_slope 142.3 µs (2.55x) mip0 180.2 µs -> hzb_slope 151.6 µs (1.19x)
82
+ -> hzb_max 123.9 µs (2.93x) -> hzb_max 135.2 µs (1.33x)
83
+
84
+ Same-frame quality replay against `mip0` (1898 x 982 / 2538 x 1342):
85
+ - overview: `hzb_slope` PSNR 52.74 / 48.78 dB (mean |dv| 0.00016 / 0.00027, bent normal mean 0.012° / 0.021°),
86
+ beating `hzb_max` (50.50 / 47.15 dB, 3.5x larger mean |dv| and 10x stronger darkening bias on slopes) and
87
+ unfiltered `0.5*(hzb.x+hzb.y)` (46.92 / 42.96 dB, 2.0x higher RMSE from silhouette shrink-wrap).
88
+ - near: `hzb_slope` PSNR 26.04 / 26.25 dB (mean |dv| 0.0107 / 0.0102, bent normal mean 0.91° / 0.87°),
89
+ vs `hzb_max` (25.52 / 25.65 dB, 1.6x stronger darkening bias) and unfiltered avg (20.66 / 20.79 dB).
90
+ */
91
+ const body = CodeChunk.from(
92
+ //language=WGSL
93
+ `
94
+ const scale = 1.0;
95
+
96
+ // small crevices more important than big surfaces
97
+ const sampleDistributionPower = 2.0;
98
+
99
+ const SLICE_COUNT = ${QUALITY_SETTINGS.slices};
100
+
101
+ const STEPS = ${QUALITY_SETTINGS.steps};
102
+
103
+ struct Output{
104
+ @location(0) visibility: vec2<f32>, // first 2 moment
105
+ @location(1) bent_normal: vec2<u32>, // octahedral encoding
106
+ }
107
+
108
+ @fragment
109
+ fn main(
110
+ @builtin(position) coord: vec4<f32>,
111
+ @location(0) uv: vec2<f32>,
112
+ ) -> Output {
113
+
114
+ // fadeout precompute optimisation
115
+ const falloffRange = ${GTAO_EFFECT_FALLOFF_RANGE} * ${GTAO_EFFECT_RADIUS};
116
+
117
+ const falloffFrom = ${GTAO_EFFECT_RADIUS} * (1.0 - ${GTAO_EFFECT_FALLOFF_RANGE});
118
+ const falloffMul = -1.0 / ( falloffRange );
119
+ const falloffAdd = falloffFrom / ( falloffRange ) + 1.0;
120
+
121
+ let texel_position_u32 = vec2<u32>(coord.xy);
122
+
123
+ let ViewportSize = textureDimensions(tDepth);
124
+ let ViewportPixelSize = 1. / vec2<f32>(ViewportSize);
125
+
126
+ var device_depth = textureLoad(tDepth, texel_position_u32, 0).r;
127
+
128
+ // NOTE: no need to reject based on depth, already handled via depthStencil
129
+
130
+ var viewspaceZ = get_view_space_depth(device_depth, camera);
131
+
132
+ // Move center pixel slightly towards camera to avoid imprecision artifacts due to depth buffer imprecision; offset depends on depth texture format used
133
+ viewspaceZ *= 0.99999; // this is good for FP32 depth buffer
134
+
135
+ let position_ws = project_position_from_depth(uv, device_depth, camera.view_projection_matrix_inverse );
136
+ let view_position_ws = mat4_extract_position(camera.view_matrix_inverse);
137
+ let view_direction_ws = normalize(view_position_ws - position_ws);
138
+
139
+ // The horizon search below only measures distances and angles, which the view rotation preserves, so it
140
+ // runs in view space: a sample there is 4 multiply-adds from the projection's inverse instead of 16
141
+ // from the view-projection's. Slices and the bent normal stay in world space.
142
+ let position_vs = project_view_position_from_depth(uv, device_depth, camera.projection_matrix_inverse);
143
+ let view_direction_vs = normalize(-position_vs);
144
+
145
+ let view_normal_ws = decode_g_buffer_normal(textureLoad(tNormal, texel_position_u32, 0).xy); // shading normal
146
+
147
+ let noiseTexel = spatio_temporal_noise_r2_64(texel_position_u32, settings.frame_index, tNoise);
148
+
149
+ let noiseSample = noiseTexel.x;
150
+ let noiseSlice = noiseTexel.y;
151
+
152
+ // quality settings / tweaks / hacks
153
+ const pixelTooCloseThreshold = 1.3; // if the offset is under approx pixel size (pixelTooCloseThreshold), push it out to the minimum distance
154
+
155
+ // approx viewspace pixel size at pixCoord; approximation of NDCToViewspace( normalizedScreenPos.xy + consts.ViewportPixelSize.xy, pixCenterPos.z ).xy - pixCenterPos.xy;
156
+ // device_depth_to_view_space.z = 1/a = aspect * tan(fovY/2) = tan(fovX/2), pairs with horizontal pixel size.
157
+ // factor of 2 converts a single uv-step (= 1 pixel) into the matching ndc-step (range 2 wide).
158
+ let NDCToViewMul_x_PixelSize = 2.0 * camera.device_depth_to_view_space.z * ViewportPixelSize.x;
159
+ let pixelDirRBViewspaceSizeAtCenterZ = viewspaceZ * NDCToViewMul_x_PixelSize;
160
+
161
+ let screenspaceRadius = abs( ${GTAO_EFFECT_RADIUS} / pixelDirRBViewspaceSizeAtCenterZ);
162
+
163
+ var visibility = 0.0;
164
+ var bent_normal:vec3<f32>;
165
+
166
+ // this is the min distance to start sampling from to avoid sampling from the center pixel (no useful data obtained from sampling center pixel)
167
+ let minS = pixelTooCloseThreshold / screenspaceRadius;
168
+
169
+ const INV_SLICE_COUNT = 1.0 / f32(SLICE_COUNT);
170
+
171
+ for (var slice = 0; slice < SLICE_COUNT; slice++) {
172
+
173
+ let sliceK = ( f32(slice) + noiseSlice ) * INV_SLICE_COUNT;
174
+
175
+ // lines 5,6 from the paper
176
+ let phi = sliceK * PI; // slice angle
177
+
178
+ let cosPhi = cos(phi);
179
+ let sinPhi = sin(phi);
180
+
181
+ let omega = vec2(cosPhi, -sinPhi) * screenspaceRadius;
182
+
183
+ // line 8 from the paper
184
+ let sliceViewDir = vec3(cosPhi, sinPhi, 0.0);
185
+ let sliceWorldDir = v3_matrix4_rotate(sliceViewDir, camera.view_matrix_inverse);
186
+
187
+ // line 9 from the paper
188
+ // NOTE: optimized with FMA, readability suffers
189
+ let orthoWorldDir = fma( vec3( -dot(sliceWorldDir, view_direction_ws) ), view_direction_ws, sliceWorldDir );
190
+
191
+ // line 10 from the paper
192
+ // axisVec is orthogonal to sliceViewDir and viewVec, used to define projectedNormal
193
+ let axisVec = normalize( cross(orthoWorldDir, view_direction_ws) );
194
+
195
+ // line 11 from paper
196
+ let projectedWorldNormal = fma( -axisVec, vec3( dot(view_normal_ws, axisVec) ), view_normal_ws );
197
+
198
+ // line 13 from the paper
199
+ let signNorm = sign( dot( orthoWorldDir, projectedWorldNormal ) );
200
+
201
+ // line 14 from the paper
202
+ var projectedNormalVecLength = length(projectedWorldNormal);
203
+ let norm_cos = saturate( dot( projectedWorldNormal, view_direction_ws ) / projectedNormalVecLength );
204
+
205
+ // line 15 from the paper
206
+ let n = signNorm * fast_acos(norm_cos);
207
+
208
+ // this is a lower weight target; not using -1 as in the original paper because it is under horizon, so a 'weight' has different meaning based on the normal
209
+ let lowHorizonCos0 = cos(n + PI_HALF);
210
+ let lowHorizonCos1 = -lowHorizonCos0; // cos(n - PI_HALF);
211
+
212
+ // lines 17, 18 from the paper, manually unrolled the 'side' loop
213
+ var horizonCos0 = lowHorizonCos0; // -1
214
+ var horizonCos1 = lowHorizonCos1; // -1
215
+
216
+ const INV_STEPS = 1.0 / f32(STEPS);
217
+
218
+ for (var step_index = 0; step_index < STEPS; step_index++) {
219
+
220
+ // R1 sequence (http://extremelearning.com.au/unreasonable-effectiveness-of-quasirandom-sequences/)
221
+ // Standard multiplier is 0.6180339887498948482 = ( 1 / GOLDEN_RATION )
222
+ let stepBaseNoise = f32(slice + step_index * STEPS) * 0.6180339887498948482;
223
+
224
+ let stepNoise = fract(noiseSample + stepBaseNoise);
225
+
226
+ // approx line 20 from the paper, with added noise
227
+ var s = ( f32(step_index) + stepNoise ) * INV_STEPS;
228
+
229
+ // additional distribution modifier
230
+ s = pow( s, sampleDistributionPower );
231
+
232
+ // avoid sampling center pixel
233
+ s += minS;
234
+
235
+ // approx lines 21-22 from the paper, unrolled
236
+ var sampleOffset = s * omega;
237
+
238
+ // omega = vec2(cosPhi, -sinPhi) * screenspaceRadius has length screenspaceRadius
239
+ let sampleOffsetLength = s * screenspaceRadius;
240
+ let mipLevel = gtao_compute_mip_level(sampleOffsetLength);
241
+
242
+ // Snap to pixel center (more correct direction math, avoids artifacts due to sampling pos not matching depth texel center - messes up slope - but adds other
243
+ // artifacts due to them being pushed off the slice). Also use full precision for high res cases.
244
+ sampleOffset = round( sampleOffset ) * ViewportPixelSize;
245
+
246
+ let sampleScreenPos0 = uv + sampleOffset;
247
+
248
+ let SZ0 = gtao_sample_depth(sampleScreenPos0, mipLevel, camera);
249
+ let samplePos0 = project_view_position_from_depth(sampleScreenPos0, SZ0, camera.projection_matrix_inverse);
250
+
251
+ let sampleScreenPos1 = uv - sampleOffset;
252
+ let SZ1 = gtao_sample_depth(sampleScreenPos1, mipLevel, camera);
253
+ let samplePos1 = project_view_position_from_depth(sampleScreenPos1, SZ1, camera.projection_matrix_inverse);
254
+
255
+ let sampleDelta0 = (samplePos0 - position_vs); // using lpfloat for sampleDelta causes precision issues
256
+ let sampleDelta1 = (samplePos1 - position_vs); // using lpfloat for sampleDelta causes precision issues
257
+
258
+ let sampleDist0 = length( sampleDelta0 );
259
+ let sampleDist1 = length( sampleDelta1 );
260
+
261
+ // approx lines 23, 24 from the paper, unrolled
262
+ let sampleHorizonVec0 = sampleDelta0 / sampleDist0;
263
+ let sampleHorizonVec1 = sampleDelta1 / sampleDist1;
264
+
265
+ let weight0 = saturate( fma(sampleDist0, falloffMul, falloffAdd) );
266
+ let weight1 = saturate( fma(sampleDist1, falloffMul, falloffAdd) );
267
+
268
+ // sample horizon cos
269
+ var shc0 = dot( sampleHorizonVec0, view_direction_vs );
270
+ var shc1 = dot( sampleHorizonVec1, view_direction_vs );
271
+
272
+ // discard unwanted samples
273
+ shc0 = mix( lowHorizonCos0, shc0, weight0 ); // this would be more correct but too expensive: cos(lerp( acos(lowHorizonCos0), acos(shc0), weight0 ));
274
+ shc1 = mix( lowHorizonCos1, shc1, weight1 ); // this would be more correct but too expensive: cos(lerp( acos(lowHorizonCos1), acos(shc1), weight1 ));
275
+
276
+ // this is a version where thicknessHeuristic is completely disabled
277
+ horizonCos0 = max( horizonCos0, shc0 );
278
+ horizonCos1 = max( horizonCos1, shc1 );
279
+
280
+ }
281
+
282
+ // line ~27, unrolled
283
+ let h0 = -fast_acos(horizonCos1);
284
+ let h1 = fast_acos(horizonCos0);
285
+
286
+ let norm_sin = sin(n);
287
+ let norm_sin2 = 2.0 * norm_sin;
288
+
289
+ let iarc0 = ( fma( h0 , norm_sin2, norm_cos ) - cos( fma( 2.0, h0, - n)) ) * 0.25;
290
+ let iarc1 = ( fma( h1 , norm_sin2, norm_cos ) - cos( fma( 2.0, h1, - n)) ) * 0.25;
291
+
292
+ // Slight overdarkening on high slopes is compensated by tilting projected length toward 1.
293
+ // In the training set, 0.05 is close (PSNR 21.34) to disabled (PSNR 21.45).
294
+ // Scoped to the visibility branch so bent normals keep their unfudged slice weighting.
295
+ let visibilityProjLength = mix( projectedNormalVecLength, 1, 0.05 );
296
+ let localVisibility = visibilityProjLength * ( iarc0 + iarc1 );
297
+
298
+ visibility += localVisibility;
299
+
300
+
301
+ // see "Algorithm 2 Extension that computes bent normals b."
302
+ let slice_tangent_ws = normalize(orthoWorldDir);
303
+ let local_bent_ts = integrate_bent_normal(
304
+ horizonCos1,
305
+ horizonCos0,
306
+ norm_cos,
307
+ norm_sin,
308
+ );
309
+
310
+ let t0 = local_bent_ts.x;
311
+ let t1 = local_bent_ts.y;
312
+
313
+ var slice_bent_normal_ws = slice_tangent_ws * t0 + view_direction_ws * t1;
314
+
315
+ bent_normal = fma(slice_bent_normal_ws, vec3(projectedNormalVecLength), bent_normal);
316
+ }
317
+
318
+ visibility *= INV_SLICE_COUNT;
319
+
320
+ visibility = pow(visibility, scale);
321
+
322
+ // disallow total occlusion (which wouldn't make any sense anyhow since pixel is visible but also helps with packing bent normals)
323
+ visibility = max(0.03, visibility);
324
+
325
+ bent_normal = normalize(bent_normal);
326
+
327
+ var out: Output;
328
+ out.visibility = vec2(visibility, visibility * visibility);
329
+ out.bent_normal = encode_g_buffer_normal( bent_normal );
330
+
331
+ return out;
332
+ }
333
+ `, [
334
+ chunk_get_view_space_depth,
335
+ PI_DECLARATION,
336
+ PI_HALF_DECLARATION,
337
+
338
+ chunk_decode_g_buffer_normal,
339
+ chunk_encode_g_buffer_normal,
340
+ chunk_spatio_temporal_noise_r2_64,
341
+
342
+ chunk_fast_acos,
343
+
344
+ chunk_mat4_extract_position,
345
+ chunk_project_position_from_depth,
346
+ chunk_project_view_position_from_depth,
347
+ chunk_v3_matrix4_rotate,
348
+ chunk_integrate_bent_normal_fast,
349
+ chunk_gtao_compute_mip_level,
350
+ chunk_gtao_sample_depth,
351
+ ]);
352
+
353
+ export const fragment_shader_gtao = new ImageShader({
354
+ descriptor: ShaderDescriptor.from(
355
+ {
356
+ label: "GTAO",
357
+ resources,
358
+ body
359
+ },
360
+ ),
361
+ depthStencil: DEPTH_STENCIL_FOREGROUND_ONLY,
362
+ targets: [
363
+ {
364
+ label: "Occlusion",
365
+ // first 2 moments of visibility
366
+ format: "rg16float",
367
+ clearValue: [1, 1], // Stores visibility, so 1 == not occluded
368
+ blend: BlendState.from({
369
+ color: {
370
+ operation: "add",
371
+ srcFactor: "one",
372
+ dstFactor: "zero",
373
+ }
374
+ })
375
+ },
376
+ {
377
+ label: "Bent Normals",
378
+ // Direction is encoded with octahedral
379
+ format: "rg16uint",
380
+ },
381
+ ]
382
+ });