Переглянути джерело

Add SSRrPass (screen space refraction) (#21420)

* SSRPassCorrectReflectorDepth

* Fix? or bypass? large groundReflector edge viewing problem.

* Renaming & some clean up.

* SSRr init copy from SSR

* ok: rename SSR to SSRr & remove groundReflector

* clean up

* clean: blur

* a

* a

* pok

* debug_effect

* pok

* pok

* doing

* doing

* add gui ior

* ok

* screenshot

* bugfix: Correct transparent object z-depth occlusion.

* add vconsole

* Add specular.

* Generalize & Clean up.

* Click toggle transparent.

* Rename: specularRenderTarget.
Fix: scene.background.
MouseMove Check: Zero is sufficient.

* Correct IOR.

* Fix: Tiny gaps display error.
Rename: tSpecular.

* Clean up.

* Rename: Metalness to Refractive.
Add: Specular togglable.

* Make `defines` all uppercase.

* Add `Specular` OUTPUT.

* BugFix: Corrent `tDepthSelects`.
Add: `DepthSelects` OUTPUT.

* Rename: `normal` to `normalSelects`.

* Let `away` consider `surfDist`.

* Setting: `maxDistance` configurable.

* Use `pointToLineDistance`.

* All use `NearestFilter`.

* Add: `infiniteThick`.

* Increase `maxDistance`.

* Add setting: fillHole.
Vis 4 роки тому
батько
коміт
cb9f8d0e5d

+ 1 - 0
examples/files.json

@@ -262,6 +262,7 @@
 		"webgl_postprocessing_sobel",
 		"webgl_postprocessing_ssao",
 		"webgl_postprocessing_ssr",
+		"webgl_postprocessing_ssrr",
 		"webgl_postprocessing_taa",
 		"webgl_postprocessing_unreal_bloom",
 		"webgl_postprocessing_unreal_bloom_selective"

+ 561 - 0
examples/jsm/postprocessing/SSRrPass.js

@@ -0,0 +1,561 @@
+import {
+	AddEquation,
+	Color,
+	NormalBlending,
+	DepthTexture,
+	SrcAlphaFactor,
+	OneMinusSrcAlphaFactor,
+	MeshNormalMaterial,
+	MeshBasicMaterial,
+	NearestFilter,
+	NoBlending,
+	RGBAFormat,
+	ShaderMaterial,
+	UniformsUtils,
+	UnsignedShortType,
+	WebGLRenderTarget,
+	HalfFloatType,
+	MeshStandardMaterial
+} from '../../../build/three.module.js';
+import { Pass } from '../postprocessing/Pass.js';
+import { SSRrShader } from '../shaders/SSRrShader.js';
+import { SSRrDepthShader } from '../shaders/SSRrShader.js';
+import { CopyShader } from '../shaders/CopyShader.js';
+
+var SSRrPass = function ( { renderer, scene, camera, width, height, selects, encoding, morphTargets = false } ) {
+
+	Pass.call( this );
+
+	this.width = ( width !== undefined ) ? width : 512;
+	this.height = ( height !== undefined ) ? height : 512;
+
+	this.clear = true;
+
+	this.renderer = renderer;
+	this.scene = scene;
+	this.camera = camera;
+
+	this.output = 0;
+	// this.output = 1;
+
+	this.ior = SSRrShader.uniforms.ior.value;
+	this.maxDistance = SSRrShader.uniforms.maxDistance.value;
+	this.surfDist = SSRrShader.uniforms.surfDist.value;
+
+	this.encoding = encoding;
+
+	this.tempColor = new Color();
+
+	this.selects = selects;
+
+	this._specular = SSRrShader.defines.SPECULAR;
+	Object.defineProperty( this, 'specular', {
+		get() {
+
+			return this._specular;
+
+		},
+		set( val ) {
+
+			if ( this._specular === val ) return;
+			this._specular = val;
+			this.ssrrMaterial.defines.SPECULAR = val;
+			this.ssrrMaterial.needsUpdate = true;
+
+		}
+	} );
+
+	this._fillHole = SSRrShader.defines.FILL_HOLE;
+	Object.defineProperty( this, 'fillHole', {
+		get() {
+
+			return this._fillHole;
+
+		},
+		set( val ) {
+
+			if ( this._fillHole === val ) return;
+			this._fillHole = val;
+			this.ssrrMaterial.defines.FILL_HOLE = val;
+			this.ssrrMaterial.needsUpdate = true;
+
+		}
+	} );
+
+	this._infiniteThick = SSRrShader.defines.INFINITE_THICK;
+	Object.defineProperty( this, 'infiniteThick', {
+		get() {
+
+			return this._infiniteThick;
+
+		},
+		set( val ) {
+
+			if ( this._infiniteThick === val ) return;
+			this._infiniteThick = val;
+			this.ssrrMaterial.defines.INFINITE_THICK = val;
+			this.ssrrMaterial.needsUpdate = true;
+
+		}
+	} );
+
+	// beauty render target with depth buffer
+
+	var depthTexture = new DepthTexture();
+	depthTexture.type = UnsignedShortType;
+	depthTexture.minFilter = NearestFilter;
+	depthTexture.magFilter = NearestFilter;
+
+	this.beautyRenderTarget = new WebGLRenderTarget( this.width, this.height, {
+		minFilter: NearestFilter,
+		magFilter: NearestFilter,
+		format: RGBAFormat,
+		depthTexture: depthTexture,
+		depthBuffer: true
+	} );
+
+	this.specularRenderTarget = new WebGLRenderTarget( this.width, this.height, { // TODO: Can merge with refractiveRenderTarget?
+		minFilter: NearestFilter,
+		magFilter: NearestFilter,
+		format: RGBAFormat,
+	} );
+
+	// normalSelects render target
+
+	var depthTextureSelects = new DepthTexture();
+	depthTextureSelects.type = UnsignedShortType;
+	depthTextureSelects.minFilter = NearestFilter;
+	depthTextureSelects.magFilter = NearestFilter;
+
+	this.normalSelectsRenderTarget = new WebGLRenderTarget( this.width, this.height, {
+		minFilter: NearestFilter,
+		magFilter: NearestFilter,
+		format: RGBAFormat,
+		type: HalfFloatType,
+		depthTexture: depthTextureSelects,
+		depthBuffer: true
+	} );
+
+	// refractive render target
+
+	this.refractiveRenderTarget = new WebGLRenderTarget( this.width, this.height, {
+		minFilter: NearestFilter,
+		magFilter: NearestFilter,
+		format: RGBAFormat
+	} );
+
+	// ssrr render target
+
+	this.ssrrRenderTarget = new WebGLRenderTarget( this.width, this.height, {
+		minFilter: NearestFilter,
+		magFilter: NearestFilter,
+		format: RGBAFormat
+	} );
+
+	// ssrr material
+
+	if ( SSRrShader === undefined ) {
+
+		console.error( 'THREE.SSRrPass: The pass relies on SSRrShader.' );
+
+	}
+
+	this.ssrrMaterial = new ShaderMaterial( {
+		defines: Object.assign( {}, SSRrShader.defines, {
+			MAX_STEP: Math.sqrt( this.width * this.width + this.height * this.height )
+		} ),
+		uniforms: UniformsUtils.clone( SSRrShader.uniforms ),
+		vertexShader: SSRrShader.vertexShader,
+		fragmentShader: SSRrShader.fragmentShader,
+		blending: NoBlending
+	} );
+
+	this.ssrrMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture;
+	this.ssrrMaterial.uniforms[ 'tSpecular' ].value = this.specularRenderTarget.texture;
+	this.ssrrMaterial.uniforms[ 'tNormalSelects' ].value = this.normalSelectsRenderTarget.texture;
+	this.ssrrMaterial.needsUpdate = true;
+	this.ssrrMaterial.uniforms[ 'tRefractive' ].value = this.refractiveRenderTarget.texture;
+	this.ssrrMaterial.uniforms[ 'tDepth' ].value = this.beautyRenderTarget.depthTexture;
+	this.ssrrMaterial.uniforms[ 'tDepthSelects' ].value = this.normalSelectsRenderTarget.depthTexture;
+	this.ssrrMaterial.uniforms[ 'cameraNear' ].value = this.camera.near;
+	this.ssrrMaterial.uniforms[ 'cameraFar' ].value = this.camera.far;
+	this.ssrrMaterial.uniforms[ 'resolution' ].value.set( this.width, this.height );
+	this.ssrrMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix );
+	this.ssrrMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse );
+
+	// normal material
+
+	this.normalMaterial = new MeshNormalMaterial( { morphTargets } );
+	this.normalMaterial.blending = NoBlending;
+
+	// refractiveOn material
+
+	this.refractiveOnMaterial = new MeshBasicMaterial( {
+		color: 'white'
+	} );
+
+	// refractiveOff material
+
+	this.refractiveOffMaterial = new MeshBasicMaterial( {
+		color: 'black'
+	});
+
+	// specular material
+	this.specularMaterial = new MeshStandardMaterial({
+		color: 'black',
+		metalness: 0,
+		roughness: .2,
+	});
+
+	// material for rendering the depth
+
+	this.depthRenderMaterial = new ShaderMaterial( {
+		defines: Object.assign( {}, SSRrDepthShader.defines ),
+		uniforms: UniformsUtils.clone( SSRrDepthShader.uniforms ),
+		vertexShader: SSRrDepthShader.vertexShader,
+		fragmentShader: SSRrDepthShader.fragmentShader,
+		blending: NoBlending
+	} );
+	this.depthRenderMaterial.uniforms[ 'tDepth' ].value = this.beautyRenderTarget.depthTexture;
+	this.depthRenderMaterial.uniforms[ 'cameraNear' ].value = this.camera.near;
+	this.depthRenderMaterial.uniforms[ 'cameraFar' ].value = this.camera.far;
+
+	// material for rendering the content of a render target
+
+	this.copyMaterial = new ShaderMaterial( {
+		uniforms: UniformsUtils.clone( CopyShader.uniforms ),
+		vertexShader: CopyShader.vertexShader,
+		fragmentShader: CopyShader.fragmentShader,
+		transparent: true,
+		depthTest: false,
+		depthWrite: false,
+		blendSrc: SrcAlphaFactor,
+		blendDst: OneMinusSrcAlphaFactor,
+		blendEquation: AddEquation,
+		blendSrcAlpha: SrcAlphaFactor,
+		blendDstAlpha: OneMinusSrcAlphaFactor,
+		blendEquationAlpha: AddEquation,
+		// premultipliedAlpha:true,
+	} );
+
+	this.fsQuad = new Pass.FullScreenQuad( null );
+
+	this.originalClearColor = new Color();
+
+};
+
+SSRrPass.prototype = Object.assign( Object.create( Pass.prototype ), {
+
+	constructor: SSRrPass,
+
+	dispose: function () {
+
+		// dispose render targets
+
+		this.beautyRenderTarget.dispose();
+		this.specularRenderTarget.dispose();
+		this.normalSelectsRenderTarget.dispose();
+		this.refractiveRenderTarget.dispose();
+		this.ssrrRenderTarget.dispose();
+
+		// dispose materials
+
+		this.normalMaterial.dispose();
+		this.refractiveOnMaterial.dispose();
+		this.refractiveOffMaterial.dispose();
+		this.copyMaterial.dispose();
+		this.depthRenderMaterial.dispose();
+
+		// dipsose full screen quad
+
+		this.fsQuad.dispose();
+
+	},
+
+	render: function ( renderer, writeBuffer /*, readBuffer, deltaTime, maskActive */ ) {
+
+		// render beauty and depth
+
+		if ( this.encoding ) this.beautyRenderTarget.texture.encoding = this.encoding;
+		renderer.setRenderTarget( this.beautyRenderTarget );
+		renderer.clear();
+		this.scene.children.forEach(child => {
+			if (this.selects.includes(child)) {
+				child.visible = false
+			} else {
+				child.visible = true
+			}
+		})
+		renderer.render(this.scene, this.camera);
+
+		renderer.setRenderTarget( this.specularRenderTarget );
+		renderer.clear();
+		this.scene.children.forEach(child => {
+			if (this.selects.includes(child)) {
+				child.visible=true
+				child._SSRrPassBackupMaterial = child.material
+				child.material=this.specularMaterial
+			} else if(!child.isLight) {
+				child.visible = false
+			}
+		})
+		renderer.render(this.scene, this.camera);
+		this.scene.children.forEach(child => {
+			if (this.selects.includes(child)) {
+				child.material=child._SSRrPassBackupMaterial
+			}
+		})
+
+
+		// render normalSelectss
+
+		this.scene.children.forEach(child => {
+			if (this.selects.includes(child)) {
+				child.visible=true
+			} else{
+				child.visible = false
+			}
+		})
+
+		this.renderOverride(renderer, this.normalMaterial, this.normalSelectsRenderTarget, 0, 0);
+
+		this.renderRefractive( renderer, this.refractiveOnMaterial, this.refractiveRenderTarget, 0, 0 );
+
+		// render SSRr
+
+		this.ssrrMaterial.uniforms[ 'ior' ].value = this.ior;
+		this.ssrrMaterial.uniforms[ 'maxDistance' ].value = this.maxDistance;
+		this.ssrrMaterial.uniforms[ 'surfDist' ].value = this.surfDist;
+		this.ssrrMaterial.uniforms[ 'tSpecular' ].value = this.specularRenderTarget.texture;
+		this.renderPass( renderer, this.ssrrMaterial, this.ssrrRenderTarget );
+
+		// output result to screen
+
+		switch ( this.output ) {
+
+			case SSRrPass.OUTPUT.Default:
+
+
+				this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture;
+				this.copyMaterial.blending = NoBlending;
+				this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer );
+
+				this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssrrRenderTarget.texture;
+				this.copyMaterial.blending = NormalBlending;
+				this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer );
+
+				break;
+			case SSRrPass.OUTPUT.SSRr:
+
+				this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.ssrrRenderTarget.texture;
+				this.copyMaterial.blending = NoBlending;
+				this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer );
+
+				break;
+
+			case SSRrPass.OUTPUT.Beauty:
+
+				this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.beautyRenderTarget.texture;
+				this.copyMaterial.blending = NoBlending;
+				this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer );
+
+				break;
+
+			case SSRrPass.OUTPUT.Depth:
+
+				this.depthRenderMaterial.uniforms[ 'tDepth' ].value = this.beautyRenderTarget.depthTexture;
+				this.renderPass( renderer, this.depthRenderMaterial, this.renderToScreen ? null : writeBuffer );
+
+				break;
+
+			case SSRrPass.OUTPUT.DepthSelects:
+
+				this.depthRenderMaterial.uniforms[ 'tDepth' ].value = this.normalSelectsRenderTarget.depthTexture;
+				this.renderPass( renderer, this.depthRenderMaterial, this.renderToScreen ? null : writeBuffer );
+
+				break;
+
+			case SSRrPass.OUTPUT.NormalSelects:
+
+				this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.normalSelectsRenderTarget.texture;
+				this.copyMaterial.blending = NoBlending;
+				this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer );
+
+				break;
+
+			case SSRrPass.OUTPUT.Refractive:
+
+				this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.refractiveRenderTarget.texture;
+				this.copyMaterial.blending = NoBlending;
+				this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer );
+
+				break;
+
+			case SSRrPass.OUTPUT.Specular:
+
+				this.copyMaterial.uniforms[ 'tDiffuse' ].value = this.specularRenderTarget.texture;
+				this.copyMaterial.blending = NoBlending;
+				this.renderPass( renderer, this.copyMaterial, this.renderToScreen ? null : writeBuffer );
+
+				break;
+
+			default:
+				console.warn( 'THREE.SSRrPass: Unknown output type.' );
+
+		}
+
+	},
+
+	renderPass: function ( renderer, passMaterial, renderTarget, clearColor, clearAlpha ) {
+
+		// save original state
+		this.originalClearColor.copy( renderer.getClearColor( this.tempColor ) );
+		var originalClearAlpha = renderer.getClearAlpha( this.tempColor );
+		var originalAutoClear = renderer.autoClear;
+
+		renderer.setRenderTarget( renderTarget );
+
+		// setup pass state
+		renderer.autoClear = false;
+		if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) {
+
+			renderer.setClearColor( clearColor );
+			renderer.setClearAlpha( clearAlpha || 0.0 );
+			renderer.clear();
+
+		}
+
+		this.fsQuad.material = passMaterial;
+		this.fsQuad.render( renderer );
+
+		// restore original state
+		renderer.autoClear = originalAutoClear;
+		renderer.setClearColor( this.originalClearColor );
+		renderer.setClearAlpha( originalClearAlpha );
+
+	},
+
+	renderOverride: function ( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) {
+
+		this.originalClearColor.copy( renderer.getClearColor( this.tempColor ) );
+		var originalClearAlpha = renderer.getClearAlpha( this.tempColor );
+		var originalAutoClear = renderer.autoClear;
+
+		renderer.setRenderTarget( renderTarget );
+		renderer.autoClear = false;
+
+		clearColor = overrideMaterial.clearColor || clearColor;
+		clearAlpha = overrideMaterial.clearAlpha || clearAlpha;
+
+		if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) {
+
+			renderer.setClearColor( clearColor );
+			renderer.setClearAlpha( clearAlpha || 0.0 );
+			renderer.clear();
+
+		}
+
+		this.scene.overrideMaterial = overrideMaterial;
+		renderer.render( this.scene, this.camera );
+		this.scene.overrideMaterial = null;
+
+		// restore original state
+
+		renderer.autoClear = originalAutoClear;
+		renderer.setClearColor( this.originalClearColor );
+		renderer.setClearAlpha( originalClearAlpha );
+
+	},
+
+
+	renderRefractive: function ( renderer, overrideMaterial, renderTarget, clearColor, clearAlpha ) {
+
+		this.originalClearColor.copy( renderer.getClearColor( this.tempColor ) );
+		var originalClearAlpha = renderer.getClearAlpha( this.tempColor );
+		var originalAutoClear = renderer.autoClear;
+
+		renderer.setRenderTarget( renderTarget );
+		renderer.autoClear = false;
+
+		clearColor = overrideMaterial.clearColor || clearColor;
+		clearAlpha = overrideMaterial.clearAlpha || clearAlpha;
+
+		if ( ( clearColor !== undefined ) && ( clearColor !== null ) ) {
+
+			renderer.setClearColor( clearColor );
+			renderer.setClearAlpha( clearAlpha || 0.0 );
+			renderer.clear();
+
+		}
+
+		this.scene.children.forEach(child => {
+			child.visible=true
+		})
+		this.scene.traverse( child => {
+
+			child._SSRrPassBackupMaterial = child.material;
+			if ( this.selects.includes( child ) ) {
+
+				child.material = this.refractiveOnMaterial;
+
+			} else {
+
+				child.material = this.refractiveOffMaterial;
+
+			}
+
+		});
+		this.scene._SSRrPassBackupBackground=this.scene.background
+		this.scene.background=null
+		this.scene._SSRrPassBackupFog=this.scene.fog
+		this.scene.fog=null
+		renderer.render(this.scene, this.camera);
+		this.scene.fog=this.scene._SSRrPassBackupFog
+		this.scene.background=this.scene._SSRrPassBackupBackground
+		this.scene.traverse( child => {
+
+			child.material = child._SSRrPassBackupMaterial;
+
+		} );
+
+		// restore original state
+
+		renderer.autoClear = originalAutoClear;
+		renderer.setClearColor( this.originalClearColor );
+		renderer.setClearAlpha( originalClearAlpha );
+
+	},
+
+	setSize: function ( width, height ) {
+
+		this.width = width;
+		this.height = height;
+
+		this.ssrrMaterial.defines.MAX_STEP = Math.sqrt( width * width + height * height );
+		this.ssrrMaterial.needsUpdate = true;
+		this.beautyRenderTarget.setSize( width, height );
+		this.specularRenderTarget.setSize( width, height );
+		this.ssrrRenderTarget.setSize( width, height );
+		this.normalSelectsRenderTarget.setSize( width, height );
+		this.refractiveRenderTarget.setSize( width, height );
+
+		this.ssrrMaterial.uniforms[ 'resolution' ].value.set( width, height );
+		this.ssrrMaterial.uniforms[ 'cameraProjectionMatrix' ].value.copy( this.camera.projectionMatrix );
+		this.ssrrMaterial.uniforms[ 'cameraInverseProjectionMatrix' ].value.copy( this.camera.projectionMatrixInverse );
+
+	},
+
+} );
+
+SSRrPass.OUTPUT = {
+	'Default': 0,
+	'SSRr': 1,
+	'Beauty': 3,
+	'Depth': 4,
+	'DepthSelects': 9,
+	'NormalSelects': 5,
+	'Refractive': 7,
+	'Specular': 8,
+};
+
+export { SSRrPass };

