Pārlūkot izejas kodu

MeshPhysicalMaterial: Add `dispersion`. (#28051)

* MeshPhysicalMaterial: Add `dispersion`.

* Examples: Clean up.
Michael Herzog 1 gadu atpakaļ
vecāks
revīzija
a7a9996d82

+ 5 - 0
editor/js/Sidebar.Material.js

@@ -136,6 +136,11 @@ function SidebarMaterial( editor ) {
 	const materialClearcoatRoughness = new SidebarMaterialNumberProperty( editor, 'clearcoatRoughness', strings.getKey( 'sidebar/material/clearcoatroughness' ), [ 0, 1 ] );
 	container.add( materialClearcoatRoughness );
 
+	// dispersion
+
+	const materialDispersion = new SidebarMaterialNumberProperty( editor, 'dispersion', strings.getKey( 'sidebar/material/dispersion' ), [ 0, 10 ] );
+	container.add( materialDispersion );
+
 	// iridescence
 
 	const materialIridescence = new SidebarMaterialNumberProperty( editor, 'iridescence', strings.getKey( 'sidebar/material/iridescence' ), [ 0, 1 ] );

+ 4 - 0
editor/js/Strings.js

@@ -251,6 +251,7 @@ function Strings( config ) {
 			'sidebar/material/shininess': 'Shininess',
 			'sidebar/material/clearcoat': 'Clearcoat',
 			'sidebar/material/clearcoatroughness': 'Clearcoat Roughness',
+			'sidebar/material/dispersion': 'Dispersion',
 			'sidebar/material/ior': 'IOR',
 			'sidebar/material/iridescence': 'Iridescence',
 			'sidebar/material/iridescenceIOR': 'Thin-Film IOR',
@@ -603,6 +604,7 @@ function Strings( config ) {
 			'sidebar/material/shininess': 'Brillance',
 			'sidebar/material/clearcoat': 'Vernis',
 			'sidebar/material/clearcoatroughness': 'Rugosité du vernis',
+			'sidebar/material/dispersion': 'Dispersion',
 			'sidebar/material/ior': 'IOR',
 			'sidebar/material/iridescence': 'Iridescence',
 			'sidebar/material/iridescenceIOR': 'Thin-Film IOR',
@@ -955,6 +957,7 @@ function Strings( config ) {
 			'sidebar/material/shininess': '高光大小',
 			'sidebar/material/clearcoat': '清漆',
 			'sidebar/material/clearcoatroughness': '清漆粗糙度',
+			'sidebar/material/dispersion': 'Dispersion',
 			'sidebar/material/ior': 'IOR',
 			'sidebar/material/iridescence': '彩虹色',
 			'sidebar/material/iridescenceIOR': '彩虹色折射率',
@@ -1307,6 +1310,7 @@ function Strings( config ) {
 			'sidebar/material/shininess': '光沢',
 			'sidebar/material/clearcoat': 'クリアコート',
 			'sidebar/material/clearcoatroughness': 'クリアコートの粗さ',
+			'sidebar/material/dispersion': 'Dispersion',
 			'sidebar/material/ior': 'IOR',
 			'sidebar/material/iridescence': '遊色効果',
 			'sidebar/material/iridescenceIOR': '遊色効果のIOR',

+ 1 - 0
examples/files.json

@@ -88,6 +88,7 @@
 		"webgl_loader_gltf",
 		"webgl_loader_gltf_avif",
 		"webgl_loader_gltf_compressed",
+		"webgl_loader_gltf_dispersion",
 		"webgl_loader_gltf_instancing",
 		"webgl_loader_gltf_iridescence",
 		"webgl_loader_gltf_lights",

+ 53 - 0
examples/jsm/loaders/GLTFLoader.js

@@ -85,6 +85,12 @@ class GLTFLoader extends Loader {
 
 		} );
 
+		this.register( function ( parser ) {
+
+			return new GLTFMaterialsDispersionExtension( parser );
+
+		} );
+
 		this.register( function ( parser ) {
 
 			return new GLTFTextureBasisUExtension( parser );
@@ -491,6 +497,7 @@ const EXTENSIONS = {
 	KHR_DRACO_MESH_COMPRESSION: 'KHR_draco_mesh_compression',
 	KHR_LIGHTS_PUNCTUAL: 'KHR_lights_punctual',
 	KHR_MATERIALS_CLEARCOAT: 'KHR_materials_clearcoat',
+	KHR_MATERIALS_DISPERSION: 'KHR_materials_dispersion',
 	KHR_MATERIALS_IOR: 'KHR_materials_ior',
 	KHR_MATERIALS_SHEEN: 'KHR_materials_sheen',
 	KHR_MATERIALS_SPECULAR: 'KHR_materials_specular',
@@ -824,6 +831,52 @@ class GLTFMaterialsClearcoatExtension {
 
 }
 
+/**
+ * Materials dispersion Extension
+ *
+ * Specification: https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_dispersion
+ */
+class GLTFMaterialsDispersionExtension {
+
+	constructor( parser ) {
+
+		this.parser = parser;
+		this.name = EXTENSIONS.KHR_MATERIALS_DISPERSION;
+
+	}
+
+	getMaterialType( materialIndex ) {
+
+		const parser = this.parser;
+		const materialDef = parser.json.materials[ materialIndex ];
+
+		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) return null;
+
+		return MeshPhysicalMaterial;
+
+	}
+
+	extendMaterialParams( materialIndex, materialParams ) {
+
+		const parser = this.parser;
+		const materialDef = parser.json.materials[ materialIndex ];
+
+		if ( ! materialDef.extensions || ! materialDef.extensions[ this.name ] ) {
+
+			return Promise.resolve();
+
+		}
+
+		const extension = materialDef.extensions[ this.name ];
+
+		materialParams.dispersion = extension.dispersion !== undefined ? extension.dispersion : 0;
+
+		return Promise.resolve();
+
+	}
+
+}
+
 /**
  * Iridescence Materials Extension
  *

BIN
examples/models/gltf/DispersionTest.glb


BIN
examples/screenshots/webgl_loader_gltf_dispersion.jpg


+ 102 - 0
examples/webgl_loader_gltf_dispersion.html

@@ -0,0 +1,102 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - GLTFloader + Dispersion</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="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - GLTFLoader + <a href="https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_materials_dispersion" target="_blank" rel="noopener">KHR_materials_dispersion</a><br />
+		</div>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+			import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
+
+			let camera, scene, renderer;
+
+			init().then( render );
+
+			async function init() {
+
+				const container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.01, 5 );
+				camera.position.set( 0.1, 0.05, 0.15 );
+
+				scene = new THREE.Scene();
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.toneMapping = THREE.NeutralToneMapping;
+				renderer.toneMappingExposure = 1;
+				container.appendChild( renderer.domElement );
+
+				const environment = new RoomEnvironment( renderer );
+				const pmremGenerator = new THREE.PMREMGenerator( renderer );
+
+				scene = new THREE.Scene();
+				scene.backgroundBlurriness = 0.5;
+
+				const env = pmremGenerator.fromScene( environment ).texture;
+				scene.background = env;
+				scene.environment = env;
+				environment.dispose();
+
+				const loader = new GLTFLoader();
+				const gltf = await loader.loadAsync( 'models/gltf/DispersionTest.glb' );
+
+				scene.add( gltf.scene );
+
+				const controls = new OrbitControls( camera, renderer.domElement );
+				controls.addEventListener( 'change', render ); // use if there is no animation loop
+				controls.minDistance = 0.1;
+				controls.maxDistance = 10;
+				controls.target.set( 0, 0, 0 );
+				controls.update();
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				render();
+
+			}
+
+			//
+
+			function render() {
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 1 - 0
src/loaders/MaterialLoader.js

@@ -103,6 +103,7 @@ class MaterialLoader extends Loader {
 		if ( json.shininess !== undefined ) material.shininess = json.shininess;
 		if ( json.clearcoat !== undefined ) material.clearcoat = json.clearcoat;
 		if ( json.clearcoatRoughness !== undefined ) material.clearcoatRoughness = json.clearcoatRoughness;
+		if ( json.dispersion !== undefined ) material.dispersion = json.dispersion;
 		if ( json.iridescence !== undefined ) material.iridescence = json.iridescence;
 		if ( json.iridescenceIOR !== undefined ) material.iridescenceIOR = json.iridescenceIOR;
 		if ( json.iridescenceThicknessRange !== undefined ) material.iridescenceThicknessRange = json.iridescenceThicknessRange;

+ 2 - 0
src/materials/Material.js

@@ -218,6 +218,8 @@ class Material extends EventDispatcher {
 
 		}
 
+		if ( this.dispersion !== undefined ) data.dispersion = this.dispersion;
+
 		if ( this.iridescence !== undefined ) data.iridescence = this.iridescence;
 		if ( this.iridescenceIOR !== undefined ) data.iridescenceIOR = this.iridescenceIOR;
 		if ( this.iridescenceThicknessRange !== undefined ) data.iridescenceThicknessRange = this.iridescenceThicknessRange;

+ 20 - 0
src/materials/MeshPhysicalMaterial.js

@@ -68,6 +68,7 @@ class MeshPhysicalMaterial extends MeshStandardMaterial {
 
 		this._anisotropy = 0;
 		this._clearcoat = 0;
+		this._dispersion = 0;
 		this._iridescence = 0;
 		this._sheen = 0.0;
 		this._transmission = 0;
@@ -130,6 +131,24 @@ class MeshPhysicalMaterial extends MeshStandardMaterial {
 
 	}
 
+	get dispersion() {
+
+		return this._dispersion;
+
+	}
+
+	set dispersion( value ) {
+
+		if ( this._dispersion > 0 !== value > 0 ) {
+
+			this.version ++;
+
+		}
+
+		this._dispersion = value;
+
+	}
+
 	get sheen() {
 
 		return this._sheen;
@@ -188,6 +207,7 @@ class MeshPhysicalMaterial extends MeshStandardMaterial {
 		this.clearcoatNormalMap = source.clearcoatNormalMap;
 		this.clearcoatNormalScale.copy( source.clearcoatNormalScale );
 
+		this.dispersion = source.dispersion;
 		this.ior = source.ior;
 
 		this.iridescence = source.iridescence;

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

@@ -75,6 +75,12 @@ material.roughness = min( material.roughness, 1.0 );
 
 #endif
 
+#ifdef USE_DISPERSION
+
+	material.dispersion = dispersion;
+
+#endif
+
 #ifdef USE_IRIDESCENCE
 
 	material.iridescence = iridescence;

+ 1 - 0
src/renderers/shaders/ShaderChunk/lights_physical_pars_fragment.glsl.js

@@ -6,6 +6,7 @@ struct PhysicalMaterial {
 	float roughness;
 	vec3 specularColor;
 	float specularF90;
+	float dispersion;
 
 	#ifdef USE_CLEARCOAT
 		float clearcoat;

+ 1 - 1
src/renderers/shaders/ShaderChunk/transmission_fragment.glsl.js

@@ -25,7 +25,7 @@ export default /* glsl */`
 
 	vec4 transmitted = getIBLVolumeRefraction(
 		n, v, material.roughness, material.diffuseColor, material.specularColor, material.specularF90,
-		pos, modelMatrix, viewMatrix, projectionMatrix, material.ior, material.thickness,
+		pos, modelMatrix, viewMatrix, projectionMatrix, material.dispersion, material.ior, material.thickness,
 		material.attenuationColor, material.attenuationDistance );
 
 	material.transmissionAlpha = mix( material.transmissionAlpha, transmitted.a, material.transmission );

+ 47 - 13
src/renderers/shaders/ShaderChunk/transmission_pars_fragment.glsl.js

@@ -169,22 +169,56 @@ export default /* glsl */`
 
 	vec4 getIBLVolumeRefraction( const in vec3 n, const in vec3 v, const in float roughness, const in vec3 diffuseColor,
 		const in vec3 specularColor, const in float specularF90, const in vec3 position, const in mat4 modelMatrix,
-		const in mat4 viewMatrix, const in mat4 projMatrix, const in float ior, const in float thickness,
+		const in mat4 viewMatrix, const in mat4 projMatrix, const in float dispersion, const in float ior, const in float thickness,
 		const in vec3 attenuationColor, const in float attenuationDistance ) {
 
-		vec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );
-		vec3 refractedRayExit = position + transmissionRay;
+		vec4 transmittedLight;
+		vec3 transmittance;
+
+		#ifdef USE_DISPERSION
+
+			float halfSpread = ( ior - 1.0 ) * 0.025 * dispersion;
+			vec3 iors = vec3( ior - halfSpread, ior, ior + halfSpread );
+
+			for ( int i = 0; i < 3; i ++ ) {
+
+				vec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, iors[ i ], modelMatrix );
+				vec3 refractedRayExit = position + transmissionRay;
+		
+				// Project refracted vector on the framebuffer, while mapping to normalized device coordinates.
+				vec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );
+				vec2 refractionCoords = ndcPos.xy / ndcPos.w;
+				refractionCoords += 1.0;
+				refractionCoords /= 2.0;
+		
+				// Sample framebuffer to get pixel the refracted ray hits.
+				vec4 transmissionSample = getTransmissionSample( refractionCoords, roughness, iors[ i ] );
+				transmittedLight[ i ] = transmissionSample[ i ];
+				transmittedLight.a += transmissionSample.a;
+
+				transmittance[ i ] = diffuseColor[ i ] * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance )[ i ];
+
+			}
+
+			transmittedLight.a /= 3.0;
+		
+		#else
+		
+			vec3 transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );
+			vec3 refractedRayExit = position + transmissionRay;
+
+			// Project refracted vector on the framebuffer, while mapping to normalized device coordinates.
+			vec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );
+			vec2 refractionCoords = ndcPos.xy / ndcPos.w;
+			refractionCoords += 1.0;
+			refractionCoords /= 2.0;
+
+			// Sample framebuffer to get pixel the refracted ray hits.
+			transmittedLight = getTransmissionSample( refractionCoords, roughness, ior );
+			transmittance = diffuseColor * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance );
+		
+		#endif
 
-		// Project refracted vector on the framebuffer, while mapping to normalized device coordinates.
-		vec4 ndcPos = projMatrix * viewMatrix * vec4( refractedRayExit, 1.0 );
-		vec2 refractionCoords = ndcPos.xy / ndcPos.w;
-		refractionCoords += 1.0;
-		refractionCoords /= 2.0;
-
-		// Sample framebuffer to get pixel the refracted ray hits.
-		vec4 transmittedLight = getTransmissionSample( refractionCoords, roughness, ior );
-
-		vec3 transmittance = diffuseColor * volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance );
 		vec3 attenuatedColor = transmittance * transmittedLight.rgb;
 
 		// Get the specular component.

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

