Jelajahi Sumber

WebGPURenderer: Custom fog example, TriNoise3D and fixes (#27599)

* LoopNode: Fix float values in GLSL

* Added TriNoise3D TSL

* Fix non-TSL class extended

* add webgpu_custom_fog example

* revision

* cleanup

* update screenshot

* Update puppeteer.js

* auto rotate

* update builds

* update
sunag 1 tahun lalu
induk
melakukan
13a5874eab

+ 1 - 0
examples/files.json

@@ -330,6 +330,7 @@
 		"webgpu_cubemap_adjustments",
 		"webgpu_cubemap_dynamic",
 		"webgpu_cubemap_mix",
+		"webgpu_custom_fog",
 		"webgpu_depth_texture",
 		"webgpu_equirectangular",
 		"webgpu_instance_mesh",

+ 1 - 0
examples/jsm/nodes/Nodes.js

@@ -41,6 +41,7 @@ export { NodeUtils };
 // math
 export { default as MathNode, PI, PI2, EPSILON, INFINITY, radians, degrees, exp, exp2, log, log2, sqrt, inverseSqrt, floor, ceil, normalize, fract, sin, cos, tan, asin, acos, atan, abs, sign, length, lengthSq, negate, oneMinus, dFdx, dFdy, round, reciprocal, trunc, fwidth, bitcast, atan2, min, max, mod, step, reflect, distance, difference, dot, cross, pow, pow2, pow3, pow4, transformDirection, mix, clamp, saturate, refract, smoothstep, faceForward, cbrt } from './math/MathNode.js';
 export { parabola, gain, pcurve, sinc } from './math/MathNode.js';
+export { triNoise3D } from './math/TriNoise3D.js';
 
 export { default as OperatorNode, add, sub, mul, div, remainder, equal, lessThan, greaterThan, lessThanEqual, greaterThanEqual, and, or, xor, bitAnd, bitOr, bitXor, shiftLeft, shiftRight } from './math/OperatorNode.js';
 export { default as CondNode, cond } from './math/CondNode.js';

+ 2 - 1
examples/jsm/nodes/fog/FogNode.js

@@ -1,4 +1,5 @@
 import Node, { addNodeClass } from '../core/Node.js';
+import { mix } from '../math/MathNode.js';
 import { addNodeElement, nodeProxy } from '../shadernode/ShaderNode.js';
 
 class FogNode extends Node {
@@ -16,7 +17,7 @@ class FogNode extends Node {
 
 	mixAssign( outputNode ) {
 
-		return this.mix( outputNode, this.colorNode );
+		return mix( outputNode, this.colorNode, this );
 
 	}
 

+ 71 - 0
examples/jsm/nodes/math/TriNoise3D.js

@@ -0,0 +1,71 @@
+// https://github.com/cabbibo/glsl-tri-noise-3d
+
+import { loop } from '../utils/LoopNode.js';
+import { float, vec3, tslFn } from '../shadernode/ShaderNode.js';
+
+const tri = tslFn( ( [ x ] ) => {
+
+	return x.fract().sub( .5 ).abs();
+
+} );
+
+const tri3 = tslFn( ( [ p ] ) => {
+
+	return vec3( tri( p.z.add( tri( p.y.mul( 1. ) ) ) ), tri( p.z.add( tri( p.x.mul( 1. ) ) ) ), tri( p.y.add( tri( p.x.mul( 1. ) ) ) ) );
+
+} );
+
+const triNoise3D = tslFn( ( [ p_immutable, spd, time ] ) => {
+
+	const p = vec3( p_immutable ).toVar();
+	const z = float( 1.4 ).toVar();
+	const rz = float( 0.0 ).toVar();
+	const bp = vec3( p ).toVar();
+
+	loop( { start: float( 0.0 ), end: float( 3.0 ), type: 'float', condition: '<=' }, () => {
+
+		const dg = vec3( tri3( bp.mul( 2.0 ) ) ).toVar();
+		p.addAssign( dg.add( time.mul( float( 0.1 ).mul( spd ) ) ) );
+		bp.mulAssign( 1.8 );
+		z.mulAssign( 1.5 );
+		p.mulAssign( 1.2 );
+
+		const t = float( tri( p.z.add( tri( p.x.add( tri( p.y ) ) ) ) ) ).toVar();
+		rz.addAssign( t.div( z ) );
+		bp.addAssign( 0.14 );
+
+	} );
+
+	return rz;
+
+} );
+
+// layouts
+
+tri.setLayout( {
+	name: 'tri',
+	type: 'float',
+	inputs: [
+		{ name: 'x', type: 'float' }
+	]
+} );
+
+tri3.setLayout( {
+	name: 'tri3',
+	type: 'vec3',
+	inputs: [
+		{ name: 'p', type: 'vec3' }
+	]
+} );
+
+triNoise3D.setLayout( {
+	name: 'triNoise3D',
+	type: 'float',
+	inputs: [
+		{ name: 'p', type: 'vec3' },
+		{ name: 'spd', type: 'float' },
+		{ name: 'time', type: 'float' }
+	]
+} );
+
+export { tri, tri3, triNoise3D };

+ 3 - 3
examples/jsm/nodes/utils/LoopNode.js

@@ -144,15 +144,15 @@ class LoopNode extends Node {
 
 			if ( ! update ) {
 
-				if ( type === 'int' ) {
+				if ( type === 'int' || type === 'uint' ) {
 
 					if ( condition.includes( '<' ) ) update = '++';
 					else update = '--';
 
 				} else {
 
-					if ( condition.includes( '<' ) ) update = '+= 1';
-					else update = '-= 1';
+					if ( condition.includes( '<' ) ) update = '+= 1.';
+					else update = '-= 1.';
 
 				}
 

TEMPAT SAMPAH
examples/screenshots/webgpu_custom_fog.jpg


+ 174 - 0
examples/webgpu_custom_fog.html

@@ -0,0 +1,174 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - custom fog</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 custom fog
+		</div>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/",
+					"three/nodes": "./jsm/nodes/Nodes.js"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+			import { color, fog, float, positionWorld, triNoise3D, positionView, normalWorld, timerLocal, MeshPhongNodeMaterial } from 'three/nodes';
+
+			import WebGPU from 'three/addons/capabilities/WebGPU.js';
+			import WebGL from 'three/addons/capabilities/WebGL.js';
+
+			import WebGPURenderer from 'three/addons/renderers/webgpu/WebGPURenderer.js';
+
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+			let camera, scene, renderer;
+			let controls;
+
+			init();
+
+			function init() {
+
+				if ( WebGPU.isAvailable() === false && WebGL.isWebGL2Available() === false ) {
+
+					document.body.appendChild( WebGPU.getErrorMessage() );
+
+					throw new Error( 'No WebGPU or WebGL2 support' );
+
+				}
+
+				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 600 );
+				camera.position.set( 30, 15, 30 );
+
+				scene = new THREE.Scene();
+
+				// custom fog
+
+				const skyColor = color( 0xf0f5f5 );
+				const groundColor = color( 0xd0dee7 );
+
+				const fogNoiseDistance = positionView.z.negate().smoothstep( 0, camera.far - 300 );
+
+				const distance = fogNoiseDistance.mul( 20 ).max( 4 );
+				const alpha = .98;
+				const groundFogArea = float( distance ).sub( positionWorld.y ).div( distance ).pow( 3 ).saturate().mul( alpha );
+
+				const timer = timerLocal( 1 );
+
+				const fogNoiseA = triNoise3D( positionWorld.mul( .005 ), 0.2, timer );
+				const fogNoiseB = triNoise3D( positionWorld.mul( .01 ), 0.2, timer.mul( 1.2 ) );
+
+				const fogNoise = fogNoiseA.add( fogNoiseB ).mul( groundColor );
+
+				// apply custom fog
+
+				scene.fogNode = fog( fogNoiseDistance.oneMinus().mix( groundColor, fogNoise ), groundFogArea );
+				scene.backgroundNode = normalWorld.y.max( 0 ).mix( groundColor, skyColor );
+
+				// builds
+
+				const buildWindows = positionWorld.y.mul( 10 ).floor().mod( 4 ).sign().mix( color( 0x000066 ).add( fogNoiseDistance ), color( 0xffffff ) );
+
+				const buildGeometry = new THREE.BoxGeometry( 1, 1, 1 );
+				const buildMaterial = new MeshPhongNodeMaterial( {
+					colorNode: buildWindows
+				} );
+
+				const buildMesh = new THREE.InstancedMesh( buildGeometry, buildMaterial, 4000 );
+				scene.add( buildMesh );
+
+				const dummy = new THREE.Object3D();
+				const center = new THREE.Vector3();
+
+				for ( let i = 0; i < buildMesh.count; i ++ ) {
+
+					const scaleY = Math.random() * 7 + .5;
+
+					dummy.position.x = Math.random() * 600 - 300;
+					dummy.position.z = Math.random() * 600 - 300;
+
+					const distance = Math.max( dummy.position.distanceTo( center ) * .012, 1 );
+
+					dummy.position.y = .5 * scaleY * distance;
+
+					dummy.scale.x = dummy.scale.z = Math.random() * 3 + .5;
+					dummy.scale.y = scaleY * distance;
+
+					dummy.updateMatrix();
+
+					buildMesh.setMatrixAt( i, dummy.matrix );
+
+				}
+
+				// lights
+
+				scene.add( new THREE.HemisphereLight( skyColor.value, groundColor.value, 0.5 ) );
+
+				// geometry
+
+				const planeGeometry = new THREE.PlaneGeometry( 200, 200 );
+				const planeMaterial = new THREE.MeshPhongMaterial( {
+					color: 0x999999
+				} );
+
+				const ground = new THREE.Mesh( planeGeometry, planeMaterial );
+				ground.rotation.x = - Math.PI / 2;
+				ground.scale.multiplyScalar( 3 );
+				ground.castShadow = true;
+				ground.receiveShadow = true;
+				scene.add( ground );
+
+				// renderer
+
+				renderer = new WebGPURenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				document.body.appendChild( renderer.domElement );
+
+				// controls
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.target.set( 0, 2, 0 );
+				controls.minDistance = 7;
+				controls.maxDistance = 100;
+				controls.maxPolarAngle = Math.PI / 2;
+				controls.autoRotate = true;
+				controls.autoRotateSpeed = .1;
+				controls.update();
+
+				window.addEventListener( 'resize', resize );
+
+			}
+
+			function resize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				controls.update();
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>

+ 1 - 0
test/e2e/puppeteer.js

@@ -134,6 +134,7 @@ const exceptionList = [
 	'webgpu_tsl_editor',
 	'webgpu_tsl_transpiler',
 	'webgpu_portal',
+	'webgpu_custom_fog',
 
 	// WebGPU idleTime and parseTime too low
 	'webgpu_compute_particles',