Sfoglia il codice sorgente

TSL: Introduce `RaymarchingBox` and `raymarchingTexture3D` (#30495)

* raymarching box

* revisions

* Update Raymarching.js

* add `raymarchingTexture3D`

* cleanup

* Update Raymarching.js

* cleanup

* Update Raymarching.js
sunag 1 anno fa
parent
commit
0c05674d38
2 ha cambiato i file con 140 aggiunte e 53 eliminazioni
  1. 114 0
      examples/jsm/tsl/utils/Raymarching.js
  2. 26 53
      examples/webgpu_volume_cloud.html

+ 114 - 0
examples/jsm/tsl/utils/Raymarching.js

@@ -0,0 +1,114 @@
+import { varying, vec4, modelWorldMatrixInverse, cameraPosition, positionGeometry, float, Fn, Loop, max, min, vec2, vec3, smoothstep, If, Break } from 'three/tsl';
+
+const hitBox = /*@__PURE__*/ Fn( ( { orig, dir } ) => {
+
+	const box_min = vec3( - 0.5 );
+	const box_max = vec3( 0.5 );
+
+	const inv_dir = dir.reciprocal();
+
+	const tmin_tmp = box_min.sub( orig ).mul( inv_dir );
+	const tmax_tmp = box_max.sub( orig ).mul( inv_dir );
+
+	const tmin = min( tmin_tmp, tmax_tmp );
+	const tmax = max( tmin_tmp, tmax_tmp );
+
+	const t0 = max( tmin.x, max( tmin.y, tmin.z ) );
+	const t1 = min( tmax.x, min( tmax.y, tmax.z ) );
+
+	return vec2( t0, t1 );
+
+} );
+
+/**
+ * Performs raymarching box-area using the specified number of steps and a callback function.
+ *
+ * ```js
+ * RaymarchingBox( count, ( { positionRay } ) => {
+ *
+ * } );
+ * ```
+ *
+ * @tsl
+ * @function
+ * @param {Number|Node} steps - The number of steps for raymarching.
+ * @param {Function|FunctionNode} callback - The callback function to execute at each step.
+ * @returns {void}
+ */
+export const RaymarchingBox = ( steps, callback ) => {
+
+	const vOrigin = varying( vec3( modelWorldMatrixInverse.mul( vec4( cameraPosition, 1.0 ) ) ) );
+	const vDirection = varying( positionGeometry.sub( vOrigin ) );
+
+	const rayDir = vDirection.normalize();
+	const bounds = vec2( hitBox( { orig: vOrigin, dir: rayDir } ) ).toVar();
+
+	bounds.x.greaterThan( bounds.y ).discard();
+
+	bounds.assign( vec2( max( bounds.x, 0.0 ), bounds.y ) );
+
+	const inc = vec3( rayDir.abs().reciprocal() ).toVar();
+	const delta = float( min( inc.x, min( inc.y, inc.z ) ) ).toVar( 'rayDelta' ); // used 'rayDelta' name in loop
+
+	delta.divAssign( float( steps ) );
+
+	const positionRay = vec3( vOrigin.add( bounds.x.mul( rayDir ) ) ).toVar();
+
+	Loop( { type: 'float', start: bounds.x, end: bounds.y, update: '+= rayDelta' }, () => {
+
+		callback( { positionRay } );
+
+		positionRay.addAssign( rayDir.mul( delta ) );
+
+	} );
+
+};
+
+/**
+ * Performs raymarching on the specified 3D texture.
+ *
+ * @tsl
+ * @function
+ * @param {Object} params - The parameters for the function.
+ * @param {Texture|Node} params.texture - The 3D texture to sample.
+ * @param {Number|Node} [params.range=0.1] - The range for the smoothstep function.
+ * @param {Number|Node} [params.threshold=0.25] - The threshold for the smoothstep function.
+ * @param {Number|Node} [params.opacity=0.25] - The opacity value for the final color.
+ * @param {Number|Node} [params.steps=100] - The number of steps for raymarching.
+ * @returns {Function} The generated function that performs raymarching on the 3D texture.
+ */
+export const raymarchingTexture3D = Fn( ( {
+	texture,
+	range = float( 0.1 ),
+	threshold = float( 0.25 ),
+	opacity = float( 0.25 ),
+	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( 3.0 ).add( positionRay.x.add( positionRay.y ).mul( 0.25 ) ).add( 0.2 );
+
+		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;
+
+} );

+ 26 - 53
examples/webgpu_volume_cloud.html

@@ -26,7 +26,9 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { vec3, materialReference, smoothstep, If, Break, Fn } from 'three/tsl';
+			import { texture3D, uniform } from 'three/tsl';
+
+			import { raymarchingTexture3D } from 'three/addons/tsl/utils/Raymarching.js';
 
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 			import { ImprovedNoise } from 'three/addons/math/ImprovedNoise.js';
@@ -109,72 +111,43 @@
 				texture.unpackAlignment = 1;
 				texture.needsUpdate = true;
 
+				// Cloud Shader
 
-				const geometry = new THREE.BoxGeometry( 1, 1, 1 );
+				const baseColor = uniform( new THREE.Color( 0x798aa0 ) );
+				const range = uniform( 0.1 );
+				const threshold = uniform( 0.25 );
+				const opacity = uniform( 0.25 );
+				const steps = uniform( 100 );
 
-				const material = new THREE.VolumeNodeMaterial( {
-					side: THREE.BackSide,
-					transparent: true
+				const cloud3d = raymarchingTexture3D( {
+					texture: texture3D( texture, null, 0 ),
+					range: range,
+					threshold: threshold,
+					opacity: opacity,
+					steps: steps
 				} );
 
-				material.map = texture;
-				material.base = new THREE.Color( 0x798aa0 );
-				material.steps = 100;
-				material.range = 0.1;
-				material.threshold = 0.25;
-				material.opacity = 0.25;
-
-				const range = materialReference( 'range', 'float' );
-				const threshold = materialReference( 'threshold', 'float' );
-				const opacity = materialReference( 'opacity', 'float' );
-
-				material.testNode = Fn( ( { map, mapValue, probe, finalColor } ) => {
-
-					mapValue.assign( smoothstep( threshold.sub( range ), threshold.add( range ), mapValue ).mul( opacity ) );
-
-					const shading = map.sample( probe.add( vec3( - 0.01 ) ) ).r.sub( map.sample( probe.add( vec3( 0.01 ) ) ).r );
-
-					const col = shading.mul( 3.0 ).add( probe.x.add( probe.y ).mul( 0.25 ) ).add( 0.2 );
-
-					finalColor.rgb.addAssign( finalColor.a.oneMinus().mul( mapValue ).mul( col ) );
-
-					finalColor.a.addAssign( finalColor.a.oneMinus().mul( mapValue ) );
-
-					If( finalColor.a.greaterThanEqual( 0.95 ), () => {
+				const finalCloud = cloud3d.setRGB( cloud3d.rgb.add( baseColor ) );
 
-						Break();
+				//
 
-					} );
+				const geometry = new THREE.BoxGeometry( 1, 1, 1 );
 
-				} );
+				const material = new THREE.NodeMaterial();
+				material.colorNode = finalCloud;
+				material.side = THREE.BackSide;
+				material.transparent = true;
 
 				mesh = new THREE.Mesh( geometry, material );
-
 				scene.add( mesh );
 
 				//
 
-				const parameters = {
-					threshold: 0.25,
-					opacity: 0.25,
-					range: 0.1,
-					steps: 100
-				};
-
-				function update() {
-
-					material.threshold = parameters.threshold;
-					material.opacity = parameters.opacity;
-					material.range = parameters.range;
-					material.steps = parameters.steps;
-
-				}
-
 				const gui = new GUI();
-				gui.add( parameters, 'threshold', 0, 1, 0.01 ).onChange( update );
-				gui.add( parameters, 'opacity', 0, 1, 0.01 ).onChange( update );
-				gui.add( parameters, 'range', 0, 1, 0.01 ).onChange( update );
-				gui.add( parameters, 'steps', 0, 200, 1 ).onChange( update );
+				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 );
 

粤ICP备19079148号