Browse Source

WebGPURenderer: Compute Texture 3D Example (#31337)

* WebGPURenderer: Compute Texture 3D Example

* remove unused file
Renaud Rohlinger 7 months ago
parent
commit
e9277b2678

+ 1 - 0
examples/files.json

@@ -315,6 +315,7 @@
 		"webgpu_compute_points",
 		"webgpu_compute_sort_bitonic",
 		"webgpu_compute_texture",
+		"webgpu_compute_texture_3d",
 		"webgpu_compute_texture_pingpong",
 		"webgpu_compute_water",
 		"webgpu_cubemap_adjustments",

BIN
examples/screenshots/webgpu_compute_texture_3d.jpg


+ 216 - 0
examples/webgpu_compute_texture_3d.html

@@ -0,0 +1,216 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - compute texture 3D</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 - Compute Texture 3D
+		</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 { time, mx_noise_vec3, instanceIndex, textureStore, float, vec3, vec4, If, Break, Fn, smoothstep, texture3D, uniform } from 'three/tsl';
+
+			import { RaymarchingBox } from 'three/addons/tsl/utils/Raymarching.js';
+
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+			let renderer, scene, camera;
+			let mesh;
+			let computeNode;
+
+			init();
+
+			async 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 );
+
+				scene = new THREE.Scene();
+
+				camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.1, 100 );
+				camera.position.set( 0, 1, 1.5 );
+
+				new OrbitControls( camera, renderer.domElement );
+
+				// Sky
+
+				const canvas = document.createElement( 'canvas' );
+				canvas.width = 1;
+				canvas.height = 32;
+
+				const context = canvas.getContext( '2d' );
+				const gradient = context.createLinearGradient( 0, 0, 0, 32 );
+				gradient.addColorStop( 0.0, '#014a84' );
+				gradient.addColorStop( 0.5, '#0561a0' );
+				gradient.addColorStop( 1.0, '#437ab6' );
+				context.fillStyle = gradient;
+				context.fillRect( 0, 0, 1, 32 );
+
+				const skyMap = new THREE.CanvasTexture( canvas );
+				skyMap.colorSpace = THREE.SRGBColorSpace;
+
+				const sky = new THREE.Mesh(
+					new THREE.SphereGeometry( 10 ),
+					new THREE.MeshBasicNodeMaterial( { map: skyMap, side: THREE.BackSide } )
+				);
+				scene.add( sky );
+
+				// Texture
+
+				const size = 200;
+
+				const computeCloud = Fn( ( { storageTexture } ) => {
+
+					const scale = float( 0.05 );
+					const id = instanceIndex;
+
+					const x = id.mod( size );
+					const y = id.div( size ).mod( size );
+					const z = id.div( size * size );
+
+			
+					const coord3d = vec3( x, y, z );
+					const centered = coord3d.sub( size / 2 ).div( size );
+					const d = float( 1.0 ).sub( centered.length() );
+
+					const noiseCoord = coord3d.mul( scale.div( 1.5 ) ).add( time );
+
+					const noise = mx_noise_vec3( noiseCoord ).toConst( 'noise' );
+
+					const data = noise.mul( d ).mul( d ).toConst( 'data' );
+
+					textureStore( storageTexture, vec3( x, y, z ), vec4( vec3( data.x ), 1.0 ) );
+
+				} );
+
+				const storageTexture = new THREE.Storage3DTexture( size, size, size );
+				storageTexture.generateMipmaps = false;
+				storageTexture.name = 'cloud';
+			
+				computeNode = computeCloud( { storageTexture } ).compute( size * size * size ).label( 'computeCloud' );
+
+				// Shader
+
+				const transparentRaymarchingTexture = Fn( ( {
+					texture,
+					range = float( 0.14 ),
+					threshold = float( 0.08 ),
+					opacity = float( 0.18 ),
+					steps = float( 100 )
+				} ) => {
+
+					const finalColor = vec4( 0 ).toVar();
+
+					RaymarchingBox( steps, ( { positionRay } ) => {
+
+						const mapValue = float( texture.sample( positionRay.add( 0.5 ) ).r ).toVar();
+
+						mapValue.assign( smoothstep( threshold.sub( range ), threshold.add( range ), mapValue ).mul( opacity ) );
+
+						const shading = texture.sample( positionRay.add( vec3( - 0.01 ) ) ).r.sub( texture.sample( positionRay.add( vec3( 0.01 ) ) ).r );
+
+						const col = shading.mul( 4.0 ).add( positionRay.x.add( positionRay.y ).mul( 0.5 ) ).add( 0.3 );
+
+						finalColor.rgb.addAssign( finalColor.a.oneMinus().mul( mapValue ).mul( col ) );
+
+						finalColor.a.addAssign( finalColor.a.oneMinus().mul( mapValue ) );
+
+						If( finalColor.a.greaterThanEqual( 0.95 ), () => {
+
+							Break();
+
+						} );
+
+					} );
+
+					return finalColor;
+
+				} );
+
+				// Material
+
+				const baseColor = uniform( new THREE.Color( 0x798aa0 ) );
+				const range = uniform( 0.1 );
+				const threshold = uniform( 0.08 );
+				const opacity = uniform( 0.08 );
+				const steps = uniform( 100 );
+
+				const cloud3d = transparentRaymarchingTexture( {
+					texture: texture3D( storageTexture, null, 0 ),
+					range,
+					threshold,
+					opacity,
+					steps
+				} );
+
+				const finalCloud = cloud3d.setRGB( cloud3d.rgb.add( baseColor ) );
+
+				const material = new THREE.NodeMaterial();
+				material.colorNode = finalCloud;
+				material.side = THREE.BackSide;
+				material.transparent = true;
+				material.name = 'transparentRaymarchingMaterial';
+
+				mesh = new THREE.Mesh( new THREE.BoxGeometry( 10, 10, 10 ), material );
+				scene.add( mesh );
+
+				mesh.rotation.y = Math.PI / 2;
+
+				//
+
+				await renderer.init();
+				await renderer.computeAsync( computeNode );
+
+				const gui = new GUI();
+				gui.add( threshold, 'value', 0, 1, 0.01 ).name( 'threshold' );
+				gui.add( opacity, 'value', 0, 1, 0.01 ).name( 'opacity' );
+				gui.add( range, 'value', 0, 1, 0.01 ).name( 'range' );
+				gui.add( steps, 'value', 0, 200, 1 ).name( 'steps' );
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				renderer.computeAsync( computeNode );
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 1 - 1
src/nodes/accessors/StorageTextureNode.js

