@woosh/meep-engine 2.165.4 → 2.165.6

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 (179) hide show
  1. package/editor/Editor.d.ts +65 -65
  2. package/editor/SelectionVisualizer.d.ts +19 -19
  3. package/editor/actions/concrete/ActionUpdateTexture.d.ts +11 -11
  4. package/editor/actions/concrete/ArrayCopyAction.d.ts +19 -19
  5. package/editor/actions/concrete/ComponentAddAction.d.ts +14 -14
  6. package/editor/actions/concrete/ComponentRemoveAction.d.ts +10 -10
  7. package/editor/actions/concrete/EntityCreateAction.d.ts +17 -17
  8. package/editor/actions/concrete/EntityRemoveAction.d.ts +10 -10
  9. package/editor/actions/concrete/ModifyPatchSampler2DAction.d.ts +46 -46
  10. package/editor/actions/concrete/ModifyPatchTextureArray2DAction.d.ts +37 -37
  11. package/editor/actions/concrete/PaintTerrainOverlayAction.d.ts +22 -22
  12. package/editor/actions/concrete/PatchTerrainHeightAction.d.ts +18 -18
  13. package/editor/actions/concrete/SelectionAddAction.d.ts +18 -18
  14. package/editor/actions/concrete/SelectionClearAction.d.ts +8 -8
  15. package/editor/actions/concrete/SelectionRemoveAction.d.ts +9 -9
  16. package/editor/actions/concrete/TransformModifyAction.d.ts +16 -16
  17. package/editor/actions/concrete/WriteGridValueAction.d.ts +14 -14
  18. package/editor/ecs/EditorEntity.d.ts +15 -15
  19. package/editor/ecs/EditorEntitySystem.d.ts +7 -7
  20. package/editor/ecs/component/FieldDescriptor.d.ts +26 -26
  21. package/editor/ecs/component/FieldValueAdapter.d.ts +6 -6
  22. package/editor/ecs/component/TypeEditor.d.ts +24 -24
  23. package/editor/ecs/component/createFieldEditor.d.ts +8 -8
  24. package/editor/ecs/component/createObjectEditor.d.ts +13 -13
  25. package/editor/ecs/component/editors/ColorEditor.d.ts +6 -6
  26. package/editor/ecs/component/editors/HTMLElementEditor.d.ts +6 -6
  27. package/editor/ecs/component/editors/ImagePathEditor.d.ts +6 -6
  28. package/editor/ecs/component/editors/LargeStringEditor.d.ts +15 -15
  29. package/editor/ecs/component/editors/NumericIntervalEditor.d.ts +6 -6
  30. package/editor/ecs/component/editors/ObservedBooleanEditor.d.ts +6 -6
  31. package/editor/ecs/component/editors/ObservedEnumEditor.d.ts +6 -6
  32. package/editor/ecs/component/editors/ObservedIntegerEditor.d.ts +6 -6
  33. package/editor/ecs/component/editors/ObservedStringEditor.d.ts +6 -6
  34. package/editor/ecs/component/editors/Sampler2DEditor.d.ts +6 -6
  35. package/editor/ecs/component/editors/collection/ListEditor.d.ts +7 -7
  36. package/editor/ecs/component/editors/common/BitFlagsEditor.d.ts +11 -11
  37. package/editor/ecs/component/editors/common/EnumEditor.d.ts +7 -7
  38. package/editor/ecs/component/editors/common/makeV3_editor.d.ts +14 -14
  39. package/editor/ecs/component/editors/common/noEditor.d.ts +2 -2
  40. package/editor/ecs/component/editors/ecs/GridObstacleEditor.d.ts +6 -6
  41. package/editor/ecs/component/editors/ecs/MinimapMarkerEditor.d.ts +13 -13
  42. package/editor/ecs/component/editors/ecs/ParameterLookupTableEditor.d.ts +66 -66
  43. package/editor/ecs/component/editors/ecs/ParameterTrackEditor.d.ts +11 -11
  44. package/editor/ecs/component/editors/ecs/ParameterTrackSetEditor.d.ts +13 -13
  45. package/editor/ecs/component/editors/ecs/ParticleEmitterEditor.d.ts +51 -51
  46. package/editor/ecs/component/editors/ecs/ParticleEmitterLayerEditor.d.ts +47 -47
  47. package/editor/ecs/component/editors/ecs/SimulationStepDefinitionEditor.d.ts +15 -15
  48. package/editor/ecs/component/editors/ecs/Trail2DEditor.d.ts +29 -29
  49. package/editor/ecs/component/editors/ecs/TransformEditor.d.ts +19 -19
  50. package/editor/ecs/component/editors/ecs/terrain/SplatMappingEditor.d.ts +18 -18
  51. package/editor/ecs/component/editors/ecs/terrain/TerrainEditor.d.ts +38 -38
  52. package/editor/ecs/component/editors/ecs/terrain/TerrainLayerEditor.d.ts +15 -15
  53. package/editor/ecs/component/editors/ecs/terrain/TerrainLayersEditor.d.ts +18 -18
  54. package/editor/ecs/component/editors/ecs/terrain/TerrainOverlayEditor.d.ts +17 -17
  55. package/editor/ecs/component/editors/geom/QuaternionEditor.d.ts +5 -5
  56. package/editor/ecs/component/editors/geom/Vector1Editor.d.ts +6 -6
  57. package/editor/ecs/component/editors/geom/Vector2Editor.d.ts +5 -5
  58. package/editor/ecs/component/editors/geom/Vector3Editor.d.ts +5 -5
  59. package/editor/ecs/component/editors/geom/Vector4Editor.d.ts +5 -5
  60. package/editor/ecs/component/editors/primitive/ArrayEditor.d.ts +7 -7
  61. package/editor/ecs/component/editors/primitive/BooleanEditor.d.ts +6 -6
  62. package/editor/ecs/component/editors/primitive/FunctionEditor.d.ts +6 -6
  63. package/editor/ecs/component/editors/primitive/NumberEditor.d.ts +7 -7
  64. package/editor/ecs/component/editors/primitive/ObjectEditor.d.ts +5 -5
  65. package/editor/ecs/component/editors/primitive/StringEditor.d.ts +6 -6
  66. package/editor/ecs/component/editors/three/BufferGeometryEditor.d.ts +6 -6
  67. package/editor/ecs/component/editors/three/MaterialEditor.d.ts +6 -6
  68. package/editor/ecs/component/editors/three/MeshEditor.d.ts +6 -6
  69. package/editor/ecs/component/editors/three/TextureEditor.d.ts +6 -6
  70. package/editor/ecs/component/findNearestRegisteredType.d.ts +7 -7
  71. package/editor/enableEditor.d.ts +13 -13
  72. package/editor/library/MeshLibrary.d.ts +12 -12
  73. package/editor/process/DisableGameUIProcess.d.ts +7 -7
  74. package/editor/process/EditorProcess.d.ts +18 -18
  75. package/editor/process/ObstacleGridDisplayProcess.d.ts +12 -12
  76. package/editor/process/ProcessEngine.d.ts +29 -29
  77. package/editor/process/SymbolicDisplayProcess.d.ts +18 -18
  78. package/editor/process/symbolic/ComponentSymbolicDisplay.d.ts +10 -10
  79. package/editor/process/symbolic/SymbolicDisplayInternalAPI.d.ts +64 -64
  80. package/editor/process/symbolic/buildThreeJSHelperEntity.d.ts +8 -8
  81. package/editor/process/symbolic/make3DSymbolicDisplay.d.ts +9 -9
  82. package/editor/process/symbolic/makeCameraSymbolicDisplay.d.ts +6 -6
  83. package/editor/process/symbolic/makeGridPositionSymbolDisplay.d.ts +5 -5
  84. package/editor/process/symbolic/makeHelperBoxGeometry.d.ts +2 -2
  85. package/editor/process/symbolic/makeHelperSphereGeometry.d.ts +8 -8
  86. package/editor/process/symbolic/makeLightSymbolicDisplay.d.ts +6 -6
  87. package/editor/process/symbolic/makeParticleEmitterSymbolicDisplay.d.ts +5 -5
  88. package/editor/process/symbolic/makePathSymbolicDisplay.d.ts +6 -6
  89. package/editor/process/symbolic/makePositionedIconDisplaySymbol.d.ts +8 -8
  90. package/editor/process/symbolic/makeSocketsSymbolicDisplay.d.ts +6 -6
  91. package/editor/process/symbolic/makeSolidArrowGeometry.d.ts +25 -25
  92. package/editor/process/symbolic/makeSoundEmitterSymbolicDisplay.d.ts +5 -5
  93. package/editor/process/symbolic/synchronizeTransform.d.ts +11 -11
  94. package/editor/tools/GridPaintTool.d.ts +16 -16
  95. package/editor/tools/SelectionTool.d.ts +26 -26
  96. package/editor/tools/TopDownCameraControlTool.d.ts +12 -12
  97. package/editor/tools/engine/Tool.d.ts +63 -63
  98. package/editor/tools/engine/ToolEngine.d.ts +22 -22
  99. package/editor/tools/engine/ToolState.d.ts +6 -6
  100. package/editor/tools/paint/TerrainHeightPaintTool.d.ts +17 -17
  101. package/editor/tools/paint/TerrainPaintTool.d.ts +30 -30
  102. package/editor/tools/paint/TerrainTexturePaintTool.d.ts +17 -17
  103. package/editor/tools/v2/TransformMode.d.ts +6 -6
  104. package/editor/view/EditorView.d.ts +38 -38
  105. package/editor/view/ecs/ComponentControlFactory.d.ts +18 -18
  106. package/editor/view/ecs/ComponentControlView.d.ts +23 -23
  107. package/editor/view/ecs/EntityEditor.d.ts +29 -29
  108. package/editor/view/ecs/EntityList.d.ts +21 -21
  109. package/editor/view/ecs/HierarchicalEntityListView.d.ts +7 -7
  110. package/editor/view/ecs/components/DatGuiController.d.ts +45 -45
  111. package/editor/view/ecs/components/GridObstacleController.d.ts +13 -13
  112. package/editor/view/ecs/components/color/GaugeView.d.ts +12 -12
  113. package/editor/view/ecs/components/common/AutoCanvasView.d.ts +19 -19
  114. package/editor/view/ecs/components/common/LineView.d.ts +11 -11
  115. package/editor/view/ecs/components/common/NumberController.d.ts +17 -17
  116. package/editor/view/ecs/components/particles/ColorParameterLUTController.d.ts +13 -13
  117. package/editor/view/makeEntityDecorators.d.ts +5 -5
  118. package/editor/view/tools/ToolSettingsView.d.ts +10 -10
  119. package/editor/view/tools/ToolView.d.ts +12 -12
  120. package/editor/view/v2/SplitView.d.ts +16 -16
  121. package/package.json +1 -1
  122. package/samples/generation/grid/GridTags.d.ts +15 -15
  123. package/samples/generation/grid/MirGridLayers.d.ts +7 -7
  124. package/samples/generation/rules/matcher_tag_occupied.d.ts +2 -2
  125. package/samples/generation/rules/matcher_tag_traversable.d.ts +2 -2
  126. package/samples/generation/rules/matcher_tag_unoccupied.d.ts +2 -2
  127. package/src/engine/ecs/gui/hud/HeadsUpDisplaySystem.d.ts.map +1 -1
  128. package/src/engine/ecs/gui/hud/HeadsUpDisplaySystem.js +65 -12
  129. package/src/engine/ecs/gui/hud/testHudEdgeStick.d.ts +2 -0
  130. package/src/engine/ecs/gui/hud/testHudEdgeStick.d.ts.map +1 -0
  131. package/src/engine/ecs/gui/hud/testHudEdgeStick.js +134 -0
  132. package/src/engine/ecs/gui/position/ViewportPositionSystem.d.ts.map +1 -1
  133. package/src/engine/ecs/gui/position/ViewportPositionSystem.js +44 -23
  134. package/src/engine/graphics/ecs/light/LightContext.d.ts +8 -0
  135. package/src/engine/graphics/ecs/light/LightContext.d.ts.map +1 -1
  136. package/src/engine/graphics/ecs/light/LightContext.js +33 -3
  137. package/src/engine/graphics/ecs/light/LightSystem.d.ts.map +1 -1
  138. package/src/engine/graphics/ecs/light/LightSystem.js +13 -6
  139. package/src/engine/graphics/ecs/light/binding/three/ThreeLightBinding.d.ts +12 -0
  140. package/src/engine/graphics/ecs/light/binding/three/ThreeLightBinding.d.ts.map +1 -1
  141. package/src/engine/graphics/ecs/light/binding/three/ThreeLightBinding.js +184 -6
  142. package/src/engine/graphics/ecs/trail3d/Trail3D.d.ts +24 -1
  143. package/src/engine/graphics/ecs/trail3d/Trail3D.d.ts.map +1 -1
  144. package/src/engine/graphics/ecs/trail3d/Trail3D.js +43 -0
  145. package/src/engine/graphics/ecs/trail3d/Trail3DFlags.d.ts +1 -0
  146. package/src/engine/graphics/ecs/trail3d/Trail3DFlags.js +9 -1
  147. package/src/engine/graphics/ecs/trail3d/Trail3DSystem.d.ts.map +1 -1
  148. package/src/engine/graphics/ecs/trail3d/Trail3DSystem.js +4 -0
  149. package/src/engine/graphics/ecs/trail3d/prototypeTrail3D.js +22 -1
  150. package/src/engine/graphics/render/buffer/simple-fx/ao/SAOShader.js +381 -362
  151. package/src/engine/graphics/render/forward_plus/LightManager.d.ts +5 -0
  152. package/src/engine/graphics/render/forward_plus/LightManager.d.ts.map +1 -1
  153. package/src/engine/graphics/render/forward_plus/LightManager.js +11 -0
  154. package/src/engine/graphics/render/forward_plus/model/AbstractLight.d.ts +16 -1
  155. package/src/engine/graphics/render/forward_plus/model/AbstractLight.d.ts.map +1 -1
  156. package/src/engine/graphics/render/forward_plus/model/AbstractLight.js +22 -1
  157. package/src/engine/graphics/render/forward_plus/model/Decal.d.ts +7 -0
  158. package/src/engine/graphics/render/forward_plus/model/Decal.d.ts.map +1 -1
  159. package/src/engine/graphics/render/forward_plus/model/Decal.js +11 -0
  160. package/src/engine/graphics/render/forward_plus/model/PointLight.d.ts +2 -0
  161. package/src/engine/graphics/render/forward_plus/model/PointLight.d.ts.map +1 -1
  162. package/src/engine/graphics/render/forward_plus/model/PointLight.js +10 -0
  163. package/src/engine/graphics/render/forward_plus/model/SpotLight.d.ts +2 -0
  164. package/src/engine/graphics/render/forward_plus/model/SpotLight.d.ts.map +1 -1
  165. package/src/engine/graphics/render/forward_plus/model/SpotLight.js +12 -0
  166. package/src/engine/graphics/shadows/ShadowMapRenderer.d.ts +10 -0
  167. package/src/engine/graphics/shadows/ShadowMapRenderer.d.ts.map +1 -1
  168. package/src/engine/graphics/shadows/ShadowMapRenderer.js +83 -10
  169. package/src/engine/graphics/shadows/testPointShadowMapRendering.d.ts +2 -0
  170. package/src/engine/graphics/shadows/testPointShadowMapRendering.d.ts.map +1 -0
  171. package/src/engine/graphics/shadows/testPointShadowMapRendering.js +242 -0
  172. package/src/engine/graphics/trail/tube/TubeMaterialX.d.ts +8 -1
  173. package/src/engine/graphics/trail/tube/TubeMaterialX.d.ts.map +1 -1
  174. package/src/engine/graphics/trail/tube/TubeMaterialX.js +111 -15
  175. package/src/engine/graphics/trail/tube/TubeXMaterialSpec.d.ts +9 -2
  176. package/src/engine/graphics/trail/tube/TubeXMaterialSpec.d.ts.map +1 -1
  177. package/src/engine/graphics/trail/tube/TubeXMaterialSpec.js +15 -4
  178. package/src/engine/graphics/trail/tube/TubeXPlugin.d.ts.map +1 -1
  179. package/src/engine/graphics/trail/tube/TubeXPlugin.js +7 -1
