Quellcode durchsuchen

WebGPURenderer: Add `SpotLight.map` support (#29989)

* SpotLight: Add `.map` support

* cleanup
sunag vor 1 Jahr
Ursprung
Commit
e2e04d3e93

+ 1 - 0
examples/files.json

@@ -338,6 +338,7 @@
 		"webgpu_lights_physical",
 		"webgpu_lights_rectarealight",
 		"webgpu_lights_selective",
+		"webgpu_lights_spotlight",
 		"webgpu_lights_tiled",
 		"webgpu_lines_fat_wireframe",
 		"webgpu_lines_fat",

BIN
examples/screenshots/webgpu_lights_spotlight.jpg


+ 252 - 0
examples/webgpu_lights_spotlight.html

@@ -0,0 +1,252 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - spotlight</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 - spotlight<br />
+		</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';
+
+			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+			import { PLYLoader } from 'three/addons/loaders/PLYLoader.js';
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+			let renderer, scene, camera;
+
+			let spotLight, lightHelper;
+
+			init();
+
+			function init() {
+
+				renderer = new THREE.WebGPURenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				document.body.appendChild( renderer.domElement );
+
+				renderer.shadowMap.enabled = true;
+				renderer.shadowMap.type = THREE.PCFSoftShadowMap;
+
+				renderer.toneMapping = THREE.ACESFilmicToneMapping;
+				renderer.toneMappingExposure = 1;
+
+				scene = new THREE.Scene();
+
+				camera = new THREE.PerspectiveCamera( 40, window.innerWidth / window.innerHeight, 0.1, 100 );
+				camera.position.set( 7, 4, 1 );
+
+				const controls = new OrbitControls( camera, renderer.domElement );
+				controls.minDistance = 2;
+				controls.maxDistance = 10;
+				controls.maxPolarAngle = Math.PI / 2;
+				controls.target.set( 0, 1, 0 );
+				controls.update();
+
+				const ambient = new THREE.HemisphereLight( 0xffffff, 0x8d8d8d, 0.15 );
+				scene.add( ambient );
+
+				const loader = new THREE.TextureLoader().setPath( 'textures/' );
+				const filenames = [ 'disturb.jpg', 'colors.png', 'uv_grid_opengl.jpg' ];
+
+				const textures = { none: null };
+
+				for ( let i = 0; i < filenames.length; i ++ ) {
+
+					const filename = filenames[ i ];
+
+					const texture = loader.load( filename );
+					texture.minFilter = THREE.LinearFilter;
+					texture.magFilter = THREE.LinearFilter;
+					texture.generateMipmaps = false;
+					texture.colorSpace = THREE.SRGBColorSpace;
+
+					textures[ filename ] = texture;
+
+				}
+
+				spotLight = new THREE.SpotLight( 0xffffff, 100 );
+				spotLight.position.set( 2.5, 5, 2.5 );
+				spotLight.angle = Math.PI / 6;
+				spotLight.penumbra = 1;
+				spotLight.decay = 2;
+				spotLight.distance = 0;
+				spotLight.map = textures[ 'disturb.jpg' ];
+
+				spotLight.castShadow = true;
+				spotLight.shadow.mapSize.width = 1024;
+				spotLight.shadow.mapSize.height = 1024;
+				spotLight.shadow.camera.near = 1;
+				spotLight.shadow.camera.far = 10;
+				spotLight.shadow.focus = 1;
+				spotLight.shadow.bias = - .003;
+				scene.add( spotLight );
+
+				lightHelper = new THREE.SpotLightHelper( spotLight );
+				scene.add( lightHelper );
+
+				//
+
+				const geometry = new THREE.PlaneGeometry( 200, 200 );
+				const material = new THREE.MeshLambertMaterial( { color: 0xbcbcbc } );
+
+				const mesh = new THREE.Mesh( geometry, material );
+				mesh.position.set( 0, - 1, 0 );
+				mesh.rotation.x = - Math.PI / 2;
+				mesh.receiveShadow = true;
+				scene.add( mesh );
+
+				//
+
+				new PLYLoader().load( 'models/ply/binary/Lucy100k.ply', function ( geometry ) {
+
+					geometry.scale( 0.0024, 0.0024, 0.0024 );
+					geometry.computeVertexNormals();
+
+					const material = new THREE.MeshLambertMaterial();
+
+					const mesh = new THREE.Mesh( geometry, material );
+					mesh.rotation.y = - Math.PI / 2;
+					mesh.position.y = 0.8;
+					mesh.castShadow = true;
+					mesh.receiveShadow = true;
+					scene.add( mesh );
+
+				} );
+
+				window.addEventListener( 'resize', onWindowResize );
+
+				// GUI
+
+				const gui = new GUI();
+
+				const params = {
+					map: textures[ 'disturb.jpg' ],
+					color: spotLight.color.getHex(),
+					intensity: spotLight.intensity,
+					distance: spotLight.distance,
+					angle: spotLight.angle,
+					penumbra: spotLight.penumbra,
+					decay: spotLight.decay,
+					focus: spotLight.shadow.focus,
+					shadows: true
+				};
+
+				gui.add( params, 'map', textures ).onChange( function ( val ) {
+
+					spotLight.map = val;
+
+				} );
+
+				gui.addColor( params, 'color' ).onChange( function ( val ) {
+
+					spotLight.color.setHex( val );
+
+				} );
+
+				gui.add( params, 'intensity', 0, 500 ).onChange( function ( val ) {
+
+					spotLight.intensity = val;
+
+				} );
+
+
+				gui.add( params, 'distance', 0, 20 ).onChange( function ( val ) {
+
+					spotLight.distance = val;
+
+				} );
+
+				gui.add( params, 'angle', 0, Math.PI / 3 ).onChange( function ( val ) {
+
+					spotLight.angle = val;
+
+				} );
+
+				gui.add( params, 'penumbra', 0, 1 ).onChange( function ( val ) {
+
+					spotLight.penumbra = val;
+
+				} );
+
+				gui.add( params, 'decay', 1, 2 ).onChange( function ( val ) {
+
+					spotLight.decay = val;
+
+				} );
+
+				gui.add( params, 'focus', 0, 1 ).onChange( function ( val ) {
+
+					spotLight.shadow.focus = val;
+
+				} );
+
+
+				gui.add( params, 'shadows' ).onChange( function ( val ) {
+
+					renderer.shadowMap.enabled = val;
+
+					scene.traverse( function ( child ) {
+
+						if ( child.material ) {
+
+							child.material.needsUpdate = true;
+
+						}
+
+					} );
+
+				} );
+
+				gui.open();
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				const time = performance.now() / 3000;
+
+				spotLight.position.x = Math.cos( time ) * 2.5;
+				spotLight.position.z = Math.sin( time ) * 2.5;
+
+				lightHelper.update();
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+
+</html>

+ 32 - 0
src/nodes/accessors/Lights.js

@@ -2,6 +2,7 @@ import { uniform } from '../core/UniformNode.js';
 import { renderGroup } from '../core/UniformGroupNode.js';
 import { Vector3 } from '../../math/Vector3.js';
 import { cameraViewMatrix } from './Camera.js';
+import { positionWorld } from './Position.js';
 
 let uniformsLib;
 
@@ -17,6 +18,37 @@ function getLightData( light ) {
 
 }
 
+export function lightShadowMatrix( light ) {
+
+	const data = getLightData( light );
+
+	return data.shadowMatrix || ( data.shadowMatrix = uniform( 'mat4' ).setGroup( renderGroup ).onRenderUpdate( () => {
+
+		light.shadow.updateMatrices( light );
+
+		return light.shadow.matrix;
+
+	} ) );
+
+}
+
+export function lightProjectionUV( light ) {
+
+	const data = getLightData( light );
+
+	if ( data.projectionUV === undefined ) {
+
+		const spotLightCoord = lightShadowMatrix( light ).mul( positionWorld );
+
+		data.projectionUV = spotLightCoord.xyz.div( spotLightCoord.w );
+
+
+	}
+
+	return data.projectionUV;
+
+}
+
 export function lightPosition( light ) {
 
 	const data = getLightData( light );

+ 3 - 3
src/nodes/lighting/AnalyticLightNode.js

@@ -19,12 +19,10 @@ class AnalyticLightNode extends LightingNode {
 
 		super();
 
-		this.updateType = NodeUpdateType.FRAME;
-
 		this.light = light;
 
 		this.color = new Color();
-		this.colorNode = uniform( this.color ).setGroup( renderGroup );
+		this.colorNode = ( light && light.colorNode ) || uniform( this.color ).setGroup( renderGroup );
 
 		this.baseColorNode = null;
 
@@ -33,6 +31,8 @@ class AnalyticLightNode extends LightingNode {
 
 		this.isAnalyticLightNode = true;
 
+		this.updateType = NodeUpdateType.FRAME;
+
 	}
 
 	getCacheKey() {

+ 3 - 5
src/nodes/lighting/ShadowNode.js

@@ -1,6 +1,5 @@
 import Node from '../core/Node.js';
 import { NodeUpdateType } from '../core/constants.js';
-import { uniform } from '../core/UniformNode.js';
 import { float, vec2, vec3, vec4, If, int, Fn, nodeObject } from '../tsl/TSLBase.js';
 import { reference } from '../accessors/ReferenceNode.js';
 import { texture } from '../accessors/TextureNode.js';
@@ -17,6 +16,7 @@ import { HalfFloatType, LessCompare, RGFormat, VSMShadowMap, WebGPUCoordinateSys
 import { renderGroup } from '../core/UniformGroupNode.js';
 import { viewZToLogarithmicDepth } from '../display/ViewportDepthNode.js';
 import { objectPosition } from '../accessors/Object3DNode.js';
+import { lightShadowMatrix } from '../accessors/Lights.js';
 
 const shadowWorldPosition = /*@__PURE__*/ vec3().toVar( 'shadowWorldPosition' );
 
@@ -383,7 +383,7 @@ class ShadowNode extends Node {
 		const shadowIntensity = reference( 'intensity', 'float', shadow ).setGroup( renderGroup );
 		const normalBias = reference( 'normalBias', 'float', shadow ).setGroup( renderGroup );
 
-		const shadowPosition = uniform( shadow.matrix ).setGroup( renderGroup ).mul( shadowWorldPosition.add( transformedNormalWorld.mul( normalBias ) ) );
+		const shadowPosition = lightShadowMatrix( light ).mul( shadowWorldPosition.add( transformedNormalWorld.mul( normalBias ) ) );
 		const shadowCoord = this.setupShadowCoord( builder, shadowPosition );
 
 		//
@@ -446,11 +446,9 @@ class ShadowNode extends Node {
 
 	renderShadow( frame ) {
 
-		const { shadow, shadowMap, light } = this;
+		const { shadow, shadowMap } = this;
 		const { renderer, scene } = frame;
 
-		shadow.updateMatrices( light );
-
 		shadowMap.setSize( shadow.mapSize.width, shadow.mapSize.height );
 
 		renderer.render( scene, shadow.camera );

+ 14 - 2
src/nodes/lighting/SpotLightNode.js

@@ -4,7 +4,8 @@ import { uniform } from '../core/UniformNode.js';
 import { smoothstep } from '../math/MathNode.js';
 import { positionView } from '../accessors/Position.js';
 import { renderGroup } from '../core/UniformGroupNode.js';
-import { lightViewPosition, lightTargetDirection } from '../accessors/Lights.js';
+import { lightViewPosition, lightTargetDirection, lightProjectionUV } from '../accessors/Lights.js';
+import { texture } from '../accessors/TextureNode.js';
 
 class SpotLightNode extends AnalyticLightNode {
 
@@ -70,7 +71,18 @@ class SpotLightNode extends AnalyticLightNode {
 			decayExponent: decayExponentNode
 		} );
 
-		const lightColor = colorNode.mul( spotAttenuation ).mul( lightAttenuation );
+		let lightColor = colorNode.mul( spotAttenuation ).mul( lightAttenuation );
+
+		if ( light.map ) {
+
+			const spotLightCoord = lightProjectionUV( light );
+			const projectedTexture = texture( light.map, spotLightCoord.xy ).onRenderUpdate( () => light.map );
+
+			const inSpotLightMap = spotLightCoord.mul( 2. ).sub( 1. ).abs().lessThan( 1. ).all();
+
+			lightColor = inSpotLightMap.select( lightColor.mul( projectedTexture ), lightColor );
+
+		}
 
 		const reflectedLight = builder.context.reflectedLight;
 

粤ICP备19079148号