Conversation
The light's view-space position was computed as `mat4 * vec3`, leaving a stray w component on the result. That w propagated into the light vector, so `directPointLight()`'s length()/normalize() evaluated `sqrt(d² + w²)` instead of `d` — flattening and offsetting the falloff so clustered point lights didn't match the forward renderer. Transform an explicit homogeneous point and drop w: `mul( vec4( position, 1.0 ) ).xyz`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Owner
Author
|
@sunag IBL doesn't work with I asked Claude to add support for it (as seen in the screenshots) but I'm not sure it's the best solution. Would you like to give it a go? Here's the example I'm using: <!DOCTYPE html>
<html lang="en">
<head>
<title>three.js webgpu - clustered lighting + ibl</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<meta property="og:title" content="three.js webgpu - clustered lighting + ibl">
<meta property="og:type" content="website">
<meta property="og:url" content="https://threejs.org/examples/webgpu_lights_clustered_ibl.html">
<meta property="og:image" content="https://threejs.org/examples/screenshots/webgpu_lights_clustered_ibl.jpg">
<link type="text/css" rel="stylesheet" href="example.css">
</head>
<body>
<div id="info">
<a href="https://threejs.org/" target="_blank" rel="noopener" class="logo-link"></a>
<div class="title-wrapper">
<a href="https://threejs.org/" target="_blank" rel="noopener">three.js</a><span>Clustered Lighting + IBL</span>
</div>
<small>
128 point lights (Forward+ clustered) layered on top of a SkyMesh PMREM environment.
</small>
</div>
<script type="importmap">
{
"imports": {
"three": "../build/three.webgpu.js",
"three/webgpu": "../build/three.webgpu.js",
"three/tsl": "../build/three.tsl.js",
"three/addons/": "./jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three/webgpu';
import { ClusteredLighting } from 'three/addons/lighting/ClusteredLighting.js';
import { SkyMesh } from 'three/addons/objects/SkyMesh.js';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
import Stats from 'three/addons/libs/stats.module.js';
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
const LIGHT_COUNT = 128;
let camera, scene, renderer, controls, stats;
let sky, pmremGenerator, envScene, envRenderTarget;
let lightMarkers;
const lights = [];
const lightData = [];
const dummy = new THREE.Object3D();
const params = {
count: 10,
environment: true,
lightPower: 250
};
const LIGHT_DISTANCE = 8; // keep each light localized so clusters don't overflow
init();
function init() {
camera = new THREE.PerspectiveCamera( 55, window.innerWidth / window.innerHeight, 0.1, 5000 );
camera.position.set( 0, 22, 48 );
scene = new THREE.Scene();
// dusk sky used both as the visible background and the IBL source
sky = new SkyMesh();
sky.scale.setScalar( 1000 );
sky.material.fog = false;
sky.turbidity.value = 10;
sky.rayleigh.value = 2;
sky.mieCoefficient.value = 0.005;
sky.mieDirectionalG.value = 0.85;
const phi = THREE.MathUtils.degToRad( 90 - 3 ); // sun just above the horizon
const theta = THREE.MathUtils.degToRad( 180 );
sky.sunPosition.value.setFromSphericalCoords( 1, phi, theta );
scene.add( sky );
// floor
const floor = new THREE.Mesh(
new THREE.PlaneGeometry( 120, 120 ),
new THREE.MeshStandardMaterial( { color: 0x222428, roughness: 0.6, metalness: 0.0 } )
);
floor.rotation.x = - Math.PI / 2;
scene.add( floor );
// a field of objects to catch both the sky IBL and the colored point lights
const fieldMaterial = new THREE.MeshStandardMaterial( { color: 0x999999, roughness: 0.45, metalness: 0.0 } );
const sphereGeometry = new THREE.SphereGeometry( 2, 32, 16 );
for ( let x = - 2; x <= 2; x ++ ) {
for ( let z = - 2; z <= 2; z ++ ) {
const sphere = new THREE.Mesh( sphereGeometry, fieldMaterial );
sphere.position.set( x * 11, 2, z * 11 );
scene.add( sphere );
}
}
const knot = new THREE.Mesh(
new THREE.TorusKnotGeometry( 4, 1.2, 160, 24 ),
new THREE.MeshStandardMaterial( { color: 0xbbbbbb, roughness: 0.3, metalness: 0.1 } )
);
knot.position.set( 0, 8, 0 );
scene.add( knot );
// point lights + emissive markers
const sphereMarker = new THREE.SphereGeometry( 0.18, 8, 8 );
lightMarkers = new THREE.InstancedMesh( sphereMarker, new THREE.MeshBasicMaterial(), LIGHT_COUNT );
lightMarkers.instanceMatrix.setUsage( THREE.DynamicDrawUsage );
scene.add( lightMarkers );
const color = new THREE.Color();
for ( let i = 0; i < LIGHT_COUNT; i ++ ) {
color.setHSL( Math.random(), 1.0, 0.5 );
const light = new THREE.PointLight( color.getHex(), 1, LIGHT_DISTANCE, 2 );
light.power = params.lightPower;
scene.add( light );
lights.push( light );
lightData.push( {
radius: 6 + Math.random() * 28,
theta: Math.random() * Math.PI * 2,
speed: ( 0.1 + Math.random() * 0.3 ) * ( Math.random() < 0.5 ? - 1 : 1 ),
yBase: 0.8 + Math.random() * 1.0, // near the floor
yAmp: 0.15 + Math.random() * 0.35,
yFreq: 0.3 + Math.random() * 0.8,
phase: Math.random() * Math.PI * 2
} );
lightMarkers.setColorAt( i, color );
}
lightMarkers.instanceColor.needsUpdate = true;
// renderer + clustered lighting
renderer = new THREE.WebGPURenderer( { antialias: true } );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( window.innerWidth, window.innerHeight );
renderer.setAnimationLoop( animate );
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 0.5;
renderer.lighting = new ClusteredLighting();
document.body.appendChild( renderer.domElement );
// bake the sky into a PMREM environment for image-based lighting
renderer.init().then( () => {
pmremGenerator = new THREE.PMREMGenerator( renderer );
envScene = new THREE.Scene();
scene.remove( sky );
envScene.add( sky );
envRenderTarget = pmremGenerator.fromScene( envScene );
scene.add( sky );
scene.environment = envRenderTarget.texture;
} );
controls = new OrbitControls( camera, renderer.domElement );
controls.target.set( 0, 8, 0 );
controls.maxDistance = 120;
controls.update();
stats = new Stats();
// document.body.appendChild( stats.dom );
// gui
const gui = new GUI();
gui.add( params, 'count', 0, LIGHT_COUNT, 1 ).name( 'light count' );
gui.add( params, 'lightPower', 0, 1500 ).name( 'light power' ).onChange( ( value ) => {
for ( const light of lights ) light.power = value;
} );
gui.add( params, 'environment' ).name( 'environment (IBL)' ).onChange( ( value ) => {
scene.environment = value && envRenderTarget ? envRenderTarget.texture : null;
} );
window.addEventListener( 'resize', onWindowResize );
}
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize( window.innerWidth, window.innerHeight );
}
function animate() {
const t = performance.now() * 0.001;
for ( let i = 0; i < LIGHT_COUNT; i ++ ) {
const d = lightData[ i ];
const angle = d.theta + t * d.speed;
const x = Math.cos( angle ) * d.radius;
const z = Math.sin( angle ) * d.radius;
const y = d.yBase + Math.sin( t * d.yFreq + d.phase ) * d.yAmp;
const visible = i < params.count;
lights[ i ].position.set( x, y, z );
lights[ i ].visible = visible;
dummy.position.set( x, y, z );
dummy.scale.setScalar( visible ? 1 : 0 );
dummy.updateMatrix();
lightMarkers.setMatrixAt( i, dummy.matrix );
}
lightMarkers.instanceMatrix.needsUpdate = true;
controls.update();
renderer.render( scene, camera );
stats.update();
}
</script>
</body>
</html> |
Collaborator
I'm checking this, I'll have to refactor some components. |
Collaborator
|
I think it's better to continue on another PR. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Related issue: #33406
Description