Skip to content

Commit e9277b2

Browse files
WebGPURenderer: Compute Texture 3D Example (mrdoob#31337)
* WebGPURenderer: Compute Texture 3D Example * remove unused file
1 parent 3b1ff76 commit e9277b2

File tree

7 files changed

+237
-3
lines changed

7 files changed

+237
-3
lines changed

examples/files.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -315,6 +315,7 @@
315315
"webgpu_compute_points",
316316
"webgpu_compute_sort_bitonic",
317317
"webgpu_compute_texture",
318+
"webgpu_compute_texture_3d",
318319
"webgpu_compute_texture_pingpong",
319320
"webgpu_compute_water",
320321
"webgpu_cubemap_adjustments",
3.33 KB
Loading
Lines changed: 216 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,216 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<title>three.js webgpu - compute texture 3D</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+
10+
<body>
11+
<div id="info">
12+
<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> WebGPU - Compute Texture 3D
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+
import { time, mx_noise_vec3, instanceIndex, textureStore, float, vec3, vec4, If, Break, Fn, smoothstep, texture3D, uniform } from 'three/tsl';
30+
31+
import { RaymarchingBox } from 'three/addons/tsl/utils/Raymarching.js';
32+
33+
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
34+
35+
import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
36+
37+
let renderer, scene, camera;
38+
let mesh;
39+
let computeNode;
40+
41+
init();
42+
43+
async function init() {
44+
45+
renderer = new THREE.WebGPURenderer( { antialias: true } );
46+
renderer.setPixelRatio( window.devicePixelRatio );
47+
renderer.setSize( window.innerWidth, window.innerHeight );
48+
renderer.setAnimationLoop( animate );
49+
document.body.appendChild( renderer.domElement );
50+
51+
scene = new THREE.Scene();
52+
53+
camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 100 );
54+
camera.position.set( 0, 1, 1.5 );
55+
56+
new OrbitControls( camera, renderer.domElement );
57+
58+
// Sky
59+
60+
const canvas = document.createElement( 'canvas' );
61+
canvas.width = 1;
62+
canvas.height = 32;
63+
64+
const context = canvas.getContext( '2d' );
65+
const gradient = context.createLinearGradient( 0, 0, 0, 32 );
66+
gradient.addColorStop( 0.0, '#014a84' );
67+
gradient.addColorStop( 0.5, '#0561a0' );
68+
gradient.addColorStop( 1.0, '#437ab6' );
69+
context.fillStyle = gradient;
70+
context.fillRect( 0, 0, 1, 32 );
71+
72+
const skyMap = new THREE.CanvasTexture( canvas );
73+
skyMap.colorSpace = THREE.SRGBColorSpace;
74+
75+
const sky = new THREE.Mesh(
76+
new THREE.SphereGeometry( 10 ),
77+
new THREE.MeshBasicNodeMaterial( { map: skyMap, side: THREE.BackSide } )
78+
);
79+
scene.add( sky );
80+
81+
// Texture
82+
83+
const size = 200;
84+
85+
const computeCloud = Fn( ( { storageTexture } ) => {
86+
87+
const scale = float( 0.05 );
88+
const id = instanceIndex;
89+
90+
const x = id.mod( size );
91+
const y = id.div( size ).mod( size );
92+
const z = id.div( size * size );
93+
94+
95+
const coord3d = vec3( x, y, z );
96+
const centered = coord3d.sub( size / 2 ).div( size );
97+
const d = float( 1.0 ).sub( centered.length() );
98+
99+
const noiseCoord = coord3d.mul( scale.div( 1.5 ) ).add( time );
100+
101+
const noise = mx_noise_vec3( noiseCoord ).toConst( 'noise' );
102+
103+
const data = noise.mul( d ).mul( d ).toConst( 'data' );
104+
105+
textureStore( storageTexture, vec3( x, y, z ), vec4( vec3( data.x ), 1.0 ) );
106+
107+
} );
108+
109+
const storageTexture = new THREE.Storage3DTexture( size, size, size );
110+
storageTexture.generateMipmaps = false;
111+
storageTexture.name = 'cloud';
112+
113+
computeNode = computeCloud( { storageTexture } ).compute( size * size * size ).label( 'computeCloud' );
114+
115+
// Shader
116+
117+
const transparentRaymarchingTexture = Fn( ( {
118+
texture,
119+
range = float( 0.14 ),
120+
threshold = float( 0.08 ),
121+
opacity = float( 0.18 ),
122+
steps = float( 100 )
123+
} ) => {
124+
125+
const finalColor = vec4( 0 ).toVar();
126+
127+
RaymarchingBox( steps, ( { positionRay } ) => {
128+
129+
const mapValue = float( texture.sample( positionRay.add( 0.5 ) ).r ).toVar();
130+
131+
mapValue.assign( smoothstep( threshold.sub( range ), threshold.add( range ), mapValue ).mul( opacity ) );
132+
133+
const shading = texture.sample( positionRay.add( vec3( - 0.01 ) ) ).r.sub( texture.sample( positionRay.add( vec3( 0.01 ) ) ).r );
134+
135+
const col = shading.mul( 4.0 ).add( positionRay.x.add( positionRay.y ).mul( 0.5 ) ).add( 0.3 );
136+
137+
finalColor.rgb.addAssign( finalColor.a.oneMinus().mul( mapValue ).mul( col ) );
138+
139+
finalColor.a.addAssign( finalColor.a.oneMinus().mul( mapValue ) );
140+
141+
If( finalColor.a.greaterThanEqual( 0.95 ), () => {
142+
143+
Break();
144+
145+
} );
146+
147+
} );
148+
149+
return finalColor;
150+
151+
} );
152+
153+
// Material
154+
155+
const baseColor = uniform( new THREE.Color( 0x798aa0 ) );
156+
const range = uniform( 0.1 );
157+
const threshold = uniform( 0.08 );
158+
const opacity = uniform( 0.08 );
159+
const steps = uniform( 100 );
160+
161+
const cloud3d = transparentRaymarchingTexture( {
162+
texture: texture3D( storageTexture, null, 0 ),
163+
range,
164+
threshold,
165+
opacity,
166+
steps
167+
} );
168+
169+
const finalCloud = cloud3d.setRGB( cloud3d.rgb.add( baseColor ) );
170+
171+
const material = new THREE.NodeMaterial();
172+
material.colorNode = finalCloud;
173+
material.side = THREE.BackSide;
174+
material.transparent = true;
175+
material.name = 'transparentRaymarchingMaterial';
176+
177+
mesh = new THREE.Mesh( new THREE.BoxGeometry( 10, 10, 10 ), material );
178+
scene.add( mesh );
179+
180+
mesh.rotation.y = Math.PI / 2;
181+
182+
//
183+
184+
await renderer.init();
185+
await renderer.computeAsync( computeNode );
186+
187+
const gui = new GUI();
188+
gui.add( threshold, 'value', 0, 1, 0.01 ).name( 'threshold' );
189+
gui.add( opacity, 'value', 0, 1, 0.01 ).name( 'opacity' );
190+
gui.add( range, 'value', 0, 1, 0.01 ).name( 'range' );
191+
gui.add( steps, 'value', 0, 200, 1 ).name( 'steps' );
192+
193+
window.addEventListener( 'resize', onWindowResize );
194+
195+
}
196+
197+
function onWindowResize() {
198+
199+
camera.aspect = window.innerWidth / window.innerHeight;
200+
camera.updateProjectionMatrix();
201+
202+
renderer.setSize( window.innerWidth, window.innerHeight );
203+
204+
}
205+
206+
function animate() {
207+
208+
renderer.computeAsync( computeNode );
209+
renderer.render( scene, camera );
210+
211+
}
212+
213+
</script>
214+
215+
</body>
216+
</html>

