227 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			227 lines
		
	
	
		
			6.5 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| 
								 | 
							
								import {
							 | 
						||
| 
								 | 
							
									Matrix4,
							 | 
						||
| 
								 | 
							
									Vector2,
							 | 
						||
| 
								 | 
							
									Vector3,
							 | 
						||
| 
								 | 
							
								} from 'three';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * References:
							 | 
						||
| 
								 | 
							
								 * 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 = {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									name: 'PoissonDenoiseShader',
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									defines: {
							 | 
						||
| 
								 | 
							
										'SAMPLES': 16,
							 | 
						||
| 
								 | 
							
										'SAMPLE_VECTORS': generatePdSamplePointInitializer( 16, 2, 1 ),
							 | 
						||
| 
								 | 
							
										'NORMAL_VECTOR_TYPE': 1,
							 | 
						||
| 
								 | 
							
										'DEPTH_VALUE_SOURCE': 0,
							 | 
						||
| 
								 | 
							
									},
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									uniforms: {
							 | 
						||
| 
								 | 
							
										'tDiffuse': { value: null },
							 | 
						||
| 
								 | 
							
										'tNormal': { value: null },
							 | 
						||
| 
								 | 
							
										'tDepth': { value: null },
							 | 
						||
| 
								 | 
							
										'tNoise': { value: null },
							 | 
						||
| 
								 | 
							
										'resolution': { value: new Vector2() },
							 | 
						||
| 
								 | 
							
										'cameraProjectionMatrixInverse': { value: new Matrix4() },
							 | 
						||
| 
								 | 
							
										'lumaPhi': { value: 5. },
							 | 
						||
| 
								 | 
							
										'depthPhi': { value: 5. },
							 | 
						||
| 
								 | 
							
										'normalPhi': { value: 5. },
							 | 
						||
| 
								 | 
							
										'radius': { value: 4. },
							 | 
						||
| 
								 | 
							
										'index': { value: 0 }
							 | 
						||
| 
								 | 
							
									},
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									vertexShader: /* glsl */`
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										varying vec2 vUv;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										void main() {
							 | 
						||
| 
								 | 
							
											vUv = uv;
							 | 
						||
| 
								 | 
							
											gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
							 | 
						||
| 
								 | 
							
										}`,
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									fragmentShader: /* glsl */`
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										varying vec2 vUv;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										uniform sampler2D tDiffuse;
							 | 
						||
| 
								 | 
							
										uniform sampler2D tNormal;
							 | 
						||
| 
								 | 
							
										uniform sampler2D tDepth;
							 | 
						||
| 
								 | 
							
										uniform sampler2D tNoise;
							 | 
						||
| 
								 | 
							
										uniform vec2 resolution;
							 | 
						||
| 
								 | 
							
										uniform mat4 cameraProjectionMatrixInverse;
							 | 
						||
| 
								 | 
							
										uniform float lumaPhi;
							 | 
						||
| 
								 | 
							
										uniform float depthPhi;
							 | 
						||
| 
								 | 
							
										uniform float normalPhi;
							 | 
						||
| 
								 | 
							
										uniform float radius;
							 | 
						||
| 
								 | 
							
										uniform int index;
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
										#include <common>
							 | 
						||
| 
								 | 
							
										#include <packing>
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										#ifndef SAMPLE_LUMINANCE
							 | 
						||
| 
								 | 
							
										#define SAMPLE_LUMINANCE dot(vec3(0.2125, 0.7154, 0.0721), a)
							 | 
						||
| 
								 | 
							
										#endif
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										#ifndef FRAGMENT_OUTPUT
							 | 
						||
| 
								 | 
							
										#define FRAGMENT_OUTPUT vec4(denoised, 1.)
							 | 
						||
| 
								 | 
							
										#endif
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										float getLuminance(const in vec3 a) {
							 | 
						||
| 
								 | 
							
											return SAMPLE_LUMINANCE;
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const vec3 poissonDisk[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
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										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
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										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;
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
											denoised += w * neighborColor;
							 | 
						||
| 
								 | 
							
											totalWeight += w;
							 | 
						||
| 
								 | 
							
										}
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
										void main() {
							 | 
						||
| 
								 | 
							
											float depth = getDepth(vUv.xy);	
							 | 
						||
| 
								 | 
							
											vec3 viewNormal = getViewNormal(vUv);	
							 | 
						||
| 
								 | 
							
											if (depth == 1. || dot(viewNormal, viewNormal) == 0.) {
							 | 
						||
| 
								 | 
							
												discard;
							 | 
						||
| 
								 | 
							
												return;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											vec4 texel = textureLod(tDiffuse, vUv, 0.0);
							 | 
						||
| 
								 | 
							
											vec3 center = texel.rgb;
							 | 
						||
| 
								 | 
							
											vec3 viewPos = getViewPosition(vUv, depth);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
											vec2 noiseResolution = vec2(textureSize(tNoise, 0));
							 | 
						||
| 
								 | 
							
											vec2 noiseUv = vUv * resolution / noiseResolution;
							 | 
						||
| 
								 | 
							
											vec4 noiseTexel = textureLod(tNoise, noiseUv, 0.0);
							 | 
						||
| 
								 | 
							
								      		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);
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
											float totalWeight = 1.0;
							 | 
						||
| 
								 | 
							
											vec3 denoised = texel.rgb;
							 | 
						||
| 
								 | 
							
											for (int i = 0; i < SAMPLES; i++) {
							 | 
						||
| 
								 | 
							
												vec3 sampleDir = poissonDisk[i];
							 | 
						||
| 
								 | 
							
												vec2 offset = rotationMatrix * (sampleDir.xy * (1. + sampleDir.z * (radius - 1.)) / resolution);
							 | 
						||
| 
								 | 
							
												vec2 sampleUv = vUv + offset;
							 | 
						||
| 
								 | 
							
												denoiseSample(center, viewNormal, viewPos, sampleUv, denoised, totalWeight);
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
										
							 | 
						||
| 
								 | 
							
											if (totalWeight > 0.) { 
							 | 
						||
| 
								 | 
							
												denoised /= totalWeight;
							 | 
						||
| 
								 | 
							
											}
							 | 
						||
| 
								 | 
							
											gl_FragColor = FRAGMENT_OUTPUT;
							 | 
						||
| 
								 | 
							
										}`
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								};
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function generatePdSamplePointInitializer( samples, rings, radiusExponent ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									const poissonDisk = generateDenoiseSamples(
							 | 
						||
| 
								 | 
							
										samples,
							 | 
						||
| 
								 | 
							
										rings,
							 | 
						||
| 
								 | 
							
										radiusExponent,
							 | 
						||
| 
								 | 
							
									);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									let glslCode = 'vec3[SAMPLES](';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									for ( let i = 0; i < samples; i ++ ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										const sample = poissonDisk[ i ];
							 | 
						||
| 
								 | 
							
										glslCode += `vec3(${sample.x}, ${sample.y}, ${sample.z})${( i < samples - 1 ) ? ',' : ')'}`;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									return glslCode;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								function generateDenoiseSamples( numSamples, numRings, radiusExponent ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									const samples = [];
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									for ( let i = 0; i < numSamples; i ++ ) {
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
										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 ) );
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
									return samples;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								}
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								export { generatePdSamplePointInitializer, PoissonDenoiseShader };
							 |