Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

NodeMaterial: Add support for alphaHash. #29757

Merged
merged 5 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
172 changes: 172 additions & 0 deletions examples/webgpu_materials_alphahash.html
Original file line number Diff line number Diff line change
@@ -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 changes: 9 additions & 0 deletions src/materials/nodes/NodeMaterial.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {

Expand Down Expand Up @@ -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 );
Expand Down
64 changes: 64 additions & 0 deletions src/nodes/functions/material/getAlphaHashThreshold.js
Original file line number Diff line number Diff line change
@@ -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 change: 1 addition & 0 deletions test/e2e/puppeteer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand Down