Jelajahi Sumber

Export OutlineDrawingPass from MMDHelper to make it reusable (#9868)

* Add BackFaceCullingOutlinePass

* Let MMDHelper use BackFaceCullingOutlinePass

* Change from BackFaceCullingOutlinePass to OutlineEffect

* Add OutlineEffect

* Correct comment

* Remove this.renderer from OutlineEffect to follow other Effects style

* Rename variables from tmp* to current*
Takahiro 8 tahun lalu
induk
melakukan
3c455c2cbc

+ 294 - 0
examples/js/effects/OutlineEffect.js

@@ -0,0 +1,294 @@
+/**
+ * @author takahirox / http://github.com/takahirox/
+ *
+ * Reference: https://en.wikipedia.org/wiki/Cel_shading
+ *
+ * // How to set default outline parameters
+ * new THREE.OutlineEffect( renderer, {
+ * 	defaultThickNess: 0.01,
+ * 	defaultColor: new THREE.Color( 0x888888 ),
+ * 	defaultAlpha: 0.8
+ * } );
+ *
+ * // How to set outline parameters for each material
+ * material.outlineParameters = {
+ * 	thickNess: 0.01,
+ * 	color: new THREE.Color( 0x888888 ),
+ * 	alpha: 0.8,
+ * 	visible: true
+ * };
+ *
+ * TODO
+ *  - shared material
+ *  - support shader material without objectNormal in its vertexShader
+ */
+
+THREE.OutlineEffect = function ( renderer, parameters ) {
+
+	var _this = this;
+
+	parameters = parameters || {};
+
+	this.autoClear = parameters.autoClear !== undefined ? parameters.autoClear : true;
+
+	var defaultThickness = parameters.defaultThickness !== undefined ? parameters.defaultThickness : 0.003;
+	var defaultColor = parameters.defaultColor !== undefined ? parameters.defaultColor : new THREE.Color( 0x000000 );
+	var defaultAlpha = parameters.defaultAlpha !== undefined ? parameters.defaultAlpha : 1.0;
+
+	var invisibleMaterial = new THREE.ShaderMaterial( { visible: false } );
+
+	// copied from WebGLPrograms and removed some materials
+	var shaderIDs = {
+		MeshBasicMaterial: 'basic',
+		MeshLambertMaterial: 'lambert',
+		MeshPhongMaterial: 'phong',
+		MeshStandardMaterial: 'physical',
+		MeshPhysicalMaterial: 'physical'
+	};
+
+	var uniformsChunk = {
+		outlineThickness: { type: "f", value: defaultThickness },
+		outlineColor: { type: "c", value: defaultColor },
+		outlineAlpha: { type: "f", value: defaultAlpha }
+	};
+
+	var vertexShaderChunk = [
+
+		"uniform float outlineThickness;",
+
+		"vec4 calculateOutline( vec4 pos, vec3 objectNormal, vec4 skinned ) {",
+
+		"	float thickness = outlineThickness;",
+		"	float ratio = 1.0;", // TODO: support outline thickness ratio for each vertex
+		"	vec4 pos2 = projectionMatrix * modelViewMatrix * vec4( skinned.xyz + objectNormal, 1.0 );",
+		// NOTE: subtract pos2 from pos because BackSide objectNormal is negative
+		"	vec4 norm = normalize( pos - pos2 );",
+		"	return pos + norm * thickness * pos.w * ratio;",
+
+		"}",
+
+	].join( "\n" );
+
+	var vertexShaderChunk2 = [
+
+		"#if ! defined( LAMBERT ) && ! defined( PHONG ) && ! defined( PHYSICAL )",
+
+		"	#ifndef USE_ENVMAP",
+		"		vec3 objectNormal = normalize( normal );",
+
+		"		#ifdef FLIP_SIDED",
+		"			objectNormal = -objectNormal;",
+		"		#endif",
+
+		"	#endif",
+
+		"#endif",
+
+		"#ifdef USE_SKINNING",
+		"	gl_Position = calculateOutline( gl_Position, objectNormal, skinned );",
+		"#else",
+		"	gl_Position = calculateOutline( gl_Position, objectNormal, vec4( transformed, 1.0 ) );",
+		"#endif",
+
+	].join( "\n" );
+
+	var fragmentShader = [
+
+		"#include <common>",
+		"#include <fog_pars_fragment>",
+
+		"uniform vec3 outlineColor;",
+		"uniform float outlineAlpha;",
+
+		"void main() {",
+
+		"	gl_FragColor = vec4( outlineColor, outlineAlpha );",
+
+		"	#include <fog_fragment>",
+
+		"}",
+
+	].join( "\n" );
+
+	function createMaterial ( originalMaterial ) {
+
+		var shaderID = shaderIDs[ originalMaterial.type ];
+		var originalUniforms, originalVertexShader;
+		var outlineParameters = originalMaterial.outlineParameters;
+
+		if ( shaderID !== undefined ) {
+
+			var shader = THREE.ShaderLib[ shaderID ];
+			originalUniforms = shader.uniforms;
+			originalVertexShader = shader.vertexShader;
+
+		} else if ( originalMaterial.isShaderMaterial === true ) {
+
+			originalUniforms = originalMaterial.uniforms;
+			originalVertexShader = originalMaterial.vertexShader;
+
+		} else {
+
+			return invisibleMaterial;
+
+		}
+
+		var uniforms = THREE.UniformsUtils.merge( [
+			originalUniforms,
+			uniformsChunk
+		] );
+
+		var vertexShader = originalVertexShader
+					// put vertexShaderChunk right before "void main() {...}"
+					.replace( /void\s+main\s*\(\s*\)/, vertexShaderChunk + '\nvoid main()' )
+					// put vertexShaderChunk2 the end of "void main() {...}"
+					// Note: here assums originalVertexShader ends with "}" of "void main() {...}"
+					.replace( /\}\s*$/, vertexShaderChunk2 + '\n}' )
+					// remove any light related lines
+					// Note: here is very sensitive to originalVertexShader
+					// TODO: consider safer way
+					.replace( /#include\s+<[\w_]*light[\w_]*>/g, '' );
+
+		var material = new THREE.ShaderMaterial( {
+			uniforms: THREE.UniformsUtils.clone( uniforms ),
+			vertexShader: vertexShader,
+			fragmentShader: fragmentShader,
+			side: THREE.BackSide,
+			//wireframe: true,
+			skinning: false,
+			morphTargets: false,
+			morphNormals: false,
+			fog: false
+		} );
+
+		return material;
+
+	}
+
+	function createMultiMaterial ( originalMaterial ) {
+
+		var materials = [];
+
+		for ( var i = 0, il = originalMaterial.materials.length; i < il; i ++ ) {
+
+			materials.push( createMaterial( originalMaterial.materials[ i ] ) );
+
+		}
+
+		return new THREE.MultiMaterial( materials );
+
+	}
+
+	function setOutlineMaterial ( object ) {
+
+		if ( object.material === undefined ) return;
+
+		object.userData.originalMaterial = object.material;
+
+		if ( object.userData.outlineMaterial === undefined ) {
+
+			object.userData.outlineMaterial = object.material.type === 'MultiMaterial' ? createMultiMaterial( object.material ) : createMaterial( object.material );
+
+		}
+
+		if ( object.userData.outlineMaterial.type === 'MultiMaterial' ) {
+
+			updateOutlineMultiMaterial( object.userData.outlineMaterial, object.userData.originalMaterial );
+
+		} else {
+
+			updateOutlineMaterial( object.userData.outlineMaterial, object.userData.originalMaterial );
+
+		}
+
+		object.material = object.userData.outlineMaterial;
+
+	}
+
+	function updateOutlineMaterial ( material, originalMaterial ) {
+
+		if ( material === invisibleMaterial ) return;
+
+		var outlineParameters = originalMaterial.outlineParameters;
+
+		material.skinning = originalMaterial.skinning;
+		material.morphTargets = originalMaterial.morphTargets;
+		material.morphNormals = originalMaterial.morphNormals;
+		material.fog = originalMaterial.fog;
+		material.visible = originalMaterial.visible;
+		material.uniforms.outlineAlpha.value = originalMaterial.opacity;
+
+		if ( outlineParameters !== undefined ) {
+
+			if ( outlineParameters.thickness !== undefined ) material.uniforms.outlineThickness.value = outlineParameters.thickness;
+			if ( outlineParameters.color !== undefined ) material.uniforms.outlineColor.value.copy( outlineParameters.color );
+			if ( outlineParameters.alpha !== undefined ) material.uniforms.outlineAlpha.value = outlineParameters.alpha;
+			if ( outlineParameters.visible !== undefined ) material.visible = outlineParameters.visible;
+
+		}
+
+		if ( material.uniforms.outlineAlpha.value < 1.0 ) material.transparent = true;
+
+	}
+
+	function updateOutlineMultiMaterial ( material, originalMaterial ) {
+
+		var outlineParameters = originalMaterial.outlineParameters;
+
+		material.visible = originalMaterial.visible;
+
+		if ( outlineParameters !== undefined ) {
+
+			if ( outlineParameters.visible !== undefined ) material.visible = outlineParameters.visible;
+
+		}
+
+		for ( var i = 0, il = material.materials.length; i < il; i ++ ) {
+
+			updateOutlineMaterial( material.materials[ i ], originalMaterial.materials[ i ] );
+
+		}
+
+	}
+
+	function restoreOriginalMaterial ( object ) {
+
+		if ( object.userData.originalMaterial !== undefined ) object.material = object.userData.originalMaterial;
+
+	}
+
+	this.setSize = function ( width, height ) {
+
+		renderer.setSize( width, height );
+
+	};
+
+	this.render = function ( scene, camera, renderTarget, forceClear ) {
+
+		var currentAutoClear = renderer.autoClear;
+		renderer.autoClear = this.autoClear;
+
+		// 1. render normally
+		renderer.render( scene, camera, renderTarget, forceClear );
+
+		// 2. render outline
+		var currentSceneAutoUpdate = scene.autoUpdate;
+		var currentShadowMapEnabled = renderer.shadowMap.enabled;
+
+		scene.autoUpdate = false;
+		renderer.autoClear = false;
+		renderer.shadowMap.enabled = false;
+
+		scene.traverse( setOutlineMaterial );
+
+		renderer.render( scene, camera, renderTarget );
+
+		scene.traverse( restoreOriginalMaterial );
+
+		scene.autoUpdate = currentSceneAutoUpdate;
+		renderer.autoClear = currentAutoClear;
+		renderer.shadowMap.enabled = currentShadowMapEnabled;
+
+	};
+
+};

+ 116 - 316
examples/js/loaders/MMDLoader.js

@@ -3,7 +3,11 @@
  *
  * Dependencies
  *  - charset-encoder-js https://github.com/takahirox/charset-encoder-js
+ *  - ammo.js https://github.com/kripken/ammo.js
  *  - THREE.TGALoader
+ *  - THREE.MMDPhysics
+ *  - THREE.CCDIKSolver
+ *  - THREE.OutlineEffect
  *
  *
  * This loader loads and parses PMD/PMX and VMD binary files
@@ -2336,7 +2340,7 @@ THREE.MMDLoader.prototype.createMesh = function ( model, texturePath, onProgress
 						var imageData = t.image.data !== undefined ? t.image : createImageData( t.image );
 						var uvs = geometry.faceVertexUvs[ 0 ].slice( m.faceOffset, m.faceOffset + m.faceNum );
 
-						m.textureTransparency = detectTextureTransparency( imageData, uvs );
+						if ( detectTextureTransparency( imageData, uvs ) ) m.transparent = true;
 
 						delete m.faceOffset;
 						delete m.faceNum;
@@ -2392,9 +2396,14 @@ THREE.MMDLoader.prototype.createMesh = function ( model, texturePath, onProgress
 
 				};
 
-				m.uniforms.outlineThickness.value = p2.edgeFlag === 1 ? 0.003 : 0.0;
-				m.uniforms.outlineColor.value = new THREE.Color( 0.0, 0.0, 0.0 );
-				m.uniforms.outlineAlpha.value = 1.0;
+				m.outlineParameters = {
+					thickness: p2.edgeFlag === 1 ? 0.003 : 0.0,
+					color: new THREE.Color( 0.0, 0.0, 0.0 ),
+					alpha: 1.0
+				};
+
+				if ( m.outlineParameters.thickness === 0.0 ) m.outlineParameters.visible = false;
+
 				m.uniforms.toonMap.value = textures[ p2.toonIndex ];
 				m.uniforms.celShading.value = 1;
 
@@ -2408,13 +2417,19 @@ THREE.MMDLoader.prototype.createMesh = function ( model, texturePath, onProgress
 					var uuid = loadTexture( n, { defaultTexturePath: isDefaultToonTexture( n ) } );
 					m.uniforms.toonMap.value = textures[ uuid ];
 					m.uniforms.hasToonTexture.value = 1;
+
 				}
 
 			} else {
 
-				m.uniforms.outlineThickness.value = p2.edgeSize / 300;
-				m.uniforms.outlineColor.value = new THREE.Color( p2.edgeColor[ 0 ], p2.edgeColor[ 1 ], p2.edgeColor[ 2 ] );
-				m.uniforms.outlineAlpha.value = p2.edgeColor[ 3 ];
+				m.outlineParameters = {
+					thickness: p2.edgeSize / 300,
+					color: new THREE.Color( p2.edgeColor[ 0 ], p2.edgeColor[ 1 ], p2.edgeColor[ 2 ] ),
+					alpha: p2.edgeColor[ 3 ]
+				};
+
+				if ( m.outlineParameters.thickness === 0.0 ) m.outlineParameters.visible = false;
+
 				m.uniforms.celShading.value = 1;
 
 				if ( p2.toonIndex === -1 ) {
@@ -2472,7 +2487,7 @@ THREE.MMDLoader.prototype.createMesh = function ( model, texturePath, onProgress
 
 					if ( m.uniforms.opacity.value !== e.diffuse[ 3 ] ) {
 
-						m.morphTransparency = true;
+						m.transparent = true;
 
 					}
 
@@ -3558,148 +3573,22 @@ THREE.ShaderLib[ 'mmd' ] = {
 
 	uniforms: THREE.UniformsUtils.merge( [
 
-		THREE.UniformsLib[ "common" ],
-		THREE.UniformsLib[ "aomap" ],
-		THREE.UniformsLib[ "lightmap" ],
-		THREE.UniformsLib[ "emissivemap" ],
-		THREE.UniformsLib[ "bumpmap" ],
-		THREE.UniformsLib[ "normalmap" ],
-		THREE.UniformsLib[ "displacementmap" ],
-		THREE.UniformsLib[ "fog" ],
-		THREE.UniformsLib[ "lights" ],
+		THREE.ShaderLib[ 'phong' ].uniforms,
 
+		// MMD specific for toon mapping
 		{
-			"emissive" : { value: new THREE.Color( 0x000000 ) },
-			"specular" : { value: new THREE.Color( 0x111111 ) },
-			"shininess": { value: 30 }
-		},
-
-		// ---- MMD specific for cel shading(outline drawing and toon mapping)
-		{
-			"outlineDrawing"  : { value: 0 },
-			"outlineThickness": { value: 0.0 },
-			"outlineColor"    : { value: new THREE.Color( 0x000000 ) },
-			"outlineAlpha"    : { value: 1.0 },
-			"celShading"      : { value: 0 },
-			"toonMap"         : { value: null },
-			"hasToonTexture"  : { value: 0 }
+			"celShading"      : { type: "i", value: 0 },
+			"toonMap"         : { type: "t", value: null },
+			"hasToonTexture"  : { type: "i", value: 0 }
 		}
-		// ---- MMD specific for cel shading(outline drawing and toon mapping)
 
 	] ),
 
-	vertexShader: [
-
-		"#define PHONG",
-
-		"varying vec3 vViewPosition;",
-
-		"#ifndef FLAT_SHADED",
-
-		"	varying vec3 vNormal;",
-
-		"#endif",
+	vertexShader: THREE.ShaderLib[ 'phong' ].vertexShader,
 
-		THREE.ShaderChunk[ "common" ],
-		THREE.ShaderChunk[ "uv_pars_vertex" ],
-		THREE.ShaderChunk[ "uv2_pars_vertex" ],
-		THREE.ShaderChunk[ "displacementmap_pars_vertex" ],
-		THREE.ShaderChunk[ "envmap_pars_vertex" ],
-		THREE.ShaderChunk[ "color_pars_vertex" ],
-		THREE.ShaderChunk[ "morphtarget_pars_vertex" ],
-		THREE.ShaderChunk[ "skinning_pars_vertex" ],
-		THREE.ShaderChunk[ "shadowmap_pars_vertex" ],
-		THREE.ShaderChunk[ "logdepthbuf_pars_vertex" ],
-		THREE.ShaderChunk[ "clipping_planes_pars_vertex" ],
+	// put toon mapping logic right before "void main() {...}"
+	fragmentShader: THREE.ShaderLib[ 'phong' ].fragmentShader.replace( /void\s+main\s*\(\s*\)/, [
 
-		// ---- MMD specific for outline drawing
-		"	uniform bool outlineDrawing;",
-		"	uniform float outlineThickness;",
-		// ---- MMD specific for outline drawing
-
-		"void main() {",
-
-			THREE.ShaderChunk[ "uv_vertex" ],
-			THREE.ShaderChunk[ "uv2_vertex" ],
-			THREE.ShaderChunk[ "color_vertex" ],
-
-			THREE.ShaderChunk[ "beginnormal_vertex" ],
-			THREE.ShaderChunk[ "morphnormal_vertex" ],
-			THREE.ShaderChunk[ "skinbase_vertex" ],
-			THREE.ShaderChunk[ "skinnormal_vertex" ],
-			THREE.ShaderChunk[ "defaultnormal_vertex" ],
-
-		"#ifndef FLAT_SHADED", // Normal computed with derivatives when FLAT_SHADED
-
-		"	vNormal = normalize( transformedNormal );",
-
-		"#endif",
-
-			THREE.ShaderChunk[ "begin_vertex" ],
-			THREE.ShaderChunk[ "displacementmap_vertex" ],
-			THREE.ShaderChunk[ "morphtarget_vertex" ],
-			THREE.ShaderChunk[ "skinning_vertex" ],
-			THREE.ShaderChunk[ "project_vertex" ],
-			THREE.ShaderChunk[ "logdepthbuf_vertex" ],
-			THREE.ShaderChunk[ "clipping_planes_vertex" ],
-
-		"	vViewPosition = - mvPosition.xyz;",
-
-			THREE.ShaderChunk[ "worldpos_vertex" ],
-			THREE.ShaderChunk[ "envmap_vertex" ],
-			THREE.ShaderChunk[ "shadowmap_vertex" ],
-
-		// ---- MMD specific for outline drawing
-		"	if ( outlineDrawing ) {",
-		"		float thickness = outlineThickness;",
-		"		float ratio = 1.0;", // TODO: support outline size ratio for each vertex
-		"		vec4 epos = projectionMatrix * modelViewMatrix * skinned;",
-		"		vec4 epos2 = projectionMatrix * modelViewMatrix * vec4( skinned.xyz + objectNormal, 1.0 );",
-		"		vec4 enorm = normalize( epos2 - epos );",
-		"		gl_Position = epos + enorm * thickness * epos.w * ratio;",
-		"	}",
-		// ---- MMD specific for outline drawing
-
-		"}"
-
-	].join( "\n" ),
-
-	fragmentShader: [
-
-		"#define PHONG",
-
-		"uniform vec3 diffuse;",
-		"uniform vec3 emissive;",
-		"uniform vec3 specular;",
-		"uniform float shininess;",
-		"uniform float opacity;",
-
-		THREE.ShaderChunk[ "common" ],
-		THREE.ShaderChunk[ "packing" ],
-		THREE.ShaderChunk[ "color_pars_fragment" ],
-		THREE.ShaderChunk[ "uv_pars_fragment" ],
-		THREE.ShaderChunk[ "uv2_pars_fragment" ],
-		THREE.ShaderChunk[ "map_pars_fragment" ],
-		THREE.ShaderChunk[ "alphamap_pars_fragment" ],
-		THREE.ShaderChunk[ "aomap_pars_fragment" ],
-		THREE.ShaderChunk[ "lightmap_pars_fragment" ],
-		THREE.ShaderChunk[ "emissivemap_pars_fragment" ],
-		THREE.ShaderChunk[ "envmap_pars_fragment" ],
-		THREE.ShaderChunk[ "fog_pars_fragment" ],
-		THREE.ShaderChunk[ "bsdfs" ],
-		THREE.ShaderChunk[ "lights_pars" ],
-		THREE.ShaderChunk[ "lights_phong_pars_fragment" ],
-		THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
-		THREE.ShaderChunk[ "bumpmap_pars_fragment" ],
-		THREE.ShaderChunk[ "normalmap_pars_fragment" ],
-		THREE.ShaderChunk[ "specularmap_pars_fragment" ],
-		THREE.ShaderChunk[ "logdepthbuf_pars_fragment" ],
-		THREE.ShaderChunk[ "clipping_planes_pars_fragment" ],
-
-		// ---- MMD specific for cel shading
-		"	uniform bool outlineDrawing;",
-		"	uniform vec3 outlineColor;",
-		"	uniform float outlineAlpha;",
 		"	uniform bool celShading;",
 		"	uniform sampler2D toonMap;",
 		"	uniform bool hasToonTexture;",
@@ -3738,52 +3627,9 @@ THREE.ShaderLib[ 'mmd' ] = {
 		"#define RE_Direct	RE_Direct_BlinnMMD",
 		// ---- MMD specific for toon mapping
 
-		"void main() {",
-
-		// ---- MMD specific for outline drawing
-		"	if ( outlineDrawing ) {",
-		"		gl_FragColor = vec4( outlineColor, outlineAlpha );",
-		"		return;",
-		"	}",
-		// ---- MMD specific for outline drawing
-
-			THREE.ShaderChunk[ "clipping_planes_fragment" ],
+		"void main()",
 
-		"	vec4 diffuseColor = vec4( diffuse, opacity );",
-		"	ReflectedLight reflectedLight = ReflectedLight( vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ), vec3( 0.0 ) );",
-		"	vec3 totalEmissiveRadiance = emissive;",
-
-			THREE.ShaderChunk[ "logdepthbuf_fragment" ],
-			THREE.ShaderChunk[ "map_fragment" ],
-			THREE.ShaderChunk[ "color_fragment" ],
-			THREE.ShaderChunk[ "alphamap_fragment" ],
-			THREE.ShaderChunk[ "alphatest_fragment" ],
-			THREE.ShaderChunk[ "specularmap_fragment" ],
-			THREE.ShaderChunk[ "normal_flip" ],
-			THREE.ShaderChunk[ "normal_fragment" ],
-			THREE.ShaderChunk[ "emissivemap_fragment" ],
-
-			// accumulation
-			THREE.ShaderChunk[ "lights_phong_fragment" ],
-			THREE.ShaderChunk[ "lights_template" ],
-
-			// modulation
-			THREE.ShaderChunk[ "aomap_fragment" ],
-
-			"vec3 outgoingLight = reflectedLight.directDiffuse + reflectedLight.indirectDiffuse + reflectedLight.directSpecular + reflectedLight.indirectSpecular + totalEmissiveRadiance;",
-
-			THREE.ShaderChunk[ "envmap_fragment" ],
-
-		"	gl_FragColor = vec4( outgoingLight, diffuseColor.a );",
-
-			THREE.ShaderChunk[ "premultiplied_alpha_fragment" ],
-			THREE.ShaderChunk[ "tonemapping_fragment" ],
-			THREE.ShaderChunk[ "encodings_fragment" ],
-			THREE.ShaderChunk[ "fog_fragment" ],
-
-		"}"
-
-	].join( "\n" )
+	].join( "\n" ) )
 
 };
 
@@ -3935,8 +3781,13 @@ THREE.MMDGrantSolver.prototype = {
 THREE.MMDHelper = function ( renderer ) {
 
 	this.renderer = renderer;
+
+	this.outlineEffect = null;
+
 	this.effect = null;
 
+	this.autoClear = true;
+
 	this.meshes = [];
 
 	this.doAnimation = true;
@@ -3959,15 +3810,10 @@ THREE.MMDHelper.prototype = {
 
 	init: function () {
 
-		this.initRender();
-
-	},
+		this.outlineEffect = new THREE.OutlineEffect( this.renderer );
 
-	initRender: function () {
-
-		this.renderer.autoClear = false;
-		this.renderer.autoClearColor = false;
-		this.renderer.autoClearDepth = false;
+		var size = this.renderer.getSize();
+		this.setSize( size.width, size.height );
 
 	},
 
@@ -3990,6 +3836,12 @@ THREE.MMDHelper.prototype = {
 
 	},
 
+	setSize: function ( width, height ) {
+
+		this.outlineEffect.setSize( width, height );
+
+	},
+
 	/*
 	 * Note: There may be a possibility that Outline wouldn't work well with Effect.
 	 *       In such a case, try to set doOutlineDrawing = false or
@@ -4106,7 +3958,7 @@ THREE.MMDHelper.prototype = {
 	},
 
 	/*
-	 * detect the longest duration among model, camera, and audio animation and then
+	 * detect the longest duration among model, camera, and audio animations and then
 	 * set it to them to sync.
 	 * TODO: touching private properties ( ._actions and ._clip ) so consider better way
 	 *       to access them for safe and modularity.
@@ -4298,187 +4150,135 @@ THREE.MMDHelper.prototype = {
 
 	render: function ( scene, camera ) {
 
-		this.renderer.clearColor();
-		this.renderer.clearDepth();
-		this.renderer.clear( true, true );
-
-		this.renderMain( scene, camera );
-
-		if ( this.doOutlineDrawing ) {
-
-			this.renderOutline( scene, camera );
-
-		}
-
-	},
-
-	renderMain: function ( scene, camera ) {
-
-		this.setupMainRendering();
-		this.callRender( scene, camera );
-
-	},
-
-	renderOutline: function () {
-
-		var invisibledObjects = [];
-		var setInvisible;
-		var restoreVisible;
-
-		return function renderOutline( scene, camera ) {
+		if ( this.effect === null ) {
 
-			var self = this;
+			if ( this.doOutlineDrawing ) {
 
-			if ( setInvisible === undefined ) {
+				this.outlineEffect.autoClear = this.autoClear;
+				this.outlineEffect.render( scene, camera );
 
-				setInvisible = function ( object ) {
+			} else {
 
-					if ( ! object.visible || ! object.layers.test( camera.layers ) ) return;
+				var currentAutoClear = this.renderer.autoClear;
+				this.renderer.autoClear = this.autoClear;
+				this.renderer.render( scene, camera );
+				this.renderer.autoClear = currentAutoClear;
 
-					// any types else to skip?
-					if ( object instanceof THREE.Scene ||
-					     object instanceof THREE.Bone ||
-					     object instanceof THREE.Light ||
-					     object instanceof THREE.Camera ||
-					     object instanceof THREE.Audio ||
-					     object instanceof THREE.AudioListener ) return;
+			}
 
-					if ( object instanceof THREE.SkinnedMesh ) {
+		} else {
 
-						for ( var i = 0, il = self.meshes.length; i < il; i ++ ) {
+			var currentAutoClear = this.renderer.autoClear;
+			this.renderer.autoClear = this.autoClear;
 
-							if ( self.meshes[ i ] === object ) return;
+			if ( this.doOutlineDrawing ) {
 
-						}
+				this.renderWithEffectAndOutline( scene, camera );
 
-					}
+			} else {
 
-					object.layers.mask &= ~ camera.layers.mask;
-					invisibledObjects.push( object );
-
-				};
+				this.effect.render( scene, camera );
 
 			}
 
-			if ( restoreVisible === undefined ) {
-
-				restoreVisible = function () {
-
-					for ( var i = 0, il = invisibledObjects.length; i < il; i ++ ) {
-
-						invisibledObjects[ i ].layers.mask |= camera.layers.mask;
-
-					}
-
-					invisibledObjects.length = 0;
+			this.renderer.autoClear = currentAutoClear;
 
-				};
-
-			}
+		}
 
-			scene.traverse( setInvisible );
+	},
 
-			var tmpEnabled = this.renderer.shadowMap.enabled;
-			this.renderer.shadowMap.enabled = false;
+	/*
+	 * Currently(r82 dev) there's no way to render with two Effects
+	 * then attempt to get them to coordinately run by myself.
+	 *
+	 * What this method does
+	 * 1. let OutlineEffect make outline materials (only once)
+	 * 2. render normally with effect
+	 * 3. set outline materials
+	 * 4. render outline with effect
+	 * 5. restore original materials
+	 */
+	renderWithEffectAndOutline: function ( scene, camera ) {
 
-			this.setupOutlineRendering();
-			this.callRender( scene, camera );
+		var hasOutlineMaterial = false;
 
-			this.renderer.shadowMap.enabled = tmpEnabled;
+		function checkIfObjectHasOutlineMaterial ( object ) {
 
-			restoreVisible();
+			if ( object.material === undefined ) return;
 
-		};
+			if ( object.userData.outlineMaterial !== undefined ) hasOutlineMaterial = true;
 
-	}(),
+		}
 
-	callRender: function ( scene, camera ) {
+		function setOutlineMaterial ( object ) {
 
-		if ( this.effect === null ) {
+			if ( object.material === undefined ) return;
 
-			this.renderer.render( scene, camera );
+			if ( object.userData.outlineMaterial === undefined ) return;
 
-		} else {
+			object.userData.originalMaterial = object.material;
 
-			this.effect.render( scene, camera );
+			object.material = object.userData.outlineMaterial;
 
 		}
 
-	},
+		function restoreOriginalMaterial ( object ) {
 
-	setupMainRendering: function () {
+			if ( object.material === undefined ) return;
 
-		for ( var i = 0; i < this.meshes.length; i++ ) {
+			if ( object.userData.originalMaterial === undefined ) return;
 
-			this.setupMainRenderingOneMesh( this.meshes[ i ] );
+			object.material = object.userData.originalMaterial;
 
 		}
 
-	},
+		return function renderWithEffectAndOutline( scene, camera ) {
 
-	setupMainRenderingOneMesh: function ( mesh ) {
+			hasOutlineMaterial = false;
 
-		for ( var i = 0; i < mesh.material.materials.length; i++ ) {
+			var forceClear = false;
 
-			var m = mesh.material.materials[ i ];
-			m.uniforms.outlineDrawing.value = 0;
-			m.visible = true;
+			scene.traverse( checkIfObjectHasOutlineMaterial );
 
-			if ( m.uniforms.opacity.value === 1.0 ) {
+			if ( ! hasOutlineMaterial ) {
 
-				m.side = THREE.FrontSide;
-				m.transparent = false;
+				this.outlineEffect.render( scene, camera );
 
-			} else {
+				forceClear = true;
 
-				m.side = THREE.DoubleSide;
-				m.transparent = true;
+				scene.traverse( checkIfObjectHasOutlineMaterial );
 
 			}
 
-			if ( m.textureTransparency === true || m.morphTransparency === true ) {
+			if ( hasOutlineMaterial ) {
 
-				m.transparent = true;
+				this.renderer.autoClear = this.autoClear || forceClear;
 
-			}
+				this.effect.render( scene, camera );
 
-		}
+				scene.traverse( setOutlineMaterial );
 
-	},
+				var currentShadowMapEnabled = this.renderer.shadowMap.enabled;
 
-	setupOutlineRendering: function () {
+				this.renderer.autoClear = false;
+				this.renderer.shadowMap.enabled = false;
 
-		for ( var i = 0; i < this.meshes.length; i++ ) {
+				this.effect.render( scene, camera );
 
-			this.setupOutlineRenderingOneMesh( this.meshes[ i ] );
+				this.renderer.shadowMap.enabled = currentShadowMapEnabled;
 
-		}
-
-	},
-
-	setupOutlineRenderingOneMesh: function ( mesh ) {
-
-		for ( var i = 0; i < mesh.material.materials.length; i++ ) {
-
-			var m = mesh.material.materials[ i ];
-			m.uniforms.outlineDrawing.value = 1;
-			m.side = THREE.BackSide;
-
-			if ( m.uniforms.outlineAlpha.value < 1.0 ) {
+				scene.traverse( restoreOriginalMaterial );
 
-				m.transparent = true;
-
-			}
-
-			if ( m.uniforms.outlineThickness.value === 0.0 ) {
+			} else {
 
-				m.visible = false;
+				this.outlineEffect.autoClear = this.autoClear || forceClear;
+				this.outlineEffect.render( scene, camera );
 
 			}
 
 		}
 
-	},
+	}(),
 
 	poseAsVpd: function ( mesh, vpd, params ) {
 

+ 3 - 2
examples/webgl_loader_mmd.html

@@ -37,8 +37,10 @@
 
 		<script src="js/libs/charsetencoder.min.js"></script>
 		<script src="js/libs/ammo.js"></script>
+
 		<script src="js/loaders/TGALoader.js"></script>
 		<script src="js/loaders/MMDLoader.js"></script>
+		<script src="js/effects/OutlineEffect.js"></script>
 		<script src="js/animation/CCDIKSolver.js"></script>
 		<script src="js/animation/MMDPhysics.js"></script>
 
@@ -151,7 +153,7 @@
 				camera.aspect = window.innerWidth / window.innerHeight;
 				camera.updateProjectionMatrix();
 
-				renderer.setSize( window.innerWidth, window.innerHeight );
+				helper.setSize( window.innerWidth, window.innerHeight );
 
 			}
 
@@ -185,7 +187,6 @@
 
 				} else {
 
-					renderer.clear();
 					renderer.render( scene, camera );
 
 				}

+ 3 - 2
examples/webgl_loader_mmd_audio.html

@@ -40,8 +40,10 @@
 
 		<script src="js/libs/charsetencoder.min.js"></script>
 		<script src="js/libs/ammo.js"></script>
+
 		<script src="js/loaders/TGALoader.js"></script>
 		<script src="js/loaders/MMDLoader.js"></script>
+		<script src="js/effects/OutlineEffect.js"></script>
 		<script src="js/animation/CCDIKSolver.js"></script>
 		<script src="js/animation/MMDPhysics.js"></script>
 
@@ -188,7 +190,7 @@
 				camera.aspect = window.innerWidth / window.innerHeight;
 				camera.updateProjectionMatrix();
 
-				renderer.setSize( window.innerWidth, window.innerHeight );
+				helper.setSize( window.innerWidth, window.innerHeight );
 
 			}
 
@@ -218,7 +220,6 @@
 
 				} else {
 
-					renderer.clear();
 					renderer.render( scene, camera );
 
 				}

+ 3 - 2
examples/webgl_loader_mmd_pose.html

@@ -37,8 +37,10 @@
 
 		<script src="js/libs/charsetencoder.min.js"></script>
 		<script src="js/libs/ammo.js"></script>
+
 		<script src="js/loaders/TGALoader.js"></script>
 		<script src="js/loaders/MMDLoader.js"></script>
+		<script src="js/effects/OutlineEffect.js"></script>
 		<script src="js/animation/CCDIKSolver.js"></script>
 		<script src="js/animation/MMDPhysics.js"></script>
 
@@ -293,7 +295,7 @@
 				camera.aspect = window.innerWidth / window.innerHeight;
 				camera.updateProjectionMatrix();
 
-				renderer.setSize( window.innerWidth, window.innerHeight );
+				helper.setSize( window.innerWidth, window.innerHeight );
 
 			}
 
@@ -326,7 +328,6 @@
 
 				} else {
 
-					renderer.clear();
 					renderer.render( scene, camera );
 
 				}