@@ -186,7 +186,7 @@ class StorageTextureNode extends TextureNode {
 		const { uvNode, storeNode, depthNode } = properties;
 
 		const textureProperty = super.generate( builder, 'property' );
-		const uvSnippet = uvNode.build( builder, 'uvec2' );
+		const uvSnippet = uvNode.build( builder, this.value.is3DTexture === true ? 'uvec3' : 'uvec2' );
 		const storeSnippet = storeNode.build( builder, 'vec4' );
 		const depthSnippet = depthNode ? depthNode.build( builder, 'int' ) : null;
 

+ 9 - 1
src/renderers/webgpu/nodes/WGSLNodeBuilder.js

@@ -899,7 +899,15 @@ class WGSLNodeBuilder extends NodeBuilder {
 
 				if ( type === 'texture' || type === 'storageTexture' ) {
 
-					texture = new NodeSampledTexture( uniformNode.name, uniformNode.node, group, access );
+					if ( node.value.is3DTexture === true ) {
+
+						texture = new NodeSampledTexture3D( uniformNode.name, uniformNode.node, group, access );
+
+					} else {
+
+						texture = new NodeSampledTexture( uniformNode.name, uniformNode.node, group, access );
+
+					}
 
 				} else if ( type === 'cubeTexture' ) {
 

+ 9 - 1
src/renderers/webgpu/utils/WebGPUBindingUtils.js

@@ -424,7 +424,15 @@ class WebGPUBindingUtils {
 				} else {
 
 					const mipLevelCount = binding.store ? 1 : textureData.texture.mipLevelCount;
-					const propertyName = `view-${ textureData.texture.width }-${ textureData.texture.height }-${ mipLevelCount }`;
+					let propertyName = `view-${ textureData.texture.width }-${ textureData.texture.height }`;
+
+					if ( textureData.texture.depthOrArrayLayers > 1 ) {
+
+						propertyName += `-${ textureData.texture.depthOrArrayLayers }`;
+
+					}
+
+					propertyName += `-${ mipLevelCount }`;
 
 					resourceGPU = textureData[ propertyName ];
 

+ 1 - 0
test/e2e/puppeteer.js

@@ -126,6 +126,7 @@ const exceptionList = [
 	// Awaiting for WebGL backend support
 	'webgpu_compute_audio',
 	'webgpu_compute_texture',
+	'webgpu_compute_texture_3d',
 	'webgpu_compute_texture_pingpong',
 	'webgpu_compute_water',
 	'webgpu_materials',

粤ICP备19079148号