Skip to content

Commit 51f4b78

Browse files
authored
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
1 parent 0ff094e commit 51f4b78

File tree

15 files changed

+642
-11
lines changed

15 files changed

+642
-11
lines changed

examples/files.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,7 @@
307307
"webgpu_camera",
308308
"webgpu_camera_array",
309309
"webgpu_camera_logarithmicdepthbuffer",
310+
"webgpu_caustics",
310311
"webgpu_centroid_sampling",
311312
"webgpu_clearcoat",
312313
"webgpu_clipping",
@@ -453,6 +454,7 @@
453454
"webgpu_tsl_vfx_tornado",
454455
"webgpu_video_frame",
455456
"webgpu_video_panorama",
457+
"webgpu_volume_caustics",
456458
"webgpu_volume_cloud",
457459
"webgpu_volume_lighting",
458460
"webgpu_volume_lighting_rectarea",

examples/jsm/objects/WaterMesh.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ class WaterMesh extends Mesh {
153153

154154
material.opacityNode = this.alpha;
155155

156-
material.shadowPositionNode = positionWorld.add( distortion );
156+
material.receivedShadowPositionNode = positionWorld.add( distortion );
157157

158158
material.setupOutgoingLight = () => diffuseColor.rgb; // backwards compatibility
159159

74.4 KB
Loading
-753 Bytes
Loading
39.5 KB
Loading
77.2 KB
Loading

examples/webgpu_caustics.html

Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title>three.js webgpu - caustics</title>
5+
<meta charset="utf-8">
6+
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
7+
<link type="text/css" rel="stylesheet" href="main.css">
8+
</head>
9+
<body>
10+
11+
<div id="info">
12+
<a href="https://threejs.org" target="_blank" rel="noopener">three.js webgpu</a> - realtime caustics
13+
</div>
14+
15+
<script type="importmap">
16+
{
17+
"imports": {
18+
"three": "../build/three.webgpu.js",
19+
"three/webgpu": "../build/three.webgpu.js",
20+
"three/tsl": "../build/three.tsl.js",
21+
"three/addons/": "./jsm/"
22+
}
23+
}
24+
</script>
25+
26+
<script type="module">
27+
28+
import * as THREE from 'three';
29+
30+
import { uniform, refract, div, positionViewDirection, positionLocal, normalView, texture, Fn, vec2, vec3, vec4 } from 'three/tsl';
31+
32+
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
33+
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
34+
import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
35+
36+
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
37+
38+
import Stats from 'three/addons/libs/stats.module.js';
39+
40+
let camera, scene, renderer, controls;
41+
let stats;
42+
let gltf;
43+
44+
init();
45+
46+
async function init() {
47+
48+
camera = new THREE.PerspectiveCamera( 25, window.innerWidth / window.innerHeight, 0.025, 5 );
49+
camera.position.set( - 0.5, 0.35, 0.2 );
50+
51+
scene = new THREE.Scene();
52+
53+
// light
54+
55+
const spotLight = new THREE.SpotLight( 0xffffff, 1 );
56+
spotLight.position.set( .2, .3, .2 );
57+
spotLight.castShadow = true;
58+
spotLight.angle = Math.PI / 6;
59+
spotLight.penumbra = 1;
60+
spotLight.decay = 2;
61+
spotLight.distance = 0;
62+
spotLight.shadow.mapType = THREE.HalfFloatType; // For HDR Caustics
63+
spotLight.shadow.mapSize.width = 1024;
64+
spotLight.shadow.mapSize.height = 1024;
65+
spotLight.shadow.camera.near = .1;
66+
spotLight.shadow.camera.far = 1;
67+
spotLight.shadow.bias = - .003;
68+
spotLight.shadow.intensity = .95;
69+
scene.add( spotLight );
70+
71+
// model / textures
72+
73+
const dracoLoader = new DRACOLoader();
74+
dracoLoader.setDecoderPath( 'jsm/libs/draco/' );
75+
dracoLoader.setDecoderConfig( { type: 'js' } );
76+
77+
gltf = ( await new GLTFLoader().setDRACOLoader( dracoLoader ).loadAsync( './models/gltf/duck.glb' ) ).scene;
78+
gltf.scale.setScalar( .5 );
79+
scene.add( gltf );
80+
81+
const causticMap = new THREE.TextureLoader().load( './textures/opengameart/Caustic_Free.jpg' );
82+
causticMap.wrapS = causticMap.wrapT = THREE.RepeatWrapping;
83+
causticMap.colorSpace = THREE.SRGBColorSpace;
84+
85+
// objects / material
86+
87+
const duck = gltf.children[ 0 ];
88+
duck.material = new THREE.MeshPhysicalNodeMaterial();
89+
duck.material.side = THREE.DoubleSide;
90+
duck.material.transparent = true;
91+
duck.material.color = new THREE.Color( 0xFFD700 );
92+
duck.material.transmission = 1;
93+
duck.material.thickness = .25;
94+
duck.material.ior = 1.5;
95+
duck.material.metalness = 0;
96+
duck.material.roughness = .1;
97+
duck.castShadow = true;
98+
99+
// tsl shader
100+
101+
const causticOcclusion = uniform( 20 );
102+
103+
duck.material.castShadowPositionNode = Fn( () => {
104+
105+
// optional: add some distortion to the geometry shadow position if needed
106+
107+
return positionLocal;
108+
109+
} )();
110+
111+
duck.material.castShadowNode = Fn( () => {
112+
113+
const refractionVector = refract( positionViewDirection.negate(), normalView, div( 1.0, duck.material.ior ) ).normalize();
114+
const viewZ = normalView.z.pow( causticOcclusion );
115+
116+
const textureUV = refractionVector.xy.mul( .6 );
117+
118+
const causticColor = uniform( duck.material.color );
119+
const chromaticAberrationOffset = normalView.z.pow( - .9 ).mul( .004 );
120+
121+
const causticProjection = vec3(
122+
texture( causticMap, textureUV.add( vec2( chromaticAberrationOffset.x.negate(), 0 ) ) ).r,
123+
texture( causticMap, textureUV.add( vec2( 0, chromaticAberrationOffset.y.negate() ) ) ).g,
124+
texture( causticMap, textureUV.add( vec2( chromaticAberrationOffset.x, chromaticAberrationOffset.y ) ) ).b
125+
);
126+
127+
return causticProjection.mul( viewZ.mul( 25 ) ).add( viewZ ).mul( causticColor );
128+
129+
} )();
130+
131+
//
132+
133+
const textureLoader = new THREE.TextureLoader();
134+
135+
// glass
136+
137+
const colorMap = textureLoader.load( 'textures/colors.png' );
138+
colorMap.wrapS = colorMap.wrapT = THREE.RepeatWrapping;
139+
colorMap.colorSpace = THREE.SRGBColorSpace;
140+
141+
const glassMaterial = new THREE.MeshPhysicalNodeMaterial();
142+
glassMaterial.map = colorMap;
143+
glassMaterial.side = THREE.DoubleSide;
144+
glassMaterial.transparent = true;
145+
glassMaterial.color = new THREE.Color( 0xffffff );
146+
glassMaterial.transmission = 1;
147+
glassMaterial.ior = 1.5;
148+
glassMaterial.metalness = 0;
149+
glassMaterial.roughness = .1;
150+
glassMaterial.castShadowNode = vec4( texture( colorMap ).rgb, .8 );
151+
152+
const glass = new THREE.Mesh( new THREE.PlaneGeometry( .2, .2 ), glassMaterial );
153+
glass.position.y = .1;
154+
glass.castShadow = true;
155+
glass.visible = false;
156+
scene.add( glass );
157+
158+
// gui
159+
160+
const gui = new GUI();
161+
gui.add( causticOcclusion, 'value', 0, 20 ).name( 'caustic occlusion' );
162+
gui.addColor( duck.material, 'color' ).name( 'material color' );
163+
gui.add( { model: 'duck' }, 'model', [
164+
'duck',
165+
'glass'
166+
] ).onChange( model => {
167+
168+
duck.visible = glass.visible = false;
169+
170+
if ( model === 'duck' ) {
171+
172+
duck.visible = true;
173+
174+
} else if ( model === 'glass' ) {
175+
176+
glass.visible = true;
177+
178+
}
179+
180+
} );
181+
182+
// ground
183+
184+
const map = textureLoader.load( 'textures/hardwood2_diffuse.jpg' );
185+
map.wrapS = map.wrapT = THREE.RepeatWrapping;
186+
map.repeat.set( 10, 10 );
187+
188+
const geometry = new THREE.PlaneGeometry( 2, 2 );
189+
const material = new THREE.MeshStandardMaterial( { color: 0x999999, map } );
190+
191+
const ground = new THREE.Mesh( geometry, material );
192+
ground.rotation.x = - Math.PI / 2;
193+
ground.receiveShadow = true;
194+
scene.add( ground );
195+
196+
// renderer
197+
198+
renderer = new THREE.WebGPURenderer( { antialias: true } );
199+
renderer.shadowMap.enabled = true;
200+
renderer.setPixelRatio( window.devicePixelRatio );
201+
renderer.setSize( window.innerWidth, window.innerHeight );
202+
renderer.setAnimationLoop( animate );
203+
document.body.appendChild( renderer.domElement );
204+
205+
// stats
206+
207+
stats = new Stats();
208+
document.body.appendChild( stats.dom );
209+
210+
// controls
211+
212+
controls = new OrbitControls( camera, renderer.domElement );
213+
controls.maxDistance = 3;
214+
controls.maxPolarAngle = Math.PI / 2;
215+
216+
window.addEventListener( 'resize', onWindowResize );
217+
218+
}
219+
220+
function onWindowResize() {
221+
222+
camera.aspect = window.innerWidth / window.innerHeight;
223+
camera.updateProjectionMatrix();
224+
225+
renderer.setSize( window.innerWidth, window.innerHeight );
226+
227+
}
228+
229+
function animate() {
230+
231+
stats.update();
232+
233+
for ( const mesh of gltf.children ) {
234+
235+
mesh.rotation.y -= .01;
236+
237+
}
238+
239+
controls.update();
240+
241+
renderer.render( scene, camera );
242+
243+
}
244+
245+
</script>
246+
</body>
247+
</html>

examples/webgpu_shadowmap.html

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -139,21 +139,20 @@
139139
scene.add( pillar4 );
140140

141141
const planeGeometry = new THREE.PlaneGeometry( 200, 200 );
142-
const planeMaterial = new THREE.MeshPhongMaterial( {
143-
color: 0x999999,
144-
shininess: 0,
145-
specular: 0x111111
146-
} );
147142

148-
planeMaterial.shadowPositionNode = Fn( () => {
143+
const planeMaterial = new THREE.MeshPhongNodeMaterial();
144+
planeMaterial.color.setHex( 0x999999 );
145+
planeMaterial.shininess = 0;
146+
planeMaterial.specular.setHex( 0x111111 );
147+
148+
planeMaterial.receivedShadowPositionNode = Fn( () => {
149149

150150
const pos = positionWorld.toVar();
151151
pos.xz.addAssign( mx_fractal_noise_vec3( positionWorld.mul( 2 ) ).saturate().xz );
152152
return pos;
153153

154154
} )();
155155

156-
157156
planeMaterial.colorNode = Fn( () => {
158157

159158
const pos = positionWorld.toVar();

0 commit comments

Comments
 (0)