2
0
Эх сурвалжийг харах

WebGPURenderer: MeshToonNodeMaterial (#28235)

* draft attempt

* clean up example

* add screenshot

* remove unused variable

* remove unused import

* remove hack

* improve name

---------

Co-authored-by: aardgoose <[email protected]>
aardgoose 1 жил өмнө
parent
commit
cc774f107f

+ 1 - 0
examples/files.json

@@ -347,6 +347,7 @@
 		"webgpu_materials_matcap",
 		"webgpu_materials_sss",
 		"webgpu_materials_transmission",
+		"webgpu_materials_toon",
 		"webgpu_materials_video",
 		"webgpu_materialx_noise",
 		"webgpu_multiple_rendertargets",

+ 49 - 0
examples/jsm/nodes/functions/ToonLightingModel.js

@@ -0,0 +1,49 @@
+import LightingModel from '../core/LightingModel.js';
+import BRDF_Lambert from './BSDF/BRDF_Lambert.js';
+import { diffuseColor } from '../core/PropertyNode.js';
+import { normalGeometry } from '../accessors/NormalNode.js';
+import { tslFn, float, vec2, vec3 } from '../shadernode/ShaderNode.js';
+import { mix, smoothstep } from '../math/MathNode.js';
+import { materialReference } from '../accessors/MaterialReferenceNode.js';
+
+const getGradientIrradiance = tslFn( ( { normal, lightDirection, builder } ) => {
+
+	// dotNL will be from -1.0 to 1.0
+	const dotNL = normal.dot( lightDirection );
+	const coord = vec2( dotNL.mul( 0.5 ).add(  0.5 ), 0.0 );
+
+	if ( builder.material.gradientMap ) {
+
+		const gradientMap = materialReference( 'gradientMap', 'texture' ).context( { getUV: () => coord } );
+
+		return vec3( gradientMap.r );
+
+	} else {
+
+		const fw = coord.fwidth().mul( 0.5 );
+
+		return mix( vec3( 0.7 ), vec3( 1.0 ), smoothstep( float( 0.7 ).sub( fw.x ), float( 0.7 ).add( fw.x ), coord.x ) );
+
+	}
+
+} );
+
+class ToonLightingModel extends LightingModel {
+
+	direct( { lightDirection, lightColor, reflectedLight }, stack, builder ) {
+
+		const irradiance = getGradientIrradiance( { normal: normalGeometry, lightDirection, builder } ).mul( lightColor );
+
+		reflectedLight.directDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor: diffuseColor.rgb } ) ) );
+
+	}
+
+	indirectDiffuse( { irradiance, reflectedLight } ) {
+
+		reflectedLight.indirectDiffuse.addAssign( irradiance.mul( BRDF_Lambert( { diffuseColor } ) ) );
+
+	}
+
+}
+
+export default ToonLightingModel;

+ 1 - 0
examples/jsm/nodes/materials/Materials.js

@@ -12,6 +12,7 @@ export { default as MeshPhongNodeMaterial } from './MeshPhongNodeMaterial.js';
 export { default as MeshStandardNodeMaterial } from './MeshStandardNodeMaterial.js';
 export { default as MeshPhysicalNodeMaterial } from './MeshPhysicalNodeMaterial.js';
 export { default as MeshSSSNodeMaterial } from './MeshSSSNodeMaterial.js';
+export { default as MeshToonNodeMaterial } from './MeshToonNodeMaterial.js';
 export { default as MeshMatcapNodeMaterial } from './MeshMatcapNodeMaterial.js';
 export { default as PointsNodeMaterial } from './PointsNodeMaterial.js';
 export { default as SpriteNodeMaterial } from './SpriteNodeMaterial.js';

+ 34 - 0
examples/jsm/nodes/materials/MeshToonNodeMaterial.js

@@ -0,0 +1,34 @@
+import NodeMaterial, { addNodeMaterial } from './NodeMaterial.js';
+import ToonLightingModel from '../functions/ToonLightingModel.js';
+
+import { MeshToonMaterial } from 'three';
+
+const defaultValues = new MeshToonMaterial();
+
+class MeshToonNodeMaterial extends NodeMaterial {
+
+	constructor( parameters ) {
+
+		super();
+
+		this.isMeshToonNodeMaterial = true;
+
+		this.lights = true;
+
+		this.setDefaultValues( defaultValues );
+
+		this.setValues( parameters );
+
+	}
+
+	setupLightingModel( /*builder*/ ) {
+
+		return new ToonLightingModel();
+
+	}
+
+}
+
+export default MeshToonNodeMaterial;
+
+addNodeMaterial( 'MeshToonNodeMaterial', MeshToonNodeMaterial );

BIN
examples/screenshots/webgpu_materials_toon.jpg


+ 211 - 0
examples/webgpu_materials_toon.html

