Skip to content

⬅️ Back to Table of Contents

📄 GTAONode.js

📊 Analysis Summary

Metric Count
🔧 Functions 11
🧱 Classes 1
📦 Imports 48
📊 Variables & Constants 14

📚 Table of Contents

🛠️ File Location:

📂 examples/jsm/tsl/display/GTAONode.js

📦 Imports

Name Source
DataTexture three/webgpu
RenderTarget three/webgpu
RepeatWrapping three/webgpu
Vector2 three/webgpu
Vector3 three/webgpu
TempNode three/webgpu
QuadMesh three/webgpu
NodeMaterial three/webgpu
RendererUtils three/webgpu
reference three/tsl
logarithmicDepthToViewZ three/tsl
viewZToPerspectiveDepth three/tsl
getNormalFromDepth three/tsl
getScreenPosition three/tsl
getViewPosition three/tsl
nodeObject three/tsl
Fn three/tsl
float three/tsl
NodeUpdateType three/tsl
uv three/tsl
uniform three/tsl
Loop three/tsl
vec2 three/tsl
vec3 three/tsl
vec4 three/tsl
int three/tsl
dot three/tsl
max three/tsl
pow three/tsl
abs three/tsl
If three/tsl
textureSize three/tsl
sin three/tsl
cos three/tsl
PI three/tsl
texture three/tsl
passTexture three/tsl
mat3 three/tsl
add three/tsl
normalize three/tsl
mul three/tsl
cross three/tsl
div three/tsl
mix three/tsl
sqrt three/tsl
sub three/tsl
acos three/tsl
clamp three/tsl

Variables & Constants

Name Type Kind Value Exported
_quadMesh any let/var new QuadMesh()
_size any let/var new Vector2()
_rendererState any let/var *not shown*
depth any let/var this.depthNode.sample( uv ).r
radiusToUse UniformNode<float> let/var this.radius
noiseSize number let/var Math.floor( size ) % 2 === 0 ? Math.floor( size ) + 1 : Math.floor( size )
noiseSquareSize number let/var magicSquare.length
data Uint8Array<ArrayBuffer> let/var new Uint8Array( noiseSquareSize * 4 )
iAng number let/var magicSquare[ inx ]
angle number let/var ( 2 * Math.PI * iAng ) / noiseSquareSize
noiseTexture any let/var new DataTexture( data, noiseSize, noiseSize )
noiseSize number let/var Math.floor( size ) % 2 === 0 ? Math.floor( size ) + 1 : Math.floor( size )
noiseSquareSize number let/var noiseSize * noiseSize
j number let/var noiseSize - 1

Functions

GTAONode.getTextureNode(): PassTextureNode

JSDoc:

/**
     * Returns the result of the effect as a texture node.
     *
     * @return {PassTextureNode} A texture node that represents the result of the effect.
     */

Returns: PassTextureNode

Code
getTextureNode() {

        return this._textureNode;

    }

GTAONode.setSize(width: number, height: number): void

JSDoc:

/**
     * Sets the size of the effect.
     *
     * @param {number} width - The width of the effect.
     * @param {number} height - The height of the effect.
     */

Parameters:

  • width number
  • height number

Returns: void

Calls:

  • Math.round
  • this.resolution.value.set
  • this._aoRenderTarget.setSize
Code
setSize( width, height ) {

        width = Math.round( this.resolutionScale * width );
        height = Math.round( this.resolutionScale * height );

        this.resolution.value.set( width, height );
        this._aoRenderTarget.setSize( width, height );

    }

GTAONode.updateBefore(frame: NodeFrame): void

JSDoc:

/**
     * This method is used to render the effect once per frame.
     *
     * @param {NodeFrame} frame - The current node frame.
     */

Parameters:

  • frame NodeFrame

Returns: void

Calls:

  • RendererUtils.resetRendererState
  • renderer.getDrawingBufferSize
  • this.setSize
  • renderer.setClearColor
  • renderer.setRenderTarget
  • _quadMesh.render
  • RendererUtils.restoreRendererState

Internal Comments:

// (x2)
// clear (x4)
// ao (x4)
// restore (x4)

Code
updateBefore( frame ) {

        const { renderer } = frame;

        _rendererState = RendererUtils.resetRendererState( renderer, _rendererState );

        //

        const size = renderer.getDrawingBufferSize( _size );
        this.setSize( size.width, size.height );

        _quadMesh.material = this._material;

        // clear

        renderer.setClearColor( 0xffffff, 1 );

        // ao

        renderer.setRenderTarget( this._aoRenderTarget );
        _quadMesh.render( renderer );

        // restore

        RendererUtils.restoreRendererState( renderer, _rendererState );

    }

GTAONode.setup(builder: NodeBuilder): PassTextureNode

