Skip to content

Commit

Permalink
WebGPURenderer: Introduce dispersion (#28439)
Browse files Browse the repository at this point in the history
  • Loading branch information
sunag authored May 20, 2024
1 parent 5d37af1 commit 7a2ccef
Show file tree
Hide file tree
Showing 8 changed files with 183 additions and 19 deletions.
1 change: 1 addition & 0 deletions examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion examples/jsm/nodes/Nodes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down
2 changes: 2 additions & 0 deletions examples/jsm/nodes/accessors/MaterialNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions examples/jsm/nodes/core/PropertyNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
74 changes: 59 additions & 15 deletions examples/jsm/nodes/functions/PhysicalLightingModel.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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();

Expand All @@ -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;
Expand Down Expand Up @@ -368,7 +411,8 @@ class PhysicalLightingModel extends LightingModel {
ior,
thickness,
attenuationColor,
attenuationDistance
attenuationDistance,
this.dispersion ? dispersion : null
);

context.backdropAlpha = transmission;
Expand Down
22 changes: 19 additions & 3 deletions examples/jsm/nodes/materials/MeshPhysicalNodeMaterial.js
Original file line number Diff line number Diff line change
@@ -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';
Expand Down Expand Up @@ -38,6 +38,7 @@ class MeshPhysicalNodeMaterial extends MeshStandardNodeMaterial {
this.thicknessNode = null;
this.attenuationDistanceNode = null;
this.attenuationColorNode = null;
this.dispersionNode = null;

this.anisotropyNode = null;

Expand Down Expand Up @@ -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;
Expand All @@ -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 );

}

Expand Down Expand Up @@ -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 );

}

}

}
Expand Down Expand Up @@ -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;

Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
100 changes: 100 additions & 0 deletions examples/webgpu_loader_gltf_dispersion.html
Original file line number Diff line number Diff line change
@@ -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>

0 comments on commit 7a2ccef

Please sign in to comment.