src/nodes/accessors/StorageTextureNode.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -186,7 +186,7 @@ class StorageTextureNode extends TextureNode {
186186
const { uvNode, storeNode, depthNode } = properties;
187187

188188
const textureProperty = super.generate( builder, 'property' );
189-
const uvSnippet = uvNode.build( builder, 'uvec2' );
189+
const uvSnippet = uvNode.build( builder, this.value.is3DTexture === true ? 'uvec3' : 'uvec2' );
190190
const storeSnippet = storeNode.build( builder, 'vec4' );
191191
const depthSnippet = depthNode ? depthNode.build( builder, 'int' ) : null;
192192

src/renderers/webgpu/nodes/WGSLNodeBuilder.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -899,7 +899,15 @@ class WGSLNodeBuilder extends NodeBuilder {
899899

900900
if ( type === 'texture' || type === 'storageTexture' ) {
901901

902-
texture = new NodeSampledTexture( uniformNode.name, uniformNode.node, group, access );
902+
if ( node.value.is3DTexture === true ) {
903+
904+
texture = new NodeSampledTexture3D( uniformNode.name, uniformNode.node, group, access );
905+
906+
} else {
907+
908+
texture = new NodeSampledTexture( uniformNode.name, uniformNode.node, group, access );
909+
910+
}
903911

904912
} else if ( type === 'cubeTexture' ) {
905913

src/renderers/webgpu/utils/WebGPUBindingUtils.js

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,15 @@ class WebGPUBindingUtils {
424424
} else {
425425

426426
const mipLevelCount = binding.store ? 1 : textureData.texture.mipLevelCount;
427-
const propertyName = `view-${ textureData.texture.width }-${ textureData.texture.height }-${ mipLevelCount }`;
427+
let propertyName = `view-${ textureData.texture.width }-${ textureData.texture.height }`;
428+
429+
if ( textureData.texture.depthOrArrayLayers > 1 ) {
430+
431+
propertyName += `-${ textureData.texture.depthOrArrayLayers }`;
432+
433+
}
434+
435+
propertyName += `-${ mipLevelCount }`;
428436

429437
resourceGPU = textureData[ propertyName ];
430438

test/e2e/puppeteer.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ const exceptionList = [
126126
// Awaiting for WebGL backend support
127127
'webgpu_compute_audio',
128128
'webgpu_compute_texture',
129+
'webgpu_compute_texture_3d',
129130
'webgpu_compute_texture_pingpong',
130131
'webgpu_compute_water',
131132
'webgpu_materials',

0 commit comments

Comments
 (0)