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.
329 changes: 329 additions & 0 deletions examples/webgpu_lights_physical.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,329 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgpu - lights - 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": "../src/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 postProcessing;

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 boxMesh4 = new THREE.Mesh( boxGeometry, cubeMat );
boxMesh4.position.set( 0, 0.25, 0 );
boxMesh4.castShadow = true;
//scene.add( boxMesh4 );

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

const walls = new THREE.Mesh( new THREE.BoxGeometry( 20, 20, 20 ), floorMat );
walls.position.set( 0, 9, 0 );
walls.castShadow = true;
//walls.material.side = THREE.DoubleSide;
walls.geometry.scale( - 1, 1, 1 );
scene.add( walls );

//

renderer = new THREE.WebGPURenderer( { forceWebGL: false } );
console.log( renderer.backend );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( animate );
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.BasicShadowMap;
renderer.toneMapping = THREE.ReinhardToneMapping;
container.appendChild( renderer.domElement );


postProcessing = new THREE.PostProcessing( renderer );



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;
//bulbLight.position.y = .5;
//bulbLight.position.y = 1.5;
//bulbLight.position.y = 1.5;

renderer.render( scene, camera );

/*if ( ! postProcessing.test ) {

const scenePass = THREE.pass( scene, camera );

postProcessing.outputNode = scenePass.add( THREE.texture( bulbLight.shadow.map.texture ) );
postProcessing.test = true;

}*/

//postProcessing.render();

stats.update();

}

</script>
</body>
</html>
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
11 changes: 10 additions & 1 deletion src/nodes/lighting/PointLightNode.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { lightViewPosition } from '../accessors/Lights.js';
import { positionView } from '../accessors/Position.js';
import { Fn } from '../tsl/TSLBase.js';
import { renderGroup } from '../core/UniformGroupNode.js';
import { pointShadow } from './PointShadowNode.js';

export const directPointLight = Fn( ( { color, lightViewPosition, cutoffDistance, decayExponent }, builder ) => {

Expand Down Expand Up @@ -61,7 +62,15 @@ class PointLightNode extends AnalyticLightNode {

}

setup() {
setupShadowNode() {

return pointShadow( this.light );

}

setup( builder ) {

super.setup( builder );

directPointLight( {
color: this.colorNode,
Expand Down
Loading
Loading