Browse Source

Examples: GTAOPass - Ground Truth Ambient Occlusion (#27317)

Gernot Steinegger 1 year ago
parent
commit
5538dd1144

+ 1 - 1
docs/examples/en/postprocessing/EffectComposer.html

@@ -39,7 +39,7 @@
 			[example:webgl_postprocessing_fxaa postprocessing fxaa]<br />
 			[example:webgl_postprocessing_fxaa postprocessing fxaa]<br />
 			[example:webgl_postprocessing_glitch postprocessing glitch]<br />
 			[example:webgl_postprocessing_glitch postprocessing glitch]<br />
 			[example:webgl_postprocessing_godrays postprocessing godrays]<br />
 			[example:webgl_postprocessing_godrays postprocessing godrays]<br />
-			[example:webgl_postprocessing_hbao postprocessing hbao]<br />
+			[example:webgl_postprocessing_gtao postprocessing gtao]<br />
 			[example:webgl_postprocessing_masking postprocessing masking]<br />
 			[example:webgl_postprocessing_masking postprocessing masking]<br />
 			[example:webgl_postprocessing_outline postprocessing outline]<br />
 			[example:webgl_postprocessing_outline postprocessing outline]<br />
 			[example:webgl_postprocessing_pixel postprocessing pixelate]<br />
 			[example:webgl_postprocessing_pixel postprocessing pixelate]<br />

+ 1 - 1
docs/examples/zh/postprocessing/EffectComposer.html

@@ -38,7 +38,7 @@
 			[example:webgl_postprocessing_fxaa postprocessing fxaa]<br />
 			[example:webgl_postprocessing_fxaa postprocessing fxaa]<br />
 			[example:webgl_postprocessing_glitch postprocessing glitch]<br />
 			[example:webgl_postprocessing_glitch postprocessing glitch]<br />
 			[example:webgl_postprocessing_godrays postprocessing godrays]<br />
 			[example:webgl_postprocessing_godrays postprocessing godrays]<br />
-			[example:webgl_postprocessing_hbao postprocessing hbao]<br />
+			[example:webgl_postprocessing_gtao postprocessing gtao]<br />
 			[example:webgl_postprocessing_masking postprocessing masking]<br />
 			[example:webgl_postprocessing_masking postprocessing masking]<br />
 			[example:webgl_postprocessing_outline postprocessing outline]<br />
 			[example:webgl_postprocessing_outline postprocessing outline]<br />
 			[example:webgl_postprocessing_pixel postprocessing pixelate]<br />
 			[example:webgl_postprocessing_pixel postprocessing pixelate]<br />

+ 1 - 1
examples/files.json

@@ -246,7 +246,7 @@
 		"webgl_postprocessing_fxaa",
 		"webgl_postprocessing_fxaa",
 		"webgl_postprocessing_glitch",
 		"webgl_postprocessing_glitch",
 		"webgl_postprocessing_godrays",
 		"webgl_postprocessing_godrays",
-		"webgl_postprocessing_hbao",
+		"webgl_postprocessing_gtao",
 		"webgl_postprocessing_rgb_halftone",
 		"webgl_postprocessing_rgb_halftone",
 		"webgl_postprocessing_masking",
 		"webgl_postprocessing_masking",
 		"webgl_postprocessing_ssaa",
 		"webgl_postprocessing_ssaa",

+ 177 - 111
examples/jsm/postprocessing/HBAOPass.js → examples/jsm/postprocessing/GTAOPass.js

@@ -3,7 +3,6 @@ import {
 	Color,
 	Color,
 	CustomBlending,
 	CustomBlending,
 	DataTexture,
 	DataTexture,
-	DepthStencilFormat,
 	DepthTexture,
 	DepthTexture,
 	DstAlphaFactor,
 	DstAlphaFactor,
 	DstColorFactor,
 	DstColorFactor,
@@ -16,19 +15,18 @@ import {
 	ShaderMaterial,
 	ShaderMaterial,
 	UniformsUtils,
 	UniformsUtils,
 	UnsignedByteType,
 	UnsignedByteType,
-	UnsignedInt248Type,
 	WebGLRenderTarget,
 	WebGLRenderTarget,
 	ZeroFactor
 	ZeroFactor
 } from 'three';
 } from 'three';
 import { Pass, FullScreenQuad } from './Pass.js';
 import { Pass, FullScreenQuad } from './Pass.js';
-import { generateHaboSampleKernelInitializer, HBAOShader, HBAODepthShader } from '../shaders/HBAOShader.js';
+import { generateMagicSquareNoise, GTAOShader, GTAODepthShader, GTAOBlendShader } from '../shaders/GTAOShader.js';
 import { generatePdSamplePointInitializer, PoissonDenoiseShader } from '../shaders/PoissonDenoiseShader.js';
 import { generatePdSamplePointInitializer, PoissonDenoiseShader } from '../shaders/PoissonDenoiseShader.js';
 import { CopyShader } from '../shaders/CopyShader.js';
 import { CopyShader } from '../shaders/CopyShader.js';
 import { SimplexNoise } from '../math/SimplexNoise.js';
 import { SimplexNoise } from '../math/SimplexNoise.js';
 
 
-class HBAOPass extends Pass {
+class GTAOPass extends Pass {
 
 
-	constructor( scene, camera, width, height, parameters ) {
+	constructor( scene, camera, width, height, parameters, aoParameters, pdParameters ) {
 
 
 		super();
 		super();
 
 
@@ -40,32 +38,32 @@ class HBAOPass extends Pass {
 		this.output = 0;
 		this.output = 0;
 		this._renderGBuffer = true;
 		this._renderGBuffer = true;
 		this._visibilityCache = new Map();
 		this._visibilityCache = new Map();
+		this.blendIntensity = 1.;
 
 
-		this.rings = 4;
-		this.samples = 16;
+		this.pdRings = 2.;
+		this.pdRadiusExponent = 2.;
+		this.pdSamples = 16;
 
 
-		this.noiseTexture = this.generateNoise();
+		this.gtaoNoiseTexture = generateMagicSquareNoise();
+		this.pdNoiseTexture = this.generateNoise();
 
 
-		this.hbaoRenderTarget = new WebGLRenderTarget( this.width, this.height, { type: HalfFloatType } );
-		this.pdRenderTarget = this.hbaoRenderTarget.clone();
+		this.gtaoRenderTarget = new WebGLRenderTarget( this.width, this.height, { type: HalfFloatType } );
+		this.pdRenderTarget = this.gtaoRenderTarget.clone();
 
 
-		this.hbaoMaterial = new ShaderMaterial( {
-			defines: Object.assign( {}, HBAOShader.defines ),
-			uniforms: UniformsUtils.clone( HBAOShader.uniforms ),
-			vertexShader: HBAOShader.vertexShader,
-			fragmentShader: HBAOShader.fragmentShader,
+		this.gtaoMaterial = new ShaderMaterial( {
+			defines: Object.assign( {}, GTAOShader.defines ),
+			uniforms: UniformsUtils.clone( GTAOShader.uniforms ),
+			vertexShader: GTAOShader.vertexShader,
+			fragmentShader: GTAOShader.fragmentShader,
 			blending: NoBlending,
 			blending: NoBlending,
 			depthTest: false,
 			depthTest: false,
 			depthWrite: false,
 			depthWrite: false,
 		} );
 		} );
-		this.hbaoMaterial.defines[ 'PERSPECTIVE_CAMERA' ] = this.camera.isPerspectiveCamera ? 1 : 0;
-		this.hbaoMaterial.uniforms[ 'tNoise' ].value = this.noiseTexture;
-		this.hbaoMaterial.uniforms[ 'resolution' ].value.set( this.width, this.height );
-		this.hbaoMaterial.uniforms[ 'cameraNear' ].value = this.camera.near;
-		this.hbaoMaterial.uniforms[ 'cameraFar' ].value = this.camera.far;
-		this.hbaoMaterial.uniforms[ 'radius' ].value = 2;
-		this.hbaoMaterial.uniforms[ 'distanceExponent' ].value = 2;
-		this.hbaoMaterial.uniforms[ 'bias' ].value = 0.01;
+		this.gtaoMaterial.definesPERSPECTIVE_CAMERA = this.camera.isPerspectiveCamera ? 1 : 0;
+		this.gtaoMaterial.uniforms.tNoise.value = this.gtaoNoiseTexture;
+		this.gtaoMaterial.uniforms.resolution.value.set( this.width, this.height );
+		this.gtaoMaterial.uniforms.cameraNear.value = this.camera.near;
+		this.gtaoMaterial.uniforms.cameraFar.value = this.camera.far;
 
 
 		this.normalMaterial = new MeshNormalMaterial();
 		this.normalMaterial = new MeshNormalMaterial();
 		this.normalMaterial.blending = NoBlending;
 		this.normalMaterial.blending = NoBlending;
@@ -78,22 +76,23 @@ class HBAOPass extends Pass {
 			depthTest: false,
 			depthTest: false,
 			depthWrite: false,
 			depthWrite: false,
 		} );
 		} );
-		this.pdMaterial.uniforms[ 'tDiffuse' ].value = this.hbaoRenderTarget.texture;
-		this.pdMaterial.uniforms[ 'tNoise' ].value = this.noiseTexture;
-		this.pdMaterial.uniforms[ 'resolution' ].value.set( this.width, this.height );
-		this.pdMaterial.uniforms[ 'lumaPhi' ].value = 10;
-		this.pdMaterial.uniforms[ 'depthPhi' ].value = 2;
-		this.pdMaterial.uniforms[ 'normalPhi' ].value = 3;
+		this.pdMaterial.uniforms.tDiffuse.value = this.gtaoRenderTarget.texture;
+		this.pdMaterial.uniforms.tNoise.value = this.pdNoiseTexture;
+		this.pdMaterial.uniforms.resolution.value.set( this.width, this.height );
+		this.pdMaterial.uniforms.lumaPhi.value = 10;
+		this.pdMaterial.uniforms.depthPhi.value = 2;
+		this.pdMaterial.uniforms.normalPhi.value = 3;
+		this.pdMaterial.uniforms.radius.value = 8;
 
 
 		this.depthRenderMaterial = new ShaderMaterial( {
 		this.depthRenderMaterial = new ShaderMaterial( {
-			defines: Object.assign( {}, HBAODepthShader.defines ),
-			uniforms: UniformsUtils.clone( HBAODepthShader.uniforms ),
-			vertexShader: HBAODepthShader.vertexShader,
-			fragmentShader: HBAODepthShader.fragmentShader,
+			defines: Object.assign( {}, GTAODepthShader.defines ),
+			uniforms: UniformsUtils.clone( GTAODepthShader.uniforms ),
+			vertexShader: GTAODepthShader.vertexShader,
+			fragmentShader: GTAODepthShader.fragmentShader,
 			blending: NoBlending
 			blending: NoBlending
 		} );
 		} );
-		this.depthRenderMaterial.uniforms[ 'cameraNear' ].value = this.camera.near;
-		this.depthRenderMaterial.uniforms[ 'cameraFar' ].value = this.camera.far;
+		this.depthRenderMaterial.uniforms.cameraNear.value = this.camera.near;
+		this.depthRenderMaterial.uniforms.cameraFar.value = this.camera.far;
 
 
 		this.copyMaterial = new ShaderMaterial( {
 		this.copyMaterial = new ShaderMaterial( {
 			uniforms: UniformsUtils.clone( CopyShader.uniforms ),
 			uniforms: UniformsUtils.clone( CopyShader.uniforms ),
@@ -110,19 +109,48 @@ class HBAOPass extends Pass {
 			blendEquationAlpha: AddEquation
 			blendEquationAlpha: AddEquation
 		} );
 		} );
 
 
+		this.blendMaterial = new ShaderMaterial( {
+			uniforms: UniformsUtils.clone( GTAOBlendShader.uniforms ),
+			vertexShader: GTAOBlendShader.vertexShader,
+			fragmentShader: GTAOBlendShader.fragmentShader,
+			transparent: true,
+			depthTest: false,
+			depthWrite: false,
+			blending: CustomBlending,
+			blendSrc: DstColorFactor,
+			blendDst: ZeroFactor,
+			blendEquation: AddEquation,
+			blendSrcAlpha: DstAlphaFactor,
+			blendDstAlpha: ZeroFactor,
+			blendEquationAlpha: AddEquation
+		} );
+
 		this.fsQuad = new FullScreenQuad( null );
 		this.fsQuad = new FullScreenQuad( null );
 
 
 		this.originalClearColor = new Color();
 		this.originalClearColor = new Color();
 
 
-		this.setTextures( parameters ? parameters.depthTexture : undefined, parameters ? parameters.normalTexture : undefined );
+		this.setGBuffer( parameters ? parameters.depthTexture : undefined, parameters ? parameters.normalTexture : undefined );
+
+		if ( aoParameters !== undefined ) {
+
+			this.updateGtaoMaterial( aoParameters );
+
+		}
+
+		if ( pdParameters !== undefined ) {
+
+			this.updatePdMaterial( pdParameters );
+
+		}
 
 
 	}
 	}
 
 
 	dispose() {
 	dispose() {
 
 
-		this.noiseTexture.dispose();
+		this.gtaoNoiseTexture.dispose();
+		this.pdNoiseTexture.dispose();
 		this.normalRenderTarget.dispose();
 		this.normalRenderTarget.dispose();
-		this.hbaoRenderTarget.dispose();
+		this.gtaoRenderTarget.dispose();
 		this.pdRenderTarget.dispose();
 		this.pdRenderTarget.dispose();
 		this.normalMaterial.dispose();
 		this.normalMaterial.dispose();
 		this.pdMaterial.dispose();
 		this.pdMaterial.dispose();
@@ -132,7 +160,7 @@ class HBAOPass extends Pass {
 
 
 	}
 	}
 
 
-	setTextures( depthTexture, normalTexture ) {
+	setGBuffer( depthTexture, normalTexture ) {
 
 
 		if ( depthTexture !== undefined ) {
 		if ( depthTexture !== undefined ) {
 
 
@@ -143,9 +171,6 @@ class HBAOPass extends Pass {
 		} else {
 		} else {
 
 
 			this.depthTexture = new DepthTexture();
 			this.depthTexture = new DepthTexture();
-			this.depthTexture.format = DepthStencilFormat;
-			this.depthTexture.type = UnsignedInt248Type;
-
 			this.normalRenderTarget = new WebGLRenderTarget( this.width, this.height, {
 			this.normalRenderTarget = new WebGLRenderTarget( this.width, this.height, {
 				minFilter: NearestFilter,
 				minFilter: NearestFilter,
 				magFilter: NearestFilter,
 				magFilter: NearestFilter,
@@ -158,47 +183,83 @@ class HBAOPass extends Pass {
 		}
 		}
 
 
 		const normalVectorType = ( this.normalTexture ) ? 1 : 0;
 		const normalVectorType = ( this.normalTexture ) ? 1 : 0;
-		const depthValueSource = ( this.depthTexture === this.normalTexture ) ? 1 : 0;
+		const depthValueSource = ( this.depthTexture === this.normalTexture ) ? 'w' : 'x';
 
 
-		this.hbaoMaterial.defines[ 'NORMAL_VECTOR_TYPE' ] = normalVectorType;
-		this.hbaoMaterial.defines[ 'DEPTH_VALUE_SOURCE' ] = depthValueSource;
-		this.hbaoMaterial.uniforms[ 'tNormal' ].value = this.normalTexture;
-		this.hbaoMaterial.uniforms[ 'tDepth' ].value = this.depthTexture;
+		this.gtaoMaterial.defines.NORMAL_VECTOR_TYPE = normalVectorType;
+		this.gtaoMaterial.defines.DEPTH_SWIZZLING = depthValueSource;
+		this.gtaoMaterial.uniforms.tNormal.value = this.normalTexture;
+		this.gtaoMaterial.uniforms.tDepth.value = this.depthTexture;
 
 
-		this.pdMaterial.defines[ 'NORMAL_VECTOR_TYPE' ] = normalVectorType;
-		this.pdMaterial.defines[ 'DEPTH_VALUE_SOURCE' ] = depthValueSource;
-		this.pdMaterial.uniforms[ 'tNormal' ].value = this.normalTexture;
-		this.pdMaterial.uniforms[ 'tDepth' ].value = this.depthTexture;
+		this.pdMaterial.defines.NORMAL_VECTOR_TYPE = normalVectorType;
+		this.pdMaterial.defines.DEPTH_SWIZZLING = depthValueSource;
+		this.pdMaterial.uniforms.tNormal.value = this.normalTexture;
+		this.pdMaterial.uniforms.tDepth.value = this.depthTexture;
 
 
-		this.depthRenderMaterial.uniforms[ 'tDepth' ].value = this.normalRenderTarget.depthTexture;
+		this.depthRenderMaterial.uniforms.tDepth.value = this.normalRenderTarget.depthTexture;
 
 
 	}
 	}
 
 
-	updateHbaoMaterial( parameters ) {
+	setSceneClipBox( box ) {
+
+		if ( box ) {
+
+			this.gtaoMaterial.needsUpdate = this.gtaoMaterial.defines.SCENE_CLIP_BOX !== 1;
+			this.gtaoMaterial.defines.SCENE_CLIP_BOX = 1;
+			this.gtaoMaterial.uniforms.sceneBoxMin.value.copy( box.min );
+			this.gtaoMaterial.uniforms.sceneBoxMax.value.copy( box.max );
+
+		} else {
+
+			this.gtaoMaterial.needsUpdate = this.gtaoMaterial.defines.SCENE_CLIP_BOX === 0;
+			this.gtaoMaterial.defines.SCENE_CLIP_BOX = 0;
+
+		}
+
+	}
+
+	updateGtaoMaterial( parameters ) {
 
 
 		if ( parameters.radius !== undefined ) {
 		if ( parameters.radius !== undefined ) {
 
 
-			this.hbaoMaterial.uniforms[ 'radius' ].value = parameters.radius;
+			this.gtaoMaterial.uniforms.radius.value = parameters.radius;
 
 
 		}
 		}
 
 
 		if ( parameters.distanceExponent !== undefined ) {
 		if ( parameters.distanceExponent !== undefined ) {
 
 
-			this.hbaoMaterial.uniforms[ 'distanceExponent' ].value = parameters.distanceExponent;
+			this.gtaoMaterial.uniforms.distanceExponent.value = parameters.distanceExponent;
+
+		}
+
+		if ( parameters.thickness !== undefined ) {
+
+			this.gtaoMaterial.uniforms.thickness.value = parameters.thickness;
 
 
 		}
 		}
 
 
 		if ( parameters.bias !== undefined ) {
 		if ( parameters.bias !== undefined ) {
 
 
-			this.hbaoMaterial.uniforms[ 'bias' ].value = parameters.bias;
+			this.gtaoMaterial.uniforms.bias.value = parameters.bias;
+
+		}
+
+		if ( parameters.scale !== undefined ) {
+
+			this.gtaoMaterial.uniforms.scale.value = parameters.scale;
 
 
 		}
 		}
 
 
-		if ( parameters.samples !== undefined && parameters.samples !== this.hbaoMaterial.defines[ 'SAMPLES' ] ) {
+		if ( parameters.samples !== undefined && parameters.samples !== this.gtaoMaterial.defines.SAMPLES ) {
 
 
-			this.hbaoMaterial.defines[ 'SAMPLES' ] = parameters.samples;
-			this.hbaoMaterial.defines[ 'SAMPLE_VECTORS' ] = generateHaboSampleKernelInitializer( parameters.samples );
-			this.hbaoMaterial.needsUpdate = true;
+			this.gtaoMaterial.defines.SAMPLES = parameters.samples;
+			this.gtaoMaterial.needsUpdate = true;
+
+		}
+
+		if ( parameters.screenSpaceRadius !== undefined && ( parameters.screenSpaceRadius ? 1 : 0 ) !== this.gtaoMaterial.defines.SCREEN_SPACE_RADIUS ) {
+
+			this.gtaoMaterial.defines.SCREEN_SPACE_RADIUS = parameters.screenSpaceRadius ? 1 : 0;
+			this.gtaoMaterial.needsUpdate = true;
 
 
 		}
 		}
 
 
@@ -210,47 +271,53 @@ class HBAOPass extends Pass {
 
 
 		if ( parameters.lumaPhi !== undefined ) {
 		if ( parameters.lumaPhi !== undefined ) {
 
 
-			this.pdMaterial.uniforms[ 'lumaPhi' ].value = parameters.lumaPhi;
+			this.pdMaterial.uniforms.lumaPhi.value = parameters.lumaPhi;
 
 
 		}
 		}
 
 
 		if ( parameters.depthPhi !== undefined ) {
 		if ( parameters.depthPhi !== undefined ) {
 
 
-			this.pdMaterial.uniforms[ 'depthPhi' ].value = parameters.depthPhi;
+			this.pdMaterial.uniforms.depthPhi.value = parameters.depthPhi;
 
 
 		}
 		}
 
 
 		if ( parameters.normalPhi !== undefined ) {
 		if ( parameters.normalPhi !== undefined ) {
 
 
-			this.pdMaterial.uniforms[ 'normalPhi' ].value = parameters.normalPhi;
+			this.pdMaterial.uniforms.normalPhi.value = parameters.normalPhi;
 
 
 		}
 		}
 
 
 		if ( parameters.radius !== undefined && parameters.radius !== this.radius ) {
 		if ( parameters.radius !== undefined && parameters.radius !== this.radius ) {
 
 
-			this.pdMaterial.uniforms[ 'radius' ].value = parameters.radius;
+			this.pdMaterial.uniforms.radius.value = parameters.radius;
 
 
 		}
 		}
 
 
-		if ( parameters.rings !== undefined && parameters.rings !== this.rings ) {
+		if ( parameters.radiusExponent !== undefined && parameters.radiusExponent !== this.pdRadiusExponent ) {
 
 
-			this.rings = parameters.rings;
+			this.pdRadiusExponent = parameters.radiusExponent;
 			updateShader = true;
 			updateShader = true;
 
 
 		}
 		}
 
 
-		if ( parameters.samples !== undefined && parameters.samples !== this.samples ) {
+		if ( parameters.rings !== undefined && parameters.rings !== this.pdRings ) {
 
 
-			this.samples = parameters.samples;
+			this.pdRings = parameters.rings;
 			updateShader = true;
 			updateShader = true;
 
 
 		}
 		}
 
 
-		if ( updateShader ) {
+		if ( parameters.samples !== undefined && parameters.samples !== this.pdSamples ) {
+
+			this.pdSamples = parameters.samples;
+			updateShader = true;
 
 
+		}
+
+		if ( updateShader ) {
 
 
-			this.pdMaterial.defines[ 'SAMPLES' ] = parameters.samples;
-			this.pdMaterial.defines[ 'SAMPLE_VECTORS' ] = generatePdSamplePointInitializer( parameters.samples, this.rings );
+			this.pdMaterial.defines.SAMPLES = this.pdSamples;
+			this.pdMaterial.defines.SAMPLE_VECTORS = generatePdSamplePointInitializer( this.pdSamples, this.pdRings, this.pdRadiusExponent );
 			this.pdMaterial.needsUpdate = true;
 			this.pdMaterial.needsUpdate = true;
 
 
 		}
 		}
@@ -259,7 +326,7 @@ class HBAOPass extends Pass {
 
 
 	render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) {
 	render( renderer, writeBuffer, readBuffer /*, deltaTime, maskActive */ ) {
 
 
-		// render normals and depth (honor only meshes, points and lines do not contribute to HBAO)
+		// render normals and depth (honor only meshes, points and lines do not contribute to AO)
 
 
 		if ( this._renderGBuffer ) {
 		if ( this._renderGBuffer ) {
 
 
@@ -269,77 +336,78 @@ class HBAOPass extends Pass {
 
 
 		}
 		}
 
 
-		// render HBAO
+		// render AO
 
 
-		this.hbaoMaterial.uniforms[ 'cameraNear' ].value = this.camera.near;
-		this.hbaoMaterial.uniforms[ 'cameraFar' ].value = this.camera.far;
-		this.hbaoMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix );
-		this.hbaoMaterial.uniforms[ 'cameraProjectionMatrixInverse' ].value.copy( this.camera.projectionMatrixInverse );
-		this.renderPass( renderer, this.hbaoMaterial, this.hbaoRenderTarget, 0xffffff, 1.0 );
+		this.gtaoMaterial.uniforms.cameraNear.value = this.camera.near;
+		this.gtaoMaterial.uniforms.cameraFar.value = this.camera.far;
+		this.gtaoMaterial.uniforms.cameraProjectionMatrix.value.copy( this.camera.projectionMatrix );
+		this.gtaoMaterial.uniforms.cameraProjectionMatrixInverse.value.copy( this.camera.projectionMatrixInverse );
+		this.gtaoMaterial.uniforms.cameraWorldMatrix.value.copy( this.camera.matrixWorld );
+		this.renderPass( renderer, this.gtaoMaterial, this.gtaoRenderTarget, 0xffffff, 1.0 );
 
 
 		// render poisson denoise
 		// render poisson denoise
 
 
-		this.pdMaterial.uniforms[ 'cameraProjectionMatrixInverse' ].value.copy( this.camera.projectionMatrixInverse );
+		this.pdMaterial.uniforms.cameraProjectionMatrixInverse.value.copy( this.camera.projectionMatrixInverse );
 		this.renderPass( renderer, this.pdMaterial, this.pdRenderTarget, 0xffffff, 1.0 );
 		this.renderPass( renderer, this.pdMaterial, this.pdRenderTarget, 0xffffff, 1.0 );
 
 
 		// output result to screen
 		// output result to screen
 
 
 		switch ( this.output ) {
 		switch ( this.output ) {
 
 
-			case HBAOPass.OUTPUT.Diffuse:
+			case GTAOPass.OUTPUT.Diffuse:
 
 
-				this.copyMaterial.uniforms[ 'tDiffuse' ].value = readBuffer.texture;
+				this.copyMaterial.uniforms.tDiffuse.value = readBuffer.texture;
 				this.copyMaterial.blending = NoBlending;
 				this.copyMaterial.blending = NoBlending;
 				this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer );
 				this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer );
 
 
 				break;
 				break;
 
 
-			case HBAOPass.OUTPUT.HBAO:
+			case GTAOPass.OUTPUT.AO:
 
 
-				this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.hbaoRenderTarget.texture;
+				this.copyMaterial.uniforms.tDiffuse.value = this.gtaoRenderTarget.texture;
 				this.copyMaterial.blending = NoBlending;
 				this.copyMaterial.blending = NoBlending;
 				this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer );
 				this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer );
 
 
 				break;
 				break;
 
 
-			case HBAOPass.OUTPUT.Denoise:
+			case GTAOPass.OUTPUT.Denoise:
 
 
-				this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.pdRenderTarget.texture;
+				this.copyMaterial.uniforms.tDiffuse.value = this.pdRenderTarget.texture;
 				this.copyMaterial.blending = NoBlending;
 				this.copyMaterial.blending = NoBlending;
 				this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer );
 				this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer );
 
 
 				break;
 				break;
 
 
-			case HBAOPass.OUTPUT.Depth:
+			case GTAOPass.OUTPUT.Depth:
 
 
-				this.depthRenderMaterial.uniforms[ 'cameraNear' ].value = this.camera.near;
-				this.depthRenderMaterial.uniforms[ 'cameraFar' ].value = this.camera.far;
+				this.depthRenderMaterial.uniforms.cameraNear.value = this.camera.near;
+				this.depthRenderMaterial.uniforms.cameraFar.value = this.camera.far;
 				this.renderPass( renderer, this.depthRenderMaterial, this.renderToScreen ? null : writeBuffer );
 				this.renderPass( renderer, this.depthRenderMaterial, this.renderToScreen ? null : writeBuffer );
 
 
 				break;
 				break;
 
 
-			case HBAOPass.OUTPUT.Normal:
+			case GTAOPass.OUTPUT.Normal:
 
 
-				this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.normalRenderTarget.texture;
+				this.copyMaterial.uniforms.tDiffuse.value = this.normalRenderTarget.texture;
 				this.copyMaterial.blending = NoBlending;
 				this.copyMaterial.blending = NoBlending;
 				this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer );
 				this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer );
 
 
 				break;
 				break;
 
 
-			case HBAOPass.OUTPUT.Default:
+			case GTAOPass.OUTPUT.Default:
 
 
-				this.copyMaterial.uniforms[ 'tDiffuse' ].value = readBuffer.texture;
+				this.copyMaterial.uniforms.tDiffuse.value = readBuffer.texture;
 				this.copyMaterial.blending = NoBlending;
 				this.copyMaterial.blending = NoBlending;
 				this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer );
 				this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer );
 
 
-				this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.pdRenderTarget.texture;
-				this.copyMaterial.blending = CustomBlending;
-				this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer );
+				this.blendMaterial.uniforms.intensity.value = this.blendIntensity;
+				this.blendMaterial.uniforms.tDiffuse.value = this.pdRenderTarget.texture;
+				this.renderPass( renderer, this.blendMaterial, this.renderToScreen ? null : writeBuffer );
 
 
 				break;
 				break;
 
 
 			default:
 			default:
-				console.warn( 'THREE.HBAOPass: Unknown output type.' );
+				console.warn( 'THREE.GTAOPass: Unknown output type.' );
 
 
 		}
 		}
 
 
@@ -398,8 +466,6 @@ class HBAOPass extends Pass {
 		renderer.render( this.scene, this.camera );
 		renderer.render( this.scene, this.camera );
 		this.scene.overrideMaterial = null;
 		this.scene.overrideMaterial = null;
 
 
-		// restore original state
-
 		renderer.autoClear = originalAutoClear;
 		renderer.autoClear = originalAutoClear;
 		renderer.setClearColor( this.originalClearColor );
 		renderer.setClearColor( this.originalClearColor );
 		renderer.setClearAlpha( originalClearAlpha );
 		renderer.setClearAlpha( originalClearAlpha );
@@ -411,16 +477,16 @@ class HBAOPass extends Pass {
 		this.width = width;
 		this.width = width;
 		this.height = height;
 		this.height = height;
 
 
-		this.hbaoRenderTarget.setSize( width, height );
+		this.gtaoRenderTarget.setSize( width, height );
 		this.normalRenderTarget.setSize( width, height );
 		this.normalRenderTarget.setSize( width, height );
 		this.pdRenderTarget.setSize( width, height );
 		this.pdRenderTarget.setSize( width, height );
 
 
-		this.hbaoMaterial.uniforms[ 'resolution' ].value.set( width, height );
-		this.hbaoMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix );
-		this.hbaoMaterial.uniforms[ 'cameraProjectionMatrixInverse' ].value.copy( this.camera.projectionMatrixInverse );
+		this.gtaoMaterial.uniforms.resolution.value.set( width, height );
+		this.gtaoMaterial.uniforms.cameraProjectionMatrix.value.copy( this.camera.projectionMatrix );
+		this.gtaoMaterial.uniforms.cameraProjectionMatrixInverse.value.copy( this.camera.projectionMatrixInverse );
 
 
-		this.pdMaterial.uniforms[ 'resolution' ].value.set( width, height );
-		this.pdMaterial.uniforms[ 'cameraProjectionMatrixInverse' ].value.copy( this.camera.projectionMatrixInverse );
+		this.pdMaterial.uniforms.resolution.value.set( width, height );
+		this.pdMaterial.uniforms.cameraProjectionMatrixInverse.value.copy( this.camera.projectionMatrixInverse );
 
 
 	}
 	}
 
 
@@ -469,10 +535,10 @@ class HBAOPass extends Pass {
 				const x = i;
 				const x = i;
 				const y = j;
 				const y = j;
 
 
-				data[ ( i * size + j ) * 4 ] = ( simplex.noise( x, y ) + 1.0 ) * 255.0;
-				data[ ( i * size + j ) * 4 + 1 ] = ( simplex.noise( x + size, y ) + 1.0 ) * 255.0;
-				data[ ( i * size + j ) * 4 + 2 ] = ( simplex.noise( x, y + size ) + 1.0 ) * 255.0;
-				data[ ( i * size + j ) * 4 + 3 ] = ( simplex.noise( x + size, y + size ) + 1.0 ) * 255.0;
+				data[ ( i * size + j ) * 4 ] = ( simplex.noise( x, y ) * 0.5 + 0.5 ) * 255;
+				data[ ( i * size + j ) * 4 + 1 ] = ( simplex.noise( x + size, y ) * 0.5 + 0.5 ) * 255;
+				data[ ( i * size + j ) * 4 + 2 ] = ( simplex.noise( x, y + size ) * 0.5 + 0.5 ) * 255;
+				data[ ( i * size + j ) * 4 + 3 ] = ( simplex.noise( x + size, y + size ) * 0.5 + 0.5 ) * 255;
 
 
 			}
 			}
 
 
@@ -489,13 +555,13 @@ class HBAOPass extends Pass {
 
 
 }
 }
 
 
