浏览代码

Outline Pass Algorithm (#9407)

1. Draw Non Selected objects in the depth buffer
2. Make non selected objects invisible, and draw only the selected objects, by comparing the depth buffer of non selected objects. Write a mask here for visible and hidden edges.
3. Downsample the Mask buffer to half
4. Edge detect the half res buffer, with different colors to hidden and visible edges.
5. Blur the edges of half res/quarter res buffer(quarter res is used for edge glow)
6. Add the blurred edges on top of original scene using the full res mask buffer
spidersharma03 9 年之前
父节点
当前提交
37a0af9f0e
共有 4 个文件被更改,包括 919 次插入0 次删除
  1. 1 0
      examples/files.js
  2. 503 0
      examples/js/postprocessing/OutlinePass.js
  3. 二进制
      examples/textures/tri_pattern.jpg
  4. 415 0
      examples/webgl_postprocessing_outline.html

+ 1 - 0
examples/files.js

@@ -201,6 +201,7 @@ var files = {
 		"webgl_postprocessing_msaa",
 		"webgl_postprocessing_msaa_unbiased",
 		"webgl_postprocessing_nodes",
+		"webgl_postprocessing_outline",
 		"webgl_postprocessing_procedural",
 		"webgl_postprocessing_smaa",
 		"webgl_postprocessing_ssao",

+ 503 - 0
examples/js/postprocessing/OutlinePass.js

@@ -0,0 +1,503 @@
+/**
+ * @author spidersharma / http://eduperiment.com/
+ */
+
+THREE.OutlinePass = function ( resolution, scene, camera, selectedObjects ) {
+
+	this.renderScene = scene;
+	this.renderCamera = camera;
+	this.selectedObjects = selectedObjects !== undefined ? selectedObjects : [];
+	this.visibleEdgeColor = new THREE.Color(1, 1, 1);
+	this.hiddenEdgeColor = new THREE.Color(0.1, 0.04, 0.02);
+	this.edgeGlow = 0.0;
+	this.usePatternTexture = false;
+	this.edgeThickness = 1.0;
+	this.edgeStrength = 3.0;
+	this.downSampleRatio = 2;
+	this.pulsePeriod = 0;
+
+	THREE.Pass.call( this );
+
+	this.resolution = ( resolution !== undefined ) ? new THREE.Vector2(resolution.x, resolution.y) : new THREE.Vector2(256, 256);
+
+	var pars = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBAFormat };
+
+	var resx = Math.round(this.resolution.x/this.downSampleRatio);
+	var resy = Math.round(this.resolution.y/this.downSampleRatio);
+
+	this.maskBufferMaterial = new THREE.MeshBasicMaterial({color:0xffffff});
+	this.maskBufferMaterial.side = THREE.DoubleSide;
+	this.renderTargetMaskBuffer = new THREE.WebGLRenderTarget( this.resolution.x, this.resolution.y, pars );
+	this.renderTargetMaskBuffer.texture.generateMipmaps = false;
+
+	this.depthMaterial = new THREE.MeshDepthMaterial();
+	this.depthMaterial.side = THREE.DoubleSide;
+	this.depthMaterial.depthPacking = THREE.RGBADepthPacking;
+	this.depthMaterial.blending = THREE.NoBlending;
+
+	this.prepareMaskMaterial = this.getPrepareMaskMaterial();
+	this.prepareMaskMaterial.side = THREE.DoubleSide;
+
+	this.renderTargetDepthBuffer = new THREE.WebGLRenderTarget( this.resolution.x, this.resolution.y, pars );
+	this.renderTargetDepthBuffer.texture.generateMipmaps = false;
+
+	this.renderTargetMaskDownSampleBuffer = new THREE.WebGLRenderTarget( resx, resy, pars );
+	this.renderTargetMaskDownSampleBuffer.texture.generateMipmaps = false;
+
+	this.renderTargetBlurBuffer1 = new THREE.WebGLRenderTarget( resx, resy, pars );
+	this.renderTargetBlurBuffer1.texture.generateMipmaps = false;
+	this.renderTargetBlurBuffer2 = new THREE.WebGLRenderTarget( Math.round(resx/2), Math.round(resy/2), pars );
+	this.renderTargetBlurBuffer2.texture.generateMipmaps = false;
+
+	this.edgeDetectionMaterial = this.getEdgeDetectionMaterial();
+	this.renderTargetEdgeBuffer1 = new THREE.WebGLRenderTarget( resx, resy, pars );
+	this.renderTargetEdgeBuffer1.texture.generateMipmaps = false;
+	this.renderTargetEdgeBuffer2 = new THREE.WebGLRenderTarget( Math.round(resx/2), Math.round(resy/2), pars );
+	this.renderTargetEdgeBuffer2.texture.generateMipmaps = false;
+
+	var MAX_EDGE_THICKNESS = 4;
+	var MAX_EDGE_GLOW = 4;
+
+	this.separableBlurMaterial1 = this.getSeperableBlurMaterial(MAX_EDGE_THICKNESS);
+	this.separableBlurMaterial1.uniforms[ "texSize" ].value = new THREE.Vector2(resx, resy);
+	this.separableBlurMaterial1.uniforms[ "kernelRadius" ].value = 1;
+	this.separableBlurMaterial2 = this.getSeperableBlurMaterial(MAX_EDGE_GLOW);
+	this.separableBlurMaterial2.uniforms[ "texSize" ].value = new THREE.Vector2(Math.round(resx/2), Math.round(resy/2));
+	this.separableBlurMaterial2.uniforms[ "kernelRadius" ].value = MAX_EDGE_GLOW;
+
+	// Overlay material
+	this.overlayMaterial = this.getOverlayMaterial();
+
+	// copy material
+	if ( THREE.CopyShader === undefined )
+		console.error( "THREE.OutlinePass relies on THREE.CopyShader" );
+
+	var copyShader = THREE.CopyShader;
+
+	this.copyUniforms = THREE.UniformsUtils.clone( copyShader.uniforms );
+	this.copyUniforms[ "opacity" ].value = 1.0;
+
+	this.materialCopy = new THREE.ShaderMaterial( {
+		uniforms: this.copyUniforms,
+		vertexShader: copyShader.vertexShader,
+		fragmentShader: copyShader.fragmentShader,
+		blending: THREE.NoBlending,
+		depthTest: false,
+		depthWrite: false,
+		transparent: true
+	} );
+
+	this.enabled = true;
+	this.needsSwap = false;
+
+	this.oldClearColor = new THREE.Color();
+	this.oldClearAlpha = 1;
+
+	this.camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
+	this.scene  = new THREE.Scene();
+
+	this.quad = new THREE.Mesh( new THREE.PlaneBufferGeometry( 2, 2 ), null );
+	this.scene.add( this.quad );
+
+	this.tempPulseColor1 = new THREE.Color();
+	this.tempPulseColor2 = new THREE.Color();
+	this.textureMatrix = new THREE.Matrix4();
+};
+
+THREE.OutlinePass.prototype = Object.assign( Object.create( THREE.Pass.prototype ), {
+
+	constructor: THREE.OutlinePass,
+
+	dispose: function() {
+		this.renderTargetMaskBuffer.dispose();
+		this.renderTargetDepthBuffer.dispose();
+		this.renderTargetMaskDownSampleBuffer.dispose();
+		this.renderTargetBlurBuffer1.dispose();
+		this.renderTargetBlurBuffer2.dispose();
+		this.renderTargetEdgeBuffer1.dispose();
+		this.renderTargetEdgeBuffer2.dispose();
+	},
+
+	setSize: function ( width, height ) {
+
+		this.renderTargetMaskBuffer.setSize(width, height );
+
+		var resx = Math.round(width/this.downSampleRatio);
+		var resy = Math.round(height/this.downSampleRatio);
+		this.renderTargetMaskDownSampleBuffer.setSize(resx, resy );
+		this.renderTargetBlurBuffer1.setSize(resx, resy );
+		this.renderTargetEdgeBuffer1.setSize(resx, resy );
+		this.separableBlurMaterial1.uniforms[ "texSize" ].value = new THREE.Vector2(resx, resy);
+
+	  resx = Math.round(resx/2);
+	  resy = Math.round(resy/2);
+
+		this.renderTargetBlurBuffer2.setSize(resx, resy );
+		this.renderTargetEdgeBuffer2.setSize(resx, resy );
+
+		this.separableBlurMaterial2.uniforms[ "texSize" ].value = new THREE.Vector2(resx, resy);
+	},
+
+	changeVisibilityOfSelectedObjects: function( bVisible ) {
+
+		var gatherSelectedMeshesCallBack = function( object ) {
+
+			if( object instanceof THREE.Mesh ) {
+				object.visible = bVisible;
+			}
+		}
+
+		for( var i=0; i<this.selectedObjects.length; i++ ) {
+
+			var selectedObject = this.selectedObjects[i];
+
+			selectedObject.traverse( gatherSelectedMeshesCallBack );
+		}
+	},
+
+	changeVisibilityOfNonSelectedObjects: function( bVisible ) {
+
+		var selectedMeshes = [];
+
+		var gatherSelectedMeshesCallBack = function( object ) {
+
+			if( object instanceof THREE.Mesh ) {
+
+				selectedMeshes.push(object);
+
+			}
+		}
+
+		for( var i=0; i<this.selectedObjects.length; i++ ) {
+
+			var selectedObject = this.selectedObjects[i];
+
+			selectedObject.traverse( gatherSelectedMeshesCallBack );
+		}
+
+		var VisibilityChangeCallBack = function( object ) {
+
+			if( object instanceof THREE.Mesh ) {
+
+				var bFound = false;
+
+				for( var i=0; i<selectedMeshes.length; i++ ) {
+
+					var selectedObjectId = selectedMeshes[i].id;
+
+					if(selectedObjectId === object.id) {
+						bFound = true;
+						break;
+					}
+
+				}
+				if(!bFound) {
+					var visibility = object.visible;
+					if( !bVisible || object.bVisible )
+						object.visible = bVisible;
+					object.bVisible = visibility;
+				}
+			}
+		}
+		this.renderScene.traverse( VisibilityChangeCallBack );
+	},
+
+	updateTextureMatrix: function() {
+
+		this.textureMatrix.set( 0.5, 0.0, 0.0, 0.5,
+														0.0, 0.5, 0.0, 0.5,
+														0.0, 0.0, 0.5, 0.5,
+														0.0, 0.0, 0.0, 1.0 );
+		this.textureMatrix.multiply( this.renderCamera.projectionMatrix );
+		this.textureMatrix.multiply( this.renderCamera.matrixWorldInverse );
+
+	},
+
+	render: function ( renderer, writeBuffer, readBuffer, delta, maskActive ) {
+
+		if(this.selectedObjects.length === 0 )
+			return;
+
+		this.oldClearColor.copy( renderer.getClearColor() );
+		this.oldClearAlpha = renderer.getClearAlpha();
+		var oldAutoClear = renderer.autoClear;
+
+		renderer.autoClear = false;
+
+		if ( maskActive ) renderer.context.disable( renderer.context.STENCIL_TEST );
+
+		renderer.setClearColor( 0xffffff, 1 );
+
+		// Make selected objects invisible
+		this.changeVisibilityOfSelectedObjects(false);
+
+		// 1. Draw Non Selected objects in the depth buffer
+		this.renderScene.overrideMaterial = this.depthMaterial;
+		renderer.render( this.renderScene, this.renderCamera, this.renderTargetDepthBuffer, true );
+
+		// Make selected objects visible
+		this.changeVisibilityOfSelectedObjects(true);
+
+		// Update Texture Matrix for Depth compare
+		this.updateTextureMatrix();
+
+		// Make non selected objects invisible, and draw only the selected objects, by comparing the depth buffer of non selected objects
+		this.changeVisibilityOfNonSelectedObjects(false);
+		this.renderScene.overrideMaterial = this.prepareMaskMaterial;
+		this.prepareMaskMaterial.uniforms[ "cameraNearFar" ].value = new THREE.Vector2(this.renderCamera.near, this.renderCamera.far);
+		this.prepareMaskMaterial.uniforms[ "depthTexture" ].value = this.renderTargetDepthBuffer.texture;
+		this.prepareMaskMaterial.uniforms[ "textureMatrix" ].value = this.textureMatrix;
+		renderer.render( this.renderScene, this.renderCamera, this.renderTargetMaskBuffer, true );
+		this.renderScene.overrideMaterial = null;
+		this.changeVisibilityOfNonSelectedObjects(true);
+
+		// 2. Downsample to Half resolution
+		this.quad.material = this.materialCopy;
+		this.copyUniforms[ "tDiffuse" ].value = this.renderTargetMaskBuffer.texture;
+		renderer.render( this.scene, this.camera, this.renderTargetMaskDownSampleBuffer, true );
+
+		this.tempPulseColor1.copy( this.visibleEdgeColor );
+		this.tempPulseColor2.copy( this.hiddenEdgeColor );
+		if( this.pulsePeriod > 0 ) {
+			var scalar = ( 1 + 0.25 ) / 2 + Math.cos( performance.now() * 0.01/ this.pulsePeriod ) * ( 1.0 - 0.25 )/2
+			this.tempPulseColor1.multiplyScalar( scalar );
+			this.tempPulseColor2.multiplyScalar( scalar );
+		}
+
+		// 3. Apply Edge Detection Pass
+		this.quad.material = this.edgeDetectionMaterial;
+		this.edgeDetectionMaterial.uniforms[ "maskTexture" ].value = this.renderTargetMaskDownSampleBuffer.texture;
+		this.edgeDetectionMaterial.uniforms[ "texSize" ].value = new THREE.Vector2(this.renderTargetMaskDownSampleBuffer.width, this.renderTargetMaskDownSampleBuffer.height);
+		this.edgeDetectionMaterial.uniforms[ "visibleEdgeColor" ].value = this.tempPulseColor1;
+		this.edgeDetectionMaterial.uniforms[ "hiddenEdgeColor" ].value = this.tempPulseColor2;
+		renderer.render( this.scene, this.camera, this.renderTargetEdgeBuffer1, true );
+
+		// 4. Apply Blur on Half res
+		this.quad.material = this.separableBlurMaterial1;
+		this.separableBlurMaterial1.uniforms[ "colorTexture" ].value = this.renderTargetEdgeBuffer1.texture;
+		this.separableBlurMaterial1.uniforms[ "direction" ].value = THREE.OutlinePass.BlurDirectionX;
+		this.separableBlurMaterial1.uniforms[ "kernelRadius" ].value = this.edgeThickness;
+		renderer.render( this.scene, this.camera, this.renderTargetBlurBuffer1, true );
+		this.separableBlurMaterial1.uniforms[ "colorTexture" ].value = this.renderTargetBlurBuffer1.texture;
+		this.separableBlurMaterial1.uniforms[ "direction" ].value = THREE.OutlinePass.BlurDirectionY;
+		renderer.render( this.scene, this.camera, this.renderTargetEdgeBuffer1, true );
+
+		// Apply Blur on quarter res
+		this.quad.material = this.separableBlurMaterial2;
+		this.separableBlurMaterial2.uniforms[ "colorTexture" ].value = this.renderTargetEdgeBuffer1.texture;
+		this.separableBlurMaterial2.uniforms[ "direction" ].value = THREE.OutlinePass.BlurDirectionX;
+		renderer.render( this.scene, this.camera, this.renderTargetBlurBuffer2, true );
+		this.separableBlurMaterial2.uniforms[ "colorTexture" ].value = this.renderTargetBlurBuffer2.texture;
+		this.separableBlurMaterial2.uniforms[ "direction" ].value = THREE.OutlinePass.BlurDirectionY;
+		renderer.render( this.scene, this.camera, this.renderTargetEdgeBuffer2, true );
+
+		// Blend it additively over the input texture
+		this.quad.material = this.overlayMaterial;
+		this.overlayMaterial.uniforms[ "maskTexture" ].value = this.renderTargetMaskBuffer.texture;
+		this.overlayMaterial.uniforms[ "edgeTexture1" ].value = this.renderTargetEdgeBuffer1.texture;
+		this.overlayMaterial.uniforms[ "edgeTexture2" ].value = this.renderTargetEdgeBuffer2.texture;
+		this.overlayMaterial.uniforms[ "patternTexture" ].value = this.patternTexture;
+		this.overlayMaterial.uniforms[ "edgeStrength" ].value = this.edgeStrength;
+		this.overlayMaterial.uniforms[ "edgeGlow" ].value = this.edgeGlow;
+		this.overlayMaterial.uniforms[ "usePatternTexture" ].value = this.usePatternTexture;
+
+
+		if ( maskActive ) renderer.context.enable( renderer.context.STENCIL_TEST );
+
+		renderer.render( this.scene, this.camera, readBuffer, false );
+
+		renderer.setClearColor( this.oldClearColor, this.oldClearAlpha );
+		renderer.autoClear = oldAutoClear;
+	},
+
+	getPrepareMaskMaterial: function() {
+
+		return new THREE.ShaderMaterial( {
+
+			uniforms: {
+				"depthTexture": { value: null },
+				"cameraNearFar": { value: new THREE.Vector2( 0.5, 0.5 ) },
+				"textureMatrix" : { value: new THREE.Matrix4() }
+			},
+
+			vertexShader:
+				"varying vec2 vUv;\
+				varying vec4 projTexCoord;\
+				varying vec4 vPosition;\
+				uniform mat4 textureMatrix;\
+				void main() {\
+					vUv = uv;\
+					vPosition = modelViewMatrix * vec4( position, 1.0 );\
+					vec4 worldPosition = modelMatrix * vec4( position, 1.0 );\
+					projTexCoord = textureMatrix * worldPosition;\
+					gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\
+				}",
+
+			fragmentShader:
+				"#include <packing>\
+				varying vec2 vUv;\
+				varying vec4 vPosition;\
+				varying vec4 projTexCoord;\
+				uniform sampler2D depthTexture;\
+				uniform vec2 cameraNearFar;\
+				\
+				void main() {\
+					float depth = unpackRGBAToDepth(texture2DProj( depthTexture, projTexCoord ));\
+					float viewZ = -perspectiveDepthToViewZ( depth, cameraNearFar.x, cameraNearFar.y );\
+					float depthTest = (-vPosition.z > viewZ) ? 1.0 : 0.0;\
+					gl_FragColor = vec4(0.0, depthTest, 1.0, 1.0);\
+				}"
+		} );
+	},
+
+	getEdgeDetectionMaterial: function() {
+
+		return new THREE.ShaderMaterial( {
+
+			uniforms: {
+				"maskTexture": { value: null },
+				"texSize": { value: new THREE.Vector2( 0.5, 0.5 ) },
+				"visibleEdgeColor": { value: new THREE.Vector3( 1.0, 1.0, 1.0 ) },
+				"hiddenEdgeColor":  { value: new THREE.Vector3( 1.0, 1.0, 1.0 ) },
+			},
+
+			vertexShader:
+				"varying vec2 vUv;\n\
+				void main() {\n\
+					vUv = uv;\n\
+					gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\
+				}",
+
+			fragmentShader:
+				"varying vec2 vUv;\
+				uniform sampler2D maskTexture;\
+				uniform vec2 texSize;\
+				uniform vec3 visibleEdgeColor;\
+				uniform vec3 hiddenEdgeColor;\
+				\
+				void main() {\n\
+					vec2 invSize = 1.0 / texSize;\
+					vec4 uvOffset = vec4(1.0, 0.0, 0.0, 1.0) * vec4(invSize, invSize);\
+					vec4 c1 = texture2D( maskTexture, vUv + uvOffset.xy);\
+					vec4 c2 = texture2D( maskTexture, vUv - uvOffset.xy);\
+					vec4 c3 = texture2D( maskTexture, vUv + uvOffset.yw);\
+					vec4 c4 = texture2D( maskTexture, vUv - uvOffset.yw);\
+					float diff1 = (c1.r - c2.r)*0.5;\
+					float diff2 = (c3.r - c4.r)*0.5;\
+					float d = length( vec2(diff1, diff2) );\
+					float a1 = min(c1.g, c2.g);\
+					float a2 = min(c3.g, c4.g);\
+					float visibilityFactor = min(a1, a2);\
+					vec3 edgeColor = 1.0 - visibilityFactor > 0.001 ? visibleEdgeColor : hiddenEdgeColor;\
+					gl_FragColor = vec4(edgeColor, 1.0) * vec4(d);\
+				}"
+		} );
+	},
+
+	getSeperableBlurMaterial: function(maxRadius) {
+
+		return new THREE.ShaderMaterial( {
+
+			defines: {
+				"MAX_RADIUS" : maxRadius,
+			},
+
+			uniforms: {
+				"colorTexture": { value: null },
+				"texSize": 	{ value: new THREE.Vector2( 0.5, 0.5 ) },
+				"direction": { value: new THREE.Vector2( 0.5, 0.5 ) },
+				"kernelRadius": { value: 1.0 }
+			},
+
+			vertexShader:
+				"varying vec2 vUv;\n\
+				void main() {\n\
+					vUv = uv;\n\
+					gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\
+				}",
+
+			fragmentShader:
+				"#include <common>\
+				varying vec2 vUv;\
+				uniform sampler2D colorTexture;\
+				uniform vec2 texSize;\
+				uniform vec2 direction;\
+				uniform float kernelRadius;\
+				\
+				float gaussianPdf(in float x, in float sigma) {\
+					return 0.39894 * exp( -0.5 * x * x/( sigma * sigma))/sigma;\
+				}\
+				void main() {\
+					vec2 invSize = 1.0 / texSize;\
+					float weightSum = gaussianPdf(0.0, kernelRadius);\
+					vec3 diffuseSum = texture2D( colorTexture, vUv).rgb * weightSum;\
+					vec2 delta = direction * invSize * kernelRadius/float(MAX_RADIUS);\
+					vec2 uvOffset = delta;\
+					for( int i = 1; i <= MAX_RADIUS; i ++ ) {\
+						float w = gaussianPdf(uvOffset.x, kernelRadius);\
+						vec3 sample1 = texture2D( colorTexture, vUv + uvOffset).rgb;\
+						vec3 sample2 = texture2D( colorTexture, vUv - uvOffset).rgb;\
+						diffuseSum += ((sample1 + sample2) * w);\
+						weightSum += (2.0 * w);\
+						uvOffset += delta;\
+					}\
+					gl_FragColor = vec4(diffuseSum/weightSum, 1.0);\
+				}"
+		} );
+	},
+
+	getOverlayMaterial: function() {
+
+		return new THREE.ShaderMaterial( {
+
+			uniforms: {
+				"maskTexture": { value: null },
+				"edgeTexture1": { value: null },
+				"edgeTexture2": { value: null },
+				"patternTexture": { value: null },
+				"edgeStrength" : { value: 1.0 },
+				"edgeGlow" : { value: 1.0 },
+				"usePatternTexture" : { value: 0.0 }
+			},
+
+			vertexShader:
+				"varying vec2 vUv;\n\
+				void main() {\n\
+					vUv = uv;\n\
+					gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );\n\
+				}",
+
+			fragmentShader:
+				"varying vec2 vUv;\
+				uniform sampler2D maskTexture;\
+				uniform sampler2D edgeTexture1;\
+				uniform sampler2D edgeTexture2;\
+				uniform sampler2D patternTexture;\
+				uniform float edgeStrength;\
+				uniform float edgeGlow;\
+				uniform bool usePatternTexture;\
+				\
+				void main() {\
+					vec4 edgeValue1 = texture2D(edgeTexture1, vUv);\
+					vec4 edgeValue2 = texture2D(edgeTexture2, vUv);\
+					vec4 maskColor = texture2D(maskTexture, vUv);\
+					vec4 patternColor = texture2D(patternTexture, 6.0 * vUv);\
+					float visibilityFactor = 1.0 - maskColor.g > 0.0 ? 1.0 : 0.5;\
+					vec4 edgeValue = edgeValue1 + edgeValue2 * edgeGlow;\
+					vec4 finalColor = edgeStrength * maskColor.r * edgeValue;\
+					if(usePatternTexture)\
+						finalColor += + visibilityFactor * (1.0 - maskColor.r) * (1.0 - patternColor.r);\
+					gl_FragColor = finalColor;\
+				}",
+
+				blending: THREE.AdditiveBlending,
+				depthTest: false,
+				depthWrite: false,
+				transparent: true
+		} );
+	}
+
+} );
+
+THREE.OutlinePass.BlurDirectionX = new THREE.Vector2( 1.0, 0.0 );
+THREE.OutlinePass.BlurDirectionY = new THREE.Vector2( 0.0, 1.0 );

二进制
examples/textures/tri_pattern.jpg


+ 415 - 0
examples/webgl_postprocessing_outline.html

@@ -0,0 +1,415 @@
+<!DOCTYPE html>
+
+<html lang="en">
+	<head>
+		<title>three.js webgl - post processing - Scalable Ambient Occlusion (SAO)</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<style>
+			body {
+				background-color: #000000;
+				margin: 0px;
+				overflow: hidden;
+				font-family:Monospace;
+				font-size:13px;
+				text-align:center;
+				font-weight: bold;
+			}
+
+			a {
+				color:#00ff78;
+			}
+
+			#info {
+				color: #fff;
+				position: absolute;
+				top: 10px;
+				width: 100%;
+				text-align: center;
+				display:block;
+			}
+			.dg.ac {
+				z-index: 1 !important; /* FIX DAT.GUI */
+			}
+		</style>
+	</head>
+	<body>
+		<script src="../build/three.js"></script>
+		<script src="js/controls/OrbitControls.js"></script>
+		<script src="js/loaders/OBJLoader.js"></script>
+
+		<script src="js/Detector.js"></script>
+
+		<script src="js/shaders/CopyShader.js"></script>
+    <script src="js/postprocessing/EffectComposer.js"></script>
+    <script src="js/postprocessing/MaskPass.js"></script>
+		<script src="js/shaders/CopyShader.js"></script>
+		<script src="js/postprocessing/RenderPass.js"></script>
+    <script src="js/postprocessing/ShaderPass.js"></script>
+		<script src="js/postprocessing/OutlinePass.js"></script>
+		<script src="js/shaders/FXAAShader.js"></script>
+		<script src="js/libs/stats.min.js"></script>
+		<script src='js/libs/dat.gui.min.js'></script>
+
+		<div id="info">
+			<a href="http://threejs.org" target="_blank">three.js</a> - Outline Pass by <a href="http://eduperiment.com" target="_blank">Prashant Sharma</a> and <a href="https://clara.io" target="_blank">Ben Houston</a><br/><br/>
+		</div>
+
+		<script>
+
+			if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
+
+			var container, stats;
+			var camera, scene, renderer, controls;
+			var mesh, decal;
+			var raycaster = new THREE.Raycaster();
+
+			var mouse = new THREE.Vector2();
+			var selectedObjects = [];
+
+			var composer, effectFXAA, outlinePass;
+			var obj3d = new THREE.Object3D();
+
+			var group = new THREE.Object3D();
+
+			var params = {
+				edgeStrength: 3.0,
+				edgeGlow: 0.0,
+				edgeThickness: 1.0,
+				pulsePeriod: 0,
+				rotate: false,
+				usePatternTexture: false
+			}
+
+			// Init gui
+			var gui = new dat.GUI();
+			gui.add( params, "edgeStrength", 0.01, 10 ).onChange( function(value) {
+	        outlinePass.edgeStrength = Number(value);
+	    });
+			gui.add( params, "edgeGlow", 0.0, 1 ).onChange( function(value) {
+	        outlinePass.edgeGlow = Number(value);
+	    })
+			gui.add( params, "edgeThickness", 1, 4 ).onChange( function(value) {
+	        outlinePass.edgeThickness = Number(value);
+	    })
+			gui.add( params, "pulsePeriod", 0.0, 5 ).onChange( function(value) {
+	        outlinePass.pulsePeriod = Number(value);
+	    })
+			gui.add( params, "rotate" )
+			gui.add( params, "usePatternTexture" ).onChange( function(value) {
+				outlinePass.usePatternTexture = value;
+	    })
+			var Configuration=function(){
+	            this.visibleEdgeColor = "#ffffff";
+							this.hiddenEdgeColor = "#190a05";
+	    };
+	    var conf = new Configuration();
+
+	    var controladorVisible = gui.addColor( conf, 'visibleEdgeColor');
+			var controladorHidden = gui.addColor( conf, 'hiddenEdgeColor');
+	    controladorVisible.onChange( function( colorValue  )
+	    {
+	      //the return value by the chooser is like as: #ffff
+	      colorValue=colorValue.replace( '#','' );
+	      function hexToRgb(hex) {
+	            var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
+	            return result ? {
+	                r: parseInt(result[1], 16),
+	                g: parseInt(result[2], 16),
+	                b: parseInt(result[3], 16)
+	            } : null;
+	        }
+	      var rgba = hexToRgb(colorValue);
+        var color = outlinePass.visibleEdgeColor;
+        color.r = rgba.r/255;
+        color.g = rgba.g/255;
+        color.b = rgba.b/255;
+	    });
+
+			controladorHidden.onChange( function( colorValue  )
+	    {
+	      //the return value by the chooser is like as: #ffff
+	      colorValue=colorValue.replace( '#','' );
+	      function hexToRgb(hex) {
+	            var result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex);
+	            return result ? {
+	                r: parseInt(result[1], 16),
+	                g: parseInt(result[2], 16),
+	                b: parseInt(result[3], 16)
+	            } : null;
+	        }
+	      var rgba = hexToRgb(colorValue);
+        var color = outlinePass.hiddenEdgeColor;
+        color.r = rgba.r/255;
+        color.g = rgba.g/255;
+        color.b = rgba.b/255;
+	    });
+
+			init();
+			animate();
+
+			function init() {
+
+				container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				var width = window.innerWidth || 1;
+				var height = window.innerHeight || 1;
+				var devicePixelRatio = window.devicePixelRatio || 1;
+
+				renderer = new THREE.WebGLRenderer( { antialias: false } );
+				renderer.shadowMap.enabled = true;
+				renderer.setClearColor( 0xa0a0a0 );
+				renderer.setPixelRatio( 1 );
+				renderer.setSize( width, height );
+				document.body.appendChild( renderer.domElement );
+
+				camera = new THREE.PerspectiveCamera( 45, width / height, 0.1, 100 );
+				camera.position.z = 8;
+				camera.position.x = 0;
+
+				scene = new THREE.Scene();
+
+				var manager = new THREE.LoadingManager();
+				manager.onProgress = function ( item, loaded, total ) {
+					console.log( item, loaded, total );
+				};
+				// model
+				var loader = new THREE.OBJLoader( manager );
+				loader.load( 'models/obj/tree.obj', function ( object ) {
+					var scale = 1.0;
+					object.traverse( function ( child ) {
+						if ( child instanceof THREE.Mesh ) {
+							child.geometry.center();
+							child.geometry.computeBoundingSphere();
+							scale = 0.2*child.geometry.boundingSphere.radius;
+							var phongMaterial = new THREE.MeshPhongMaterial( { color: 0xffffff, specular: 0xffffff, shininess: 5 } );
+							child.material = phongMaterial;
+							child.material.side = THREE.DoubleSide;
+							child.receiveShadow = true;
+							child.castShadow = true;
+						}
+					} );
+					object.position.y = 1;
+					object.scale.x /= scale;
+					object.scale.y /= scale;
+					object.scale.z /= scale;
+					obj3d.add( object );
+				} );
+				group.add(obj3d);
+
+				controls = new THREE.OrbitControls( camera, renderer.domElement );
+				controls.enableDamping = true;
+				controls.dampingFactor = 0.25;
+
+				scene.add( group );
+
+				var light = new THREE.DirectionalLight( 0xddffdd, 0.4);
+				light.position.z = 1;
+				light.position.y = 1;
+				light.position.x = 1;
+				scene.add( light );
+				light.castShadow = true;
+
+				light.shadow.mapSize.width = 1024;
+				light.shadow.mapSize.height = 1024;
+
+				var d = 20;
+
+				light.shadow.camera.left = - d;
+				light.shadow.camera.right = d;
+				light.shadow.camera.top = d;
+				light.shadow.camera.bottom = - d;
+
+				light.shadow.camera.far = 1000;
+
+				var light2 = new THREE.DirectionalLight( 0xaadddd, 0.15 );
+				light2.position.z = 1;
+				light2.position.x = -1;
+				light2.position.y = -1;
+				scene.add( light2 );
+
+				var light3 = new THREE.DirectionalLight( 0xddddaa, 0.1 );
+				light3.position.z = 1;
+				light3.position.x = -1;
+				light3.position.y = 1;
+				scene.add( light3 );
+
+				var light3 = new THREE.AmbientLight( 0xaaaaaa, 0.2 );
+				scene.add( light3 );
+
+				var geometry = new THREE.SphereBufferGeometry( 3, 48, 24 );
+				for ( var i = 0; i < 20; i ++ ) {
+
+					var material = new THREE.MeshLambertMaterial();
+					material.roughness = 1;//0.5 * Math.random() + 0.25;
+					material.metalness = 0;
+					material.color.setHSL( Math.random(), 1.0, 0.3 );
+
+					var mesh = new THREE.Mesh( geometry, material );
+					mesh.position.x = Math.random() * 4 - 2;
+					mesh.position.y = Math.random() * 4 - 2;
+					mesh.position.z = Math.random() * 4 - 2;
+					mesh.rotation.x = Math.random();
+					mesh.rotation.y = Math.random();
+					mesh.rotation.z = Math.random();
+					mesh.receiveShadow = true;
+					mesh.castShadow = true;
+					mesh.scale.x = mesh.scale.y = mesh.scale.z = Math.random() * 0.3 + 0.1;
+					group.add( mesh );
+				}
+
+				var floorMaterial = new THREE.MeshLambertMaterial();
+				floorMaterial.side = THREE.DoubleSide;
+				material.roughness = 0.5 * Math.random() + 0.25;
+				material.metalness = 0;
+
+				var floorGeometry = new THREE.PlaneBufferGeometry( 12, 12 );
+				var floorMesh = new THREE.Mesh( floorGeometry, floorMaterial );
+				floorMesh.rotation.x -= Math.PI * 0.5;
+				floorMesh.position.y -= 1.5;
+				group.add( floorMesh );
+				floorMesh.receiveShadow = true;
+
+				var geometry = new THREE.TorusGeometry( 1, 0.3, 16, 100 );
+				var material = new THREE.MeshPhongMaterial( { color: 0xffaaff } );
+				var torus = new THREE.Mesh( geometry, material );
+				torus.position.z = -4;
+				group.add( torus );
+				torus.receiveShadow = true;
+				torus.castShadow = true;
+
+				stats = new Stats();
+				container.appendChild( stats.dom );
+
+				// postprocessing
+				composer = new THREE.EffectComposer( renderer );
+
+				renderPass = new THREE.RenderPass( scene, camera );
+				composer.addPass( renderPass );
+
+				outlinePass = new THREE.OutlinePass( new THREE.Vector2(window.innerWidth, window.innerHeight), scene, camera);
+				composer.addPass( outlinePass );
+				var onLoad = function ( texture ) {
+					outlinePass.patternTexture = texture;
+					texture.wrapS = THREE.RepeatWrapping;
+					texture.wrapT = THREE.RepeatWrapping;
+				};
+
+				var loader = new THREE.TextureLoader();
+
+				// load a resource
+				loader.load(
+					// resource URL
+					'textures/tri_pattern.jpg',
+					// Function when resource is loaded
+					onLoad
+				);
+
+				effectFXAA = new THREE.ShaderPass(THREE.FXAAShader);
+		    effectFXAA.uniforms['resolution'].value.set(1 / window.innerWidth, 1 / window.innerHeight );
+		    effectFXAA.renderToScreen = true;
+				composer.addPass( effectFXAA );
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+				var moved = false;
+
+				controls.addEventListener( 'change', function() {
+
+					moved = true;
+
+				} );
+
+				window.addEventListener( 'mousedown', function () {
+
+					moved = false;
+
+				}, false );
+
+				window.addEventListener( 'mouseup', function() {
+					if(!moved)
+						checkIntersection();
+				} );
+
+				window.addEventListener( 'mousemove', onTouchMove );
+				window.addEventListener( 'touchmove', onTouchMove );
+
+				function onTouchMove( event ) {
+
+					if ( event.changedTouches ) {
+
+						x = event.changedTouches[ 0 ].pageX;
+						y = event.changedTouches[ 0 ].pageY;
+
+					} else {
+
+						x = event.clientX;
+						y = event.clientY;
+
+					}
+
+					mouse.x = ( x / window.innerWidth ) * 2 - 1;
+					mouse.y = - ( y / window.innerHeight ) * 2 + 1;
+
+				}
+
+				function addSelectedObject(object) {
+					selectedObjects = [];
+					selectedObjects.push(object);
+				}
+
+				function checkIntersection() {
+
+					raycaster.setFromCamera( mouse, camera );
+
+					var intersects = raycaster.intersectObjects( [ scene ], true );
+
+					if ( intersects.length > 0 ) {
+						var selectedObject = intersects[ 0 ].object;
+						addSelectedObject(selectedObject);
+						outlinePass.selectedObjects = selectedObjects;
+					}
+					else {
+						// outlinePass.selectedObjects = [];
+					}
+				}
+
+			}
+
+			function onWindowResize() {
+
+				var width = window.innerWidth || 1;
+				var height = window.innerHeight || 1;
+				var devicePixelRatio = window.devicePixelRatio || 1;
+
+				camera.aspect = width / height;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( width, height );
+        composer.setSize( width, height );
+				effectFXAA.uniforms['resolution'].value.set(1 / window.innerWidth, 1 / window.innerHeight );
+			}
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				stats.begin();
+
+				var timer = performance.now();
+				if(params.rotate)
+					group.rotation.y = timer * 0.0001;
+				renderer.autoClear = true;
+				renderer.setClearColor( 0xfff0f0 );
+				renderer.setClearAlpha( 0.0 );
+
+				composer.render();
+				controls.update();
+				stats.end();
+			}
+
+
+		</script>
+	</body>
+</html>