Browse Source

WebGPURenderer: MeshSSSPhysicalNodeMaterial (#27488)

* Add MeshSSSPhysicalNodeMaterial

* add `webgpu_materials_sss` example

* update title

* fix from scanning

* fix old property name

* update default values
sunag 1 year ago
parent
commit
d28e94e208

+ 1 - 0
examples/files.json

@@ -346,6 +346,7 @@
 		"webgpu_loader_gltf_sheen",
 		"webgpu_loader_materialx",
 		"webgpu_materials",
+		"webgpu_materials_sss",
 		"webgpu_materials_video",
 		"webgpu_materialx_noise",
 		"webgpu_multiple_rendertargets",

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

@@ -11,5 +11,6 @@ export { default as MeshLambertNodeMaterial } from './MeshLambertNodeMaterial.js
 export { default as MeshPhongNodeMaterial } from './MeshPhongNodeMaterial.js';
 export { default as MeshStandardNodeMaterial } from './MeshStandardNodeMaterial.js';
 export { default as MeshPhysicalNodeMaterial } from './MeshPhysicalNodeMaterial.js';
+export { default as MeshSSSPhysicalNodeMaterial } from './MeshSSSPhysicalNodeMaterial.js';
 export { default as PointsNodeMaterial } from './PointsNodeMaterial.js';
 export { default as SpriteNodeMaterial } from './SpriteNodeMaterial.js';

+ 84 - 0
examples/jsm/nodes/materials/MeshSSSPhysicalNodeMaterial.js

@@ -0,0 +1,84 @@
+import { addNodeMaterial } from './NodeMaterial.js';
+import { transformedNormalView } from '../accessors/NormalNode.js';
+import { positionViewDirection } from '../accessors/PositionNode.js';
+import PhysicalLightingModel from '../functions/PhysicalLightingModel.js';
+import MeshPhysicalNodeMaterial from './MeshPhysicalNodeMaterial.js';
+import { float, vec3 } from '../shadernode/ShaderNode.js';
+
+class SSSPhysicalLightingModel extends PhysicalLightingModel {
+
+	constructor( useClearcoat, useSheen, useIridescence, useSSS ) {
+
+		super( useClearcoat, useSheen, useIridescence );
+
+		this.useSSS = useSSS;
+
+	}
+
+	direct( { lightDirection, lightColor, reflectedLight }, stack, builder ) {
+
+		if ( this.useSSS === true ) {
+
+			const material = builder.material;
+
+			const { thicknessColorNode, thicknessDistortionNode, thicknessAmbientNode, thicknessAttenuationNode, thicknessPowerNode, thicknessScaleNode } = material;
+
+			const scatteringHalf = lightDirection.add( transformedNormalView.mul( thicknessDistortionNode ) ).normalize();
+			const scatteringDot = float( positionViewDirection.dot( scatteringHalf.negate() ).saturate().pow( thicknessPowerNode ).mul( thicknessScaleNode ) );
+			const scatteringIllu = vec3( scatteringDot.add( thicknessAmbientNode ).mul( thicknessColorNode ) );
+
+			reflectedLight.directDiffuse.addAssign( scatteringIllu.mul( thicknessAttenuationNode.mul( lightColor ) ) );
+
+		}
+
+		super.direct( { lightDirection, lightColor, reflectedLight }, stack, builder );
+
+	}
+
+}
+
+class MeshSSSPhysicalNodeMaterial extends MeshPhysicalNodeMaterial {
+
+	constructor( parameters ) {
+
+		super( parameters );
+
+		this.thicknessColorNode = null;
+		this.thicknessDistortionNode = float( 0.1 );
+		this.thicknessAmbientNode = float( 0.0 );
+		this.thicknessAttenuationNode = float( .1 );
+		this.thicknessPowerNode = float( 2.0 );
+		this.thicknessScaleNode = float( 10.0 );
+
+	}
+
+	get useSSS() {
+
+		return this.thicknessColorNode !== null;
+
+	}
+
+	setupLightingModel( /*builder*/ ) {
+
+		return new SSSPhysicalLightingModel( this.useClearcoat, this.useSheen, this.useIridescence, this.useSSS );
+
+	}
+
+	copy( source ) {
+
+		this.thicknessColorNode = source.thicknessColorNode;
+		this.thicknessDistortionNode = source.thicknessDistortionNode;
+		this.thicknessAmbientNode = source.thicknessAmbientNode;
+		this.thicknessAttenuationNode = source.thicknessAttenuationNode;
+		this.thicknessPowerNode = source.thicknessPowerNode;
+		this.thicknessScaleNode = source.thicknessScaleNode;
+
+		return super.copy( source );
+
+	}
+
+}
+
+export default MeshSSSPhysicalNodeMaterial;
+
+addNodeMaterial( 'MeshSSSPhysicalNodeMaterial', MeshSSSPhysicalNodeMaterial );

BIN
examples/screenshots/webgpu_materials_sss.jpg


+ 214 - 0
examples/webgpu_materials_sss.html

@@ -0,0 +1,214 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - sss</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>
+			<br/>Fast subsurface scattering<br/>
+			[Thanks for the art support from <a href="https://github.com/shaochun" target="_blank" rel="noopener">Shaochun Lin</a>]
+		</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 * as Nodes from 'three/nodes';
+
+			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
+
+			import Stats from 'three/addons/libs/stats.module.js';
+
+			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+			import { FBXLoader } from 'three/addons/loaders/FBXLoader.js';
+
+			let container, stats;
+			let camera, scene, renderer;
+			let model;
+
+			init();
+
+			function init() {
+
+				container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 1, 5000 );
+				camera.position.set( 0.0, 300, 400 * 4 );
+
+				scene = new THREE.Scene();
+
+				// Lights
+
+				scene.add( new THREE.AmbientLight( 0xc1c1c1 ) );
+
+				const directionalLight = new THREE.DirectionalLight( 0xffffff, 0.03 );
+				directionalLight.position.set( 0.0, 0.5, 0.5 ).normalize();
+				scene.add( directionalLight );
+
+				const pointLight1 = new THREE.Mesh( new THREE.SphereGeometry( 4, 8, 8 ), new THREE.MeshBasicMaterial( { color: 0xc1c1c1 } ) );
+				pointLight1.add( new THREE.PointLight( 0xc1c1c1, 4.0, 300, 0 ) );
+				scene.add( pointLight1 );
+				pointLight1.position.x = 0;
+				pointLight1.position.y = - 50;
+				pointLight1.position.z = 350;
+
+				const pointLight2 = new THREE.Mesh( new THREE.SphereGeometry( 4, 8, 8 ), new THREE.MeshBasicMaterial( { color: 0xc1c100 } ) );
+				pointLight2.add( new THREE.PointLight( 0xc1c100, 0.75, 500, 0 ) );
+				scene.add( pointLight2 );
+				pointLight2.position.x = - 100;
+				pointLight2.position.y = 20;
+				pointLight2.position.z = - 260;
+
+				renderer = new WebGPURenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				container.appendChild( renderer.domElement );
+
+				//
+
+				stats = new Stats();
+				container.appendChild( stats.dom );
+
+				const controls = new OrbitControls( camera, container );
+				controls.minDistance = 500;
+				controls.maxDistance = 3000;
+
+				window.addEventListener( 'resize', onWindowResize );
+
+				initMaterial();
+
+			}
+
+			function initMaterial() {
+
+				const loader = new THREE.TextureLoader();
+				const imgTexture = loader.load( 'models/fbx/white.jpg' );
+				imgTexture.colorSpace = THREE.SRGBColorSpace;
+
+				const thicknessTexture = loader.load( 'models/fbx/bunny_thickness.jpg' );
+				imgTexture.wrapS = imgTexture.wrapT = THREE.RepeatWrapping;
+
+				const material = new Nodes.MeshSSSPhysicalNodeMaterial();
+				material.color = new THREE.Color( 1.0, 0.2, 0.2 );
+				material.roughness = 0.3;
+				material.thicknessColorNode = Nodes.texture( thicknessTexture ).mul( Nodes.vec3( 0.5, 0.3, 0.0 ) );
+				material.thicknessDistortionNode = Nodes.uniform( 0.1 );
+				material.thicknessAmbientNode = Nodes.uniform( 0.4 );
+				material.thicknessAttenuationNode = Nodes.uniform( 0.8 );
+				material.thicknessPowerNode = Nodes.uniform( 2.0 );
+				material.thicknessScaleNode = Nodes.uniform( 16.0 );
+
+				// LOADER
+
+				const loaderFBX = new FBXLoader();
+				loaderFBX.load( 'models/fbx/stanford-bunny.fbx', function ( object ) {
+
+					model = object.children[ 0 ];
+					model.position.set( 0, 0, 10 );
+					model.scale.setScalar( 1 );
+					model.material = material;
+					scene.add( model );
+
+				} );
+
+				initGUI( material );
+
+			}
+
+			function initGUI( material ) {
+
+				const gui = new GUI( { title: 'Thickness Control' } );
+
+				const ThicknessControls = function () {
+
+					this.distortion = material.thicknessDistortionNode.value;
+					this.ambient = material.thicknessAmbientNode.value;
+					this.attenuation = material.thicknessAttenuationNode.value;
+					this.power = material.thicknessPowerNode.value;
+					this.scale = material.thicknessScaleNode.value;
+
+				};
+
+				const thicknessControls = new ThicknessControls();
+
+				gui.add( thicknessControls, 'distortion' ).min( 0.01 ).max( 1 ).step( 0.01 ).onChange( function () {
+
+					material.thicknessDistortionNode.value = thicknessControls.distortion;
+					console.log( 'distortion' );
+
+				} );
+
+				gui.add( thicknessControls, 'ambient' ).min( 0.01 ).max( 5.0 ).step( 0.05 ).onChange( function () {
+
+					material.thicknessAmbientNode.value = thicknessControls.ambient;
+
+				} );
+
+				gui.add( thicknessControls, 'attenuation' ).min( 0.01 ).max( 5.0 ).step( 0.05 ).onChange( function () {
+
+					material.thicknessAttenuationNode.value = thicknessControls.attenuation;
+
+				} );
+
+				gui.add( thicknessControls, 'power' ).min( 0.01 ).max( 16.0 ).step( 0.1 ).onChange( function () {
+
+					material.thicknessPowerNode.value = thicknessControls.power;
+
+				} );
+
+				gui.add( thicknessControls, 'scale' ).min( 0.01 ).max( 50.0 ).step( 0.1 ).onChange( function () {
+
+					material.thicknessScaleNode.value = thicknessControls.scale;
+
+				} );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			//
+
+			function animate() {
+
+				render();
+
+				stats.update();
+
+			}
+
+			function render() {
+
+				if ( model ) model.rotation.y = performance.now() / 5000;
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>