JSDoc:

/**
     * This method is used to setup the effect's TSL code.
     *
     * @param {NodeBuilder} builder - The current node builder.
     * @return {PassTextureNode}
     */

Parameters:

  • builder NodeBuilder

Returns: PassTextureNode

Calls:

  • uv (from three/tsl)
  • this.depthNode.sample
  • logarithmicDepthToViewZ (from three/tsl)
  • viewZToPerspectiveDepth (from three/tsl)
  • this._noiseNode.sample
  • this.normalNode.sample( uv ).rgb.normalize
  • getNormalFromDepth (from three/tsl)
  • Fn (from three/tsl)
  • sampleDepth( uvNode ).toVar
  • depth.greaterThanEqual( 1.0 ).discard
  • getViewPosition( uvNode, depth, this._cameraProjectionMatrixInverse ).toVar
  • sampleNormal( uvNode ).toVar
  • textureSize (from three/tsl)
  • vec2 (from three/tsl)
  • uvNode.y.oneMinus
  • noiseUv.mul
  • this.resolution.div
  • sampleNoise
  • noiseTexel.xyz.mul( 2.0 ).sub
  • vec3( randomVec.xy, 0.0 ).normalize
  • vec3 (from three/tsl)
  • tangent.y.mul
  • mat3 (from three/tsl)
  • this.samples.lessThan( 30 ).select( 3, 5 ).toVar
  • add( this.samples, DIRECTIONS.sub( 1 ) ).div( DIRECTIONS ).toVar
  • float( 0 ).toVar
  • Loop (from three/tsl)
  • int (from three/tsl)
  • float( i ).div( float( DIRECTIONS ) ).mul( PI ).toVar
  • vec4 (from three/tsl)
  • cos (from three/tsl)
  • sin (from three/tsl)
  • add (from three/tsl)
  • mul (from three/tsl)
  • normalize (from three/tsl)
  • kernelMatrix.mul
  • normalize( viewPosition.xyz.negate() ).toVar
  • normalize( cross( sampleDir.xyz, viewDir ) ).toVar
  • cross (from three/tsl)
  • viewNormal.sub
  • sliceBitangent.mul
  • dot (from three/tsl)
  • cross( normalInSlice, sliceBitangent ).toVar
  • vec2( dot( viewDir, tangentToNormalInSlice ), dot( viewDir, tangentToNormalInSlice.negate() ) ).toVar
  • sampleDir.xyz.mul( radiusToUse ).mul( sampleDir.w ).mul
  • pow (from three/tsl)
  • div (from three/tsl)
  • float( j ).add
  • float (from three/tsl)
  • getScreenPosition( viewPosition.add( sampleViewOffset ), this._cameraProjectionMatrix ).toVar
  • sampleDepth( sampleScreenPositionX ).toVar
  • getViewPosition( sampleScreenPositionX, sampleDepthX, this._cameraProjectionMatrixInverse ).toVar
  • sampleSceneViewPositionX.sub( viewPosition ).toVar
  • If (from three/tsl)
  • abs( viewDeltaX.z ).lessThan
  • cosHorizons.x.addAssign
  • max (from three/tsl)
  • sampleCosHorizon.sub
  • mix (from three/tsl)
  • float( 2.0 ).div
  • getScreenPosition( viewPosition.sub( sampleViewOffset ), this._cameraProjectionMatrix ).toVar
  • sampleDepth( sampleScreenPositionY ).toVar
  • getViewPosition( sampleScreenPositionY, sampleDepthY, this._cameraProjectionMatrixInverse ).toVar
  • sampleSceneViewPositionY.sub( viewPosition ).toVar
  • abs( viewDeltaY.z ).lessThan
  • cosHorizons.y.addAssign
  • sqrt( sub( 1.0, cosHorizons.mul( cosHorizons ) ) ).toVar
  • acos( cosHorizons.y ).sub( acos( cosHorizons.x ) ).add
  • sinHorizons.x.mul( cosHorizons.x ).sub
  • sinHorizons.y.mul
  • sub( 2.0, cosHorizons.x.mul( cosHorizons.x ) ).sub
  • cosHorizons.y.mul
  • nx.mul( nxb ).add
  • ny.mul
  • ao.addAssign
  • ao.assign
  • clamp (from three/tsl)
  • ao.div
  • ao().context
  • builder.getSharedContext

Internal Comments:

// x (x2)
// y (x2)
//