+ 307 - 0
examples/jsm/shaders/SSRrShader.js

@@ -0,0 +1,307 @@
+import {
+  Matrix4,
+  Vector2
+} from "../../../build/three.module.js";
+
+var SSRrShader = {
+
+  defines: {
+		MAX_STEP: 0,
+		PERSPECTIVE_CAMERA: true,
+		SPECULAR: true,
+    FILL_HOLE: true,
+    INFINITE_THICK: false,
+  },
+
+  uniforms: {
+
+    "tDiffuse": { value: null },
+    "tSpecular": { value: null },
+    "tNormalSelects": { value: null },
+    "tRefractive": { value: null },
+    "tDepth": { value: null },
+    "tDepthSelects": { value: null },
+    "cameraNear": { value: null },
+    "cameraFar": { value: null },
+    "resolution": { value: new Vector2() },
+    "cameraProjectionMatrix": { value: new Matrix4() },
+    "cameraInverseProjectionMatrix": { value: new Matrix4() },
+    "ior": { value: 1.03 },
+    "cameraRange": { value: 0 },
+    "maxDistance": { value: 180 },
+    "surfDist": { value: .007 },
+
+  },
+
+  vertexShader: /* glsl */`
+
+    varying vec2 vUv;
+
+    void main() {
+
+			vUv = uv;
+
+			gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
+
+    }
+
+  `,
+
+  fragmentShader: /* glsl */`
+		// precision highp float;
+		precision highp sampler2D;
+		varying vec2 vUv;
+		uniform sampler2D tDepth;
+		uniform sampler2D tDepthSelects;
+		uniform sampler2D tNormalSelects;
+		uniform sampler2D tRefractive;
+		uniform sampler2D tDiffuse;
+		uniform sampler2D tSpecular;
+		uniform float cameraRange;
+		uniform vec2 resolution;
+		uniform float cameraNear;
+		uniform float cameraFar;
+		uniform float ior;
+		uniform mat4 cameraProjectionMatrix;
+		uniform mat4 cameraInverseProjectionMatrix;
+		uniform float maxDistance;
+		uniform float surfDist;
+		#include <packing>
+		float pointToLineDistance(vec3 x0, vec3 x1, vec3 x2) {
+			//x0: point, x1: linePointA, x2: linePointB
+			//https://mathworld.wolfram.com/Point-LineDistance3-Dimensional.html
+			return length(cross(x0-x1,x0-x2))/length(x2-x1);
+		}
+		float pointPlaneDistance(vec3 point,vec3 planePoint,vec3 planeNormal){
+			// https://mathworld.wolfram.com/Point-PlaneDistance.html
+			//// https://en.wikipedia.org/wiki/Plane_(geometry)
+			//// http://paulbourke.net/geometry/pointlineplane/
+			float a=planeNormal.x,b=planeNormal.y,c=planeNormal.z;
+			float x0=point.x,y0=point.y,z0=point.z;
+			float x=planePoint.x,y=planePoint.y,z=planePoint.z;
+			float d=-(a*x+b*y+c*z);
+			float distance=(a*x0+b*y0+c*z0+d)/sqrt(a*a+b*b+c*c);
+			return distance;
+		}
+		float getDepth( const in vec2 uv ) {
+			return texture2D( tDepth, uv ).x;
+		}
+		float getDepthSelects( const in vec2 uv ) {
+			return texture2D( tDepthSelects, uv ).x;
+		}
+		float getViewZ( const in float depth ) {
+			#ifdef PERSPECTIVE_CAMERA
+				return perspectiveDepthToViewZ( depth, cameraNear, cameraFar );
+			#else
+				return orthographicDepthToViewZ( depth, cameraNear, cameraFar );
+			#endif
+		}
+		vec3 getViewPosition( const in vec2 uv, const in float depth/*clip space*/, const in float clipW ) {
+			vec4 clipPosition = vec4( ( vec3( uv, depth ) - 0.5 ) * 2.0, 1.0 );//ndc
+			clipPosition *= clipW; //clip
+			return ( cameraInverseProjectionMatrix * clipPosition ).xyz;//view
+		}
+		vec3 getViewNormalSelects( const in vec2 uv ) {
+			return unpackRGBToNormal( texture2D( tNormalSelects, uv ).xyz );
+		}
+		vec2 viewPositionToXY(vec3 viewPosition){
+			vec2 xy;
+			vec4 clip=cameraProjectionMatrix*vec4(viewPosition,1);
+			xy=clip.xy;//clip
+			float clipW=clip.w;
+			xy/=clipW;//NDC
+			xy=(xy+1.)/2.;//uv
+			xy*=resolution;//screen
+			return xy;
+		}
+		void setResultColor(vec2 uv){
+			vec4 refractColor=texture2D(tDiffuse,uv);
+			#ifdef SPECULAR
+				vec4 specularColor=texture2D(tSpecular,vUv);
+				gl_FragColor.xyz=mix(refractColor.xyz,vec3(1),specularColor.r);
+				// gl_FragColor.xyz=refractColor.xyz*(1.+specularColor.r*3.);
+			#else
+				gl_FragColor.xyz=refractColor.xyz;
+			#endif
+			gl_FragColor.a=1.;
+
+		}
+		void main(){
+			if(ior==1.) return; // Adding this line may have better performance, but more importantly, it can avoid display errors at the very edges of the model when IOR is equal to 1.
+
+			float refractive=texture2D(tRefractive,vUv).r;
+			if(refractive<=0.) return;
+
+			// gl_FragColor=vec4(0,0,.5,1);return;
+			vec3 viewNormalSelects=getViewNormalSelects( vUv );
+			// gl_FragColor=vec4(viewNormalSelects,1);return;
+
+			// if(viewNormalSelects.x<=0.&&viewNormalSelects.y<=0.&&viewNormalSelects.z<=0.) return;
+
+			float depth = getDepthSelects( vUv );
+			float viewZ = getViewZ( depth );
+			// if(-viewZ>=cameraFar) return;
+
+			float clipW = cameraProjectionMatrix[2][3] * viewZ+cameraProjectionMatrix[3][3];
+			vec3 viewPosition=getViewPosition( vUv, depth, clipW );
+
+			vec2 d0=gl_FragCoord.xy;
+			vec2 d1;
+
+			#ifdef PERSPECTIVE_CAMERA
+				vec3 viewIncidentDir=normalize(viewPosition);
+			#else
+				vec3 viewIncidentDir=vec3(0,0,-1);
+			#endif
+
+			vec3 viewRefractDir=refract(viewIncidentDir,viewNormalSelects,1./ior);
+			// https://www.khronos.org/registry/OpenGL-Refpages/gl4/html/refract.xhtml
+
+			vec3 d1viewPosition=viewPosition+viewRefractDir*maxDistance;
+			#ifdef PERSPECTIVE_CAMERA
+				if(d1viewPosition.z>-cameraNear){
+					//https://tutorial.math.lamar.edu/Classes/CalcIII/EqnsOfLines.aspx
+					float t=(-cameraNear-viewPosition.z)/viewRefractDir.z;
+					d1viewPosition=viewPosition+viewRefractDir*t;
+				}
+			#endif
+			d1=viewPositionToXY(d1viewPosition);
+
+			float totalLen=length(d1-d0);
+			float xLen=d1.x-d0.x;
+			float yLen=d1.y-d0.y;
+			float totalStep=max(abs(xLen),abs(yLen));
+			float xSpan=xLen/totalStep;
+			float ySpan=yLen/totalStep;
+			#ifdef FILL_HOLE
+				bool isRough=false;
+				vec2 uvRough;
+			#endif
+			for(float i=0.;i<float(MAX_STEP);i++){
+				if(i>=totalStep) break;
+				vec2 xy=vec2(d0.x+i*xSpan,d0.y+i*ySpan);
+				if(xy.x<0.||xy.x>resolution.x||xy.y<0.||xy.y>resolution.y) break;
+				float s=length(xy-d0)/totalLen;
+				vec2 uv=xy/resolution;
+
+				float d = getDepth(uv);
+				float vZ = getViewZ( d );
+				float cW = cameraProjectionMatrix[2][3] * vZ+cameraProjectionMatrix[3][3];
+				vec3 vP=getViewPosition( uv, d, cW );
+
+				#ifdef PERSPECTIVE_CAMERA
+					// https://www.comp.nus.edu.sg/~lowkl/publications/lowk_persp_interp_techrep.pdf
+					float recipVPZ=1./viewPosition.z;
+					float viewRefractRayZ=1./(recipVPZ+s*(1./d1viewPosition.z-recipVPZ));
+					float sD=surfDist*cW;
+				#else
+					float viewRefractRayZ=viewPosition.z+s*(d1viewPosition.z-viewPosition.z);
+					float sD=surfDist;
+				#endif
+
+				#ifdef FILL_HOLE // TODO: May can improve performance by check if INFINITE_THICK too.
+					if(viewRefractRayZ<=vZ){
+						if(!isRough){
+							uvRough=uv;
+							isRough=true;
+						}
+					}
+				#endif
+
+				bool hit;
+				#ifdef INFINITE_THICK
+					hit=viewRefractRayZ<=vZ;
+				#else
+					if(viewRefractRayZ-sD>vZ) continue;
+					float away=pointToLineDistance(vP,viewPosition,d1viewPosition);
+					hit=away<=sD;
+				#endif
+				if(hit){
+					setResultColor(uv);
+					return;
+				}
+			}
+
+			#ifdef FILL_HOLE
+				if(isRough){
+					setResultColor(uvRough);
+				}
+				// else{
+				// 	gl_FragColor=texture2D(tDiffuse,vUv);//For afterward add color mix feature.
+				// }
+			#else
+				// gl_FragColor=texture2D(tDiffuse,vUv);//For afterward add color mix feature.
+			#endif
+		}
+	`
+
+};
+
+var SSRrDepthShader = {
+
+  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 uv ) {
+
+			#if PERSPECTIVE_CAMERA == 1
+
+				float fragCoordZ = texture2D( tDepth, uv ).x;
+				float viewZ = perspectiveDepthToViewZ( fragCoordZ, cameraNear, cameraFar );
+				return viewZToOrthographicDepth( viewZ, cameraNear, cameraFar );
+
+			#else
+
+				return texture2D( tDepth, uv ).x;
+
+			#endif
+
+		}
+
+    void main() {
+
+    	float depth = getLinearDepth( vUv );
+			float d = 1.0 - depth;
+			// d=(d-.999)*1000.;
+    	gl_FragColor = vec4( vec3( d ), 1.0 );
+
+    }
+
+  `
+
+};
+
+export { SSRrShader, SSRrDepthShader };

BIN
examples/screenshots/webgl_postprocessing_ssrr.jpg


+ 306 - 0
examples/webgl_postprocessing_ssrr.html

@@ -0,0 +1,306 @@
+<!DOCTYPE html>
+<html lang="en">
+
+<head>
+
+	<head>
+		<title>three.js webgl - postprocessing - Screen Space Refraction</title>
+		<meta charset="utf-8">
+		<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">
+	</head>
+
+<body>
+	<div id="container"></div>
+	<div id="info">
+		<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> -
+		SSRrPass demo by <a href="https://github.com/gonnavis" target="_blank">Vis</a>.<br />
+		click object to toggle transparent<br/>
+	</div>
+	
+	<script type="module">
+		import * as THREE from '../build/three.module.js';
+
+		import Stats from './jsm/libs/stats.module.js';
+
+		import { OrbitControls } from './jsm/controls/OrbitControls.js';
+
+		import { GUI } from './jsm/libs/dat.gui.module.js';
+		import { EffectComposer } from './jsm/postprocessing/EffectComposer.js';
+		import { SSRrPass } from './jsm/postprocessing/SSRrPass.js';
+
+		import { DRACOLoader } from './jsm/loaders/DRACOLoader.js';
+
+		const params = {
+			enableSSRr: true,
+			autoRotate: true,
+		};
+		let composer;
+		let ssrrPass;
+		let gui;
+		let stats;
+		let controls;
+		let camera, scene, renderer;
+		const objects = [];
+		const selects = [];
+		const raycaster = new THREE.Raycaster();
+		const mouseDown = new THREE.Vector2();
+		const mouse = new THREE.Vector2();
+
+		const container = document.querySelector('#container');
+
+		// Configure and create Draco decoder.
+		const dracoLoader = new DRACOLoader();
+		dracoLoader.setDecoderPath('js/libs/draco/');
+		dracoLoader.setDecoderConfig({ type: 'js' });
+
+		init();
+		animate();
+
+		function init() {
+
+			camera = new THREE.PerspectiveCamera(35, window.innerWidth / window.innerHeight, 0.1, 15);
+			camera.position.set(0.13271600513224902, 0.3489546826045913, 0.43921296427927076);
+
+			scene = new THREE.Scene();
+			scene.background = new THREE.Color(0x443333);
+			scene.fog = new THREE.Fog(0x443333, 1, 4);
+
+			// Ground
+			let map=new THREE.TextureLoader().load('./textures/uv_grid_opengl.jpg')
+			map.wrapS = THREE.RepeatWrapping;
+			map.wrapT = THREE.RepeatWrapping;
+			map.repeat.set(20,20)
+			const plane = new THREE.Mesh(
+				new THREE.PlaneGeometry(8, 8),
+				new THREE.MeshPhongMaterial({ 
+					color: 0x999999, 
+					specular: 0x101010,
+					map,
+				})
+			);
+			plane.rotation.x = - Math.PI / 2;
+			plane.position.y = - 0.0001;
+			// plane.receiveShadow = true;
+			scene.add(plane);
+			plane.name='plane'
+
+			// Lights
+			const hemiLight = new THREE.HemisphereLight(0x443333, 0x111122);
+			hemiLight.name='hemiLight'
+			scene.add(hemiLight);
+
+			const spotLight = new THREE.SpotLight();
+			spotLight.name='spotLight'
+			spotLight.angle = Math.PI / 16;
+			spotLight.penumbra = 0.5;
+			// spotLight.castShadow = true;
+			spotLight.position.set(- 1, 1, 1);
+			scene.add(spotLight);
+
+			dracoLoader.load('models/draco/bunny.drc', function (geometry) {
+
+				geometry.computeVertexNormals();
+
+				const material = new THREE.MeshStandardMaterial({ color: 0x606060 });
+				const mesh = new THREE.Mesh(geometry, material);
+				mesh.position.y = - 0.0365;
+				mesh.name='bunny'
+				scene.add(mesh);
+				objects.push(mesh);
+				selects.push(mesh);
+
+				// Release decoder resources.
+				dracoLoader.dispose();
+
+			});
+
+			let geometry, material, mesh;
+
+			geometry = new THREE.BoxBufferGeometry(.05, .05, .05);
+			material = new THREE.MeshStandardMaterial({ color: 'green' });
+			mesh = new THREE.Mesh(geometry, material);
+			mesh.position.set(- .12, .025, .015);
+			mesh.name='box'
+			scene.add(mesh);
+			objects.push( mesh );
+			selects.push( mesh );
+
+			geometry = new THREE.IcosahedronBufferGeometry(.025, 4);
+			material = new THREE.MeshStandardMaterial({ color: 'cyan' });
+			mesh = new THREE.Mesh(geometry, material);
+			mesh.position.set(- .05, .025, .08);
+			mesh.name='sphere'
+			scene.add(mesh);
+			objects.push( mesh );
+			// selects.push( mesh );
+
+			geometry = new THREE.ConeBufferGeometry(.025, .05, 64);
+			material = new THREE.MeshStandardMaterial({ color: 'yellow' });
+			mesh = new THREE.Mesh(geometry, material);
+			mesh.position.set(- .05, .025, - .055);
+			mesh.name='cone'
+			scene.add(mesh);
+			objects.push( mesh );
+			// selects.push( mesh );
+
+			// renderer
+			renderer = new THREE.WebGLRenderer({ antialias: false });
+			renderer.setSize(window.innerWidth, window.innerHeight);
+			renderer.outputEncoding = THREE.sRGBEncoding;
+			renderer.autoClear=false
+			container.appendChild(renderer.domElement);
+
+			//
+
+			controls = new OrbitControls(camera, renderer.domElement);
+			controls.enableDamping = true;
+			controls.target.set(0, 0.0635, 0);
+			controls.update();
+			controls.enabled = !params.autoRotate;
+
+			// STATS
+
+			stats = new Stats();
+			container.appendChild(stats.dom);
+
+			window.addEventListener('resize', onWindowResize, false);
+			window.addEventListener( 'pointerdown', onPointerDown, false );
+			window.addEventListener( 'pointerup', onPointerUp, false );
+
+			// composer
+
+			composer = new EffectComposer(renderer);
+			ssrrPass = new SSRrPass({
+				renderer,
+				scene,
+				camera,
+				width: innerWidth,
+				height: innerHeight,
+				encoding: THREE.sRGBEncoding,
+				selects: selects
+			});
+
+			composer.addPass(ssrrPass);
+
+			// GUI
+
+			gui = new GUI();
+			gui.add(params, 'enableSSRr').name('Enable SSRr');
+			ssrrPass.ior = 1.1;
+			gui.add(ssrrPass, 'ior').name('IOR').min(1).max(1.5).step(.0001);
+			gui.add( ssrrPass, 'fillHole' );
+			gui.add(params, 'autoRotate').onChange(() => {
+
+				controls.enabled = !params.autoRotate;
+
+			});
+
+			const folder = gui.addFolder('more settings');
+			folder.add( ssrrPass, 'specular' );
+			folder.add(ssrrPass.specularMaterial, 'metalness').min(0).max(1).step(.01);
+			folder.add(ssrrPass.specularMaterial, 'roughness').min(0).max(1).step(.01);
+			folder.add(ssrrPass, 'output', {
+				'Default': SSRrPass.OUTPUT.Default,
+				'SSRr Only': SSRrPass.OUTPUT.SSRr,
+				'Beauty': SSRrPass.OUTPUT.Beauty,
+				'Depth': SSRrPass.OUTPUT.Depth,
+				'DepthSelects': SSRrPass.OUTPUT.DepthSelects,
+				'NormalSelects': SSRrPass.OUTPUT.NormalSelects,
+				'Refractive': SSRrPass.OUTPUT.Refractive,
+				'Specular': SSRrPass.OUTPUT.Specular,
+			}).onChange(function (value) {
+
+				ssrrPass.output = parseInt(value);
+
+			});
+			ssrrPass.surfDist = 0.0015;
+			folder.add( ssrrPass, 'surfDist' ).min( 0 ).max( .005 ).step( .0001 );
+			ssrrPass.maxDistance = 50;
+			folder.add( ssrrPass, 'maxDistance' ).min( 0 ).max( 100 ).step( .001 )
+			folder.add( ssrrPass, 'infiniteThick' );
+			// folder.open()
+			// gui.close()
+
+		}
+
+		function onPointerDown( event ) {
+			mouseDown.x = ( event.clientX / window.innerWidth ) * 2 - 1;
+			mouseDown.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
+		}
+		function onPointerUp( event ) {
+
+			// calculate mouse position in normalized device coordinates
+			// (-1 to +1) for both components
+
+			mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
+			mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
+
+			if(mouseDown.sub(mouse).length()>0) return
+
+
+			raycaster.setFromCamera( mouse, camera );
+			const intersect = raycaster.intersectObjects( objects )[0];
+			if(intersect){
+				let index=selects.indexOf(intersect.object)
+				if(index>=0){
+					selects.splice(index,1)
+				}else{
+					selects.push(intersect.object)
+				}
+			}
+		}
+
+
+		function onWindowResize() {
+
+			camera.aspect = window.innerWidth / window.innerHeight;
+			camera.updateProjectionMatrix();
+
+			renderer.setSize(window.innerWidth, window.innerHeight);
+			composer.setSize(window.innerWidth, window.innerHeight);
+
+		}
+
+		function animate() {
+
+			requestAnimationFrame(animate);
+
+			stats.begin();
+			render();
+			stats.end();
+
+		}
+
+		function render() {
+
+			if (params.autoRotate) {
+
+				const timer = Date.now() * 0.0003;
+
+				camera.position.x = Math.sin(timer) * 0.5;
+				camera.position.y = 0.2135;
+				camera.position.z = Math.cos(timer) * 0.5;
+				camera.lookAt(0, 0.0635, 0);
+
+			} else {
+
+				controls.update();
+
+			}
+
+			if (params.enableSSRr) {
+
+				composer.render();
+
+			} else {
+
+				renderer.render(scene, camera);
+
+			}
+
+		}
+	</script>
+</body>
+
+</html>