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

NodeMaterial: Add support for `alphaHash`. (#29757)

* NodeMaterial: Add support for `alphaHash`.

* Clean up.

* E2E: Update exception list.

* Examples: Clean up.

* NodeMaterial: Fix alpha hash check.
Michael Herzog 1 год назад
Родитель
Сommit
ff6450a995

+ 1 - 0
examples/files.json

@@ -350,6 +350,7 @@
 		"webgpu_loader_gltf_transmission",
 		"webgpu_loader_materialx",
 		"webgpu_materials",
+		"webgpu_materials_alphahash",
 		"webgpu_materials_arrays",
 		"webgpu_materials_basic",
 		"webgpu_materials_displacementmap",

BIN
examples/screenshots/webgpu_materials_alphahash.jpg


+ 172 - 0
examples/webgpu_materials_alphahash.html

@@ -0,0 +1,172 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - materials - alpha hash</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>
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.webgpu.js",
+					"three/tsl": "../build/three.webgpu.js",
+					"three/addons/": "./jsm/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+
+			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 { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
+
+			import { ssaaPass } from 'three/addons/tsl/display/SSAAPassNode.js';
+
+			let camera, scene, renderer, controls, stats, mesh, material, postProcessing;
+
+			const amount = parseInt( window.location.search.slice( 1 ) ) || 3;
+			const count = Math.pow( amount, 3 );
+
+			const color = new THREE.Color();
+
+			const params = {
+				alpha: 0.5,
+				alphaHash: true
+			};
+
+			init();
+
+			async function init() {
+
+				camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 100 );
+				camera.position.set( amount, amount, amount );
+				camera.lookAt( 0, 0, 0 );
+
+				scene = new THREE.Scene();
+
+				const geometry = new THREE.IcosahedronGeometry( 0.5, 3 );
+
+				material = new THREE.MeshStandardMaterial( {
+					color: 0xffffff,
+					alphaHash: params.alphaHash,
+					opacity: params.alpha
+				} );
+
+				mesh = new THREE.InstancedMesh( geometry, material, count );
+
+				let i = 0;
+				const offset = ( amount - 1 ) / 2;
+
+				const matrix = new THREE.Matrix4();
+
+				for ( let x = 0; x < amount; x ++ ) {
+
+					for ( let y = 0; y < amount; y ++ ) {
+
+						for ( let z = 0; z < amount; z ++ ) {
+
+							matrix.setPosition( offset - x, offset - y, offset - z );
+
+							mesh.setMatrixAt( i, matrix );
+							mesh.setColorAt( i, color.setHex( Math.random() * 0xffffff ) );
+
+							i ++;
+
+						}
+
+					}
+
+				}
+
+				scene.add( mesh );
+
+				//
+
+				renderer = new THREE.WebGPURenderer();
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				document.body.appendChild( renderer.domElement );
+
+				await renderer.init();
+
+				//
+
+				const environment = new RoomEnvironment();
+				const pmremGenerator = new THREE.PMREMGenerator( renderer );
+
+				scene.environment = pmremGenerator.fromScene( environment ).texture;
+				environment.dispose();
+
+				//
+
+				// postprocessing
+
+				postProcessing = new THREE.PostProcessing( renderer );
+				const scenePass = ssaaPass( scene, camera );
+				scenePass.sampleLevel = 3;
+
+				postProcessing.outputNode = scenePass;
+
+				//
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.enableZoom = false;
+				controls.enablePan = false;
+
+				//
+
+				const gui = new GUI();
+
+				gui.add( params, 'alpha', 0, 1 ).onChange( onMaterialUpdate );
+				gui.add( params, 'alphaHash' ).onChange( onMaterialUpdate );
+
+				const ssaaFolder = gui.addFolder( 'SSAA' );
+				ssaaFolder.add( scenePass, 'sampleLevel', 0, 4, 1 );
+
+				//
+
+				stats = new Stats();
+				document.body.appendChild( stats.dom );
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function onMaterialUpdate() {
+
+				material.opacity = params.alpha;
+				material.alphaHash = params.alphaHash;
+				material.transparent = ! params.alphaHash;
+				material.depthWrite = params.alphaHash;
+
+				material.needsUpdate = true;
+
+			}
+
+			function animate() {
+
+				postProcessing.render();
+
+				stats.update();
+
+			}
+
+		</script>
+	</body>
+</html>

+ 9 - 0
src/materials/nodes/NodeMaterial.js

@@ -22,6 +22,7 @@ import { depth, perspectiveDepthToLogarithmicDepth, viewZToOrthographicDepth } f
 import { cameraFar, cameraNear } from '../../nodes/accessors/Camera.js';
 import { clipping, clippingAlpha } from '../../nodes/accessors/ClippingNode.js';
 import NodeMaterialObserver from './manager/NodeMaterialObserver.js';
+import getAlphaHashThreshold from '../../nodes/functions/material/getAlphaHashThreshold.js';
 
 class NodeMaterial extends Material {
 
@@ -367,6 +368,14 @@ class NodeMaterial extends Material {
 
 		}
 
+		// ALPHA HASH
+
+		if ( this.alphaHash === true ) {
+
+			diffuseColor.a.lessThan( getAlphaHashThreshold( positionLocal ) ).discard();
+
+		}
+
 		if ( this.transparent === false && this.blending === NormalBlending && this.alphaToCoverage === false ) {
 
 			diffuseColor.a.assign( 1.0 );

+ 64 - 0
src/nodes/functions/material/getAlphaHashThreshold.js

@@ -0,0 +1,64 @@
+import { abs, add, ceil, clamp, dFdx, dFdy, exp2, float, floor, Fn, fract, length, log2, max, min, mul, sin, sub, vec2, vec3 } from '../../tsl/TSLBase.js';
+
+/**
+ * See: https://casual-effects.com/research/Wyman2017Hashed/index.html
+ */
+
+const ALPHA_HASH_SCALE = 0.05; // Derived from trials only, and may be changed.
+
+const hash2D = /*@__PURE__*/ Fn( ( [ value ] ) => {
+
+	return fract( mul( 1.0e4, sin( mul( 17.0, value.x ).add( mul( 0.1, value.y ) ) ) ).mul( add( 0.1, abs( sin( mul( 13.0, value.y ).add( value.x ) ) ) ) ) );
+
+} );
+
+const hash3D = /*@__PURE__*/ Fn( ( [ value ] ) => {
+
+	return hash2D( vec2( hash2D( value.xy ), value.z ) );
+
+} );
+
+const getAlphaHashThreshold = /*@__PURE__*/ Fn( ( [ position ] ) => {
+
+	// Find the discretized derivatives of our coordinates
+	const maxDeriv = max(
+		length( dFdx( position.xyz ) ),
+		length( dFdy( position.xyz ) )
+	).toVar( 'maxDeriv' );
+
+	const pixScale = float( 1 ).div( float( ALPHA_HASH_SCALE ).mul( maxDeriv ) ).toVar( 'pixScale' );
+
+	// Find two nearest log-discretized noise scales
+	const pixScales = vec2(
+		exp2( floor( log2( pixScale ) ) ),
+		exp2( ceil( log2( pixScale ) ) )
+	).toVar( 'pixScales' );
+
+	// Compute alpha thresholds at our two noise scales
+	const alpha = vec2(
+		hash3D( floor( pixScales.x.mul( position.xyz ) ) ),
+		hash3D( floor( pixScales.y.mul( position.xyz ) ) ),
+	).toVar( 'alpha' );
+
+	// Factor to interpolate lerp with
+	const lerpFactor = fract( log2( pixScale ) ).toVar( 'lerpFactor' );
+
+	// Interpolate alpha threshold from noise at two scales
+	const x = add( mul( lerpFactor.oneMinus(), alpha.x ), mul( lerpFactor, alpha.y ) ).toVar( 'x' );
+
+	// Pass into CDF to compute uniformly distrib threshold
+	const a = min( lerpFactor, lerpFactor.oneMinus() ).toVar( 'a' );
+	const cases = vec3(
+		x.mul( x ).div( mul( 2.0, a ).mul( sub( 1.0, a ) ) ),
+		x.sub( mul( 0.5, a ) ).div( sub( 1.0, a ) ),
+		sub( 1.0, sub( 1.0, x ).mul( sub( 1.0, x ) ).div( mul( 2.0, a ).mul( sub( 1.0, a ) ) ) ) ).toVar( 'cases' );
+
+	// Find our final, uniformly distributed alpha threshold (ατ)
+	const threshold = x.lessThan( a.oneMinus() ).select( x.lessThan( a ).select( cases.x, cases.y ), cases.z );
+
+	// Avoids ατ == 0. Could also do ατ =1-ατ
+	return clamp( threshold, 1.0e-6, 1.0 );
+
+} );
+
+export default getAlphaHashThreshold;

+ 1 - 0
test/e2e/puppeteer.js

@@ -89,6 +89,7 @@ const exceptionList = [
 	'webgl_loader_texture_lottie',
 	'webgl_loader_texture_pvrtc',
 	'webgl_materials_alphahash',
+	'webgpu_materials_alphahash',
 	'webgl_materials_blending',
 	'webgl_mirror',
 	'webgl_morphtargets_face',

粤ICP备19079148号