Code
setup( builder ) {

        const uvNode = uv();

        const sampleDepth = ( uv ) => {

            const depth = this.depthNode.sample( uv ).r;

            if ( builder.renderer.logarithmicDepthBuffer === true ) {

                const viewZ = logarithmicDepthToViewZ( depth, this._cameraNear, this._cameraFar );

                return viewZToPerspectiveDepth( viewZ, this._cameraNear, this._cameraFar );

            }

            return depth;

        };

        const sampleNoise = ( uv ) => this._noiseNode.sample( uv );
        const sampleNormal = ( uv ) => ( this.normalNode !== null ) ? this.normalNode.sample( uv ).rgb.normalize() : getNormalFromDepth( uv, this.depthNode.value, this._cameraProjectionMatrixInverse );

        const ao = Fn( () => {

            const depth = sampleDepth( uvNode ).toVar();

            depth.greaterThanEqual( 1.0 ).discard();

            const viewPosition = getViewPosition( uvNode, depth, this._cameraProjectionMatrixInverse ).toVar();
            const viewNormal = sampleNormal( uvNode ).toVar();

            const radiusToUse = this.radius;

            const noiseResolution = textureSize( this._noiseNode, 0 );
            let noiseUv = vec2( uvNode.x, uvNode.y.oneMinus() );
            noiseUv = noiseUv.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();
            const bitangent = vec3( tangent.y.mul( - 1.0 ), tangent.x, 0.0 );
            const kernelMatrix = mat3( tangent, bitangent, vec3( 0.0, 0.0, 1.0 ) );

            const DIRECTIONS = this.samples.lessThan( 30 ).select( 3, 5 ).toVar();
            const STEPS = add( this.samples, DIRECTIONS.sub( 1 ) ).div( DIRECTIONS ).toVar();

            const ao = float( 0 ).toVar();

            Loop( { start: int( 0 ), end: DIRECTIONS, type: 'int', condition: '<' }, ( { i } ) => {

                const angle = float( i ).div( float( DIRECTIONS ) ).mul( PI ).toVar();
                const sampleDir = vec4( cos( angle ), sin( angle ), 0., add( 0.5, mul( 0.5, noiseTexel.w ) ) );
                sampleDir.xyz = normalize( kernelMatrix.mul( sampleDir.xyz ) );

                const viewDir = normalize( viewPosition.xyz.negate() ).toVar();
                const sliceBitangent = normalize( cross( sampleDir.xyz, viewDir ) ).toVar();
                const sliceTangent = cross( sliceBitangent, viewDir );
                const normalInSlice = normalize( viewNormal.sub( sliceBitangent.mul( dot( viewNormal, sliceBitangent ) ) ) );

                const tangentToNormalInSlice = cross( normalInSlice, sliceBitangent ).toVar();
                const cosHorizons = vec2( dot( viewDir, tangentToNormalInSlice ), dot( viewDir, tangentToNormalInSlice.negate() ) ).toVar();

                Loop( { end: STEPS, type: 'int', name: 'j', condition: '<' }, ( { j } ) => {

                    const sampleViewOffset = sampleDir.xyz.mul( radiusToUse ).mul( sampleDir.w ).mul( pow( div( float( j ).add( 1.0 ), float( STEPS ) ), this.distanceExponent ) );

                    // x

                    const sampleScreenPositionX = getScreenPosition( viewPosition.add( sampleViewOffset ), this._cameraProjectionMatrix ).toVar();
                    const sampleDepthX = sampleDepth( sampleScreenPositionX ).toVar();
                    const sampleSceneViewPositionX = getViewPosition( sampleScreenPositionX, sampleDepthX, this._cameraProjectionMatrixInverse ).toVar();
                    const viewDeltaX = sampleSceneViewPositionX.sub( viewPosition ).toVar();

                    If( abs( viewDeltaX.z ).lessThan( this.thickness ), () => {

                        const sampleCosHorizon = dot( viewDir, normalize( viewDeltaX ) );
                        cosHorizons.x.addAssign( max( 0, mul( sampleCosHorizon.sub( cosHorizons.x ), mix( 1.0, float( 2.0 ).div( float( j ).add( 2 ) ), this.distanceFallOff ) ) ) );

                    } );

                    // y

                    const sampleScreenPositionY = getScreenPosition( viewPosition.sub( sampleViewOffset ), this._cameraProjectionMatrix ).toVar();
                    const sampleDepthY = sampleDepth( sampleScreenPositionY ).toVar();
                    const sampleSceneViewPositionY = getViewPosition( sampleScreenPositionY, sampleDepthY, this._cameraProjectionMatrixInverse ).toVar();
                    const viewDeltaY = sampleSceneViewPositionY.sub( viewPosition ).toVar();

                    If( abs( viewDeltaY.z ).lessThan( this.thickness ), () => {

                        const sampleCosHorizon = dot( viewDir, normalize( viewDeltaY ) );
                        cosHorizons.y.addAssign( max( 0, mul( sampleCosHorizon.sub( cosHorizons.y ), mix( 1.0, float( 2.0 ).div( float( j ).add( 2 ) ), this.distanceFallOff ) ) ) );

                    } );

                } );

                const sinHorizons = sqrt( sub( 1.0, cosHorizons.mul( cosHorizons ) ) ).toVar();
                const nx = dot( normalInSlice, sliceTangent );
                const ny = dot( normalInSlice, viewDir );
                const nxb = mul( 0.5, acos( cosHorizons.y ).sub( acos( cosHorizons.x ) ).add( sinHorizons.x.mul( cosHorizons.x ).sub( sinHorizons.y.mul( cosHorizons.y ) ) ) );
                const nyb = mul( 0.5, sub( 2.0, cosHorizons.x.mul( cosHorizons.x ) ).sub( cosHorizons.y.mul( cosHorizons.y ) ) );
                const occlusion = nx.mul( nxb ).add( ny.mul( nyb ) );
                ao.addAssign( occlusion );

            } );

            ao.assign( clamp( ao.div( DIRECTIONS ), 0, 1 ) );
            ao.assign( pow( ao, this.scale ) );

            return vec4( vec3( ao ), 1.0 );

        } );

        this._material.fragmentNode = ao().context( builder.getSharedContext() );
        this._material.needsUpdate = true;

        //

        return this._textureNode;

    }