-HBAOPass.OUTPUT = {
+GTAOPass.OUTPUT = {
 	'Default': 0,
 	'Default': 0,
 	'Diffuse': 1,
 	'Diffuse': 1,
 	'Depth': 2,
 	'Depth': 2,
 	'Normal': 3,
 	'Normal': 3,
-	'HBAO': 4,
+	'AO': 4,
 	'Denoise': 5,
 	'Denoise': 5,
 };
 };
 
 
-export { HBAOPass };
+export { GTAOPass };

+ 425 - 0
examples/jsm/shaders/GTAOShader.js

@@ -0,0 +1,425 @@
+import {
+	DataTexture,
+	Matrix4,
+	RepeatWrapping,
+	Vector2,
+	Vector3,
+} from 'three';
+
+/**
+ * References:
+ * - implemented algorithm - GTAO
+ *   - https://iryoku.com/downloads/Practical-Realtime-Strategies-for-Accurate-Indirect-Occlusion.pdf
+ *   - https://github.com/Patapom/GodComplex/blob/master/Tests/TestHBIL/2018%20Mayaux%20-%20Horizon-Based%20Indirect%20Lighting%20(HBIL).pdf
+ *
+ * - other AO algorithms that are not implemented here:
+ *   - Screen Space Ambient Occlusion (SSAO), see also SSAOShader.js
+ *     - http://john-chapman-graphics.blogspot.com/2013/01/ssao-tutorial.html
+ *     - https://learnopengl.com/Advanced-Lighting/SSAO
+ *     - https://creativecoding.soe.ucsc.edu/courses/cmpm164/_schedule/AmbientOcclusion.pdf
+ *     - https://drive.google.com/file/d/1SyagcEVplIm2KkRD3WQYSO9O0Iyi1hfy/edit
+ *   - Scalable Ambient Occlusion (SAO), see also SAOShader.js
+ *     - https://casual-effects.com/research/McGuire2012SAO/index.html
+ *	   - https://research.nvidia.com/sites/default/files/pubs/2012-06_Scalable-Ambient-Obscurance/McGuire12SAO.pdf
+ *   - N8HO
+ *     - https://github.com/N8python/n8ao
+ *   - Horizon Based Ambient Occlusion (HBAO)
+ *     - http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.577.2286&rep=rep1&type=pdf
+ *     - https://www.derschmale.com/2013/12/20/an-alternative-implementation-for-hbao-2/
+ *
+ * - further reading
+ * 	 - https://ceur-ws.org/Vol-3027/paper5.pdf
+ *   - https://www.comp.nus.edu.sg/~lowkl/publications/mssao_visual_computer_2012.pdf
+ *   - https://web.ics.purdue.edu/~tmcgraw/papers/mcgraw-ao-2008.pdf
+ *   - https://www.activision.com/cdn/research/Practical_Real_Time_Strategies_for_Accurate_Indirect_Occlusion_NEW%20VERSION_COLOR.pdf
+ *   - https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.390.2463&rep=rep1&type=pdf
+ *   - https://www.intel.com/content/www/us/en/developer/articles/technical/adaptive-screen-space-ambient-occlusion.html
+ */
+
+const GTAOShader = {
+
+	name: 'GTAOShader',
+
+	defines: {
+		PERSPECTIVE_CAMERA: 1,
+		SAMPLES: 16,
+		NORMAL_VECTOR_TYPE: 1,
+		DEPTH_SWIZZLING: 'x',
+		SCREEN_SPACE_RADIUS: 0,
+		SCREEN_SPACE_RADIUS_SCALE: 100.0,
+		SCENE_CLIP_BOX: 0,
+	},
+
+	uniforms: {
+		tNormal: { value: null },
+		tDepth: { value: null },
+		tNoise: { value: null },
+		resolution: { value: new Vector2() },
+		cameraNear: { value: null },
+		cameraFar: { value: null },
+		cameraProjectionMatrix: { value: new Matrix4() },
+		cameraProjectionMatrixInverse: { value: new Matrix4() },
+		cameraWorldMatrix: { value: new Matrix4() },
+		radius: { value: 0.25 },
+		distanceExponent: { value: 1. },
+		thickness: { value: 1. },
+		bias: { value: 0.001 },
+		scale: { value: 1. },
+		sceneBoxMin: { value: new Vector3( - 1, - 1, - 1 ) },
+      	sceneBoxMax: { value: new Vector3( 1, 1, 1 ) },
+	},
+
+	vertexShader: /* glsl */`
+
+		varying vec2 vUv;
+
+		void main() {
+			vUv = uv;
+			gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
+		}`,
+
+	fragmentShader: /* glsl */`
+
+		varying vec2 vUv;
+		uniform sampler2D tNormal;
+		uniform sampler2D tDepth;
+		uniform sampler2D tNoise;
+		uniform vec2 resolution;
+		uniform float cameraNear;
+		uniform float cameraFar;
+		uniform mat4 cameraProjectionMatrix;
+		uniform mat4 cameraProjectionMatrixInverse;		
+		uniform mat4 cameraWorldMatrix;
+		uniform float radius;
+		uniform float distanceExponent;
+		uniform float thickness;
+		uniform float bias;
+		uniform float scale;
+		#if SCENE_CLIP_BOX == 1
+			uniform vec3 sceneBoxMin;
+			uniform vec3 sceneBoxMax;
+		#endif
+		
+		#include <common>
+		#include <packing>
+
+		#ifndef FRAGMENT_OUTPUT
+		#define FRAGMENT_OUTPUT vec4(vec3(ao), 1.)
+		#endif
+
+		vec3 getViewPosition(const in vec2 screenPosition, const in float depth) {
+			vec4 clipSpacePosition = vec4(vec3(screenPosition, depth) * 2.0 - 1.0, 1.0);
+			vec4 viewSpacePosition = cameraProjectionMatrixInverse * clipSpacePosition;
+			return viewSpacePosition.xyz / viewSpacePosition.w;
+		}
+
+		float getDepth(const vec2 uv) {  
+			return textureLod(tDepth, uv.xy, 0.0).DEPTH_SWIZZLING;
+		}
+
+		float fetchDepth(const ivec2 uv) {   
+			return texelFetch(tDepth, uv.xy, 0).DEPTH_SWIZZLING;
+		}
+
+		float getViewZ(const in float depth) {
+			#if PERSPECTIVE_CAMERA == 1
+				return perspectiveDepthToViewZ(depth, cameraNear, cameraFar);
+			#else
+				return orthographicDepthToViewZ(depth, cameraNear, cameraFar);
+			#endif
+		}
+
+		vec3 computeNormalFromDepth(const vec2 uv) {
+            vec2 size = vec2(textureSize(tDepth, 0));
+            ivec2 p = ivec2(uv * size);
+            float c0 = fetchDepth(p);
+            float l2 = fetchDepth(p - ivec2(2, 0));
+            float l1 = fetchDepth(p - ivec2(1, 0));
+            float r1 = fetchDepth(p + ivec2(1, 0));
+            float r2 = fetchDepth(p + ivec2(2, 0));
+            float b2 = fetchDepth(p - ivec2(0, 2));
+            float b1 = fetchDepth(p - ivec2(0, 1));
+            float t1 = fetchDepth(p + ivec2(0, 1));
+            float t2 = fetchDepth(p + ivec2(0, 2));
+            float dl = abs((2.0 * l1 - l2) - c0);
+            float dr = abs((2.0 * r1 - r2) - c0);
+            float db = abs((2.0 * b1 - b2) - c0);
+            float dt = abs((2.0 * t1 - t2) - c0);
+            vec3 ce = getViewPosition(uv, c0).xyz;
+            vec3 dpdx = (dl < dr) ?  ce - getViewPosition((uv - vec2(1.0 / size.x, 0.0)), l1).xyz
+                                  : -ce + getViewPosition((uv + vec2(1.0 / size.x, 0.0)), r1).xyz;
+            vec3 dpdy = (db < dt) ?  ce - getViewPosition((uv - vec2(0.0, 1.0 / size.y)), b1).xyz
+                                  : -ce + getViewPosition((uv + vec2(0.0, 1.0 / size.y)), t1).xyz;
+            return normalize(cross(dpdx, dpdy));
+		}
+
+		vec3 getViewNormal(const vec2 uv) {
+			#if NORMAL_VECTOR_TYPE == 2
+				return normalize(textureLod(tNormal, uv, 0.).rgb);
+			#elif NORMAL_VECTOR_TYPE == 1
+				return unpackRGBToNormal(textureLod(tNormal, uv, 0.).rgb);
+			#else
+				return computeNormalFromDepth(uv);
+			#endif
+		}
+
+		vec3 getSceneUvAndDepth(vec3 sampleViewPos) {
+			vec4 sampleClipPos = cameraProjectionMatrix * vec4(sampleViewPos, 1.);
+			vec2 sampleUv = sampleClipPos.xy / sampleClipPos.w * 0.5 + 0.5;
+			float sampleSceneDepth = getDepth(sampleUv);
+			return vec3(sampleUv, sampleSceneDepth);
+		}
+		
+		void main() {
+			float depth = getDepth(vUv.xy);
+			if (depth >= 1.0) {
+				discard;
+				return;
+			}
+			vec3 viewPos = getViewPosition(vUv, depth);
+			vec3 viewNormal = getViewNormal(vUv);
+
+			float radiusToUse = radius;
+			float distanceFalloffToUse = thickness;
+			#if SCREEN_SPACE_RADIUS == 1
+			    float radiusScale = getViewPosition(vec2(0.5 + float(SCREEN_SPACE_RADIUS_SCALE) / resolution.x, 0.0), depth).x;
+				radiusToUse *= radiusScale;
+				distanceFalloffToUse *= radiusScale;
+			#endif
+
+			#if SCENE_CLIP_BOX == 1
+				vec3 worldPos = (cameraWorldMatrix * vec4(viewPos, 1.0)).xyz;
+      			float boxDistance = length(max(vec3(0.0), max(sceneBoxMin - worldPos, worldPos - sceneBoxMax)));
+				if (boxDistance > radiusToUse) {
+					discard;
+					return;
+				}
+			#endif
+			
+			vec2 noiseResolution = vec2(textureSize(tNoise, 0));
+			vec2 noiseUv = vUv * resolution / noiseResolution;
+			vec4 noiseTexel = textureLod(tNoise, noiseUv, 0.0);
+			vec3 randomVec = noiseTexel.xyz * 2.0 - 1.0;
+			vec3 tangent = normalize(vec3(randomVec.xy, 0.));
+			vec3 bitangent = vec3(-tangent.y, tangent.x, 0.);
+			mat3 kernelMatrix = mat3(tangent, bitangent, vec3(0., 0., 1.));
+
+			const int DIRECTIONS = SAMPLES < 30 ? 3 : 5;
+			const int STEPS = (SAMPLES + DIRECTIONS - 1) / DIRECTIONS;
+			float ao = 0.0, totalWeight = 0.0;
+			for (int i = 0; i < DIRECTIONS; ++i) {
+				
+				float angle = float(i) / float(DIRECTIONS) * PI;
+				vec4 sampleDir = vec4(cos(angle), sin(angle), 0., 0.5 + 0.5 * noiseTexel.w); 
+				sampleDir.xyz = normalize(kernelMatrix * sampleDir.xyz);
+
+				vec3 viewDir = normalize(-viewPos.xyz);
+				vec3 sliceBitangent = normalize(cross(sampleDir.xyz, viewDir));
+				vec3 sliceTangent = cross(sliceBitangent, viewDir);
+				vec3 normalInSlice = normalize(viewNormal - sliceBitangent * dot(viewNormal, sliceBitangent));
+				
+				vec3 tangentToNormalInSlice = cross(normalInSlice, sliceBitangent);
+				vec2 cosHorizons = vec2(dot(viewDir, tangentToNormalInSlice), dot(viewDir, -tangentToNormalInSlice));
+				
+				for (int j = 0; j < STEPS; ++j) {
+					vec3 sampleViewOffset = sampleDir.xyz * radiusToUse * sampleDir.w * pow(float(j + 1) / float(STEPS), distanceExponent);	
+
+					vec3 sampleSceneUvDepth = getSceneUvAndDepth(viewPos + sampleViewOffset);
+					vec3 sampleSceneViewPos = getViewPosition(sampleSceneUvDepth.xy, sampleSceneUvDepth.z);
+					vec3 viewDelta = sampleSceneViewPos - viewPos;
+					if (abs(viewDelta.z) < thickness) {
+						float sampleCosHorizon = dot(viewDir, normalize(viewDelta));
+						cosHorizons.x = max(cosHorizons.x, sampleCosHorizon);	
+					}		
+
+					sampleSceneUvDepth = getSceneUvAndDepth(viewPos - sampleViewOffset);
+					sampleSceneViewPos = getViewPosition(sampleSceneUvDepth.xy, sampleSceneUvDepth.z);
+					viewDelta = sampleSceneViewPos - viewPos;
+					if (abs(viewDelta.z) < thickness) {
+						float sampleCosHorizon = dot(viewDir, normalize(viewDelta));
+						cosHorizons.y = max(cosHorizons.y, sampleCosHorizon);	
+					}
+				}
+
+				vec2 sinHorizons = sqrt(1. - cosHorizons * cosHorizons);
+				float nx = dot(normalInSlice, sliceTangent);
+				float ny = dot(normalInSlice, viewDir);
+				float nxb = 1. / 2. * (acos(cosHorizons.y) - acos(cosHorizons.x) + sinHorizons.x * cosHorizons.x - sinHorizons.y * cosHorizons.y);
+				float nyb = 1. / 2. * (2. - cosHorizons.x * cosHorizons.x - cosHorizons.y * cosHorizons.y);
+				float occlusion = nx * nxb + ny * nyb;
+				ao += occlusion;
+			}
+
+			ao = clamp(ao / float(DIRECTIONS), 0., 1.);		
+		#if SCENE_CLIP_BOX == 1
+			ao = mix(ao, 1., smoothstep(0., radiusToUse, boxDistance));
+		#endif
+			ao = pow(ao, scale);
+
+			gl_FragColor = FRAGMENT_OUTPUT;
+		}`
+
+};
+
+const GTAODepthShader = {
+
+	name: 'GTAODepthShader',
+
+	defines: {
+		PERSPECTIVE_CAMERA: 1
+	},
+
+	uniforms: {
+		tDepth: { value: null },
+		cameraNear: { value: null },
+		cameraFar: { value: null },
+	},
+
+	vertexShader: /* glsl */`
+		varying vec2 vUv;
+
+		void main() {
+			vUv = uv;
+			gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
+		}`,
+
+	fragmentShader: /* glsl */`
+		uniform sampler2D tDepth;
+		uniform float cameraNear;
+		uniform float cameraFar;
+		varying vec2 vUv;
+
+		#include <packing>
+
+		float getLinearDepth( const in vec2 screenPosition ) {
+			#if PERSPECTIVE_CAMERA == 1
+				float fragCoordZ = texture2D( tDepth, screenPosition ).x;
+				float viewZ = perspectiveDepthToViewZ( fragCoordZ, cameraNear, cameraFar );
+				return viewZToOrthographicDepth( viewZ, cameraNear, cameraFar );
+			#else
+				return texture2D( tDepth, screenPosition ).x;
+			#endif
+		}
+
+		void main() {
+			float depth = getLinearDepth( vUv );
+			gl_FragColor = vec4( vec3( 1.0 - depth ), 1.0 );
+
+		}`
+
+};
+
+const GTAOBlendShader = {
+
+	name: 'GTAOBlendShader',
+
+	uniforms: {
+		tDiffuse: { value: null },
+		intensity: { value: 1.0 }
+	},
+
+	vertexShader: /* glsl */`
+		varying vec2 vUv;
+
+		void main() {
+			vUv = uv;
+			gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
+		}`,
+
+	fragmentShader: /* glsl */`
+		uniform float intensity;
+		uniform sampler2D tDiffuse;
+		varying vec2 vUv;
+
+		void main() {
+			vec4 texel = texture2D( tDiffuse, vUv );
+			gl_FragColor = vec4(mix(vec3(1.), texel.rgb, intensity), texel.a);
+		}`
+
+};
+
+
+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 ] = 0;
+
+	}
+
+	const noiseTexture = new DataTexture( data, noiseSize, noiseSize );
+	noiseTexture.wrapS = RepeatWrapping;
+	noiseTexture.wrapT = RepeatWrapping;
+	noiseTexture.needsUpdate = true;
+	return noiseTexture;
+
+}
+
+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;
+
+}
+
+
+export { generateMagicSquareNoise, GTAOShader, GTAODepthShader, GTAOBlendShader };

