|
|
@@ -1,5 +1,6 @@
|
|
|
-import { DataTexture, RenderTarget, RepeatWrapping, Vector2, Vector3, TempNode, QuadMesh, NodeMaterial, RendererUtils, RedFormat, FloatType, NearestFilter } from 'three/webgpu';
|
|
|
+import { RenderTarget, Vector2, TempNode, QuadMesh, NodeMaterial, RendererUtils, RedFormat, FloatType, NearestFilter } from 'three/webgpu';
|
|
|
import { reference, logarithmicDepthToViewZ, viewZToPerspectiveDepth, getNormalFromDepth, getScreenPosition, getViewPosition, nodeObject, Fn, float, NodeUpdateType, uv, uniform, Loop, vec2, vec3, vec4, int, dot, max, min, pow, abs, If, textureSize, sin, cos, PI, texture, passTexture, mat3, add, normalize, mul, cross, mix, acos, clamp } from 'three/tsl';
|
|
|
+import { generateBlueNoiseTexture } from '../../math/BlueNoise.js';
|
|
|
|
|
|
const _quadMesh = /*@__PURE__*/ new QuadMesh();
|
|
|
const _size = /*@__PURE__*/ new Vector2();
|
|
|
@@ -186,12 +187,14 @@ class GTAONode extends TempNode {
|
|
|
this.useTemporalFiltering = false;
|
|
|
|
|
|
/**
|
|
|
- * The node represents the internal noise texture used by the AO.
|
|
|
+ * Blue-noise texture sampled for the slice rotation and step jitter.
|
|
|
+ * Generated by Ulichney's void-and-cluster method (see
|
|
|
+ * {@link generateBlueNoiseTexture}); 64×64 single-channel, tileable.
|
|
|
*
|
|
|
* @private
|
|
|
* @type {TextureNode}
|
|
|
*/
|
|
|
- this._noiseNode = texture( generateMagicSquareNoise() );
|
|
|
+ this._noiseNode = texture( generateBlueNoiseTexture( 64 ) );
|
|
|
|
|
|
/**
|
|
|
* Represents the projection matrix of the scene's camera.
|
|
|
@@ -436,13 +439,19 @@ class GTAONode extends TempNode {
|
|
|
|
|
|
const radiusToUse = this.radius;
|
|
|
|
|
|
+ // Tile the blue-noise texture across the screen at its native resolution.
|
|
|
const noiseResolution = textureSize( this._noiseNode, 0 );
|
|
|
- let noiseUv = vec2( uvNode.x, uvNode.y.oneMinus() );
|
|
|
- noiseUv = noiseUv.mul( this.resolution.div( noiseResolution ) );
|
|
|
+ const noiseUv = uvNode.mul( this.resolution.div( noiseResolution ) );
|
|
|
|
|
|
- const noiseTexel = sampleNoise( noiseUv );
|
|
|
- const randomVec = noiseTexel.xyz.mul( 2.0 ).sub( 1.0 );
|
|
|
- const tangent = vec3( randomVec.xy, 0.0 ).normalize();
|
|
|
+ // Single-channel blue noise: take two samples with a half-texture UV offset
|
|
|
+ // for a decorrelated second channel. `noise1` → slice rotation, `noise2` →
|
|
|
+ // per-step phase jitter.
|
|
|
+ const noise1 = sampleNoise( noiseUv ).r;
|
|
|
+ const noise2 = sampleNoise( noiseUv.add( vec2( 0.5, 0.5 ) ) ).r;
|
|
|
+
|
|
|
+ // Random tangent direction from noise1, used to rotate the per-slice azimuth.
|
|
|
+ const tangentAngle = noise1.mul( PI ).mul( 2.0 );
|
|
|
+ const tangent = vec3( cos( tangentAngle ), sin( tangentAngle ), 0.0 );
|
|
|
const bitangent = vec3( tangent.y.mul( - 1.0 ), tangent.x, 0.0 );
|
|
|
const kernelMatrix = mat3( tangent, bitangent, vec3( 0.0, 0.0, 1.0 ) );
|
|
|
|
|
|
@@ -455,7 +464,7 @@ class GTAONode extends TempNode {
|
|
|
|
|
|
// Per-step phase jitter for spatio-temporal decorrelation.
|
|
|
// (Activision GTAO slides 86, 92–93 "Noise Distribution".)
|
|
|
- const stepJitter = mul( 0.5, noiseTexel.w ).toVar();
|
|
|
+ const stepJitter = mul( 0.5, noise2 ).toVar();
|
|
|
|
|
|
Loop( { start: int( 0 ), end: DIRECTIONS, type: 'int', condition: '<' }, ( { i } ) => {
|
|
|
|
|
|
@@ -629,102 +638,6 @@ class GTAONode extends TempNode {
|
|
|
|
|
|
export default GTAONode;
|
|
|
|
|
|
-/**
|
|
|
- * Generates the AO's noise texture for the given size.
|
|
|
- *
|
|
|
- * @param {number} [size=5] - The noise size.
|
|
|
- * @return {DataTexture} The generated noise texture.
|
|
|
- */
|
|
|
-function generateMagicSquareNoise( size = 5 ) {
|
|
|
-
|
|
|
- const noiseSize = Math.floor( size ) % 2 === 0 ? Math.floor( size ) + 1 : Math.floor( size );
|
|
|
- const magicSquare = generateMagicSquare( noiseSize );
|
|
|
- const noiseSquareSize = magicSquare.length;
|
|
|
- const data = new Uint8Array( noiseSquareSize * 4 );
|
|
|
-
|
|
|
- for ( let inx = 0; inx < noiseSquareSize; ++ inx ) {
|
|
|
-
|
|
|
- const iAng = magicSquare[ inx ];
|
|
|
- const angle = ( 2 * Math.PI * iAng ) / noiseSquareSize;
|
|
|
- const randomVec = new Vector3(
|
|
|
- Math.cos( angle ),
|
|
|
- Math.sin( angle ),
|
|
|
- 0
|
|
|
- ).normalize();
|
|
|
- data[ inx * 4 ] = ( randomVec.x * 0.5 + 0.5 ) * 255;
|
|
|
- data[ inx * 4 + 1 ] = ( randomVec.y * 0.5 + 0.5 ) * 255;
|
|
|
- data[ inx * 4 + 2 ] = 127;
|
|
|
- data[ inx * 4 + 3 ] = 255;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- const noiseTexture = new DataTexture( data, noiseSize, noiseSize );
|
|
|
- noiseTexture.wrapS = RepeatWrapping;
|
|
|
- noiseTexture.wrapT = RepeatWrapping;
|
|
|
- noiseTexture.needsUpdate = true;
|
|
|
-
|
|
|
- return noiseTexture;
|
|
|
-
|
|
|
-}
|
|
|
-
|
|
|
-/**
|
|
|
- * Computes an array of magic square values required to generate the noise texture.
|
|
|
- *
|
|
|
- * @param {number} size - The noise size.
|
|
|
- * @return {Array<number>} The magic square values.
|
|
|
- */
|
|
|
-function generateMagicSquare( size ) {
|
|
|
-
|
|
|
- const noiseSize = Math.floor( size ) % 2 === 0 ? Math.floor( size ) + 1 : Math.floor( size );
|
|
|
- const noiseSquareSize = noiseSize * noiseSize;
|
|
|
- const magicSquare = Array( noiseSquareSize ).fill( 0 );
|
|
|
- let i = Math.floor( noiseSize / 2 );
|
|
|
- let j = noiseSize - 1;
|
|
|
-
|
|
|
- for ( let num = 1; num <= noiseSquareSize; ) {
|
|
|
-
|
|
|
- if ( i === - 1 && j === noiseSize ) {
|
|
|
-
|
|
|
- j = noiseSize - 2;
|
|
|
- i = 0;
|
|
|
-
|
|
|
- } else {
|
|
|
-
|
|
|
- if ( j === noiseSize ) {
|
|
|
-
|
|
|
- j = 0;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- if ( i < 0 ) {
|
|
|
-
|
|
|
- i = noiseSize - 1;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- if ( magicSquare[ i * noiseSize + j ] !== 0 ) {
|
|
|
-
|
|
|
- j -= 2;
|
|
|
- i ++;
|
|
|
- continue;
|
|
|
-
|
|
|
- } else {
|
|
|
-
|
|
|
- magicSquare[ i * noiseSize + j ] = num ++;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- j ++;
|
|
|
- i --;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- return magicSquare;
|
|
|
-
|
|
|
-}
|
|
|
-
|
|
|
/**
|
|
|
* TSL function for creating a Ground Truth Ambient Occlusion (GTAO) effect.
|
|
|
*
|