GTAONode.dispose(): void

JSDoc:

/**
     * Frees internal resources. This method should be called
     * when the effect is no longer required.
     */

Returns: void

Calls:

  • this._aoRenderTarget.dispose
  • this._material.dispose
Code
dispose() {

        this._aoRenderTarget.dispose();

        this._material.dispose();

    }

sampleDepth(uv: any): any

Parameters:

  • uv any

Returns: any

Calls:

  • this.depthNode.sample
  • logarithmicDepthToViewZ (from three/tsl)
  • viewZToPerspectiveDepth (from three/tsl)
Code
( uv ) => {

            const depth = this.depthNode.sample( uv ).r;

            if ( builder.renderer.logarithmicDepthBuffer === true ) {

                const viewZ = logarithmicDepthToViewZ( depth, this._cameraNear, this._cameraFar );

                return viewZToPerspectiveDepth( viewZ, this._cameraNear, this._cameraFar );

            }

            return depth;

        }

sampleNoise(uv: any): any

Parameters:

  • uv any

Returns: any

Calls:

  • this._noiseNode.sample
Code
( uv ) => this._noiseNode.sample( uv )

sampleNormal(uv: any): any

Parameters:

  • uv any

Returns: any

Code
( uv ) => ( this.normalNode !== null ) ? this.normalNode.sample( uv ).rgb.normalize() : getNormalFromDepth( uv, this.depthNode.value, this._cameraProjectionMatrixInverse )

generateMagicSquareNoise(size: number): DataTexture

JSDoc:

/**
 * Generates the AO's noise texture for the given size.
 *
 * @param {number} [size=5] - The noise size.
 * @return {DataTexture} The generated noise texture.
 */

Parameters:

  • size number

Returns: DataTexture

Calls:

  • Math.floor
  • generateMagicSquare
  • new Vector3( Math.cos( angle ), Math.sin( angle ), 0 ).normalize
  • Math.cos
  • Math.sin
Code
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;

}

generateMagicSquare(size: number): number[]

JSDoc:

/**
 * 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.
 */

Parameters:

  • size number

Returns: number[]

Calls:

  • Math.floor
  • Array( noiseSquareSize ).fill
Code
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;

}

ao(depthNode: any, normalNode: any, camera: Camera): GTAONode

Parameters:

  • depthNode any
  • normalNode any
  • camera Camera

Returns: GTAONode

Calls:

  • nodeObject (from three/tsl)
Code
( depthNode, normalNode, camera ) => nodeObject( new GTAONode( nodeObject( depthNode ), nodeObject( normalNode ), camera ) )

Classes

GTAONode

Class Code
class GTAONode extends TempNode {

    static get type() {

        return 'GTAONode';

    }