@@ -0,0 +1,211 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - materials - toon</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<link type="text/css" rel="stylesheet" href="main.css">
+	</head>
+	<body>
+
+		<div id="container"></div>
+		<div id="info"><a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - Toon Material</div>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/",
+					"three/nodes": "./jsm/nodes/Nodes.js"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+
+			import { MeshBasicNodeMaterial, MeshToonNodeMaterial } from 'three/nodes';
+
+			import WebGPU from 'three/addons/capabilities/WebGPU.js';
+			import WebGL from 'three/addons/capabilities/WebGL.js';
+
+			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
+
+			import Stats from 'three/addons/libs/stats.module.js';
+
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+			import { FontLoader } from 'three/addons/loaders/FontLoader.js';
+			import { TextGeometry } from 'three/addons/geometries/TextGeometry.js';
+
+			let container, stats;
+
+			let camera, scene, renderer;
+			let particleLight;
+
+			const loader = new FontLoader();
+			loader.load( 'fonts/gentilis_regular.typeface.json', function ( font ) {
+
+				init( font );
+
+			} );
+
+			function init( font ) {
+
+				if ( WebGPU.isAvailable() === false && WebGL.isWebGL2Available() === false ) {
+
+					document.body.appendChild( WebGPU.getErrorMessage() );
+
+					throw new Error( 'No WebGPU or WebGL2 support' );
+
+				}
+
+				container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 2500 );
+				camera.position.set( 0.0, 400, 400 * 3.5 );
+
+				//
+
+				scene = new THREE.Scene();
+				scene.background = new THREE.Color( 0x444488 );
+
+				//
+
+				renderer = new WebGPURenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( render );
+				container.appendChild( renderer.domElement );
+
+				// Materials
+
+				const cubeWidth = 400;
+				const numberOfSphersPerSide = 5;
+				const sphereRadius = ( cubeWidth / numberOfSphersPerSide ) * 0.8 * 0.5;
+				const stepSize = 1.0 / numberOfSphersPerSide;
+
+				const geometry = new THREE.SphereGeometry( sphereRadius, 32, 16 );
+
+				for ( let alpha = 0, alphaIndex = 0; alpha <= 1.0; alpha += stepSize, alphaIndex ++ ) {
+
+					const colors = new Uint8Array( alphaIndex + 2 );
+
+					for ( let c = 0; c <= colors.length; c ++ ) {
+
+						colors[ c ] = ( c / colors.length ) * 256;
+
+					}
+
+					const gradientMap = new THREE.DataTexture( colors, colors.length, 1, THREE.RedFormat );
+					gradientMap.needsUpdate = true;
+
+					for ( let beta = 0; beta <= 1.0; beta += stepSize ) {
+
+						for ( let gamma = 0; gamma <= 1.0; gamma += stepSize ) {
+
+							// basic monochromatic energy preservation
+							const diffuseColor = new THREE.Color().setHSL( alpha, 0.5, gamma * 0.5 + 0.1 ).multiplyScalar( 1 - beta * 0.2 );
+
+							const material = new MeshToonNodeMaterial( {
+								color: diffuseColor,
+								gradientMap: gradientMap
+							} );
+
+							const 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 ) {
+
+					const textGeo = new TextGeometry( name, {
+
+						font: font,
+
+						size: 20,
+						depth: 1,
+						curveSegments: 1
+
+					} );
+
+					const textMaterial = new MeshBasicNodeMaterial();
+					const textMesh = new THREE.Mesh( textGeo, textMaterial );
+					textMesh.position.copy( location );
+					scene.add( textMesh );
+
+				}
+
+				addLabel( '-gradientMap', new THREE.Vector3( - 350, 0, 0 ) );
+				addLabel( '+gradientMap', new THREE.Vector3( 350, 0, 0 ) );
+
+				addLabel( '-diffuse', new THREE.Vector3( 0, 0, - 300 ) );
+				addLabel( '+diffuse', new THREE.Vector3( 0, 0, 300 ) );
+
+				particleLight = new THREE.Mesh(
+					new THREE.SphereGeometry( 4, 8, 8 ),
+					new MeshBasicNodeMaterial( { color: 0xffffff } )
+				);
+				scene.add( particleLight );
+
+				// Lights
+
+				scene.add( new THREE.AmbientLight( 0xc1c1c1, 3 ) );
+
+				const pointLight = new THREE.PointLight( 0xffffff, 2, 800, 0 );
+				particleLight.add( pointLight );
+
+				//
+
+				stats = new Stats();
+				container.appendChild( stats.dom );
+
+				const controls = new OrbitControls( camera, renderer.domElement );
+				controls.minDistance = 200;
+				controls.maxDistance = 2000;
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			//
+
+			function render() {
+
+				const timer = Date.now() * 0.00025;
+
+				particleLight.position.x = Math.sin( timer * 7 ) * 300;
+				particleLight.position.y = Math.cos( timer * 5 ) * 400;
+				particleLight.position.z = Math.cos( timer * 3 ) * 300;
+
+				stats.begin();
+
+				renderer.render( scene, camera );
+
+				stats.end();
+
+			}
+
+		</script>
+
+	</body>
+</html>