+ 0 - 322
examples/jsm/shaders/HBAOShader.js

@@ -1,322 +0,0 @@
-import {
-	Matrix4,
-	Vector2,
-	Vector4,
-} from 'three';
-
-/**
- * References:
- * http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.577.2286&rep=rep1&type=pdf
- * https://ceur-ws.org/Vol-3027/paper5.pdf
- * http://www.derschmale.com/2013/12/20/an-alternative-implementation-for-hbao-2
- * https://github.com/N8python/n8ao
- * https://github.com/0beqz/realism-effects
- * https://github.com/scanberg/hbao/blob/master/resources/shaders/hbao_frag.glsl
- * https://github.com/nvpro-samples/gl_ssao/blob/master/hbao.frag.glsl
- */
-
-const HBAOShader = {
-
-	name: 'HBAOShader',
-
-	defines: {
-		'PERSPECTIVE_CAMERA': 1,
-		'SAMPLES': 16,
-		'SAMPLE_VECTORS': generateHaboSampleKernelInitializer( 16 ),
-		'NORMAL_VECTOR_TYPE': 1,
-		'DEPTH_VALUE_SOURCE': 0,
-		'SAMPLING_FROM_NOISE': 0,
-	},
-
-	uniforms: {
-		'tNormal': { value: null },
-		'tDepth': { value: null },
-		'tNoise': { value: null },
-		'resolution': { value: new Vector2() },
-		'cameraNear': { value: null },
-		'cameraFar': { value: null },
-		'cameraProjectionMatrix': { value: new Matrix4() },
-		'cameraProjectionMatrixInverse': { value: new Matrix4() },
-		'radius': { value: 2. },
-		'distanceExponent': { value: 1. },
-		'bias': { value: 0.01 },
-	},
-
-	vertexShader: /* glsl */`
-
-		varying vec2 vUv;
-
-		void main() {
-
-			vUv = uv;
-
-			gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
-
-		}`,
-
-	fragmentShader: /* glsl */`
-
-		varying vec2 vUv;
-
-		uniform sampler2D tNormal;
-		uniform sampler2D tDepth;
-		uniform sampler2D tNoise;
-		uniform vec2 resolution;
-		uniform float cameraNear;
-		uniform float cameraFar;
-		uniform mat4 cameraProjectionMatrix;
-		uniform mat4 cameraProjectionMatrixInverse;		
-		uniform float radius;
-		uniform float distanceExponent;
-		uniform float bias;
-		
-		#include <common>
-		#include <packing>
-
-		#ifndef FRAGMENT_OUTPUT
-		#define FRAGMENT_OUTPUT vec4(vec3(ao), 1.)
-		#endif
-
-		const vec4 sampleKernel[SAMPLES] = SAMPLE_VECTORS;
-
-		vec3 getViewPosition(const in vec2 screenPosition, const in float depth) {
-			vec4 clipSpacePosition = vec4(vec3(screenPosition, depth) * 2.0 - 1.0, 1.0);
-			vec4 viewSpacePosition = cameraProjectionMatrixInverse * clipSpacePosition;
-			return viewSpacePosition.xyz / viewSpacePosition.w;
-		}
-
-		float getDepth(const vec2 uv) {
-		#if DEPTH_VALUE_SOURCE == 1    
-			return textureLod(tDepth, uv.xy, 0.0).a;
-		#else
-			return textureLod(tDepth, uv.xy, 0.0).r;
-		#endif
-		}
-
-		float fetchDepth(const ivec2 uv) {
-			#if DEPTH_VALUE_SOURCE == 1    
-				return texelFetch(tDepth, uv.xy, 0).a;
-			#else
-				return texelFetch(tDepth, uv.xy, 0).r;
-			#endif
-		}
-
-		float getViewZ(const in float depth) {
-			#if PERSPECTIVE_CAMERA == 1
-				return perspectiveDepthToViewZ(depth, cameraNear, cameraFar);
-			#else
-				return orthographicDepthToViewZ(depth, cameraNear, cameraFar);
-			#endif
-		}
-
-		vec3 computeNormalFromDepth(const vec2 uv) {
-            vec2 size = vec2(textureSize(tDepth, 0));
-            ivec2 p = ivec2(uv * size);
-            float c0 = fetchDepth(p);
-            float l2 = fetchDepth(p - ivec2(2, 0));
-            float l1 = fetchDepth(p - ivec2(1, 0));
-            float r1 = fetchDepth(p + ivec2(1, 0));
-            float r2 = fetchDepth(p + ivec2(2, 0));
-            float b2 = fetchDepth(p - ivec2(0, 2));
-            float b1 = fetchDepth(p - ivec2(0, 1));
-            float t1 = fetchDepth(p + ivec2(0, 1));
-            float t2 = fetchDepth(p + ivec2(0, 2));
-            float dl = abs((2.0 * l1 - l2) - c0);
-            float dr = abs((2.0 * r1 - r2) - c0);
-            float db = abs((2.0 * b1 - b2) - c0);
-            float dt = abs((2.0 * t1 - t2) - c0);
-            vec3 ce = getViewPosition(uv, c0).xyz;
-            vec3 dpdx = (dl < dr) ?  ce - getViewPosition((uv - vec2(1.0 / size.x, 0.0)), l1).xyz
-                                  : -ce + getViewPosition((uv + vec2(1.0 / size.x, 0.0)), r1).xyz;
-            vec3 dpdy = (db < dt) ?  ce - getViewPosition((uv - vec2(0.0, 1.0 / size.y)), b1).xyz
-                                  : -ce + getViewPosition((uv + vec2(0.0, 1.0 / size.y)), t1).xyz;
-            return normalize(cross(dpdx, dpdy));
-		}
-
-		vec3 getViewNormal(const vec2 uv) {
-		#if NORMAL_VECTOR_TYPE == 2
-			return normalize(textureLod(tNormal, uv, 0.).rgb);
-		#elif NORMAL_VECTOR_TYPE == 1
-			return unpackRGBToNormal(textureLod(tNormal, uv, 0.).rgb);
-		#else
-			return computeNormalFromDepth(uv);
-		#endif
-		}
-		
-		float getOcclusion(const vec2 uv, const vec3 viewPos, const vec3 viewNormal, const float depth, const vec4 sampleViewDir, inout float totalWeight) {
-			
-			vec3 sampleViewPos = viewPos + sampleViewDir.xyz * radius * pow(sampleViewDir.w, distanceExponent);
-			vec4 sampleClipPos = cameraProjectionMatrix * vec4(sampleViewPos, 1.);
-			vec2 sampleUv = sampleClipPos.xy / sampleClipPos.w * 0.5 + 0.5;
-			float sampleDepth = getDepth(sampleUv);
-			float distSample = abs(getViewZ(sampleDepth));
-			float distWorld = abs(sampleViewPos.z);
-			float distanceFalloffToUse = radius;
-			float rangeCheck = smoothstep(0.0, 1.0, distanceFalloffToUse / (abs(distSample - distWorld)));
-			float weight = dot(viewNormal, sampleViewDir.xyz);
-			vec2 diff = (uv - sampleUv) * resolution;
-			vec2 clipRangeCheck = step(0., sampleUv) * step(sampleUv, vec2(1.));
-			float occlusion = rangeCheck * weight * step(distSample + bias, distWorld) * step(0.707, dot(diff, diff)) * clipRangeCheck.x * clipRangeCheck.y;
-			totalWeight += weight;
-
-			return occlusion;
-		}
-		
-		void main() {
-			float depth = getDepth(vUv.xy);
-			if (depth == 1.0) {
-				discard;
-				return;
-			}
-			vec3 viewPos = getViewPosition(vUv, depth);
-			vec3 viewNormal = getViewNormal(vUv);
-			
-			vec2 noiseResolution = vec2(textureSize(tNoise, 0));
-			vec2 noiseUv = vUv * resolution / noiseResolution;
-			vec4 noiseTexel = textureLod(tNoise, noiseUv, 0.0);
-			vec3 randomVec = noiseTexel.xyz * 2.0 - 1.0;
-  			vec3 tangent = normalize(randomVec - viewNormal * dot(randomVec, viewNormal));
-      		vec3 bitangent = cross(viewNormal, tangent);
-      		mat3 kernelMatrix = mat3(tangent, bitangent, viewNormal);
-
-			float ao = 0.0, totalWeight = 0.0;
-			for (int i = 0; i < SAMPLES; i++) {		
-				#if SAMPLING_FROM_NOISE == 1
-					vec4 sampleNoise = noiseTexel;
-					if (i != 0) {
-						const vec4 hn = vec4(0.618033988749895, 0.3247179572447458, 0.2207440846057596, 0.1673039782614187);
-						sampleNoise = fract(sampleNoise + hn * float(i));
-						sampleNoise = mix(sampleNoise, 1.0 - sampleNoise, step(0.5, sampleNoise)) * 2.0;
-					}
-					vec3 hemisphereDir = normalize(kernelMatrix * vec3(sampleNoise.xy * 2. - 1., sampleNoise.z));
-					vec4 sampleViewDir = vec4(hemisphereDir, sampleNoise.a);
-				#else
-					vec4 sampleViewDir = sampleKernel[i];
-					sampleViewDir.xyz = normalize(kernelMatrix * sampleViewDir.xyz);
-				#endif
-				float occlusion = getOcclusion(vUv, viewPos, viewNormal, depth, sampleViewDir, totalWeight);
-				ao += occlusion;
-			}		
-			if (totalWeight > 0.) { 
-				ao /= totalWeight;
-			}
-			ao = clamp(1. - ao, 0., 1.);
-			gl_FragColor = FRAGMENT_OUTPUT;
-		}`
-
-};
-
-const HBAODepthShader = {
-
-	name: 'HBAODepthShader',
-
-	defines: {
-		'PERSPECTIVE_CAMERA': 1
-	},
-
-	uniforms: {
-
-		'tDepth': { value: null },
-		'cameraNear': { value: null },
-		'cameraFar': { value: null },
-
-	},
-
-	vertexShader: /* glsl */`
-
-		varying vec2 vUv;
-
-		void main() {
-
-			vUv = uv;
-			gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
-
-		}`,
-
-	fragmentShader: /* glsl */`
-
-		uniform sampler2D tDepth;
-
-		uniform float cameraNear;
-		uniform float cameraFar;
-
-		varying vec2 vUv;
-
-		#include <packing>
-
-		float getLinearDepth( const in vec2 screenPosition ) {
-
-			#if PERSPECTIVE_CAMERA == 1
-
-				float fragCoordZ = texture2D( tDepth, screenPosition ).x;
-				float viewZ = perspectiveDepthToViewZ( fragCoordZ, cameraNear, cameraFar );
-				return viewZToOrthographicDepth( viewZ, cameraNear, cameraFar );
-
-			#else
-
-				return texture2D( tDepth, screenPosition ).x;
-
-			#endif
-
-		}
-
-		void main() {
-
-			float depth = getLinearDepth( vUv );
-			gl_FragColor = vec4( vec3( 1.0 - depth ), 1.0 );
-
-		}`
-
-};
-
-function generateHaboSampleKernelInitializer( samples ) {
-
-	const poissonDisk = generateHaboSamples( samples );
-
-	let glslCode = 'vec4[SAMPLES](';
-
-	for ( let i = 0; i < samples; i ++ ) {
-
-		const sample = poissonDisk[ i ];
-		glslCode += `vec4(${sample.x}, ${sample.y}, ${sample.z}, ${sample.w})`;
-
-		if ( i < samples - 1 ) {
-
-			glslCode += ',';
-
-		}
-
-	}
-
-	glslCode += ')';
-
-	return glslCode;
-
-}
-
-function generateHaboSamples( samples ) {
-
-	const kernel = [];
-	for ( let kernelIndex = 0; kernelIndex < samples; kernelIndex ++ ) {
-
-		const spiralAngle = kernelIndex * Math.PI * ( 3 - Math.sqrt( 5 ) );
-		const z = Math.sqrt( 0.99 - ( kernelIndex / ( samples - 1 ) ) * 0.98 );
-		const radius = Math.sqrt( 1 - z * z );
-		const x = Math.cos( spiralAngle ) * radius;
-		const y = Math.sin( spiralAngle ) * radius;
-		const scaleStep = 8;
-		const scaleRange = Math.floor( samples / scaleStep );
-		const scaleIndex =
-			Math.floor( kernelIndex / scaleStep ) +
-			( kernelIndex % scaleStep ) * scaleRange;
-		let scale = 1 - scaleIndex / samples;
-		scale = 0.1 + 0.9 * scale;
-		kernel.push( new Vector4( x, y, z, scale ) );
-
-	}
-
-	return kernel;
-
-}
-
-export { generateHaboSampleKernelInitializer, HBAOShader, HBAODepthShader };