    /**
     * Constructs a new GTAO node.
     *
     * @param {Node<float>} depthNode - A node that represents the scene's depth.
     * @param {?Node<vec3>} normalNode - A node that represents the scene's normals.
     * @param {Camera} camera - The camera the scene is rendered with.
     */
    constructor( depthNode, normalNode, camera ) {

        super( 'vec4' );

        /**
         * A node that represents the scene's depth.
         *
         * @type {Node<float>}
         */
        this.depthNode = depthNode;

        /**
         * A node that represents the scene's normals. If no normals are passed to the
         * constructor (because MRT is not available), normals can be automatically
         * reconstructed from depth values in the shader.
         *
         * @type {?Node<vec3>}
         */
        this.normalNode = normalNode;

        /**
         * The resolution scale. By default the effect is rendered in full resolution
         * for best quality but a value of `0.5` should be sufficient for most scenes.
         *
         * @type {number}
         * @default 1
         */
        this.resolutionScale = 1;

        /**
         * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node renders
         * its effect once per frame in `updateBefore()`.
         *
         * @type {string}
         * @default 'frame'
         */
        this.updateBeforeType = NodeUpdateType.FRAME;

        /**
         * The render target the ambient occlusion is rendered into.
         *
         * @private
         * @type {RenderTarget}
         */
        this._aoRenderTarget = new RenderTarget( 1, 1, { depthBuffer: false } );
        this._aoRenderTarget.texture.name = 'GTAONode.AO';

        // uniforms

        /**
         * The radius of the ambient occlusion.
         *
         * @type {UniformNode<float>}
         */
        this.radius = uniform( 0.25 );

        /**
         * The resolution of the effect. Can be scaled via
         * `resolutionScale`.
         *
         * @type {UniformNode<vec2>}
         */
        this.resolution = uniform( new Vector2() );

        /**
         * The thickness of the ambient occlusion.
         *
         * @type {UniformNode<float>}
         */
        this.thickness = uniform( 1 );

        /**
         * Another option to tweak the occlusion. The recommended range is
         * `[1,2]` for attenuating the AO.
         *
         * @type {UniformNode<float>}
         */
        this.distanceExponent = uniform( 1 );

        /**
         * The distance fall off value of the ambient occlusion.
         * A lower value leads to a larger AO effect. The value
         * should lie in the range `[0,1]`.
         *
         * @type {UniformNode<float>}
         */
        this.distanceFallOff = uniform( 1 );

        /**
         * The scale of the ambient occlusion.
         *
         * @type {UniformNode<float>}
         */
        this.scale = uniform( 1 );

        /**
         * How many samples are used to compute the AO.
         * A higher value results in better quality but also
         * in a more expensive runtime behavior.
         *
         * @type {UniformNode<float>}
         */
        this.samples = uniform( 16 );

        /**
         * The node represents the internal noise texture used by the AO.
         *
         * @private
         * @type {TextureNode}
         */
        this._noiseNode = texture( generateMagicSquareNoise() );

        /**
         * Represents the projection matrix of the scene's camera.
         *
         * @private
         * @type {UniformNode<mat4>}
         */
        this._cameraProjectionMatrix = uniform( camera.projectionMatrix );

        /**
         * Represents the inverse projection matrix of the scene's camera.
         *
         * @private
         * @type {UniformNode<mat4>}
         */
        this._cameraProjectionMatrixInverse = uniform( camera.projectionMatrixInverse );

        /**
         * Represents the near value of the scene's camera.
         *
         * @private
         * @type {ReferenceNode<float>}
         */
        this._cameraNear = reference( 'near', 'float', camera );

        /**
         * Represents the far value of the scene's camera.
         *
         * @private
         * @type {ReferenceNode<float>}
         */
        this._cameraFar = reference( 'far', 'float', camera );

        /**
         * The material that is used to render the effect.
         *
         * @private
         * @type {NodeMaterial}
         */
        this._material = new NodeMaterial();
        this._material.name = 'GTAO';

        /**
         * The result of the effect is represented as a separate texture node.
         *
         * @private
         * @type {PassTextureNode}
         */
        this._textureNode = passTexture( this, this._aoRenderTarget.texture );

    }

    /**
     * Returns the result of the effect as a texture node.
     *
     * @return {PassTextureNode} A texture node that represents the result of the effect.
     */
    getTextureNode() {

        return this._textureNode;

    }

    /**
     * Sets the size of the effect.
     *
     * @param {number} width - The width of the effect.
     * @param {number} height - The height of the effect.
     */
    setSize( width, height ) {

        width = Math.round( this.resolutionScale * width );
        height = Math.round( this.resolutionScale * height );

        this.resolution.value.set( width, height );
        this._aoRenderTarget.setSize( width, height );

    }

    /**
     * This method is used to render the effect once per frame.
     *
     * @param {NodeFrame} frame - The current node frame.
     */
    updateBefore( frame ) {

        const { renderer } = frame;

        _rendererState = RendererUtils.resetRendererState( renderer, _rendererState );

        //

        const size = renderer.getDrawingBufferSize( _size );
        this.setSize( size.width, size.height );

        _quadMesh.material = this._material;

        // clear

        renderer.setClearColor( 0xffffff, 1 );

        // ao

        renderer.setRenderTarget( this._aoRenderTarget );
        _quadMesh.render( renderer );

        // restore

        RendererUtils.restoreRendererState( renderer, _rendererState );

    }

