Skip to content

Commit 35f8853

Browse files
authored
Examples: Add basic fog scattering demo. (#32613)
1 parent c49f8a6 commit 35f8853

3 files changed

Lines changed: 212 additions & 0 deletions

File tree

examples/files.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -328,6 +328,7 @@
328328
"webgpu_cubemap_mix",
329329
"webgpu_custom_fog",
330330
"webgpu_custom_fog_background",
331+
"webgpu_custom_fog_scattering",
331332
"webgpu_depth_texture",
332333
"webgpu_display_stereo",
333334
"webgpu_equirectangular",
33.9 KB
Loading
Lines changed: 211 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,211 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title>three.js webgpu - fog - scattering</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="example.css">
8+
</head>
9+
<body>
10+
11+
<div id="info" class="invert">
12+
<a href="https://threejs.org/" target="_blank" rel="noopener" class="logo-link"></a>
13+
14+
<div class="title-wrapper">
15+
<a href="https://threejs.org/" target="_blank" rel="noopener">three.js</a><span>Fog - Scattering</span>
16+
</div>
17+
18+
<small>
19+
The demo simulates light scattering due to fog.<br/>
20+
</small>
21+
</div>
22+
23+
<script type="importmap">
24+
{
25+
"imports": {
26+
"three": "../build/three.webgpu.js",
27+
"three/webgpu": "../build/three.webgpu.js",
28+
"three/tsl": "../build/three.tsl.js",
29+
"three/addons/": "./jsm/"
30+
}
31+
}
32+
</script>
33+
34+
<script type="module">
35+
36+
import * as THREE from 'three/webgpu';
37+
import { positionWorld, densityFogFactor, pass, vec2, uniform, mix, color } from 'three/tsl';
38+
import { gaussianBlur } from 'three/addons/tsl/display/GaussianBlurNode.js';
39+
40+
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
41+
import { Inspector } from 'three/addons/inspector/Inspector.js';
42+
43+
let camera, scene, renderer, postProcessing, controls;
44+
45+
const params = {
46+
scatteringEnabled: true
47+
};
48+
49+
init();
50+
51+
async function init() {
52+
53+
camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 600 );
54+
camera.position.set( 30, 10, 30 );
55+
56+
scene = new THREE.Scene();
57+
58+
// fog
59+
60+
scene.fog = new THREE.FogExp2( 0xcccccc, 0.014 );
61+
scene.background = new THREE.Color( 0xcccccc );
62+
63+
// builds
64+
65+
const buildWindows = positionWorld.y.mul( 10 ).floor().mod( 4 ).sign().mix( color( 0x000066 ), color( 0xffffff ) );
66+
67+
const buildGeometry = new THREE.BoxGeometry( 1, 1, 1 );
68+
const buildMaterial = new THREE.MeshPhongNodeMaterial( {
69+
colorNode: buildWindows
70+
} );
71+
72+
const buildMesh = new THREE.InstancedMesh( buildGeometry, buildMaterial, 4000 );
73+
scene.add( buildMesh );
74+
75+
const dummy = new THREE.Object3D();
76+
const center = new THREE.Vector3();
77+
78+
for ( let i = 0; i < buildMesh.count; i ++ ) {
79+
80+
const scaleY = Math.random() * 7 + .5;
81+
82+
dummy.position.x = Math.random() * 600 - 300;
83+
dummy.position.z = Math.random() * 600 - 300;
84+
85+
const distance = Math.max( dummy.position.distanceTo( center ) * .012, 1 );
86+
87+
dummy.position.y = .5 * scaleY * distance;
88+
89+
dummy.scale.x = dummy.scale.z = Math.random() * 3 + .5;
90+
dummy.scale.y = scaleY * distance;
91+
92+
dummy.updateMatrix();
93+
94+
buildMesh.setMatrixAt( i, dummy.matrix );
95+
96+
}
97+
98+
// lights
99+
100+
scene.add( new THREE.HemisphereLight( 0xf0f5f5, 0xd0dee7, 0.5 ) );
101+
102+
// geometry
103+
104+
const planeGeometry = new THREE.PlaneGeometry( 200, 200 );
105+
const planeMaterial = new THREE.MeshPhongMaterial( {
106+
color: 0x999999
107+
} );
108+
109+
const ground = new THREE.Mesh( planeGeometry, planeMaterial );
110+
ground.rotation.x = - Math.PI / 2;
111+
ground.scale.multiplyScalar( 3 );
112+
ground.castShadow = true;
113+
ground.receiveShadow = true;
114+
scene.add( ground );
115+
116+
// renderer
117+
118+
renderer = new THREE.WebGPURenderer( { antialias: true } );
119+
renderer.setPixelRatio( window.devicePixelRatio );
120+
renderer.setSize( window.innerWidth, window.innerHeight );
121+
renderer.setAnimationLoop( animate );
122+
renderer.inspector = new Inspector();
123+
document.body.appendChild( renderer.domElement );
124+
125+
// controls
126+
127+
controls = new OrbitControls( camera, renderer.domElement );
128+
controls.target.set( 0, 2, 0 );
129+
controls.minDistance = 7;
130+
controls.maxDistance = 100;
131+
controls.maxPolarAngle = Math.PI / 2;
132+
controls.enableDamping = true;
133+
controls.update();
134+
135+
//
136+
137+
postProcessing = new THREE.PostProcessing( renderer );
138+
139+
// uniforms
140+
141+
const density = uniform( 0.014 );
142+
const scattering = uniform( 2 );
143+
144+
// scene pass
145+
146+
const scenePass = pass( scene, camera );
147+
148+
const scenePassColor = scenePass.getTextureNode( 'output' );
149+
const scenePassViewZ = scenePass.getViewZNode();
150+
151+
// blur pass (always downsampled to improve performance)
152+
153+
const sceneColorBlurred = gaussianBlur( scenePassColor, vec2( scattering ), 4, { resolutionScale: 0.5 } );
154+
155+
// composite
156+
157+
const fogFactor = densityFogFactor( density ).context( { getViewZ: () => scenePassViewZ } );
158+
159+
const compositeNode = mix( scenePassColor, sceneColorBlurred, fogFactor );
160+
161+
postProcessing.outputNode = compositeNode;
162+
163+
// gui
164+
165+
const gui = renderer.inspector.createParameters( 'Settings' );
166+
gui.add( density, 'value', 0.005, 0.03 ).step( 0.0001 ).name( 'fog density' ).onChange( ( value ) => {
167+
168+
scene.fog.density = value;
169+
170+
} );
171+
gui.add( scattering, 'value', 0, 5 ).name( 'scattering factor' );
172+
gui.add( params, 'scatteringEnabled' ).name( 'enable scattering' ).onChange( ( value ) => {
173+
174+
if ( value === true ) {
175+
176+
postProcessing.outputNode = compositeNode;
177+
178+
} else {
179+
180+
postProcessing.outputNode = scenePassColor;
181+
182+
}
183+
184+
postProcessing.needsUpdate = true;
185+
186+
} );
187+
188+
window.addEventListener( 'resize', resize );
189+
190+
}
191+
192+
function resize() {
193+
194+
camera.aspect = window.innerWidth / window.innerHeight;
195+
camera.updateProjectionMatrix();
196+
197+
renderer.setSize( window.innerWidth, window.innerHeight );
198+
199+
}
200+
201+
function animate() {
202+
203+
controls.update();
204+
205+
postProcessing.render();
206+
207+
}
208+
209+
</script>
210+
</body>
211+
</html>

0 commit comments

Comments
 (0)