Răsfoiți Sursa

Examples: Add `webgpu_caustics` and shadow revisions (#30962)

* `material.shadowPositionNode` renamed to `material.receivedShadowPositionNode`

* add `shadow.mapType`

* add `material.castShadowPositionNode`

* add `webgpu_caustics` example

* update title

* add stats

* use webgpu

* add `webgpu_volumetric_caustics` example

* updates

* Update puppeteer.js

* Update puppeteer.js

* Update webgpu_volume_caustics.html

* fix shadow-map alpha

* add glass example

* Update webgpu_shadowmap_opacity.jpg

* Update webgpu_volume_caustics.html
sunag 1 an în urmă
părinte
comite
51f4b78bca

+ 2 - 0
examples/files.json

@@ -307,6 +307,7 @@
 		"webgpu_camera",
 		"webgpu_camera",
 		"webgpu_camera_array",
 		"webgpu_camera_array",
 		"webgpu_camera_logarithmicdepthbuffer",
 		"webgpu_camera_logarithmicdepthbuffer",
+		"webgpu_caustics",
 		"webgpu_centroid_sampling",
 		"webgpu_centroid_sampling",
 		"webgpu_clearcoat",
 		"webgpu_clearcoat",
 		"webgpu_clipping",
 		"webgpu_clipping",
@@ -453,6 +454,7 @@
 		"webgpu_tsl_vfx_tornado",
 		"webgpu_tsl_vfx_tornado",
 		"webgpu_video_frame",
 		"webgpu_video_frame",
 		"webgpu_video_panorama",
 		"webgpu_video_panorama",
+		"webgpu_volume_caustics",
 		"webgpu_volume_cloud",
 		"webgpu_volume_cloud",
 		"webgpu_volume_lighting",
 		"webgpu_volume_lighting",
 		"webgpu_volume_lighting_rectarea",
 		"webgpu_volume_lighting_rectarea",

+ 1 - 1
examples/jsm/objects/WaterMesh.js

@@ -153,7 +153,7 @@ class WaterMesh extends Mesh {
 
 
 		material.opacityNode = this.alpha;
 		material.opacityNode = this.alpha;
 
 
-		material.shadowPositionNode = positionWorld.add( distortion );
+		material.receivedShadowPositionNode = positionWorld.add( distortion );
 
 
 		material.setupOutgoingLight = () => diffuseColor.rgb; // backwards compatibility
 		material.setupOutgoingLight = () => diffuseColor.rgb; // backwards compatibility
 
 

BIN
examples/screenshots/webgpu_caustics.jpg


BIN
examples/screenshots/webgpu_shadowmap_opacity.jpg


BIN
examples/screenshots/webgpu_volume_caustics.jpg


BIN
examples/textures/opengameart/Caustic_Free.jpg


+ 247 - 0
examples/webgpu_caustics.html

@@ -0,0 +1,247 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - caustics</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 webgpu</a> - realtime caustics
+		</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 { uniform, refract, div, positionViewDirection, positionLocal, normalView, texture, Fn, vec2, vec3, vec4 } from 'three/tsl';
+
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+			import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+
+			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+			import Stats from 'three/addons/libs/stats.module.js';
+
+			let camera, scene, renderer, controls;
+			let stats;
+			let gltf;
+
+			init();
+
+			async function init() {
+
+				camera = new THREE.PerspectiveCamera( 25, window.innerWidth / window.innerHeight, 0.025, 5 );
+				camera.position.set( - 0.5, 0.35, 0.2 );
+
+				scene = new THREE.Scene();
+
+				// light
+
+				const spotLight = new THREE.SpotLight( 0xffffff, 1 );
+				spotLight.position.set( .2, .3, .2 );
+				spotLight.castShadow = true;
+				spotLight.angle = Math.PI / 6;
+				spotLight.penumbra = 1;
+				spotLight.decay = 2;
+				spotLight.distance = 0;
+				spotLight.shadow.mapType = THREE.HalfFloatType; // For HDR Caustics
+				spotLight.shadow.mapSize.width = 1024;
+				spotLight.shadow.mapSize.height = 1024;
+				spotLight.shadow.camera.near = .1;
+				spotLight.shadow.camera.far = 1;
+				spotLight.shadow.bias = - .003;
+				spotLight.shadow.intensity = .95;
+				scene.add( spotLight );
+
+				// model / textures
+
+				const dracoLoader = new DRACOLoader();
+				dracoLoader.setDecoderPath( 'jsm/libs/draco/' );
+				dracoLoader.setDecoderConfig( { type: 'js' } );
+
+				gltf = ( await new GLTFLoader().setDRACOLoader( dracoLoader ).loadAsync( './models/gltf/duck.glb' ) ).scene;
+				gltf.scale.setScalar( .5 );
+				scene.add( gltf );
+
+				const causticMap = new THREE.TextureLoader().load( './textures/opengameart/Caustic_Free.jpg' );
+				causticMap.wrapS = causticMap.wrapT = THREE.RepeatWrapping;
+				causticMap.colorSpace = THREE.SRGBColorSpace;
+
+				// objects / material
+
+				const duck = gltf.children[ 0 ];
+				duck.material = new THREE.MeshPhysicalNodeMaterial();
+				duck.material.side = THREE.DoubleSide;
+				duck.material.transparent = true;
+				duck.material.color = new THREE.Color( 0xFFD700 );
+				duck.material.transmission = 1;
+				duck.material.thickness = .25;
+				duck.material.ior = 1.5;
+				duck.material.metalness = 0;
+				duck.material.roughness = .1;
+				duck.castShadow = true;
+
+				// tsl shader
+
+				const causticOcclusion = uniform( 20 );
+
+				duck.material.castShadowPositionNode = Fn( () => {
+
+					// optional: add some distortion to the geometry shadow position if needed
+
+					return positionLocal;
+
+				} )();
+
+				duck.material.castShadowNode = Fn( () => {
+
+					const refractionVector = refract( positionViewDirection.negate(), normalView, div( 1.0, duck.material.ior ) ).normalize();
+					const viewZ = normalView.z.pow( causticOcclusion );
+
+					const textureUV = refractionVector.xy.mul( .6 );
+
+					const causticColor = uniform( duck.material.color );
+					const chromaticAberrationOffset = normalView.z.pow( - .9 ).mul( .004 );
+
+					const causticProjection = vec3(
+						texture( causticMap, textureUV.add( vec2( chromaticAberrationOffset.x.negate(), 0 ) ) ).r,
+						texture( causticMap, textureUV.add( vec2( 0, chromaticAberrationOffset.y.negate() ) ) ).g,
+						texture( causticMap, textureUV.add( vec2( chromaticAberrationOffset.x, chromaticAberrationOffset.y ) ) ).b
+					);
+
+					return causticProjection.mul( viewZ.mul( 25 ) ).add( viewZ ).mul( causticColor );
+
+				} )();
+
+				//
+
+				const textureLoader = new THREE.TextureLoader();
+
+				// glass
+
+				const colorMap = textureLoader.load( 'textures/colors.png' );
+				colorMap.wrapS = colorMap.wrapT = THREE.RepeatWrapping;
+				colorMap.colorSpace = THREE.SRGBColorSpace;
+
+				const glassMaterial = new THREE.MeshPhysicalNodeMaterial();
+				glassMaterial.map = colorMap;
+				glassMaterial.side = THREE.DoubleSide;
+				glassMaterial.transparent = true;
+				glassMaterial.color = new THREE.Color( 0xffffff );
+				glassMaterial.transmission = 1;
+				glassMaterial.ior = 1.5;
+				glassMaterial.metalness = 0;
+				glassMaterial.roughness = .1;
+				glassMaterial.castShadowNode = vec4( texture( colorMap ).rgb, .8 );
+
+				const glass = new THREE.Mesh( new THREE.PlaneGeometry( .2, .2 ), glassMaterial );
+				glass.position.y = .1;
+				glass.castShadow = true;
+				glass.visible = false;
+				scene.add( glass );
+
+				// gui
+
+				const gui = new GUI();
+				gui.add( causticOcclusion, 'value', 0, 20 ).name( 'caustic occlusion' );
+				gui.addColor( duck.material, 'color' ).name( 'material color' );
+				gui.add( { model: 'duck' }, 'model', [
+					'duck',
+					'glass'
+				] ).onChange( model => {
+
+					duck.visible = glass.visible = false;
+
+					if ( model === 'duck' ) {
+
+						duck.visible = true;
+
+					} else if ( model === 'glass' ) {
+
+						glass.visible = true;
+
+					}
+
+				} );
+
+				// ground
+
+				const map = textureLoader.load( 'textures/hardwood2_diffuse.jpg' );
+				map.wrapS = map.wrapT = THREE.RepeatWrapping;
+				map.repeat.set( 10, 10 );
+
+				const geometry = new THREE.PlaneGeometry( 2, 2 );
+				const material = new THREE.MeshStandardMaterial( { color: 0x999999, map } );
+
+				const ground = new THREE.Mesh( geometry, material );
+				ground.rotation.x = - Math.PI / 2;
+				ground.receiveShadow = true;
+				scene.add( ground );
+
+				// renderer
+
+				renderer = new THREE.WebGPURenderer( { antialias: true } );
+				renderer.shadowMap.enabled = true;
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				document.body.appendChild( renderer.domElement );
+
+				// stats
+
+				stats = new Stats();
+				document.body.appendChild( stats.dom );
+
+				// controls
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.maxDistance = 3;
+				controls.maxPolarAngle = Math.PI / 2;
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				stats.update();
+
+				for ( const mesh of gltf.children ) {
+
+					mesh.rotation.y -= .01;
+
+				}
+
+				controls.update();
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>

+ 6 - 7
examples/webgpu_shadowmap.html

@@ -139,13 +139,13 @@
 				scene.add( pillar4 );
 				scene.add( pillar4 );
 
 
 				const planeGeometry = new THREE.PlaneGeometry( 200, 200 );
 				const planeGeometry = new THREE.PlaneGeometry( 200, 200 );
-				const planeMaterial = new THREE.MeshPhongMaterial( {
-					color: 0x999999,
-					shininess: 0,
-					specular: 0x111111
-				} );
 
 
-				planeMaterial.shadowPositionNode = Fn( () => {
+				const planeMaterial = new THREE.MeshPhongNodeMaterial();
+				planeMaterial.color.setHex( 0x999999 );
+				planeMaterial.shininess = 0;
+				planeMaterial.specular.setHex( 0x111111 );
+
+				planeMaterial.receivedShadowPositionNode = Fn( () => {
 
 
 					const pos = positionWorld.toVar();
 					const pos = positionWorld.toVar();
 					pos.xz.addAssign( mx_fractal_noise_vec3( positionWorld.mul( 2 ) ).saturate().xz );
 					pos.xz.addAssign( mx_fractal_noise_vec3( positionWorld.mul( 2 ) ).saturate().xz );
@@ -153,7 +153,6 @@
 
 
 				} )();
 				} )();
 
 
-
 				planeMaterial.colorNode = Fn( () => {
 				planeMaterial.colorNode = Fn( () => {
 
 
 					const pos = positionWorld.toVar();
 					const pos = positionWorld.toVar();

+ 333 - 0
examples/webgpu_volume_caustics.html

@@ -0,0 +1,333 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - volumetric caustics</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 webgpu</a> - volumetric caustics
+		</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 { uniform, refract, div, frameId, lightViewPosition, float, positionView, positionViewDirection, screenUV, pass, texture3D, time, screenCoordinate, normalView, texture, Fn, vec2, vec3 } from 'three/tsl';
+
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
+			import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+			import { ImprovedNoise } from 'three/addons/math/ImprovedNoise.js';
+
+			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+			import { bayer16 } from 'three/addons/tsl/math/Bayer.js';
+			import { bloom } from 'three/addons/tsl/display/BloomNode.js';
+
+			import Stats from 'three/addons/libs/stats.module.js';
+
+			let camera, scene, renderer, controls;
+			let postProcessing;
+			let stats;
+			let gltf;
+
+			init();
+
+			async function init() {
+
+				const LAYER_VOLUMETRIC_LIGHTING = 10;
+
+				camera = new THREE.PerspectiveCamera( 25, window.innerWidth / window.innerHeight, 0.025, 5 );
+				camera.position.set( - 0.7, 0.2, 0.2 );
+
+				scene = new THREE.Scene();
+
+				// Light
+
+				const spotLight = new THREE.SpotLight( 0xffffff, 1 );
+				spotLight.position.set( .2, .3, .2 );
+				spotLight.castShadow = true;
+				spotLight.angle = Math.PI / 6;
+				spotLight.penumbra = 1;
+				spotLight.decay = 2;
+				spotLight.distance = 0;
+				spotLight.shadow.mapType = THREE.HalfFloatType; // For HDR Caustics
+				spotLight.shadow.mapSize.width = 1024;
+				spotLight.shadow.mapSize.height = 1024;
+				spotLight.shadow.camera.near = .1;
+				spotLight.shadow.camera.far = 1;
+				spotLight.shadow.bias = - .003;
+				spotLight.shadow.intensity = .95;
+				spotLight.layers.enable( LAYER_VOLUMETRIC_LIGHTING );
+				scene.add( spotLight );
+
+				// Model / Textures
+
+				const dracoLoader = new DRACOLoader();
+				dracoLoader.setDecoderPath( 'jsm/libs/draco/' );
+				dracoLoader.setDecoderConfig( { type: 'js' } );
+
+				gltf = ( await new GLTFLoader().setDRACOLoader( dracoLoader ).loadAsync( './models/gltf/duck.glb' ) ).scene;
+				gltf.scale.setScalar( .5 );
+				scene.add( gltf );
+
+				const causticMap = new THREE.TextureLoader().load( './textures/opengameart/Caustic_Free.jpg' );
+				causticMap.wrapS = causticMap.wrapT = THREE.RepeatWrapping;
+				causticMap.colorSpace = THREE.SRGBColorSpace;
+
+				// Material
+
+				const duck = gltf.children[ 0 ];
+				duck.material = new THREE.MeshPhysicalNodeMaterial();
+				duck.material.side = THREE.DoubleSide;
+				duck.material.transparent = true;
+				duck.material.color = new THREE.Color( 0xFFD700 );
+				duck.material.transmission = 1;
+				duck.material.thickness = .25;
+				duck.material.ior = 1.5;
+				duck.material.metalness = 0;
+				duck.material.roughness = .1;
+				duck.castShadow = true;
+
+				// TSL Shader
+
+				const causticOcclusion = uniform( 1 );
+
+				const causticEffect = Fn( () => {
+
+					const refractionVector = refract( positionViewDirection.negate(), normalView, div( 1.0, duck.material.ior ) ).normalize();
+					const viewZ = normalView.z.pow( causticOcclusion );
+
+					const textureUV = refractionVector.xy.mul( .6 );
+
+					const causticColor = uniform( duck.material.color );
+					const chromaticAberrationOffset = normalView.z.pow( - .9 ).mul( .004 );
+
+					const causticProjection = vec3(
+						texture( causticMap, textureUV.add( vec2( chromaticAberrationOffset.x.negate(), 0 ) ) ).r,
+						texture( causticMap, textureUV.add( vec2( 0, chromaticAberrationOffset.y.negate() ) ) ).g,
+						texture( causticMap, textureUV.add( vec2( chromaticAberrationOffset.x, chromaticAberrationOffset.y ) ) ).b
+					);
+
+					return causticProjection.mul( viewZ.mul( 60 ) ).add( viewZ ).mul( causticColor );
+
+				} )().toVar();
+
+				duck.material.castShadowNode = causticEffect;
+
+				duck.material.emissiveNode = Fn( () => {
+
+					// Custom emissive for illuminating backside of the mesh based on the caustic effect and light direction
+
+					const thicknessPowerNode = float( 3.0 );
+
+					const scatteringHalf = lightViewPosition( spotLight ).sub( positionView ).normalize();
+					const scatteringDot = float( positionViewDirection.dot( scatteringHalf.negate() ).saturate().pow( thicknessPowerNode ) );
+
+					return causticEffect.mul( scatteringDot.add( .1 ) ).mul( .02 );
+
+				} )();
+
+				// GUI
+
+				const gui = new GUI();
+				gui.add( causticOcclusion, 'value', 0, 20 ).name( 'caustic occlusion' );
+				gui.addColor( duck.material, 'color' ).name( 'material color' );
+
+				// Ground
+
+				const textureLoader = new THREE.TextureLoader();
+				const map = textureLoader.load( 'textures/hardwood2_diffuse.jpg' );
+				map.wrapS = map.wrapT = THREE.RepeatWrapping;
+				map.repeat.set( 10, 10 );
+
+				const geometry = new THREE.PlaneGeometry( 2, 2 );
+				const material = new THREE.MeshStandardMaterial( { color: 0 } );
+
+				const ground = new THREE.Mesh( geometry, material );
+				ground.rotation.x = - Math.PI / 2;
+				ground.receiveShadow = true;
+				scene.add( ground );
+
+				// Renderer
+
+				renderer = new THREE.WebGPURenderer( { antialias: true } );
+				renderer.shadowMap.enabled = true;
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				document.body.appendChild( renderer.domElement );
+
+				// Post-Processing
+
+				postProcessing = new THREE.PostProcessing( renderer );
+
+				// Layers
+
+				const volumetricLightingIntensity = uniform( .7 );
+
+				const volumetricLayer = new THREE.Layers();
+				volumetricLayer.disableAll();
+				volumetricLayer.enable( LAYER_VOLUMETRIC_LIGHTING );
+
+				// Volumetric Fog Area
+
+				function createTexture3D() {
+
+					let i = 0;
+
+					const size = 128;
+					const data = new Uint8Array( size * size * size );
+
+					const scale = 10;
+					const perlin = new ImprovedNoise();
+
+					const repeatFactor = 5.0;
+
+					for ( let z = 0; z < size; z ++ ) {
+
+						for ( let y = 0; y < size; y ++ ) {
+
+							for ( let x = 0; x < size; x ++ ) {
+
+								const nx = ( x / size ) * repeatFactor;
+								const ny = ( y / size ) * repeatFactor;
+								const nz = ( z / size ) * repeatFactor;
+
+								const noiseValue = perlin.noise( nx * scale, ny * scale, nz * scale );
+
+								data[ i ] = ( 128 + 128 * noiseValue );
+
+								i ++;
+
+							}
+
+						}
+
+					}
+
+					const texture = new THREE.Data3DTexture( data, size, size, size );
+					texture.format = THREE.RedFormat;
+					texture.minFilter = THREE.LinearFilter;
+					texture.magFilter = THREE.LinearFilter;
+					texture.wrapS = THREE.RepeatWrapping;
+					texture.wrapT = THREE.RepeatWrapping;
+					texture.unpackAlignment = 1;
+					texture.needsUpdate = true;
+
+					return texture;
+
+				}
+
+				const noiseTexture3D = createTexture3D();
+
+				const smokeAmount = uniform( 3 );
+
+				const volumetricMaterial = new THREE.VolumeNodeMaterial();
+				volumetricMaterial.steps = 20;
+				volumetricMaterial.offsetNode = bayer16( screenCoordinate.add( frameId ) ); // Add dithering to reduce banding
+				volumetricMaterial.scatteringNode = Fn( ( { positionRay } ) => {
+
+					// Return the amount of fog based on the noise texture
+
+					const timeScaled = vec3( time.mul( .01 ), 0, time.mul( .03 ) );
+
+					const sampleGrain = ( scale, timeScale = 1 ) => texture3D( noiseTexture3D, positionRay.add( timeScaled.mul( timeScale ) ).mul( scale ).mod( 1 ), 0 ).r.add( .5 );
+
+					let density = sampleGrain( 1 );
+					density = density.mul( sampleGrain( .5, 1 ) );
+					density = density.mul( sampleGrain( .2, 2 ) );
+
+					return smokeAmount.mix( 1, density );
+
+				} );
+
+				const volumetricMesh = new THREE.Mesh( new THREE.BoxGeometry( 1.5, .5, 1.5 ), volumetricMaterial );
+				volumetricMesh.receiveShadow = true;
+				volumetricMesh.position.y = .25;
+				volumetricMesh.layers.disableAll();
+				volumetricMesh.layers.enable( LAYER_VOLUMETRIC_LIGHTING );
+				scene.add( volumetricMesh );
+
+				// Scene Pass
+
+				const scenePass = pass( scene, camera );
+				const sceneDepth = scenePass.getTextureNode( 'depth' );
+
+				// Material - Apply occlusion depth of volumetric lighting based on the scene depth
+
+				volumetricMaterial.depthNode = sceneDepth.sample( screenUV );
+
+				// Volumetric Lighting Pass
+
+				const volumetricPass = pass( scene, camera, { depthBuffer: false } );
+				volumetricPass.setLayers( volumetricLayer );
+				volumetricPass.setResolution( .5 );
+
+				// Compose and Denoise
+
+				const bloomPass = bloom( volumetricPass, 1, 1, 0 );
+
+				const scenePassColor = scenePass.add( bloomPass.mul( volumetricLightingIntensity ) );
+
+				postProcessing.outputNode = scenePassColor;
+
+				// Stats
+
+				stats = new Stats();
+				document.body.appendChild( stats.dom );
+
+				// Controls
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.target.z = - .05;
+				controls.target.y = .02;
+				controls.maxDistance = 1;
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				stats.update();
+
+				for ( const mesh of gltf.children ) {
+
+					mesh.rotation.y -= .01;
+
+				}
+
+				controls.update();
+
+				postProcessing.render();
+
+			}
+
+		</script>
+	</body>
+</html>

+ 9 - 0
src/lights/LightShadow.js

@@ -3,6 +3,7 @@ import { Vector2 } from '../math/Vector2.js';
 import { Vector3 } from '../math/Vector3.js';
 import { Vector3 } from '../math/Vector3.js';
 import { Vector4 } from '../math/Vector4.js';
 import { Vector4 } from '../math/Vector4.js';
 import { Frustum } from '../math/Frustum.js';
 import { Frustum } from '../math/Frustum.js';
+import { UnsignedByteType } from '../constants.js';
 
 
 const _projScreenMatrix = /*@__PURE__*/ new Matrix4();
 const _projScreenMatrix = /*@__PURE__*/ new Matrix4();
 const _lightPositionWorld = /*@__PURE__*/ new Vector3();
 const _lightPositionWorld = /*@__PURE__*/ new Vector3();
@@ -95,6 +96,14 @@ class LightShadow {
 		 */
 		 */
 		this.mapSize = new Vector2( 512, 512 );
 		this.mapSize = new Vector2( 512, 512 );
 
 
+		/**
+		 * The type of shadow texture. The default is `UnsignedByteType`.
+		 *
+		 * @type {number}
+		 * @default UnsignedByteType
+		 */
+		this.mapType = UnsignedByteType;
+
 		/**
 		/**
 		 * The depth map generated using the internal camera; a location beyond a
 		 * The depth map generated using the internal camera; a location beyond a
 		 * pixel's depth is in shadow. Computed internally during rendering.
 		 * pixel's depth is in shadow. Computed internally during rendering.

+ 32 - 2
src/materials/nodes/NodeMaterial.js

@@ -278,7 +278,16 @@ class NodeMaterial extends Material {
 		 * @type {?Node<float>}
 		 * @type {?Node<float>}
 		 * @default null
 		 * @default null
 		 */
 		 */
-		this.shadowPositionNode = null;
+		this.receivedShadowPositionNode = null;
+
+		/**
+		 * Allows to overwrite the geometry position used for shadow map projection which
+		 * is by default {@link positionLocal}, the vertex position in local space.
+		 *
+		 * @type {?Node<float>}
+		 * @default null
+		 */
+		this.castShadowPositionNode = null;
 
 
 		/**
 		/**
 		 * This node can be used to influence how an object using this node material
 		 * This node can be used to influence how an object using this node material
@@ -362,6 +371,26 @@ class NodeMaterial extends Material {
 		 */
 		 */
 		this.vertexNode = null;
 		this.vertexNode = null;
 
 
+		// Deprecated properties
+
+		Object.defineProperty( this, 'shadowPositionNode', { // @deprecated, r176
+
+			get: () => {
+
+				return this.receivedShadowPositionNode;
+
+			},
+
+			set: ( value ) => {
+
+				console.warn( 'THREE.NodeMaterial: ".shadowPositionNode" was renamed to ".receivedShadowPositionNode".' );
+
+				this.receivedShadowPositionNode = value;
+
+			}
+
+		} );
+
 	}
 	}
 
 
 	/**
 	/**
@@ -1165,7 +1194,8 @@ class NodeMaterial extends Material {
 		this.geometryNode = source.geometryNode;
 		this.geometryNode = source.geometryNode;
 
 
 		this.depthNode = source.depthNode;
 		this.depthNode = source.depthNode;
-		this.shadowPositionNode = source.shadowPositionNode;
+		this.receivedShadowPositionNode = source.receivedShadowPositionNode;
+		this.castShadowPositionNode = source.castShadowPositionNode;
 		this.receivedShadowNode = source.receivedShadowNode;
 		this.receivedShadowNode = source.receivedShadowNode;
 		this.castShadowNode = source.castShadowNode;
 		this.castShadowNode = source.castShadowNode;
 
 

+ 1 - 1
src/nodes/lighting/ShadowBaseNode.js

@@ -64,7 +64,7 @@ class ShadowBaseNode extends Node {
 
 
 		// Use assign inside an Fn()
 		// Use assign inside an Fn()
 
 
-		shadowPositionWorld.assign( material.shadowPositionNode || context.shadowPositionWorld || positionWorld );
+		shadowPositionWorld.assign( material.receivedShadowPositionNode || context.shadowPositionWorld || positionWorld );
 
 
 	}
 	}
 
 

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

@@ -390,6 +390,7 @@ class ShadowNode extends ShadowBaseNode {
 
 
 		const shadowMap = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height );
 		const shadowMap = builder.createRenderTarget( shadow.mapSize.width, shadow.mapSize.height );
 		shadowMap.texture.name = 'ShadowMap';
 		shadowMap.texture.name = 'ShadowMap';
+		shadowMap.texture.type = shadow.mapType;
 		shadowMap.depthTexture = depthTexture;
 		shadowMap.depthTexture = depthTexture;
 
 
 		return { shadowMap, depthTexture };
 		return { shadowMap, depthTexture };
@@ -613,6 +614,8 @@ class ShadowNode extends ShadowBaseNode {
 
 
 		renderer.setRenderObjectFunction( getShadowRenderObjectFunction( renderer, shadow, shadowType, useVelocity ) );
 		renderer.setRenderObjectFunction( getShadowRenderObjectFunction( renderer, shadow, shadowType, useVelocity ) );
 
 
+		renderer.setClearColor( 0x000000, 0 );
+
 		renderer.setRenderTarget( shadowMap );
 		renderer.setRenderTarget( shadowMap );
 
 
 		this.renderShadow( frame );
 		this.renderShadow( frame );

+ 7 - 0
src/renderers/common/Renderer.js

@@ -2857,6 +2857,13 @@ class Renderer {
 
 
 				}
 				}
 
 
+				if ( material.castShadowPositionNode && material.castShadowPositionNode.isNode ) {
+
+					overridePositionNode = overrideMaterial.positionNode;
+					overrideMaterial.positionNode = material.castShadowPositionNode;
+
+				}
+
 			}
 			}
 
 
 			material = overrideMaterial;
 			material = overrideMaterial;

+ 1 - 0
test/e2e/puppeteer.js

@@ -171,6 +171,7 @@ const exceptionList = [
 	'webgpu_postprocessing_fxaa',
 	'webgpu_postprocessing_fxaa',
 	'webgpu_postprocessing_afterimage',
 	'webgpu_postprocessing_afterimage',
 	'webgpu_xr_native_layers',
 	'webgpu_xr_native_layers',
+	'webgpu_volume_caustics',
 
 
 	// WebGPU idleTime and parseTime too low
 	// WebGPU idleTime and parseTime too low
 	'webgpu_compute_particles',
 	'webgpu_compute_particles',

粤ICP备19079148号