    /**
     * This method is used to setup the effect's TSL code.
     *
     * @param {NodeBuilder} builder - The current node builder.
     * @return {PassTextureNode}
     */
    setup( builder ) {

        const uvNode = uv();

        const sampleDepth = ( uv ) => {

            const depth = this.depthNode.sample( uv ).r;

            if ( builder.renderer.logarithmicDepthBuffer === true ) {

                const viewZ = logarithmicDepthToViewZ( depth, this._cameraNear, this._cameraFar );

                return viewZToPerspectiveDepth( viewZ, this._cameraNear, this._cameraFar );

            }

            return depth;

        };

        const sampleNoise = ( uv ) => this._noiseNode.sample( uv );
        const sampleNormal = ( uv ) => ( this.normalNode !== null ) ? this.normalNode.sample( uv ).rgb.normalize() : getNormalFromDepth( uv, this.depthNode.value, this._cameraProjectionMatrixInverse );

        const ao = Fn( () => {

            const depth = sampleDepth( uvNode ).toVar();

            depth.greaterThanEqual( 1.0 ).discard();

            const viewPosition = getViewPosition( uvNode, depth, this._cameraProjectionMatrixInverse ).toVar();
            const viewNormal = sampleNormal( uvNode ).toVar();

            const radiusToUse = this.radius;

            const noiseResolution = textureSize( this._noiseNode, 0 );
            let noiseUv = vec2( uvNode.x, uvNode.y.oneMinus() );
            noiseUv = noiseUv.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();
            const bitangent = vec3( tangent.y.mul( - 1.0 ), tangent.x, 0.0 );
            const kernelMatrix = mat3( tangent, bitangent, vec3( 0.0, 0.0, 1.0 ) );

            const DIRECTIONS = this.samples.lessThan( 30 ).select( 3, 5 ).toVar();
            const STEPS = add( this.samples, DIRECTIONS.sub( 1 ) ).div( DIRECTIONS ).toVar();

            const ao = float( 0 ).toVar();

            Loop( { start: int( 0 ), end: DIRECTIONS, type: 'int', condition: '<' }, ( { i } ) => {

                const angle = float( i ).div( float( DIRECTIONS ) ).mul( PI ).toVar();
                const sampleDir = vec4( cos( angle ), sin( angle ), 0., add( 0.5, mul( 0.5, noiseTexel.w ) ) );
                sampleDir.xyz = normalize( kernelMatrix.mul( sampleDir.xyz ) );

                const viewDir = normalize( viewPosition.xyz.negate() ).toVar();
                const sliceBitangent = normalize( cross( sampleDir.xyz, viewDir ) ).toVar();
                const sliceTangent = cross( sliceBitangent, viewDir );
                const normalInSlice = normalize( viewNormal.sub( sliceBitangent.mul( dot( viewNormal, sliceBitangent ) ) ) );

                const tangentToNormalInSlice = cross( normalInSlice, sliceBitangent ).toVar();
                const cosHorizons = vec2( dot( viewDir, tangentToNormalInSlice ), dot( viewDir, tangentToNormalInSlice.negate() ) ).toVar();

                Loop( { end: STEPS, type: 'int', name: 'j', condition: '<' }, ( { j } ) => {

                    const sampleViewOffset = sampleDir.xyz.mul( radiusToUse ).mul( sampleDir.w ).mul( pow( div( float( j ).add( 1.0 ), float( STEPS ) ), this.distanceExponent ) );

                    // x

                    const sampleScreenPositionX = getScreenPosition( viewPosition.add( sampleViewOffset ), this._cameraProjectionMatrix ).toVar();
                    const sampleDepthX = sampleDepth( sampleScreenPositionX ).toVar();
                    const sampleSceneViewPositionX = getViewPosition( sampleScreenPositionX, sampleDepthX, this._cameraProjectionMatrixInverse ).toVar();
                    const viewDeltaX = sampleSceneViewPositionX.sub( viewPosition ).toVar();

                    If( abs( viewDeltaX.z ).lessThan( this.thickness ), () => {

                        const sampleCosHorizon = dot( viewDir, normalize( viewDeltaX ) );
                        cosHorizons.x.addAssign( max( 0, mul( sampleCosHorizon.sub( cosHorizons.x ), mix( 1.0, float( 2.0 ).div( float( j ).add( 2 ) ), this.distanceFallOff ) ) ) );

                    } );

                    // y

                    const sampleScreenPositionY = getScreenPosition( viewPosition.sub( sampleViewOffset ), this._cameraProjectionMatrix ).toVar();
                    const sampleDepthY = sampleDepth( sampleScreenPositionY ).toVar();
                    const sampleSceneViewPositionY = getViewPosition( sampleScreenPositionY, sampleDepthY, this._cameraProjectionMatrixInverse ).toVar();
                    const viewDeltaY = sampleSceneViewPositionY.sub( viewPosition ).toVar();

                    If( abs( viewDeltaY.z ).lessThan( this.thickness ), () => {

                        const sampleCosHorizon = dot( viewDir, normalize( viewDeltaY ) );
                        cosHorizons.y.addAssign( max( 0, mul( sampleCosHorizon.sub( cosHorizons.y ), mix( 1.0, float( 2.0 ).div( float( j ).add( 2 ) ), this.distanceFallOff ) ) ) );

                    } );

                } );

                const sinHorizons = sqrt( sub( 1.0, cosHorizons.mul( cosHorizons ) ) ).toVar();
                const nx = dot( normalInSlice, sliceTangent );
                const ny = dot( normalInSlice, viewDir );
                const nxb = mul( 0.5, acos( cosHorizons.y ).sub( acos( cosHorizons.x ) ).add( sinHorizons.x.mul( cosHorizons.x ).sub( sinHorizons.y.mul( cosHorizons.y ) ) ) );
                const nyb = mul( 0.5, sub( 2.0, cosHorizons.x.mul( cosHorizons.x ) ).sub( cosHorizons.y.mul( cosHorizons.y ) ) );
                const occlusion = nx.mul( nxb ).add( ny.mul( nyb ) );
                ao.addAssign( occlusion );

            } );

            ao.assign( clamp( ao.div( DIRECTIONS ), 0, 1 ) );
            ao.assign( pow( ao, this.scale ) );

            return vec4( vec3( ao ), 1.0 );

        } );

        this._material.fragmentNode = ao().context( builder.getSharedContext() );
        this._material.needsUpdate = true;

        //

        return this._textureNode;

    }

