Pārlūkot izejas kodu

WebGPURenderer: Introduce `dispersion` (#28439)

sunag 1 gadu atpakaļ
vecāks
revīzija
7a2ccef359

+ 1 - 0
examples/files.json

@@ -337,6 +337,7 @@
 		"webgpu_loader_gltf",
 		"webgpu_loader_gltf_anisotropy",
 		"webgpu_loader_gltf_compressed",
+		"webgpu_loader_gltf_dispersion",
 		"webgpu_loader_gltf_iridescence",
 		"webgpu_loader_gltf_sheen",
 		"webgpu_loader_gltf_transmission",

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

@@ -85,7 +85,7 @@ export { default as VertexColorNode, vertexColor } from './accessors/VertexColor
 export { default as CubeTextureNode, cubeTexture } from './accessors/CubeTextureNode.js';
 export { default as InstanceNode, instance } from './accessors/InstanceNode.js';
 export { default as BatchNode, batch } from './accessors/BatchNode.js';
-export { default as MaterialNode, materialAlphaTest, materialColor, materialShininess, materialEmissive, materialOpacity, materialSpecular, materialSpecularStrength, materialReflectivity, materialRoughness, materialMetalness, materialNormal, materialClearcoat, materialClearcoatRoughness, materialClearcoatNormal, materialRotation, materialSheen, materialSheenRoughness, materialIridescence, materialIridescenceIOR, materialIridescenceThickness, materialLineScale, materialLineDashSize, materialLineGapSize, materialLineWidth, materialLineDashOffset, materialPointWidth, materialAnisotropy, materialAnisotropyVector } from './accessors/MaterialNode.js';
+export { default as MaterialNode, materialAlphaTest, materialColor, materialShininess, materialEmissive, materialOpacity, materialSpecular, materialSpecularStrength, materialReflectivity, materialRoughness, materialMetalness, materialNormal, materialClearcoat, materialClearcoatRoughness, materialClearcoatNormal, materialRotation, materialSheen, materialSheenRoughness, materialIridescence, materialIridescenceIOR, materialIridescenceThickness, materialLineScale, materialLineDashSize, materialLineGapSize, materialLineWidth, materialLineDashOffset, materialPointWidth, materialAnisotropy, materialAnisotropyVector, materialDispersion } from './accessors/MaterialNode.js';
 export { default as MaterialReferenceNode, materialReference } from './accessors/MaterialReferenceNode.js';
 export { default as RendererReferenceNode, rendererReference } from './accessors/RendererReferenceNode.js';
 export { default as MorphNode, morphReference } from './accessors/MorphNode.js';

+ 2 - 0
examples/jsm/nodes/accessors/MaterialNode.js

@@ -366,6 +366,7 @@ MaterialNode.LINE_GAP_SIZE = 'gapSize';
 MaterialNode.LINE_WIDTH = 'linewidth';
 MaterialNode.LINE_DASH_OFFSET = 'dashOffset';
 MaterialNode.POINT_WIDTH = 'pointWidth';
+MaterialNode.DISPERSION = 'dispersion';
 
 export default MaterialNode;
 
@@ -405,6 +406,7 @@ export const materialLineGapSize = nodeImmutable( MaterialNode, MaterialNode.LIN
 export const materialLineWidth = nodeImmutable( MaterialNode, MaterialNode.LINE_WIDTH );
 export const materialLineDashOffset = nodeImmutable( MaterialNode, MaterialNode.LINE_DASH_OFFSET );
 export const materialPointWidth = nodeImmutable( MaterialNode, MaterialNode.POINT_WIDTH );
+export const materialDispersion = nodeImmutable( MaterialNode, MaterialNode.DISPERSION );
 export const materialAnisotropyVector = uniform( new Vector2() ).onReference( function ( frame ) {
 
 	return frame.material;

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

@@ -78,5 +78,6 @@ export const transmission = nodeImmutable( PropertyNode, 'float', 'Transmission'
 export const thickness = nodeImmutable( PropertyNode, 'float', 'Thickness' );
 export const attenuationDistance = nodeImmutable( PropertyNode, 'float', 'AttenuationDistance' );
 export const attenuationColor = nodeImmutable( PropertyNode, 'color', 'AttenuationColor' );
+export const dispersion = nodeImmutable( PropertyNode, 'float', 'Dispersion' );
 
 addNodeClass( 'PropertyNode', PropertyNode );

+ 59 - 15
examples/jsm/nodes/functions/PhysicalLightingModel.js

@@ -6,7 +6,7 @@ import F_Schlick from './BSDF/F_Schlick.js';
 import Schlick_to_F0 from './BSDF/Schlick_to_F0.js';
 import BRDF_Sheen from './BSDF/BRDF_Sheen.js';
 import LightingModel from '../core/LightingModel.js';
-import { diffuseColor, specularColor, specularF90, roughness, clearcoat, clearcoatRoughness, sheen, sheenRoughness, iridescence, iridescenceIOR, iridescenceThickness, ior, thickness, transmission, attenuationDistance, attenuationColor } from '../core/PropertyNode.js';
+import { diffuseColor, specularColor, specularF90, roughness, clearcoat, clearcoatRoughness, sheen, sheenRoughness, iridescence, iridescenceIOR, iridescenceThickness, ior, thickness, transmission, attenuationDistance, attenuationColor, dispersion } from '../core/PropertyNode.js';
 import { transformedNormalView, transformedClearcoatNormalView, transformedNormalWorld } from '../accessors/NormalNode.js';
 import { positionViewDirection, positionWorld } from '../accessors/PositionNode.js';
 import { tslFn, float, vec2, vec3, vec4, mat3, If } from '../shadernode/ShaderNode.js';
@@ -17,6 +17,7 @@ import { cameraPosition, cameraProjectionMatrix, cameraViewMatrix } from '../acc
 import { modelWorldMatrix } from '../accessors/ModelNode.js';
 import { viewportResolution } from '../display/ViewportNode.js';
 import { viewportMipTexture } from '../display/ViewportTextureNode.js';
+import { loop } from '../utils/LoopNode.js';
 
 //
 // Transmission
@@ -102,21 +103,62 @@ const volumeAttenuation = tslFn( ( [ transmissionDistance, attenuationColor, att
 	]
 } );
 
-const getIBLVolumeRefraction = tslFn( ( [ n, v, roughness, diffuseColor, specularColor, specularF90, position, modelMatrix, viewMatrix, projMatrix, ior, thickness, attenuationColor, attenuationDistance ] ) => {
+const getIBLVolumeRefraction = tslFn( ( [ n, v, roughness, diffuseColor, specularColor, specularF90, position, modelMatrix, viewMatrix, projMatrix, ior, thickness, attenuationColor, attenuationDistance, dispersion ] ) => {
 
-	const transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );
-	const refractedRayExit = position.add( transmissionRay );
+	let transmittedLight, transmittance;
 
-	// Project refracted vector on the framebuffer, while mapping to normalized device coordinates.
-	const ndcPos = projMatrix.mul( viewMatrix.mul( vec4( refractedRayExit, 1.0 ) ) );
-	const refractionCoords = vec2( ndcPos.xy.div( ndcPos.w ) ).toVar();
-	refractionCoords.addAssign( 1.0 );
-	refractionCoords.divAssign( 2.0 );
-	refractionCoords.assign( vec2( refractionCoords.x, refractionCoords.y.oneMinus() ) ); // webgpu
+	if ( dispersion ) {
+
+		transmittedLight = vec4().toVar();
+		transmittance = vec3().toVar();
+
+		const halfSpread = ior.sub( 1.0 ).mul( dispersion.mul( 0.025 ) );
+		const iors = vec3( ior.sub( halfSpread ), ior, ior.add( halfSpread ) );
+
+		loop( { start: 0, end: 3 }, ( { i } ) => {
+
+			const ior = iors.element( i );
+
+			const transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );
+			const refractedRayExit = position.add( transmissionRay );
+
+			// Project refracted vector on the framebuffer, while mapping to normalized device coordinates.
+			const ndcPos = projMatrix.mul( viewMatrix.mul( vec4( refractedRayExit, 1.0 ) ) );
+			const refractionCoords = vec2( ndcPos.xy.div( ndcPos.w ) ).toVar();
+			refractionCoords.addAssign( 1.0 );
+			refractionCoords.divAssign( 2.0 );
+			refractionCoords.assign( vec2( refractionCoords.x, refractionCoords.y.oneMinus() ) ); // webgpu
+
+			// Sample framebuffer to get pixel the refracted ray hits.
+			const transmissionSample = getTransmissionSample( refractionCoords, roughness, ior );
+
+			transmittedLight.element( i ).assign( transmissionSample.element( i ) );
+			transmittedLight.a.addAssign( transmissionSample.a );
+
+			transmittance.element( i ).assign( diffuseColor.element( i ).mul( volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance ).element( i ) ) );
+
+		} );
+
+		transmittedLight.a.divAssign( 3.0 );
+
+	} else {
+
+		const transmissionRay = getVolumeTransmissionRay( n, v, thickness, ior, modelMatrix );
+		const refractedRayExit = position.add( transmissionRay );
+
+		// Project refracted vector on the framebuffer, while mapping to normalized device coordinates.
+		const ndcPos = projMatrix.mul( viewMatrix.mul( vec4( refractedRayExit, 1.0 ) ) );
+		const refractionCoords = vec2( ndcPos.xy.div( ndcPos.w ) ).toVar();
+		refractionCoords.addAssign( 1.0 );
+		refractionCoords.divAssign( 2.0 );
+		refractionCoords.assign( vec2( refractionCoords.x, refractionCoords.y.oneMinus() ) ); // webgpu
+
+		// Sample framebuffer to get pixel the refracted ray hits.
+		transmittedLight = getTransmissionSample( refractionCoords, roughness, ior );
+		transmittance = diffuseColor.mul( volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance ) );
+
+	}
 
-	// Sample framebuffer to get pixel the refracted ray hits.
-	const transmittedLight = getTransmissionSample( refractionCoords, roughness, ior );
-	const transmittance = diffuseColor.mul( volumeAttenuation( length( transmissionRay ), attenuationColor, attenuationDistance ) );
 	const attenuatedColor = transmittance.rgb.mul( transmittedLight.rgb );
 	const dotNV = n.dot( v ).clamp();
 
@@ -295,7 +337,7 @@ const clearcoatF90 = vec3( 1 );
 
 class PhysicalLightingModel extends LightingModel {
 
-	constructor( clearcoat = false, sheen = false, iridescence = false, anisotropy = false, transmission = false ) {
+	constructor( clearcoat = false, sheen = false, iridescence = false, anisotropy = false, transmission = false, dispersion = false ) {
 
 		super();
 
@@ -304,6 +346,7 @@ class PhysicalLightingModel extends LightingModel {
 		this.iridescence = iridescence;
 		this.anisotropy = anisotropy;
 		this.transmission = transmission;
+		this.dispersion = dispersion;
 
 		this.clearcoatRadiance = null;
 		this.clearcoatSpecularDirect = null;
@@ -368,7 +411,8 @@ class PhysicalLightingModel extends LightingModel {
 				ior,
 				thickness,
 				attenuationColor,
-				attenuationDistance
+				attenuationDistance,
+				this.dispersion ? dispersion : null
 			);
 
 			context.backdropAlpha = transmission;

+ 19 - 3
examples/jsm/nodes/materials/MeshPhysicalNodeMaterial.js

@@ -1,7 +1,7 @@
 import { addNodeMaterial } from './NodeMaterial.js';
 import { transformedClearcoatNormalView } from '../accessors/NormalNode.js';
-import { clearcoat, clearcoatRoughness, sheen, sheenRoughness, iridescence, iridescenceIOR, iridescenceThickness, specularColor, specularF90, diffuseColor, metalness, roughness, anisotropy, alphaT, anisotropyT, anisotropyB, ior, transmission, thickness, attenuationDistance, attenuationColor } from '../core/PropertyNode.js';
-import { materialClearcoat, materialClearcoatRoughness, materialClearcoatNormal, materialSheen, materialSheenRoughness, materialIridescence, materialIridescenceIOR, materialIridescenceThickness, materialSpecularIntensity, materialSpecularColor, materialAnisotropy, materialIOR, materialTransmission, materialThickness, materialAttenuationDistance, materialAttenuationColor } from '../accessors/MaterialNode.js';
+import { clearcoat, clearcoatRoughness, sheen, sheenRoughness, iridescence, iridescenceIOR, iridescenceThickness, specularColor, specularF90, diffuseColor, metalness, roughness, anisotropy, alphaT, anisotropyT, anisotropyB, ior, transmission, thickness, attenuationDistance, attenuationColor, dispersion } from '../core/PropertyNode.js';
+import { materialClearcoat, materialClearcoatRoughness, materialClearcoatNormal, materialSheen, materialSheenRoughness, materialIridescence, materialIridescenceIOR, materialIridescenceThickness, materialSpecularIntensity, materialSpecularColor, materialAnisotropy, materialIOR, materialTransmission, materialThickness, materialAttenuationDistance, materialAttenuationColor, materialDispersion } from '../accessors/MaterialNode.js';
 import { float, vec2, vec3, If } from '../shadernode/ShaderNode.js';
 import { TBNViewMatrix } from '../accessors/AccessorsUtils.js';
 import PhysicalLightingModel from '../functions/PhysicalLightingModel.js';
@@ -38,6 +38,7 @@ class MeshPhysicalNodeMaterial extends MeshStandardNodeMaterial {
 		this.thicknessNode = null;
 		this.attenuationDistanceNode = null;
 		this.attenuationColorNode = null;
+		this.dispersionNode = null;
 
 		this.anisotropyNode = null;
 
@@ -77,6 +78,12 @@ class MeshPhysicalNodeMaterial extends MeshStandardNodeMaterial {
 
 	}
 
+	get useDispersion() {
+
+		return this.dispersion > 0 || this.dispersionNode !== null;
+
+	}
+
 	setupSpecular() {
 
 		const iorNode = this.iorNode ? float( this.iorNode ) : materialIOR;
@@ -89,7 +96,7 @@ class MeshPhysicalNodeMaterial extends MeshStandardNodeMaterial {
 
 	setupLightingModel( /*builder*/ ) {
 
-		return new PhysicalLightingModel( this.useClearcoat, this.useSheen, this.useIridescence, this.useAnisotropy, this.useTransmission );
+		return new PhysicalLightingModel( this.useClearcoat, this.useSheen, this.useIridescence, this.useAnisotropy, this.useTransmission, this.useDispersion );
 
 	}
 
@@ -176,6 +183,14 @@ class MeshPhysicalNodeMaterial extends MeshStandardNodeMaterial {
 			attenuationDistance.assign( attenuationDistanceNode );
 			attenuationColor.assign( attenuationColorNode );
 
+			if ( this.useDispersion ) {
+
+				const dispersionNode = this.dispersionNode ? float( this.dispersionNode ) : materialDispersion;
+
+				dispersion.assign( dispersionNode );
+
+			}
+
 		}
 
 	}
@@ -212,6 +227,7 @@ class MeshPhysicalNodeMaterial extends MeshStandardNodeMaterial {
 		this.thicknessNode = source.thicknessNode;
 		this.attenuationDistanceNode = source.attenuationDistanceNode;
 		this.attenuationColorNode = source.attenuationColorNode;
+		this.dispersionNode = source.dispersionNode;
 
 		this.anisotropyNode = source.anisotropyNode;
 

BIN
examples/screenshots/webgpu_loader_gltf_dispersion.jpg


+ 100 - 0
examples/webgpu_loader_gltf_dispersion.html

@@ -0,0 +1,100 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - 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> webgpu - 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 />
+			HDR by <a href="https://polyhaven.com/a/studio_small_08" target="_blank" rel="noopener">Poly Haven</a> 
+		</div>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+
+			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';
+
+			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 WebGPURenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( render );
+				renderer.toneMapping = THREE.ReinhardToneMapping; // TODO: Add THREE.NeutralToneMapping;
+				renderer.toneMappingExposure = 1;
+				container.appendChild( renderer.domElement );
+
+				const rgbeLoader = await new RGBELoader().setPath( 'textures/equirectangular/' ).loadAsync( 'pedestrian_overpass_1k.hdr' );
+				rgbeLoader.mapping = THREE.EquirectangularReflectionMapping;
+
+				scene = new THREE.Scene();
+				scene.backgroundBlurriness = 0.5;
+				scene.environment = rgbeLoader;
+				scene.background = rgbeLoader;
+
+				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.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 );
+
+			}
+
+			//
+
+			function render() {
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>