236 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			236 lines
		
	
	
		
			6.1 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| import {
 | |
| 	WebGLRenderTarget,
 | |
| 	MeshNormalMaterial,
 | |
| 	ShaderMaterial,
 | |
| 	Vector2,
 | |
| 	Vector4,
 | |
| 	DepthTexture,
 | |
| 	NearestFilter,
 | |
| 	HalfFloatType
 | |
| } from 'three';
 | |
| import { Pass, FullScreenQuad } from './Pass.js';
 | |
| 
 | |
| class RenderPixelatedPass extends Pass {
 | |
| 
 | |
| 	constructor( pixelSize, scene, camera, options = {} ) {
 | |
| 
 | |
| 		super();
 | |
| 
 | |
| 		this.pixelSize = pixelSize;
 | |
| 		this.resolution = new Vector2();
 | |
| 		this.renderResolution = new Vector2();
 | |
| 
 | |
| 		this.pixelatedMaterial = this.createPixelatedMaterial();
 | |
| 		this.normalMaterial = new MeshNormalMaterial();
 | |
| 
 | |
| 		this.fsQuad = new FullScreenQuad( this.pixelatedMaterial );
 | |
| 		this.scene = scene;
 | |
| 		this.camera = camera;
 | |
| 
 | |
| 		this.normalEdgeStrength = options.normalEdgeStrength || 0.3;
 | |
| 		this.depthEdgeStrength = options.depthEdgeStrength || 0.4;
 | |
| 
 | |
| 		this.beautyRenderTarget = new WebGLRenderTarget();
 | |
| 		this.beautyRenderTarget.texture.minFilter = NearestFilter;
 | |
| 		this.beautyRenderTarget.texture.magFilter = NearestFilter;
 | |
| 		this.beautyRenderTarget.texture.type = HalfFloatType;
 | |
| 		this.beautyRenderTarget.depthTexture = new DepthTexture();
 | |
| 
 | |
| 		this.normalRenderTarget = new WebGLRenderTarget();
 | |
| 		this.normalRenderTarget.texture.minFilter = NearestFilter;
 | |
| 		this.normalRenderTarget.texture.magFilter = NearestFilter;
 | |
| 		this.normalRenderTarget.texture.type = HalfFloatType;
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	dispose() {
 | |
| 
 | |
| 		this.beautyRenderTarget.dispose();
 | |
| 		this.normalRenderTarget.dispose();
 | |
| 
 | |
| 		this.pixelatedMaterial.dispose();
 | |
| 		this.normalMaterial.dispose();
 | |
| 
 | |
| 		this.fsQuad.dispose();
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	setSize( width, height ) {
 | |
| 
 | |
| 		this.resolution.set( width, height );
 | |
| 		this.renderResolution.set( ( width / this.pixelSize ) | 0, ( height / this.pixelSize ) | 0 );
 | |
| 		const { x, y } = this.renderResolution;
 | |
| 		this.beautyRenderTarget.setSize( x, y );
 | |
| 		this.normalRenderTarget.setSize( x, y );
 | |
| 		this.fsQuad.material.uniforms.resolution.value.set( x, y, 1 / x, 1 / y );
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	setPixelSize( pixelSize ) {
 | |
| 
 | |
| 		this.pixelSize = pixelSize;
 | |
| 		this.setSize( this.resolution.x, this.resolution.y );
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	render( renderer, writeBuffer ) {
 | |
| 
 | |
| 		const uniforms = this.fsQuad.material.uniforms;
 | |
| 		uniforms.normalEdgeStrength.value = this.normalEdgeStrength;
 | |
| 		uniforms.depthEdgeStrength.value = this.depthEdgeStrength;
 | |
| 
 | |
| 		renderer.setRenderTarget( this.beautyRenderTarget );
 | |
| 		renderer.render( this.scene, this.camera );
 | |
| 
 | |
| 		const overrideMaterial_old = this.scene.overrideMaterial;
 | |
| 		renderer.setRenderTarget( this.normalRenderTarget );
 | |
| 		this.scene.overrideMaterial = this.normalMaterial;
 | |
| 		renderer.render( this.scene, this.camera );
 | |
| 		this.scene.overrideMaterial = overrideMaterial_old;
 | |
| 
 | |
| 		uniforms.tDiffuse.value = this.beautyRenderTarget.texture;
 | |
| 		uniforms.tDepth.value = this.beautyRenderTarget.depthTexture;
 | |
| 		uniforms.tNormal.value = this.normalRenderTarget.texture;
 | |
| 
 | |
| 		if ( this.renderToScreen ) {
 | |
| 
 | |
| 			renderer.setRenderTarget( null );
 | |
| 
 | |
| 		} else {
 | |
| 
 | |
| 			renderer.setRenderTarget( writeBuffer );
 | |
| 
 | |
| 			if ( this.clear ) renderer.clear();
 | |
| 
 | |
| 		}
 | |
| 
 | |
| 		this.fsQuad.render( renderer );
 | |
| 
 | |
| 	}
 | |
| 
 | |
| 	createPixelatedMaterial() {
 | |
| 
 | |
| 		return new ShaderMaterial( {
 | |
| 			uniforms: {
 | |
| 				tDiffuse: { value: null },
 | |
| 				tDepth: { value: null },
 | |
| 				tNormal: { value: null },
 | |
| 				resolution: {
 | |
| 					value: new Vector4(
 | |
| 						this.renderResolution.x,
 | |
| 						this.renderResolution.y,
 | |
| 						1 / this.renderResolution.x,
 | |
| 						1 / this.renderResolution.y,
 | |
| 					)
 | |
| 				},
 | |
| 				normalEdgeStrength: { value: 0 },
 | |
| 				depthEdgeStrength: { value: 0 }
 | |
| 			},
 | |
| 			vertexShader: /* glsl */`
 | |
| 				varying vec2 vUv;
 | |
| 
 | |
| 				void main() {
 | |
| 
 | |
| 					vUv = uv;
 | |
| 					gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );
 | |
| 
 | |
| 				}
 | |
| 			`,
 | |
| 			fragmentShader: /* glsl */`
 | |
| 				uniform sampler2D tDiffuse;
 | |
| 				uniform sampler2D tDepth;
 | |
| 				uniform sampler2D tNormal;
 | |
| 				uniform vec4 resolution;
 | |
| 				uniform float normalEdgeStrength;
 | |
| 				uniform float depthEdgeStrength;
 | |
| 				varying vec2 vUv;
 | |
| 
 | |
| 				float getDepth(int x, int y) {
 | |
| 
 | |
| 					return texture2D( tDepth, vUv + vec2(x, y) * resolution.zw ).r;
 | |
| 
 | |
| 				}
 | |
| 
 | |
| 				vec3 getNormal(int x, int y) {
 | |
| 
 | |
| 					return texture2D( tNormal, vUv + vec2(x, y) * resolution.zw ).rgb * 2.0 - 1.0;
 | |
| 
 | |
| 				}
 | |
| 
 | |
| 				float depthEdgeIndicator(float depth, vec3 normal) {
 | |
| 
 | |
| 					float diff = 0.0;
 | |
| 					diff += clamp(getDepth(1, 0) - depth, 0.0, 1.0);
 | |
| 					diff += clamp(getDepth(-1, 0) - depth, 0.0, 1.0);
 | |
| 					diff += clamp(getDepth(0, 1) - depth, 0.0, 1.0);
 | |
| 					diff += clamp(getDepth(0, -1) - depth, 0.0, 1.0);
 | |
| 					return floor(smoothstep(0.01, 0.02, diff) * 2.) / 2.;
 | |
| 
 | |
| 				}
 | |
| 
 | |
| 				float neighborNormalEdgeIndicator(int x, int y, float depth, vec3 normal) {
 | |
| 
 | |
| 					float depthDiff = getDepth(x, y) - depth;
 | |
| 					vec3 neighborNormal = getNormal(x, y);
 | |
| 
 | |
| 					// Edge pixels should yield to faces who's normals are closer to the bias normal.
 | |
| 					vec3 normalEdgeBias = vec3(1., 1., 1.); // This should probably be a parameter.
 | |
| 					float normalDiff = dot(normal - neighborNormal, normalEdgeBias);
 | |
| 					float normalIndicator = clamp(smoothstep(-.01, .01, normalDiff), 0.0, 1.0);
 | |
| 
 | |
| 					// Only the shallower pixel should detect the normal edge.
 | |
| 					float depthIndicator = clamp(sign(depthDiff * .25 + .0025), 0.0, 1.0);
 | |
| 
 | |
| 					return (1.0 - dot(normal, neighborNormal)) * depthIndicator * normalIndicator;
 | |
| 
 | |
| 				}
 | |
| 
 | |
| 				float normalEdgeIndicator(float depth, vec3 normal) {
 | |
| 
 | |
| 					float indicator = 0.0;
 | |
| 
 | |
| 					indicator += neighborNormalEdgeIndicator(0, -1, depth, normal);
 | |
| 					indicator += neighborNormalEdgeIndicator(0, 1, depth, normal);
 | |
| 					indicator += neighborNormalEdgeIndicator(-1, 0, depth, normal);
 | |
| 					indicator += neighborNormalEdgeIndicator(1, 0, depth, normal);
 | |
| 
 | |
| 					return step(0.1, indicator);
 | |
| 
 | |
| 				}
 | |
| 
 | |
| 				void main() {
 | |
| 
 | |
| 					vec4 texel = texture2D( tDiffuse, vUv );
 | |
| 
 | |
| 					float depth = 0.0;
 | |
| 					vec3 normal = vec3(0.0);
 | |
| 
 | |
| 					if (depthEdgeStrength > 0.0 || normalEdgeStrength > 0.0) {
 | |
| 
 | |
| 						depth = getDepth(0, 0);
 | |
| 						normal = getNormal(0, 0);
 | |
| 
 | |
| 					}
 | |
| 
 | |
| 					float dei = 0.0;
 | |
| 					if (depthEdgeStrength > 0.0)
 | |
| 						dei = depthEdgeIndicator(depth, normal);
 | |
| 
 | |
| 					float nei = 0.0;
 | |
| 					if (normalEdgeStrength > 0.0)
 | |
| 						nei = normalEdgeIndicator(depth, normal);
 | |
| 
 | |
| 					float Strength = dei > 0.0 ? (1.0 - depthEdgeStrength * dei) : (1.0 + normalEdgeStrength * nei);
 | |
| 
 | |
| 					gl_FragColor = texel * Strength;
 | |
| 
 | |
| 				}
 | |
| 			`
 | |
| 		} );
 | |
| 
 | |
| 	}
 | |
| 
 | |
| }
 | |
| 
 | |
| export { RenderPixelatedPass };
 |