    /**
     * Frees internal resources. This method should be called
     * when the effect is no longer required.
     */
    dispose() {

        this._aoRenderTarget.dispose();

        this._material.dispose();

    }

}

Methods

getTextureNode(): PassTextureNode
Code
getTextureNode() {

        return this._textureNode;

    }
setSize(width: number, height: number): void
Code
setSize( width, height ) {

        width = Math.round( this.resolutionScale * width );
        height = Math.round( this.resolutionScale * height );

        this.resolution.value.set( width, height );
        this._aoRenderTarget.setSize( width, height );

    }
updateBefore(frame: NodeFrame): void
Code
updateBefore( frame ) {

        const { renderer } = frame;

        _rendererState = RendererUtils.resetRendererState( renderer, _rendererState );

        //

        const size = renderer.getDrawingBufferSize( _size );
        this.setSize( size.width, size.height );

        _quadMesh.material = this._material;

        // clear

        renderer.setClearColor( 0xffffff, 1 );

        // ao

        renderer.setRenderTarget( this._aoRenderTarget );
        _quadMesh.render( renderer );

        // restore

        RendererUtils.restoreRendererState( renderer, _rendererState );

    }
setup(builder: NodeBuilder): PassTextureNode
Code
setup( builder ) {

        const uvNode = uv();

        const sampleDepth = ( uv ) => {

            const depth = this.depthNode.sample( uv ).r;

            if ( builder.renderer.logarithmicDepthBuffer === true ) {

                const viewZ = logarithmicDepthToViewZ( depth, this._cameraNear, this._cameraFar );

                return viewZToPerspectiveDepth( viewZ, this._cameraNear, this._cameraFar );

            }

            return depth;

        };

        const sampleNoise = ( uv ) => this._noiseNode.sample( uv );
        const sampleNormal = ( uv ) => ( this.normalNode !== null ) ? this.normalNode.sample( uv ).rgb.normalize() : getNormalFromDepth( uv, this.depthNode.value, this._cameraProjectionMatrixInverse );

        const ao = Fn( () => {

            const depth = sampleDepth( uvNode ).toVar();

            depth.greaterThanEqual( 1.0 ).discard();

            const viewPosition = getViewPosition( uvNode, depth, this._cameraProjectionMatrixInverse ).toVar();
            const viewNormal = sampleNormal( uvNode ).toVar();

            const radiusToUse = this.radius;

            const noiseResolution = textureSize( this._noiseNode, 0 );
            let noiseUv = vec2( uvNode.x, uvNode.y.oneMinus() );
            noiseUv = noiseUv.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();
            const bitangent = vec3( tangent.y.mul( - 1.0 ), tangent.x, 0.0 );
            const kernelMatrix = mat3( tangent, bitangent, vec3( 0.0, 0.0, 1.0 ) );

            const DIRECTIONS = this.samples.lessThan( 30 ).select( 3, 5 ).toVar();
            const STEPS = add( this.samples, DIRECTIONS.sub( 1 ) ).div( DIRECTIONS ).toVar();

            const ao = float( 0 ).toVar();

            Loop( { start: int( 0 ), end: DIRECTIONS, type: 'int', condition: '<' }, ( { i } ) => {

                const angle = float( i ).div( float( DIRECTIONS ) ).mul( PI ).toVar();
                const sampleDir = vec4( cos( angle ), sin( angle ), 0., add( 0.5, mul( 0.5, noiseTexel.w ) ) );
                sampleDir.xyz = normalize( kernelMatrix.mul( sampleDir.xyz ) );

                const viewDir = normalize( viewPosition.xyz.negate() ).toVar();
                const sliceBitangent = normalize( cross( sampleDir.xyz, viewDir ) ).toVar();
                const sliceTangent = cross( sliceBitangent, viewDir );
                const normalInSlice = normalize( viewNormal.sub( sliceBitangent.mul( dot( viewNormal, sliceBitangent ) ) ) );

                const tangentToNormalInSlice = cross( normalInSlice, sliceBitangent ).toVar();
                const cosHorizons = vec2( dot( viewDir, tangentToNormalInSlice ), dot( viewDir, tangentToNormalInSlice.negate() ) ).toVar();

                Loop( { end: STEPS, type: 'int', name: 'j', condition: '<' }, ( { j } ) => {

                    const sampleViewOffset = sampleDir.xyz.mul( radiusToUse ).mul( sampleDir.w ).mul( pow( div( float( j ).add( 1.0 ), float( STEPS ) ), this.distanceExponent ) );

                    // x

                    const sampleScreenPositionX = getScreenPosition( viewPosition.add( sampleViewOffset ), this._cameraProjectionMatrix ).toVar();
                    const sampleDepthX = sampleDepth( sampleScreenPositionX ).toVar();
                    const sampleSceneViewPositionX = getViewPosition( sampleScreenPositionX, sampleDepthX, this._cameraProjectionMatrixInverse ).toVar();
                    const viewDeltaX = sampleSceneViewPositionX.sub( viewPosition ).toVar();

                    If( abs( viewDeltaX.z ).lessThan( this.thickness ), () => {

                        const sampleCosHorizon = dot( viewDir, normalize( viewDeltaX ) );
                        cosHorizons.x.addAssign( max( 0, mul( sampleCosHorizon.sub( cosHorizons.x ), mix( 1.0, float( 2.0 ).div( float( j ).add( 2 ) ), this.distanceFallOff ) ) ) );

                    } );

                    // y

                    const sampleScreenPositionY = getScreenPosition( viewPosition.sub( sampleViewOffset ), this._cameraProjectionMatrix ).toVar();
                    const sampleDepthY = sampleDepth( sampleScreenPositionY ).toVar();
                    const sampleSceneViewPositionY = getViewPosition( sampleScreenPositionY, sampleDepthY, this._cameraProjectionMatrixInverse ).toVar();
                    const viewDeltaY = sampleSceneViewPositionY.sub( viewPosition ).toVar();

                    If( abs( viewDeltaY.z ).lessThan( this.thickness ), () => {

                        const sampleCosHorizon = dot( viewDir, normalize( viewDeltaY ) );
                        cosHorizons.y.addAssign( max( 0, mul( sampleCosHorizon.sub( cosHorizons.y ), mix( 1.0, float( 2.0 ).div( float( j ).add( 2 ) ), this.distanceFallOff ) ) ) );

                    } );

                } );

                const sinHorizons = sqrt( sub( 1.0, cosHorizons.mul( cosHorizons ) ) ).toVar();
                const nx = dot( normalInSlice, sliceTangent );
                const ny = dot( normalInSlice, viewDir );
                const nxb = mul( 0.5, acos( cosHorizons.y ).sub( acos( cosHorizons.x ) ).add( sinHorizons.x.mul( cosHorizons.x ).sub( sinHorizons.y.mul( cosHorizons.y ) ) ) );
                const nyb = mul( 0.5, sub( 2.0, cosHorizons.x.mul( cosHorizons.x ) ).sub( cosHorizons.y.mul( cosHorizons.y ) ) );
                const occlusion = nx.mul( nxb ).add( ny.mul( nyb ) );
                ao.addAssign( occlusion );

            } );

            ao.assign( clamp( ao.div( DIRECTIONS ), 0, 1 ) );
            ao.assign( pow( ao, this.scale ) );

            return vec4( vec3( ao ), 1.0 );

        } );

        this._material.fragmentNode = ao().context( builder.getSharedContext() );
        this._material.needsUpdate = true;

        //

        return this._textureNode;

    }
dispose(): void
Code
dispose() {

        this._aoRenderTarget.dispose();

        this._material.dispose();

    }