|
|
@@ -38,7 +38,7 @@
|
|
|
<script type="module">
|
|
|
|
|
|
import * as THREE from 'three/webgpu';
|
|
|
- import { pass, uniform, vec3, float, mix, step } from 'three/tsl';
|
|
|
+ import { pass, uniform, vec3, float, mix, step, uv, instancedBufferAttribute } from 'three/tsl';
|
|
|
|
|
|
import { ClusteredLighting } from 'three/addons/lighting/ClusteredLighting.js';
|
|
|
|
|
|
@@ -54,6 +54,7 @@
|
|
|
|
|
|
let camera, scene, renderer,
|
|
|
lights, lightDummy,
|
|
|
+ lightPositionAttribute,
|
|
|
controls,
|
|
|
scenePass, clusterInfluence, debugZSliceNode,
|
|
|
lighting,
|
|
|
@@ -102,12 +103,37 @@
|
|
|
const modelSize = box.getSize( new THREE.Vector3() );
|
|
|
const modelCenter = box.getCenter( new THREE.Vector3() );
|
|
|
|
|
|
- const material = new THREE.MeshBasicMaterial();
|
|
|
+ const lightPositions = new Float32Array( maxCount * 3 );
|
|
|
+ const lightColors = new Float32Array( maxCount * 3 );
|
|
|
|
|
|
- lightDummy = new THREE.InstancedMesh( new THREE.SphereGeometry( 0.05, 16, 8 ), material, maxCount );
|
|
|
- lightDummy.instanceMatrix.setUsage( THREE.DynamicDrawUsage );
|
|
|
- lightDummy.frustumCulled = false;
|
|
|
+ lightPositionAttribute = new THREE.InstancedBufferAttribute( lightPositions, 3 );
|
|
|
+ lightPositionAttribute.setUsage( THREE.DynamicDrawUsage );
|
|
|
+
|
|
|
+ const lightColorAttribute = new THREE.InstancedBufferAttribute( lightColors, 3 );
|
|
|
+
|
|
|
+ // Physical 1/r² point-source falloff (Plummer profile): I = peak * r0² / ( r² + r0² )
|
|
|
+ // Subtract the value at r = 0.5 so the soft tail reaches exactly 0 at the sprite edge.
|
|
|
+ const coreRadius = 0.02;
|
|
|
+ const peak = 16;
|
|
|
+ const r0sq = coreRadius * coreRadius;
|
|
|
+ const edgeBias = r0sq / ( 0.25 + r0sq );
|
|
|
+
|
|
|
+ const offset = uv().sub( 0.5 );
|
|
|
+ const r2 = offset.dot( offset );
|
|
|
+ const glow = float( r0sq ).div( r2.add( r0sq ) ).sub( edgeBias ).max( 0 ).mul( peak );
|
|
|
+
|
|
|
+ const spriteMaterial = new THREE.SpriteNodeMaterial( {
|
|
|
+ transparent: true,
|
|
|
+ depthWrite: false,
|
|
|
+ blending: THREE.AdditiveBlending
|
|
|
+ } );
|
|
|
+ spriteMaterial.positionNode = instancedBufferAttribute( lightPositionAttribute );
|
|
|
+ spriteMaterial.colorNode = glow.mul( instancedBufferAttribute( lightColorAttribute ) );
|
|
|
+ spriteMaterial.scaleNode = uniform( 0.2 );
|
|
|
+
|
|
|
+ lightDummy = new THREE.Sprite( spriteMaterial );
|
|
|
lightDummy.count = count;
|
|
|
+ lightDummy.frustumCulled = false;
|
|
|
scene.add( lightDummy );
|
|
|
|
|
|
// lights
|
|
|
@@ -115,7 +141,7 @@
|
|
|
lights = new THREE.Group();
|
|
|
scene.add( lights );
|
|
|
|
|
|
- const addLight = ( hexColor, power = 30, distance = 1 ) => {
|
|
|
+ const addLight = ( hexColor, power = 10, distance = 1 ) => {
|
|
|
|
|
|
const light = new THREE.PointLight( hexColor, 1, distance );
|
|
|
light.position.set(
|
|
|
@@ -128,9 +154,10 @@
|
|
|
light.userData.fixedPosition = light.position.clone();
|
|
|
light.visible = ( lights.children.length < count );
|
|
|
|
|
|
- light.updateMatrixWorld();
|
|
|
-
|
|
|
- lightDummy.setMatrixAt( lights.children.length, light.matrixWorld );
|
|
|
+ const i = lights.children.length;
|
|
|
+ lightPositions[ i * 3 + 0 ] = light.position.x;
|
|
|
+ lightPositions[ i * 3 + 1 ] = light.position.y;
|
|
|
+ lightPositions[ i * 3 + 2 ] = light.position.z;
|
|
|
|
|
|
lights.add( light );
|
|
|
|
|
|
@@ -144,7 +171,10 @@
|
|
|
|
|
|
const hex = ( Math.random() * 0x888888 ) + 0x888888;
|
|
|
|
|
|
- lightDummy.setColorAt( i, color.setHex( hex ) );
|
|
|
+ color.setHex( hex );
|
|
|
+ lightColors[ i * 3 + 0 ] = color.r;
|
|
|
+ lightColors[ i * 3 + 1 ] = color.g;
|
|
|
+ lightColors[ i * 3 + 2 ] = color.b;
|
|
|
|
|
|
addLight( hex );
|
|
|
|
|
|
@@ -279,6 +309,7 @@
|
|
|
function animate() {
|
|
|
|
|
|
const time = performance.now() / 1000;
|
|
|
+ const positions = lightPositionAttribute.array;
|
|
|
|
|
|
for ( let i = 0; i < lights.children.length; i ++ ) {
|
|
|
|
|
|
@@ -290,10 +321,14 @@
|
|
|
light.position.y += Math.cos( lightTime * 0.5 ) * .3;
|
|
|
light.position.z += Math.cos( lightTime * 0.3 ) * 1.5;
|
|
|
|
|
|
- lightDummy.setMatrixAt( i, light.matrixWorld );
|
|
|
+ positions[ i * 3 + 0 ] = light.position.x;
|
|
|
+ positions[ i * 3 + 1 ] = light.position.y;
|
|
|
+ positions[ i * 3 + 2 ] = light.position.z;
|
|
|
|
|
|
}
|
|
|
|
|
|
+ lightPositionAttribute.needsUpdate = true;
|
|
|
+
|
|
|
renderPipeline.render();
|
|
|
|
|
|
}
|