Browse Source

Merge pull request #9912 from takahirox/toonMap

Export Gradient Map from MMDLoader to let it reusable
Mr.doob 8 years ago
parent
commit
b16343500a

+ 1 - 0
examples/files.js

@@ -155,6 +155,7 @@ var files = {
 		"webgl_materials_variations_phong",
 		"webgl_materials_variations_standard",
 		"webgl_materials_variations_physical",
+		"webgl_materials_variations_toon",
 		"webgl_materials_video",
 		"webgl_materials_wireframe",
 		"webgl_math_spherical_distribution",

+ 2 - 1
examples/js/effects/OutlineEffect.js

@@ -64,6 +64,7 @@ THREE.OutlineEffect = function ( renderer, parameters ) {
 		MeshBasicMaterial: 'basic',
 		MeshLambertMaterial: 'lambert',
 		MeshPhongMaterial: 'phong',
+		MeshToonMaterial: 'toon',
 		MeshStandardMaterial: 'physical',
 		MeshPhysicalMaterial: 'physical'
 	};
@@ -93,7 +94,7 @@ THREE.OutlineEffect = function ( renderer, parameters ) {
 
 	var vertexShaderChunk2 = [
 
-		"#if ! defined( LAMBERT ) && ! defined( PHONG ) && ! defined( PHYSICAL )",
+		"#if ! defined( LAMBERT ) && ! defined( PHONG ) && ! defined( TOON ) && ! defined( PHYSICAL )",
 
 		"	#ifndef USE_ENVMAP",
 		"		vec3 objectNormal = normalize( normal );",

+ 55 - 128
examples/js/loaders/MMDLoader.js

@@ -106,15 +106,15 @@ THREE.MMDLoader.prototype.loadModel = function ( url, callback, onProgress, onEr
 
 	this.loadFileAsBuffer( url, function ( buffer ) {
 
-		callback( scope.createModel( buffer, modelExtension, texturePath ) );
+		callback( scope.createModel( buffer, modelExtension, texturePath, onProgress, onError ) );
 
 	}, onProgress, onError );
 
 };
 
-THREE.MMDLoader.prototype.createModel = function ( buffer, modelExtension, texturePath ) {
+THREE.MMDLoader.prototype.createModel = function ( buffer, modelExtension, texturePath, onProgress, onError ) {
 
-	return this.createMesh( this.parseModel( buffer, modelExtension ), texturePath );
+	return this.createMesh( this.parseModel( buffer, modelExtension ), texturePath, onProgress, onError );
 
 };
 
@@ -870,9 +870,11 @@ THREE.MMDLoader.prototype.createMesh = function ( model, texturePath, onProgress
 
 	var initMaterials = function () {
 
-		var textures = [];
+		var textures = {};
 		var textureLoader = new THREE.TextureLoader( scope.manager );
 		var tgaLoader = new THREE.TGALoader( scope.manager );
+		var canvas = document.createElement( 'canvas' );
+		var context = canvas.getContext( '2d' );
 		var offset = 0;
 		var materialParams = [];
 
@@ -907,6 +909,8 @@ THREE.MMDLoader.prototype.createMesh = function ( model, texturePath, onProgress
 
 			}
 
+			if ( textures[ fullPath ] !== undefined ) return fullPath;
+
 			var loader = THREE.Loader.Handlers.get( fullPath );
 
 			if ( loader === null ) {
@@ -917,6 +921,28 @@ THREE.MMDLoader.prototype.createMesh = function ( model, texturePath, onProgress
 
 			var texture = loader.load( fullPath, function ( t ) {
 
+				// MMD toon texture is Axis-Y oriented
+				// but Three.js gradient map is Axis-X oriented.
+				// So here replaces the toon texture image with the rotated one.
+				if ( params.isToonTexture === true ) {
+
+					var image = t.image;
+					var width = image.width;
+					var height = image.height;
+
+					canvas.width = width;
+					canvas.height = height;
+
+					context.clearRect( 0, 0, width, height );
+					context.translate( width / 2.0, height / 2.0 );
+					context.rotate( 0.5 * Math.PI );  // 90.0 * Math.PI / 180.0
+					context.translate( -width / 2.0, -height / 2.0 );
+					context.drawImage( image, 0, 0 );
+
+					t.image = context.getImageData( 0, 0, width, height );
+
+				}
+
 				t.flipY = false;
 				t.wrapS = THREE.RepeatWrapping;
 				t.wrapT = THREE.RepeatWrapping;
@@ -935,15 +961,13 @@ THREE.MMDLoader.prototype.createMesh = function ( model, texturePath, onProgress
 
 				delete texture.readyCallbacks;
 
-			} );
+			}, onProgress, onError );
 
 			texture.readyCallbacks = [];
 
-			var uuid = THREE.Math.generateUUID();
-
-			textures[ uuid ] = texture;
+			textures[ fullPath ] = texture;
 
-			return uuid;
+			return fullPath;
 
 		}
 
@@ -974,13 +998,13 @@ THREE.MMDLoader.prototype.createMesh = function ( model, texturePath, onProgress
 			/*
 			 * Color
 			 *
-			 * MMD         MeshPhongMaterial
+			 * MMD         MeshToonMaterial
 			 * diffuse  -  color
 			 * specular -  specular
 			 * ambient  -  emissive * a
 			 *               (a = 1.0 without map texture or 0.2 with map texture)
 			 *
-			 * MeshPhongMaterial doesn't have ambient. Set it to emissive instead.
+			 * MeshToonMaterial doesn't have ambient. Set it to emissive instead.
 			 * It'll be too bright if material has map texture so using coef 0.2.
 			 */
 			params.color = new THREE.Color( m.diffuse[ 0 ], m.diffuse[ 1 ], m.diffuse[ 2 ] );
@@ -1084,17 +1108,11 @@ THREE.MMDLoader.prototype.createMesh = function ( model, texturePath, onProgress
 
 		}
 
-		var shader = THREE.ShaderLib[ 'mmd' ];
-
 		for ( var i = 0; i < materialParams.length; i++ ) {
 
 			var p = materialParams[ i ];
 			var p2 = model.materials[ i ];
-			var m = new THREE.ShaderMaterial( {
-				uniforms: THREE.UniformsUtils.clone( shader.uniforms ),
-				vertexShader: shader.vertexShader,
-				fragmentShader: shader.fragmentShader
-			} );
+			var m = new THREE.MeshToonMaterial();
 
 			geometry.addGroup( p.faceOffset * 3, p.faceNum * 3, i );
 
@@ -1231,7 +1249,6 @@ THREE.MMDLoader.prototype.createMesh = function ( model, texturePath, onProgress
 				}
 
 				m.map = getTexture( p.map, textures );
-				m.uniforms.map.value = m.map;
 				checkTextureTransparency( m );
 
 			}
@@ -1239,7 +1256,6 @@ THREE.MMDLoader.prototype.createMesh = function ( model, texturePath, onProgress
 			if ( p.envMap !== undefined ) {
 
 				m.envMap = getTexture( p.envMap, textures );
-				m.uniforms.envMap.value = m.envMap;
 				m.combine = p.envMapType;
 
 				// TODO: WebGLRenderer should automatically update?
@@ -1251,17 +1267,17 @@ THREE.MMDLoader.prototype.createMesh = function ( model, texturePath, onProgress
 
 			}
 
-			m.uniforms.opacity.value = p.opacity;
-			m.uniforms.diffuse.value.copy( p.color );
+			m.opacity = p.opacity;
+			m.color = p.color;
 
 			if ( p.emissive !== undefined ) {
 
-				m.uniforms.emissive.value.copy( p.emissive );
+				m.emissive = p.emissive;
 
 			}
 
-			m.uniforms.specular.value.copy( p.specular );
-			m.uniforms.shininess.value = Math.max( p.shininess, 1e-4 ); // to prevent pow( 0.0, 0.0 )
+			m.specular = p.specular;
+			m.shininess = Math.max( p.shininess, 1e-4 ); // to prevent pow( 0.0, 0.0 )
 
 			if ( model.metadata.format === 'pmd' ) {
 
@@ -1285,21 +1301,9 @@ THREE.MMDLoader.prototype.createMesh = function ( model, texturePath, onProgress
 
 				if ( m.outlineParameters.thickness === 0.0 ) m.outlineParameters.visible = false;
 
-				m.uniforms.toonMap.value = textures[ p2.toonIndex ];
-				m.uniforms.celShading.value = 1;
-
-				if ( p2.toonIndex === -1 ) {
-
-					m.uniforms.hasToonTexture.value = 0;
-
-				} else {
-
-					var n = model.toonTextures[ p2.toonIndex ].fileName;
-					var uuid = loadTexture( n, { defaultTexturePath: isDefaultToonTexture( n ) } );
-					m.uniforms.toonMap.value = textures[ uuid ];
-					m.uniforms.hasToonTexture.value = 1;
-
-				}
+				var toonFileName = ( p2.toonIndex === -1 ) ? 'toon00.bmp' : model.toonTextures[ p2.toonIndex ].fileName;
+				var uuid = loadTexture( toonFileName, { isToonTexture: true, defaultTexturePath: isDefaultToonTexture( toonFileName ) } );
+				m.gradientMap = getTexture( uuid, textures );
 
 			} else {
 
@@ -1311,33 +1315,24 @@ THREE.MMDLoader.prototype.createMesh = function ( model, texturePath, onProgress
 
 				if ( ( p2.flag & 0x10 ) === 0 || m.outlineParameters.thickness === 0.0 ) m.outlineParameters.visible = false;
 
-				m.uniforms.celShading.value = 1;
+				var toonFileName, isDefaultToon;
 
-				if ( p2.toonIndex === -1 ) {
+				if ( p2.toonIndex === -1 || p2.toonFlag !== 0 ) {
 
-					m.uniforms.hasToonTexture.value = 0;
+					var num = p2.toonIndex + 1;
+					toonFileName = 'toon' + ( num < 10 ? '0' + num : num ) + '.bmp';
+					isDefaultToon = true;
 
 				} else {
 
-					if ( p2.toonFlag === 0 ) {
-
-						var n = model.textures[ p2.toonIndex ];
-						var uuid = loadTexture( n );
-						m.uniforms.toonMap.value = textures[ uuid ];
-
-					} else {
-
-						var num = p2.toonIndex + 1;
-						var fileName = 'toon' + ( num < 10 ? '0' + num : num ) + '.bmp';
-						var uuid = loadTexture( fileName, { defaultTexturePath: true } );
-						m.uniforms.toonMap.value = textures[ uuid ];
-
-					}
-
-					m.uniforms.hasToonTexture.value = 1;
+					toonFileName = model.textures[ p2.toonIndex ];
+					isDefaultToon = false;
 
 				}
 
+				var uuid = loadTexture( toonFileName, { isToonTexture: true, defaultTexturePath: isDefaultToon } );
+				m.gradientMap = getTexture( uuid, textures );
+
 			}
 
 			material.materials.push( m );
@@ -1366,7 +1361,7 @@ THREE.MMDLoader.prototype.createMesh = function ( model, texturePath, onProgress
 
 					var m = material.materials[ e.index ];
 
-					if ( m.uniforms.opacity.value !== e.diffuse[ 3 ] ) {
+					if ( m.opacity !== e.diffuse[ 3 ] ) {
 
 						m.transparent = true;
 
@@ -1992,74 +1987,6 @@ THREE.MMDLoader.CubicBezierInterpolation.prototype._calculate = function( x1, x2
 
 };
 
-/*
- * Shaders are copied from MeshPhongMaterial and then MMD spcific codes are inserted.
- * Keep shaders updated on MeshPhongMaterial.
- */
-THREE.ShaderLib[ 'mmd' ] = {
-
-	uniforms: THREE.UniformsUtils.merge( [
-
-		THREE.ShaderLib[ 'phong' ].uniforms,
-
-		// MMD specific for toon mapping
-		{
-			"celShading"      : { type: "i", value: 0 },
-			"toonMap"         : { type: "t", value: null },
-			"hasToonTexture"  : { type: "i", value: 0 }
-		}
-
-	] ),
-
-	vertexShader: THREE.ShaderLib[ 'phong' ].vertexShader,
-
-	// put toon mapping logic right before "void main() {...}"
-	fragmentShader: THREE.ShaderLib[ 'phong' ].fragmentShader.replace( /void\s+main\s*\(\s*\)/, [
-
-		"	uniform bool celShading;",
-		"	uniform sampler2D toonMap;",
-		"	uniform bool hasToonTexture;",
-
-		"	vec3 toon ( vec3 lightDirection, vec3 norm ) {",
-		"		if ( ! hasToonTexture ) {",
-		"			return vec3( 1.0 );",
-		"		}",
-		"		vec2 coord = vec2( 0.0, 0.5 * ( 1.0 - dot( lightDirection, norm ) ) );",
-		"		return texture2D( toonMap, coord ).rgb;",
-		"	}",
-
-		// redefine for MMD
-		"#undef RE_Direct",
-		"void RE_Direct_BlinnMMD( const in IncidentLight directLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {",
-		"	float dotNL = saturate( dot( geometry.normal, directLight.direction ) );",
-		"	vec3 irradiance = dotNL * directLight.color;",
-
-		"	#ifndef PHYSICALLY_CORRECT_LIGHTS",
-
-		"		irradiance *= PI; // punctual light",
-
-		"	#endif",
-
-		// ---- MMD specific for toon mapping
-		"	if ( celShading ) {",
-		"		reflectedLight.directDiffuse += material.diffuseColor * directLight.color * toon( directLight.direction, geometry.normal );",
-		"	} else {",
-		"		reflectedLight.directDiffuse += irradiance * BRDF_Diffuse_Lambert( material.diffuseColor );",
-		"	}",
-		// ---- MMD specific for toon mapping
-
-		"	reflectedLight.directSpecular += irradiance * BRDF_Specular_BlinnPhong( directLight, geometry, material.specularColor, material.specularShininess ) * material.specularStrength;",
-		"}",
-		// ---- MMD specific for toon mapping
-		"#define RE_Direct	RE_Direct_BlinnMMD",
-		// ---- MMD specific for toon mapping
-
-		"void main()"
-
-	].join( "\n" ) )
-
-};
-
 THREE.MMDAudioManager = function ( audio, listener, p ) {
 
 	var params = ( p === null || p === undefined ) ? {} : p;

+ 30 - 2
examples/webgl_loader_mmd.html

@@ -158,6 +158,27 @@
 
 				window.addEventListener( 'resize', onWindowResize, false );
 
+				var phongMaterials;
+				var originalMaterials;
+
+				function makePhongMaterials ( materials ) {
+
+					var array = [];
+
+					for ( var i = 0, il = materials.length; i < il; i ++ ) {
+
+						var m = new THREE.MeshPhongMaterial();
+						m.copy( materials[ i ] );
+						m.needsUpdate = true;
+
+						array.push( m );
+
+					}
+
+					phongMaterials = new THREE.MultiMaterial( array );
+
+				}
+
 				function initGui () {
 
 					var api = {
@@ -178,9 +199,16 @@
 
 					gui.add( api, 'gradient mapping' ).onChange( function () {
 
-						for ( var i = 0, il = mesh.material.materials.length; i < il; i ++ ) {
+						if ( originalMaterials === undefined ) originalMaterials = mesh.material;
+						if ( phongMaterials === undefined ) makePhongMaterials( mesh.material.materials );
+
+						if ( api[ 'gradient mapping' ] ) {
+
+							mesh.material = originalMaterials;
+
+						} else {
 
-							var material = mesh.material.materials[ i ].uniforms.celShading.value = api[ 'gradient mapping' ] ? 1 : 0;
+							mesh.material = phongMaterials;
 
 						}
 

+ 239 - 0
examples/webgl_materials_variations_toon.html

@@ -0,0 +1,239 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - materials</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 {
+				color: #fff;
+				font-family:Monospace;
+				font-size:13px;
+				text-align:center;
+
+				background-color: #000;
+				margin: 0px;
+				overflow: hidden;
+			}
+
+			#info {
+				position: absolute;
+				top: 0px; width: 100%;
+				padding: 5px;
+			}
+		</style>
+	</head>
+	<body>
+
+		<div id="container"></div>
+		<div id="info"><a href="http://threejs.org" target="_blank">three.js</a> - Toon Material Variantions w/ OutlineEffect by <a href="http://clara.io/" target="_blank">Ben Houston</a>.</div>
+
+		<script src="../build/three.js"></script>
+		<script src="js/controls/OrbitControls.js"></script>
+
+		<script src="js/effects/OutlineEffect.js"></script>
+
+		<script src="js/Detector.js"></script>
+		<script src="js/libs/stats.min.js"></script>
+
+		<script>
+
+			if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
+
+			var container, stats;
+
+			var camera, scene, renderer, controls, effect;
+			var particleLight;
+
+			var loader = new THREE.FontLoader();
+			loader.load( 'fonts/gentilis_regular.typeface.json', function ( font ) {
+
+				init( font );
+				animate();
+
+			} );
+
+			function init( font ) {
+
+				container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 2000 );
+				camera.position.set( 0.0, 400, 400 * 3.5 );
+
+				//
+
+				var reflectionCube = new THREE.CubeTextureLoader()
+					.setPath( 'textures/cube/SwedishRoyalCastle/' )
+					.load( [ 'px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg' ] );
+				reflectionCube.format = THREE.RGBFormat;
+
+				scene = new THREE.Scene();
+				scene.background = reflectionCube;
+
+				// Materials
+
+				var imgTexture = new THREE.TextureLoader().load( "textures/planets/moon_1024.jpg" );
+				imgTexture.wrapS = imgTexture.wrapT = THREE.RepeatWrapping;
+				imgTexture.anisotropy = 16;
+				imgTexture = null;
+
+				var shininess = 50, specular = 0x333333, bumpScale = 1, shading = THREE.SmoothShading;
+
+				var materials = [];
+
+				var cubeWidth = 400;
+				var numberOfSphersPerSide = 5;
+				var sphereRadius = ( cubeWidth / numberOfSphersPerSide ) * 0.8 * 0.5;
+				var stepSize = 1.0 / numberOfSphersPerSide;
+
+				var geometry = new THREE.SphereBufferGeometry( sphereRadius, 32, 16 );
+
+				for ( var alpha = 0, alphaIndex = 0; alpha <= 1.0; alpha += stepSize, alphaIndex ++ ) {
+
+					var specularShininess = Math.pow( 2, alpha * 10 );
+
+					for ( var beta = 0; beta <= 1.0; beta += stepSize ) {
+
+						var specularColor = new THREE.Color( beta * 0.2, beta * 0.2, beta * 0.2 );
+
+						for ( var gamma = 0; gamma <= 1.0; gamma += stepSize ) {
+
+							// basic monochromatic energy preservation
+							var diffuseColor = new THREE.Color().setHSL( alpha, 0.5, gamma * 0.5 ).multiplyScalar( 1 - beta * 0.2 );
+
+							var material = new THREE.MeshToonMaterial( {
+								map: imgTexture,
+								bumpMap: imgTexture,
+								bumpScale: bumpScale,
+								color: diffuseColor,
+								specular: specularColor,
+								reflectivity: beta,
+								shininess: specularShininess,
+								shading: THREE.SmoothShading,
+								envMap: alphaIndex % 2 === 0 ? null : reflectionCube
+							} );
+
+							var mesh = new THREE.Mesh( geometry, material );
+
+							mesh.position.x = alpha * 400 - 200;
+							mesh.position.y = beta * 400 - 200;
+							mesh.position.z = gamma * 400 - 200;
+
+							scene.add( mesh );
+
+						}
+
+					}
+
+				}
+
+				function addLabel( name, location ) {
+
+					var textGeo = new THREE.TextGeometry( name, {
+
+						font: font,
+
+						size: 20,
+						height: 1,
+						curveSegments: 1
+
+					});
+
+					var textMaterial = new THREE.MeshBasicMaterial( { color: 0xffffff } );
+					var textMesh = new THREE.Mesh( textGeo, textMaterial );
+					textMesh.position.copy( location );
+					scene.add( textMesh );
+
+				}
+
+				addLabel( "-shininess", new THREE.Vector3( -350, 0, 0 ) );
+				addLabel( "+shininess", new THREE.Vector3( 350, 0, 0 ) );
+
+				addLabel( "-specular, -reflectivity", new THREE.Vector3( 0, -300, 0 ) );
+				addLabel( "+specular, +reflectivity", new THREE.Vector3( 0, 300, 0 ) );
+
+				addLabel( "-diffuse", new THREE.Vector3( 0, 0, -300 ) );
+				addLabel( "+diffuse", new THREE.Vector3( 0, 0, 300 ) );
+
+				particleLight = new THREE.Mesh( new THREE.SphereBufferGeometry( 4, 8, 8 ), new THREE.MeshBasicMaterial( { color: 0xffffff } ) );
+				scene.add( particleLight );
+
+				// Lights
+
+				scene.add( new THREE.AmbientLight( 0x222222 ) );
+
+				var directionalLight = new THREE.DirectionalLight( 0xffffff, 1 );
+				directionalLight.position.set( 1, 1, 1 ).normalize();
+				scene.add( directionalLight );
+
+				var pointLight = new THREE.PointLight( 0xffffff, 2, 800 );
+				particleLight.add( pointLight );
+
+				//
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				container.appendChild( renderer.domElement );
+
+				renderer.gammaInput = true;
+				renderer.gammaOutput = true;
+
+				effect = new THREE.OutlineEffect( renderer );
+
+				//
+
+				stats = new Stats();
+				container.appendChild( stats.dom );
+
+				controls = new THREE.OrbitControls( camera );
+				controls.target.set( 0, 0, 0 );
+				controls.update();
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			//
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				stats.begin();
+				render();
+				stats.end();
+
+			}
+
+			function render() {
+
+				var timer = Date.now() * 0.00025;
+
+				//camera.position.x = Math.cos( timer ) * 800;
+				//camera.position.z = Math.sin( timer ) * 800;
+
+				camera.lookAt( scene.position );
+
+				particleLight.position.x = Math.sin( timer * 7 ) * 300;
+				particleLight.position.y = Math.cos( timer * 5 ) * 400;
+				particleLight.position.z = Math.cos( timer * 3 ) * 300;
+
+				effect.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 2 - 0
src/loaders/MaterialLoader.js

@@ -142,6 +142,8 @@ Object.assign( MaterialLoader.prototype, {
 		if ( json.aoMap !== undefined ) material.aoMap = getTexture( json.aoMap );
 		if ( json.aoMapIntensity !== undefined ) material.aoMapIntensity = json.aoMapIntensity;
 
+		if ( json.gradientMap !== undefined ) material.gradientMap = getTexture( json.gradientMap );
+
 		// MultiMaterial
 
 		if ( json.materials !== undefined ) {

+ 6 - 0
src/materials/Material.js

@@ -202,6 +202,12 @@ Material.prototype = {
 
 		}
 
+		if ( this.gradientMap && this.gradientMap.isTexture ) {
+
+			data.gradientMap = this.gradientMap.toJSON( meta ).uuid;
+
+		}
+
 		if ( this.size !== undefined ) data.size = this.size;
 		if ( this.sizeAttenuation !== undefined ) data.sizeAttenuation = this.sizeAttenuation;
 

+ 1 - 0
src/materials/Materials.js

@@ -7,6 +7,7 @@ export { MultiMaterial } from './MultiMaterial.js';
 export { MeshPhysicalMaterial } from './MeshPhysicalMaterial.js';
 export { MeshStandardMaterial } from './MeshStandardMaterial.js';
 export { MeshPhongMaterial } from './MeshPhongMaterial.js';
+export { MeshToonMaterial } from './MeshToonMaterial.js';
 export { MeshNormalMaterial } from './MeshNormalMaterial.js';
 export { MeshLambertMaterial } from './MeshLambertMaterial.js';
 export { MeshDepthMaterial } from './MeshDepthMaterial.js';

+ 20 - 0
src/renderers/WebGLRenderer.js

@@ -623,6 +623,7 @@ function WebGLRenderer( parameters ) {
 			_gl.bindBuffer( _gl.ARRAY_BUFFER, buffers.normal );
 
 			if ( ! material.isMeshPhongMaterial &&
+				! material.isMeshToonMaterial &&
 				! material.isMeshStandardMaterial &&
 				material.shading === FlatShading ) {
 
@@ -1775,6 +1776,7 @@ function WebGLRenderer( parameters ) {
 
 			if ( material.isShaderMaterial ||
 				material.isMeshPhongMaterial ||
+				material.isMeshToonMaterial ||
 				material.isMeshStandardMaterial ||
 				material.envMap ) {
 
@@ -1790,6 +1792,7 @@ function WebGLRenderer( parameters ) {
 			}
 
 			if ( material.isMeshPhongMaterial ||
+				material.isMeshToonMaterial ||
 				material.isMeshLambertMaterial ||
 				material.isMeshBasicMaterial ||
 				material.isMeshStandardMaterial ||
@@ -1862,6 +1865,7 @@ function WebGLRenderer( parameters ) {
 			if ( material.isMeshBasicMaterial ||
 				material.isMeshLambertMaterial ||
 				material.isMeshPhongMaterial ||
+				material.isMeshToonMaterial ||
 				material.isMeshStandardMaterial ||
 				material.isMeshDepthMaterial ) {
 
@@ -1892,6 +1896,10 @@ function WebGLRenderer( parameters ) {
 
 				refreshUniformsPhong( m_uniforms, material );
 
+			} else if ( material.isMeshToonMaterial ) {
+
+				refreshUniformsToon( m_uniforms, material );
+
 			} else if ( material.isMeshPhysicalMaterial ) {
 
 				refreshUniformsPhysical( m_uniforms, material );
@@ -2138,6 +2146,18 @@ function WebGLRenderer( parameters ) {
 
 	}
 
+	function refreshUniformsToon( uniforms, material ) {
+
+		refreshUniformsPhong( uniforms, material );
+
+		if ( material.gradientMap ) {
+
+			uniforms.gradientMap.value = material.gradientMap;
+
+		}
+
+	}
+
 	function refreshUniformsStandard( uniforms, material ) {
 
 		uniforms.roughness.value = material.roughness;

+ 6 - 0
src/renderers/shaders/ShaderChunk.js

@@ -30,6 +30,7 @@ import envmap_pars_vertex from './ShaderChunk/envmap_pars_vertex.glsl';
 import envmap_vertex from './ShaderChunk/envmap_vertex.glsl';
 import fog_fragment from './ShaderChunk/fog_fragment.glsl';
 import fog_pars_fragment from './ShaderChunk/fog_pars_fragment.glsl';
+import gradientmap_pars_fragment from './ShaderChunk/gradientmap_pars_fragment.glsl';
 import lightmap_fragment from './ShaderChunk/lightmap_fragment.glsl';
 import lightmap_pars_fragment from './ShaderChunk/lightmap_pars_fragment.glsl';
 import lights_lambert_vertex from './ShaderChunk/lights_lambert_vertex.glsl';
@@ -98,6 +99,8 @@ import meshphong_frag from './ShaderLib/meshphong_frag.glsl';
 import meshphong_vert from './ShaderLib/meshphong_vert.glsl';
 import meshphysical_frag from './ShaderLib/meshphysical_frag.glsl';
 import meshphysical_vert from './ShaderLib/meshphysical_vert.glsl';
+import meshtoon_frag from './ShaderLib/meshtoon_frag.glsl';
+import meshtoon_vert from './ShaderLib/meshtoon_vert.glsl';
 import normal_frag from './ShaderLib/normal_frag.glsl';
 import normal_vert from './ShaderLib/normal_vert.glsl';
 import points_frag from './ShaderLib/points_frag.glsl';
@@ -138,6 +141,7 @@ export var ShaderChunk = {
 	envmap_vertex: envmap_vertex,
 	fog_fragment: fog_fragment,
 	fog_pars_fragment: fog_pars_fragment,
+	gradientmap_pars_fragment: gradientmap_pars_fragment,
 	lightmap_fragment: lightmap_fragment,
 	lightmap_pars_fragment: lightmap_pars_fragment,
 	lights_lambert_vertex: lights_lambert_vertex,
@@ -206,6 +210,8 @@ export var ShaderChunk = {
 	meshphong_vert: meshphong_vert,
 	meshphysical_frag: meshphysical_frag,
 	meshphysical_vert: meshphysical_vert,
+	meshtoon_frag: meshtoon_frag,
+	meshtoon_vert: meshtoon_vert,
 	normal_frag: normal_frag,
 	normal_vert: normal_vert,
 	points_frag: points_frag,

+ 1 - 1
src/renderers/shaders/ShaderChunk/clipping_planes_pars_fragment.glsl

@@ -1,6 +1,6 @@
 #if NUM_CLIPPING_PLANES > 0
 
-	#if ! defined( PHYSICAL ) && ! defined( PHONG )
+	#if ! defined( PHYSICAL ) && ! defined( PHONG ) && ! defined( TOON )
 		varying vec3 vViewPosition;
 	#endif
 

+ 1 - 1
src/renderers/shaders/ShaderChunk/clipping_planes_pars_vertex.glsl

@@ -1,3 +1,3 @@
-#if NUM_CLIPPING_PLANES > 0 && ! defined( PHYSICAL ) && ! defined( PHONG )
+#if NUM_CLIPPING_PLANES > 0 && ! defined( PHYSICAL ) && ! defined( PHONG ) && ! defined( TOON )
 	varying vec3 vViewPosition;
 #endif

+ 1 - 1
src/renderers/shaders/ShaderChunk/clipping_planes_vertex.glsl

@@ -1,4 +1,4 @@
-#if NUM_CLIPPING_PLANES > 0 && ! defined( PHYSICAL ) && ! defined( PHONG )
+#if NUM_CLIPPING_PLANES > 0 && ! defined( PHYSICAL ) && ! defined( PHONG ) && ! defined( TOON )
 	vViewPosition = - mvPosition.xyz;
 #endif
 

+ 1 - 1
src/renderers/shaders/ShaderChunk/envmap_fragment.glsl

@@ -1,6 +1,6 @@
 #ifdef USE_ENVMAP
 
-	#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )
+	#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( TOON )
 
 		vec3 cameraToVertex = normalize( vWorldPosition - cameraPosition );
 

+ 2 - 2
src/renderers/shaders/ShaderChunk/envmap_pars_fragment.glsl

@@ -5,7 +5,7 @@
 
 #ifdef USE_ENVMAP
 
-	#if ! defined( PHYSICAL ) && ( defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) )
+	#if ! defined( PHYSICAL ) && ( defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( TOON ) )
 		varying vec3 vWorldPosition;
 	#endif
 
@@ -16,7 +16,7 @@
 	#endif
 	uniform float flipEnvMap;
 
-	#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( PHYSICAL )
+	#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( TOON ) || defined( PHYSICAL )
 		uniform float refractionRatio;
 	#else
 		varying vec3 vReflect;

+ 1 - 1
src/renderers/shaders/ShaderChunk/envmap_pars_vertex.glsl

@@ -1,6 +1,6 @@
 #ifdef USE_ENVMAP
 
-	#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )
+	#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( TOON )
 		varying vec3 vWorldPosition;
 
 	#else

+ 1 - 1
src/renderers/shaders/ShaderChunk/envmap_vertex.glsl

@@ -1,6 +1,6 @@
 #ifdef USE_ENVMAP
 
-	#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG )
+	#if defined( USE_BUMPMAP ) || defined( USE_NORMALMAP ) || defined( PHONG ) || defined( TOON )
 
 		vWorldPosition = worldPosition.xyz;
 

+ 24 - 0
src/renderers/shaders/ShaderChunk/gradientmap_pars_fragment.glsl

@@ -0,0 +1,24 @@
+#ifdef TOON
+
+	uniform sampler2D gradientMap;
+
+	vec3 getGradientIrradiance( vec3 normal, vec3 lightDirection ) {
+
+		// dotNL will be from -1.0 to 1.0
+		float dotNL = dot( normal, lightDirection );
+		vec2 coord = vec2( dotNL * 0.5 + 0.5, 0.0 );
+
+		#ifdef USE_GRADIENTMAP
+
+			return texture2D( gradientMap, coord ).rgb;
+
+		#else
+
+			return ( coord.x < 0.7 ) ? vec3( 0.7 ) : vec3( 1.0 );
+
+		#endif
+
+
+	}
+
+#endif

+ 9 - 2
src/renderers/shaders/ShaderChunk/lights_phong_pars_fragment.glsl

@@ -44,9 +44,16 @@ struct BlinnPhongMaterial {
 
 void RE_Direct_BlinnPhong( const in IncidentLight directLight, const in GeometricContext geometry, const in BlinnPhongMaterial material, inout ReflectedLight reflectedLight ) {
 
-	float dotNL = saturate( dot( geometry.normal, directLight.direction ) );
+	#ifdef TOON
 
-	vec3 irradiance = dotNL * directLight.color;
+		vec3 irradiance = getGradientIrradiance( geometry.normal, directLight.direction ) * directLight.color;
+
+	#else
+
+		float dotNL = saturate( dot( geometry.normal, directLight.direction ) );
+		vec3 irradiance = dotNL * directLight.color;
+
+	#endif
 
 	#ifndef PHYSICALLY_CORRECT_LIGHTS
 

+ 1 - 1
src/renderers/shaders/ShaderChunk/worldpos_vertex.glsl

@@ -1,4 +1,4 @@
-#if defined( USE_ENVMAP ) || defined( PHONG ) || defined( PHYSICAL ) || defined( LAMBERT ) || defined ( USE_SHADOWMAP )
+#if defined( USE_ENVMAP ) || defined( PHONG ) || defined( TOON ) || defined( PHYSICAL ) || defined( LAMBERT ) || defined ( USE_SHADOWMAP )
 
 	#ifdef USE_SKINNING
 

+ 25 - 0
src/renderers/shaders/ShaderLib.js

@@ -68,6 +68,31 @@ var ShaderLib = {
 
 	},
 
+	toon: {
+
+		uniforms: Object.assign( {},
+			UniformsLib.common,
+			UniformsLib.aomap,
+			UniformsLib.lightmap,
+			UniformsLib.emissivemap,
+			UniformsLib.bumpmap,
+			UniformsLib.normalmap,
+			UniformsLib.displacementmap,
+			UniformsLib.gradientmap,
+			UniformsLib.fog,
+			UniformsLib.lights,
+			{
+				emissive : { value: new Color( 0x000000 ) },
+				specular : { value: new Color( 0x111111 ) },
+				shininess: { value: 30 }
+			}
+		),
+
+		vertexShader: ShaderChunk.meshtoon_vert,
+		fragmentShader: ShaderChunk.meshtoon_frag
+
+	},
+
 	standard: {
 
 		uniforms: Object.assign( {},

+ 6 - 0
src/renderers/shaders/UniformsLib.js

@@ -81,6 +81,12 @@ var UniformsLib = {
 
 	},
 
+	gradientmap: {
+
+		gradientMap: { value: null }
+
+	},
+
 	fog: {
 
 		fogDensity: { value: 0.00025 },

+ 2 - 0
src/renderers/webgl/WebGLProgram.js

@@ -446,6 +446,8 @@ function WebGLProgram( renderer, code, material, parameters ) {
 			parameters.alphaMap ? '#define USE_ALPHAMAP' : '',
 			parameters.vertexColors ? '#define USE_COLOR' : '',
 
+			parameters.gradientMap ? '#define USE_GRADIENTMAP' : '',
+
 			parameters.flatShading ? '#define FLAT_SHADED' : '',
 
 			parameters.doubleSided ? '#define DOUBLE_SIDED' : '',

+ 4 - 1
src/renderers/webgl/WebGLPrograms.js

@@ -15,6 +15,7 @@ function WebGLPrograms( renderer, capabilities ) {
 		MeshBasicMaterial: 'basic',
 		MeshLambertMaterial: 'lambert',
 		MeshPhongMaterial: 'phong',
+		MeshToonMaterial: 'toon',
 		MeshStandardMaterial: 'physical',
 		MeshPhysicalMaterial: 'physical',
 		LineBasicMaterial: 'basic',
@@ -25,7 +26,7 @@ function WebGLPrograms( renderer, capabilities ) {
 	var parameterNames = [
 		"precision", "supportsVertexTextures", "map", "mapEncoding", "envMap", "envMapMode", "envMapEncoding",
 		"lightMap", "aoMap", "emissiveMap", "emissiveMapEncoding", "bumpMap", "normalMap", "displacementMap", "specularMap",
-		"roughnessMap", "metalnessMap",
+		"roughnessMap", "metalnessMap", "gradientMap",
 		"alphaMap", "combine", "vertexColors", "fog", "useFog", "fogExp",
 		"flatShading", "sizeAttenuation", "logarithmicDepthBuffer", "skinning",
 		"maxBones", "useVertexTexture", "morphTargets", "morphNormals",
@@ -153,6 +154,8 @@ function WebGLPrograms( renderer, capabilities ) {
 			specularMap: !! material.specularMap,
 			alphaMap: !! material.alphaMap,
 
+			gradientMap: !! material.gradientMap,
+
 			combine: material.combine,
 
 			vertexColors: material.vertexColors,