@@ -314,6 +314,7 @@ ShaderLib.physical = {
 			clearcoatRoughness: { value: 0 },
 			clearcoatRoughnessMap: { value: null },
 			clearcoatRoughnessMapTransform: { value: /*@__PURE__*/ new Matrix3() },
+			dispersion: { value: 0 },
 			iridescence: { value: 0 },
 			iridescenceMap: { value: null },
 			iridescenceMapTransform: { value: /*@__PURE__*/ new Matrix3() },

+ 4 - 0
src/renderers/shaders/ShaderLib/meshphysical.glsl.js

@@ -95,6 +95,10 @@ uniform float opacity;
 	uniform float clearcoatRoughness;
 #endif
 
+#ifdef USE_DISPERSION
+	uniform float dispersion;
+#endif
+
 #ifdef USE_IRIDESCENCE
 	uniform float iridescence;
 	uniform float iridescenceIOR;

+ 6 - 0
src/renderers/webgl/WebGLMaterials.js

@@ -470,6 +470,12 @@ function WebGLMaterials( renderer, properties ) {
 
 		}
 
+		if ( material.dispersion > 0 ) {
+
+			uniforms.dispersion.value = material.dispersion;
+
+		}
+
 		if ( material.iridescence > 0 ) {
 
 			uniforms.iridescence.value = material.iridescence;

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

@@ -777,6 +777,8 @@ function WebGLProgram( renderer, cacheKey, parameters, bindingStates ) {
 			parameters.clearcoatRoughnessMap ? '#define USE_CLEARCOAT_ROUGHNESSMAP' : '',
 			parameters.clearcoatNormalMap ? '#define USE_CLEARCOAT_NORMALMAP' : '',
 
+			parameters.dispersion ? '#define USE_DISPERSION' : '',
+
 			parameters.iridescence ? '#define USE_IRIDESCENCE' : '',
 			parameters.iridescenceMap ? '#define USE_IRIDESCENCEMAP' : '',
 			parameters.iridescenceThicknessMap ? '#define USE_IRIDESCENCE_THICKNESSMAP' : '',

+ 5 - 0
src/renderers/webgl/WebGLPrograms.js

@@ -127,6 +127,7 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities
 
 		const HAS_ANISOTROPY = material.anisotropy > 0;
 		const HAS_CLEARCOAT = material.clearcoat > 0;
+		const HAS_DISPERSION = material.dispersion > 0;
 		const HAS_IRIDESCENCE = material.iridescence > 0;
 		const HAS_SHEEN = material.sheen > 0;
 		const HAS_TRANSMISSION = material.transmission > 0;
@@ -225,6 +226,8 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities
 			clearcoatNormalMap: HAS_CLEARCOAT_NORMALMAP,
 			clearcoatRoughnessMap: HAS_CLEARCOAT_ROUGHNESSMAP,
 
+			dispersion: HAS_DISPERSION,
+
 			iridescence: HAS_IRIDESCENCE,
 			iridescenceMap: HAS_IRIDESCENCEMAP,
 			iridescenceThicknessMap: HAS_IRIDESCENCE_THICKNESSMAP,
@@ -505,6 +508,8 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities
 			_programLayers.enable( 18 );
 		if ( parameters.batching )
 			_programLayers.enable( 19 );
+		if ( parameters.dispersion )
+			_programLayers.enable( 20 );
 
 		array.push( _programLayers.mask );
 		_programLayers.disableAll();