+ 36 - 58
examples/jsm/shaders/PoissonDenoiseShader.js

@@ -1,12 +1,13 @@
 import {
 import {
 	Matrix4,
 	Matrix4,
 	Vector2,
 	Vector2,
+	Vector3,
 } from 'three';
 } from 'three';
 
 
 /**
 /**
  * References:
  * References:
- * https://github.com/0beqz/realism-effects
- * https://github.com/N8python/n8ao
+ * https://openaccess.thecvf.com/content/WACV2021/papers/Khademi_Self-Supervised_Poisson-Gaussian_Denoising_WACV_2021_paper.pdf
+ * https://arxiv.org/pdf/2206.01856.pdf
  */
  */
 
 
 const PoissonDenoiseShader = {
 const PoissonDenoiseShader = {
@@ -15,7 +16,7 @@ const PoissonDenoiseShader = {
 
 
 	defines: {
 	defines: {
 		'SAMPLES': 16,
 		'SAMPLES': 16,
-		'SAMPLE_VECTORS': generatePdSamplePointInitializer( 16, 4 ),
+		'SAMPLE_VECTORS': generatePdSamplePointInitializer( 16, 2, 1 ),
 		'NORMAL_VECTOR_TYPE': 1,
 		'NORMAL_VECTOR_TYPE': 1,
 		'DEPTH_VALUE_SOURCE': 0,
 		'DEPTH_VALUE_SOURCE': 0,
 	},
 	},
@@ -30,7 +31,7 @@ const PoissonDenoiseShader = {
 		'lumaPhi': { value: 5. },
 		'lumaPhi': { value: 5. },
 		'depthPhi': { value: 5. },
 		'depthPhi': { value: 5. },
 		'normalPhi': { value: 5. },
 		'normalPhi': { value: 5. },
-		'radius': { value: 10. },
+		'radius': { value: 4. },
 		'index': { value: 0 }
 		'index': { value: 0 }
 	},
 	},
 
 
@@ -39,11 +40,8 @@ const PoissonDenoiseShader = {
 		varying vec2 vUv;
 		varying vec2 vUv;
 
 
 		void main() {
 		void main() {
-
 			vUv = uv;
 			vUv = uv;
-
 			gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
 			gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
-
 		}`,
 		}`,
 
 
 	fragmentShader: /* glsl */`
 	fragmentShader: /* glsl */`
@@ -77,7 +75,7 @@ const PoissonDenoiseShader = {
 			return SAMPLE_LUMINANCE;
 			return SAMPLE_LUMINANCE;
 		}
 		}
 
 
-		const vec2 poissonDisk[SAMPLES] = SAMPLE_VECTORS;
+		const vec3 poissonDisk[SAMPLES] = SAMPLE_VECTORS;
 
 
 		vec3 getViewPosition(const in vec2 screenPosition, const in float depth) {
 		vec3 getViewPosition(const in vec2 screenPosition, const in float depth) {
 			vec4 clipSpacePosition = vec4(vec3(screenPosition, depth) * 2.0 - 1.0, 1.0);
 			vec4 clipSpacePosition = vec4(vec3(screenPosition, depth) * 2.0 - 1.0, 1.0);
@@ -134,9 +132,24 @@ const PoissonDenoiseShader = {
 			return computeNormalFromDepth(uv);
 			return computeNormalFromDepth(uv);
 		#endif
 		#endif
 		}
 		}
+
+		void denoiseSample(in vec3 center, in vec3 viewNormal, in vec3 viewPos, in vec2 sampleUv, inout vec3 denoised, inout float totalWeight) {
+			vec4 sampleTexel = textureLod(tDiffuse, sampleUv, 0.0);
+			float sampleDepth = getDepth(sampleUv);
+			vec3 sampleNormal = getViewNormal(sampleUv);
+			vec3 neighborColor = sampleTexel.rgb;
+			vec3 viewPosSample = getViewPosition(sampleUv, sampleDepth);
+			
+			float normalDiff = dot(viewNormal, sampleNormal);
+			float normalSimilarity = pow(max(normalDiff, 0.), normalPhi);
+			float lumaDiff = abs(getLuminance(neighborColor) - getLuminance(center));
+			float lumaSimilarity = max(1.0 - lumaDiff / lumaPhi, 0.0);
+			float depthDiff = abs(dot(viewPos - viewPosSample, viewNormal));
+			float depthSimilarity = max(1. - depthDiff / depthPhi, 0.);
+			float w = lumaSimilarity * depthSimilarity * normalSimilarity;
 		
 		
-		float distToPlane(const vec3 viewPos, const vec3 neighborViewPos, const vec3 viewNormal) {
-			return abs(dot(viewPos - neighborViewPos, viewNormal));
+			denoised += w * neighborColor;
+			totalWeight += w;
 		}
 		}
 		
 		
 		void main() {
 		void main() {
@@ -147,41 +160,22 @@ const PoissonDenoiseShader = {
 				return;
 				return;
 			}
 			}
 			vec4 texel = textureLod(tDiffuse, vUv, 0.0);
 			vec4 texel = textureLod(tDiffuse, vUv, 0.0);
-			vec3 denoised = texel.rgb;
 			vec3 center = texel.rgb;
 			vec3 center = texel.rgb;
 			vec3 viewPos = getViewPosition(vUv, depth);
 			vec3 viewPos = getViewPosition(vUv, depth);
 
 
 			vec2 noiseResolution = vec2(textureSize(tNoise, 0));
 			vec2 noiseResolution = vec2(textureSize(tNoise, 0));
 			vec2 noiseUv = vUv * resolution / noiseResolution;
 			vec2 noiseUv = vUv * resolution / noiseResolution;
 			vec4 noiseTexel = textureLod(tNoise, noiseUv, 0.0);
 			vec4 noiseTexel = textureLod(tNoise, noiseUv, 0.0);
-      		//vec2 noiseVec = normalize((index % 2 == 0 ? noiseTexel.xy : noiseTexel.yz) * 2.0 - 1.0);
-			vec2 noiseVec = vec2(sin(noiseTexel[index % 4] * 2. * PI), cos(noiseTexel[index % 4] * 2. * PI));
+      		vec2 noiseVec = vec2(sin(noiseTexel[index % 4] * 2. * PI), cos(noiseTexel[index % 4] * 2. * PI));
     		mat2 rotationMatrix = mat2(noiseVec.x, -noiseVec.y, noiseVec.x, noiseVec.y);
     		mat2 rotationMatrix = mat2(noiseVec.x, -noiseVec.y, noiseVec.x, noiseVec.y);
 		
 		
 			float totalWeight = 1.0;
 			float totalWeight = 1.0;
+			vec3 denoised = texel.rgb;
 			for (int i = 0; i < SAMPLES; i++) {
 			for (int i = 0; i < SAMPLES; i++) {
-				vec2 offset = rotationMatrix * (poissonDisk[i] * radius / resolution);
+				vec3 sampleDir = poissonDisk[i];
+				vec2 offset = rotationMatrix * (sampleDir.xy * (1. + sampleDir.z * (radius - 1.)) / resolution);
 				vec2 sampleUv = vUv + offset;
 				vec2 sampleUv = vUv + offset;
-				vec4 sampleTexel = textureLod(tDiffuse, sampleUv, 0.0);
-				float sampleDepth = getDepth(sampleUv);
-				vec3 sampleNormal = getViewNormal(sampleUv);
-				vec3 neighborColor = sampleTexel.rgb;
-		
-				vec3 viewPosSample = getViewPosition(sampleUv, sampleDepth);
-				
-				float normalDiff = dot(viewNormal, sampleNormal);
-				float normalSimilarity = pow(max(normalDiff, 0.), normalPhi);
-		
-				float lumaDiff = abs(getLuminance(neighborColor) - getLuminance(center));
-				float lumaSimilarity = max(1.0 - lumaDiff / lumaPhi, 0.0);
-		
-				float depthDiff = 1. - distToPlane(viewPos, viewPosSample, viewNormal);
-				float depthSimilarity = max(depthDiff / depthPhi, 0.);
-		
-				float w = lumaSimilarity * depthSimilarity * normalSimilarity;
-		
-				denoised += w * neighborColor;
-				totalWeight += w;
+				denoiseSample(center, viewNormal, viewPos, sampleUv, denoised, totalWeight);
 			}
 			}
 		
 		
 			if (totalWeight > 0.) { 
 			if (totalWeight > 0.) { 
@@ -192,52 +186,36 @@ const PoissonDenoiseShader = {
 
 
 };
 };
 
 
-function generatePdSamplePointInitializer( samples, rings ) {
+function generatePdSamplePointInitializer( samples, rings, radiusExponent ) {
 
 
 	const poissonDisk = generateDenoiseSamples(
 	const poissonDisk = generateDenoiseSamples(
 		samples,
 		samples,
 		rings,
 		rings,
-
+		radiusExponent,
 	);
 	);
 
 
-	let glslCode = 'vec2[SAMPLES](';
+	let glslCode = 'vec3[SAMPLES](';
 
 
 	for ( let i = 0; i < samples; i ++ ) {
 	for ( let i = 0; i < samples; i ++ ) {
 
 
 		const sample = poissonDisk[ i ];
 		const sample = poissonDisk[ i ];
-		glslCode += `vec2(${sample.x}, ${sample.y})`;
-
-		if ( i < samples - 1 ) {
-
-			glslCode += ',';
-
-		}
+		glslCode += `vec3(${sample.x}, ${sample.y}, ${sample.z})${( i < samples - 1 ) ? ',' : ')'}`;
 
 
 	}
 	}
 
 
-	glslCode += ')';
-
 	return glslCode;
 	return glslCode;
 
 
 }
 }
 
 
-function generateDenoiseSamples( numSamples, numRings ) {
+function generateDenoiseSamples( numSamples, numRings, radiusExponent ) {
 
 
-	const angleStep = ( 2 * Math.PI * numRings ) / numSamples;
-	const invNumSamples = 1.0 / numSamples;
-	const radiusStep = invNumSamples;
 	const samples = [];
 	const samples = [];
-	let radius = invNumSamples;
-	let angle = 0;
 
 
 	for ( let i = 0; i < numSamples; i ++ ) {
 	for ( let i = 0; i < numSamples; i ++ ) {
 
 
-		const v = new Vector2( Math.cos( angle ), Math.sin( angle ) )
-			.multiplyScalar( Math.pow( radius, 0.75 ) );
-
-		samples.push( v );
-		radius += radiusStep;
-		angle += angleStep;
+		const angle = 2 * Math.PI * numRings * i / numSamples;
+		const radius = Math.pow( i / ( numSamples - 1 ), radiusExponent );
+		samples.push( new Vector3( Math.cos( angle ), Math.sin( angle ), radius ) );
 
 
 	}
 	}
 
 

BIN
examples/screenshots/webgl_postprocessing_gtao.jpg


BIN
examples/screenshots/webgl_postprocessing_hbao.jpg


+ 1 - 1
examples/tags.json

@@ -87,7 +87,7 @@
 	"webgl_postprocessing_dof2": [ "bokeh" ],
 	"webgl_postprocessing_dof2": [ "bokeh" ],
 	"webgl_postprocessing_fxaa": [ "msaa", "multisampled" ],
 	"webgl_postprocessing_fxaa": [ "msaa", "multisampled" ],
 	"webgl_postprocessing_godrays": [ "light scattering" ],
 	"webgl_postprocessing_godrays": [ "light scattering" ],
-	"webgl_postprocessing_hbao": [ "ambient occlusion" ],
+	"webgl_postprocessing_gtao": [ "ambient occlusion" ],
 	"webgl_shadowmap_progressive": [ "shadow", "soft", "lightmap", "onBeforeCompile" ],
 	"webgl_shadowmap_progressive": [ "shadow", "soft", "lightmap", "onBeforeCompile" ],
 	"webgl_postprocessing_ssaa": [ "msaa", "multisampled" ],
 	"webgl_postprocessing_ssaa": [ "msaa", "multisampled" ],
 	"webgl_postprocessing_ssaa_unbiased": [ "msaa", "multisampled" ],
 	"webgl_postprocessing_ssaa_unbiased": [ "msaa", "multisampled" ],

+ 84 - 62
examples/webgl_postprocessing_hbao.html → examples/webgl_postprocessing_gtao.html

@@ -1,23 +1,23 @@
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html lang="en">
 <html lang="en">
 	<head>
 	<head>
-		<title>three.js webgl - postprocessing - Horizon Based Ambient Occlusion</title>
+		<title>three.js webgl - postprocessing - GTAO</title>
 		<meta charset="utf-8">
 		<meta charset="utf-8">
 		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
 		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
 		<link type="text/css" rel="stylesheet" href="main.css">
 		<link type="text/css" rel="stylesheet" href="main.css">
 		<style>
 		<style>
 			body {
 			body {
-				background-color: #ffffff;
+				background-color: #bfe3dd;
 				color: #000;
 				color: #000;
 			}
 			}
 			a {
 			a {
 				color: #2983ff;
 				color: #2983ff;
 			}
 			}
-		</style>
+		</style>f
 	</head>
 	</head>
 	<body>
 	<body>
 		<div id="info">
 		<div id="info">
-			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - horizon based ambient occlusion (HBAO) by <a href="https://github.com/Rabbid76">Rabbid76</a><br/>
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - Ground Truth Ambient Occlusion (GTAO) by <a href="https://github.com/Rabbid76" target="_blank" rel="noopener">Rabbid76</a><br/>
 		</div>
 		</div>
 
 
 		<script type="importmap">
 		<script type="importmap">
@@ -32,22 +32,47 @@
 		<script type="module">
 		<script type="module">
 
 
 			import * as THREE from 'three';
 			import * as THREE from 'three';
-
 			import Stats from 'three/addons/libs/stats.module.js';
 			import Stats from 'three/addons/libs/stats.module.js';
 			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
 			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 			import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
 			import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
 			import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
 			import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
-
 			import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
 			import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
 			import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
 			import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
-			import { HBAOPass } from 'three/addons/postprocessing/HBAOPass.js';
+			import { GTAOPass } from 'three/addons/postprocessing/GTAOPass.js';
 			import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
 			import { OutputPass } from 'three/addons/postprocessing/OutputPass.js';
 
 
+			const dracoLoader = new DRACOLoader();
+			dracoLoader.setDecoderPath( 'jsm/libs/draco/' );
+			dracoLoader.setDecoderConfig( { type: 'js' } );
+			const loader = new GLTFLoader();
+			loader.setDRACOLoader( dracoLoader );
+			loader.setPath( 'models/gltf/' );
+
 			let mixer;
 			let mixer;
 
 
+			const generateLittlestTokyoScene = ( scene ) => {
+
+				loader.load( 'LittlestTokyo.glb', ( gltf ) => {
+
+					const model = gltf.scene;
+					model.position.set( 1, 1, 0 );
+					model.scale.set( 0.01, 0.01, 0.01 );
+					scene.add( model );
+
+					mixer = new THREE.AnimationMixer( model );
+					mixer.clipAction( gltf.animations[ 0 ] ).play();
+
+					const box = new THREE.Box3().setFromObject( scene );
+					gtaoPass.setSceneClipBox( box );
+
+					animate();
+
+				}, undefined, ( e ) => console.error( e ) );
+
+			};
+
 			const clock = new THREE.Clock();
 			const clock = new THREE.Clock();
 			const container = document.createElement( 'div' );
 			const container = document.createElement( 'div' );
 			document.body.appendChild( container );
 			document.body.appendChild( container );
@@ -56,7 +81,6 @@
 			container.appendChild( stats.dom );
 			container.appendChild( stats.dom );
 
 
 			const renderer = new THREE.WebGLRenderer( { antialias: true } );
 			const renderer = new THREE.WebGLRenderer( { antialias: true } );
-			// renderer.setPixelRatio( window.devicePixelRatio );
 			renderer.setSize( window.innerWidth, window.innerHeight );
 			renderer.setSize( window.innerWidth, window.innerHeight );
 			document.body.appendChild( renderer.domElement );
 			document.body.appendChild( renderer.domElement );
 
 
@@ -75,29 +99,6 @@
 			controls.enablePan = false;
 			controls.enablePan = false;
 			controls.enableDamping = true;
 			controls.enableDamping = true;
 
 
-			const dracoLoader = new DRACOLoader();
-			dracoLoader.setDecoderPath( 'jsm/libs/draco/gltf/' );
-
-			const loader = new GLTFLoader();
-			loader.setDRACOLoader( dracoLoader );
-			loader.load( 'models/gltf/LittlestTokyo.glb', function ( gltf ) {
-
-				const model = gltf.scene;
-				model.position.set( 1, 1, 0 );
-				model.scale.set( 0.01, 0.01, 0.01 );
-				scene.add( model );
-
-				mixer = new THREE.AnimationMixer( model );
-				mixer.clipAction( gltf.animations[ 0 ] ).play();
-
-				animate();
-
-			}, undefined, function ( e ) {
-
-				console.error( e );
-
-			} );
-
 			const width = window.innerWidth;
 			const width = window.innerWidth;
 			const height = window.innerHeight;
 			const height = window.innerHeight;
 			const pixelRatio = renderer.getPixelRatio();
 			const pixelRatio = renderer.getPixelRatio();
@@ -113,56 +114,71 @@
 			const renderPass = new RenderPass( scene, camera );
 			const renderPass = new RenderPass( scene, camera );
 			composer.addPass( renderPass );
 			composer.addPass( renderPass );
 
 
-			const hbaoPass = new HBAOPass( scene, camera, width, height );
-			hbaoPass.output = HBAOPass.OUTPUT.Denoise;
-			composer.addPass( hbaoPass );
+			const gtaoPass = new GTAOPass( scene, camera, width, height );
+			gtaoPass.output = GTAOPass.OUTPUT.Denoise;
+			composer.addPass( gtaoPass );
 
 
 			const outputPass = new OutputPass();
 			const outputPass = new OutputPass();
 			composer.addPass( outputPass );
 			composer.addPass( outputPass );
 
 
+			generateLittlestTokyoScene( scene );
+
 			// Init gui
 			// Init gui
 			const gui = new GUI();
 			const gui = new GUI();
 
 
-			gui.add( hbaoPass, 'output', {
-				'Default': HBAOPass.OUTPUT.Default,
-				'Diffuse': HBAOPass.OUTPUT.Diffuse,
-				'HBAO Only': HBAOPass.OUTPUT.HBAO,
-				'HBAO Only + Denoise': HBAOPass.OUTPUT.Denoise,
-				'Depth': HBAOPass.OUTPUT.Depth,
-				'Normal': HBAOPass.OUTPUT.Normal
+			gui.add( gtaoPass, 'output', {
+				'Default': GTAOPass.OUTPUT.Default,
+				'Diffuse': GTAOPass.OUTPUT.Diffuse,
+				'AO Only': GTAOPass.OUTPUT.AO,
+				'AO Only + Denoise': GTAOPass.OUTPUT.Denoise,
+				'Depth': GTAOPass.OUTPUT.Depth,
+				'Normal': GTAOPass.OUTPUT.Normal
 			} ).onChange( function ( value ) {
 			} ).onChange( function ( value ) {
 
 
-				hbaoPass.output = value;
+				gtaoPass.output = value;
 
 
 			} );
 			} );
 
 
-			const hbaoParameters = {
-				radius: 2.,
+			const aoParameters = {
+				radius: 0.25,
 				distanceExponent: 1.,
 				distanceExponent: 1.,
-				bias: 0.01,
+				thickness: 1.,
+				bias: 0.001,
+				scale: 1.,
 				samples: 16,
 				samples: 16,
+				distanceFallOff: true,
+				clipRangeCheck: true,
+				depthRelativeBias: false,
+				nvAlignedSamples: false,
+				screenSpaceRadius: false,
 			};
 			};
 			const pdParameters = {
 			const pdParameters = {
 				lumaPhi: 10.,
 				lumaPhi: 10.,
 				depthPhi: 2.,
 				depthPhi: 2.,
 				normalPhi: 3.,
 				normalPhi: 3.,
-				radius: 10.,
-				rings: 4,
+				radius: 4.,
+				radiusExponent: 1.,
+				rings: 2.,
 				samples: 16,
 				samples: 16,
 			};
 			};
-			hbaoPass.updateHbaoMaterial( hbaoParameters );
-			hbaoPass.updatePdMaterial( pdParameters );
-			gui.add( hbaoParameters, 'radius' ).min( 0.01 ).max( 10 ).step( 0.01 ).onChange( () => hbaoPass.updateHbaoMaterial( hbaoParameters ) );
-			gui.add( hbaoParameters, 'distanceExponent' ).min( 1 ).max( 4 ).step( 0.01 ).onChange( () => hbaoPass.updateHbaoMaterial( hbaoParameters ) );
-			gui.add( hbaoParameters, 'bias' ).min( 0 ).max( 0.1 ).step( 0.001 ).onChange( () => hbaoPass.updateHbaoMaterial( hbaoParameters ) );
-			gui.add( hbaoParameters, 'samples' ).min( 1 ).max( 32 ).step( 1 ).onChange( () => hbaoPass.updateHbaoMaterial( hbaoParameters ) );
-			gui.add( pdParameters, 'lumaPhi' ).min( 0 ).max( 20 ).step( 0.01 ).onChange( () => hbaoPass.updatePdMaterial( pdParameters ) );
-			gui.add( pdParameters, 'depthPhi' ).min( 0.01 ).max( 20 ).step( 0.01 ).onChange( () => hbaoPass.updatePdMaterial( pdParameters ) );
-			gui.add( pdParameters, 'normalPhi' ).min( 0.01 ).max( 20 ).step( 0.01 ).onChange( () => hbaoPass.updatePdMaterial( pdParameters ) );
-			gui.add( pdParameters, 'radius' ).min( 0 ).max( 32 ).step( 1 ).onChange( () => hbaoPass.updatePdMaterial( pdParameters ) );
-			gui.add( pdParameters, 'rings' ).min( 0 ).max( 16 ).step( 0.125 ).onChange( () => hbaoPass.updatePdMaterial( pdParameters ) );
-			gui.add( pdParameters, 'samples' ).min( 1 ).max( 32 ).step( 1 ).onChange( () => hbaoPass.updatePdMaterial( pdParameters ) );
-
+			gtaoPass.updateGtaoMaterial( aoParameters );
+			gtaoPass.updatePdMaterial( pdParameters );
+			gui.add( gtaoPass, 'blendIntensity' ).min( 0 ).max( 1 ).step( 0.01 );
+			gui.add( aoParameters, 'radius' ).min( 0.01 ).max( 1 ).step( 0.01 ).onChange( () => gtaoPass.updateGtaoMaterial( aoParameters ) );
+			gui.add( aoParameters, 'distanceExponent' ).min( 1 ).max( 4 ).step( 0.01 ).onChange( () => gtaoPass.updateGtaoMaterial( aoParameters ) );
+			gui.add( aoParameters, 'thickness' ).min( 0.01 ).max( 10 ).step( 0.01 ).onChange( () => gtaoPass.updateGtaoMaterial( aoParameters ) );
+			gui.add( aoParameters, 'bias' ).min( 0 ).max( 0.1 ).step( 0.0001 ).onChange( () => gtaoPass.updateGtaoMaterial( aoParameters ) );
+			gui.add( aoParameters, 'scale' ).min( 0.01 ).max( 2.0 ).step( 0.01 ).onChange( () => gtaoPass.updateGtaoMaterial( aoParameters ) );
+			gui.add( aoParameters, 'samples' ).min( 2 ).max( 32 ).step( 1 ).onChange( () => gtaoPass.updateGtaoMaterial( aoParameters ) );
+			gui.add( aoParameters, 'screenSpaceRadius' ).onChange( () => gtaoPass.updateGtaoMaterial( aoParameters ) );
+			gui.add( pdParameters, 'lumaPhi' ).min( 0 ).max( 20 ).step( 0.01 ).onChange( () => gtaoPass.updatePdMaterial( pdParameters ) );
+			gui.add( pdParameters, 'depthPhi' ).min( 0.01 ).max( 20 ).step( 0.01 ).onChange( () => gtaoPass.updatePdMaterial( pdParameters ) );
+			gui.add( pdParameters, 'normalPhi' ).min( 0.01 ).max( 20 ).step( 0.01 ).onChange( () => gtaoPass.updatePdMaterial( pdParameters ) );
+			gui.add( pdParameters, 'radius' ).min( 0 ).max( 32 ).step( 1 ).onChange( () => gtaoPass.updatePdMaterial( pdParameters ) );
+			gui.add( pdParameters, 'radiusExponent' ).min( 0.1 ).max( 4. ).step( 0.1 ).onChange( () => gtaoPass.updatePdMaterial( pdParameters ) );
+			gui.add( pdParameters, 'rings' ).min( 1 ).max( 16 ).step( 0.125 ).onChange( () => gtaoPass.updatePdMaterial( pdParameters ) );
+			gui.add( pdParameters, 'samples' ).min( 2 ).max( 32 ).step( 1 ).onChange( () => gtaoPass.updatePdMaterial( pdParameters ) );
+			
 			window.addEventListener( 'resize', onWindowResize );
 			window.addEventListener( 'resize', onWindowResize );
 
 
 			function onWindowResize() {
 			function onWindowResize() {
@@ -184,7 +200,11 @@
 
 
 				const delta = clock.getDelta();
 				const delta = clock.getDelta();
 
 
-				mixer.update( delta );
+				if ( mixer ) {
+
+					mixer.update( delta );
+
+				}
 
 
 				controls.update();
 				controls.update();
 
 
@@ -194,6 +214,8 @@
 
 
 			}
 			}
 
 
+			animate();
+
 		</script>
 		</script>
 	</body>
 	</body>
 </html>
 </html>