@@ -1,362 +1,381 @@
1
- import { Matrix4, Vector2 } from 'three';
2
-
3
- /**
4
- * Point-sampled screen-space volumetric obscurance.
5
- *
6
- * A hemisphere of radius `kernelRadius` is sampled in the volume above the surface (oriented by the
7
- * surface normal, lifted slightly off it to avoid self-occlusion). Each sample is counted as
8
- * occluded when it lies behind the depth buffer *and* the surface it tapped is itself within
9
- * `kernelRadius` (so distant geometry seen through the depth buffer does not occlude). The
10
- * occluded fraction is a physically plausible obscurance estimate, so `intensity = 1.0` produces a
11
- * correct-looking result with no scene-dependent multiplier; higher values are an artistic
12
- * darkening control.
13
- *
14
- * Sample placement uses a fixed R3 low-discrepancy kernel rotated per pixel by an R2 blue-noise
15
- * dither (interleaved sampling). The blue-noise rotation pushes the sampling error into high
16
- * frequencies that the edge-aware upscale downstream removes cleanly, leaving little residual.
17
- */
18
-
19
- const SAOShader = {
20
- defines: {
21
- 'NUM_SAMPLES': 7,
22
- 'NORMAL_TEXTURE': 0,
23
- 'DEPTH_PACKING': 1,
24
- 'PERSPECTIVE_CAMERA': 1
25
- },
26
- uniforms: {
27
-
28
- 'tDepth': { value: null },
29
- 'tNormal': { value: null },
30
- 'tHilbert': { value: null },
31
- 'size': { value: new Vector2(512, 512) },
32
-
33
- 'cameraNear': { value: 1 },
34
- 'cameraFar': { value: 100 },
35
- 'cameraProjectionMatrix': { value: new Matrix4() },
36
- 'cameraInverseProjectionMatrix': { value: new Matrix4() },
37
-
38
- // artistic darkening control; 1.0 == physically plausible
39
- 'intensity': { value: 1.0 },
40
-
41
- // radius of the sampling hemisphere, in world/view-space units; also the maximum distance at
42
- // which a tapped surface counts as an occluder
43
- 'kernelRadius': { value: 0.5 }
44
- },
45
-
46
- //language=GLSL
47
- vertexShader:`
48
-
49
- varying vec2 vUv;
50
-
51
- void main() {
52
- vUv = uv;
53
- gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
54
- }`,
55
-
56
- //language=GLSL
57
- fragmentShader: `
58
-
59
- #include <common>
60
-
61
- varying vec2 vUv;
62
-
63
- uniform sampler2D tDepth;
64
-
65
- #if NORMAL_TEXTURE == 1
66
- uniform sampler2D tNormal;
67
- #endif
68
-
69
- uniform highp usampler2D tHilbert;
70
-
71
- uniform float cameraNear;
72
- uniform float cameraFar;
73
- uniform mat4 cameraProjectionMatrix;
74
- uniform mat4 cameraInverseProjectionMatrix;
75
-
76
- uniform float intensity;
77
- uniform float kernelRadius;
78
- uniform vec2 size;
79
-
80
- // RGBA depth
81
-
82
- #include <packing>
83
-
84
- out float out_occlusion;
85
-
86
- float getDepth( const in vec2 screenPosition ) {
87
- #if DEPTH_PACKING == 1
88
- return unpackRGBAToDepth( texture2D( tDepth, screenPosition ) );
89
- #else
90
- return texture2D( tDepth, screenPosition ).x;
91
- #endif
92
- }
93
-
94
- float getViewZ( const in float depth ) {
95
- #if PERSPECTIVE_CAMERA == 1
96
- return perspectiveDepthToViewZ( depth, cameraNear, cameraFar );
97
- #else
98
- return orthographicDepthToViewZ( depth, cameraNear, cameraFar );
99
- #endif
100
- }
101
-
102
- vec3 getViewPosition( const in vec2 screenPosition, const in float depth, const in float viewZ ) {
103
- float clipW = cameraProjectionMatrix[2][3] * viewZ + cameraProjectionMatrix[3][3];
104
- vec4 clipPosition = vec4( ( vec3( screenPosition, depth ) - 0.5 ) * 2.0, 1.0 );
105
- clipPosition *= clipW; // unprojection.
106
-
107
- return ( cameraInverseProjectionMatrix * clipPosition ).xyz;
108
- }
109
-
110
- vec3 getViewNormal( const in vec3 viewPosition, const in vec2 screenPosition ) {
111
- #if NORMAL_TEXTURE == 1
112
- return unpackRGBToNormal( texture2D( tNormal, screenPosition ).xyz );
113
- #else
114
- // Accurate normal reconstruction (Drobot): for each axis pick the nearer-in-depth
115
- // neighbour, so the tangent basis never straddles a silhouette. dFdx-based normals read
116
- // garbage across depth discontinuities, which mis-orients the sampling hemisphere and
117
- // makes flat, camera-facing surfaces self-occlude along their edges.
118
- vec2 texel = 1.0 / size;
119
-
120
- float dc = getDepth( screenPosition );
121
- float dl = getDepth( screenPosition - vec2( texel.x, 0.0 ) );
122
- float dr = getDepth( screenPosition + vec2( texel.x, 0.0 ) );
123
- float dd = getDepth( screenPosition - vec2( 0.0, texel.y ) );
124
- float du = getDepth( screenPosition + vec2( 0.0, texel.y ) );
125
-
126
- vec3 dpdx;
127
- if ( abs( dl - dc ) < abs( dr - dc ) ) {
128
- vec3 left = getViewPosition( screenPosition - vec2( texel.x, 0.0 ), dl, getViewZ( dl ) );
129
- dpdx = viewPosition - left;
130
- } else {
131
- vec3 right = getViewPosition( screenPosition + vec2( texel.x, 0.0 ), dr, getViewZ( dr ) );
132
- dpdx = right - viewPosition;
133
- }
134
-
135
- vec3 dpdy;
136
- if ( abs( dd - dc ) < abs( du - dc ) ) {
137
- vec3 down = getViewPosition( screenPosition - vec2( 0.0, texel.y ), dd, getViewZ( dd ) );
138
- dpdy = viewPosition - down;
139
- } else {
140
- vec3 up = getViewPosition( screenPosition + vec2( 0.0, texel.y ), du, getViewZ( du ) );
141
- dpdy = up - viewPosition;
142
- }
143
-
144
- return normalize( cross( dpdx, dpdy ) );
145
- #endif
146
- }
147
-
148
- // Marty's R2 low-discrepancy sequence: maps a permutation index to a 2D blue-noise-like point.
149
- // (GLSL ES has no fma; the expanded multiply-add is equivalent here.)
150
- vec2 r2_marty( uint index ) {
151
- return fract( vec2( float( index ) ) * vec2( 0.245122333753, 0.430159709002 ) + 0.5 );
152
- }
153
-
154
- // Concentric disk mapping (Shirley & Chiu): low-distortion uniform disk sample from [0,1)^2.
155
- vec2 sampleUniformDiskConcentric( const in vec2 u ) {
156
- vec2 offset = 2.0 * u - 1.0;
157
-
158
- if ( offset.x == 0.0 && offset.y == 0.0 ) return vec2( 0.0 );
159
-
160
- float r;
161
- float theta;
162
-
163
- if ( abs( offset.x ) > abs( offset.y ) ) {
164
- r = offset.x;
165
- theta = ( PI * 0.25 ) * ( offset.y / offset.x );
166
- } else {
167
- r = offset.y;
168
- theta = ( PI * 0.5 ) - ( PI * 0.25 ) * ( offset.x / offset.y );
169
- }
170
-
171
- return r * vec2( cos( theta ), sin( theta ) );
172
- }
173
-
174
- // Cosine-weighted hemisphere direction in tangent space (z is up), via Malley's method.
175
- vec3 sampleCosineWeightedHemisphere( const in vec2 u ) {
176
- vec2 d = sampleUniformDiskConcentric( u );
177
- float z = sqrt( max( 0.0, 1.0 - dot( d, d ) ) );
178
- return vec3( d.x, d.y, z );
179
- }
180
-
181
- // Branchless orthonormal basis from a unit normal (Duff et al. 2017). Columns are T, B, N, so
182
- // basis * v maps a tangent-space vector (z up) into the normal's frame.
183
- mat3 buildOrthonormalBasis( const in vec3 n ) {
184
- vec3 T;
185
- vec3 B;
186
-
187
- if ( n.z < 0.0 ) {
188
- float a = 1.0 / ( 1.0 - n.z );
189
- float b = n.x * n.y * a;
190
- T = vec3( 1.0 - n.x * n.x * a, -b, n.x );
191
- B = vec3( b, n.y * n.y * a - 1.0, -n.y );
192
- } else {
193
- float a = 1.0 / ( 1.0 + n.z );
194
- float b = -n.x * n.y * a;
195
- T = vec3( 1.0 - n.x * n.x * a, b, -n.x );
196
- B = vec3( b, 1.0 - n.y * n.y * a, -n.y );
197
- }
198
-
199
- return mat3( T, B, n );
200
- }
201
-
202
- // Roberts' R3 low-discrepancy additive recurrence: ( 1/g, 1/g^2, 1/g^3 ), g^4 = g + 1
203
- const vec3 R3 = vec3( 0.8191725134, 0.6710436067, 0.5497004779 );
204
-
205
- // normal-bias as a fraction of kernelRadius: lifts the sample origin off the surface to avoid
206
- // self-occlusion. Larger removes acne but lets near-surface contact occlusion leak.
207
- const float SURFACE_BIAS = 0.05;
208
-
209
- // push each sample this many pixels further along its screen-space direction so it never
210
- // reads the centre pixel (self-occlusion); folded into the offset instead of a hard reject
211
- const float SELF_OCCLUSION_MARGIN_PIXELS = 2.0;
212
-
213
- // radial distribution bias: normalized radius -> pow( radius, power ) pulls samples toward
214
- // the origin, where occlusion contributes most (small crevices over large open surfaces)
215
- const float sampleDistributionPower = 2.0;
216
-
217
- // distant samples contribute less (less reliable / more likely to be noise): fraction of the
218
- // radius over which an occluder's contribution fades to zero at the edge. Must be in [0, 1]. (GTAO)
219
- const float GTAO_EFFECT_FALLOFF_RANGE = 0.615;
220
-
221
- float getOccludedFraction( const in vec3 centerViewPosition, const in vec2 centerUv ) {
222
- vec3 centerViewNormal = getViewNormal( centerViewPosition, centerUv );
223
-
224
- // sample origin lifted slightly off the surface along the normal (see SURFACE_BIAS)
225
- vec3 sampleOrigin = centerViewPosition + centerViewNormal * ( kernelRadius * SURFACE_BIAS );
226
-
227
- // tangent->view basis, built once and reused for every sample
228
- mat3 tbn = buildOrthonormalBasis( centerViewNormal );
229
-
230
- // per-pixel 2D blue noise: a Hilbert-curve permutation index (spatially well distributed)
231
- // fed through Marty's R2 sequence. .x rotates the kernel around the normal, .y offsets the
232
- // sampling radius -- decorrelating neighbours so the edge-aware upscale resolves them.
233
- uint hilbertIndex = texelFetch( tHilbert, ivec2( gl_FragCoord.xy ) & 63, 0 ).x;
234
- vec2 noise = r2_marty( hilbertIndex );
235
-
236
- float ca = cos( noise.x * PI2 );
237
- float sa = sin( noise.x * PI2 );
238
- vec3 rT = tbn[0] * ca + tbn[1] * sa;
239
- vec3 rB = tbn[1] * ca - tbn[0] * sa;
240
- tbn[0] = rT;
241
- tbn[1] = rB;
242
-
243
- // distance falloff (GTAO): full weight until falloffFrom, then linear to zero at the radius
244
- float falloffRange = GTAO_EFFECT_FALLOFF_RANGE * kernelRadius;
245
- float falloffFrom = kernelRadius - falloffRange;
246
- float falloffMul = -1.0 / falloffRange;
247
- float falloffAdd = falloffFrom / falloffRange + 1.0;
248
-
249
- float occlusion = 0.0;
250
- float weightSum = 0.0;
251
-
252
- for ( int i = 0; i < NUM_SAMPLES; i ++ ) {
253
- // fixed R3 low-discrepancy point; per-pixel variation comes from the rotated frame
254
- // and the radius offset below
255
- vec3 q = fract( float( i ) * R3 );
256
-
257
- // cosine-weighted hemisphere direction, oriented to the (rotated) surface frame:
258
- // concentrates samples near the normal where occlusion matters most
259
- vec3 dir = normalize( tbn * sampleCosineWeightedHemisphere( q.xy ) );
260
-
261
- // normalized radius with a per-pixel offset, biased toward the origin (see
262
- // sampleDistributionPower) so the near occluders that dominate AO are favoured
263
- float radius = pow( fract( q.z + noise.y ), sampleDistributionPower );
264
-
265
- // falloff weight from the probe's own distance -- defined for every sample, so the
266
- // weighted normaliser is well posed; distant probes are less reliable (GTAO)
267
- float weight = saturate( radius * kernelRadius * falloffMul + falloffAdd );
268
-
269
- vec3 samplePositionVS = sampleOrigin + dir * ( radius * kernelRadius );
270
-
271
- // project the sample into screen space
272
- vec4 clip = cameraProjectionMatrix * vec4( samplePositionVS, 1.0 );
273
- vec2 sampleUv = ( clip.xy / clip.w ) * 0.5 + 0.5;
274
-
275
- // screen-space offset from the centre, in pixels
276
- vec2 offsetPixels = ( sampleUv - centerUv ) * size;
277
-
278
- // push forward along the screen direction by the self-occlusion margin (extends the
279
- // screen-space radius), then snap to the nearest pixel centre so the depth fetch is
280
- // exact and neighbouring pixels stay coherent (GTAO)
281
- float offsetLength = length( offsetPixels );
282
- offsetPixels += offsetPixels / max( offsetLength, EPSILON ) * SELF_OCCLUSION_MARGIN_PIXELS;
283
- offsetPixels = round( offsetPixels );
284
-
285
- sampleUv = centerUv + offsetPixels / size;
286
-
287
- // samples outside the screen have no depth to test against
288
- if ( sampleUv.x < 0.0 || sampleUv.x > 1.0 || sampleUv.y < 0.0 || sampleUv.y > 1.0 ) {
289
- continue;
290
- }
291
-
292
- // a valid (on-screen) sample contributes its weight to the normaliser, occluded or not
293
- weightSum += weight;
294
-
295
- float sampleDepth = getDepth( sampleUv );
296
-
297
- // skip the sky / far plane
298
- if ( sampleDepth >= ( 1.0 - EPSILON ) ) {
299
- continue;
300
- }
301
-
302
- float sceneViewZ = getViewZ( sampleDepth );
303
-
304
- // ...and the tapped surface must actually be within the sampling radius, otherwise we
305
- // have hit distant geometry through the depth buffer that does not occlude this point
306
- vec3 occluderVS = getViewPosition( sampleUv, sampleDepth, sceneViewZ );
307
- vec3 to_hit = occluderVS - centerViewPosition;
308
-
309
- float distance_to_hit = length( to_hit );
310
-
311
- if ( distance_to_hit > kernelRadius ) {
312
- continue;
313
- }
314
-
315
- vec3 direction_to_hit = normalize(occluderVS - sampleOrigin);
316
-
317
- if( dot( direction_to_hit, centerViewNormal) < 0.06 ){ // ~sqrt( 1/255 )
318
- // unable to occlude
319
- continue;
320
- }
321
-
322
-
323
- // occluded: add this sample's weight to the numerator
324
- occlusion += weight;
325
- }
326
-
327
- // weighted occlusion fraction; off-screen samples were excluded so screen borders are unbiased
328
- return weightSum > 0.0 ? occlusion / weightSum : 0.0;
329
- }
330
-
331
- void main() {
332
- // snap to the centre of the full-res depth texel we sample. The half-res pass otherwise
333
- // lands on texel boundaries, where NEAREST biases consistently to one side and the
334
- // reconstructed position disagrees with the sampled depth -> asymmetric edge self-occlusion.
335
- vec2 centerUv = ( floor( vUv * size ) + 0.5 ) / size;
336
-
337
- float centerDepth = getDepth( centerUv );
338
- if( centerDepth >= ( 1.0 - EPSILON ) ) {
339
- // background / far plane is fully visible. Write white rather than discarding, so the
340
- // half-res buffer never leaves the clear value for the upscale to read back as occlusion
341
- // (that is the foreground-edge halo over background).
342
- out_occlusion = 1.0;
343
- return;
344
- }
345
-
346
- float centerViewZ = getViewZ( centerDepth );
347
- vec3 viewPosition = getViewPosition( centerUv, centerDepth, centerViewZ );
348
-
349
- float occlusion = getOccludedFraction( viewPosition, centerUv );
350
-
351
- // intensity is an artistic power: 1.0 leaves the physical estimate untouched, higher
352
- // darkens occluded areas while leaving fully-lit areas at 1.0
353
- float visibility = pow( 1.0 - occlusion, intensity );
354
-
355
- // never fully occlude: if the receiver can see a sample at all it is not truly black, and
356
- // the R8 target cannot represent anything below 1/255 anyway
357
- out_occlusion = max( 1.0 / 255.0, visibility );
358
- }`
359
-
360
- };
361
-
362
- export { SAOShader };
1
+ import { Matrix4, Vector2 } from 'three';
2
+
3
+ /**
4
+ * Point-sampled screen-space volumetric obscurance.
5
+ *
6
+ * A hemisphere of radius `kernelRadius` is sampled in the volume above the surface (oriented by the
7
+ * surface normal, lifted slightly off it to avoid self-occlusion). Each sample is counted as
8
+ * occluded when it lies behind the depth buffer *and* the surface it tapped is itself within
9
+ * `kernelRadius` (so distant geometry seen through the depth buffer does not occlude). The
10
+ * occluded fraction is a physically plausible obscurance estimate, so `intensity = 1.0` produces a
11
+ * correct-looking result with no scene-dependent multiplier; higher values are an artistic
12
+ * darkening control.
13
+ *
14
+ * Sample placement uses a fixed R3 low-discrepancy kernel rotated per pixel by an R2 blue-noise
15
+ * dither (interleaved sampling). The blue-noise rotation pushes the sampling error into high
16
+ * frequencies that the edge-aware upscale downstream removes cleanly, leaving little residual.
17
+ */
18
+
19
+ const SAOShader = {
20
+ defines: {
21
+ 'NUM_SAMPLES': 7,
22
+ 'NORMAL_TEXTURE': 0,
23
+ 'DEPTH_PACKING': 1,
24
+ 'PERSPECTIVE_CAMERA': 1
25
+ },
26
+ uniforms: {
27
+
28
+ 'tDepth': { value: null },
29
+ 'tNormal': { value: null },
30
+ 'tHilbert': { value: null },
31
+ 'size': { value: new Vector2(512, 512) },
32
+
33
+ 'cameraNear': { value: 1 },
34
+ 'cameraFar': { value: 100 },
35
+ 'cameraProjectionMatrix': { value: new Matrix4() },
36
+ 'cameraInverseProjectionMatrix': { value: new Matrix4() },
37
+
38
+ // artistic darkening control; 1.0 == physically plausible
39
+ 'intensity': { value: 1.0 },
40
+
41
+ // radius of the sampling hemisphere, in world/view-space units; also the maximum distance at
42
+ // which a tapped surface counts as an occluder
43
+ 'kernelRadius': { value: 0.5 }
44
+ },
45
+
46
+ //language=GLSL
47
+ vertexShader:`
48
+
49
+ varying vec2 vUv;
50
+
51
+ void main() {
52
+ vUv = uv;
53
+ gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
54
+ }`,
55
+
56
+ //language=GLSL
57
+ fragmentShader: `
58
+
59
+ #include <common>
60
+
61
+ varying vec2 vUv;
62
+
63
+ uniform sampler2D tDepth;
64
+
65
+ #if NORMAL_TEXTURE == 1
66
+ uniform sampler2D tNormal;
67
+ #endif
68
+
69
+ uniform highp usampler2D tHilbert;
70
+
71
+ uniform float cameraNear;
72
+ uniform float cameraFar;
73
+ uniform mat4 cameraProjectionMatrix;
74
+ uniform mat4 cameraInverseProjectionMatrix;
75
+
76
+ uniform float intensity;
77
+ uniform float kernelRadius;
78
+ uniform vec2 size;
79
+
80
+ // RGBA depth
81
+
82
+ #include <packing>
83
+
84
+ out float out_occlusion;
85
+
86
+ float getDepth( const in vec2 screenPosition ) {
87
+ #if DEPTH_PACKING == 1
88
+ return unpackRGBAToDepth( texture2D( tDepth, screenPosition ) );
89
+ #else
90
+ return texture2D( tDepth, screenPosition ).x;
91
+ #endif
92
+ }
93
+
94
+ float getViewZ( const in float depth ) {
95
+ #if PERSPECTIVE_CAMERA == 1
96
+ return perspectiveDepthToViewZ( depth, cameraNear, cameraFar );
97
+ #else
98
+ return orthographicDepthToViewZ( depth, cameraNear, cameraFar );
99
+ #endif
100
+ }
101
+
102
+ vec3 getViewPosition( const in vec2 screenPosition, const in float depth, const in float viewZ ) {
103
+ float clipW = cameraProjectionMatrix[2][3] * viewZ + cameraProjectionMatrix[3][3];
104
+ vec4 clipPosition = vec4( ( vec3( screenPosition, depth ) - 0.5 ) * 2.0, 1.0 );
105
+ clipPosition *= clipW; // unprojection.
106
+
107
+ return ( cameraInverseProjectionMatrix * clipPosition ).xyz;
108
+ }
109
+
110
+ vec3 getViewNormal( const in vec3 viewPosition, const in vec2 screenPosition ) {
111
+ #if NORMAL_TEXTURE == 1
112
+ return unpackRGBToNormal( texture2D( tNormal, screenPosition ).xyz );
113
+ #else
114
+ // Accurate normal reconstruction (Drobot): for each axis pick the nearer-in-depth
115
+ // neighbour, so the tangent basis never straddles a silhouette. dFdx-based normals read
116
+ // garbage across depth discontinuities, which mis-orients the sampling hemisphere and
117
+ // makes flat, camera-facing surfaces self-occlude along their edges.
118
+ vec2 texel = 1.0 / size;
119
+
120
+ float dc = getDepth( screenPosition );
121
+ float dl = getDepth( screenPosition - vec2( texel.x, 0.0 ) );
122
+ float dr = getDepth( screenPosition + vec2( texel.x, 0.0 ) );
123
+ float dd = getDepth( screenPosition - vec2( 0.0, texel.y ) );
124
+ float du = getDepth( screenPosition + vec2( 0.0, texel.y ) );
125
+
126
+ // A tap past the screen border clamps back onto the centre texel (CLAMP_TO_EDGE) and
127
+ // reads dc: a zero depth-delta that always wins the nearer-in-depth pick yet carries no
128
+ // depth slope, collapsing the normal toward the view axis along the outermost texel
129
+ // row/column. The mis-oriented hemisphere then reads nearby ground as an occluder -- a
130
+ // dark band along the top screen edge on tilted top-down cameras. Use the interior side.
131
+ bool useLeft = abs( dl - dc ) < abs( dr - dc );
132
+ if ( screenPosition.x < texel.x ) {
133
+ useLeft = false;
134
+ } else if ( screenPosition.x + texel.x > 1.0 ) {
135
+ useLeft = true;
136
+ }
137
+
138
+ vec3 dpdx;
139
+ if ( useLeft ) {
140
+ vec3 left = getViewPosition( screenPosition - vec2( texel.x, 0.0 ), dl, getViewZ( dl ) );
141
+ dpdx = viewPosition - left;
142
+ } else {
143
+ vec3 right = getViewPosition( screenPosition + vec2( texel.x, 0.0 ), dr, getViewZ( dr ) );
144
+ dpdx = right - viewPosition;
145
+ }
146
+
147
+ bool useDown = abs( dd - dc ) < abs( du - dc );
148
+ if ( screenPosition.y < texel.y ) {
149
+ useDown = false;
150
+ } else if ( screenPosition.y + texel.y > 1.0 ) {
151
+ useDown = true;
152
+ }
153
+
154
+ vec3 dpdy;
155
+ if ( useDown ) {
156
+ vec3 down = getViewPosition( screenPosition - vec2( 0.0, texel.y ), dd, getViewZ( dd ) );
157
+ dpdy = viewPosition - down;
158
+ } else {
159
+ vec3 up = getViewPosition( screenPosition + vec2( 0.0, texel.y ), du, getViewZ( du ) );
160
+ dpdy = up - viewPosition;
161
+ }
162
+
163
+ return normalize( cross( dpdx, dpdy ) );
164
+ #endif
165
+ }
166
+
167
+ // Marty's R2 low-discrepancy sequence: maps a permutation index to a 2D blue-noise-like point.
168
+ // (GLSL ES has no fma; the expanded multiply-add is equivalent here.)
169
+ vec2 r2_marty( uint index ) {
170
+ return fract( vec2( float( index ) ) * vec2( 0.245122333753, 0.430159709002 ) + 0.5 );
171
+ }
172
+
173
+ // Concentric disk mapping (Shirley & Chiu): low-distortion uniform disk sample from [0,1)^2.
174
+ vec2 sampleUniformDiskConcentric( const in vec2 u ) {
175
+ vec2 offset = 2.0 * u - 1.0;
176
+
177
+ if ( offset.x == 0.0 && offset.y == 0.0 ) return vec2( 0.0 );
178
+
179
+ float r;
180
+ float theta;
181
+
182
+ if ( abs( offset.x ) > abs( offset.y ) ) {
183
+ r = offset.x;
184
+ theta = ( PI * 0.25 ) * ( offset.y / offset.x );
185
+ } else {
186
+ r = offset.y;
187
+ theta = ( PI * 0.5 ) - ( PI * 0.25 ) * ( offset.x / offset.y );
188
+ }
189
+
190
+ return r * vec2( cos( theta ), sin( theta ) );
191
+ }
192
+
193
+ // Cosine-weighted hemisphere direction in tangent space (z is up), via Malley's method.
194
+ vec3 sampleCosineWeightedHemisphere( const in vec2 u ) {
195
+ vec2 d = sampleUniformDiskConcentric( u );
196
+ float z = sqrt( max( 0.0, 1.0 - dot( d, d ) ) );
197
+ return vec3( d.x, d.y, z );
198
+ }
199
+
200
+ // Branchless orthonormal basis from a unit normal (Duff et al. 2017). Columns are T, B, N, so
201
+ // basis * v maps a tangent-space vector (z up) into the normal's frame.
202
+ mat3 buildOrthonormalBasis( const in vec3 n ) {
203
+ vec3 T;
204
+ vec3 B;
205
+
206
+ if ( n.z < 0.0 ) {
207
+ float a = 1.0 / ( 1.0 - n.z );
208
+ float b = n.x * n.y * a;
209
+ T = vec3( 1.0 - n.x * n.x * a, -b, n.x );
210
+ B = vec3( b, n.y * n.y * a - 1.0, -n.y );
211
+ } else {
212
+ float a = 1.0 / ( 1.0 + n.z );
213
+ float b = -n.x * n.y * a;
214
+ T = vec3( 1.0 - n.x * n.x * a, b, -n.x );
215
+ B = vec3( b, 1.0 - n.y * n.y * a, -n.y );
216
+ }
217
+
218
+ return mat3( T, B, n );
219
+ }
220
+
221
+ // Roberts' R3 low-discrepancy additive recurrence: ( 1/g, 1/g^2, 1/g^3 ), g^4 = g + 1
222
+ const vec3 R3 = vec3( 0.8191725134, 0.6710436067, 0.5497004779 );
223
+
224
+ // normal-bias as a fraction of kernelRadius: lifts the sample origin off the surface to avoid
225
+ // self-occlusion. Larger removes acne but lets near-surface contact occlusion leak.
226
+ const float SURFACE_BIAS = 0.05;
227
+
228
+ // push each sample this many pixels further along its screen-space direction so it never
229
+ // reads the centre pixel (self-occlusion); folded into the offset instead of a hard reject
230
+ const float SELF_OCCLUSION_MARGIN_PIXELS = 2.0;
231
+
232
+ // radial distribution bias: normalized radius -> pow( radius, power ) pulls samples toward
233
+ // the origin, where occlusion contributes most (small crevices over large open surfaces)
234
+ const float sampleDistributionPower = 2.0;
235
+
236
+ // distant samples contribute less (less reliable / more likely to be noise): fraction of the
237
+ // radius over which an occluder's contribution fades to zero at the edge. Must be in [0, 1]. (GTAO)
238
+ const float GTAO_EFFECT_FALLOFF_RANGE = 0.615;
239
+
240
+ float getOccludedFraction( const in vec3 centerViewPosition, const in vec2 centerUv ) {
241
+ vec3 centerViewNormal = getViewNormal( centerViewPosition, centerUv );
242
+
243
+ // sample origin lifted slightly off the surface along the normal (see SURFACE_BIAS)
244
+ vec3 sampleOrigin = centerViewPosition + centerViewNormal * ( kernelRadius * SURFACE_BIAS );
245
+
246
+ // tangent->view basis, built once and reused for every sample
247
+ mat3 tbn = buildOrthonormalBasis( centerViewNormal );
248
+
249
+ // per-pixel 2D blue noise: a Hilbert-curve permutation index (spatially well distributed)
250
+ // fed through Marty's R2 sequence. .x rotates the kernel around the normal, .y offsets the
251
+ // sampling radius -- decorrelating neighbours so the edge-aware upscale resolves them.
252
+ uint hilbertIndex = texelFetch( tHilbert, ivec2( gl_FragCoord.xy ) & 63, 0 ).x;
253
+ vec2 noise = r2_marty( hilbertIndex );
254
+
255
+ float ca = cos( noise.x * PI2 );
256
+ float sa = sin( noise.x * PI2 );
257
+ vec3 rT = tbn[0] * ca + tbn[1] * sa;
258
+ vec3 rB = tbn[1] * ca - tbn[0] * sa;
259
+ tbn[0] = rT;
260
+ tbn[1] = rB;
261
+
262
+ // distance falloff (GTAO): full weight until falloffFrom, then linear to zero at the radius
263
+ float falloffRange = GTAO_EFFECT_FALLOFF_RANGE * kernelRadius;
264
+ float falloffFrom = kernelRadius - falloffRange;
265
+ float falloffMul = -1.0 / falloffRange;
266
+ float falloffAdd = falloffFrom / falloffRange + 1.0;
267
+
268
+ float occlusion = 0.0;
269
+ float weightSum = 0.0;
270
+
271
+ for ( int i = 0; i < NUM_SAMPLES; i ++ ) {
272
+ // fixed R3 low-discrepancy point; per-pixel variation comes from the rotated frame
273
+ // and the radius offset below
274
+ vec3 q = fract( float( i ) * R3 );
275
+
276
+ // cosine-weighted hemisphere direction, oriented to the (rotated) surface frame:
277
+ // concentrates samples near the normal where occlusion matters most
278
+ vec3 dir = normalize( tbn * sampleCosineWeightedHemisphere( q.xy ) );
279
+
280
+ // normalized radius with a per-pixel offset, biased toward the origin (see
281
+ // sampleDistributionPower) so the near occluders that dominate AO are favoured
282
+ float radius = pow( fract( q.z + noise.y ), sampleDistributionPower );
283
+
284
+ // falloff weight from the probe's own distance -- defined for every sample, so the
285
+ // weighted normaliser is well posed; distant probes are less reliable (GTAO)
286
+ float weight = saturate( radius * kernelRadius * falloffMul + falloffAdd );
287
+
288
+ vec3 samplePositionVS = sampleOrigin + dir * ( radius * kernelRadius );
289
+
290
+ // project the sample into screen space
291
+ vec4 clip = cameraProjectionMatrix * vec4( samplePositionVS, 1.0 );
292
+ vec2 sampleUv = ( clip.xy / clip.w ) * 0.5 + 0.5;
293
+
294
+ // screen-space offset from the centre, in pixels
295
+ vec2 offsetPixels = ( sampleUv - centerUv ) * size;
296
+
297
+ // push forward along the screen direction by the self-occlusion margin (extends the
298
+ // screen-space radius), then snap to the nearest pixel centre so the depth fetch is
299
+ // exact and neighbouring pixels stay coherent (GTAO)
300
+ float offsetLength = length( offsetPixels );
301
+ offsetPixels += offsetPixels / max( offsetLength, EPSILON ) * SELF_OCCLUSION_MARGIN_PIXELS;
302
+ offsetPixels = round( offsetPixels );
303
+
304
+ sampleUv = centerUv + offsetPixels / size;
305
+
306
+ // samples outside the screen have no depth to test against
307
+ if ( sampleUv.x < 0.0 || sampleUv.x > 1.0 || sampleUv.y < 0.0 || sampleUv.y > 1.0 ) {
308
+ continue;
309
+ }
310
+
311
+ // a valid (on-screen) sample contributes its weight to the normaliser, occluded or not
312
+ weightSum += weight;
313
+
314
+ float sampleDepth = getDepth( sampleUv );
315
+
316
+ // skip the sky / far plane
317
+ if ( sampleDepth >= ( 1.0 - EPSILON ) ) {
318
+ continue;
319
+ }
320
+
321
+ float sceneViewZ = getViewZ( sampleDepth );
322
+
323
+ // ...and the tapped surface must actually be within the sampling radius, otherwise we
324
+ // have hit distant geometry through the depth buffer that does not occlude this point
325
+ vec3 occluderVS = getViewPosition( sampleUv, sampleDepth, sceneViewZ );
326
+ vec3 to_hit = occluderVS - centerViewPosition;
327
+
328
+ float distance_to_hit = length( to_hit );
329
+
330
+ if ( distance_to_hit > kernelRadius ) {
331
+ continue;
332
+ }
333
+
334
+ vec3 direction_to_hit = normalize(occluderVS - sampleOrigin);
335
+
336
+ if( dot( direction_to_hit, centerViewNormal) < 0.06 ){ // ~sqrt( 1/255 )
337
+ // unable to occlude
338
+ continue;
339
+ }
340
+
341
+
342
+ // occluded: add this sample's weight to the numerator
343
+ occlusion += weight;
344
+ }
345
+
346
+ // weighted occlusion fraction; off-screen samples were excluded so screen borders are unbiased
347
+ return weightSum > 0.0 ? occlusion / weightSum : 0.0;
348
+ }
349
+
350
+ void main() {
351
+ // snap to the centre of the full-res depth texel we sample. The half-res pass otherwise
352
+ // lands on texel boundaries, where NEAREST biases consistently to one side and the
353
+ // reconstructed position disagrees with the sampled depth -> asymmetric edge self-occlusion.
354
+ vec2 centerUv = ( floor( vUv * size ) + 0.5 ) / size;
355
+
356
+ float centerDepth = getDepth( centerUv );
357
+ if( centerDepth >= ( 1.0 - EPSILON ) ) {
358
+ // background / far plane is fully visible. Write white rather than discarding, so the
359
+ // half-res buffer never leaves the clear value for the upscale to read back as occlusion
360
+ // (that is the foreground-edge halo over background).
361
+ out_occlusion = 1.0;
362
+ return;
363
+ }
364
+
365
+ float centerViewZ = getViewZ( centerDepth );
366
+ vec3 viewPosition = getViewPosition( centerUv, centerDepth, centerViewZ );
367
+
368
+ float occlusion = getOccludedFraction( viewPosition, centerUv );
369
+
370
+ // intensity is an artistic power: 1.0 leaves the physical estimate untouched, higher
371
+ // darkens occluded areas while leaving fully-lit areas at 1.0
372
+ float visibility = pow( 1.0 - occlusion, intensity );
373
+
374
+ // never fully occlude: if the receiver can see a sample at all it is not truly black, and
375
+ // the R8 target cannot represent anything below 1/255 anyway
376
+ out_occlusion = max( 1.0 / 255.0, visibility );
377
+ }`
378
+
379
+ };
380
+
381
+ export { SAOShader };