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

WebGPURenderer: Add PointShadowNode #29849

Merged
merged 16 commits into from
Nov 21, 2024
1 change: 1 addition & 0 deletions examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -335,6 +335,7 @@
"webgpu_lights_custom",
"webgpu_lights_ies_spotlight",
"webgpu_lights_phong",
"webgpu_lights_physical",
"webgpu_lights_rectarealight",
"webgpu_lights_selective",
"webgpu_lights_tiled",
Expand Down
Binary file added examples/screenshots/webgpu_lights_physical.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
297 changes: 297 additions & 0 deletions examples/webgpu_lights_physical.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,297 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgpu - physical lights</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="container"></div>
<div id="info">
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - Physically accurate incandescent bulb by <a href="http://clara.io" target="_blank" rel="noopener">Ben Houston</a><br />
Real world scale: Brick cube is 50 cm in size. Globe is 50 cm in diameter.<br/>
Reinhard inline tonemapping with real-world light falloff (decay = 2).
</div>

<script type="importmap">
{
"imports": {
"three": "../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';

let camera, scene, renderer, bulbLight, bulbMat, hemiLight, stats;
let ballMat, cubeMat, floorMat;

let previousShadowMap = false;

// ref for lumens: http://www.power-sure.com/lumens.htm
const bulbLuminousPowers = {
'110000 lm (1000W)': 110000,
'3500 lm (300W)': 3500,
'1700 lm (100W)': 1700,
'800 lm (60W)': 800,
'400 lm (40W)': 400,
'180 lm (25W)': 180,
'20 lm (4W)': 20,
'Off': 0
};

// ref for solar irradiances: https://en.wikipedia.org/wiki/Lux
const hemiLuminousIrradiances = {
'0.0001 lx (Moonless Night)': 0.0001,
'0.002 lx (Night Airglow)': 0.002,
'0.5 lx (Full Moon)': 0.5,
'3.4 lx (City Twilight)': 3.4,
'50 lx (Living Room)': 50,
'100 lx (Very Overcast)': 100,
'350 lx (Office Room)': 350,
'400 lx (Sunrise/Sunset)': 400,
'1000 lx (Overcast)': 1000,
'18000 lx (Daylight)': 18000,
'50000 lx (Direct Sun)': 50000
};

const params = {
shadows: true,
exposure: 0.68,
bulbPower: Object.keys( bulbLuminousPowers )[ 4 ],
hemiIrradiance: Object.keys( hemiLuminousIrradiances )[ 0 ]
};

init();

function init() {

const container = document.getElementById( 'container' );

stats = new Stats();
container.appendChild( stats.dom );

//

camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.1, 100 );
camera.position.x = - 4;
camera.position.z = 4;
camera.position.y = 2;

scene = new THREE.Scene();

const bulbGeometry = new THREE.SphereGeometry( 0.02, 16, 8 );
bulbLight = new THREE.PointLight( 0xffee88, 1, 100, 2 );

bulbMat = new THREE.MeshStandardMaterial( {
emissive: 0xffffee,
emissiveIntensity: 1,
color: 0x000000
} );
bulbLight.add( new THREE.Mesh( bulbGeometry, bulbMat ) );
bulbLight.position.set( 0, 2, 0 );
bulbLight.castShadow = true;
scene.add( bulbLight );

hemiLight = new THREE.HemisphereLight( 0xddeeff, 0x0f0e0d, 0.02 );
scene.add( hemiLight );

floorMat = new THREE.MeshStandardMaterial( {
roughness: 0.8,
color: 0xffffff,
metalness: 0.2,
bumpScale: 1
} );
const textureLoader = new THREE.TextureLoader();
textureLoader.load( 'textures/hardwood2_diffuse.jpg', function ( map ) {

map.wrapS = THREE.RepeatWrapping;
map.wrapT = THREE.RepeatWrapping;
map.anisotropy = 4;
map.repeat.set( 10, 24 );
map.colorSpace = THREE.SRGBColorSpace;
floorMat.map = map;
floorMat.needsUpdate = true;

} );
textureLoader.load( 'textures/hardwood2_bump.jpg', function ( map ) {

map.wrapS = THREE.RepeatWrapping;
map.wrapT = THREE.RepeatWrapping;
map.anisotropy = 4;
map.repeat.set( 10, 24 );
floorMat.bumpMap = map;
floorMat.needsUpdate = true;

} );
textureLoader.load( 'textures/hardwood2_roughness.jpg', function ( map ) {

map.wrapS = THREE.RepeatWrapping;
map.wrapT = THREE.RepeatWrapping;
map.anisotropy = 4;
map.repeat.set( 10, 24 );
floorMat.roughnessMap = map;
floorMat.needsUpdate = true;

} );

cubeMat = new THREE.MeshStandardMaterial( {
roughness: 0.7,
color: 0xffffff,
bumpScale: 1,
metalness: 0.2
} );
textureLoader.load( 'textures/brick_diffuse.jpg', function ( map ) {

map.wrapS = THREE.RepeatWrapping;
map.wrapT = THREE.RepeatWrapping;
map.anisotropy = 4;
map.repeat.set( 1, 1 );
map.colorSpace = THREE.SRGBColorSpace;
cubeMat.map = map;
cubeMat.needsUpdate = true;

} );
textureLoader.load( 'textures/brick_bump.jpg', function ( map ) {

map.wrapS = THREE.RepeatWrapping;
map.wrapT = THREE.RepeatWrapping;
map.anisotropy = 4;
map.repeat.set( 1, 1 );
cubeMat.bumpMap = map;
cubeMat.needsUpdate = true;

} );

ballMat = new THREE.MeshStandardMaterial( {
color: 0xffffff,
roughness: 0.5,
metalness: 1.0
} );
textureLoader.load( 'textures/planets/earth_atmos_2048.jpg', function ( map ) {

map.anisotropy = 4;
map.colorSpace = THREE.SRGBColorSpace;
ballMat.map = map;
ballMat.needsUpdate = true;

} );
textureLoader.load( 'textures/planets/earth_specular_2048.jpg', function ( map ) {

map.anisotropy = 4;
map.colorSpace = THREE.SRGBColorSpace;
ballMat.metalnessMap = map;
ballMat.needsUpdate = true;

} );

const floorGeometry = new THREE.PlaneGeometry( 20, 20 );
const floorMesh = new THREE.Mesh( floorGeometry, floorMat );
floorMesh.receiveShadow = true;
floorMesh.rotation.x = - Math.PI / 2.0;
scene.add( floorMesh );

const ballGeometry = new THREE.SphereGeometry( 0.25, 32, 32 );
const ballMesh = new THREE.Mesh( ballGeometry, ballMat );
ballMesh.position.set( 1, 0.25, 1 );
ballMesh.rotation.y = Math.PI;
ballMesh.castShadow = true;
scene.add( ballMesh );

const boxGeometry = new THREE.BoxGeometry( 0.5, 0.5, 0.5 );
const boxMesh = new THREE.Mesh( boxGeometry, cubeMat );
boxMesh.position.set( - 0.5, 0.25, - 1 );
boxMesh.castShadow = true;
scene.add( boxMesh );

const boxMesh2 = new THREE.Mesh( boxGeometry, cubeMat );
boxMesh2.position.set( 0, 0.25, - 5 );
boxMesh2.castShadow = true;
scene.add( boxMesh2 );

const boxMesh3 = new THREE.Mesh( boxGeometry, cubeMat );
boxMesh3.position.set( 7, 0.25, 0 );
boxMesh3.castShadow = true;
scene.add( boxMesh3 );

//

renderer = new THREE.WebGPURenderer();
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( animate );
renderer.shadowMap.enabled = true;
renderer.toneMapping = THREE.ReinhardToneMapping;
container.appendChild( renderer.domElement );

const controls = new OrbitControls( camera, renderer.domElement );
controls.minDistance = 1;
controls.maxDistance = 20;

window.addEventListener( 'resize', onWindowResize );

//

const gui = new GUI();

gui.add( params, 'hemiIrradiance', Object.keys( hemiLuminousIrradiances ) );
gui.add( params, 'bulbPower', Object.keys( bulbLuminousPowers ) );
gui.add( params, 'exposure', 0, 1 );
gui.add( params, 'shadows' );
gui.open();

}

function onWindowResize() {

camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();

renderer.setSize( window.innerWidth, window.innerHeight );

}

//

function animate() {

renderer.toneMappingExposure = Math.pow( params.exposure, 5.0 ); // to allow for very bright scenes.
renderer.shadowMap.enabled = params.shadows;
bulbLight.castShadow = params.shadows;

if ( params.shadows !== previousShadowMap ) {

ballMat.needsUpdate = true;
cubeMat.needsUpdate = true;
floorMat.needsUpdate = true;
previousShadowMap = params.shadows;

}

bulbLight.power = bulbLuminousPowers[ params.bulbPower ];
bulbMat.emissiveIntensity = bulbLight.intensity / Math.pow( 0.02, 2.0 ); // convert from intensity to irradiance at bulb surface

hemiLight.intensity = hemiLuminousIrradiances[ params.hemiIrradiance ];
const time = Date.now() * 0.0005;

bulbLight.position.y = Math.cos( time ) * 0.75 + 1.25;

renderer.render( scene, camera );

stats.update();

}

</script>
</body>
</html>
23 changes: 13 additions & 10 deletions src/materials/nodes/manager/NodeMaterialObserver.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ class NodeMaterialObserver {

if ( data === undefined ) {

const { geometry, material } = renderObject;
const { geometry, material, object } = renderObject;

data = {
material: this.getMaterialData( material ),
Expand All @@ -94,18 +94,18 @@ class NodeMaterialObserver {
indexVersion: geometry.index ? geometry.index.version : null,
drawRange: { start: geometry.drawRange.start, count: geometry.drawRange.count }
},
worldMatrix: renderObject.object.matrixWorld.clone()
worldMatrix: object.matrixWorld.clone()
};

if ( renderObject.object.center ) {
if ( object.center ) {

data.center = renderObject.object.center.clone();
data.center = object.center.clone();

}

if ( renderObject.object.morphTargetInfluences ) {
if ( object.morphTargetInfluences ) {

data.morphTargetInfluences = renderObject.object.morphTargetInfluences.slice();
data.morphTargetInfluences = object.morphTargetInfluences.slice();

}

Expand Down Expand Up @@ -289,15 +289,16 @@ class NodeMaterialObserver {

}

// Compare each attribute
// compare each attribute

for ( const name of storedAttributeNames ) {

const storedAttributeData = storedAttributes[ name ];
const attribute = attributes[ name ];

if ( attribute === undefined ) {

// Attribute was removed
// attribute was removed
delete storedAttributes[ name ];
return false;

Expand All @@ -312,7 +313,8 @@ class NodeMaterialObserver {

}

// Check index
// check index

const index = geometry.index;
const storedIndexVersion = storedGeometryData.indexVersion;
const currentIndexVersion = index ? index.version : null;
Expand All @@ -324,7 +326,8 @@ class NodeMaterialObserver {

}

// Check drawRange
// check drawRange

if ( storedGeometryData.drawRange.start !== geometry.drawRange.start || storedGeometryData.drawRange.count !== geometry.drawRange.count ) {

storedGeometryData.drawRange.start = geometry.drawRange.start;
Expand Down
8 changes: 7 additions & 1 deletion src/nodes/lighting/AnalyticLightNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ class AnalyticLightNode extends LightingNode {

}

setupShadowNode() {

return shadow( this.light );

}

setupShadow( builder ) {

const { renderer } = builder;
Expand All @@ -67,7 +73,7 @@ class AnalyticLightNode extends LightingNode {

} else {

shadowNode = shadow( this.light );
shadowNode = this.setupShadowNode( builder );

}

Expand Down
Loading