Просмотр исходного кода

ToonOutlinePassNode: Add FX pass for toon outlines. (#29483)

* ToonOutlineNode: Add FX pass for toon outlines.

* ToonOutlinePassNode: Refactor code.

* E2E: Update screenshot.

* ToonOutlinePassNode: Clean up.

* ToonOutlinePassNode: More clean up.

* ToonOutlinePassNode: More clean up.
Michael Herzog 1 год назад
Родитель
Сommit
be667f12f9

BIN
examples/screenshots/webgpu_materials_toon.jpg


+ 10 - 2
examples/webgpu_materials_toon.html

@@ -24,6 +24,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
+			import { toonOutlinePass } from 'three/tsl';
 
 			import Stats from 'three/addons/libs/stats.module.js';
 
@@ -33,7 +34,7 @@
 
 			let container, stats;
 
-			let camera, scene, renderer;
+			let camera, scene, renderer, postProcessing;
 			let particleLight;
 
 			const loader = new FontLoader();
@@ -64,6 +65,13 @@
 				renderer.setAnimationLoop( render );
 				container.appendChild( renderer.domElement );
 
+				//
+
+				postProcessing = new THREE.PostProcessing( renderer );
+
+				postProcessing.outputNode = toonOutlinePass( scene, camera );
+
+
 				// Materials
 
 				const cubeWidth = 400;
@@ -184,7 +192,7 @@
 
 				stats.begin();
 
-				renderer.render( scene, camera );
+				postProcessing.render();
 
 				stats.end();
 

+ 1 - 0
src/nodes/Nodes.js

@@ -114,6 +114,7 @@ export { default as StereoPassNode } from './display/StereoPassNode.js';
 export { default as AnaglyphPassNode } from './display/AnaglyphPassNode.js';
 export { default as ParallaxBarrierPassNode } from './display/ParallaxBarrierPassNode.js';
 export { default as PassNode } from './display/PassNode.js';
+export { default as ToonOutlinePassNode } from './display/ToonOutlinePassNode.js';
 
 // code
 export { default as ExpressionNode } from './code/ExpressionNode.js';

+ 1 - 0
src/nodes/TSL.js

@@ -118,6 +118,7 @@ export * from './display/AnaglyphPassNode.js';
 export * from './display/ParallaxBarrierPassNode.js';
 export * from './display/BleachBypass.js';
 export * from './display/Sepia.js';
+export * from './display/ToonOutlinePassNode.js';
 
 export * from './display/PassNode.js';
 

+ 111 - 0
src/nodes/display/ToonOutlinePassNode.js

@@ -0,0 +1,111 @@
+import { float, nodeObject, normalize, vec4 } from '../tsl/TSLBase.js';
+import { Color } from '../../math/Color.js';
+import NodeMaterial from '../../materials/nodes/NodeMaterial.js';
+import { cameraProjectionMatrix } from '../../nodes/accessors/Camera.js';
+import { modelViewMatrix } from '../../nodes/accessors/ModelNode.js';
+import { positionLocal } from '../../nodes/accessors/Position.js';
+import { normalLocal } from '../../nodes/accessors/Normal.js';
+import { BackSide } from '../../constants.js';
+import PassNode from './PassNode.js';
+
+class ToonOutlinePassNode extends PassNode {
+
+	static get type() {
+
+		return 'ToonOutlinePassNode';
+
+	}
+
+	constructor( scene, camera, colorNode, thicknessNode, alphaNode ) {
+
+		super( PassNode.COLOR, scene, camera );
+
+		this.colorNode = colorNode;
+		this.thicknessNode = thicknessNode;
+		this.alphaNode = alphaNode;
+
+		this._materialCache = new WeakMap();
+
+	}
+
+	updateBefore( frame ) {
+
+		const { renderer } = frame;
+
+		const currentRenderObjectFunction = renderer.getRenderObjectFunction();
+
+		renderer.setRenderObjectFunction( ( object, scene, camera, geometry, material, group, lightsNode ) => {
+
+			// only render outline for supported materials
+
+			if ( material.isMeshToonMaterial || material.isMeshToonNodeMaterial ) {
+
+				if ( material.wireframe === false ) {
+
+					const outlineMaterial = this._getOutlineMaterial( material );
+					renderer.renderObject( object, scene, camera, geometry, outlineMaterial, group, lightsNode );
+
+				}
+
+			}
+
+			// default
+
+			renderer.renderObject( object, scene, camera, geometry, material, group, lightsNode );
+
+		} );
+
+		super.updateBefore( frame );
+
+		renderer.setRenderObjectFunction( currentRenderObjectFunction );
+
+	}
+
+	_createMaterial() {
+
+		const material = new NodeMaterial();
+		material.isMeshToonOutlineMaterial = true;
+		material.name = 'Toon_Outline';
+		material.side = BackSide;
+
+		// vertex node
+
+		const outlineNormal = normalLocal.negate();
+		const mvp = cameraProjectionMatrix.mul( modelViewMatrix );
+
+		const ratio = float( 1.0 ); // TODO: support outline thickness ratio for each vertex
+		const pos = mvp.mul( vec4( positionLocal, 1.0 ) );
+		const pos2 = mvp.mul( vec4( positionLocal.add( outlineNormal ), 1.0 ) );
+		const norm = normalize( pos.sub( pos2 ) ); // NOTE: subtract pos2 from pos because BackSide objectNormal is negative
+
+		material.vertexNode = pos.add( norm.mul( this.thicknessNode ).mul( pos.w ).mul( ratio ) );
+
+		// color node
+
+		material.colorNode = vec4( this.colorNode, this.alphaNode );
+
+		return material;
+
+	}
+
+	_getOutlineMaterial( originalMaterial ) {
+
+		let outlineMaterial = this._materialCache.get( originalMaterial );
+
+		if ( outlineMaterial === undefined ) {
+
+			outlineMaterial = this._createMaterial();
+
+			this._materialCache.set( originalMaterial, outlineMaterial );
+
+		}
+
+		return outlineMaterial;
+
+	}
+
+}
+
+export default ToonOutlinePassNode;
+
+export const toonOutlinePass = ( scene, camera, color = new Color( 0, 0, 0 ), thickness = 0.003, alpha = 1 ) => nodeObject( new ToonOutlinePassNode( scene, camera, nodeObject( color ), nodeObject( thickness ), nodeObject( alpha ) ) );

粤ICP备19079148号