Browse Source

WebGPURenderer: Sheen (#26329)

* Added: SceneNode, `backgroundBlurriness`

* TextureNode: .channel and .matrix as default

* Background: Added backgroundBlurriness

* PhysicalLightingModel: +Sheen

* Add `webgpu_loader_gltf_sheen` example.
sunag 2 years ago
parent
commit
b621f0eeed

+ 1 - 0
examples/files.json

@@ -323,6 +323,7 @@
 		"webgpu_lights_selective",
 		"webgpu_loader_gltf",
 		"webgpu_loader_gltf_compressed",
+		"webgpu_loader_gltf_sheen",
 		"webgpu_materials",
 		"webgpu_materials_video",
 		"webgpu_morphtargets",

+ 2 - 1
examples/jsm/nodes/Nodes.js

@@ -69,7 +69,7 @@ export { default as CameraNode, cameraProjectionMatrix, cameraViewMatrix, camera
 export { default as CubeTextureNode, cubeTexture } from './accessors/CubeTextureNode.js';
 export { default as ExtendedMaterialNode, materialNormal } from './accessors/ExtendedMaterialNode.js';
 export { default as InstanceNode, instance } from './accessors/InstanceNode.js';
-export { default as MaterialNode, materialUV, materialAlphaTest, materialColor, materialShininess, materialEmissive, materialOpacity, materialSpecularColor, materialReflectivity, materialRoughness, materialMetalness, materialRotation } from './accessors/MaterialNode.js';
+export { default as MaterialNode, materialAlphaTest, materialColor, materialShininess, materialEmissive, materialOpacity, materialSpecularColor, materialReflectivity, materialRoughness, materialMetalness, materialRotation, materialSheen, materialSheenRoughness } from './accessors/MaterialNode.js';
 export { default as MaterialReferenceNode, materialReference } from './accessors/MaterialReferenceNode.js';
 export { default as MorphNode, morph } from './accessors/MorphNode.js';
 export { default as TextureBicubicNode, textureBicubic } from './accessors/TextureBicubicNode.js';
@@ -82,6 +82,7 @@ export { default as PositionNode, positionGeometry, positionLocal, positionWorld
 export { default as ReferenceNode, reference } from './accessors/ReferenceNode.js';
 export { default as ReflectVectorNode, reflectVector } from './accessors/ReflectVectorNode.js';
 export { default as SkinningNode, skinning } from './accessors/SkinningNode.js';
+export { default as SceneNode, backgroundBlurriness } from './accessors/SceneNode.js';
 export { default as StorageBufferNode, storage } from './accessors/StorageBufferNode.js';
 export { default as TangentNode, tangentGeometry, tangentLocal, tangentView, tangentWorld, transformedTangentView, transformedTangentWorld } from './accessors/TangentNode.js';
 export { default as TextureNode, texture, /*textureLevel,*/ sampler } from './accessors/TextureNode.js';

+ 25 - 49
examples/jsm/nodes/accessors/MaterialNode.js

@@ -1,8 +1,6 @@
 import Node, { addNodeClass } from '../core/Node.js';
-import { uniform } from '../core/UniformNode.js';
 import { materialReference } from './MaterialReferenceNode.js';
-import { uv } from './UVNode.js';
-import { nodeImmutable, vec3 } from '../shadernode/ShaderNode.js';
+import { nodeImmutable } from '../shadernode/ShaderNode.js';
 
 class MaterialNode extends Node {
 
@@ -27,15 +25,11 @@ class MaterialNode extends Node {
 
 			return 'float';
 
-		} else if ( scope === MaterialNode.UV ) {
-
-			return 'vec2';
-
-		} else if ( scope === MaterialNode.EMISSIVE ) {
+		} else if ( scope === MaterialNode.EMISSIVE || scope === MaterialNode.SHEEN ) {
 
 			return 'vec3';
 
-		} else if ( scope === MaterialNode.ROUGHNESS || scope === MaterialNode.METALNESS || scope === MaterialNode.SPECULAR || scope === MaterialNode.SHININESS ) {
+		} else if ( scope === MaterialNode.ROUGHNESS || scope === MaterialNode.METALNESS || scope === MaterialNode.SPECULAR || scope === MaterialNode.SHININESS || scope === MaterialNode.CLEARCOAT_ROUGHNESS || scope === MaterialNode.SHEEN_ROUGHNESS ) {
 
 			return 'float';
 
@@ -64,7 +58,6 @@ class MaterialNode extends Node {
 		//@TODO: Check if it can be cached by property name.
 
 		const textureRefNode = materialReference( property, 'texture' );
-		textureRefNode.node.uvNode = materialUV;
 
 		return textureRefNode;
 
@@ -201,59 +194,40 @@ class MaterialNode extends Node {
 
 			}
 
-		} else if ( scope === MaterialNode.ROTATION ) {
-
-			node = this.getFloat( 'rotation' );
-
-		} else if ( scope === MaterialNode.UV ) {
+		} else if ( scope === MaterialNode.SHEEN ) {
 
-			// uv repeat and offset setting priorities
+			const sheenNode = this.getColor( 'sheenColor' ).mul( this.getFloat( 'sheen' ) ); // Move this mul() to CPU
 
-			let uvScaleMap =
-				material.map ||
-				material.specularMap ||
-				material.displacementMap ||
-				material.normalMap ||
-				material.bumpMap ||
-				material.roughnessMap ||
-				material.metalnessMap ||
-				material.alphaMap ||
-				material.emissiveMap ||
-				material.clearcoatMap ||
-				material.clearcoatNormalMap ||
-				material.clearcoatRoughnessMap ||
-				material.iridescenceMap ||
-				material.iridescenceThicknessMap ||
-				material.specularIntensityMap ||
-				material.specularColorMap ||
-				material.transmissionMap ||
-				material.thicknessMap ||
-				material.sheenColorMap ||
-				material.sheenRoughnessMap;
+			if ( material.sheenColorMap && material.sheenColorMap.isTexture === true ) {
 
-			if ( uvScaleMap ) {
+				node = sheenNode.mul( this.getTexture( 'sheenColorMap' ).rgb );
 
-				// backwards compatibility
-				if ( uvScaleMap.isWebGLRenderTarget ) {
+			} else {
 
-					uvScaleMap = uvScaleMap.texture;
+				node = sheenNode;
 
-				}
+			}
 
-				if ( uvScaleMap.matrixAutoUpdate === true ) {
+		} else if ( scope === MaterialNode.SHEEN_ROUGHNESS ) {
 
-					uvScaleMap.updateMatrix();
+			const sheenRoughnessNode = this.getFloat( 'sheenRoughness' );
 
-				}
+			if ( material.sheenRoughnessMap && material.sheenRoughnessMap.isTexture === true ) {
 
-				node = uniform( uvScaleMap.matrix ).mul( vec3( uv(), 1 ) );
+				node = sheenRoughnessNode.mul( this.getTexture( 'sheenRoughnessMap' ).a );
 
 			} else {
 
-				node = uv();
+				node = sheenRoughnessNode;
 
 			}
 
+			node = node.clamp( 0.07, 1.0 );
+
+		} else if ( scope === MaterialNode.ROTATION ) {
+
+			node = this.getFloat( 'rotation' );
+
 		} else {
 
 			const outputType = this.getNodeType( builder );
@@ -280,11 +254,11 @@ MaterialNode.CLEARCOAT = 'clearcoat';
 MaterialNode.CLEARCOAT_ROUGHNESS = 'clearcoatRoughness';
 MaterialNode.EMISSIVE = 'emissive';
 MaterialNode.ROTATION = 'rotation';
-MaterialNode.UV = 'uv';
+MaterialNode.SHEEN = 'sheen';
+MaterialNode.SHEEN_ROUGHNESS = 'sheenRoughness';
 
 export default MaterialNode;
 
-export const materialUV = nodeImmutable( MaterialNode, MaterialNode.UV );
 export const materialAlphaTest = nodeImmutable( MaterialNode, MaterialNode.ALPHA_TEST );
 export const materialColor = nodeImmutable( MaterialNode, MaterialNode.COLOR );
 export const materialShininess = nodeImmutable( MaterialNode, MaterialNode.SHININESS );
@@ -297,5 +271,7 @@ export const materialMetalness = nodeImmutable( MaterialNode, MaterialNode.METAL
 export const materialClearcoat = nodeImmutable( MaterialNode, MaterialNode.CLEARCOAT );
 export const materialClearcoatRoughness = nodeImmutable( MaterialNode, MaterialNode.CLEARCOAT_ROUGHNESS );
 export const materialRotation = nodeImmutable( MaterialNode, MaterialNode.ROTATION );
+export const materialSheen = nodeImmutable( MaterialNode, MaterialNode.SHEEN );
+export const materialSheenRoughness = nodeImmutable( MaterialNode, MaterialNode.SHEEN_ROUGHNESS );
 
 addNodeClass( MaterialNode );

+ 46 - 0
examples/jsm/nodes/accessors/SceneNode.js

@@ -0,0 +1,46 @@
+import Node from '../core/Node.js';
+import { addNodeClass } from '../core/Node.js';
+import { nodeImmutable } from '../shadernode/ShaderNode.js';
+import { reference } from './ReferenceNode.js';
+
+class SceneNode extends Node {
+
+	constructor( scope = SceneNode.BACKGROUND_BLURRINESS, scene = null ) {
+
+		super();
+
+		this.scope = scope;
+		this.scene = scene;
+
+	}
+
+	construct( builder ) {
+
+		const scope = this.scope;
+		const scene = this.scene !== null ? this.scene : builder.scene;
+
+		let output;
+
+		if ( scope === SceneNode.BACKGROUND_BLURRINESS ) {
+
+			output = reference( 'backgroundBlurriness', 'float', scene );
+
+		} else {
+
+			console.error( 'THREE.SceneNode: Unknown scope:', scope );
+
+		}
+
+		return output;
+
+	}
+
+}
+
+SceneNode.BACKGROUND_BLURRINESS = 'backgroundBlurriness';
+
+export default SceneNode;
+
+export const backgroundBlurriness = nodeImmutable( SceneNode, SceneNode.BACKGROUND_BLURRINESS );
+
+addNodeClass( SceneNode );

+ 20 - 5
examples/jsm/nodes/accessors/TextureNode.js

@@ -1,13 +1,12 @@
-import UniformNode from '../core/UniformNode.js';
+import UniformNode, { uniform } from '../core/UniformNode.js';
 import { uv } from './UVNode.js';
 import { textureSize } from './TextureSizeNode.js';
 import { colorSpaceToLinear } from '../display/ColorSpaceNode.js';
 import { context } from '../core/ContextNode.js';
 import { expression } from '../code/ExpressionNode.js';
 import { addNodeClass } from '../core/Node.js';
-import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js';
-
-let defaultUV;
+import { addNodeElement, nodeProxy, vec3 } from '../shadernode/ShaderNode.js';
+import { NodeUpdateType } from '../core/constants.js';
 
 class TextureNode extends UniformNode {
 
@@ -20,6 +19,8 @@ class TextureNode extends UniformNode {
 		this.uvNode = uvNode;
 		this.levelNode = levelNode;
 
+		this.updateType = NodeUpdateType.FRAME;
+
 	}
 
 	getUniformHash( /*builder*/ ) {
@@ -44,7 +45,9 @@ class TextureNode extends UniformNode {
 
 	getDefaultUV() {
 
-		return defaultUV || ( defaultUV = uv() );
+		const texture = this.value;
+
+		return uniform( texture.matrix ).mul( vec3( uv( texture.channel ), 1 ) );
 
 	}
 
@@ -194,6 +197,18 @@ class TextureNode extends UniformNode {
 
 	}
 
+	update() {
+
+		const texture = this.value;
+
+		if ( texture.matrixAutoUpdate === true ) {
+
+			texture.updateMatrix();
+
+		}
+
+	}
+
 	clone() {
 
 		return new this.constructor( this.value, this.uvNode, this.levelNode );

+ 2 - 1
examples/jsm/nodes/core/NodeBuilder.js

@@ -43,13 +43,14 @@ const toFloat = ( value ) => {
 
 class NodeBuilder {
 
-	constructor( object, renderer, parser ) {
+	constructor( object, renderer, parser, scene = null ) {
 
 		this.object = object;
 		this.material = ( object && object.material ) || null;
 		this.geometry = ( object && object.geometry ) || null;
 		this.renderer = renderer;
 		this.parser = parser;
+		this.scene = scene;
 
 		this.nodes = [];
 		this.updateNodes = [];

+ 2 - 0
examples/jsm/nodes/core/PropertyNode.js

@@ -49,6 +49,8 @@ export const roughness = nodeImmutable( PropertyNode, 'float', 'Roughness' );
 export const metalness = nodeImmutable( PropertyNode, 'float', 'Metalness' );
 export const clearcoat = nodeImmutable( PropertyNode, 'float', 'Clearcoat' );
 export const clearcoatRoughness = nodeImmutable( PropertyNode, 'float', 'ClearcoatRoughness' );
+export const sheen = nodeImmutable( PropertyNode, 'vec3', 'Sheen' );
+export const sheenRoughness = nodeImmutable( PropertyNode, 'float', 'SheenRoughness' );
 export const specularColor = nodeImmutable( PropertyNode, 'color', 'SpecularColor' );
 export const shininess = nodeImmutable( PropertyNode, 'float', 'Shininess' );
 

+ 43 - 0
examples/jsm/nodes/functions/BSDF/BRDF_Sheen.js

@@ -0,0 +1,43 @@
+import { transformedNormalView } from '../../accessors/NormalNode.js';
+import { positionViewDirection } from '../../accessors/PositionNode.js';
+import { sheen, sheenRoughness } from '../../core/PropertyNode.js';
+import { tslFn, float } from '../../shadernode/ShaderNode.js';
+
+// https://github.com/google/filament/blob/master/shaders/src/brdf.fs
+const D_Charlie = ( roughness, dotNH ) => {
+
+	const alpha = roughness.pow2();
+
+	// Estevez and Kulla 2017, "Production Friendly Microfacet Sheen BRDF"
+	const invAlpha = float( 1.0 ).div( alpha );
+	const cos2h = dotNH.pow2();
+	const sin2h = cos2h.oneMinus().max( 0.0078125 ); // 2^(-14/2), so sin2h^2 > 0 in fp16
+
+	return float( 2.0 ).add( invAlpha ).mul( sin2h.pow( invAlpha.mul( 0.5 ) ) ).div( 2.0 * Math.PI );
+
+};
+
+// https://github.com/google/filament/blob/master/shaders/src/brdf.fs
+const V_Neubelt = ( dotNV, dotNL ) => {
+
+	// Neubelt and Pettineo 2013, "Crafting a Next-gen Material Pipeline for The Order: 1886"
+	return float( 1.0 ).div( float( 4.0 ).mul( dotNL.add( dotNV ).sub( dotNL.mul( dotNV ) ) ) );
+
+};
+
+const BRDF_Sheen = tslFn( ( { lightDirection } ) => {
+
+	const halfDir = lightDirection.add( positionViewDirection ).normalize();
+
+	const dotNL = transformedNormalView.dot( lightDirection ).clamp();
+	const dotNV = transformedNormalView.dot( positionViewDirection ).clamp();
+	const dotNH = transformedNormalView.dot( halfDir ).clamp();
+
+	const D = D_Charlie( sheenRoughness, dotNH );
+	const V = V_Neubelt( dotNV, dotNL );
+
+	return sheen.mul( D ).mul( V );
+
+} );
+
+export default BRDF_Sheen;

+ 58 - 1
examples/jsm/nodes/functions/PhysicalLightingModel.js

@@ -3,15 +3,44 @@ import BRDF_GGX from './BSDF/BRDF_GGX.js';
 import DFGApprox from './BSDF/DFGApprox.js';
 import EnvironmentBRDF from './BSDF/EnvironmentBRDF.js';
 import F_Schlick from './BSDF/F_Schlick.js';
+import BRDF_Sheen from './BSDF/BRDF_Sheen.js';
 import { lightingModel } from '../core/LightingModel.js';
-import { diffuseColor, specularColor, roughness, clearcoat, clearcoatRoughness } from '../core/PropertyNode.js';
+import { diffuseColor, specularColor, roughness, clearcoat, clearcoatRoughness, sheen, sheenRoughness } from '../core/PropertyNode.js';
 import { transformedNormalView, transformedClearcoatNormalView } from '../accessors/NormalNode.js';
 import { positionViewDirection } from '../accessors/PositionNode.js';
 import { tslFn, float, vec3 } from '../shadernode/ShaderNode.js';
+import { cond } from '../math/CondNode.js';
 
 const clearcoatF0 = vec3( 0.04 );
 const clearcoatF90 = vec3( 1 );
 
+// This is a curve-fit approxmation to the "Charlie sheen" BRDF integrated over the hemisphere from
+// Estevez and Kulla 2017, "Production Friendly Microfacet Sheen BRDF". The analysis can be found
+// in the Sheen section of https://drive.google.com/file/d/1T0D1VSyR4AllqIJTQAraEIzjlb5h4FKH/view?usp=sharing
+const IBLSheenBRDF = ( normal, viewDir, roughness ) => {
+
+	const dotNV = normal.dot( viewDir ).saturate();
+
+	const r2 = roughness.pow2();
+
+	const a = cond(
+		roughness.lessThan( 0.25 ),
+		float( - 339.2 ).mul( r2 ).add( float( 161.4 ).mul( roughness ) ).sub( 25.9 ),
+		float( - 8.48 ).mul( r2 ).add( float( 14.3 ).mul( roughness ) ).sub( 9.95 )
+	);
+
+	const b = cond(
+		roughness.lessThan( 0.25 ),
+		float( 44.0 ).mul( r2 ).sub( float( 23.7 ).mul( roughness ) ).add( 3.26 ),
+		float( 1.97 ).mul( r2 ).sub( float( 3.27 ).mul( roughness ) ).add( 0.72 )
+	);
+
+	const DG = cond( roughness.lessThan( 0.25 ), 0.0, float( 0.1 ).mul( roughness ).sub( 0.025 ) ).add( a.mul( dotNV ).add( b ).exp() );
+
+	return DG.mul( 1.0 / Math.PI ).saturate();
+
+};
+
 // Fdez-Agüera's "Multiple-Scattering Microfacet Model for Real-Time Image Based Lighting"
 // Approximates multiscattering in order to preserve energy.
 // http://www.jcgt.org/published/0008/01/03/
@@ -54,12 +83,34 @@ const LM_Init = tslFn( ( context, stack, builder ) => {
 
 	}
 
+	if ( builder.includes( sheen ) ) {
+
+		context.reflectedLight.sheenSpecular = vec3().temp();
+
+		const outgoingLight = context.reflectedLight.total;
+
+		const sheenEnergyComp = sheen.r.max( sheen.g ).max( sheen.b ).mul( 0.157 ).oneMinus();
+		const sheenLight = outgoingLight.mul( sheenEnergyComp ).add( context.reflectedLight.sheenSpecular );
+
+		outgoingLight.assign( sheenLight );
+
+	}
+
 } );
 
 const RE_IndirectSpecular_Physical = tslFn( ( context ) => {
 
 	const { radiance, iblIrradiance, reflectedLight } = context;
 
+	if ( reflectedLight.sheenSpecular ) {
+
+		reflectedLight.sheenSpecular.addAssign( iblIrradiance.mul(
+			sheen,
+			IBLSheenBRDF( transformedNormalView, positionViewDirection, sheenRoughness )
+		) );
+
+	}
+
 	if ( reflectedLight.clearcoatSpecular ) {
 
 		const dotNVcc = transformedClearcoatNormalView.dot( positionViewDirection ).clamp();
@@ -109,6 +160,12 @@ const RE_Direct_Physical = tslFn( ( inputs ) => {
 	const dotNL = transformedNormalView.dot( lightDirection ).clamp();
 	const irradiance = dotNL.mul( lightColor );
 
+	if ( reflectedLight.sheenSpecular ) {
+
+		reflectedLight.sheenSpecular.addAssign( irradiance.mul( BRDF_Sheen( { lightDirection } ) ) );
+
+	}
+
 	if ( reflectedLight.clearcoatSpecular ) {
 
 		const dotNLcc = transformedClearcoatNormalView.dot( lightDirection ).clamp();

+ 10 - 2
examples/jsm/nodes/materials/MeshPhysicalNodeMaterial.js

@@ -1,8 +1,8 @@
 import { addNodeMaterial } from './NodeMaterial.js';
 import { transformedClearcoatNormalView } from '../accessors/NormalNode.js';
-import { clearcoat, clearcoatRoughness } from '../core/PropertyNode.js';
+import { clearcoat, clearcoatRoughness, sheen, sheenRoughness } from '../core/PropertyNode.js';
 import { materialClearcoatNormal } from '../accessors/ExtendedMaterialNode.js';
-import { materialClearcoat, materialClearcoatRoughness } from '../accessors/MaterialNode.js';
+import { materialClearcoat, materialClearcoatRoughness, materialSheen, materialSheenRoughness } from '../accessors/MaterialNode.js';
 import { float, vec3 } from '../shadernode/ShaderNode.js';
 import MeshStandardNodeMaterial from './MeshStandardNodeMaterial.js';
 
@@ -57,6 +57,14 @@ class MeshPhysicalNodeMaterial extends MeshStandardNodeMaterial {
 		stack.assign( clearcoat, clearcoatNode );
 		stack.assign( clearcoatRoughness, clearcoatRoughnessNode );
 
+		// SHEEN
+
+		const sheenNode = this.sheenNode ? vec3( this.sheenNode ) : materialSheen;
+		const sheenRoughnessNode = this.sheenRoughnessNode ? float( this.sheenRoughnessNode ) : materialSheenRoughness;
+
+		stack.assign( sheen, sheenNode );
+		stack.assign( sheenRoughness, sheenRoughnessNode );
+
 	}
 
 	constructNormal( builder ) {

+ 3 - 2
examples/jsm/renderers/common/Background.js

@@ -1,6 +1,6 @@
 import DataMap from './DataMap.js';
 import { Color, Mesh, SphereGeometry, BackSide } from 'three';
-import { context, positionWorldDirection, MeshBasicNodeMaterial } from '../../nodes/Nodes.js';
+import { context, positionWorldDirection, backgroundBlurriness, MeshBasicNodeMaterial } from '../../nodes/Nodes.js';
 
 let _clearAlpha;
 const _clearColor = new Color();
@@ -55,7 +55,8 @@ class Background extends DataMap {
 
 				this.boxMeshNode = context( backgroundNode, {
 					// @TODO: Add Texture2D support using node context
-					getUVNode: () => positionWorldDirection
+					getUVNode: () => positionWorldDirection,
+					getSamplerLevelNode: () => backgroundBlurriness
 				} );
 
 				const nodeMaterial = new MeshBasicNodeMaterial();

+ 1 - 1
examples/jsm/renderers/common/nodes/Nodes.js

@@ -22,7 +22,7 @@ class Nodes extends DataMap {
 
 		if ( nodeBuilder === undefined ) {
 
-			nodeBuilder = this.backend.createNodeBuilder( renderObject.object, this.renderer );
+			nodeBuilder = this.backend.createNodeBuilder( renderObject.object, this.renderer, renderObject.scene );
 			nodeBuilder.material = renderObject.material;
 			nodeBuilder.lightsNode = renderObject.lightsNode;
 			nodeBuilder.environmentNode = this.getEnvironmentNode( renderObject.scene );

+ 2 - 2
examples/jsm/renderers/webgl/nodes/GLSLNodeBuilder.js

@@ -12,9 +12,9 @@ const precisionLib = {
 
 class GLSLNodeBuilder extends NodeBuilder {
 
-	constructor( object, renderer ) {
+	constructor( object, renderer, scene = null ) {
 
-		super( object, renderer, new GLSLNodeParser() );
+		super( object, renderer, new GLSLNodeParser(), scene );
 
 	}
 

+ 2 - 2
examples/jsm/renderers/webgpu/WebGPUBackend.js

@@ -580,9 +580,9 @@ class WebGPUBackend extends Backend {
 
 	// node builder
 
-	createNodeBuilder( object, renderer ) {
+	createNodeBuilder( object, renderer, scene = null ) {
 
-		return new WGSLNodeBuilder( object, renderer );
+		return new WGSLNodeBuilder( object, renderer, scene );
 
 	}
 

+ 2 - 2
examples/jsm/renderers/webgpu/nodes/WGSLNodeBuilder.js

@@ -100,9 +100,9 @@ fn threejs_repeatWrapping( uv : vec2<f32>, dimension : vec2<u32> ) -> vec2<u32>
 
 class WGSLNodeBuilder extends NodeBuilder {
 
-	constructor( object, renderer ) {
+	constructor( object, renderer, scene = null ) {
 
-		super( object, renderer, new WGSLNodeParser() );
+		super( object, renderer, new WGSLNodeParser(), scene );
 
 		this.uniformsGroup = {};
 

BIN
examples/screenshots/webgpu_loader_gltf_sheen.jpg


+ 151 - 0
examples/webgpu_loader_gltf_sheen.html

@@ -0,0 +1,151 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - sheen</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">
+		<style>
+			body {
+				background: #bbbbbb;
+			}
+		</style>
+	</head>
+
+	<body>
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - GLTFLoader + <a href="https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Khronos/KHR_materials_sheen" target="_blank" rel="noopener">KHR_materials_sheen</a><br />
+			Sheen Chair from <a href="https://github.com/KhronosGroup/glTF-Sample-Models/tree/master/2.0/SheenChair" target="_blank" rel="noopener">glTF-Sample-Models</a>
+		</div>
+
+		<!-- Import maps polyfill -->
+		<!-- Remove this when import maps will be widely supported -->
+		<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+
+			import WebGPU from 'three/addons/capabilities/WebGPU.js';
+			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
+
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+			import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
+
+			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+			let camera, scene, renderer, controls;
+
+			init();
+			animate();
+
+			function init() {
+
+				if ( WebGPU.isAvailable() === false ) {
+
+					document.body.appendChild( WebGPU.getErrorMessage() );
+
+					throw new Error( 'No WebGPU support' );
+
+				}
+
+				const container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 20 );
+				camera.position.set( - 0.75, 0.7, 1.25 );
+
+				scene = new THREE.Scene();
+				//scene.add( new THREE.DirectionalLight( 0xffffff, 2 ) );
+
+				// model
+
+				new GLTFLoader()
+					.setPath( 'models/gltf/' )
+					.load( 'SheenChair.glb', function ( gltf ) {
+
+						scene.add( gltf.scene );
+
+						const object = gltf.scene.getObjectByName( 'SheenChair_fabric' );
+
+						const gui = new GUI();
+
+						gui.add( object.material, 'sheen', 0, 1 );
+						gui.open();
+
+					} );
+
+				renderer = new WebGPURenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.toneMapping = THREE.ACESFilmicToneMapping;
+				renderer.toneMappingExposure = 1;
+				renderer.useLegacyLights = false;
+				container.appendChild( renderer.domElement );
+
+				scene.background = new THREE.Color( 0xAAAAAA );
+
+				new RGBELoader()
+					.setPath( 'textures/equirectangular/' )
+					.load( 'royal_esplanade_1k.hdr', function ( texture ) {
+
+						texture.mapping = THREE.EquirectangularReflectionMapping;
+
+						scene.background = texture;
+						//scene.backgroundBlurriness = 1; // @TODO: Needs PMREM
+						scene.environment = texture;
+
+					} );
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.enableDamping = true;
+				controls.minDistance = 1;
+				controls.maxDistance = 10;
+				controls.target.set( 0, 0.35, 0 );
+				controls.update();
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			//
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				controls.update(); // required if damping enabled
+
+				render();
+
+			}
+
+			function render() {
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 1 - 0
test/e2e/puppeteer.js

@@ -121,6 +121,7 @@ const exceptionList = [
 	'webgpu_lights_selective',
 	'webgpu_loader_gltf',
 	'webgpu_loader_gltf_compressed',
+	'webgpu_loader_gltf_sheen',
 	'webgpu_materials',
 	'webgpu_materials_video',
 	'webgpu_morphtargets',