Mr.doob 9 months ago
parent
commit
a9215ec000
100 changed files with 4168 additions and 1364 deletions
  1. 138 72
      build/three.cjs
  2. 122 65
      build/three.core.js
  3. 0 0
      build/three.core.min.js
  4. 16 7
      build/three.module.js
  5. 0 0
      build/three.module.min.js
  6. 8 8
      build/three.tsl.js
  7. 0 0
      build/three.tsl.min.js
  8. 0 0
      build/three.webgpu.js
  9. 0 0
      build/three.webgpu.min.js
  10. 0 0
      build/three.webgpu.nodes.js
  11. 0 0
      build/three.webgpu.nodes.min.js
  12. 39 18
      docs/api/ar/core/GLBufferAttribute.html
  13. 39 19
      docs/api/en/core/GLBufferAttribute.html
  14. 36 16
      docs/api/it/core/GLBufferAttribute.html
  15. 36 16
      docs/api/ko/core/GLBufferAttribute.html
  16. 36 16
      docs/api/zh/core/GLBufferAttribute.html
  17. 8 8
      docs/scenes/material-browser.html
  18. 1 1
      editor/js/libs/jsonlint.js
  19. 5 5
      examples/files.json
  20. 0 27
      examples/jsm/capabilities/WebGL.js
  21. 1 1
      examples/jsm/controls/ArcballControls.js
  22. 61 14
      examples/jsm/controls/TransformControls.js
  23. 1 1
      examples/jsm/geometries/RoundedBoxGeometry.js
  24. 1 1
      examples/jsm/loaders/ColladaLoader.js
  25. 2 2
      examples/jsm/loaders/FBXLoader.js
  26. 81 11
      examples/jsm/physics/RapierPhysics.js
  27. 381 30
      examples/jsm/transpiler/AST.js
  28. 225 85
      examples/jsm/transpiler/GLSLDecoder.js
  29. 327 0
      examples/jsm/transpiler/Linker.js
  30. 197 92
      examples/jsm/transpiler/TSLEncoder.js
  31. 17 1
      examples/jsm/transpiler/Transpiler.js
  32. 29 0
      examples/jsm/transpiler/TranspilerUtils.js
  33. 788 0
      examples/jsm/transpiler/WGSLEncoder.js
  34. 206 0
      examples/jsm/tsl/display/ChromaticAberrationNode.js
  35. 3 3
      examples/jsm/tsl/display/GaussianBlurNode.js
  36. 2 2
      examples/jsm/tsl/display/SSAAPassNode.js
  37. 3 3
      examples/jsm/tsl/display/hashBlur.js
  38. 1 1
      examples/misc_exporter_usdz.html
  39. BIN
      examples/models/gltf/pool.glb
  40. 20 3
      examples/physics_rapier_basic.html
  41. BIN
      examples/screenshots/webgl_batch_lod_bvh.jpg
  42. BIN
      examples/screenshots/webgl_buffergeometry_glbufferattribute.jpg
  43. BIN
      examples/screenshots/webgl_geometries.jpg
  44. BIN
      examples/screenshots/webgl_geometries_parametric.jpg
  45. BIN
      examples/screenshots/webgl_loader_obj.jpg
  46. BIN
      examples/screenshots/webgl_loader_obj_mtl.jpg
  47. BIN
      examples/screenshots/webgl_water.jpg
  48. BIN
      examples/screenshots/webgl_water_flowmap.jpg
  49. BIN
      examples/screenshots/webgpu_compute_birds.jpg
  50. BIN
      examples/screenshots/webgpu_custom_fog_background.jpg
  51. BIN
      examples/screenshots/webgpu_instance_path.jpg
  52. BIN
      examples/screenshots/webgpu_materials_toon.jpg
  53. BIN
      examples/screenshots/webgpu_postprocessing_ao.jpg
  54. BIN
      examples/screenshots/webgpu_postprocessing_ca.jpg
  55. BIN
      examples/screenshots/webgpu_reflection.jpg
  56. BIN
      examples/screenshots/webgpu_reflection_roughness.jpg
  57. BIN
      examples/screenshots/webgpu_sandbox.jpg
  58. BIN
      examples/screenshots/webgpu_skinning_instancing.jpg
  59. BIN
      examples/screenshots/webgpu_water.jpg
  60. 2 1
      examples/tags.json
  61. BIN
      examples/textures/equirectangular/moonless_golf_2k.hdr.jpg
  62. 280 0
      examples/webgl_batch_lod_bvh.html
  63. 4 3
      examples/webgl_buffergeometry_glbufferattribute.html
  64. 45 17
      examples/webgl_geometries.html
  65. 0 144
      examples/webgl_geometries_parametric.html
  66. 2 1
      examples/webgl_geometry_colors.html
  67. 13 0
      examples/webgl_geometry_extrude_shapes.html
  68. 20 51
      examples/webgl_loader_obj.html
  69. 0 125
      examples/webgl_loader_obj_mtl.html
  70. 11 11
      examples/webgl_loader_texture_lottie.html
  71. 2 0
      examples/webgl_materials_blending.html
  72. 1 1
      examples/webgl_materials_car.html
  73. 1 1
      examples/webgl_materials_envmaps_groundprojected.html
  74. 0 202
      examples/webgl_water.html
  75. 0 139
      examples/webgl_water_flowmap.html
  76. 3 3
      examples/webgpu_animation_retargeting.html
  77. 6 6
      examples/webgpu_centroid_sampling.html
  78. 4 4
      examples/webgpu_compute_birds.html
  79. 10 4
      examples/webgpu_compute_cloth.html
  80. 0 6
      examples/webgpu_compute_water.html
  81. 171 0
      examples/webgpu_instance_path.html
  82. 2 2
      examples/webgpu_mesh_batch.html
  83. 2 2
      examples/webgpu_mrt.html
  84. 2 2
      examples/webgpu_multiple_rendertargets_readback.html
  85. 2 2
      examples/webgpu_pmrem_cubemap.html
  86. 2 2
      examples/webgpu_pmrem_equirectangular.html
  87. 334 0
      examples/webgpu_postprocessing_ca.html
  88. 22 3
      examples/webgpu_postprocessing_ssr.html
  89. 2 2
      examples/webgpu_reflection.html
  90. 167 0
      examples/webgpu_reflection_roughness.html
  91. 0 1
      examples/webgpu_shadertoy.html
  92. 14 2
      examples/webgpu_skinning_points.html
  93. 3 3
      examples/webgpu_tsl_earth.html
  94. 2 2
      examples/webgpu_tsl_editor.html
  95. 2 4
      examples/webgpu_tsl_halftone.html
  96. 88 11
      examples/webgpu_tsl_transpiler.html
  97. 2 2
      examples/webgpu_tsl_vfx_flames.html
  98. 2 2
      examples/webgpu_tsl_vfx_linkedparticles.html
  99. 5 5
      examples/webgpu_tsl_vfx_tornado.html
  100. 110 70
      examples/webgpu_water.html

+ 138 - 72
build/three.cjs

@@ -5,7 +5,7 @@
  */
 'use strict';
 
-const REVISION = '177';
+const REVISION = '178';
 
 /**
  * Represents mouse buttons and interaction types in context of controls.
@@ -1619,8 +1619,8 @@ const InterpolationSamplingMode = {
 	NORMAL: 'normal',
 	CENTROID: 'centroid',
 	SAMPLE: 'sample',
-	FLAT_FIRST: 'flat first',
-	FLAT_EITHER: 'flat either'
+	FIRST: 'first',
+	EITHER: 'either'
 };
 
 /**
@@ -3869,7 +3869,7 @@ class Quaternion {
 
 		let r = vFrom.dot( vTo ) + 1;
 
-		if ( r < Number.EPSILON ) {
+		if ( r < 1e-8 ) { // the epsilon value has been discussed in #31286
 
 			// vFrom and vTo point in opposite directions
 
@@ -13190,7 +13190,7 @@ const _removedEvent = { type: 'removed' };
 const _childaddedEvent = { type: 'childadded', child: null };
 
 /**
- * Fires when a new child object has been added.
+ * Fires when a child object has been removed.
  *
  * @event Object3D#childremoved
  * @type {Object}
@@ -17786,7 +17786,7 @@ class BufferAttribute {
 		/**
 		 * Applies to integer data only. Indicates how the underlying data in the buffer maps to
 		 * the values in the GLSL code. For instance, if `array` is an instance of `UInt16Array`,
-		 * and `normalized` is `true`, the values `0 -+65535` in the array data will be mapped to
+		 * and `normalized` is `true`, the values `0 - +65535` in the array data will be mapped to
 		 * `0.0f - +1.0f` in the GLSL attribute. If `normalized` is `false`, the values will be converted
 		 * to floats unmodified, i.e. `65535` becomes `65535.0f`.
 		 *
@@ -18539,8 +18539,8 @@ class Uint32BufferAttribute extends BufferAttribute {
  * Convenient class that can be used when creating a `Float16` buffer attribute with
  * a plain `Array` instance.
  *
- * This class automatically converts to and from FP16 since `Float16Array` is not
- * natively supported in JavaScript.
+ * This class automatically converts to and from FP16 via `Uint16Array` since `Float16Array`
+ * browser support is still problematic.
  *
  * @augments BufferAttribute
  */
@@ -26306,6 +26306,7 @@ class Plane {
 }
 
 const _sphere$3 = /*@__PURE__*/ new Sphere();
+const _defaultSpriteCenter = /*@__PURE__*/ new Vector2( 0.5, 0.5 );
 const _vector$6 = /*@__PURE__*/ new Vector3();
 
 /**
@@ -26463,7 +26464,10 @@ class Frustum {
 	intersectsSprite( sprite ) {
 
 		_sphere$3.center.set( 0, 0, 0 );
-		_sphere$3.radius = 0.7071067811865476;
+
+		const offset = _defaultSpriteCenter.distanceTo( sprite.center );
+
+		_sphere$3.radius = 0.7071067811865476 + offset;
 		_sphere$3.applyMatrix4( sprite.matrixWorld );
 
 		return this.intersectsSphere( _sphere$3 );
@@ -33896,11 +33900,11 @@ class Path extends CurvePath {
 	 * Adds an arc as an instance of {@link EllipseCurve} to the path, positioned relative
 	 * to the current point.
 	 *
-	 * @param {number} aX - The x coordinate of the center of the arc offsetted from the previous curve.
-	 * @param {number} aY - The y coordinate of the center of the arc offsetted from the previous curve.
-	 * @param {number} aRadius - The radius of the arc.
-	 * @param {number} aStartAngle - The start angle in radians.
-	 * @param {number} aEndAngle - The end angle in radians.
+	 * @param {number} [aX=0] - The x coordinate of the center of the arc offsetted from the previous curve.
+	 * @param {number} [aY=0] - The y coordinate of the center of the arc offsetted from the previous curve.
+	 * @param {number} [aRadius=1] - The radius of the arc.
+	 * @param {number} [aStartAngle=0] - The start angle in radians.
+	 * @param {number} [aEndAngle=Math.PI*2] - The end angle in radians.
 	 * @param {boolean} [aClockwise=false] - Whether to sweep the arc clockwise or not.
 	 * @return {Path} A reference to this path.
 	 */
@@ -33919,11 +33923,11 @@ class Path extends CurvePath {
 	/**
 	 * Adds an absolutely positioned arc as an instance of {@link EllipseCurve} to the path.
 	 *
-	 * @param {number} aX - The x coordinate of the center of the arc.
-	 * @param {number} aY - The y coordinate of the center of the arc.
-	 * @param {number} aRadius - The radius of the arc.
-	 * @param {number} aStartAngle - The start angle in radians.
-	 * @param {number} aEndAngle - The end angle in radians.
+	 * @param {number} [aX=0] - The x coordinate of the center of the arc.
+	 * @param {number} [aY=0] - The y coordinate of the center of the arc.
+	 * @param {number} [aRadius=1] - The radius of the arc.
+	 * @param {number} [aStartAngle=0] - The start angle in radians.
+	 * @param {number} [aEndAngle=Math.PI*2] - The end angle in radians.
 	 * @param {boolean} [aClockwise=false] - Whether to sweep the arc clockwise or not.
 	 * @return {Path} A reference to this path.
 	 */
@@ -33939,12 +33943,12 @@ class Path extends CurvePath {
 	 * Adds an ellipse as an instance of {@link EllipseCurve} to the path, positioned relative
 	 * to the current point
 	 *
-	 * @param {number} aX - The x coordinate of the center of the ellipse offsetted from the previous curve.
-	 * @param {number} aY - The y coordinate of the center of the ellipse offsetted from the previous curve.
-	 * @param {number} xRadius - The radius of the ellipse in the x axis.
-	 * @param {number} yRadius - The radius of the ellipse in the y axis.
-	 * @param {number} aStartAngle - The start angle in radians.
-	 * @param {number} aEndAngle - The end angle in radians.
+	 * @param {number} [aX=0] - The x coordinate of the center of the ellipse offsetted from the previous curve.
+	 * @param {number} [aY=0] - The y coordinate of the center of the ellipse offsetted from the previous curve.
+	 * @param {number} [xRadius=1] - The radius of the ellipse in the x axis.
+	 * @param {number} [yRadius=1] - The radius of the ellipse in the y axis.
+	 * @param {number} [aStartAngle=0] - The start angle in radians.
+	 * @param {number} [aEndAngle=Math.PI*2] - The end angle in radians.
 	 * @param {boolean} [aClockwise=false] - Whether to sweep the ellipse clockwise or not.
 	 * @param {number} [aRotation=0] - The rotation angle of the ellipse in radians, counterclockwise from the positive X axis.
 	 * @return {Path} A reference to this path.
@@ -33963,12 +33967,12 @@ class Path extends CurvePath {
 	/**
 	 * Adds an absolutely positioned ellipse as an instance of {@link EllipseCurve} to the path.
 	 *
-	 * @param {number} aX - The x coordinate of the absolute center of the ellipse.
-	 * @param {number} aY - The y coordinate of the absolute center of the ellipse.
-	 * @param {number} xRadius - The radius of the ellipse in the x axis.
-	 * @param {number} yRadius - The radius of the ellipse in the y axis.
-	 * @param {number} aStartAngle - The start angle in radians.
-	 * @param {number} aEndAngle - The end angle in radians.
+	 * @param {number} [aX=0] - The x coordinate of the absolute center of the ellipse.
+	 * @param {number} [aY=0] - The y coordinate of the absolute center of the ellipse.
+	 * @param {number} [xRadius=1] - The radius of the ellipse in the x axis.
+	 * @param {number} [yRadius=1] - The radius of the ellipse in the y axis.
+	 * @param {number} [aStartAngle=0] - The start angle in radians.
+	 * @param {number} [aEndAngle=Math.PI*2] - The end angle in radians.
 	 * @param {boolean} [aClockwise=false] - Whether to sweep the ellipse clockwise or not.
 	 * @param {number} [aRotation=0] - The rotation angle of the ellipse in radians, counterclockwise from the positive X axis.
 	 * @return {Path} A reference to this path.
@@ -43788,7 +43792,7 @@ class FileLoader extends Loader {
 
 		url = this.manager.resolveURL( url );
 
-		const cached = Cache.get( url );
+		const cached = Cache.get( `file:${url}` );
 
 		if ( cached !== undefined ) {
 
@@ -43977,7 +43981,7 @@ class FileLoader extends Loader {
 
 				// Add to cache only on HTTP success, so that we do not cache
 				// error response bodies as proper responses to requests.
-				Cache.add( url, data );
+				Cache.add( `file:${url}`, data );
 
 				const callbacks = loading[ url ];
 				delete loading[ url ];
@@ -44293,6 +44297,8 @@ class CompressedTextureLoader extends Loader {
 
 }
 
+const _loading = new WeakMap();
+
 /**
  * A loader for loading images. The class loads images with the HTML `Image` API.
  *
@@ -44339,19 +44345,36 @@ class ImageLoader extends Loader {
 
 		const scope = this;
 
-		const cached = Cache.get( url );
+		const cached = Cache.get( `image:${url}` );
 
 		if ( cached !== undefined ) {
 
-			scope.manager.itemStart( url );
+			if ( cached.complete === true ) {
 
-			setTimeout( function () {
+				scope.manager.itemStart( url );
 
-				if ( onLoad ) onLoad( cached );
+				setTimeout( function () {
 
-				scope.manager.itemEnd( url );
+					if ( onLoad ) onLoad( cached );
 
-			}, 0 );
+					scope.manager.itemEnd( url );
+
+				}, 0 );
+
+			} else {
+
+				let arr = _loading.get( cached );
+
+				if ( arr === undefined ) {
+
+					arr = [];
+					_loading.set( cached, arr );
+
+				}
+
+				arr.push( { onLoad, onError } );
+
+			}
 
 			return cached;
 
@@ -44363,10 +44386,21 @@ class ImageLoader extends Loader {
 
 			removeEventListeners();
 
-			Cache.add( url, this );
-
 			if ( onLoad ) onLoad( this );
 
+			//
+
+			const callbacks = _loading.get( this ) || [];
+
+			for ( let i = 0; i < callbacks.length; i ++ ) {
+
+				const callback = callbacks[ i ];
+				if ( callback.onLoad ) callback.onLoad( this );
+
+			}
+
+			_loading.delete( this );
+
 			scope.manager.itemEnd( url );
 
 		}
@@ -44377,6 +44411,22 @@ class ImageLoader extends Loader {
 
 			if ( onError ) onError( event );
 
+			Cache.remove( `image:${url}` );
+
+			//
+
+			const callbacks = _loading.get( this ) || [];
+
+			for ( let i = 0; i < callbacks.length; i ++ ) {
+
+				const callback = callbacks[ i ];
+				if ( callback.onError ) callback.onError( event );
+
+			}
+
+			_loading.delete( this );
+
+
 			scope.manager.itemError( url );
 			scope.manager.itemEnd( url );
 
@@ -44398,6 +44448,7 @@ class ImageLoader extends Loader {
 
 		}
 
+		Cache.add( `image:${url}`, image );
 		scope.manager.itemStart( url );
 
 		image.src = url;
@@ -48640,7 +48691,7 @@ class ImageBitmapLoader extends Loader {
 
 		const scope = this;
 
-		const cached = Cache.get( url );
+		const cached = Cache.get( `image-bitmap:${url}` );
 
 		if ( cached !== undefined ) {
 
@@ -48703,7 +48754,7 @@ class ImageBitmapLoader extends Loader {
 
 		} ).then( function ( imageBitmap ) {
 
-			Cache.add( url, imageBitmap );
+			Cache.add( `image-bitmap:${url}`, imageBitmap );
 
 			if ( onLoad ) onLoad( imageBitmap );
 
@@ -48717,14 +48768,14 @@ class ImageBitmapLoader extends Loader {
 
 			_errorMap.set( promise, e );
 
-			Cache.remove( url );
+			Cache.remove( `image-bitmap:${url}` );
 
 			scope.manager.itemError( url );
 			scope.manager.itemEnd( url );
 
 		} );
 
-		Cache.add( url, promise );
+		Cache.add( `image-bitmap:${url}`, promise );
 		scope.manager.itemStart( url );
 
 	}
@@ -49116,7 +49167,7 @@ class Clock {
 	 */
 	start() {
 
-		this.startTime = now();
+		this.startTime = performance.now();
 
 		this.oldTime = this.startTime;
 		this.elapsedTime = 0;
@@ -49165,7 +49216,7 @@ class Clock {
 
 		if ( this.running ) {
 
-			const newTime = now();
+			const newTime = performance.now();
 
 			diff = ( newTime - this.oldTime ) / 1000;
 			this.oldTime = newTime;
@@ -49180,12 +49231,6 @@ class Clock {
 
 }
 
-function now() {
-
-	return performance.now();
-
-}
-
 const _position$1 = /*@__PURE__*/ new Vector3();
 const _quaternion$1 = /*@__PURE__*/ new Quaternion();
 const _scale$1 = /*@__PURE__*/ new Vector3();
@@ -54221,8 +54266,9 @@ class GLBufferAttribute {
 	 * @param {number} itemSize - The item size.
 	 * @param {number} elementSize - The corresponding size (in bytes) for the given `type` parameter.
 	 * @param {number} count - The expected number of vertices in VBO.
+	 * @param {boolean} [normalized=false] - Whether the data are normalized or not.
 	 */
-	constructor( buffer, type, itemSize, elementSize, count ) {
+	constructor( buffer, type, itemSize, elementSize, count, normalized = false ) {
 
 		/**
 		 * This flag can be used for type testing.
@@ -54275,6 +54321,17 @@ class GLBufferAttribute {
 		 */
 		this.count = count;
 
+		/**
+		 * Applies to integer data only. Indicates how the underlying data in the buffer maps to
+		 * the values in the GLSL code. For instance, if `buffer` contains data of `gl.UNSIGNED_SHORT`,
+		 * and `normalized` is `true`, the values `0 - +65535` in the buffer data will be mapped to
+		 * `0.0f - +1.0f` in the GLSL attribute. If `normalized` is `false`, the values will be converted
+		 * to floats unmodified, i.e. `65535` becomes `65535.0f`.
+		 *
+		 * @type {boolean}
+		 */
+		this.normalized = normalized;
+
 		/**
 		 * A version number, incremented every time the `needsUpdate` is set to `true`.
 		 *
@@ -56690,34 +56747,34 @@ class CameraHelper extends LineSegments {
 
 		// near
 
-		setPoint( 'n1', pointMap, geometry, _camera, -1, -1, nearZ );
-		setPoint( 'n2', pointMap, geometry, _camera, w, -1, nearZ );
-		setPoint( 'n3', pointMap, geometry, _camera, -1, h, nearZ );
+		setPoint( 'n1', pointMap, geometry, _camera, - w, - h, nearZ );
+		setPoint( 'n2', pointMap, geometry, _camera, w, - h, nearZ );
+		setPoint( 'n3', pointMap, geometry, _camera, - w, h, nearZ );
 		setPoint( 'n4', pointMap, geometry, _camera, w, h, nearZ );
 
 		// far
 
-		setPoint( 'f1', pointMap, geometry, _camera, -1, -1, 1 );
-		setPoint( 'f2', pointMap, geometry, _camera, w, -1, 1 );
-		setPoint( 'f3', pointMap, geometry, _camera, -1, h, 1 );
+		setPoint( 'f1', pointMap, geometry, _camera, - w, - h, 1 );
+		setPoint( 'f2', pointMap, geometry, _camera, w, - h, 1 );
+		setPoint( 'f3', pointMap, geometry, _camera, - w, h, 1 );
 		setPoint( 'f4', pointMap, geometry, _camera, w, h, 1 );
 
 		// up
 
 		setPoint( 'u1', pointMap, geometry, _camera, w * 0.7, h * 1.1, nearZ );
-		setPoint( 'u2', pointMap, geometry, _camera, -1 * 0.7, h * 1.1, nearZ );
+		setPoint( 'u2', pointMap, geometry, _camera, - w * 0.7, h * 1.1, nearZ );
 		setPoint( 'u3', pointMap, geometry, _camera, 0, h * 2, nearZ );
 
 		// cross
 
-		setPoint( 'cf1', pointMap, geometry, _camera, -1, 0, 1 );
+		setPoint( 'cf1', pointMap, geometry, _camera, - w, 0, 1 );
 		setPoint( 'cf2', pointMap, geometry, _camera, w, 0, 1 );
-		setPoint( 'cf3', pointMap, geometry, _camera, 0, -1, 1 );
+		setPoint( 'cf3', pointMap, geometry, _camera, 0, - h, 1 );
 		setPoint( 'cf4', pointMap, geometry, _camera, 0, h, 1 );
 
-		setPoint( 'cn1', pointMap, geometry, _camera, -1, 0, nearZ );
+		setPoint( 'cn1', pointMap, geometry, _camera, - w, 0, nearZ );
 		setPoint( 'cn2', pointMap, geometry, _camera, w, 0, nearZ );
-		setPoint( 'cn3', pointMap, geometry, _camera, 0, -1, nearZ );
+		setPoint( 'cn3', pointMap, geometry, _camera, 0, - h, nearZ );
 		setPoint( 'cn4', pointMap, geometry, _camera, 0, h, nearZ );
 
 		geometry.getAttribute( 'position' ).needsUpdate = true;
@@ -58174,6 +58231,10 @@ function WebGLAttributes( gl ) {
 
 			type = gl.FLOAT;
 
+		} else if ( typeof Float16Array !== 'undefined' && array instanceof Float16Array ) {
+
+			type = gl.HALF_FLOAT;
+
 		} else if ( array instanceof Uint16Array ) {
 
 			if ( attribute.isFloat16BufferAttribute ) {
@@ -65151,7 +65212,7 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities
 			useFog: material.fog === true,
 			fogExp2: ( !! fog && fog.isFogExp2 ),
 
-			flatShading: material.flatShading === true,
+			flatShading: ( material.flatShading === true && material.wireframe === false ),
 
 			sizeAttenuation: material.sizeAttenuation === true,
 			logarithmicDepthBuffer: logarithmicDepthBuffer,
@@ -65364,6 +65425,8 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities
 			_programLayers.enable( 20 );
 		if ( parameters.batchingColor )
 			_programLayers.enable( 21 );
+		if ( parameters.gradientMap )
+			_programLayers.enable( 22 );
 
 		array.push( _programLayers.mask );
 		_programLayers.disableAll();
@@ -67545,7 +67608,7 @@ function WebGLState( gl, extensions ) {
 							break;
 
 						case MultiplyBlending:
-							gl.blendFuncSeparate( gl.ZERO, gl.SRC_COLOR, gl.ZERO, gl.SRC_ALPHA );
+							gl.blendFuncSeparate( gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA, gl.ZERO, gl.ONE );
 							break;
 
 						default:
@@ -67563,15 +67626,15 @@ function WebGLState( gl, extensions ) {
 							break;
 
 						case AdditiveBlending:
-							gl.blendFunc( gl.SRC_ALPHA, gl.ONE );
+							gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE, gl.ONE, gl.ONE );
 							break;
 
 						case SubtractiveBlending:
-							gl.blendFuncSeparate( gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ZERO, gl.ONE );
+							console.error( 'THREE.WebGLState: SubtractiveBlending requires material.premultipliedAlpha = true' );
 							break;
 
 						case MultiplyBlending:
-							gl.blendFunc( gl.ZERO, gl.SRC_COLOR );
+							console.error( 'THREE.WebGLState: MultiplyBlending requires material.premultipliedAlpha = true' );
 							break;
 
 						default:
@@ -74706,6 +74769,9 @@ class WebGLRenderer {
 			//
 
 			const currentRenderTarget = _this.getRenderTarget();
+			const currentActiveCubeFace = _this.getActiveCubeFace();
+			const currentActiveMipmapLevel = _this.getActiveMipmapLevel();
+
 			_this.setRenderTarget( transmissionRenderTarget );
 
 			_this.getClearColor( _currentClearColor );
@@ -74775,7 +74841,7 @@ class WebGLRenderer {
 
 			}
 
-			_this.setRenderTarget( currentRenderTarget );
+			_this.setRenderTarget( currentRenderTarget, currentActiveCubeFace, currentActiveMipmapLevel );
 
 			_this.setClearColor( _currentClearColor, _currentClearAlpha );
 
@@ -75705,7 +75771,7 @@ class WebGLRenderer {
 
 					if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) {
 
-						// when using MRT, select the corect color buffer for the subsequent read command
+						// when using MRT, select the correct color buffer for the subsequent read command
 
 						if ( renderTarget.textures.length > 1 ) _gl.readBuffer( _gl.COLOR_ATTACHMENT0 + textureIndex );
 

+ 122 - 65
build/three.core.js

@@ -3,7 +3,7 @@
  * Copyright 2010-2025 Three.js Authors
  * SPDX-License-Identifier: MIT
  */
-const REVISION = '177';
+const REVISION = '178';
 
 /**
  * Represents mouse buttons and interaction types in context of controls.
@@ -1617,8 +1617,8 @@ const InterpolationSamplingMode = {
 	NORMAL: 'normal',
 	CENTROID: 'centroid',
 	SAMPLE: 'sample',
-	FLAT_FIRST: 'flat first',
-	FLAT_EITHER: 'flat either'
+	FIRST: 'first',
+	EITHER: 'either'
 };
 
 /**
@@ -3867,7 +3867,7 @@ class Quaternion {
 
 		let r = vFrom.dot( vTo ) + 1;
 
-		if ( r < Number.EPSILON ) {
+		if ( r < 1e-8 ) { // the epsilon value has been discussed in #31286
 
 			// vFrom and vTo point in opposite directions
 
@@ -13188,7 +13188,7 @@ const _removedEvent = { type: 'removed' };
 const _childaddedEvent = { type: 'childadded', child: null };
 
 /**
- * Fires when a new child object has been added.
+ * Fires when a child object has been removed.
  *
  * @event Object3D#childremoved
  * @type {Object}
@@ -17784,7 +17784,7 @@ class BufferAttribute {
 		/**
 		 * Applies to integer data only. Indicates how the underlying data in the buffer maps to
 		 * the values in the GLSL code. For instance, if `array` is an instance of `UInt16Array`,
-		 * and `normalized` is `true`, the values `0 -+65535` in the array data will be mapped to
+		 * and `normalized` is `true`, the values `0 - +65535` in the array data will be mapped to
 		 * `0.0f - +1.0f` in the GLSL attribute. If `normalized` is `false`, the values will be converted
 		 * to floats unmodified, i.e. `65535` becomes `65535.0f`.
 		 *
@@ -18537,8 +18537,8 @@ class Uint32BufferAttribute extends BufferAttribute {
  * Convenient class that can be used when creating a `Float16` buffer attribute with
  * a plain `Array` instance.
  *
- * This class automatically converts to and from FP16 since `Float16Array` is not
- * natively supported in JavaScript.
+ * This class automatically converts to and from FP16 via `Uint16Array` since `Float16Array`
+ * browser support is still problematic.
  *
  * @augments BufferAttribute
  */
@@ -26304,6 +26304,7 @@ class Plane {
 }
 
 const _sphere$3 = /*@__PURE__*/ new Sphere();
+const _defaultSpriteCenter = /*@__PURE__*/ new Vector2( 0.5, 0.5 );
 const _vector$6 = /*@__PURE__*/ new Vector3();
 
 /**
@@ -26461,7 +26462,10 @@ class Frustum {
 	intersectsSprite( sprite ) {
 
 		_sphere$3.center.set( 0, 0, 0 );
-		_sphere$3.radius = 0.7071067811865476;
+
+		const offset = _defaultSpriteCenter.distanceTo( sprite.center );
+
+		_sphere$3.radius = 0.7071067811865476 + offset;
 		_sphere$3.applyMatrix4( sprite.matrixWorld );
 
 		return this.intersectsSphere( _sphere$3 );
@@ -33894,11 +33898,11 @@ class Path extends CurvePath {
 	 * Adds an arc as an instance of {@link EllipseCurve} to the path, positioned relative
 	 * to the current point.
 	 *
-	 * @param {number} aX - The x coordinate of the center of the arc offsetted from the previous curve.
-	 * @param {number} aY - The y coordinate of the center of the arc offsetted from the previous curve.
-	 * @param {number} aRadius - The radius of the arc.
-	 * @param {number} aStartAngle - The start angle in radians.
-	 * @param {number} aEndAngle - The end angle in radians.
+	 * @param {number} [aX=0] - The x coordinate of the center of the arc offsetted from the previous curve.
+	 * @param {number} [aY=0] - The y coordinate of the center of the arc offsetted from the previous curve.
+	 * @param {number} [aRadius=1] - The radius of the arc.
+	 * @param {number} [aStartAngle=0] - The start angle in radians.
+	 * @param {number} [aEndAngle=Math.PI*2] - The end angle in radians.
 	 * @param {boolean} [aClockwise=false] - Whether to sweep the arc clockwise or not.
 	 * @return {Path} A reference to this path.
 	 */
@@ -33917,11 +33921,11 @@ class Path extends CurvePath {
 	/**
 	 * Adds an absolutely positioned arc as an instance of {@link EllipseCurve} to the path.
 	 *
-	 * @param {number} aX - The x coordinate of the center of the arc.
-	 * @param {number} aY - The y coordinate of the center of the arc.
-	 * @param {number} aRadius - The radius of the arc.
-	 * @param {number} aStartAngle - The start angle in radians.
-	 * @param {number} aEndAngle - The end angle in radians.
+	 * @param {number} [aX=0] - The x coordinate of the center of the arc.
+	 * @param {number} [aY=0] - The y coordinate of the center of the arc.
+	 * @param {number} [aRadius=1] - The radius of the arc.
+	 * @param {number} [aStartAngle=0] - The start angle in radians.
+	 * @param {number} [aEndAngle=Math.PI*2] - The end angle in radians.
 	 * @param {boolean} [aClockwise=false] - Whether to sweep the arc clockwise or not.
 	 * @return {Path} A reference to this path.
 	 */
@@ -33937,12 +33941,12 @@ class Path extends CurvePath {
 	 * Adds an ellipse as an instance of {@link EllipseCurve} to the path, positioned relative
 	 * to the current point
 	 *
-	 * @param {number} aX - The x coordinate of the center of the ellipse offsetted from the previous curve.
-	 * @param {number} aY - The y coordinate of the center of the ellipse offsetted from the previous curve.
-	 * @param {number} xRadius - The radius of the ellipse in the x axis.
-	 * @param {number} yRadius - The radius of the ellipse in the y axis.
-	 * @param {number} aStartAngle - The start angle in radians.
-	 * @param {number} aEndAngle - The end angle in radians.
+	 * @param {number} [aX=0] - The x coordinate of the center of the ellipse offsetted from the previous curve.
+	 * @param {number} [aY=0] - The y coordinate of the center of the ellipse offsetted from the previous curve.
+	 * @param {number} [xRadius=1] - The radius of the ellipse in the x axis.
+	 * @param {number} [yRadius=1] - The radius of the ellipse in the y axis.
+	 * @param {number} [aStartAngle=0] - The start angle in radians.
+	 * @param {number} [aEndAngle=Math.PI*2] - The end angle in radians.
 	 * @param {boolean} [aClockwise=false] - Whether to sweep the ellipse clockwise or not.
 	 * @param {number} [aRotation=0] - The rotation angle of the ellipse in radians, counterclockwise from the positive X axis.
 	 * @return {Path} A reference to this path.
@@ -33961,12 +33965,12 @@ class Path extends CurvePath {
 	/**
 	 * Adds an absolutely positioned ellipse as an instance of {@link EllipseCurve} to the path.
 	 *
-	 * @param {number} aX - The x coordinate of the absolute center of the ellipse.
-	 * @param {number} aY - The y coordinate of the absolute center of the ellipse.
-	 * @param {number} xRadius - The radius of the ellipse in the x axis.
-	 * @param {number} yRadius - The radius of the ellipse in the y axis.
-	 * @param {number} aStartAngle - The start angle in radians.
-	 * @param {number} aEndAngle - The end angle in radians.
+	 * @param {number} [aX=0] - The x coordinate of the absolute center of the ellipse.
+	 * @param {number} [aY=0] - The y coordinate of the absolute center of the ellipse.
+	 * @param {number} [xRadius=1] - The radius of the ellipse in the x axis.
+	 * @param {number} [yRadius=1] - The radius of the ellipse in the y axis.
+	 * @param {number} [aStartAngle=0] - The start angle in radians.
+	 * @param {number} [aEndAngle=Math.PI*2] - The end angle in radians.
 	 * @param {boolean} [aClockwise=false] - Whether to sweep the ellipse clockwise or not.
 	 * @param {number} [aRotation=0] - The rotation angle of the ellipse in radians, counterclockwise from the positive X axis.
 	 * @return {Path} A reference to this path.
@@ -43786,7 +43790,7 @@ class FileLoader extends Loader {
 
 		url = this.manager.resolveURL( url );
 
-		const cached = Cache.get( url );
+		const cached = Cache.get( `file:${url}` );
 
 		if ( cached !== undefined ) {
 
@@ -43975,7 +43979,7 @@ class FileLoader extends Loader {
 
 				// Add to cache only on HTTP success, so that we do not cache
 				// error response bodies as proper responses to requests.
-				Cache.add( url, data );
+				Cache.add( `file:${url}`, data );
 
 				const callbacks = loading[ url ];
 				delete loading[ url ];
@@ -44291,6 +44295,8 @@ class CompressedTextureLoader extends Loader {
 
 }
 
+const _loading = new WeakMap();
+
 /**
  * A loader for loading images. The class loads images with the HTML `Image` API.
  *
@@ -44337,19 +44343,36 @@ class ImageLoader extends Loader {
 
 		const scope = this;
 
-		const cached = Cache.get( url );
+		const cached = Cache.get( `image:${url}` );
 
 		if ( cached !== undefined ) {
 
-			scope.manager.itemStart( url );
+			if ( cached.complete === true ) {
 
-			setTimeout( function () {
+				scope.manager.itemStart( url );
 
-				if ( onLoad ) onLoad( cached );
+				setTimeout( function () {
 
-				scope.manager.itemEnd( url );
+					if ( onLoad ) onLoad( cached );
 
-			}, 0 );
+					scope.manager.itemEnd( url );
+
+				}, 0 );
+
+			} else {
+
+				let arr = _loading.get( cached );
+
+				if ( arr === undefined ) {
+
+					arr = [];
+					_loading.set( cached, arr );
+
+				}
+
+				arr.push( { onLoad, onError } );
+
+			}
 
 			return cached;
 
@@ -44361,10 +44384,21 @@ class ImageLoader extends Loader {
 
 			removeEventListeners();
 
-			Cache.add( url, this );
-
 			if ( onLoad ) onLoad( this );
 
+			//
+
+			const callbacks = _loading.get( this ) || [];
+
+			for ( let i = 0; i < callbacks.length; i ++ ) {
+
+				const callback = callbacks[ i ];
+				if ( callback.onLoad ) callback.onLoad( this );
+
+			}
+
+			_loading.delete( this );
+
 			scope.manager.itemEnd( url );
 
 		}
@@ -44375,6 +44409,22 @@ class ImageLoader extends Loader {
 
 			if ( onError ) onError( event );
 
+			Cache.remove( `image:${url}` );
+
+			//
+
+			const callbacks = _loading.get( this ) || [];
+
+			for ( let i = 0; i < callbacks.length; i ++ ) {
+
+				const callback = callbacks[ i ];
+				if ( callback.onError ) callback.onError( event );
+
+			}
+
+			_loading.delete( this );
+
+
 			scope.manager.itemError( url );
 			scope.manager.itemEnd( url );
 
@@ -44396,6 +44446,7 @@ class ImageLoader extends Loader {
 
 		}
 
+		Cache.add( `image:${url}`, image );
 		scope.manager.itemStart( url );
 
 		image.src = url;
@@ -48638,7 +48689,7 @@ class ImageBitmapLoader extends Loader {
 
 		const scope = this;
 
-		const cached = Cache.get( url );
+		const cached = Cache.get( `image-bitmap:${url}` );
 
 		if ( cached !== undefined ) {
 
@@ -48701,7 +48752,7 @@ class ImageBitmapLoader extends Loader {
 
 		} ).then( function ( imageBitmap ) {
 
-			Cache.add( url, imageBitmap );
+			Cache.add( `image-bitmap:${url}`, imageBitmap );
 
 			if ( onLoad ) onLoad( imageBitmap );
 
@@ -48715,14 +48766,14 @@ class ImageBitmapLoader extends Loader {
 
 			_errorMap.set( promise, e );
 
-			Cache.remove( url );
+			Cache.remove( `image-bitmap:${url}` );
 
 			scope.manager.itemError( url );
 			scope.manager.itemEnd( url );
 
 		} );
 
-		Cache.add( url, promise );
+		Cache.add( `image-bitmap:${url}`, promise );
 		scope.manager.itemStart( url );
 
 	}
@@ -49114,7 +49165,7 @@ class Clock {
 	 */
 	start() {
 
-		this.startTime = now();
+		this.startTime = performance.now();
 
 		this.oldTime = this.startTime;
 		this.elapsedTime = 0;
@@ -49163,7 +49214,7 @@ class Clock {
 
 		if ( this.running ) {
 
-			const newTime = now();
+			const newTime = performance.now();
 
 			diff = ( newTime - this.oldTime ) / 1000;
 			this.oldTime = newTime;
@@ -49178,12 +49229,6 @@ class Clock {
 
 }
 
-function now() {
-
-	return performance.now();
-
-}
-
 const _position$1 = /*@__PURE__*/ new Vector3();
 const _quaternion$1 = /*@__PURE__*/ new Quaternion();
 const _scale$1 = /*@__PURE__*/ new Vector3();
@@ -54219,8 +54264,9 @@ class GLBufferAttribute {
 	 * @param {number} itemSize - The item size.
 	 * @param {number} elementSize - The corresponding size (in bytes) for the given `type` parameter.
 	 * @param {number} count - The expected number of vertices in VBO.
+	 * @param {boolean} [normalized=false] - Whether the data are normalized or not.
 	 */
-	constructor( buffer, type, itemSize, elementSize, count ) {
+	constructor( buffer, type, itemSize, elementSize, count, normalized = false ) {
 
 		/**
 		 * This flag can be used for type testing.
@@ -54273,6 +54319,17 @@ class GLBufferAttribute {
 		 */
 		this.count = count;
 
+		/**
+		 * Applies to integer data only. Indicates how the underlying data in the buffer maps to
+		 * the values in the GLSL code. For instance, if `buffer` contains data of `gl.UNSIGNED_SHORT`,
+		 * and `normalized` is `true`, the values `0 - +65535` in the buffer data will be mapped to
+		 * `0.0f - +1.0f` in the GLSL attribute. If `normalized` is `false`, the values will be converted
+		 * to floats unmodified, i.e. `65535` becomes `65535.0f`.
+		 *
+		 * @type {boolean}
+		 */
+		this.normalized = normalized;
+
 		/**
 		 * A version number, incremented every time the `needsUpdate` is set to `true`.
 		 *
@@ -56688,34 +56745,34 @@ class CameraHelper extends LineSegments {
 
 		// near
 
-		setPoint( 'n1', pointMap, geometry, _camera, -1, -1, nearZ );
-		setPoint( 'n2', pointMap, geometry, _camera, w, -1, nearZ );
-		setPoint( 'n3', pointMap, geometry, _camera, -1, h, nearZ );
+		setPoint( 'n1', pointMap, geometry, _camera, - w, - h, nearZ );
+		setPoint( 'n2', pointMap, geometry, _camera, w, - h, nearZ );
+		setPoint( 'n3', pointMap, geometry, _camera, - w, h, nearZ );
 		setPoint( 'n4', pointMap, geometry, _camera, w, h, nearZ );
 
 		// far
 
-		setPoint( 'f1', pointMap, geometry, _camera, -1, -1, 1 );
-		setPoint( 'f2', pointMap, geometry, _camera, w, -1, 1 );
-		setPoint( 'f3', pointMap, geometry, _camera, -1, h, 1 );
+		setPoint( 'f1', pointMap, geometry, _camera, - w, - h, 1 );
+		setPoint( 'f2', pointMap, geometry, _camera, w, - h, 1 );
+		setPoint( 'f3', pointMap, geometry, _camera, - w, h, 1 );
 		setPoint( 'f4', pointMap, geometry, _camera, w, h, 1 );
 
 		// up
 
 		setPoint( 'u1', pointMap, geometry, _camera, w * 0.7, h * 1.1, nearZ );
-		setPoint( 'u2', pointMap, geometry, _camera, -1 * 0.7, h * 1.1, nearZ );
+		setPoint( 'u2', pointMap, geometry, _camera, - w * 0.7, h * 1.1, nearZ );
 		setPoint( 'u3', pointMap, geometry, _camera, 0, h * 2, nearZ );
 
 		// cross
 
-		setPoint( 'cf1', pointMap, geometry, _camera, -1, 0, 1 );
+		setPoint( 'cf1', pointMap, geometry, _camera, - w, 0, 1 );
 		setPoint( 'cf2', pointMap, geometry, _camera, w, 0, 1 );
-		setPoint( 'cf3', pointMap, geometry, _camera, 0, -1, 1 );
+		setPoint( 'cf3', pointMap, geometry, _camera, 0, - h, 1 );
 		setPoint( 'cf4', pointMap, geometry, _camera, 0, h, 1 );
 
-		setPoint( 'cn1', pointMap, geometry, _camera, -1, 0, nearZ );
+		setPoint( 'cn1', pointMap, geometry, _camera, - w, 0, nearZ );
 		setPoint( 'cn2', pointMap, geometry, _camera, w, 0, nearZ );
-		setPoint( 'cn3', pointMap, geometry, _camera, 0, -1, nearZ );
+		setPoint( 'cn3', pointMap, geometry, _camera, 0, - h, nearZ );
 		setPoint( 'cn4', pointMap, geometry, _camera, 0, h, nearZ );
 
 		geometry.getAttribute( 'position' ).needsUpdate = true;

File diff suppressed because it is too large
+ 0 - 0
build/three.core.min.js


+ 16 - 7
build/three.module.js

@@ -81,6 +81,10 @@ function WebGLAttributes( gl ) {
 
 			type = gl.FLOAT;
 
+		} else if ( typeof Float16Array !== 'undefined' && array instanceof Float16Array ) {
+
+			type = gl.HALF_FLOAT;
+
 		} else if ( array instanceof Uint16Array ) {
 
 			if ( attribute.isFloat16BufferAttribute ) {
@@ -7058,7 +7062,7 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities
 			useFog: material.fog === true,
 			fogExp2: ( !! fog && fog.isFogExp2 ),
 
-			flatShading: material.flatShading === true,
+			flatShading: ( material.flatShading === true && material.wireframe === false ),
 
 			sizeAttenuation: material.sizeAttenuation === true,
 			logarithmicDepthBuffer: logarithmicDepthBuffer,
@@ -7271,6 +7275,8 @@ function WebGLPrograms( renderer, cubemaps, cubeuvmaps, extensions, capabilities
 			_programLayers.enable( 20 );
 		if ( parameters.batchingColor )
 			_programLayers.enable( 21 );
+		if ( parameters.gradientMap )
+			_programLayers.enable( 22 );
 
 		array.push( _programLayers.mask );
 		_programLayers.disableAll();
@@ -9452,7 +9458,7 @@ function WebGLState( gl, extensions ) {
 							break;
 
 						case MultiplyBlending:
-							gl.blendFuncSeparate( gl.ZERO, gl.SRC_COLOR, gl.ZERO, gl.SRC_ALPHA );
+							gl.blendFuncSeparate( gl.DST_COLOR, gl.ONE_MINUS_SRC_ALPHA, gl.ZERO, gl.ONE );
 							break;
 
 						default:
@@ -9470,15 +9476,15 @@ function WebGLState( gl, extensions ) {
 							break;
 
 						case AdditiveBlending:
-							gl.blendFunc( gl.SRC_ALPHA, gl.ONE );
+							gl.blendFuncSeparate( gl.SRC_ALPHA, gl.ONE, gl.ONE, gl.ONE );
 							break;
 
 						case SubtractiveBlending:
-							gl.blendFuncSeparate( gl.ZERO, gl.ONE_MINUS_SRC_COLOR, gl.ZERO, gl.ONE );
+							console.error( 'THREE.WebGLState: SubtractiveBlending requires material.premultipliedAlpha = true' );
 							break;
 
 						case MultiplyBlending:
-							gl.blendFunc( gl.ZERO, gl.SRC_COLOR );
+							console.error( 'THREE.WebGLState: MultiplyBlending requires material.premultipliedAlpha = true' );
 							break;
 
 						default:
@@ -16613,6 +16619,9 @@ class WebGLRenderer {
 			//
 
 			const currentRenderTarget = _this.getRenderTarget();
+			const currentActiveCubeFace = _this.getActiveCubeFace();
+			const currentActiveMipmapLevel = _this.getActiveMipmapLevel();
+
 			_this.setRenderTarget( transmissionRenderTarget );
 
 			_this.getClearColor( _currentClearColor );
@@ -16682,7 +16691,7 @@ class WebGLRenderer {
 
 			}
 
-			_this.setRenderTarget( currentRenderTarget );
+			_this.setRenderTarget( currentRenderTarget, currentActiveCubeFace, currentActiveMipmapLevel );
 
 			_this.setClearColor( _currentClearColor, _currentClearAlpha );
 
@@ -17612,7 +17621,7 @@ class WebGLRenderer {
 
 					if ( ( x >= 0 && x <= ( renderTarget.width - width ) ) && ( y >= 0 && y <= ( renderTarget.height - height ) ) ) {
 
-						// when using MRT, select the corect color buffer for the subsequent read command
+						// when using MRT, select the correct color buffer for the subsequent read command
 
 						if ( renderTarget.textures.length > 1 ) _gl.readBuffer( _gl.COLOR_ATTACHMENT0 + textureIndex );
 

File diff suppressed because it is too large
+ 0 - 0
build/three.module.min.js


File diff suppressed because it is too large
+ 8 - 8
build/three.tsl.js


File diff suppressed because it is too large
+ 0 - 0
build/three.tsl.min.js


File diff suppressed because it is too large
+ 0 - 0
build/three.webgpu.js


File diff suppressed because it is too large
+ 0 - 0
build/three.webgpu.min.js


File diff suppressed because it is too large
+ 0 - 0
build/three.webgpu.nodes.js


File diff suppressed because it is too large
+ 0 - 0
build/three.webgpu.nodes.min.js


+ 39 - 18
docs/api/ar/core/GLBufferAttribute.html

@@ -19,9 +19,14 @@
 			أكثر حالات الاستخدام شيوعًا لهذه الفئة هي عندما يتداخل نوع من
 			حسابات GPGPU أو حتى ينتج VBOs المعنية.
 		</p>
-	
+
+		<h2>Examples</h2>
+		<p>
+			[example:webgl_buffergeometry_glbufferattribute Points with custom buffers]<br />
+		</p>
+
 		<h2>المنشئ (Constructor)</h2>
-		<h3>[name]( [param:WebGLBuffer buffer], [param:GLenum type], [param:Integer itemSize], [param:Integer elementSize], [param:Integer count] )</h3>
+		<h3>[name]( [param:WebGLBuffer buffer], [param:GLenum type], [param:Integer itemSize], [param:Integer elementSize], [param:Integer count], [param:Boolean normalized] )</h3>
 		<p>
 			`buffer` — يجب أن يكون
 			[link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLBuffer WebGLBuffer].
@@ -47,6 +52,16 @@
 			<li>gl.UNSIGNED_BYTE: 1</li>
 		</ul>
 		<p>`count` — عدد الرؤوس المتوقع في VBO.</p>
+		<p>
+		`normalized` — (optional) Applies to integer data only.
+			Indicates how the underlying data in the buffer maps to the values in the
+			GLSL code. For instance, if [page:WebGLBuffer buffer] contains data of
+			`gl.UNSIGNED_SHORT`, and [page:Boolean normalized] is true, the values `0 -
+			+65535` in the buffer data will be mapped to 0.0f - +1.0f in the GLSL
+			attribute. A `gl.SHORT` (signed) would map from -32768 - +32767 to -1.0f
+			- +1.0f. If [page:Boolean normalized] is false, the values will be
+			converted to floats unmodified, i.e. 32767 becomes 32767.0f.
+		</p>
 	
 		<h2>الخصائص (Properties)</h2>
 	
@@ -59,21 +74,33 @@
 		<h3>[property:Integer count]</h3>
 		<p>عدد الرؤوس المتوقع في VBO.</p>
 	
+		<h3>[property:Integer elementSize]</h3>
+		<p>
+			يخزن الحجم المقابل بالبايت لقيمة خاصية `type` الحالية.
+		</p>
+		<p>انظر أعلاه (المُنشئ) لقائمة بأحجام الأنواع المعروفة.</p>
+	
 		<h3>[property:Boolean isGLBufferAttribute]</h3>
 		<p>للقراءة فقط. دائمًا `true`.</p>
 	
 		<h3>[property:Integer itemSize]</h3>
 		<p>كم عدد القيم التي تشكل كل عنصر (رأس).</p>
 	
-		<h3>[property:Integer elementSize]</h3>
+		<h3>[property:String name]</h3>
 		<p>
-			يخزن الحجم المقابل بالبايت لقيمة خاصية `type` الحالية.
+			اسم اختياري لهذه الحالة من السمة. الافتراضي هو سلسلة فارغة.
 		</p>
-		<p>انظر أعلاه (المُنشئ) لقائمة بأحجام الأنواع المعروفة.</p>
 	
-		<h3>[property:String name]</h3>
+		<h3>[property:Boolean needsUpdate]</h3>
 		<p>
-			اسم اختياري لهذه الحالة من السمة. الافتراضي هو سلسلة فارغة.
+			الافتراضي هو `false`. تعيين هذا إلى true يزاد
+			[page:GLBufferAttribute.version version].
+		</p>
+
+		<h3>[property:Boolean normalized]</h3>
+		<p>
+			Indicates how the underlying data in the buffer maps to the values in the
+			GLSL shader code. See the constructor above for details.
 		</p>
 	
 		<h3>[property:GLenum type]</h3>
@@ -85,6 +112,11 @@
 			باستخدام طريقة `setType`.
 		</p>
 	
+		<h3>[property:Integer version]</h3>
+		<p>
+			رقم إصدار، يزاد كل مرة يتم فيها تعيين خاصية needsUpdate على true.
+		</p>
+	
 		<h2>الوظائف (Methods)</h2>
 	
 		<h3>[method:this setBuffer]( buffer )</h3>
@@ -98,17 +130,6 @@
 	
 		<h3>[method:this setCount]( count )</h3>
 		<p>تضبط خاصية `count`.</p>
-	
-		<h3>[property:Integer version]</h3>
-		<p>
-			رقم إصدار، يزاد كل مرة يتم فيها تعيين خاصية needsUpdate على true.
-		</p>
-	
-		<h3>[property:Boolean needsUpdate]</h3>
-		<p>
-			الافتراضي هو `false`. تعيين هذا إلى true يزاد
-			[page:GLBufferAttribute.version version].
-		</p>
 
 		<h2>المصدر (Source)</h2>
 		<p>

+ 39 - 19
docs/api/en/core/GLBufferAttribute.html

@@ -20,8 +20,13 @@
 			calculation interferes or even produces the VBOs in question.
 		</p>
 
+		<h2>Examples</h2>
+		<p>
+			[example:webgl_buffergeometry_glbufferattribute Points with custom buffers]<br />
+		</p>
+
 		<h2>Constructor</h2>
-		<h3>[name]( [param:WebGLBuffer buffer], [param:GLenum type], [param:Integer itemSize], [param:Integer elementSize], [param:Integer count] )</h3>
+		<h3>[name]( [param:WebGLBuffer buffer], [param:GLenum type], [param:Integer itemSize], [param:Integer elementSize], [param:Integer count], [param:Boolean normalized] )</h3>
 		<p>
 			`buffer` — Must be a
 			[link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLBuffer WebGLBuffer].
@@ -48,6 +53,15 @@
 			<li>gl.UNSIGNED_BYTE: 1</li>
 		</ul>
 		`count` — The expected number of vertices in VBO.
+		<br />
+		`normalized` — (optional) Applies to integer data only.
+			Indicates how the underlying data in the buffer maps to the values in the
+			GLSL code. For instance, if [page:WebGLBuffer buffer] contains data of
+			`gl.UNSIGNED_SHORT`, and [page:Boolean normalized] is true, the values `0 -
+			+65535` in the buffer data will be mapped to 0.0f - +1.0f in the GLSL
+			attribute. A `gl.SHORT` (signed) would map from -32768 - +32767 to -1.0f
+			- +1.0f. If [page:Boolean normalized] is false, the values will be
+			converted to floats unmodified, i.e. 32767 becomes 32767.0f.
 
 		<h2>Properties</h2>
 
@@ -60,12 +74,6 @@
 		<h3>[property:Integer count]</h3>
 		<p>The expected number of vertices in VBO.</p>
 
-		<h3>[property:Boolean isGLBufferAttribute]</h3>
-		<p>Read-only. Always `true`.</p>
-
-		<h3>[property:Integer itemSize]</h3>
-		<p>How many values make up each item (vertex).</p>
-
 		<h3>[property:Integer elementSize]</h3>
 		<p>
 			Stores the corresponding size in bytes for the current `type` property
@@ -73,11 +81,29 @@
 		</p>
 		<p>See above (constructor) for a list of known type sizes.</p>
 
+		<h3>[property:Boolean isGLBufferAttribute]</h3>
+		<p>Read-only. Always `true`.</p>
+
+		<h3>[property:Integer itemSize]</h3>
+		<p>How many values make up each item (vertex).</p>
+
 		<h3>[property:String name]</h3>
 		<p>
 			Optional name for this attribute instance. Default is an empty string.
 		</p>
 
+		<h3>[property:Boolean needsUpdate]</h3>
+		<p>
+			Default is `false`. Setting this to true increments
+			[page:GLBufferAttribute.version version].
+		</p>
+
+		<h3>[property:Boolean normalized]</h3>
+		<p>
+			Indicates how the underlying data in the buffer maps to the values in the
+			GLSL shader code. See the constructor above for details.
+		</p>
+
 		<h3>[property:GLenum type]</h3>
 		<p>
 			A
@@ -88,6 +114,12 @@
 			using the `setType` method.
 		</p>
 
+		<h3>[property:Integer version]</h3>
+		<p>
+			A version number, incremented every time the needsUpdate property is set
+			to true.
+		</p>
+
 		<h2>Methods</h2>
 
 		<h3>[method:this setBuffer]( buffer )</h3>
@@ -102,18 +134,6 @@
 		<h3>[method:this setCount]( count )</h3>
 		<p>Sets the `count` property.</p>
 
-		<h3>[property:Integer version]</h3>
-		<p>
-			A version number, incremented every time the needsUpdate property is set
-			to true.
-		</p>
-
-		<h3>[property:Boolean needsUpdate]</h3>
-		<p>
-			Default is `false`. Setting this to true increments
-			[page:GLBufferAttribute.version version].
-		</p>
-
 		<h2>Source</h2>
 		<p>
 			[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]

+ 36 - 16
docs/api/it/core/GLBufferAttribute.html

@@ -20,8 +20,13 @@
       calcolo GPGPU interferisce o addirittura produce i VBO in questione.
 		</p>
 
+		<h2>Examples</h2>
+		<p>
+			[example:webgl_buffergeometry_glbufferattribute Points with custom buffers]<br />
+		</p>
+
 		<h2>Costruttore</h2>
-		<h3>[name]( [param:WebGLBuffer buffer], [param:GLenum type], [param:Integer itemSize], [param:Integer elementSize], [param:Integer count] )</h3>
+		<h3>[name]( [param:WebGLBuffer buffer], [param:GLenum type], [param:Integer itemSize], [param:Integer elementSize], [param:Integer count], [param:Boolean normalized] )</h3>
 		<p>
 		`buffer` — Deve essere un [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLBuffer WebGLBuffer].
 		<br />
@@ -41,6 +46,15 @@
 			<li>gl.UNSIGNED_BYTE: 1</li>
 		</ul>
 		`count` — Il numero previsto di vertici in VBO.
+		<br />
+		`normalized` — (optional) Applies to integer data only.
+			Indicates how the underlying data in the buffer maps to the values in the
+			GLSL code. For instance, if [page:WebGLBuffer buffer] contains data of
+			`gl.UNSIGNED_SHORT`, and [page:Boolean normalized] is true, the values `0 -
+			+65535` in the buffer data will be mapped to 0.0f - +1.0f in the GLSL
+			attribute. A `gl.SHORT` (signed) would map from -32768 - +32767 to -1.0f
+			- +1.0f. If [page:Boolean normalized] is false, the values will be
+			converted to floats unmodified, i.e. 32767 becomes 32767.0f.
 		</p>
 
 		<h2>Proprietà</h2>
@@ -55,6 +69,14 @@
       Il numero previsto di vertici in VBO.
 		</p>
 
+		<h3>[property:Integer elementSize]</h3>
+		<p>
+      Memorizza la dimensione corrispondente in byte per il valore della proprietà del `type` corrente.
+		</p>
+		<p>
+      Vedi sopra (costruttore) per un elenco di dimensioni di type conosciute.
+		</p>
+
 		<h3>[property:Boolean isGLBufferAttribute]</h3>
 		<p>
       Solo lettura. Sempre `true`.
@@ -65,17 +87,20 @@
       Quanti valori compongono ogni elemento (vertice).
 		</p>
 
-		<h3>[property:Integer elementSize]</h3>
+		<h3>[property:String name]</h3>
 		<p>
-      Memorizza la dimensione corrispondente in byte per il valore della proprietà del `type` corrente.
+      Un nome opzionale per questa istanza dell'attributo. Il valore predefinito è una stringa vuota.
 		</p>
+
+		<h3>[property:Boolean needsUpdate]</h3>
 		<p>
-      Vedi sopra (costruttore) per un elenco di dimensioni di type conosciute.
+      Il valore predefinito è `false`. Impostando questo metodo a true incrementa la [page:GLBufferAttribute.version versione].
 		</p>
 
-		<h3>[property:String name]</h3>
+		<h3>[property:Boolean normalized]</h3>
 		<p>
-      Un nome opzionale per questa istanza dell'attributo. Il valore predefinito è una stringa vuota.
+			Indicates how the underlying data in the buffer maps to the values in the
+			GLSL shader code. See the constructor above for details.
 		</p>
 
 		<h3>[property:GLenum type]</h3>
@@ -88,6 +113,11 @@
       di usare il metodo `setType`.
 		</p>
 
+		<h3>[property:Integer version]</h3>
+		<p>
+      Un numero di versione, incrementato ogni volta che la proprietà needsUpdate è impostata a true.
+		</p>
+
 		<h2>Metodi</h2>
 
 		<h3>[method:this setBuffer]( buffer ) </h3>
@@ -102,16 +132,6 @@
 		<h3>[method:this setCount]( count ) </h3>
 		<p>Imposta la proprietà `count`.</p>
 
-		<h3>[property:Integer version]</h3>
-		<p>
-      Un numero di versione, incrementato ogni volta che la proprietà needsUpdate è impostata a true.
-		</p>
-
-		<h3>[property:Boolean needsUpdate]</h3>
-		<p>
-      Il valore predefinito è `false`. Impostando questo metodo a true incrementa la [page:GLBufferAttribute.version versione].
-		</p>
-
 		<h2>Source</h2>
 		<p>
 			[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]

+ 36 - 16
docs/api/ko/core/GLBufferAttribute.html

@@ -17,8 +17,13 @@
 			이 클래스의 가장 일반적인 사용 사례는 어떤 종류의 GPGPU 계산이 해당 VBO를 방해하거나 심지어 생성하는 경우입니다.
 		</p>
 
+		<h2>Examples</h2>
+		<p>
+			[example:webgl_buffergeometry_glbufferattribute Points with custom buffers]<br />
+		</p>
+
 		<h2>생성자</h2>
-		<h3>[name]( [param:WebGLBuffer buffer], [param:GLenum type], [param:Integer itemSize], [param:Integer elementSize], [param:Integer count] )</h3>
+		<h3>[name]( [param:WebGLBuffer buffer], [param:GLenum type], [param:Integer itemSize], [param:Integer elementSize], [param:Integer count], [param:Boolean normalized] )</h3>
 		<p>
 		*buffer* — 반드시 [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLBuffer WebGLBuffer]여야 합니다.
 		<br />
@@ -38,6 +43,15 @@
 			<li>gl.UNSIGNED_BYTE: 1</li>
 		</ul>
 		*count* — 예상되는 VBO의 꼭짓점 수.
+		<br />
+		*normalized* — (optional) Applies to integer data only.
+			Indicates how the underlying data in the buffer maps to the values in the
+			GLSL code. For instance, if [page:WebGLBuffer buffer] contains data of
+			`gl.UNSIGNED_SHORT`, and [page:Boolean normalized] is true, the values `0 -
+			+65535` in the buffer data will be mapped to 0.0f - +1.0f in the GLSL
+			attribute. A `gl.SHORT` (signed) would map from -32768 - +32767 to -1.0f
+			- +1.0f. If [page:Boolean normalized] is false, the values will be
+			converted to floats unmodified, i.e. 32767 becomes 32767.0f.
 		</p>
 
 		<h2>프로퍼티</h2>
@@ -52,6 +66,14 @@
 			VBO의 꼭짓점 수.
 		</p>
 
+		<h3>[property:Integer elementSize]</h3>
+		<p>
+			현재의 *type* 속성 값에 맞는 바이트 사이즈를 저장.
+		</p>
+		<p>
+			알려진 타입 크기 리스트는 위의 (생성자)를 참고.
+		</p>
+
 		<h3>[property:Boolean isGLBufferAttribute]</h3>
 		<p>
 			읽기 전용. 언제나 *true*입니다.
@@ -62,17 +84,20 @@
 			각 항목을 구성하는 값의 크기 (꼭짓점).
 		</p>
 
-		<h3>[property:Integer elementSize]</h3>
+		<h3>[property:String name]</h3>
 		<p>
-			현재의 *type* 속성 값에 맞는 바이트 사이즈를 저장.
+		이 속성 인스턴스의 임시 이름. 기본값은 빈 문자열입니다.
 		</p>
+
+		<h3>[property:Boolean needsUpdate]</h3>
 		<p>
-			알려진 타입 크기 리스트는 위의 (생성자)를 참고.
+		기본값은 *false* 입니다. true로 설정하면 [page:GLBufferAttribute.version version]을 증가시킵니다.
 		</p>
 
-		<h3>[property:String name]</h3>
+		<h3>[property:Boolean normalized]</h3>
 		<p>
-		이 속성 인스턴스의 임시 이름. 기본값은 빈 문자열입니다.
+			Indicates how the underlying data in the buffer maps to the values in the
+			GLSL shader code. See the constructor above for details.
 		</p>
 
 		<h3>[property:GLenum type]</h3>
@@ -84,6 +109,11 @@
 			*elementSize*와 함께 이 속성을 설정합니다. 추천하는 방법은 *setType* 메서드를 사용하는 것입니다.
 		</p>
 
+		<h3>[property:Integer version]</h3>
+		<p>
+		버전 넘버이며 needsUpdate 속성이 true가 될 때마다 증가합니다.
+		</p>
+
 		<h2>메서드</h2>
 
 		<h3>[method:this setBuffer]( buffer ) </h3>
@@ -98,16 +128,6 @@
 		<h3>[method:this setCount]( count ) </h3>
 		<p>*count* 속성을 설정합니다.</p>
 
-		<h3>[property:Integer version]</h3>
-		<p>
-		버전 넘버이며 needsUpdate 속성이 true가 될 때마다 증가합니다.
-		</p>
-
-		<h3>[property:Boolean needsUpdate]</h3>
-		<p>
-		기본값은 *false* 입니다. true로 설정하면 [page:GLBufferAttribute.version version]을 증가시킵니다.
-		</p>
-
 		<h2>소스코드</h2>
 		<p>
 			[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]

+ 36 - 16
docs/api/zh/core/GLBufferAttribute.html

@@ -16,7 +16,7 @@
 		</p>
 
 		<h2>构造方法(Constructor)</h2>
-		<h3>[name]( [param:WebGLBuffer buffer], [param:GLenum type], [param:Integer itemSize], [param:Integer elementSize], [param:Integer count] )</h3>
+		<h3>[name]( [param:WebGLBuffer buffer], [param:GLenum type], [param:Integer itemSize], [param:Integer elementSize], [param:Integer count], [param:Boolean normalized] )</h3>
 		<p>
 		*buffer* — 必须是 [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLBuffer WebGLBuffer].
 		<br />
@@ -35,6 +35,20 @@
 			<li>gl.UNSIGNED_BYTE: 1</li>
 		</ul>
 		*count* — VBO 中预期的顶点数。
+		<br />
+		*normalized* — (optional) Applies to integer data only.
+			Indicates how the underlying data in the buffer maps to the values in the
+			GLSL code. For instance, if [page:WebGLBuffer buffer] contains data of
+			`gl.UNSIGNED_SHORT`, and [page:Boolean normalized] is true, the values `0 -
+			+65535` in the buffer data will be mapped to 0.0f - +1.0f in the GLSL
+			attribute. A `gl.SHORT` (signed) would map from -32768 - +32767 to -1.0f
+			- +1.0f. If [page:Boolean normalized] is false, the values will be
+			converted to floats unmodified, i.e. 32767 becomes 32767.0f.
+		</p>
+
+		<h2>Examples</h2>
+		<p>
+			[example:webgl_buffergeometry_glbufferattribute Points with custom buffers]<br />
 		</p>
 
 		<h2>特性(Properties)</h2>
@@ -49,6 +63,14 @@
 			VBO 中的预期顶点数。
 		</p>
 
+		<h3>[property:Integer elementSize]</h3>
+		<p>
+			存储当前类型属性值的相应大小(以字节为单位)。
+		</p>
+		<p>
+			有关已知类型大小的列表,请参见上面的(构造函数)。
+		</p>
+
 		<h3>[property:Boolean isGLBufferAttribute]</h3>
 		<p>
 			只读。值永远为"true"。
@@ -59,17 +81,20 @@
 			每个项目(顶点)组成多少个值。
 		</p>
 
-		<h3>[property:Integer elementSize]</h3>
+		<h3>[property:String name]</h3>
 		<p>
-			存储当前类型属性值的相应大小(以字节为单位)
+			该attribute实例的别名,默认值为空字符串
 		</p>
+
+		<h3>[property:Boolean needsUpdate]</h3>
 		<p>
-			有关已知类型大小的列表,请参见上面的(构造函数)。
+			默认为假。将此设置为 true 增量[page:GLBufferAttribute.version 版本]
 		</p>
 
-		<h3>[property:String name]</h3>
+		<h3>[property:Boolean normalized]</h3>
 		<p>
-			该attribute实例的别名,默认值为空字符串。
+			Indicates how the underlying data in the buffer maps to the values in the
+			GLSL shader code. See the constructor above for details.
 		</p>
 
 		<h3>[property:GLenum type]</h3>
@@ -80,6 +105,11 @@
 			将此属性与elementSize一起设置。推荐的方法是使用setType方法。
 		</p>
 
+		<h3>[property:Integer version]</h3>
+		<p>
+		版本号,每次将needsUpdate属性设置为true时递增。
+		</p>
+
 		<h2>方法(Methods)</h2>
 
 		<h3>[method:this setBuffer]( buffer ) </h3>
@@ -94,16 +124,6 @@
 		<h3>[method:this setCount]( count ) </h3>
 		<p>设置计数属性。</p>
 
-		<h3>[property:Integer version]</h3>
-		<p>
-		版本号,每次将needsUpdate属性设置为true时递增。
-		</p>
-
-		<h3>[property:Boolean needsUpdate]</h3>
-		<p>
-			默认为假。将此设置为 true 增量[page:GLBufferAttribute.version 版本]
-		</p>
-
 		<h2>源代码(Source)</h2>
 		<p>
 			[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]

+ 8 - 8
docs/scenes/material-browser.html

@@ -370,7 +370,7 @@
 				const folder = gui.addFolder( 'THREE.MeshBasicMaterial' );
 
 				folder.addColor( data, 'color' ).onChange( handleColorChange( material.color ) );
-				folder.add( material, 'wireframe' );
+				folder.add( material, 'wireframe' ).onChange( needsUpdate( material, geometry ) );
 				folder.add( material, 'vertexColors' ).onChange( needsUpdate( material, geometry ) );
 				folder.add( material, 'fog' ).onChange( needsUpdate( material, geometry ) );
 
@@ -391,7 +391,7 @@
 
 				const folder = gui.addFolder( 'THREE.MeshDepthMaterial' );
 
-				folder.add( material, 'wireframe' );
+				folder.add( material, 'wireframe' ).onChange( needsUpdate( material, geometry ) );
 
 				folder.add( data, 'alphaMap', alphaMapKeys ).onChange( updateTexture( material, 'alphaMap', alphaMaps ) );
 
@@ -402,7 +402,7 @@
 				const folder = gui.addFolder( 'THREE.MeshNormalMaterial' );
 
 				folder.add( material, 'flatShading' ).onChange( needsUpdate( material, geometry ) );
-				folder.add( material, 'wireframe' );
+				folder.add( material, 'wireframe' ).onChange( needsUpdate( material, geometry ) );
 
 			}
 
@@ -438,7 +438,7 @@
 				folder.addColor( data, 'color' ).onChange( handleColorChange( material.color ) );
 				folder.addColor( data, 'emissive' ).onChange( handleColorChange( material.emissive ) );
 
-				folder.add( material, 'wireframe' );
+				folder.add( material, 'wireframe' ).onChange( needsUpdate( material, geometry ) );
 				folder.add( material, 'vertexColors' ).onChange( needsUpdate( material, geometry ) );
 				folder.add( material, 'fog' ).onChange( needsUpdate( material, geometry ) );
 
@@ -488,7 +488,7 @@
 
 				folder.add( material, 'shininess', 0, 100 );
 				folder.add( material, 'flatShading' ).onChange( needsUpdate( material, geometry ) );
-				folder.add( material, 'wireframe' );
+				folder.add( material, 'wireframe' ).onChange( needsUpdate( material, geometry ) );
 				folder.add( material, 'vertexColors' ).onChange( needsUpdate( material, geometry ) );
 				folder.add( material, 'fog' ).onChange( needsUpdate( material, geometry ) );
 				folder.add( data, 'envMaps', envMapKeys ).onChange( updateTexture( material, 'envMap', envMaps ) );
@@ -538,7 +538,7 @@
 				folder.add( material, 'roughness', 0, 1 );
 				folder.add( material, 'metalness', 0, 1 );
 				folder.add( material, 'flatShading' ).onChange( needsUpdate( material, geometry ) );
-				folder.add( material, 'wireframe' );
+				folder.add( material, 'wireframe' ).onChange( needsUpdate( material, geometry ) );
 				folder.add( material, 'vertexColors' ).onChange( needsUpdate( material, geometry ) );
 				folder.add( material, 'fog' ).onChange( needsUpdate( material, geometry ) );
 				folder.add( data, 'envMaps', envMapKeysPBR ).onChange( updateTexture( material, 'envMap', envMaps ) );
@@ -580,10 +580,10 @@
 				folder.addColor( data, 'sheenColor' ).onChange( handleColorChange( material.sheenColor ) );
 				folder.add( material, 'clearcoat', 0, 1 ).step( 0.01 );
 				folder.add( material, 'clearcoatRoughness', 0, 1 ).step( 0.01 );
-				folder.add( material, 'specularIntensity', 0, 1);
+				folder.add( material, 'specularIntensity', 0, 1 );
 				folder.addColor( data, 'specularColor' ).onChange( handleColorChange( material.specularColor ) );
 				folder.add( material, 'flatShading' ).onChange( needsUpdate( material, geometry ) );
-				folder.add( material, 'wireframe' );
+				folder.add( material, 'wireframe' ).onChange( needsUpdate( material, geometry ) );
 				folder.add( material, 'vertexColors' ).onChange( needsUpdate( material, geometry ) );
 				folder.add( material, 'fog' ).onChange( needsUpdate( material, geometry ) );
 				folder.add( data, 'envMaps', envMapKeysPBR ).onChange( updateTexture( material, 'envMap', envMaps ) );

+ 1 - 1
editor/js/libs/jsonlint.js

@@ -122,7 +122,7 @@ parse: function parse(input) {
 
     var symbol, preErrorSymbol, state, action, r, yyval={},p,len,newState, expected;
     while (true) {
-        // retreive state number from top of stack
+        // retrieve state number from top of stack
         state = stack[stack.length-1];
 
         // use default actions if available

+ 5 - 5
examples/files.json

@@ -7,6 +7,7 @@
 		"webgl_animation_skinning_morph",
 		"webgl_animation_multiple",
 		"webgl_animation_walk",
+		"webgl_batch_lod_bvh",
 		"webgl_camera",
 		"webgl_camera_array",
 		"webgl_camera_logarithmicdepthbuffer",
@@ -22,7 +23,6 @@
 		"webgl_effects_stereo",
 		"webgl_framebuffer_texture",
 		"webgl_geometries",
-		"webgl_geometries_parametric",
 		"webgl_geometry_colors",
 		"webgl_geometry_colors_lookuptable",
 		"webgl_geometry_convex",
@@ -101,7 +101,6 @@
 		"webgl_loader_mdd",
 		"webgl_loader_nrrd",
 		"webgl_loader_obj",
-		"webgl_loader_obj_mtl",
 		"webgl_loader_pcd",
 		"webgl_loader_pdb",
 		"webgl_loader_ply",
@@ -211,9 +210,7 @@
 		"webgl_tonemapping",
 		"webgl_video_kinect",
 		"webgl_video_panorama_equirectangular",
-		"webgl_watch",
-		"webgl_water",
-		"webgl_water_flowmap"
+		"webgl_watch"
 	],
 	"webgl / postprocessing": [
 		"webgl_postprocessing",
@@ -329,6 +326,7 @@
 		"webgpu_display_stereo",
 		"webgpu_equirectangular",
 		"webgpu_instance_mesh",
+		"webgpu_instance_path",
 		"webgpu_instance_points",
 		"webgpu_instance_sprites",
 		"webgpu_instance_uniform",
@@ -405,6 +403,7 @@
 		"webgpu_postprocessing_fxaa",
 		"webgpu_postprocessing_lensflare",
 		"webgpu_postprocessing_masking",
+		"webgpu_postprocessing_ca",
 		"webgpu_postprocessing_motion_blur",
 		"webgpu_postprocessing_outline",
 		"webgpu_postprocessing_smaa",
@@ -417,6 +416,7 @@
 		"webgpu_procedural_texture",
 		"webgpu_reflection",
 		"webgpu_reflection_blurred",
+		"webgpu_reflection_roughness",
 		"webgpu_refraction",
 		"webgpu_rendertarget_2d-array_3d",
 		"webgpu_rtt",

+ 0 - 27
examples/jsm/capabilities/WebGL.js

@@ -108,33 +108,6 @@ class WebGL {
 
 	}
 
-	// @deprecated, r168
-
-	static isWebGLAvailable() {
-
-		console.warn( 'isWebGLAvailable() has been deprecated and will be removed in r178. Use isWebGL2Available() instead.' );
-
-		try {
-
-			const canvas = document.createElement( 'canvas' );
-			return !! ( window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ) );
-
-		} catch ( e ) {
-
-			return false;
-
-		}
-
-	}
-
-	static getWebGLErrorMessage() {
-
-		console.warn( 'getWebGLErrorMessage() has been deprecated and will be removed in r178. Use getWebGL2ErrorMessage() instead.' );
-
-		return this._getErrorMessage( 1 );
-
-	}
-
 }
 
 export default WebGL;

+ 1 - 1
examples/jsm/controls/ArcballControls.js

@@ -457,7 +457,7 @@ class ArcballControls extends Controls {
 		this._devPxRatio = window.devicePixelRatio;
 
 		this.domElement.addEventListener( 'contextmenu', this._onContextMenu );
-		this.domElement.addEventListener( 'wheel', this._onWheel );
+		this.domElement.addEventListener( 'wheel', this._onWheel, { passive: false } );
 		this.domElement.addEventListener( 'pointerdown', this._onPointerDown );
 		this.domElement.addEventListener( 'pointercancel', this._onPointerCancel );
 

+ 61 - 14
examples/jsm/controls/TransformControls.js

@@ -888,6 +888,40 @@ class TransformControls extends Controls {
 
 	}
 
+	/**
+	 * Sets the colors of the control's gizmo.
+	 *
+	 * @param {number|Color|string} xAxis - The x-axis color.
+	 * @param {number|Color|string} yAxis - The y-axis color.
+	 * @param {number|Color|string} zAxis - The z-axis color.
+	 * @param {number|Color|string} active - The color for active elements.
+	 */
+	setColors( xAxis, yAxis, zAxis, active ) {
+
+		const materialLib = this._gizmo.materialLib;
+
+		materialLib.xAxis.color.set( xAxis );
+		materialLib.yAxis.color.set( yAxis );
+		materialLib.zAxis.color.set( zAxis );
+		materialLib.active.color.set( active );
+		materialLib.xAxisTransparent.color.set( xAxis );
+		materialLib.yAxisTransparent.color.set( yAxis );
+		materialLib.zAxisTransparent.color.set( zAxis );
+		materialLib.activeTransparent.color.set( active );
+
+		// update color caches
+
+		if ( materialLib.xAxis._color ) materialLib.xAxis._color.set( xAxis );
+		if ( materialLib.yAxis._color ) materialLib.yAxis._color.set( yAxis );
+		if ( materialLib.zAxis._color ) materialLib.zAxis._color.set( zAxis );
+		if ( materialLib.active._color ) materialLib.active._color.set( active );
+		if ( materialLib.xAxisTransparent._color ) materialLib.xAxisTransparent._color.set( xAxis );
+		if ( materialLib.yAxisTransparent._color ) materialLib.yAxisTransparent._color.set( yAxis );
+		if ( materialLib.zAxisTransparent._color ) materialLib.zAxisTransparent._color.set( zAxis );
+		if ( materialLib.activeTransparent._color ) materialLib.activeTransparent._color.set( active );
+
+	}
+
 }
 
 // mouse / touch event handlers
@@ -1146,6 +1180,19 @@ class TransformControlsGizmo extends Object3D {
 		const matGray = gizmoMaterial.clone();
 		matGray.color.setHex( 0x787878 );
 
+		// materials in the below property are configurable via setColors()
+
+		this.materialLib = {
+			xAxis: matRed,
+			yAxis: matGreen,
+			zAxis: matBlue,
+			active: matYellow,
+			xAxisTransparent: matRedTransparent,
+			yAxisTransparent: matGreenTransparent,
+			zAxisTransparent: matBlueTransparent,
+			activeTransparent: matYellowTransparent
+		};
+
 		// reusable geometry
 
 		const arrowGeometry = new CylinderGeometry( 0, 0.04, 0.1, 12 );
@@ -1200,16 +1247,16 @@ class TransformControlsGizmo extends Object3D {
 				[ new Mesh( lineGeometry2, matBlue ), null, [ Math.PI / 2, 0, 0 ]]
 			],
 			XYZ: [
-				[ new Mesh( new OctahedronGeometry( 0.1, 0 ), matWhiteTransparent.clone() ), [ 0, 0, 0 ]]
+				[ new Mesh( new OctahedronGeometry( 0.1, 0 ), matWhiteTransparent ), [ 0, 0, 0 ]]
 			],
 			XY: [
-				[ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matBlueTransparent.clone() ), [ 0.15, 0.15, 0 ]]
+				[ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matBlueTransparent ), [ 0.15, 0.15, 0 ]]
 			],
 			YZ: [
-				[ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matRedTransparent.clone() ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]]
+				[ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matRedTransparent ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]]
 			],
 			XZ: [
-				[ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matGreenTransparent.clone() ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]]
+				[ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matGreenTransparent ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]]
 			]
 		};
 
@@ -1251,13 +1298,13 @@ class TransformControlsGizmo extends Object3D {
 				[ new Line( TranslateHelperGeometry(), matHelper ), null, null, null, 'helper' ]
 			],
 			X: [
-				[ new Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]
+				[ new Line( lineGeometry, matHelper ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]
 			],
 			Y: [
-				[ new Line( lineGeometry, matHelper.clone() ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ]
+				[ new Line( lineGeometry, matHelper ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ]
 			],
 			Z: [
-				[ new Line( lineGeometry, matHelper.clone() ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ]
+				[ new Line( lineGeometry, matHelper ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ]
 			]
 		};
 
@@ -1281,7 +1328,7 @@ class TransformControlsGizmo extends Object3D {
 
 		const helperRotate = {
 			AXIS: [
-				[ new Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]
+				[ new Line( lineGeometry, matHelper ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]
 			]
 		};
 
@@ -1329,7 +1376,7 @@ class TransformControlsGizmo extends Object3D {
 				[ new Mesh( new BoxGeometry( 0.15, 0.15, 0.01 ), matGreenTransparent ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]]
 			],
 			XYZ: [
-				[ new Mesh( new BoxGeometry( 0.1, 0.1, 0.1 ), matWhiteTransparent.clone() ) ],
+				[ new Mesh( new BoxGeometry( 0.1, 0.1, 0.1 ), matWhiteTransparent ) ],
 			]
 		};
 
@@ -1362,13 +1409,13 @@ class TransformControlsGizmo extends Object3D {
 
 		const helperScale = {
 			X: [
-				[ new Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]
+				[ new Line( lineGeometry, matHelper ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]
 			],
 			Y: [
-				[ new Line( lineGeometry, matHelper.clone() ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ]
+				[ new Line( lineGeometry, matHelper ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ]
 			],
 			Z: [
-				[ new Line( lineGeometry, matHelper.clone() ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ]
+				[ new Line( lineGeometry, matHelper ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ]
 			]
 		};
 
@@ -1749,7 +1796,7 @@ class TransformControlsGizmo extends Object3D {
 
 				if ( handle.name === this.axis ) {
 
-					handle.material.color.setHex( 0xffff00 );
+					handle.material.color.copy( this.materialLib.active.color );
 					handle.material.opacity = 1.0;
 
 				} else if ( this.axis.split( '' ).some( function ( a ) {
@@ -1758,7 +1805,7 @@ class TransformControlsGizmo extends Object3D {
 
 				} ) ) {
 
-					handle.material.color.setHex( 0xffff00 );
+					handle.material.color.copy( this.materialLib.active.color );
 					handle.material.opacity = 1.0;
 
 				}

+ 1 - 1
examples/jsm/geometries/RoundedBoxGeometry.js

@@ -70,7 +70,7 @@ class RoundedBoxGeometry extends BoxGeometry {
 		// ensure radius isn't bigger than shortest side
 		radius = Math.min( width / 2, height / 2, depth / 2, radius );
 
-		super( 1, 1, 1, segments, segments, segments );
+		super( width, height, depth, segments, segments, segments );
 
 		// if we just have one segment we're the same as a regular box
 		if ( segments === 1 ) return;

+ 1 - 1
examples/jsm/loaders/ColladaLoader.js

@@ -108,7 +108,7 @@ class ColladaLoader extends Loader {
 	}
 
 	/**
-	 * Parses the given Collada data and returns a result oject holding the parsed scene,
+	 * Parses the given Collada data and returns a result object holding the parsed scene,
 	 * an array of animation clips and kinematics.
 	 *
 	 * @param {string} text - The raw Collada data as a string.

+ 2 - 2
examples/jsm/loaders/FBXLoader.js

@@ -445,7 +445,7 @@ class FBXTreeParser {
 		const extension = textureNode.FileName.split( '.' ).pop().toLowerCase();
 
 		let loader = this.manager.getHandler( `.${extension}` );
-		if ( loader === null) loader = this.textureLoader;
+		if ( loader === null ) loader = this.textureLoader;
 
 		const loaderPath = loader.path;
 
@@ -1784,7 +1784,7 @@ class GeometryParser {
 		geoInfo.vertexPositions = ( geoNode.Vertices !== undefined ) ? geoNode.Vertices.a : [];
 		geoInfo.vertexIndices = ( geoNode.PolygonVertexIndex !== undefined ) ? geoNode.PolygonVertexIndex.a : [];
 
-		if ( geoNode.LayerElementColor ) {
+		if ( geoNode.LayerElementColor && geoNode.LayerElementColor.Color ) {
 
 			geoInfo.color = this.parseVertexColors( geoNode.LayerElementColor[ 0 ] );
 

+ 81 - 11
examples/jsm/physics/RapierPhysics.js

@@ -1,6 +1,6 @@
 import { Clock, Vector3, Quaternion, Matrix4 } from 'three';
 
-const RAPIER_PATH = 'https://cdn.skypack.dev/@dimforge/rapier3d-compat@0.12.0';
+const RAPIER_PATH = 'https://cdn.skypack.dev/@dimforge/rapier3d-compat@0.17.3';
 
 const frameRate = 60;
 
@@ -132,18 +132,40 @@ async function RapierPhysics() {
 		shape.setMass( mass );
 		shape.setRestitution( restitution );
 
-		const body = mesh.isInstancedMesh
+		const { body, collider } = mesh.isInstancedMesh
 			? createInstancedBody( mesh, mass, shape )
 			: createBody( mesh.position, mesh.quaternion, mass, shape );
 
 		if ( ! mesh.userData.physics ) mesh.userData.physics = {};
 
 		mesh.userData.physics.body = body;
+		mesh.userData.physics.collider = collider;
 
 		if ( mass > 0 ) {
 
 			meshes.push( mesh );
-			meshMap.set( mesh, body );
+			meshMap.set( mesh, { body, collider } );
+
+		}
+
+	}
+
+	function removeMesh( mesh ) {
+
+		const index = meshes.indexOf( mesh );
+
+		if ( index !== - 1 ) {
+
+			meshes.splice( index, 1 );
+			meshMap.delete( mesh );
+
+			if ( ! mesh.userData.physics ) return;
+
+			const body = mesh.userData.physics.body;
+			const collider = mesh.userData.physics.collider;
+
+			if ( body ) removeBody( body );
+			if ( collider ) removeCollider( collider );
 
 		}
 
@@ -154,15 +176,18 @@ async function RapierPhysics() {
 		const array = mesh.instanceMatrix.array;
 
 		const bodies = [];
+		const colliders = [];
 
 		for ( let i = 0; i < mesh.count; i ++ ) {
 
 			const position = _vector.fromArray( array, i * 16 + 12 );
-			bodies.push( createBody( position, null, mass, shape ) );
+			const { body, collider } = createBody( position, null, mass, shape );
+			bodies.push( body );
+			colliders.push( collider );
 
 		}
 
-		return bodies;
+		return { body: bodies, collider: colliders };
 
 	}
 
@@ -173,15 +198,51 @@ async function RapierPhysics() {
 		if ( quaternion !== null ) desc.setRotation( quaternion );
 
 		const body = world.createRigidBody( desc );
-		world.createCollider( shape, body );
+		const collider = world.createCollider( shape, body );
 
-		return body;
+		return { body, collider };
+
+	}
+
+	function removeBody( body ) {
+
+		if ( Array.isArray( body ) ) {
+
+			for ( let i = 0; i < body.length; i ++ ) {
+
+				world.removeRigidBody( body[ i ] );
+
+			}
+
+		} else {
+
+			world.removeRigidBody( body );
+
+		}
+
+	}
+
+	function removeCollider( collider ) {
+
+		if ( Array.isArray( collider ) ) {
+
+			for ( let i = 0; i < collider.length; i ++ ) {
+
+				world.removeCollider( collider[ i ] );
+
+			}
+
+		} else {
+
+			world.removeCollider( collider );
+
+		}
 
 	}
 
 	function setMeshPosition( mesh, position, index = 0 ) {
 
-		let body = meshMap.get( mesh );
+		let { body } = meshMap.get( mesh );
 
 		if ( mesh.isInstancedMesh ) {
 
@@ -197,7 +258,7 @@ async function RapierPhysics() {
 
 	function setMeshVelocity( mesh, velocity, index = 0 ) {
 
-		let body = meshMap.get( mesh );
+		let { body } = meshMap.get( mesh );
 
 		if ( mesh.isInstancedMesh ) {
 
@@ -245,7 +306,7 @@ async function RapierPhysics() {
 			if ( mesh.isInstancedMesh ) {
 
 				const array = mesh.instanceMatrix.array;
-				const bodies = meshMap.get( mesh );
+				const { body: bodies } = meshMap.get( mesh );
 
 				for ( let j = 0; j < bodies.length; j ++ ) {
 
@@ -263,7 +324,7 @@ async function RapierPhysics() {
 
 			} else {
 
-				const body = meshMap.get( mesh );
+				const { body } = meshMap.get( mesh );
 
 				mesh.position.copy( body.translation() );
 				mesh.quaternion.copy( body.rotation() );
@@ -306,6 +367,15 @@ async function RapierPhysics() {
 		 */
 		addMesh: addMesh,
 
+		/**
+		 * Removes the given mesh from this physics simulation.
+		 *
+		 * @method
+		 * @name RapierPhysics#removeMesh
+		 * @param {Mesh} mesh The mesh to remove.
+		 */
+		removeMesh: removeMesh,
+
 		/**
 		 * Set the position of the given mesh which is part of the physics simulation. Calling this
 		 * method will reset the current simulated velocity of the mesh.

+ 381 - 30
examples/jsm/transpiler/AST.js

@@ -1,19 +1,134 @@
-export class Program {
+import { toFloatType } from './TranspilerUtils.js';
+
+export class ASTNode {
 
 	constructor() {
 
-		this.body = [];
+		this.isASTNode = true;
+
+		this.linker = {
+			reference: null,
+			accesses: [],
+			assignments: []
+		};
+
+		this.parent = null;
+
+	}
+
+	get isNumericExpression() {
+
+		return false;
+
+	}
+
+	get hasAssignment() {
+
+		if ( this.isAssignment === true ) {
+
+			return true;
+
+		}
+
+		if ( this.parent === null ) {
+
+			return false;
+
+		}
+
+		return this.parent.hasAssignment;
+
+	}
+
+	getType() {
+
+		return this.type || null;
+
+	}
+
+	getParent( parents = [] ) {
+
+		if ( this.parent === null ) {
+
+			return parents;
+
+		}
+
+		parents.push( this.parent );
+
+		return this.parent.getParent( parents );
+
+	}
+
+	initialize() {
+
+		for ( const key in this ) {
+
+			if ( this[ key ] && this[ key ].isASTNode ) {
+
+				this[ key ].parent = this;
+
+			} else if ( Array.isArray( this[ key ] ) ) {
+
+				const array = this[ key ];
+
+				for ( const item of array ) {
+
+					if ( item && item.isASTNode ) {
+
+						item.parent = this;
+
+					}
+
+				}
+
+			}
+
+		}
+
+	}
+
+}
+
+export class Comment extends ASTNode {
+
+	constructor( comment ) {
+
+		super();
+
+		this.comment = comment;
+
+		this.isComment = true;
+
+		this.initialize();
+
+	}
+
+}
+
+
+export class Program extends ASTNode {
+
+	constructor( body = [] ) {
+
+		super();
+
+		this.body = body;
 
 		this.isProgram = true;
 
+		this.initialize();
+
 	}
 
 }
 
-export class VariableDeclaration {
+export class VariableDeclaration extends ASTNode {
 
 	constructor( type, name, value = null, next = null, immutable = false ) {
 
+		super();
+
 		this.type = type;
 		this.name = name;
 		this.value = value;
@@ -23,40 +138,58 @@ export class VariableDeclaration {
 
 		this.isVariableDeclaration = true;
 
+		this.initialize();
+
+	}
+
+	get isAssignment() {
+
+		return this.value !== null;
+
 	}
 
 }
 
-export class Uniform {
+export class Uniform extends ASTNode {
 
 	constructor( type, name ) {
 
+		super();
+
 		this.type = type;
 		this.name = name;
 
 		this.isUniform = true;
 
+		this.initialize();
+
 	}
 
 }
 
-export class Varying {
+export class Varying extends ASTNode {
 
 	constructor( type, name ) {
 
+		super();
+
 		this.type = type;
 		this.name = name;
 
 		this.isVarying = true;
 
+		this.initialize();
+
 	}
 
 }
 
-export class FunctionParameter {
+export class FunctionParameter extends ASTNode {
 
 	constructor( type, name, qualifier = null, immutable = true ) {
 
+		super();
+
 		this.type = type;
 		this.name = name;
 		this.qualifier = qualifier;
@@ -64,217 +197,435 @@ export class FunctionParameter {
 
 		this.isFunctionParameter = true;
 
+		this.initialize();
+
 	}
 
 }
 
-export class FunctionDeclaration {
+export class FunctionDeclaration extends ASTNode {
+
+	constructor( type, name, params = [], body = [] ) {
 
-	constructor( type, name, params = [] ) {
+		super();
 
 		this.type = type;
 		this.name = name;
 		this.params = params;
-		this.body = [];
+		this.body = body;
 
 		this.isFunctionDeclaration = true;
 
+		this.initialize();
+
 	}
 
 }
 
-export class Expression {
+export class Expression extends ASTNode {
 
 	constructor( expression ) {
 
+		super();
+
 		this.expression = expression;
 
 		this.isExpression = true;
 
+		this.initialize();
+
 	}
 
 }
 
-export class Ternary {
+export class Ternary extends ASTNode {
 
 	constructor( cond, left, right ) {
 
+		super();
+
 		this.cond = cond;
 		this.left = left;
 		this.right = right;
 
 		this.isTernary = true;
 
+		this.initialize();
+
 	}
 
 }
 
-export class Operator {
+export class Operator extends ASTNode {
 
 	constructor( type, left, right ) {
 
+		super();
+
 		this.type = type;
 		this.left = left;
 		this.right = right;
 
 		this.isOperator = true;
 
+		this.initialize();
+
+	}
+
+	get isAssignment() {
+
+		return /^(=|\+=|-=|\*=|\/=|%=|<<=|>>=|>>>=|&=|\^=|\|=)$/.test( this.type );
+
+	}
+
+	get isNumericExpression() {
+
+		if ( this.left.isNumericExpression && this.right.isNumericExpression ) {
+
+			return true;
+
+		}
+
+		return false;
+
+	}
+
+	getType() {
+
+		const leftType = this.left.getType();
+		const rightType = this.right.getType();
+
+		if ( leftType === rightType ) {
+
+			return leftType;
+
+		} else if ( toFloatType( leftType ) === toFloatType( rightType ) ) {
+
+			return toFloatType( leftType );
+
+		}
+
+		return null;
+
 	}
 
 }
 
 
-export class Unary {
+export class Unary extends ASTNode {
 
 	constructor( type, expression, after = false ) {
 
+		super();
+
 		this.type = type;
 		this.expression = expression;
 		this.after = after;
 
 		this.isUnary = true;
 
+		this.initialize();
+
+	}
+
+	get isAssignment() {
+
+		return /^(\+\+|--)$/.test( this.type );
+
+	}
+
+	get isNumericExpression() {
+
+		if ( this.expression.isNumber ) {
+
+			return true;
+
+		}
+
+		return false;
+
 	}
 
 }
 
-export class Number {
+export class Number extends ASTNode {
 
 	constructor( value, type = 'float' ) {
 
+		super();
+
 		this.type = type;
 		this.value = value;
 
 		this.isNumber = true;
 
+		this.initialize();
+
+	}
+
+	get isNumericExpression() {
+
+		return true;
+
 	}
 
 }
 
-export class String {
+export class String extends ASTNode {
 
 	constructor( value ) {
 
+		super();
+
 		this.value = value;
 
 		this.isString = true;
 
+		this.initialize();
+
 	}
 
 }
 
 
-export class Conditional {
+export class Conditional extends ASTNode {
 
-	constructor( cond = null ) {
+	constructor( cond = null, body = [] ) {
 
-		this.cond = cond;
+		super();
 
-		this.body = [];
+		this.cond = cond;
+		this.body = body;
 		this.elseConditional = null;
 
 		this.isConditional = true;
 
+		this.initialize();
+
 	}
 
 }
 
-export class FunctionCall {
+export class FunctionCall extends ASTNode {
 
 	constructor( name, params = [] ) {
 
+		super();
+
 		this.name = name;
 		this.params = params;
 
 		this.isFunctionCall = true;
 
+		this.initialize();
+
 	}
 
 }
 
-export class Return {
+export class Return extends ASTNode {
 
 	constructor( value ) {
 
+		super();
+
 		this.value = value;
 
 		this.isReturn = true;
 
+		this.initialize();
+
 	}
 
 }
 
-export class Discard {
+export class Discard extends ASTNode {
 
 	constructor() {
 
+		super();
+
 		this.isDiscard = true;
 
+		this.initialize();
+
+	}
+
+}
+
+export class Continue extends ASTNode {
+
+	constructor() {
+
+		super();
+
+		this.isContinue = true;
+
+		this.initialize();
+
+	}
+
+}
+
+export class Break extends ASTNode {
+
+	constructor() {
+
+		super();
+
+		this.isBreak = true;
+
+		this.initialize();
+
 	}
 
 }
 
-export class Accessor {
+export class Accessor extends ASTNode {
 
 	constructor( property ) {
 
+		super();
+
 		this.property = property;
 
 		this.isAccessor = true;
 
+		this.initialize();
+
+	}
+
+	getType() {
+
+		if ( this.linker.reference ) {
+
+			return this.linker.reference.getType();
+
+		}
+
+		return super.getType();
+
 	}
 
 }
 
-export class StaticElement {
+export class StaticElement extends ASTNode {
 
 	constructor( value ) {
 
+		super();
+
 		this.value = value;
 
 		this.isStaticElement = true;
 
+		this.initialize();
+
 	}
 
 }
 
-export class DynamicElement {
+export class DynamicElement extends ASTNode {
 
 	constructor( value ) {
 
+		super();
+
 		this.value = value;
 
 		this.isDynamicElement = true;
 
+		this.initialize();
+
 	}
 
 }
 
-export class AccessorElements {
+export class AccessorElements extends ASTNode {
 
 	constructor( object, elements = [] ) {
 
+		super();
+
 		this.object = object;
 		this.elements = elements;
 
 		this.isAccessorElements = true;
 
+		this.initialize();
+
 	}
 
 }
 
-export class For {
+export class For extends ASTNode {
+
+	constructor( initialization, condition, afterthought, body = [] ) {
 
-	constructor( initialization, condition, afterthought ) {
+		super();
 
 		this.initialization = initialization;
 		this.condition = condition;
 		this.afterthought = afterthought;
-
-		this.body = [];
+		this.body = body;
 
 		this.isFor = true;
 
+		this.initialize();
+
+	}
+
+}
+
+export class While extends ASTNode {
+
+	constructor( condition, body = [] ) {
+
+		super();
+
+		this.condition = condition;
+		this.body = body;
+
+		this.isWhile = true;
+
+		this.initialize();
+
+	}
+
+}
+
+
+export class Switch extends ASTNode {
+
+	constructor( discriminant, cases ) {
+
+		super();
+
+		this.discriminant = discriminant;
+		this.cases = cases;
+
+		this.isSwitch = true;
+
+		this.initialize();
+
+	}
+
+}
+
+export class SwitchCase extends ASTNode {
+
+	constructor( body, conditions = null ) {
+
+		super();
+
+		this.body = body;
+		this.conditions = conditions;
+
+		this.isDefault = conditions === null ? true : false;
+		this.isSwitchCase = true;
+
+		this.initialize();
+
 	}
 
 }

+ 225 - 85
examples/jsm/transpiler/GLSLDecoder.js

@@ -1,9 +1,15 @@
-import { Program, FunctionDeclaration, For, AccessorElements, Ternary, Varying, DynamicElement, StaticElement, FunctionParameter, Unary, Conditional, VariableDeclaration, Operator, Number, String, FunctionCall, Return, Accessor, Uniform, Discard } from './AST.js';
+import { Program, FunctionDeclaration, Switch, For, AccessorElements, Ternary, Varying, DynamicElement, StaticElement, FunctionParameter, Unary, Conditional, VariableDeclaration, Operator, Number, String, FunctionCall, Return, Accessor, Uniform, Discard, SwitchCase, Continue, Break, While, Comment } from './AST.js';
+
+import { isType } from './TranspilerUtils.js';
 
 const unaryOperators = [
 	'+', '-', '~', '!', '++', '--'
 ];
 
+const arithmeticOperators = [
+	'*', '/', '%', '+', '-', '<<', '>>'
+];
+
 const precedenceOperators = [
 	'*', '/', '%',
 	'-', '+',
@@ -41,7 +47,7 @@ const samplers3D = [ 'sampler3D', 'isampler3D', 'usampler3D' ];
 const spaceRegExp = /^((\t| )\n*)+/;
 const lineRegExp = /^\n+/;
 const commentRegExp = /^\/\*[\s\S]*?\*\//;
-const inlineCommentRegExp = /^\/\/.*?(\n|$)/;
+const inlineCommentRegExp = /^\/\/.*?(?=\n|$)/;
 
 const numberRegExp = /^((0x\w+)|(\.?\d+\.?\d*((e-?\d+)|\w)?))/;
 const stringDoubleRegExp = /^(\"((?:[^"\\]|\\.)*)\")/;
@@ -80,7 +86,9 @@ class Token {
 		this.str = str;
 		this.pos = pos;
 
-		this.tag = null;
+		this.isTag = false;
+
+		this.tags = null;
 
 	}
 
@@ -189,7 +197,7 @@ class Tokenizer {
 
 	}
 
-	readToken() {
+	nextToken() {
 
 		const remainingCode = this.skip( spaceRegExp );
 
@@ -201,35 +209,46 @@ class Tokenizer {
 			if ( result ) {
 
 				const token = new Token( this, parser.type, result[ parser.group || 0 ], this.position );
+				token.isTag = parser.isTag;
 
 				this.position += result[ 0 ].length;
 
-				if ( parser.isTag ) {
+				return token;
 
-					const nextToken = this.readToken();
+			}
 
-					if ( nextToken ) {
+		}
 
-						nextToken.tag = token;
+	}
 
-					}
+	readToken() {
 
-					return nextToken;
+		let token = this.nextToken();
 
-				}
+		if ( token && token.isTag ) {
 
-				return token;
+			const tags = [];
+
+			while ( token.isTag ) {
+
+				tags.push( token );
+
+				token = this.nextToken();
+
+				if ( ! token ) return;
 
 			}
 
+			token.tags = tags;
+
 		}
 
+		return token;
+
 	}
 
 }
 
-const isType = ( str ) => /void|bool|float|u?int|mat[234]|mat[234]x[234]|(u|i|b)?vec[234]/.test( str );
-
 class GLSLDecoder {
 
 	constructor() {
@@ -238,8 +257,6 @@ class GLSLDecoder {
 		this.tokenizer = null;
 		this.keywords = [];
 
-		this._currentFunction = null;
-
 		this.addPolyfill( 'gl_FragCoord', 'vec3 gl_FragCoord = vec3( screenCoordinate.x, screenCoordinate.y.oneMinus(), screenCoordinate.z );' );
 
 	}
@@ -327,6 +344,13 @@ class GLSLDecoder {
 
 				if ( ! token.isOperator || i === 0 || i === tokens.length - 1 ) return;
 
+				// important for negate operator after arithmetic operator: a * -1, a * -( b )
+				if ( inverse && arithmeticOperators.includes( tokens[ i - 1 ].str ) ) {
+
+					return;
+
+				}
+
 				if ( groupIndex === 0 && token.str === operator ) {
 
 					if ( operator === '?' ) {
@@ -346,7 +370,7 @@ class GLSLDecoder {
 						const left = this.parseExpressionFromTokens( tokens.slice( 0, i ) );
 						const right = this.parseExpressionFromTokens( tokens.slice( i + 1, tokens.length ) );
 
-						return this._evalOperator( new Operator( operator, left, right ) );
+						return new Operator( operator, left, right );
 
 					}
 
@@ -447,7 +471,7 @@ class GLSLDecoder {
 				const rightTokens = tokens.slice( leftTokens.length + 1 );
 				const right = this.parseExpressionFromTokens( rightTokens );
 
-				return this._evalOperator( new Operator( operator.str, left, right ) );
+				return new Operator( operator.str, left, right );
 
 			}
 
@@ -492,6 +516,14 @@ class GLSLDecoder {
 
 				return new Discard();
 
+			} else if ( firstToken.str === 'continue' ) {
+
+				return new Continue();
+
+			} else if ( firstToken.str === 'break' ) {
+
+				return new Break();
+
 			}
 
 			const secondToken = tokens[ 1 ];
@@ -657,14 +689,9 @@ class GLSLDecoder {
 		const paramsTokens = this.readTokensUntil( ')' );
 
 		const params = this.parseFunctionParams( paramsTokens.slice( 1, paramsTokens.length - 1 ) );
+		const body = this.parseBlock();
 
-		const func = new FunctionDeclaration( type, name, params );
-
-		this._currentFunction = func;
-
-		this.parseBlock( func );
-
-		this._currentFunction = null;
+		const func = new FunctionDeclaration( type, name, params, body );
 
 		return func;
 
@@ -760,6 +787,31 @@ class GLSLDecoder {
 
 	}
 
+	parseWhile() {
+
+		this.readToken(); // skip 'while'
+
+		const conditionTokens = this.readTokensUntil( ')' ).slice( 1, - 1 );
+		const condition = this.parseExpressionFromTokens( conditionTokens );
+
+		let body;
+
+		if ( this.getToken().str === '{' ) {
+
+			body = this.parseBlock();
+
+		} else {
+
+			body = [ this.parseExpression() ];
+
+		}
+
+		const statement = new While( condition, body );
+
+		return statement;
+
+	}
+
 	parseFor() {
 
 		this.readToken(); // skip 'for'
@@ -785,22 +837,100 @@ class GLSLDecoder {
 		const condition = this.parseExpressionFromTokens( conditionTokens );
 		const afterthought = this.parseExpressionFromTokens( afterthoughtTokens );
 
-		const statement = new For( initialization, condition, afterthought );
+		let body;
 
 		if ( this.getToken().str === '{' ) {
 
-			this.parseBlock( statement );
+			body = this.parseBlock();
 
 		} else {
 
-			statement.body.push( this.parseExpression() );
+			body = [ this.parseExpression() ];
 
 		}
 
+		const statement = new For( initialization, condition, afterthought, body );
+
 		return statement;
 
 	}
 
+	parseSwitch() {
+
+		this.readToken(); // Skip 'switch'
+
+		const switchDeterminantTokens = this.readTokensUntil( ')' );
+
+		// Parse expresison between parentheses. Index 1: char after '('. Index -1: char before ')'
+		const discriminant = this.parseExpressionFromTokens( switchDeterminantTokens.slice( 1, - 1 ) );
+
+		// Validate curly braces
+		if ( this.getToken().str !== '{' ) {
+
+			throw new Error( 'Expected \'{\' after switch(...) ' );
+
+		}
+
+		this.readToken(); // Skip '{'
+
+		const cases = this.parseSwitchCases();
+
+		const switchStatement = new Switch( discriminant, cases );
+
+		return switchStatement;
+
+	}
+
+	parseSwitchCases() {
+
+		const cases = [];
+
+		let token = this.getToken();
+		let conditions = null;
+
+		const isCase = ( token ) => token.str === 'case' || token.str === 'default';
+
+		while ( isCase( token ) ) {
+
+			this.readToken(); // Skip 'case' or 'default'
+
+			if ( token.str === 'case' ) {
+
+				const caseTokens = this.readTokensUntil( ':' );
+				const caseStatement = this.parseExpressionFromTokens( caseTokens.slice( 0, - 1 ) );
+
+				conditions = conditions || [];
+				conditions.push( caseStatement );
+
+			} else {
+
+				this.readTokensUntil( ':' ); // Skip 'default:'
+
+				conditions = null;
+
+			}
+
+			token = this.getToken();
+
+			if ( isCase( token ) ) {
+
+				// If the next token is another case/default, continue parsing
+				continue;
+
+			}
+
+			cases.push( new SwitchCase( this.parseBlock(), conditions ) );
+
+			token = this.getToken();
+
+			conditions = null;
+
+		}
+
+		return cases;
+
+	}
+
 	parseIf() {
 
 		const parseIfExpression = () => {
@@ -813,25 +943,28 @@ class GLSLDecoder {
 
 		};
 
-		const parseIfBlock = ( cond ) => {
+		const parseIfBlock = () => {
+
+			let body;
 
 			if ( this.getToken().str === '{' ) {
 
-				this.parseBlock( cond );
+				body = this.parseBlock();
 
 			} else {
 
-				cond.body.push( this.parseExpression() );
+				body = [ this.parseExpression() ];
 
 			}
 
+			return body;
+
 		};
 
 		//
 
-		const conditional = new Conditional( parseIfExpression() );
-
-		parseIfBlock( conditional );
+		// Parse the first if statement
+		const conditional = new Conditional( parseIfExpression(), parseIfBlock() );
 
 		//
 
@@ -841,21 +974,24 @@ class GLSLDecoder {
 
 			this.readToken(); // skip 'else'
 
+			// Assign the current if/else statement as the previous within the chain of conditionals
 			const previous = current;
 
-			if ( this.getToken().str === 'if' ) {
-
-				current = new Conditional( parseIfExpression() );
+			let expression = null;
 
-			} else {
+			// If an 'else if' statement, parse the conditional within the if
+			if ( this.getToken().str === 'if' ) {
 
-				current = new Conditional();
+				// Current conditional now equal to next conditional in the chain
+				expression = parseIfExpression();
 
 			}
 
-			previous.elseConditional = current;
+			current = new Conditional( expression, parseIfBlock() );
+			current.parent = previous;
 
-			parseIfBlock( current );
+			// n - 1 conditional's else statement assigned to new if/else statement
+			previous.elseConditional = current;
 
 		}
 
@@ -863,7 +999,9 @@ class GLSLDecoder {
 
 	}
 
-	parseBlock( scope ) {
+	parseBlock() {
+
+		const body = [];
 
 		const firstToken = this.getToken();
 
@@ -883,16 +1021,47 @@ class GLSLDecoder {
 
 			groupIndex += getGroupDelta( token.str );
 
-			if ( groupIndex < 0 ) {
+			if ( groupIndex === 0 && ( token.str === 'case' || token.str === 'default' ) ) {
+
+				return body; // switch case or default statement, return body
+
+			} else if ( groupIndex < 0 ) {
 
 				this.readToken(); // skip '}'
 
-				break;
+				return body;
 
 			}
 
 			//
 
+			if ( token.tags ) {
+
+				let lastStatement = null;
+
+				for ( const tag of token.tags ) {
+
+					if ( tag.type === Token.COMMENT ) {
+
+						const str = tag.str.replace( /\t/g, '' );
+
+						if ( ! lastStatement || lastStatement.isComment !== true ) {
+
+							lastStatement = new Comment( str );
+							body.push( lastStatement );
+
+						} else {
+
+							lastStatement.comment += '\n' + str;
+
+						}
+
+					}
+
+				}
+
+			}
+
 			if ( token.isLiteral || token.isOperator ) {
 
 				if ( token.str === 'const' ) {
@@ -931,6 +1100,14 @@ class GLSLDecoder {
 
 					statement = this.parseFor();
 
+				} else if ( token.str === 'while' ) {
+
+					statement = this.parseWhile();
+
+				} else if ( token.str === 'switch' ) {
+
+					statement = this.parseSwitch();
+
 				} else {
 
 					statement = this.parseExpression();
@@ -941,7 +1118,7 @@ class GLSLDecoder {
 
 			if ( statement ) {
 
-				scope.body.push( statement );
+				body.push( statement );
 
 			} else {
 
@@ -951,43 +1128,7 @@ class GLSLDecoder {
 
 		}
 
-	}
-
-	_evalOperator( operator ) {
-
-		if ( operator.type.includes( '=' ) ) {
-
-			const parameter = this._getFunctionParameter( operator.left.property );
-
-			if ( parameter !== undefined ) {
-
-				// Parameters are immutable in WGSL
-
-				parameter.immutable = false;
-
-			}
-
-		}
-
-		return operator;
-
-	}
-
-	_getFunctionParameter( name ) {
-
-		if ( this._currentFunction ) {
-
-			for ( const param of this._currentFunction.params ) {
-
-				if ( param.name === name ) {
-
-					return param;
-
-				}
-
-			}
-
-		}
+		return body;
 
 	}
 
@@ -1014,9 +1155,8 @@ class GLSLDecoder {
 		this.index = 0;
 		this.tokenizer = new Tokenizer( polyfill + source ).tokenize();
 
-		const program = new Program();
-
-		this.parseBlock( program );
+		const body = this.parseBlock();
+		const program = new Program( body );
 
 		return program;
 

+ 327 - 0
examples/jsm/transpiler/Linker.js

@@ -0,0 +1,327 @@
+class Block {
+
+	constructor( node, parent = null ) {
+
+		this.node = node;
+		this.parent = parent;
+
+		this.properties = {};
+
+	}
+
+	setProperty( name, value ) {
+
+		this.properties[ name ] = value;
+
+	}
+
+	getProperty( name ) {
+
+		let value = this.properties[ name ];
+
+		if ( value === undefined && this.parent !== null ) {
+
+			value = this.parent.getProperty( name );
+
+		}
+
+		return value;
+
+	}
+
+}
+
+class Linker {
+
+	constructor() {
+
+		this.block = null;
+
+	}
+
+	addBlock( node ) {
+
+		this.block = new Block( node, this.block );
+
+	}
+
+	removeBlock( node ) {
+
+		if ( this.block === null || this.block.node !== node ) {
+
+			throw new Error( 'No block to remove or block mismatch.' );
+
+		}
+
+		this.block = this.block.parent;
+
+	}
+
+	processVariables( node ) {
+
+		this.block.setProperty( node.name, node );
+
+		if ( node.value ) {
+
+			this.processExpression( node.value );
+
+		}
+
+	}
+
+	processUniform( node ) {
+
+		this.block.setProperty( node.name, node );
+
+	}
+
+	processVarying( node ) {
+
+		this.block.setProperty( node.name, node );
+
+	}
+
+	evalProperty( node ) {
+
+		let property = '';
+
+		if ( node.isAccessor ) {
+
+			property += node.property;
+
+		}
+
+		return property;
+
+	}
+
+	processExpression( node ) {
+
+		if ( node.isAccessor ) {
+
+			const property = this.block.getProperty( this.evalProperty( node ) );
+
+			if ( property ) {
+
+				node.linker.reference = property;
+
+				property.linker.accesses.push( node );
+
+			}
+
+		} else if ( node.isNumber || node.isString ) {
+
+			// Process primitive values
+
+		} else if ( node.isOperator ) {
+
+			this.processExpression( node.left );
+			this.processExpression( node.right );
+
+			if ( node.isAssignment ) {
+
+				const property = this.block.getProperty( this.evalProperty( node.left ) );
+
+				if ( property ) {
+
+					property.linker.assignments.push( node );
+
+				}
+
+			}
+
+		} else if ( node.isFunctionCall ) {
+
+			for ( const param of node.params ) {
+
+				this.processExpression( param );
+
+			}
+
+		} else if ( node.isReturn ) {
+
+			if ( node.value ) this.processExpression( node.value );
+
+		} else if ( node.isDiscard || node.isBreak || node.isContinue ) {
+
+			// Process control flow
+
+		} else if ( node.isAccessorElements ) {
+
+			this.processExpression( node.object );
+
+			for ( const element of node.elements ) {
+
+				this.processExpression( element.value );
+
+			}
+
+		} else if ( node.isDynamicElement || node.isStaticElement ) {
+
+			this.processExpression( node.value );
+
+		} else if ( node.isFor || node.isWhile ) {
+
+			this.processForWhile( node );
+
+		} else if ( node.isSwitch ) {
+
+			this.processSwitch( node );
+
+		} else if ( node.isVariableDeclaration ) {
+
+			this.processVariables( node );
+
+		} else if ( node.isUniform ) {
+
+			this.processUniform( node );
+
+		} else if ( node.isVarying ) {
+
+			this.processVarying( node );
+
+		} else if ( node.isTernary ) {
+
+			this.processExpression( node.cond );
+			this.processExpression( node.left );
+			this.processExpression( node.right );
+
+		} else if ( node.isConditional ) {
+
+			this.processConditional( node );
+
+		} else if ( node.isUnary ) {
+
+			this.processExpression( node.expression );
+
+			if ( node.isAssignment ) {
+
+				if ( node.parent.hasAssignment !== true ) {
+
+					// optimize increment/decrement operator
+					// to avoid creating a new variable
+
+					node.after = false;
+
+				}
+
+				const property = this.block.getProperty( this.evalProperty( node.expression ) );
+
+				if ( property ) {
+
+					property.linker.assignments.push( node );
+
+				}
+
+			}
+
+		}
+
+	}
+
+	processBody( body ) {
+
+		for ( const statement of body ) {
+
+			this.processExpression( statement );
+
+		}
+
+	}
+
+	processConditional( node ) {
+
+		this.processExpression( node.cond );
+		this.processBody( node.body );
+
+		let current = node;
+
+		while ( current.elseConditional ) {
+
+			if ( current.elseConditional.cond ) {
+
+				this.processExpression( current.elseConditional.cond );
+
+			}
+
+			this.processBody( current.elseConditional.body );
+
+			current = current.elseConditional;
+
+		}
+
+	}
+
+	processForWhile( node ) {
+
+		if ( node.initialization ) this.processExpression( node.initialization );
+		if ( node.condition ) this.processExpression( node.condition );
+		if ( node.afterthought ) this.processExpression( node.afterthought );
+
+		this.processBody( node.body );
+
+	}
+
+	processSwitch( switchNode ) {
+
+		this.processExpression( switchNode.discriminant );
+
+		for ( const switchCase of switchNode.cases ) {
+
+			if ( switchCase.isDefault !== true ) {
+
+				for ( const condition of switchCase.conditions ) {
+
+					this.processExpression( condition );
+
+				}
+
+			}
+
+			this.processBody( switchCase.body );
+
+		}
+
+	}
+
+	processFunction( node ) {
+
+		this.addBlock( node );
+
+		for ( const param of node.params ) {
+
+			this.block.setProperty( param.name, param );
+
+		}
+
+		this.processBody( node.body );
+
+		this.removeBlock( node );
+
+	}
+
+	process( ast ) {
+
+		this.addBlock( ast );
+
+		for ( const statement of ast.body ) {
+
+			if ( statement.isFunctionDeclaration ) {
+
+				this.processFunction( statement );
+
+			} else {
+
+				this.processExpression( statement );
+
+			}
+
+		}
+
+		this.removeBlock( ast );
+
+	}
+
+}
+
+export default Linker;

+ 197 - 92
examples/jsm/transpiler/TSLEncoder.js

@@ -2,6 +2,7 @@ import { REVISION } from 'three/webgpu';
 import * as TSL from 'three/tsl';
 
 import { VariableDeclaration, Accessor } from './AST.js';
+import { isExpression, isPrimitive } from './TranspilerUtils.js';
 
 const opLib = {
 	'=': 'assign',
@@ -47,8 +48,6 @@ const unaryLib = {
 
 const textureLookupFunctions = [ 'texture', 'texture2D', 'texture3D', 'textureCube', 'textureLod', 'texelFetch', 'textureGrad' ];
 
-const isPrimitive = ( value ) => /^(true|false|-?(\d|\.\d))/.test( value );
-
 class TSLEncoder {
 
 	constructor() {
@@ -58,13 +57,9 @@ class TSLEncoder {
 		this.global = new Set();
 		this.overloadings = new Map();
 		this.iife = false;
-		this.uniqueNames = false;
 		this.reference = false;
 
-		this._currentVariable = null;
-
-		this._currentProperties = {};
-		this._lastStatement = null;
+		this.block = null;
 
 	}
 
@@ -74,7 +69,7 @@ class TSLEncoder {
 
 		name = name.split( '.' )[ 0 ];
 
-		if ( TSL[ name ] !== undefined && this.global.has( name ) === false && this._currentProperties[ name ] === undefined ) {
+		if ( TSL[ name ] !== undefined && this.global.has( name ) === false ) {
 
 			this.imports.add( name );
 
@@ -132,29 +127,23 @@ class TSLEncoder {
 
 	}
 
-	emitExpression( node ) {
+	emitExpression( node, output = null ) {
 
 		let code;
 
 		if ( node.isAccessor ) {
 
-			this.addImport( node.property );
+			if ( node.linker.reference === null ) {
 
-			code = node.property;
-
-		} else if ( node.isNumber ) {
+				this.addImport( node.property );
 
-			if ( node.type === 'int' || node.type === 'uint' ) {
-
-				code = node.type + '( ' + node.value + ' )';
-
-				this.addImport( node.type );
+			}
 
-			} else {
+			code = node.property;
 
-				code = node.value;
+		} else if ( node.isNumber ) {
 
-			}
+			code = node.value;
 
 		} else if ( node.isString ) {
 
@@ -164,10 +153,10 @@ class TSLEncoder {
 
 			const opFn = opLib[ node.type ] || node.type;
 
-			const left = this.emitExpression( node.left );
-			const right = this.emitExpression( node.right );
+			const left = this.emitExpression( node.left, output );
+			const right = this.emitExpression( node.right, output );
 
-			if ( isPrimitive( left ) && isPrimitive( right ) ) {
+			if ( node.isNumericExpression ) {
 
 				return left + ' ' + node.type + ' ' + right;
 
@@ -253,6 +242,18 @@ class TSLEncoder {
 
 			code = 'Discard()';
 
+		} else if ( node.isBreak ) {
+
+			this.addImport( 'Break' );
+
+			code = 'Break()';
+
+		} else if ( node.isContinue ) {
+
+			this.addImport( 'Continue' );
+
+			code = 'Continue()';
+
 		} else if ( node.isAccessorElements ) {
 
 			code = this.emitExpression( node.object );
@@ -293,6 +294,14 @@ class TSLEncoder {
 
 			code = this.emitFor( node );
 
+		} else if ( node.isWhile ) {
+
+			code = this.emitWhile( node );
+
+		} else if ( node.isSwitch ) {
+
+			code = this.emitSwitch( node );
+
 		} else if ( node.isVariableDeclaration ) {
 
 			code = this.emitVariables( node );
@@ -313,26 +322,23 @@ class TSLEncoder {
 
 			code = this.emitConditional( node );
 
-		} else if ( node.isUnary && node.expression.isNumber ) {
+		} else if ( node.isUnary && node.expression.isNumber && node.type === '-' ) {
 
-			code = node.expression.type + '( ' + node.type + ' ' + node.expression.value + ' )';
+			code = '- ' + node.expression.value;
 
-			this.addImport( node.expression.type );
+			if ( node.expression.type !== 'float' ) {
 
-		} else if ( node.isUnary ) {
-
-			let type = unaryLib[ node.type ];
+				code = node.expression.type + '( ' + code + ' )';
 
-			if ( node.type === '++' || node.type === '--' ) {
+				this.addImport( node.expression.type );
 
-				if ( this._currentVariable === null ) {
+			}
 
-					// optimize increment/decrement operator
-					// to avoid creating a new variable
+		} else if ( node.isUnary ) {
 
-					node.after = false;
+			let type = unaryLib[ node.type ];
 
-				}
+			if ( node.hasAssignment ) {
 
 				if ( node.after === false ) {
 
@@ -370,23 +376,34 @@ class TSLEncoder {
 
 	emitBody( body ) {
 
-		this.setLastStatement( null );
-
 		let code = '';
 
 		this.tab += '\t';
 
 		for ( const statement of body ) {
 
-			code += this.emitExtraLine( statement );
+			code += this.emitExtraLine( statement, body );
+
+			if ( statement.isComment ) {
+
+				code += this.emitComment( statement, body );
+
+				continue;
+
+			}
+
+			if ( this.block && this.block.isSwitchCase ) {
+
+				if ( statement.isBreak ) continue; // skip break statements in switch cases
+
+			}
+
 			code += this.tab + this.emitExpression( statement );
 
 			if ( code.slice( - 1 ) !== '}' ) code += ';';
 
 			code += '\n';
 
-			this.setLastStatement( statement );
-
 		}
 
 		code = code.slice( 0, - 1 ); // remove the last extra line
@@ -507,6 +524,63 @@ ${ this.tab }} )`;
 
 	}
 
+
+	emitSwitch( switchNode ) {
+
+		const discriminantString = this.emitExpression( switchNode.discriminant );
+
+		this.tab += '\t';
+
+		let switchString = `Switch( ${ discriminantString } )\n${ this.tab }`;
+
+		const previousBlock = this.block;
+
+		for ( const switchCase of switchNode.cases ) {
+
+			this.block = switchCase;
+
+			let caseBodyString;
+
+			if ( ! switchCase.isDefault ) {
+
+				const caseConditions = [ ];
+
+				for ( const condition of switchCase.conditions ) {
+
+					caseConditions.push( this.emitExpression( condition ) );
+
+				}
+
+				caseBodyString = this.emitBody( switchCase.body );
+
+				switchString += `.Case( ${ caseConditions.join( ', ' ) }, `;
+
+			} else {
+
+				caseBodyString = this.emitBody( switchCase.body );
+
+				switchString += '.Default( ';
+
+			}
+
+			switchString += `() => {
+
+${ caseBodyString }
+
+${ this.tab }} )`;
+
+		}
+
+		this.block = previousBlock;
+
+		this.tab = this.tab.slice( 0, - 1 );
+
+		this.imports.add( 'Switch' );
+
+		return switchString;
+
+	}
+
 	emitFor( node ) {
 
 		const { initialization, condition, afterthought } = node;
@@ -554,38 +628,58 @@ ${ this.tab }} )`;
 
 	}
 
-	emitVariables( node, isRoot = true ) {
+	emitWhile( node ) {
 
-		const { name, type, value, next } = node;
+		const condition = this.emitExpression( node.condition );
+
+		let whileStr = `Loop( ${ condition }, () => {\n\n`;
+
+		whileStr += this.emitBody( node.body ) + '\n\n';
+
+		whileStr += this.tab + '} )';
 
-		this._currentVariable = node;
+		this.imports.add( 'Loop' );
+
+		return whileStr;
 
-		const valueStr = value ? this.emitExpression( value ) : '';
+	}
+
+	emitVariables( node, isRoot = true ) {
+
+		const { name, type, value, next } = node;
 
 		let varStr = isRoot ? 'const ' : '';
 		varStr += name;
 
 		if ( value ) {
 
-			if ( value.isFunctionCall && value.name === type ) {
+			let valueStr = this.emitExpression( value );
 
-				varStr += ' = ' + valueStr;
+			if ( value.isNumericExpression ) {
 
-			} else {
+				// convert JS primitive to node
 
-				varStr += ` = ${ type }( ${ valueStr } )`;
+				valueStr = `${ type }( ${ valueStr } )`;
+
+				this.addImport( type );
 
 			}
 
-		} else {
+			if ( node.linker.assignments.length > 0 ) {
 
-			varStr += ` = ${ type }()`;
+				varStr += ' = ' + valueStr + '.toVar()';
 
-		}
+			} else {
 
-		if ( node.immutable === false ) {
+				varStr += ' = ' + valueStr;
 
-			varStr += '.toVar()';
+			}
+
+		} else {
+
+			varStr += ` = property( '${ type }' )`;
+
+			this.addImport( 'property' );
 
 		}
 
@@ -595,10 +689,6 @@ ${ this.tab }} )`;
 
 		}
 
-		this.addImport( type );
-
-		this._currentVariable = null;
-
 		return varStr;
 
 	}
@@ -622,7 +712,7 @@ ${ this.tab }} )`;
 
 		const prefix = this.iife === false ? 'export ' : '';
 
-		return `${ prefix }const ${ name } = /*#__PURE__*/ overloadingFn( [ ${ nodes.map( node => node.name + '_' + nodes.indexOf( node ) ).join( ', ' ) } ] );\n`;
+		return `${ prefix }const ${ name } = /*@__PURE__*/ overloadingFn( [ ${ nodes.map( node => node.name + '_' + nodes.indexOf( node ) ).join( ', ' ) } ] );\n`;
 
 	}
 
@@ -630,8 +720,6 @@ ${ this.tab }} )`;
 
 		const { name, type } = node;
 
-		this._currentProperties = { name: node };
-
 		const params = [];
 		const inputs = [];
 		const mutableParams = [];
@@ -640,11 +728,9 @@ ${ this.tab }} )`;
 
 		for ( const param of node.params ) {
 
-			let str = `{ name: '${ param.name }', type: '${ param.type }'`;
-
 			let name = param.name;
 
-			if ( param.immutable === false && ( param.qualifier !== 'inout' && param.qualifier !== 'out' ) ) {
+			if ( param.linker.assignments.length > 0 ) {
 
 				name = name + '_immutable';
 
@@ -660,20 +746,20 @@ ${ this.tab }} )`;
 
 				}
 
-				str += ', qualifier: \'' + param.qualifier + '\'';
-
 			}
 
-			inputs.push( str + ' }' );
+			inputs.push( param.name + ': \'' + param.type + '\'' );
 			params.push( name );
 
-			this._currentProperties[ name ] = param;
-
 		}
 
 		for ( const param of mutableParams ) {
 
-			node.body.unshift( new VariableDeclaration( param.type, param.name, new Accessor( param.name + '_immutable' ) ) );
+			const mutableParam = new VariableDeclaration( param.type, param.name, new Accessor( param.name + '_immutable' ), null, true );
+			mutableParam.parent = param.parent; // link to the original node
+			mutableParam.linker.assignments.push( mutableParam );
+
+			node.body.unshift( mutableParam );
 
 		}
 
@@ -705,27 +791,19 @@ ${ this.tab }} )`;
 
 		const prefix = this.iife === false ? 'export ' : '';
 
-		let funcStr = `${ prefix }const ${ fnName } = /*#__PURE__*/ Fn( (${ paramsStr }) => {
+		let funcStr = `${ prefix }const ${ fnName } = /*@__PURE__*/ Fn( (${ paramsStr }) => {
 
 ${ bodyStr }
 
-${ this.tab }} )`;
-
-		const layoutInput = inputs.length > 0 ? '\n\t\t' + this.tab + inputs.join( ',\n\t\t' + this.tab ) + '\n\t' + this.tab : '';
+${ this.tab }}`;
 
 		if ( node.layout !== false && hasPointer === false ) {
 
-			const uniqueName = this.uniqueNames ? fnName + '_' + Math.random().toString( 36 ).slice( 2 ) : fnName;
-
-			funcStr += `.setLayout( {
-${ this.tab }\tname: '${ uniqueName }',
-${ this.tab }\ttype: '${ type }',
-${ this.tab }\tinputs: [${ layoutInput }]
-${ this.tab }} )`;
+			funcStr += ', { ' + inputs.join( ', ' ) + ', return: \'' + type + '\' }';
 
 		}
 
-		funcStr += ';\n';
+		funcStr += ' );\n';
 
 		this.imports.add( 'Fn' );
 
@@ -741,21 +819,42 @@ ${ this.tab }} )`;
 
 	}
 
-	setLastStatement( statement ) {
+	emitComment( statement, body ) {
+
+		const index = body.indexOf( statement );
+		const previous = body[ index - 1 ];
+		const next = body[ index + 1 ];
+
+		let output = '';
 
-		this._lastStatement = statement;
+		if ( previous && isExpression( previous ) ) {
+
+			output += '\n';
+
+		}
+
+		output += this.tab + statement.comment.replace( /\n/g, '\n' + this.tab ) + '\n';
+
+		if ( next && isExpression( next ) ) {
+
+			output += '\n';
+
+		}
+
+		return output;
 
 	}
 
-	emitExtraLine( statement ) {
+	emitExtraLine( statement, body ) {
+
+		const index = body.indexOf( statement );
+		const previous = body[ index - 1 ];
 
-		const last = this._lastStatement;
-		if ( last === null ) return '';
+		if ( previous === undefined ) return '';
 
 		if ( statement.isReturn ) return '\n';
 
-		const isExpression = ( st ) => st.isFunctionDeclaration !== true && st.isFor !== true && st.isConditional !== true;
-		const lastExp = isExpression( last );
+		const lastExp = isExpression( previous );
 		const currExp = isExpression( statement );
 
 		if ( lastExp !== currExp || ( ! lastExp && ! currExp ) ) return '\n';
@@ -790,7 +889,15 @@ ${ this.tab }} )`;
 
 		for ( const statement of ast.body ) {
 
-			code += this.emitExtraLine( statement );
+			code += this.emitExtraLine( statement, ast.body );
+
+			if ( statement.isComment ) {
+
+				code += this.emitComment( statement, ast.body );
+
+				continue;
+
+			}
 
 			if ( statement.isFunctionDeclaration ) {
 
@@ -802,8 +909,6 @@ ${ this.tab }} )`;
 
 			}
 
-			this.setLastStatement( statement );
-
 		}
 
 		const imports = [ ...this.imports ];

+ 17 - 1
examples/jsm/transpiler/Transpiler.js

@@ -1,3 +1,5 @@
+import Linker from './Linker.js';
+
 /**
  * A class that transpiles shader code from one language into another.
  *
@@ -32,6 +34,15 @@ class Transpiler {
 		 */
 		this.encoder = encoder;
 
+		/**
+		 * The linker. It processes the AST and resolves
+		 * variable and function references, ensuring that all
+		 * dependencies are properly linked.
+		 *
+		 * @type {Linker}
+		 */
+		this.linker = new Linker();
+
 	}
 
 	/**
@@ -42,7 +53,12 @@ class Transpiler {
 	 */
 	parse( source ) {
 
-		return this.encoder.emit( this.decoder.parse( source ) );
+		const ast = this.decoder.parse( source );
+
+		// Process the AST to resolve variable and function references and optimizations.
+		this.linker.process( ast );
+
+		return this.encoder.emit( ast );
 
 	}
 

+ 29 - 0
examples/jsm/transpiler/TranspilerUtils.js

@@ -0,0 +1,29 @@
+export function isExpression( st ) {
+
+	return st.isFunctionDeclaration !== true && st.isFor !== true && st.isWhile !== true && st.isConditional !== true && st.isSwitch !== true;
+
+}
+
+export function isPrimitive( value ) {
+
+	return /^(true|false|-?(\d|\.\d))/.test( value );
+
+}
+
+export function isType( str ) {
+
+	return /void|bool|float|u?int|mat[234]|mat[234]x[234]|(u|i|b)?vec[234]/.test( str );
+
+}
+
+export function toFloatType( type ) {
+
+	if ( /^(i?int)$/.test( type ) ) return 'float';
+
+	const vecMatch = /^(i|u)?vec([234])$/.exec( type );
+
+	if ( vecMatch ) return 'vec' + vecMatch[ 2 ];
+
+	return type;
+
+}

+ 788 - 0
examples/jsm/transpiler/WGSLEncoder.js

@@ -0,0 +1,788 @@
+import { REVISION } from 'three/webgpu';
+
+import { VariableDeclaration, Accessor } from './AST.js';
+import { isExpression } from './TranspilerUtils.js';
+
+// Note: This is a simplified list. A complete implementation would need more mappings.
+const typeMap = {
+	'float': 'f32',
+	'int': 'i32',
+	'uint': 'u32',
+	'bool': 'bool',
+	'vec2': 'vec2f',
+	'ivec2': 'vec2i',
+	'uvec2': 'vec2u',
+	'bvec2': 'vec2b',
+	'vec3': 'vec3f',
+	'ivec3': 'vec3i',
+	'uvec3': 'vec3u',
+	'bvec3': 'vec3b',
+	'vec4': 'vec4f',
+	'ivec4': 'vec4i',
+	'uvec4': 'vec4u',
+	'bvec4': 'vec4b',
+	'mat3': 'mat3x3<f32>',
+	'mat4': 'mat4x4<f32>',
+	'texture': 'texture_2d<f32>',
+	'textureCube': 'texture_cube<f32>',
+	'texture3D': 'texture_3d<f32>',
+};
+
+// GLSL to WGSL built-in function mapping
+const wgslLib = {
+	'abs': 'abs',
+	'acos': 'acos',
+	'asin': 'asin',
+	'atan': 'atan',
+	'atan2': 'atan2',
+	'ceil': 'ceil',
+	'clamp': 'clamp',
+	'cos': 'cos',
+	'cross': 'cross',
+	'degrees': 'degrees',
+	'distance': 'distance',
+	'dot': 'dot',
+	'exp': 'exp',
+	'exp2': 'exp2',
+	'faceforward': 'faceForward',
+	'floor': 'floor',
+	'fract': 'fract',
+	'inverse': 'inverse',
+	'inversesqrt': 'inverseSqrt',
+	'length': 'length',
+	'log': 'log',
+	'log2': 'log2',
+	'max': 'max',
+	'min': 'min',
+	'mix': 'mix',
+	'normalize': 'normalize',
+	'pow': 'pow',
+	'radians': 'radians',
+	'reflect': 'reflect',
+	'refract': 'refract',
+	'round': 'round',
+	'sign': 'sign',
+	'sin': 'sin',
+	'smoothstep': 'smoothstep',
+	'sqrt': 'sqrt',
+	'step': 'step',
+	'tan': 'tan',
+	'transpose': 'transpose',
+	'trunc': 'trunc',
+	'dFdx': 'dpdx',
+	'dFdy': 'dpdy',
+	'fwidth': 'fwidth',
+	// Texture functions are handled separately
+	'texture': 'textureSample',
+	'texture2D': 'textureSample',
+	'texture3D': 'textureSample',
+	'textureCube': 'textureSample',
+	'textureLod': 'textureSampleLevel',
+	'texelFetch': 'textureLoad',
+	'textureGrad': 'textureSampleGrad',
+};
+
+class WGSLEncoder {
+
+	constructor() {
+
+		this.tab = '';
+		this.functions = new Map();
+		this.uniforms = [];
+		this.varyings = [];
+		this.structs = new Map();
+		this.polyfills = new Map();
+
+		// Assume a single group for simplicity
+		this.groupIndex = 0;
+
+	}
+
+	getWgslType( type ) {
+
+		return typeMap[ type ] || type;
+
+	}
+
+	emitExpression( node ) {
+
+		if ( ! node ) return '';
+
+		let code;
+
+		if ( node.isAccessor ) {
+
+			// Check if this accessor is part of a uniform struct
+			const uniform = this.uniforms.find( u => u.name === node.property );
+
+			if ( uniform && ! uniform.type.includes( 'texture' ) ) {
+
+				return `uniforms.${node.property}`;
+
+			}
+
+			code = node.property;
+
+		} else if ( node.isNumber ) {
+
+			code = node.value;
+
+			// WGSL requires floating point numbers to have a decimal
+			if ( node.type === 'float' && ! code.includes( '.' ) ) {
+
+				code += '.0';
+
+			}
+
+		} else if ( node.isOperator ) {
+
+			const left = this.emitExpression( node.left );
+			const right = this.emitExpression( node.right );
+
+			code = `${ left } ${ node.type } ${ right }`;
+
+			if ( node.parent.isAssignment !== true && node.parent.isOperator ) {
+
+				code = `( ${ code } )`;
+
+			}
+
+		} else if ( node.isFunctionCall ) {
+
+			const fnName = wgslLib[ node.name ] || node.name;
+
+			if ( fnName === 'mod' ) {
+
+				const snippets = node.params.map( p => this.emitExpression( p ) );
+				const types = node.params.map( p => p.getType() );
+
+				const modFnName = 'mod_' + types.join( '_' );
+
+				if ( this.polyfills.has( modFnName ) === false ) {
+
+					this.polyfills.set( modFnName, `fn ${ modFnName }( x: ${ this.getWgslType( types[ 0 ] ) }, y: ${ this.getWgslType( types[ 1 ] ) } ) -> ${ this.getWgslType( types[ 0 ] ) } {
+
+	return x - y * floor( x / y );
+
+}` );
+
+				}
+
+				code = `${ modFnName }( ${ snippets.join( ', ' ) } )`;
+
+			} else if ( fnName.startsWith( 'texture' ) ) {
+
+				// Handle texture functions separately due to sampler handling
+
+				code = this.emitTextureAccess( node );
+
+			} else {
+
+				const params = node.params.map( p => this.emitExpression( p ) );
+
+				if ( typeMap[ fnName ] ) {
+
+					// Handle type constructors like vec3(...)
+
+					code = this.getWgslType( fnName );
+
+				} else {
+
+					code = fnName;
+
+				}
+
+				if ( params.length > 0 ) {
+
+					code += '( ' + params.join( ', ' ) + ' )';
+
+				} else {
+
+					code += '()';
+
+				}
+
+			}
+
+		} else if ( node.isReturn ) {
+
+			code = 'return';
+
+			if ( node.value ) {
+
+				code += ' ' + this.emitExpression( node.value );
+
+			}
+
+		} else if ( node.isDiscard ) {
+
+			code = 'discard';
+
+		} else if ( node.isBreak ) {
+
+			if ( node.parent.isSwitchCase !== true ) {
+
+				code = 'break';
+
+			}
+
+		} else if ( node.isContinue ) {
+
+			code = 'continue';
+
+		} else if ( node.isAccessorElements ) {
+
+			code = this.emitExpression( node.object );
+
+			for ( const element of node.elements ) {
+
+				const value = this.emitExpression( element.value );
+
+				if ( element.isStaticElement ) {
+
+					code += '.' + value;
+
+				} else if ( element.isDynamicElement ) {
+
+					code += `[${value}]`;
+
+				}
+
+			}
+
+		} else if ( node.isFor ) {
+
+			code = this.emitFor( node );
+
+		} else if ( node.isWhile ) {
+
+			code = this.emitWhile( node );
+
+		} else if ( node.isSwitch ) {
+
+			code = this.emitSwitch( node );
+
+		} else if ( node.isVariableDeclaration ) {
+
+			code = this.emitVariables( node );
+
+		} else if ( node.isUniform ) {
+
+			this.uniforms.push( node );
+			return ''; // Defer emission to the header
+
+		} else if ( node.isVarying ) {
+
+			this.varyings.push( node );
+			return ''; // Defer emission to the header
+
+		} else if ( node.isTernary ) {
+
+			const cond = this.emitExpression( node.cond );
+			const left = this.emitExpression( node.left );
+			const right = this.emitExpression( node.right );
+
+			// WGSL's equivalent to the ternary operator is select(false_val, true_val, condition)
+			code = `select( ${ right }, ${ left }, ${ cond } )`;
+
+		} else if ( node.isConditional ) {
+
+			code = this.emitConditional( node );
+
+		} else if ( node.isUnary ) {
+
+			const expr = this.emitExpression( node.expression );
+
+			if ( node.type === '++' || node.type === '--' ) {
+
+				const op = node.type === '++' ? '+' : '-';
+
+				code = `${ expr } = ${ expr } ${ op } 1`;
+
+			} else {
+
+				code = `${ node.type }${ expr }`;
+
+			}
+
+		} else {
+
+			console.warn( 'Unknown node type in WGSL Encoder:', node );
+
+			code = `/* unknown node: ${ node.constructor.name } */`;
+
+		}
+
+		return code;
+
+	}
+
+	emitTextureAccess( node ) {
+
+		const wgslFn = wgslLib[ node.name ];
+		const textureName = this.emitExpression( node.params[ 0 ] );
+		const uv = this.emitExpression( node.params[ 1 ] );
+
+		// WGSL requires explicit samplers. We assume a naming convention.
+		const samplerName = `${textureName}_sampler`;
+
+		let code;
+
+		switch ( node.name ) {
+
+			case 'texture':
+			case 'texture2D':
+			case 'texture3D':
+			case 'textureCube':
+				// format: textureSample(texture, sampler, coords, [offset])
+				code = `${wgslFn}(${textureName}, ${samplerName}, ${uv}`;
+				// Handle optional bias parameter (note: WGSL uses textureSampleBias)
+				if ( node.params.length === 3 ) {
+
+					const bias = this.emitExpression( node.params[ 2 ] );
+					code = `textureSampleBias(${textureName}, ${samplerName}, ${uv}, ${bias})`;
+
+				} else {
+
+					code += ')';
+
+				}
+
+				break;
+
+			case 'textureLod':
+				// format: textureSampleLevel(texture, sampler, coords, level)
+				const lod = this.emitExpression( node.params[ 2 ] );
+				code = `${wgslFn}(${textureName}, ${samplerName}, ${uv}, ${lod})`;
+				break;
+
+			case 'textureGrad':
+				// format: textureSampleGrad(texture, sampler, coords, ddx, ddy)
+				const ddx = this.emitExpression( node.params[ 2 ] );
+				const ddy = this.emitExpression( node.params[ 3 ] );
+				code = `${wgslFn}(${textureName}, ${samplerName}, ${uv}, ${ddx}, ${ddy})`;
+				break;
+
+			case 'texelFetch':
+				// format: textureLoad(texture, coords, [level])
+				const coords = this.emitExpression( node.params[ 1 ] ); // should be ivec
+				const lodFetch = node.params.length > 2 ? this.emitExpression( node.params[ 2 ] ) : '0';
+				code = `${wgslFn}(${textureName}, ${coords}, ${lodFetch})`;
+				break;
+
+			default:
+				code = `/* unsupported texture op: ${node.name} */`;
+
+		}
+
+		return code;
+
+	}
+
+	emitBody( body ) {
+
+		let code = '';
+		this.tab += '\t';
+
+		for ( const statement of body ) {
+
+			code += this.emitExtraLine( statement, body );
+
+			if ( statement.isComment ) {
+
+				code += this.emitComment( statement, body );
+				continue;
+
+			}
+
+			const statementCode = this.emitExpression( statement );
+
+			if ( statementCode ) {
+
+				code += this.tab + statementCode;
+
+				if ( ! statementCode.endsWith( '}' ) && ! statementCode.endsWith( '{' ) ) {
+
+					code += ';';
+
+				}
+
+				code += '\n';
+
+			}
+
+		}
+
+		this.tab = this.tab.slice( 0, - 1 );
+		return code.slice( 0, - 1 ); // remove the last extra line
+
+	}
+
+	emitConditional( node ) {
+
+		const condStr = this.emitExpression( node.cond );
+		const bodyStr = this.emitBody( node.body );
+
+		let ifStr = `if ( ${ condStr } ) {\n\n${ bodyStr }\n\n${ this.tab }}`;
+
+		let current = node;
+
+		while ( current.elseConditional ) {
+
+			current = current.elseConditional;
+			const elseBodyStr = this.emitBody( current.body );
+
+			if ( current.cond ) { // This is an 'else if'
+
+				const elseCondStr = this.emitExpression( current.cond );
+
+				ifStr += ` else if ( ${ elseCondStr } ) {\n\n${ elseBodyStr }\n\n${ this.tab }}`;
+
+			} else { // This is an 'else'
+
+				ifStr += ` else {\n\n${ elseBodyStr }\n\n${ this.tab }}`;
+
+			}
+
+		}
+
+		return ifStr;
+
+	}
+
+	emitFor( node ) {
+
+		const init = this.emitExpression( node.initialization );
+		const cond = this.emitExpression( node.condition );
+		const after = this.emitExpression( node.afterthought );
+		const body = this.emitBody( node.body );
+
+		return `for ( ${ init }; ${ cond }; ${ after } ) {\n\n${ body }\n\n${ this.tab }}`;
+
+	}
+
+	emitWhile( node ) {
+
+		const cond = this.emitExpression( node.condition );
+		const body = this.emitBody( node.body );
+
+		return `while ( ${ cond } ) {\n\n${ body }\n\n${ this.tab }}`;
+
+	}
+
+	emitSwitch( node ) {
+
+		const discriminant = this.emitExpression( node.discriminant );
+
+		let switchStr = `switch ( ${ discriminant } ) {\n\n`;
+
+		this.tab += '\t';
+
+		for ( const switchCase of node.cases ) {
+
+			const body = this.emitBody( switchCase.body );
+
+			if ( switchCase.isDefault ) {
+
+				switchStr += `${ this.tab }default: {\n\n${ body }\n\n${ this.tab }}\n\n`;
+
+			} else {
+
+				const cases = switchCase.conditions.map( c => this.emitExpression( c ) ).join( ', ' );
+
+				switchStr += `${ this.tab }case ${ cases }: {\n\n${ body }\n\n${ this.tab }}\n\n`;
+
+			}
+
+		}
+
+		this.tab = this.tab.slice( 0, - 1 );
+
+		switchStr += `${this.tab}}`;
+
+		return switchStr;
+
+	}
+
+	emitVariables( node ) {
+
+		const declarations = [];
+
+		let current = node;
+
+		while ( current ) {
+
+			const type = this.getWgslType( current.type );
+
+			let valueStr = '';
+
+			if ( current.value ) {
+
+				valueStr = ` = ${this.emitExpression( current.value )}`;
+
+			}
+
+			// The AST linker tracks if a variable is ever reassigned.
+			// If so, use 'var'; otherwise, use 'let'.
+
+			let keyword;
+
+			if ( current.linker ) {
+
+				if ( current.linker.assignments.length > 0 ) {
+
+					keyword = 'var'; // Reassigned variable
+
+				} else {
+
+					if ( current.value && current.value.isNumericExpression ) {
+
+						keyword = 'const'; // Immutable numeric expression
+
+					} else {
+
+						keyword = 'let'; // Immutable variable
+
+					}
+
+				}
+
+			}
+
+			declarations.push( `${ keyword } ${ current.name }: ${ type }${ valueStr }` );
+
+			current = current.next;
+
+		}
+
+		// In WGSL, multiple declarations in one line are not supported, so join with semicolons.
+		return declarations.join( ';\n' + this.tab );
+
+	}
+
+	emitFunction( node ) {
+
+		const name = node.name;
+		const returnType = this.getWgslType( node.type );
+
+		const params = [];
+		// We will prepend to a copy of the body, not the original AST node.
+		const body = [ ...node.body ];
+
+		for ( const param of node.params ) {
+
+			const paramName = param.name;
+			let paramType = this.getWgslType( param.type );
+
+			// Handle 'inout' and 'out' qualifiers using pointers. They are already mutable.
+			if ( param.qualifier === 'inout' || param.qualifier === 'out' ) {
+
+				paramType = `ptr<function, ${paramType}>`;
+				params.push( `${paramName}: ${paramType}` );
+				continue;
+
+			}
+
+			// If the parameter is reassigned within the function, we need to
+			// create a local, mutable variable that shadows the parameter's name.
+			if ( param.linker && param.linker.assignments.length > 0 ) {
+
+				// 1. Rename the incoming parameter to avoid name collision.
+				const immutableParamName = `${paramName}_in`;
+				params.push( `${immutableParamName}: ${paramType}` );
+
+				// 2. Create a new Accessor node for the renamed immutable parameter.
+				const immutableAccessor = new Accessor( immutableParamName );
+				immutableAccessor.isAccessor = true;
+				immutableAccessor.property = immutableParamName;
+
+				// 3. Create a new VariableDeclaration node for the mutable local variable.
+				// This new variable will have the original parameter's name.
+				const mutableVar = new VariableDeclaration( param.type, param.name, immutableAccessor );
+
+				// 4. Mark this new variable as mutable so `emitVariables` uses `var`.
+				mutableVar.linker = { assignments: [ true ] };
+
+				// 5. Prepend this new declaration to the function's body.
+				body.unshift( mutableVar );
+
+			} else {
+
+				// This parameter is not reassigned, so treat it as a normal immutable parameter.
+				params.push( `${paramName}: ${paramType}` );
+
+			}
+
+		}
+
+		const paramsStr = params.length > 0 ? ' ' + params.join( ', ' ) + ' ' : '';
+		const returnStr = ( returnType && returnType !== 'void' ) ? ` -> ${returnType}` : '';
+
+		// Emit the function body, which now includes our injected variable declarations.
+		const bodyStr = this.emitBody( body );
+
+		return `fn ${name}(${paramsStr})${returnStr} {\n\n${bodyStr}\n\n${this.tab}}`;
+
+	}
+
+	emitComment( statement, body ) {
+
+		const index = body.indexOf( statement );
+		const previous = body[ index - 1 ];
+		const next = body[ index + 1 ];
+
+		let output = '';
+
+		if ( previous && isExpression( previous ) ) {
+
+			output += '\n';
+
+		}
+
+		output += this.tab + statement.comment.replace( /\n/g, '\n' + this.tab ) + '\n';
+
+		if ( next && isExpression( next ) ) {
+
+			output += '\n';
+
+		}
+
+		return output;
+
+	}
+
+	emitExtraLine( statement, body ) {
+
+		const index = body.indexOf( statement );
+		const previous = body[ index - 1 ];
+
+		if ( previous === undefined ) return '';
+
+		if ( statement.isReturn ) return '\n';
+
+		const lastExp = isExpression( previous );
+		const currExp = isExpression( statement );
+
+		if ( lastExp !== currExp || ( ! lastExp && ! currExp ) ) return '\n';
+
+		return '';
+
+	}
+
+	emit( ast ) {
+
+		const header = '// Three.js Transpiler r' + REVISION + '\n\n';
+
+		let globals = '';
+		let functions = '';
+		let dependencies = '';
+
+		// 1. Pre-process to find all global declarations
+		for ( const statement of ast.body ) {
+
+			if ( statement.isFunctionDeclaration ) {
+
+				this.functions.set( statement.name, statement );
+
+			} else if ( statement.isUniform ) {
+
+				this.uniforms.push( statement );
+
+			} else if ( statement.isVarying ) {
+
+				this.varyings.push( statement );
+
+			}
+
+		}
+
+		// 2. Build resource bindings (uniforms, textures, samplers)
+		if ( this.uniforms.length > 0 ) {
+
+			let bindingIndex = 0;
+			const uniformStructMembers = [];
+			const textureGlobals = [];
+
+			for ( const uniform of this.uniforms ) {
+
+				// Textures are declared as separate global variables, not in the UBO
+				if ( uniform.type.includes( 'texture' ) ) {
+
+					textureGlobals.push( `@group(${this.groupIndex}) @binding(${bindingIndex ++}) var ${uniform.name}: ${this.getWgslType( uniform.type )};` );
+					textureGlobals.push( `@group(${this.groupIndex}) @binding(${bindingIndex ++}) var ${uniform.name}_sampler: sampler;` );
+
+				} else {
+
+					uniformStructMembers.push( `\t${uniform.name}: ${this.getWgslType( uniform.type )},` );
+
+				}
+
+			}
+
+			// Create a UBO struct if there are any non-texture uniforms
+			if ( uniformStructMembers.length > 0 ) {
+
+				globals += 'struct Uniforms {\n';
+				globals += uniformStructMembers.join( '\n' );
+				globals += '\n};\n';
+				globals += `@group(${this.groupIndex}) @binding(${bindingIndex ++}) var<uniform> uniforms: Uniforms;\n\n`;
+
+			}
+
+			// Add the texture and sampler globals
+			globals += textureGlobals.join( '\n' ) + '\n\n';
+
+		}
+
+		// 3. Build varying structs for stage I/O
+		// This is a simplification; a full implementation would need to know the shader stage.
+		if ( this.varyings.length > 0 ) {
+
+			globals += 'struct Varyings {\n';
+			let location = 0;
+			for ( const varying of this.varyings ) {
+
+				globals += `\t@location(${location ++}) ${varying.name}: ${this.getWgslType( varying.type )},\n`;
+
+			}
+
+			globals += '};\n\n';
+
+		}
+
+		// 4. Emit all functions and other global statements
+		for ( const statement of ast.body ) {
+
+			functions += this.emitExtraLine( statement, ast.body );
+
+			if ( statement.isFunctionDeclaration ) {
+
+				functions += this.emitFunction( statement ) + '\n';
+
+			} else if ( statement.isComment ) {
+
+				functions += this.emitComment( statement, ast.body );
+
+			} else if ( ! statement.isUniform && ! statement.isVarying ) {
+
+				// Handle other top-level statements like 'const'
+				functions += this.emitExpression( statement ) + ';\n';
+
+			}
+
+		}
+
+		// 4. Build dependencies
+		for ( const value of this.polyfills.values() ) {
+
+			dependencies = `${ value }\n\n`;
+
+		}
+
+		return header + dependencies + globals + functions.trimEnd() + '\n';
+
+	}
+
+}
+
+export default WGSLEncoder;

+ 206 - 0
examples/jsm/tsl/display/ChromaticAberrationNode.js

@@ -0,0 +1,206 @@
+import { Vector2, TempNode } from 'three/webgpu';
+import {
+	nodeObject,
+	Fn,
+	uniform,
+	convertToTexture,
+	float,
+	vec4,
+	uv,
+	NodeUpdateType,
+} from 'three/tsl';
+
+/**
+ * Post processing node for applying chromatic aberration effect.
+ * This effect simulates the color fringing that occurs in real camera lenses
+ * by separating and offsetting the red, green, and blue channels.
+ *
+ * @augments TempNode
+ * @three_import import { chromaticAberration } from 'three/addons/tsl/display/ChromaticAberrationNode.js';
+ */
+class ChromaticAberrationNode extends TempNode {
+
+	static get type() {
+
+		return 'ChromaticAberrationNode';
+
+	}
+
+	/**
+	 * Constructs a new chromatic aberration node.
+	 *
+	 * @param {TextureNode} textureNode - The texture node that represents the input of the effect.
+	 * @param {Node} strengthNode - The strength of the chromatic aberration effect as a node.
+	 * @param {Node} centerNode - The center point of the effect as a node.
+	 * @param {Node} scaleNode - The scale factor for stepped scaling from center as a node.
+	 */
+	constructor( textureNode, strengthNode, centerNode, scaleNode ) {
+
+		super( 'vec4' );
+
+		/**
+		 * The texture node that represents the input of the effect.
+		 *
+		 * @type {texture}
+		 */
+		this.textureNode = textureNode;
+
+		/**
+		 * The `updateBeforeType` is set to `NodeUpdateType.FRAME` since the node updates
+		 * its internal uniforms once per frame in `updateBefore()`.
+		 *
+		 * @type {string}
+		 * @default 'frame'
+		 */
+		this.updateBeforeType = NodeUpdateType.FRAME;
+
+		/**
+		 * A node holding the strength of the effect.
+		 *
+		 * @type {Node}
+		 */
+		this.strengthNode = strengthNode;
+
+		/**
+		 * A node holding the center point of the effect.
+		 *
+		 * @type {Node}
+		 */
+		this.centerNode = centerNode;
+
+		/**
+		 * A node holding the scale factor for stepped scaling.
+		 *
+		 * @type {Node}
+		 */
+		this.scaleNode = scaleNode;
+
+		/**
+		 * A uniform node holding the inverse resolution value.
+		 *
+		 * @private
+		 * @type {UniformNode<vec2>}
+		 */
+		this._invSize = uniform( new Vector2() );
+
+	}
+
+	/**
+	 * This method is used to update the effect's uniforms once per frame.
+	 *
+	 * @param {NodeFrame} frame - The current node frame.
+	 */
+	updateBefore( /* frame */ ) {
+
+		const map = this.textureNode.value;
+		this._invSize.value.set( 1 / map.image.width, 1 / map.image.height );
+
+	}
+
+	/**
+	 * This method is used to setup the effect's TSL code.
+	 *
+	 * @param {NodeBuilder} builder - The current node builder.
+	 * @return {ShaderCallNodeInternal}
+	 */
+	setup( /* builder */ ) {
+
+		const textureNode = this.textureNode;
+		const uvNode = textureNode.uvNode || uv();
+
+		const ApplyChromaticAberration = Fn( ( [ uv, strength, center, scale ] ) => {
+
+			// Calculate distance from center
+			const offset = uv.sub( center );
+			const distance = offset.length();
+
+			// Create stepped scaling zones based on distance
+			// Each channel gets different scaling steps
+			const redScale = float( 1.0 ).add( scale.mul( 0.02 ).mul( strength ) ); // Red channel scaled outward
+			const greenScale = float( 1.0 ); // Green stays at original scale
+			const blueScale = float( 1.0 ).sub( scale.mul( 0.02 ).mul( strength ) ); // Blue channel scaled inward
+
+			// Create radial distortion based on distance from center
+			const aberrationStrength = strength.mul( distance );
+
+			// Calculate scaled UV coordinates for each channel
+			const redUV = center.add( offset.mul( redScale ) );
+			const greenUV = center.add( offset.mul( greenScale ) );
+			const blueUV = center.add( offset.mul( blueScale ) );
+
+			// Apply additional chromatic offset based on aberration strength
+			const rOffset = offset.mul( aberrationStrength ).mul( float( 0.01 ) );
+			const gOffset = offset.mul( aberrationStrength ).mul( float( 0.0 ) );
+			const bOffset = offset.mul( aberrationStrength ).mul( float( - 0.01 ) );
+
+			// Final UV coordinates combining scale and chromatic aberration
+			const finalRedUV = redUV.add( rOffset );
+			const finalGreenUV = greenUV.add( gOffset );
+			const finalBlueUV = blueUV.add( bOffset );
+
+			// Sample texture for each channel
+			const r = textureNode.sample( finalRedUV ).r;
+			const g = textureNode.sample( finalGreenUV ).g;
+			const b = textureNode.sample( finalBlueUV ).b;
+
+			// Get original alpha
+			const a = textureNode.sample( uv ).a;
+
+			return vec4( r, g, b, a );
+
+		} ).setLayout( {
+			name: 'ChromaticAberrationShader',
+			type: 'vec4',
+			inputs: [
+				{ name: 'uv', type: 'vec2' },
+				{ name: 'strength', type: 'float' },
+				{ name: 'center', type: 'vec2' },
+				{ name: 'scale', type: 'float' },
+				{ name: 'invSize', type: 'vec2' }
+			]
+		} );
+
+		const chromaticAberrationFn = Fn( () => {
+
+			return ApplyChromaticAberration(
+				uvNode,
+				this.strengthNode,
+				this.centerNode,
+				this.scaleNode,
+				this._invSize
+			);
+
+		} );
+
+		const outputNode = chromaticAberrationFn();
+
+		return outputNode;
+
+	}
+
+}
+
+export default ChromaticAberrationNode;
+
+/**
+ * TSL function for creating a chromatic aberration node for post processing.
+ *
+ * @tsl
+ * @function
+ * @param {Node<vec4>} node - The node that represents the input of the effect.
+ * @param {Node|number} [strength=1.0] - The strength of the chromatic aberration effect as a node or value.
+ * @param {Node|Vector2} [center=null] - The center point of the effect as a node or value. If null, uses screen center (0.5, 0.5).
+ * @param {Node|number} [scale=1.1] - The scale factor for stepped scaling from center as a node or value.
+ * @returns {ChromaticAberrationNode}
+ */
+export const chromaticAberration = ( node, strength = 1.0, center = null, scale = 1.1 ) => {
+
+	return nodeObject(
+		new ChromaticAberrationNode(
+			convertToTexture( node ),
+			nodeObject( strength ),
+			nodeObject( center ),
+			nodeObject( scale )
+		)
+	);
+};

+ 3 - 3
examples/jsm/tsl/display/GaussianBlurNode.js

@@ -1,5 +1,5 @@
 import { RenderTarget, Vector2, NodeMaterial, RendererUtils, QuadMesh, TempNode, NodeUpdateType } from 'three/webgpu';
-import { nodeObject, Fn, float, uv, uniform, convertToTexture, vec2, vec4, passTexture, mul, premult, unpremult } from 'three/tsl';
+import { nodeObject, Fn, float, uv, uniform, convertToTexture, vec2, vec4, passTexture, mul, premultiplyAlpha, unpremultiplyAlpha } from 'three/tsl';
 
 const _quadMesh = /*@__PURE__*/ new QuadMesh();
 
@@ -249,8 +249,8 @@ class GaussianBlurNode extends TempNode {
 
 			// https://lisyarus.github.io/blog/posts/blur-coefficients-generator.html
 
-			sampleTexture = ( uv ) => premult( textureNode.sample( uv ) );
-			output = ( color ) => unpremult( color );
+			sampleTexture = ( uv ) => premultiplyAlpha( textureNode.sample( uv ) );
+			output = ( color ) => unpremultiplyAlpha( color );
 
 		} else {
 

+ 2 - 2
examples/jsm/tsl/display/SSAAPassNode.js

@@ -1,5 +1,5 @@
 import { AdditiveBlending, Color, Vector2, RendererUtils, PassNode, QuadMesh, NodeMaterial } from 'three/webgpu';
-import { nodeObject, uniform, mrt, texture, getTextureIndex } from 'three/tsl';
+import { nodeObject, uniform, mrt, texture, getTextureIndex, unpremultiplyAlpha } from 'three/tsl';
 
 const _size = /*@__PURE__*/ new Vector2();
 
@@ -277,7 +277,7 @@ class SSAAPassNode extends PassNode {
 		}
 
 		this._quadMesh.material = new NodeMaterial();
-		this._quadMesh.material.fragmentNode = sampleTexture;
+		this._quadMesh.material.fragmentNode = unpremultiplyAlpha( sampleTexture );
 		this._quadMesh.material.transparent = true;
 		this._quadMesh.material.depthTest = false;
 		this._quadMesh.material.depthWrite = false;

+ 3 - 3
examples/jsm/tsl/display/hashBlur.js

@@ -1,4 +1,4 @@
-import { float, Fn, vec2, uv, sin, rand, degrees, cos, Loop, vec4, premult, unpremult } from 'three/tsl';
+import { float, Fn, vec2, uv, sin, rand, degrees, cos, Loop, vec4, premultiplyAlpha, unpremultiplyAlpha } from 'three/tsl';
 
 /**
  * Applies a hash blur effect to the given texture node.
@@ -34,7 +34,7 @@ export const hashBlur = /*#__PURE__*/ Fn( ( [ textureNode, bluramount = float( 0
 
 		}
 
-		return premultipliedAlpha ? premult( sample ) : sample;
+		return premultipliedAlpha ? premultiplyAlpha( sample ) : sample;
 
 	};
 
@@ -51,6 +51,6 @@ export const hashBlur = /*#__PURE__*/ Fn( ( [ textureNode, bluramount = float( 0
 
 	blurred_image.divAssign( repeats );
 
-	return premultipliedAlpha ? unpremult( blurred_image ) : blurred_image;
+	return premultipliedAlpha ? unpremultiplyAlpha( blurred_image ) : blurred_image;
 
 } );

+ 1 - 1
examples/misc_exporter_usdz.html

@@ -145,7 +145,7 @@
 
 				const geometry = new THREE.PlaneGeometry();
 				const material = new THREE.MeshBasicMaterial( {
-					map: shadowTexture, blending: THREE.MultiplyBlending, toneMapped: false
+					map: shadowTexture, blending: THREE.MultiplyBlending, toneMapped: false, premultipliedAlpha: true
 				} );
 
 				const mesh = new THREE.Mesh( geometry, material );

BIN
examples/models/gltf/pool.glb


+ 20 - 3
examples/physics_rapier_basic.html

@@ -34,7 +34,7 @@
 			import { RapierHelper } from 'three/addons/helpers/RapierHelper.js';
 			import Stats from 'three/addons/libs/stats.module.js';
 
-			let camera, scene, renderer, stats;
+			let camera, scene, renderer, stats, controls;
 			let physics, physicsHelper;
 
 			init();
@@ -77,7 +77,8 @@
 				document.body.appendChild( renderer.domElement );
 				renderer.setAnimationLoop( animate );
 
-				const controls = new OrbitControls( camera, renderer.domElement );
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.enableDamping = true;
 				controls.target = new THREE.Vector3( 0, 2, 0 );
 				controls.update();
 
@@ -156,11 +157,27 @@
 
 			}
 
-
 			function animate() {
 
+				for ( const object of scene.children ) {
+
+					if ( object.isMesh ) {
+
+						if ( object.position.y < - 10 ) {
+
+							scene.remove( object );
+							physics.removeMesh( object );
+
+						}
+			
+					}
+
+				}
+
 				if ( physicsHelper ) physicsHelper.update();
 
+				controls.update();
+
 				renderer.render( scene, camera );
 
 				stats.update();

BIN
examples/screenshots/webgl_batch_lod_bvh.jpg


BIN
examples/screenshots/webgl_buffergeometry_glbufferattribute.jpg


BIN
examples/screenshots/webgl_geometries.jpg


BIN
examples/screenshots/webgl_geometries_parametric.jpg


BIN
examples/screenshots/webgl_loader_obj.jpg


BIN
examples/screenshots/webgl_loader_obj_mtl.jpg


BIN
examples/screenshots/webgl_water.jpg


BIN
examples/screenshots/webgl_water_flowmap.jpg


BIN
examples/screenshots/webgpu_compute_birds.jpg


BIN
examples/screenshots/webgpu_custom_fog_background.jpg


BIN
examples/screenshots/webgpu_instance_path.jpg


BIN
examples/screenshots/webgpu_materials_toon.jpg


BIN
examples/screenshots/webgpu_postprocessing_ao.jpg


BIN
examples/screenshots/webgpu_postprocessing_ca.jpg


BIN
examples/screenshots/webgpu_reflection.jpg


BIN
examples/screenshots/webgpu_reflection_roughness.jpg


BIN
examples/screenshots/webgpu_sandbox.jpg


BIN
examples/screenshots/webgpu_skinning_instancing.jpg


BIN
examples/screenshots/webgpu_water.jpg


+ 2 - 1
examples/tags.json

@@ -13,6 +13,7 @@
 	"physics_rapier_joints": [ "external" ],
 	"physics_rapier_character_controller": [ "external" ],
 	"physics_rapier_vehicle_controller": [ "external" ],
+	"webgl_batch_lod_bvh": [ "external", "accelerate", "performance", "extension", "plugin", "library", "three.ez" ],
 	"webgl_clipping": [ "solid" ],
 	"webgl_clipping_advanced": [ "solid" ],
 	"webgl_clipping_intersection": [ "solid" ],
@@ -21,7 +22,6 @@
 	"webgl_depth_texture": [ "renderTarget" ],
 	"webgl_framebuffer_texture": [ "renderTarget" ],
 	"webgl_geometries": [ "geometry" ],
-	"webgl_geometries_parametric": [ "geometry" ],
 	"webgl_geometry_colors_lookuptable": [ "vertex" ],
 	"webgl_geometry_csg": [ "external", "csg", "bvh", "constructive", "solid", "geometry", "games", "level" ],
 	"webgl_geometry_nurbs": [ "curve", "surface" ],
@@ -148,6 +148,7 @@
 	"webgpu_postprocessing_dof": [ "bokeh" ],
 	"webgpu_postprocessing_fxaa": [ "msaa", "multisampled" ],
 	"webgpu_postprocessing_motion_blur": [ "mrt" ],
+	"webgpu_postprocessing_ca": [ "chromatic aberration" ],
 	"webgpu_postprocessing_sobel": [ "filter", "edge detection" ],
 	"webgpu_postprocessing_ssaa": [ "msaa", "multisampled" ],
 	"webgpu_refraction": [ "water" ],

BIN
examples/textures/equirectangular/moonless_golf_2k.hdr.jpg


+ 280 - 0
examples/webgl_batch_lod_bvh.html

@@ -0,0 +1,280 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js raycaster - batch - lod - bvh</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">
+		<style>
+			a {
+				text-decoration: underline;
+			}
+		</style>
+	</head>
+
+	<body>
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> batch lod bvh - <a href="https://github.com/agargaro/batched-mesh-extensions" target="_blank" rel="noopener">@three.ez/batched-mesh-extensions</a><br/>
+			BatchedMesh with 10 geometries and 500k instances.<br>
+			Each geometry has 5 LODs (4 generated with meshoptimizer). <br>
+			Frustum culling and raycasting are accelerated by using BVHs (TLAS & BLAS). <br>
+		</div>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/",
+
+					"three-mesh-bvh": "https://cdn.jsdelivr.net/npm/three-mesh-bvh@0.9.0/build/index.module.js",
+
+					"@three.ez/batched-mesh-extensions": "https://cdn.jsdelivr.net/npm/@three.ez/batched-mesh-extensions@0.0.8/build/webgl.js",
+					"bvh.js": "https://cdn.jsdelivr.net/npm/bvh.js@0.0.13/build/index.js",
+
+					"@three.ez/simplify-geometry": "https://cdn.jsdelivr.net/npm/@three.ez/simplify-geometry@0.0.1/build/index.js",
+					"meshoptimizer": "https://cdn.jsdelivr.net/npm/meshoptimizer@0.23.0/+esm"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+			import Stats from 'three/addons/libs/stats.module.js';
+			import { MapControls } from 'three/addons/controls/MapControls.js';
+			import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
+			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+			import { acceleratedRaycast, computeBatchedBoundsTree } from 'three-mesh-bvh';
+
+			import { createRadixSort, extendBatchedMeshPrototype, getBatchedMeshLODCount } from '@three.ez/batched-mesh-extensions';
+			import { performanceRangeLOD, simplifyGeometriesByErrorLOD } from '@three.ez/simplify-geometry';
+
+			// add and override BatchedMesh methods ( @three.ez/batched-mesh-extensions )
+			extendBatchedMeshPrototype();
+
+			// add the extension functions ( three-mesh-bvh )
+			THREE.Mesh.prototype.raycast = acceleratedRaycast;
+			THREE.BatchedMesh.prototype.computeBoundsTree = computeBatchedBoundsTree;
+
+			let stats;
+			let camera, scene, renderer;
+
+			const instancesCount = 500000;
+			let batchedMesh;
+			let lastHoveredInstance = null;
+			const lastHoveredColor = new THREE.Color();
+			const highlight = new THREE.Color( 'red' );
+
+			const raycaster = new THREE.Raycaster();
+			const mouse = new THREE.Vector2( 1, 1 );
+			const position = new THREE.Vector3();
+			const quaternion = new THREE.Quaternion();
+			const scale = new THREE.Vector3( 1, 1, 1 );
+			const matrix = new THREE.Matrix4();
+			const color = new THREE.Color();
+
+			init();
+
+			async function init() {
+
+				// renderer
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.toneMapping = THREE.ACESFilmicToneMapping;
+				renderer.toneMappingExposure = 0.8;
+				document.body.appendChild( renderer.domElement );
+
+				//
+
+				scene = new THREE.Scene();
+
+				const pmremGenerator = new THREE.PMREMGenerator( renderer );
+				scene.environment = pmremGenerator.fromScene( new RoomEnvironment(), 0.04 ).texture;
+
+				//
+
+				camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.1, 1000 );
+				camera.position.set( 0, 20, 55 );
+
+				//
+
+				raycaster.firstHitOnly = true;
+
+				stats = new Stats();
+				document.body.appendChild( stats.dom );
+
+				const controls = new MapControls( camera, renderer.domElement );
+				controls.maxPolarAngle = Math.PI / 2;
+
+				const geometries = [
+					new THREE.TorusKnotGeometry( 1, 0.4, 256, 32, 1, 1 ),
+					new THREE.TorusKnotGeometry( 1, 0.4, 256, 32, 1, 2 ),
+					new THREE.TorusKnotGeometry( 1, 0.4, 256, 32, 1, 3 ),
+					new THREE.TorusKnotGeometry( 1, 0.4, 256, 32, 1, 4 ),
+					new THREE.TorusKnotGeometry( 1, 0.4, 256, 32, 1, 5 ),
+					new THREE.TorusKnotGeometry( 1, 0.4, 256, 32, 2, 1 ),
+					new THREE.TorusKnotGeometry( 1, 0.4, 256, 32, 2, 3 ),
+					new THREE.TorusKnotGeometry( 1, 0.4, 256, 32, 3, 1 ),
+					new THREE.TorusKnotGeometry( 1, 0.4, 256, 32, 4, 1 ),
+					new THREE.TorusKnotGeometry( 1, 0.4, 256, 32, 5, 3 )
+				];
+
+				// generate 4 LODs (levels of detail) for each geometry
+				const geometriesLODArray = await simplifyGeometriesByErrorLOD( geometries, 4, performanceRangeLOD );
+
+				// create BatchedMesh
+				const { vertexCount, indexCount, LODIndexCount } = getBatchedMeshLODCount( geometriesLODArray );
+				batchedMesh = new THREE.BatchedMesh( instancesCount, vertexCount, indexCount, new THREE.MeshStandardMaterial( { metalness: 1, roughness: 0.8 } ) );
+			
+				// enable radix sort for better performance
+				batchedMesh.customSort = createRadixSort( batchedMesh );
+
+				// add geometries and their LODs to the batched mesh ( all LODs share the same position array )
+				for ( let i = 0; i < geometriesLODArray.length; i ++ ) {
+
+					const geometryLOD = geometriesLODArray[ i ];
+					const geometryId = batchedMesh.addGeometry( geometryLOD[ 0 ], - 1, LODIndexCount[ i ] );
+					batchedMesh.addGeometryLOD( geometryId, geometryLOD[ 1 ], 50 );
+					batchedMesh.addGeometryLOD( geometryId, geometryLOD[ 2 ], 100 );
+					batchedMesh.addGeometryLOD( geometryId, geometryLOD[ 3 ], 125 );
+					batchedMesh.addGeometryLOD( geometryId, geometryLOD[ 4 ], 200 );
+			
+				}
+
+				// place instances in a 2D grid with randomized rotation and color
+				const sqrtCount = Math.ceil( Math.sqrt( instancesCount ) );
+				const size = 5.5;
+				const start = ( sqrtCount / - 2 * size ) + ( size / 2 );
+
+				for ( let i = 0; i < instancesCount; i ++ ) {
+
+					const r = Math.floor( i / sqrtCount );
+					const c = i % sqrtCount;
+					const id = batchedMesh.addInstance( Math.floor( Math.random() * geometriesLODArray.length ) );
+					position.set( c * size + start, 0, r * size + start );
+					quaternion.random();
+					batchedMesh.setMatrixAt( id, matrix.compose( position, quaternion, scale ) );
+					batchedMesh.setColorAt( id, color.setHSL( Math.random(), 0.6, 0.5 ) );
+			
+				}
+
+				// compute blas (bottom-level acceleration structure) bvh ( three-mesh-bvh )
+				batchedMesh.computeBoundsTree();
+
+				// compute tlas (top-level acceleration structure) bvh ( @three.ez/batched-mesh-extensions )
+				batchedMesh.computeBVH( THREE.WebGLCoordinateSystem );
+			
+				scene.add( batchedMesh );
+			
+				// set up gui
+				const config = {
+					freeze: false,
+					useBVH: true,
+					useLOD: true
+				};
+			
+				const bvh = batchedMesh.bvh;
+				const lods = batchedMesh._geometryInfo.map( x => x.LOD );
+				const onBeforeRender = batchedMesh.onBeforeRender;
+			
+				const gui = new GUI();
+
+				gui.add( batchedMesh, 'instanceCount' ).disable();
+			
+				gui.add( config, 'freeze' ).onChange( v => {
+
+					batchedMesh.onBeforeRender = v ? () => {} : onBeforeRender;
+
+				} );
+
+				const frustumCullingFolder = gui.addFolder( 'Frustum culling & raycasting' );
+				frustumCullingFolder.add( config, 'useBVH' ).onChange( v => {
+
+					batchedMesh.bvh = v ? bvh : null;
+
+				} );
+
+				const geometriesFolder = gui.addFolder( 'Geometries' );
+				geometriesFolder.add( config, 'useLOD' ).onChange( v => {
+
+					const geometryInfo = batchedMesh._geometryInfo;
+					for ( let i = 0; i < geometryInfo.length; i ++ ) {
+
+						geometryInfo[ i ].LOD = v ? lods[ i ] : null;
+
+					}
+
+				} );
+
+				document.addEventListener( 'pointermove', onPointerMove );
+				window.addEventListener( 'resize', onWindowResize );
+				onWindowResize();
+			
+				renderer.setAnimationLoop( animate );
+
+			}
+
+
+			function onPointerMove( event ) {
+
+				event.preventDefault();
+
+				mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
+				mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
+
+				raycast();
+
+			}
+			
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function raycast() {
+
+				raycaster.setFromCamera( mouse, camera );
+				const intersection = raycaster.intersectObject( batchedMesh );
+
+				const batchId = intersection.length > 0 ? intersection[ 0 ].batchId : null;
+			
+				if ( lastHoveredInstance === batchId ) return;
+
+				if ( lastHoveredInstance ) {
+
+					batchedMesh.setColorAt( lastHoveredInstance, lastHoveredColor );
+			
+				}
+
+				if ( batchId ) {
+
+					batchedMesh.getColorAt( batchId, lastHoveredColor );
+					batchedMesh.setColorAt( batchId, highlight );
+			
+				}
+
+				lastHoveredInstance = batchId;
+
+			}
+
+			function animate() {
+
+				stats.begin();
+
+				renderer.render( scene, camera );
+
+				stats.end();
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 4 - 3
examples/webgl_buffergeometry_glbufferattribute.html

@@ -91,7 +91,8 @@
 
 					color.setRGB( vx, vy, vz, THREE.SRGBColorSpace );
 
-					colors.push( color.r, color.g, color.b );
+					const hex = color.getHex( THREE.LinearSRGBColorSpace );
+					colors.push( hex >> 16 & 255, hex >> 8 & 255, hex & 255 );
 
 				}
 
@@ -107,7 +108,7 @@
 
 				const rgb = gl.createBuffer();
 				gl.bindBuffer( gl.ARRAY_BUFFER, rgb );
-				gl.bufferData( gl.ARRAY_BUFFER, new Float32Array( colors ), gl.STATIC_DRAW );
+				gl.bufferData( gl.ARRAY_BUFFER, new Uint8Array( colors ), gl.STATIC_DRAW );
 
 				const posAttr1 = new THREE.GLBufferAttribute( pos, gl.FLOAT, 3, 4, particles );
 				const posAttr2 = new THREE.GLBufferAttribute( pos2, gl.FLOAT, 3, 4, particles );
@@ -121,7 +122,7 @@
 
 				}, 2000 );
 
-				geometry.setAttribute( 'color', new THREE.GLBufferAttribute( rgb, gl.FLOAT, 3, 4, particles ) );
+				geometry.setAttribute( 'color', new THREE.GLBufferAttribute( rgb, gl.UNSIGNED_BYTE, 3, 1, particles, true ) );
 
 				//
 

+ 45 - 17
examples/webgl_geometries.html

@@ -25,6 +25,9 @@
 
 			import Stats from 'three/addons/libs/stats.module.js';
 
+			import { ParametricGeometry } from 'three/addons/geometries/ParametricGeometry.js';
+			import { plane, klein, mobius } from 'three/addons/geometries/ParametricFunctions.js';
+
 			let camera, scene, renderer, stats;
 
 			init();
@@ -32,11 +35,11 @@
 			function init() {
 
 				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 2000 );
-				camera.position.y = 400;
+				camera.position.y = 500;
 
 				scene = new THREE.Scene();
 
-				let object;
+				let object, geometry;
 
 				const ambientLight = new THREE.AmbientLight( 0xcccccc, 1.5 );
 				scene.add( ambientLight );
@@ -55,43 +58,43 @@
 				//
 
 				object = new THREE.Mesh( new THREE.SphereGeometry( 75, 20, 10 ), material );
-				object.position.set( - 300, 0, 200 );
+				object.position.set( - 300, 0, 300 );
 				scene.add( object );
 
-				object = new THREE.Mesh( new THREE.IcosahedronGeometry( 75, 1 ), material );
-				object.position.set( - 100, 0, 200 );
+				object = new THREE.Mesh( new THREE.IcosahedronGeometry( 75 ), material );
+				object.position.set( - 100, 0, 300 );
 				scene.add( object );
 
-				object = new THREE.Mesh( new THREE.OctahedronGeometry( 75, 2 ), material );
-				object.position.set( 100, 0, 200 );
+				object = new THREE.Mesh( new THREE.OctahedronGeometry( 75 ), material );
+				object.position.set( 100, 0, 300 );
 				scene.add( object );
 
-				object = new THREE.Mesh( new THREE.TetrahedronGeometry( 75, 0 ), material );
-				object.position.set( 300, 0, 200 );
+				object = new THREE.Mesh( new THREE.TetrahedronGeometry( 75 ), material );
+				object.position.set( 300, 0, 300 );
 				scene.add( object );
 
 				//
 
 				object = new THREE.Mesh( new THREE.PlaneGeometry( 100, 100, 4, 4 ), material );
-				object.position.set( - 300, 0, 0 );
+				object.position.set( - 300, 0, 100 );
 				scene.add( object );
 
 				object = new THREE.Mesh( new THREE.BoxGeometry( 100, 100, 100, 4, 4, 4 ), material );
-				object.position.set( - 100, 0, 0 );
+				object.position.set( - 100, 0, 100 );
 				scene.add( object );
 
 				object = new THREE.Mesh( new THREE.CircleGeometry( 50, 20, 0, Math.PI * 2 ), material );
-				object.position.set( 100, 0, 0 );
+				object.position.set( 100, 0, 100 );
 				scene.add( object );
 
 				object = new THREE.Mesh( new THREE.RingGeometry( 10, 50, 20, 5, 0, Math.PI * 2 ), material );
-				object.position.set( 300, 0, 0 );
+				object.position.set( 300, 0, 100 );
 				scene.add( object );
 
 				//
 
 				object = new THREE.Mesh( new THREE.CylinderGeometry( 25, 75, 100, 40, 5 ), material );
-				object.position.set( - 300, 0, - 200 );
+				object.position.set( - 300, 0, - 100 );
 				scene.add( object );
 
 				const points = [];
@@ -103,15 +106,40 @@
 				}
 
 				object = new THREE.Mesh( new THREE.LatheGeometry( points, 20 ), material );
-				object.position.set( - 100, 0, - 200 );
+				object.position.set( - 100, 0, - 100 );
 				scene.add( object );
 
 				object = new THREE.Mesh( new THREE.TorusGeometry( 50, 20, 20, 20 ), material );
-				object.position.set( 100, 0, - 200 );
+				object.position.set( 100, 0, - 100 );
 				scene.add( object );
 
 				object = new THREE.Mesh( new THREE.TorusKnotGeometry( 50, 10, 50, 20 ), material );
-				object.position.set( 300, 0, - 200 );
+				object.position.set( 300, 0, - 100 );
+				scene.add( object );
+
+				//
+
+				object = new THREE.Mesh( new THREE.CapsuleGeometry( 20, 50 ), material );
+				object.position.set( - 300, 0, - 300 );
+				scene.add( object );
+
+				geometry = new ParametricGeometry( plane, 10, 10 );
+				geometry.scale( 100, 100, 100 );
+				geometry.center();
+				object = new THREE.Mesh( geometry, material );
+				object.position.set( - 100, 0, - 300 );
+				scene.add( object );
+
+				geometry = new ParametricGeometry( klein, 20, 20 );
+				object = new THREE.Mesh( geometry, material );
+				object.position.set( 100, 0, - 300 );
+				object.scale.multiplyScalar( 5 );
+				scene.add( object );
+
+				geometry = new ParametricGeometry( mobius, 20, 20 );
+				object = new THREE.Mesh( geometry, material );
+				object.position.set( 300, 0, - 300 );
+				object.scale.multiplyScalar( 30 );
 				scene.add( object );
 
 				//

+ 0 - 144
examples/webgl_geometries_parametric.html

@@ -1,144 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-	<head>
-		<title>three.js webgl - parametric geometries</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="container"></div>
-		<div id="info"><a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgl - parametric geometries</div>
-
-		<script type="importmap">
-			{
-				"imports": {
-					"three": "../build/three.module.js",
-					"three/addons/": "./jsm/"
-				}
-			}
-		</script>
-
-		<script type="module">
-
-			import * as THREE from 'three';
-
-			import Stats from 'three/addons/libs/stats.module.js';
-
-			import { ParametricGeometry } from 'three/addons/geometries/ParametricGeometry.js';
-			import { plane, klein, mobius } from 'three/addons/geometries/ParametricFunctions.js';
-
-			let camera, scene, renderer, stats;
-
-			init();
-
-			function init() {
-
-				const container = document.getElementById( 'container' );
-
-				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 2000 );
-				camera.position.y = 400;
-
-				scene = new THREE.Scene();
-
-				//
-
-				const ambientLight = new THREE.AmbientLight( 0xcccccc, 1.5 );
-				scene.add( ambientLight );
-
-				const pointLight = new THREE.PointLight( 0xffffff, 2.5, 0, 0 );
-				camera.add( pointLight );
-				scene.add( camera );
-
-				//
-
-				const map = new THREE.TextureLoader().load( 'textures/uv_grid_opengl.jpg' );
-				map.wrapS = map.wrapT = THREE.RepeatWrapping;
-				map.anisotropy = 16;
-				map.colorSpace = THREE.SRGBColorSpace;
-
-				const material = new THREE.MeshPhongMaterial( { map: map, side: THREE.DoubleSide } );
-
-				//
-
-				let geometry, object;
-
-				geometry = new ParametricGeometry( plane, 10, 10 );
-				geometry.scale( 100, 100, 100 );
-				geometry.center();
-				object = new THREE.Mesh( geometry, material );
-				object.position.set( - 200, 0, 0 );
-				scene.add( object );
-
-				geometry = new ParametricGeometry( klein, 20, 20 );
-				object = new THREE.Mesh( geometry, material );
-				object.position.set( 0, 0, 0 );
-				object.scale.multiplyScalar( 5 );
-				scene.add( object );
-
-				geometry = new ParametricGeometry( mobius, 20, 20 );
-				object = new THREE.Mesh( geometry, material );
-				object.position.set( 200, 0, 0 );
-				object.scale.multiplyScalar( 30 );
-				scene.add( object );
-
-				//
-
-				renderer = new THREE.WebGLRenderer( { antialias: true } );
-				renderer.setPixelRatio( window.devicePixelRatio );
-				renderer.setSize( window.innerWidth, window.innerHeight );
-				renderer.setAnimationLoop( animate );
-				container.appendChild( renderer.domElement );
-
-				stats = new Stats();
-				container.appendChild( stats.dom );
-
-				window.addEventListener( 'resize', onWindowResize );
-
-			}
-
-			function onWindowResize() {
-
-				camera.aspect = window.innerWidth / window.innerHeight;
-				camera.updateProjectionMatrix();
-
-				renderer.setSize( window.innerWidth, window.innerHeight );
-
-			}
-
-			function animate() {
-
-				render();
-				stats.update();
-
-			}
-
-			function render() {
-
-				const timer = Date.now() * 0.0001;
-
-				camera.position.x = Math.cos( timer ) * 800;
-				camera.position.z = Math.sin( timer ) * 800;
-
-				camera.lookAt( scene.position );
-
-				scene.traverse( function ( object ) {
-
-					if ( object.isMesh === true ) {
-
-						object.rotation.x = timer * 5;
-						object.rotation.y = timer * 2.5;
-
-					}
-
-				} );
-
-				renderer.render( scene, camera );
-
-			}
-
-		</script>
-
-	</body>
-</html>

+ 2 - 1
examples/webgl_geometry_colors.html

@@ -103,7 +103,8 @@
 				const geometry1 = new THREE.IcosahedronGeometry( radius, 1 );
 
 				const count = geometry1.attributes.position.count;
-				geometry1.setAttribute( 'color', new THREE.BufferAttribute( new Float32Array( count * 3 ), 3 ) );
+				const arrayType = ( typeof Float16Array !== 'undefined' ) ? Float16Array : Float32Array;
+				geometry1.setAttribute( 'color', new THREE.BufferAttribute( new arrayType( count * 3 ), 3 ) );
 
 				const geometry2 = geometry1.clone();
 				const geometry3 = geometry1.clone();

+ 13 - 0
examples/webgl_geometry_extrude_shapes.html

@@ -177,6 +177,19 @@
 
 				scene.add( mesh3 );
 
+				//
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
 			}
 
 			function animate() {

+ 20 - 51
examples/webgl_loader_obj.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html lang="en">
 	<head>
-		<title>three.js webgl - loaders - OBJ loader</title>
+		<title>three.js webgl - loaders - OBJ/MTL loader</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">
@@ -9,7 +9,7 @@
 
 	<body>
 		<div id="info">
-		<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - OBJLoader test
+		<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> - OBJ/MTL loader
 		</div>
 
 		<script type="importmap">
@@ -25,16 +25,15 @@
 
 			import * as THREE from 'three';
 
+			import { MTLLoader } from 'three/addons/loaders/MTLLoader.js';
 			import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 
-			let camera, scene, renderer;
-
-			let object;
+			let camera, scene, renderer, controls;
 
 			init();
 
-			function init() {
+			async function init() {
 
 				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 20 );
 				camera.position.z = 2.5;
@@ -50,67 +49,35 @@
 				camera.add( pointLight );
 				scene.add( camera );
 
-				// manager
-
-				function loadModel() {
-
-					object.traverse( function ( child ) {
-
-						if ( child.isMesh ) child.material.map = texture;
-
-					} );
-
-					object.position.y = - 0.95;
-					object.scale.setScalar( 0.01 );
-					scene.add( object );
-
-					render();
-
-				}
-
-				const manager = new THREE.LoadingManager( loadModel );
-
-				// texture
-
-				const textureLoader = new THREE.TextureLoader( manager );
-				const texture = textureLoader.load( 'textures/uv_grid_opengl.jpg', render );
-				texture.colorSpace = THREE.SRGBColorSpace;
-
 				// model
 
-				function onProgress( xhr ) {
-
-					if ( xhr.lengthComputable ) {
-
-						const percentComplete = xhr.loaded / xhr.total * 100;
-						console.log( 'model ' + percentComplete.toFixed( 2 ) + '% downloaded' );
+				const mtlLoader = new MTLLoader().setPath( 'models/obj/male02/' );
+				const materials = await mtlLoader.loadAsync( 'male02.mtl' );
+				materials.preload();
 
-					}
+				const objLoader = new OBJLoader().setPath( 'models/obj/male02/' );
+				objLoader.setMaterials( materials ); // optional since OBJ asstes can be loaded without an accompanying MTL file
 
-				}
-
-				function onError() {}
-
-				const loader = new OBJLoader( manager );
-				loader.load( 'models/obj/male02/male02.obj', function ( obj ) {
+				const object = await objLoader.loadAsync( 'male02.obj' );
 
-					object = obj;
-
-				}, onProgress, onError );
+				object.position.y = - 0.95;
+				object.scale.setScalar( 0.01 );
+				scene.add( object );
 
 				//
 
 				renderer = new THREE.WebGLRenderer( { antialias: true } );
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
 				document.body.appendChild( renderer.domElement );
 
 				//
 
-				const controls = new OrbitControls( camera, renderer.domElement );
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.enableDamping = true;
 				controls.minDistance = 2;
 				controls.maxDistance = 5;
-				controls.addEventListener( 'change', render );
 
 				//
 
@@ -127,7 +94,9 @@
 
 			}
 
-			function render() {
+			function animate() {
+
+				controls.update();
 
 				renderer.render( scene, camera );
 

+ 0 - 125
examples/webgl_loader_obj_mtl.html

@@ -1,125 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-	<head>
-		<title>three.js webgl - OBJLoader + MTLLoader</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> - OBJLoader + MTLLoader
-		</div>
-
-		<script type="importmap">
-			{
-				"imports": {
-					"three": "../build/three.module.js",
-					"three/addons/": "./jsm/"
-				}
-			}
-		</script>
-
-		<script type="module">
-
-			import * as THREE from 'three';
-
-			import { MTLLoader } from 'three/addons/loaders/MTLLoader.js';
-			import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
-			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-
-			let camera, scene, renderer;
-
-			init();
-
-			function init() {
-
-				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 20 );
-				camera.position.z = 2.5;
-
-				// scene
-
-				scene = new THREE.Scene();
-
-				const ambientLight = new THREE.AmbientLight( 0xffffff );
-				scene.add( ambientLight );
-
-				const pointLight = new THREE.PointLight( 0xffffff, 15 );
-				camera.add( pointLight );
-				scene.add( camera );
-
-				// model
-
-				const onProgress = function ( xhr ) {
-
-					if ( xhr.lengthComputable ) {
-
-						const percentComplete = xhr.loaded / xhr.total * 100;
-						console.log( percentComplete.toFixed( 2 ) + '% downloaded' );
-
-					}
-
-				};
-
-				new MTLLoader()
-					.setPath( 'models/obj/male02/' )
-					.load( 'male02.mtl', function ( materials ) {
-
-						materials.preload();
-
-						new OBJLoader()
-							.setMaterials( materials )
-							.setPath( 'models/obj/male02/' )
-							.load( 'male02.obj', function ( object ) {
-
-								object.position.y = - 0.95;
-								object.scale.setScalar( 0.01 );
-								scene.add( object );
-
-							}, onProgress );
-
-					} );
-
-				//
-
-				renderer = new THREE.WebGLRenderer( { antialias: true } );
-				renderer.setPixelRatio( window.devicePixelRatio );
-				renderer.setSize( window.innerWidth, window.innerHeight );
-				renderer.setAnimationLoop( animate );
-				document.body.appendChild( renderer.domElement );
-
-				//
-
-				const controls = new OrbitControls( camera, renderer.domElement );
-				controls.minDistance = 2;
-				controls.maxDistance = 5;
-
-				//
-
-				window.addEventListener( 'resize', onWindowResize );
-
-			}
-
-			function onWindowResize() {
-
-				camera.aspect = window.innerWidth / window.innerHeight;
-				camera.updateProjectionMatrix();
-
-				renderer.setSize( window.innerWidth, window.innerHeight );
-
-			}
-
-			//
-
-			function animate() {
-
-				renderer.render( scene, camera );
-
-			}
-
-
-		</script>
-
-	</body>
-</html>

+ 11 - 11
examples/webgl_loader_texture_lottie.html

@@ -27,11 +27,11 @@
 			import * as THREE from 'three';
 			import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
 			import { RoundedBoxGeometry } from 'three/addons/geometries/RoundedBoxGeometry.js';
-			import { LottieLoader } from 'three/addons/loaders/LottieLoader.js';
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 
-			import lottie from 'https://cdn.jsdelivr.net/npm/lottie-web@5.12.2/+esm';
+			import lottie from 'https://cdn.jsdelivr.net/npm/lottie-web@5.13.0/+esm';
 
-			let renderer, scene, camera;
+			let renderer, scene, camera, controls;
 			let mesh;
 
 			init();
@@ -51,8 +51,9 @@
 				loader.load( 'textures/lottie/24017-lottie-logo-animation.json', function ( data ) {
 
 					const container = document.createElement( 'div' );
-					container.style.width = data.w + 'px';
-					container.style.height = data.h + 'px';
+					const dpr = window.devicePixelRatio;
+					container.style.width = data.w * dpr + 'px';
+					container.style.height = data.h * dpr + 'px';
 					document.body.appendChild( container );
 
 					const animation = lottie.loadAnimation( {
@@ -61,7 +62,7 @@
 						loop: true,
 						autoplay: true,
 						animationData: data,
-						rendererSettings: { dpr: 1 }
+						rendererSettings: { dpr: dpr }
 					} );
 
 					const texture = new THREE.CanvasTexture( animation.container );
@@ -100,6 +101,9 @@
 
 				scene.environment = pmremGenerator.fromScene( environment ).texture;
 
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.autoRotate = true;
+
 				//
 
 				window.addEventListener( 'resize', onWindowResize );
@@ -156,11 +160,7 @@
 
 			function animate() {
 
-				if ( mesh ) {
-
-					mesh.rotation.y -= 0.001;
-
-				}
+				controls.update();
 
 				renderer.render( scene, camera );
 

+ 2 - 0
examples/webgl_materials_blending.html

@@ -102,6 +102,8 @@
 						material.transparent = true;
 						material.blending = blending.constant;
 
+						material.premultipliedAlpha = true;
+
 						const x = ( i - blendings.length / 2 ) * 110;
 						const z = 0;
 

+ 1 - 1
examples/webgl_materials_car.html

@@ -171,7 +171,7 @@
 					const mesh = new THREE.Mesh(
 						new THREE.PlaneGeometry( 0.655 * 4, 1.3 * 4 ),
 						new THREE.MeshBasicMaterial( {
-							map: shadow, blending: THREE.MultiplyBlending, toneMapped: false, transparent: true
+							map: shadow, blending: THREE.MultiplyBlending, toneMapped: false, transparent: true, premultipliedAlpha: true
 						} )
 					);
 					mesh.rotation.x = - Math.PI / 2;

+ 1 - 1
examples/webgl_materials_envmaps_groundprojected.html

@@ -112,7 +112,7 @@
 					const mesh = new THREE.Mesh(
 						new THREE.PlaneGeometry( 0.655 * 4, 1.3 * 4 ),
 						new THREE.MeshBasicMaterial( {
-							map: shadow, blending: THREE.MultiplyBlending, toneMapped: false, transparent: true
+							map: shadow, blending: THREE.MultiplyBlending, toneMapped: false, transparent: true, premultipliedAlpha: true
 						} )
 					);
 					mesh.rotation.x = - Math.PI / 2;

+ 0 - 202
examples/webgl_water.html

@@ -1,202 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-	<head>
-		<title>three.js - water</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="container"></div>
-		<div id="info">
-			<a href="https://threejs.org" target="_blank" rel="noopener noreferrer">three.js</a> - water
-		</div>
-
-		<script type="importmap">
-			{
-				"imports": {
-					"three": "../build/three.module.js",
-					"three/addons/": "./jsm/"
-				}
-			}
-		</script>
-
-		<script type="module">
-
-			import * as THREE from 'three';
-
-			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-			import { Water } from 'three/addons/objects/Water2.js';
-
-			let scene, camera, clock, renderer, water;
-
-			let torusKnot;
-
-			const params = {
-				color: '#ffffff',
-				scale: 4,
-				flowX: 1,
-				flowY: 1
-			};
-
-			init();
-
-			function init() {
-
-				// scene
-
-				scene = new THREE.Scene();
-
-				// camera
-
-				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 200 );
-				camera.position.set( - 15, 7, 15 );
-				camera.lookAt( scene.position );
-
-				// clock
-
-				clock = new THREE.Clock();
-
-				// mesh
-
-				const torusKnotGeometry = new THREE.TorusKnotGeometry( 3, 1, 256, 32 );
-				const torusKnotMaterial = new THREE.MeshNormalMaterial();
-
-				torusKnot = new THREE.Mesh( torusKnotGeometry, torusKnotMaterial );
-				torusKnot.position.y = 4;
-				torusKnot.scale.set( 0.5, 0.5, 0.5 );
-				scene.add( torusKnot );
-
-				// ground
-
-				const groundGeometry = new THREE.PlaneGeometry( 20, 20 );
-				const groundMaterial = new THREE.MeshStandardMaterial( { roughness: 0.8, metalness: 0.4 } );
-				const ground = new THREE.Mesh( groundGeometry, groundMaterial );
-				ground.rotation.x = Math.PI * - 0.5;
-				scene.add( ground );
-
-				const textureLoader = new THREE.TextureLoader();
-				textureLoader.load( 'textures/hardwood2_diffuse.jpg', function ( map ) {
-
-					map.wrapS = THREE.RepeatWrapping;
-					map.wrapT = THREE.RepeatWrapping;
-					map.anisotropy = 16;
-					map.repeat.set( 4, 4 );
-					map.colorSpace = THREE.SRGBColorSpace;
-					groundMaterial.map = map;
-					groundMaterial.needsUpdate = true;
-
-				} );
-
-				// water
-
-				const waterGeometry = new THREE.PlaneGeometry( 20, 20 );
-
-				water = new Water( waterGeometry, {
-					color: params.color,
-					scale: params.scale,
-					flowDirection: new THREE.Vector2( params.flowX, params.flowY ),
-					textureWidth: 1024,
-					textureHeight: 1024
-				} );
-
-				water.position.y = 1;
-				water.rotation.x = Math.PI * - 0.5;
-				scene.add( water );
-
-				// skybox
-
-				const cubeTextureLoader = new THREE.CubeTextureLoader();
-				cubeTextureLoader.setPath( 'textures/cube/Park2/' );
-
-				const cubeTexture = cubeTextureLoader.load( [
-					'posx.jpg', 'negx.jpg',
-					'posy.jpg', 'negy.jpg',
-					'posz.jpg', 'negz.jpg'
-				] );
-
-				scene.background = cubeTexture;
-
-				// light
-
-				const ambientLight = new THREE.AmbientLight( 0xe7e7e7, 1.2 );
-				scene.add( ambientLight );
-
-				const directionalLight = new THREE.DirectionalLight( 0xffffff, 2 );
-				directionalLight.position.set( - 1, 1, 1 );
-				scene.add( directionalLight );
-
-				// renderer
-
-				renderer = new THREE.WebGLRenderer( { antialias: true } );
-				renderer.setSize( window.innerWidth, window.innerHeight );
-				renderer.setPixelRatio( window.devicePixelRatio );
-				renderer.setAnimationLoop( animate );
-				document.body.appendChild( renderer.domElement );
-
-				// gui
-
-				const gui = new GUI();
-
-				gui.addColor( params, 'color' ).onChange( function ( value ) {
-
-					water.material.uniforms[ 'color' ].value.set( value );
-
-				} );
-				gui.add( params, 'scale', 1, 10 ).onChange( function ( value ) {
-
-					water.material.uniforms[ 'config' ].value.w = value;
-
-				} );
-				gui.add( params, 'flowX', - 1, 1 ).step( 0.01 ).onChange( function ( value ) {
-
-					water.material.uniforms[ 'flowDirection' ].value.x = value;
-					water.material.uniforms[ 'flowDirection' ].value.normalize();
-
-				} );
-				gui.add( params, 'flowY', - 1, 1 ).step( 0.01 ).onChange( function ( value ) {
-
-					water.material.uniforms[ 'flowDirection' ].value.y = value;
-					water.material.uniforms[ 'flowDirection' ].value.normalize();
-
-				} );
-
-				gui.open();
-
-				//
-
-				const controls = new OrbitControls( camera, renderer.domElement );
-				controls.minDistance = 5;
-				controls.maxDistance = 50;
-
-				//
-
-				window.addEventListener( 'resize', onWindowResize );
-
-			}
-
-			function onWindowResize() {
-
-				camera.aspect = window.innerWidth / window.innerHeight;
-				camera.updateProjectionMatrix();
-				renderer.setSize( window.innerWidth, window.innerHeight );
-
-			}
-
-			function animate() {
-
-				const delta = clock.getDelta();
-
-				torusKnot.rotation.x += delta;
-				torusKnot.rotation.y += delta * 0.5;
-
-				renderer.render( scene, camera );
-
-			}
-
-		</script>
-
-</body>
-</html>

+ 0 - 139
examples/webgl_water_flowmap.html

@@ -1,139 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-	<head>
-		<title>three.js - water flow map</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="container"></div>
-		<div id="info">
-			<a href="https://threejs.org" target="_blank" rel="noopener noreferrer">three.js</a> - water flow map
-		</div>
-
-		<script type="importmap">
-			{
-				"imports": {
-					"three": "../build/three.module.js",
-					"three/addons/": "./jsm/"
-				}
-			}
-		</script>
-
-		<script type="module">
-
-			import * as THREE from 'three';
-
-			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
-			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-			import { Water } from 'three/addons/objects/Water2.js';
-
-			let scene, camera, renderer, water;
-
-			init();
-
-			function init() {
-
-				// scene
-
-				scene = new THREE.Scene();
-
-				// camera
-
-				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 200 );
-				camera.position.set( 0, 25, 0 );
-				camera.lookAt( scene.position );
-
-				// ground
-
-				const groundGeometry = new THREE.PlaneGeometry( 20, 20, 10, 10 );
-				const groundMaterial = new THREE.MeshBasicMaterial( { color: 0xe7e7e7 } );
-				const ground = new THREE.Mesh( groundGeometry, groundMaterial );
-				ground.rotation.x = Math.PI * - 0.5;
-				scene.add( ground );
-
-				const textureLoader = new THREE.TextureLoader();
-				textureLoader.load( 'textures/floors/FloorsCheckerboard_S_Diffuse.jpg', function ( map ) {
-
-					map.wrapS = THREE.RepeatWrapping;
-					map.wrapT = THREE.RepeatWrapping;
-					map.anisotropy = 16;
-					map.repeat.set( 4, 4 );
-					map.colorSpace = THREE.SRGBColorSpace;
-					groundMaterial.map = map;
-					groundMaterial.needsUpdate = true;
-
-				} );
-
-				// water
-
-				const waterGeometry = new THREE.PlaneGeometry( 20, 20 );
-				const flowMap = textureLoader.load( 'textures/water/Water_1_M_Flow.jpg' );
-
-				water = new Water( waterGeometry, {
-					scale: 2,
-					textureWidth: 1024,
-					textureHeight: 1024,
-					flowMap: flowMap
-				} );
-
-				water.position.y = 1;
-				water.rotation.x = Math.PI * - 0.5;
-				scene.add( water );
-
-				// flow map helper
-
-				const helperGeometry = new THREE.PlaneGeometry( 20, 20 );
-				const helperMaterial = new THREE.MeshBasicMaterial( { map: flowMap } );
-				const helper = new THREE.Mesh( helperGeometry, helperMaterial );
-				helper.position.y = 1.01;
-				helper.rotation.x = Math.PI * - 0.5;
-				helper.visible = false;
-				scene.add( helper );
-
-				// renderer
-
-				renderer = new THREE.WebGLRenderer( { antialias: true } );
-				renderer.setSize( window.innerWidth, window.innerHeight );
-				renderer.setPixelRatio( window.devicePixelRatio );
-				renderer.setAnimationLoop( animate );
-				document.body.appendChild( renderer.domElement );
-
-				//
-
-				const gui = new GUI();
-				gui.add( helper, 'visible' ).name( 'Show Flow Map' );
-				gui.open();
-
-				//
-
-				const controls = new OrbitControls( camera, renderer.domElement );
-				controls.minDistance = 5;
-				controls.maxDistance = 50;
-
-				//
-
-				window.addEventListener( 'resize', onWindowResize );
-
-			}
-
-			function onWindowResize() {
-
-				camera.aspect = window.innerWidth / window.innerHeight;
-				camera.updateProjectionMatrix();
-				renderer.setSize( window.innerWidth, window.innerHeight );
-
-			}
-
-			function animate() {
-
-				renderer.render( scene, camera );
-
-			}
-
-		</script>
-
-</body>
-</html>

+ 3 - 3
examples/webgpu_animation_retargeting.html

@@ -25,7 +25,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { color, screenUV, hue, reflector, time, Fn, vec2, length, atan, float, sin, cos, vec3, sub, mul, pow, blendDodge, normalWorld } from 'three/tsl';
+			import { color, screenUV, hue, reflector, time, Fn, vec2, length, atan, float, sin, cos, vec3, sub, mul, pow, blendDodge, normalWorldGeometry } from 'three/tsl';
 
 			import Stats from 'three/addons/libs/stats.module.js';
 
@@ -84,8 +84,8 @@
 			// background
 
 			const coloredVignette = screenUV.distance( .5 ).mix( hue( color( 0x0175ad ), time.mul( .1 ) ), hue( color( 0x02274f ), time.mul( .5 ) ) );
-			const lightSpeedEffect = lightSpeed( normalWorld ).clamp();
-			const lightSpeedSky = normalWorld.y.remapClamp( - .1, 1 ).mix( 0, lightSpeedEffect );
+			const lightSpeedEffect = lightSpeed( normalWorldGeometry ).clamp();
+			const lightSpeedSky = normalWorldGeometry.y.remapClamp( - .1, 1 ).mix( 0, lightSpeedEffect );
 			const composedBackground = blendDodge( coloredVignette, lightSpeedSky );
 
 			scene.backgroundNode = composedBackground;

+ 6 - 6
examples/webgpu_centroid_sampling.html

@@ -158,8 +158,8 @@
 
 				};
 
-				const withFlatFirstShader = createShader( THREE.InterpolationSamplingType.FLAT, THREE.InterpolationSamplingMode.FLAT_FIRST );
-				const withFlatEitherShader = createShader( THREE.InterpolationSamplingType.FLAT, THREE.InterpolationSamplingMode.FLAT_EITHER );
+				const withFlatFirstShader = createShader( THREE.InterpolationSamplingType.FLAT, THREE.InterpolationSamplingMode.FIRST );
+				const withFlatEitherShader = createShader( THREE.InterpolationSamplingType.FLAT, THREE.InterpolationSamplingMode.EITHER );
 
 				const withSampleShader = Fn( () => {
 
@@ -235,16 +235,16 @@
 					THREE.InterpolationSamplingMode.NORMAL,
 					THREE.InterpolationSamplingMode.CENTROID,
 					THREE.InterpolationSamplingMode.SAMPLE,
-					THREE.InterpolationSamplingMode.FLAT_FIRST,
-					THREE.InterpolationSamplingMode.FLAT_EITHER
+					'flat first',
+					'flat either'
 				] ).onChange( () => {
 
 					const interpolationShaderLib = {
 						[ THREE.InterpolationSamplingMode.NORMAL ]: withoutInterpolationShader,
 						[ THREE.InterpolationSamplingMode.CENTROID ]: withInterpolationShader,
 						[ THREE.InterpolationSamplingMode.SAMPLE ]: withSampleShader,
-						[ THREE.InterpolationSamplingMode.FLAT_FIRST ]: withFlatFirstShader,
-						[ THREE.InterpolationSamplingMode.FLAT_EITHER ]: withFlatEitherShader
+						[ 'flat first' ]: withFlatFirstShader,
+						[ 'flat either' ]: withFlatEitherShader
 					};
 
 					const shader = interpolationShaderLib[ effectController.sampling ];

+ 4 - 4
examples/webgpu_compute_birds.html

@@ -37,7 +37,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { uniform, varying, vec4, add, sub, max, dot, sin, mat3, uint, negate, attributeArray, cameraProjectionMatrix, cameraViewMatrix, positionLocal, modelWorldMatrix, sqrt, attribute, property, float, Fn, If, cos, Loop, Continue, normalize, instanceIndex, length } from 'three/tsl';
+			import { uniform, varying, vec4, add, sub, max, dot, sin, mat3, uint, negate, instancedArray, cameraProjectionMatrix, cameraViewMatrix, positionLocal, modelWorldMatrix, sqrt, attribute, property, float, Fn, If, cos, Loop, Continue, normalize, instanceIndex, length } from 'three/tsl';
 
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 
@@ -210,9 +210,9 @@
 				// Labels applied to storage nodes and uniform nodes are reflected within the shader output,
 				// and are useful for debugging purposes.
 
-				const positionStorage = attributeArray( positionArray, 'vec3' ).label( 'positionStorage' );
-				const velocityStorage = attributeArray( velocityArray, 'vec3' ).label( 'velocityStorage' );
-				const phaseStorage = attributeArray( phaseArray, 'float' ).label( 'phaseStorage' );
+				const positionStorage = instancedArray( positionArray, 'vec3' ).label( 'positionStorage' );
+				const velocityStorage = instancedArray( velocityArray, 'vec3' ).label( 'velocityStorage' );
+				const phaseStorage = instancedArray( phaseArray, 'float' ).label( 'phaseStorage' );
 
 				// The Pixel Buffer Object (PBO) is required to get the GPU computed data in the WebGL2 fallback.
 

+ 10 - 4
examples/webgpu_compute_cloth.html

@@ -62,6 +62,11 @@
 				wind: 1.0,
 			};
 
+			const API = {
+				color: 0x204080, // sRGB
+				sheenColor: 0xffffff // sRGB
+			};
+
 			init();
 
 			async function init() {
@@ -99,12 +104,13 @@
 				gui.add( params, 'wireframe' );
 				gui.add( params, 'sphere' );
 				gui.add( params, 'wind', 0, 5, 0.1 );
+
 				const materialFolder = gui.addFolder( 'material' );
-				materialFolder.addColor( clothMaterial, 'color' );
+				materialFolder.addColor( API, 'color' ).onChange( function ( color ) { clothMaterial.color.setHex( color ); } );
 				materialFolder.add( clothMaterial, 'roughness', 0.0, 1, 0.01 );
 				materialFolder.add( clothMaterial, 'sheen', 0.0, 1, 0.01 );
 				materialFolder.add( clothMaterial, 'sheenRoughness', 0.0, 1, 0.01 );
-				materialFolder.addColor( clothMaterial, 'sheenColor' );
+				materialFolder.addColor( API, 'sheenColor' ).onChange( function ( color ) { clothMaterial.sheenColor.setHex( color ); } );
 
 				window.addEventListener( 'resize', onWindowResize );
 
@@ -452,13 +458,13 @@
 				geometry.setIndex( indices );
 
 				clothMaterial = new THREE.MeshPhysicalNodeMaterial( {
-					color: 0x204080,
+					color: new THREE.Color().setHex( API.color ),
 					side: THREE.DoubleSide,
 					transparent: true,
 					opacity: 0.85,
 					sheen: 1.0,
 					sheenRoughness: 0.5,
-					sheenColor: 0xffffff
+					sheenColor: new THREE.Color().setHex( API.sheenColor ),
 				} );
 
 				clothMaterial.positionNode = Fn( ( { material } ) => {

+ 0 - 6
examples/webgpu_compute_water.html

@@ -266,8 +266,6 @@
 				waterMesh.rotation.x = - Math.PI * 0.5;
 				waterMesh.matrixAutoUpdate = false;
 				waterMesh.updateMatrix();
-				waterMesh.receiveShadow = true;
-				waterMesh.castShadow = true;
 
 				scene.add( waterMesh );
 
@@ -277,8 +275,6 @@
  				borderGeom.rotateY( Math.PI * 0.25 );
  				poolBorder = new THREE.Mesh( borderGeom, new THREE.MeshStandardMaterial( { color: 0x908877, roughness: 0.2 } ) );
  				scene.add( poolBorder );
- 				borderGeom.receiveShadow = true;
- 				borderGeom.castShadow = true;
 
 				// THREE.Mesh just for mouse raycasting
 				const geometryRay = new THREE.PlaneGeometry( BOUNDS, BOUNDS, 1, 1 );
@@ -407,8 +403,6 @@
  				scene.environmentIntensity = 1.25;
 
 				duckModel = model.scene.children[ 0 ];
-				duckModel.receiveShadow = true;
-				duckModel.castShadow = true;
 				duckModel.material.positionNode = Fn( () => {
 
 					const instancePosition = duckInstanceDataStorage.element( instanceIndex ).get( 'position' );

+ 171 - 0
examples/webgpu_instance_path.html

@@ -0,0 +1,171 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - geometry path</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 - geometry path
+		</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 { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+			import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
+
+			import { abs, add, instancedBufferAttribute, positionLocal, mod, time, sin, vec3, select, float, screenUV, color } from 'three/tsl';
+
+			let camera, scene, renderer, controls;
+
+			const count = 1000;
+
+			init();
+
+			async function init() {
+
+				camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.01, 100 );
+				camera.position.z = 15;
+
+				scene = new THREE.Scene();
+				scene.backgroundNode = screenUV.distance( .5 ).remap( 0, 0.65 ).mix( color( 0x94254c ), color( 0x000000 ) );
+
+				// generate a path representing a heart shape
+
+				const x = 0, y = 0;
+
+				const path = new THREE.Path()
+					.moveTo( x - 2.5, y - 2.5 )
+					.bezierCurveTo( x - 2.5, y - 2.5, x - 2, y, x, y )
+					.bezierCurveTo( x + 3, y, x + 3, y - 3.5, x + 3, y - 3.5 )
+					.bezierCurveTo( x + 3, y - 5.5, x + 1, y - 7.7, x - 2.5, y - 9.5 )
+					.bezierCurveTo( x - 6, y - 7.7, x - 8, y - 5.5, x - 8, y - 3.5 )
+					.bezierCurveTo( x - 8, y - 3.5, x - 8, y, x - 5, y )
+					.bezierCurveTo( x - 3.5, y, x - 2.5, y - 2.5, x - 2.5, y - 2.5 );
+
+				// generate instanced ico-spheres along the path
+			
+				const geometry = new THREE.IcosahedronGeometry( 0.1 );
+				const material = new THREE.MeshStandardNodeMaterial();
+
+				const mesh = new THREE.Mesh( geometry, material );
+				mesh.position.set( 2.5, 5, 0 );
+				mesh.count = count;
+				mesh.frustumCulled = false;
+
+				scene.add( mesh );
+
+				// instance data
+
+				const v = new THREE.Vector3();
+				const c = new THREE.Color();
+
+				const positions = [];
+				const times = [];
+				const seeds = [];
+				const colors = [];
+
+				for ( let i = 0; i < count; i ++ ) {
+
+					const t = i / count;
+					path.getPointAt( t, v );
+
+					v.x += ( 0.5 - Math.random() );
+					v.y += ( 0.5 - Math.random() );
+					v.z = ( 0.5 - Math.random() );
+
+					positions.push( v.x, v.y, v.z );
+					times.push( t );
+					seeds.push( Math.random() );
+
+					c.setHSL( 0.75 + ( Math.random() * 0.25 ), 1, 0.4 );
+
+					colors.push( c.r, c.g, c.b );
+
+				}
+
+				const positionAttribute = new THREE.InstancedBufferAttribute( new Float32Array( positions ), 3 );
+				const colorAttribute = new THREE.InstancedBufferAttribute( new Float32Array( colors ), 3 );
+				const timeAttribute = new THREE.InstancedBufferAttribute( new Float32Array( times ), 1 );
+				const seedAttribute = new THREE.InstancedBufferAttribute( new Float32Array( seeds ), 1 );
+
+				// TSL
+
+				const instancePosition = instancedBufferAttribute( positionAttribute );
+				const instanceColor = instancedBufferAttribute( colorAttribute );
+				const instanceSeed = instancedBufferAttribute( seedAttribute );
+				const instanceTime = instancedBufferAttribute( timeAttribute );
+
+				const localTime = instanceTime.add( time );
+				const modTime = mod( time.mul( 0.4 ), 1 );
+			
+				const s0 = sin( localTime.add( instanceSeed ) ).mul( 0.25 );
+			
+				const dist = abs( instanceTime.sub( modTime ) ).toConst(); // modTime and instanceTime are in the range [0,1]
+				const wrapDist = select( dist.greaterThan( 0.5 ), dist.oneMinus(), dist ).toConst(); // the normalized distance should wrap around 0/1
+				const s1 = select( wrapDist.greaterThan( 0.1 ), float( 1 ), wrapDist.remap( 0, 0.1, 3, 1 ) ); // compute a scale in a range around the current interpolated value
+			
+				const offset = vec3( instancePosition.x, instancePosition.y.add( s0 ), instancePosition.z ).toConst( 'offset' );
+				material.positionNode = add( positionLocal.mul( s1 ), offset );
+				material.colorNode = instanceColor;
+			
+				//
+
+				renderer = new THREE.WebGPURenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				renderer.toneMapping = THREE.NeutralToneMapping;
+				document.body.appendChild( renderer.domElement );
+
+				await renderer.init();
+
+				const pmremGenerator = new THREE.PMREMGenerator( renderer );
+				scene.environment = pmremGenerator.fromScene( new RoomEnvironment(), 0.04 ).texture;
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.enableDamping = true;
+
+				//
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				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>

+ 2 - 2
examples/webgpu_mesh_batch.html

@@ -39,7 +39,7 @@
 		import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 		import { radixSort } from 'three/addons/utils/SortUtils.js';
 
-		import { transformedNormalView, directionToColor, diffuseColor } from 'three/tsl';
+		import { normalView, directionToColor, diffuseColor } from 'three/tsl';
 
 		let camera, scene, renderer;
 		let controls, stats;
@@ -127,7 +127,7 @@
 			if ( ! material ) {
 
 				material = new THREE.MeshBasicNodeMaterial();
-				material.outputNode = diffuseColor.mul( directionToColor( transformedNormalView ).y.add( 0.5 ) );
+				material.outputNode = diffuseColor.mul( directionToColor( normalView ).y.add( 0.5 ) );
 		
 	}
 

+ 2 - 2
examples/webgpu_mrt.html

@@ -27,7 +27,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { output, transformedNormalView, pass, step, diffuseColor, emissive, directionToColor, screenUV, mix, mrt, Fn } from 'three/tsl';
+			import { output, normalView, pass, step, diffuseColor, emissive, directionToColor, screenUV, mix, mrt, Fn } from 'three/tsl';
 
 			import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
 
@@ -85,7 +85,7 @@
 				const scenePass = pass( scene, camera, { minFilter: THREE.NearestFilter, magFilter: THREE.NearestFilter } );
 				scenePass.setMRT( mrt( {
 					output: output,
-					normal: directionToColor( transformedNormalView ),
+					normal: directionToColor( normalView ),
 					diffuse: diffuseColor,
 					emissive: emissive
 				} ) );

+ 2 - 2
examples/webgpu_multiple_rendertargets_readback.html

@@ -26,7 +26,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { mix, step, texture, screenUV, mrt, output, transformedNormalWorld, uv, vec2 } from 'three/tsl';
+			import { mix, step, texture, screenUV, mrt, output, normalWorld, uv, vec2 } from 'three/tsl';
 
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 
@@ -85,7 +85,7 @@
 
 				sceneMRT = mrt( {
 					'output': output,
-					'normal': transformedNormalWorld
+					'normal': normalWorld
 				} );
 
 				// Scene

+ 2 - 2
examples/webgpu_pmrem_cubemap.html

@@ -22,7 +22,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { normalWorld, uniform, normalView, positionViewDirection, cameraViewMatrix, pmremTexture } from 'three/tsl';
+			import { normalWorldGeometry, uniform, normalView, positionViewDirection, cameraViewMatrix, pmremTexture } from 'three/tsl';
 
 			import { RGBMLoader } from 'three/addons/loaders/RGBMLoader.js';
 
@@ -70,7 +70,7 @@
 						const pmremRoughness = uniform( .5 );
 						const pmremNode = pmremTexture( map, reflectVec, pmremRoughness );
 
-						scene.backgroundNode = pmremTexture( map, normalWorld, pmremRoughness );
+						scene.backgroundNode = pmremTexture( map, normalWorldGeometry, pmremRoughness );
 
 						scene.add( new THREE.Mesh( new THREE.SphereGeometry( .5, 64, 64 ), new THREE.MeshBasicNodeMaterial( { colorNode: pmremNode } ) ) );
 

+ 2 - 2
examples/webgpu_pmrem_equirectangular.html

@@ -22,7 +22,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { normalWorld, uniform, normalView, positionViewDirection, cameraViewMatrix, pmremTexture } from 'three/tsl';
+			import { normalWorldGeometry, uniform, normalView, positionViewDirection, cameraViewMatrix, pmremTexture } from 'three/tsl';
 
 			import { RGBELoader } from 'three/addons/loaders/RGBELoader.js';
 
@@ -72,7 +72,7 @@
 						const pmremRoughness = uniform( .5 );
 						const pmremNode = pmremTexture( map, reflectVec, pmremRoughness );
 
-						scene.backgroundNode = pmremTexture( map, normalWorld, pmremRoughness );
+						scene.backgroundNode = pmremTexture( map, normalWorldGeometry, pmremRoughness );
 
 						scene.add( new THREE.Mesh( new THREE.SphereGeometry( .5, 64, 64 ), new THREE.MeshBasicNodeMaterial( { colorNode: pmremNode } ) ) );
 

+ 334 - 0
examples/webgpu_postprocessing_ca.html

@@ -0,0 +1,334 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - chromatic aberration</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 - chromatic aberration
+		</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 { pass, renderOutput, uniform } from 'three/tsl';
+
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+			import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
+			import { chromaticAberration } from 'three/addons/tsl/display/ChromaticAberrationNode.js';
+
+			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
+			const params = {
+				enabled: true,
+				animated: true,
+				strength: 1.5,
+				center: new THREE.Vector2(0.5, 0.5),
+				scale: 1.2,
+				autoRotate: true,
+				cameraDistance: 40
+			};
+
+			let camera, scene, renderer, clock, mainGroup;
+			let controls, postProcessing;
+
+			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 );
+
+				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 200 );
+				camera.position.set( 0, 15, params.cameraDistance );
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.enableDamping = true;
+				controls.dampingFactor = 0.1;
+				controls.autoRotate = true;
+				controls.autoRotateSpeed = -0.1;
+				controls.target.set( 0, 0.5, 0 );
+				controls.update();
+
+				scene = new THREE.Scene();
+				scene.background = new THREE.Color( 0x0a0a0a );
+
+				const pmremGenerator = new THREE.PMREMGenerator( renderer );
+				scene.environment = pmremGenerator.fromScene( new RoomEnvironment(), 0.04 ).texture;
+
+				clock = new THREE.Clock();
+
+				// Create main group
+				mainGroup = new THREE.Group();
+				scene.add( mainGroup );
+
+				// Create shapes
+				createShapes();
+
+				// Add a grid for reference
+				const gridHelper = new THREE.GridHelper( 40, 20, 0x444444, 0x222222 );
+				gridHelper.position.y = -10;
+				scene.add( gridHelper );
+
+				// post processing
+				postProcessing = new THREE.PostProcessing( renderer );
+				postProcessing.outputColorTransform = false;
+
+				// scene pass
+				const scenePass = pass( scene, camera );
+				const outputPass = renderOutput( scenePass );
+
+				// Create uniform nodes for the static version that can be updated
+				const staticStrength = uniform( params.strength );
+				const staticCenter = uniform( new THREE.Vector2( params.center.x, params.center.y ) );
+				const staticScale = uniform( params.scale );
+
+				// With static values (using uniform nodes)
+				const caPass = chromaticAberration( outputPass, staticStrength, staticCenter, staticScale );
+
+				// Set initial output based on params
+				postProcessing.outputNode = params.enabled ? caPass : outputPass;
+
+				window.addEventListener( 'resize', onWindowResize );
+
+				// GUI
+
+				const gui = new GUI();
+				gui.title( 'Chromatic Aberration' );
+
+				gui.add( params, 'enabled' ).onChange( ( value ) => {
+
+					postProcessing.outputNode = value ? caPass : outputPass;
+					postProcessing.needsUpdate = true;
+
+				} );
+
+				const staticFolder = gui.addFolder( 'Static Parameters' );
+
+				staticFolder.add( staticStrength, 'value', 0, 3 ).name( 'Strength' );
+				staticFolder.add( staticCenter.value, 'x', - 1, 1 ).name( 'Center X' );
+				staticFolder.add( staticCenter.value, 'y', - 1, 1 ).name( 'Center Y' );
+				staticFolder.add( staticScale, 'value', 0.5, 2 ).name( 'Scale' );
+
+				const animationFolder = gui.addFolder( 'Animation' );
+				animationFolder.add( params, 'animated' );
+				animationFolder.add( params, 'autoRotate' ).onChange( ( value ) => {
+
+					controls.autoRotate = value;
+
+				} );
+
+			}
+
+			function createShapes() {
+
+				const shapes = [];
+				const materials = [];
+
+				// Define colors for different materials
+				const colors = [
+					0xff0000, // Red
+					0x00ff00, // Green
+					0x0000ff, // Blue
+					0xffff00, // Yellow
+					0xff00ff, // Magenta
+					0x00ffff, // Cyan
+					0xffffff, // White
+					0xff8800  // Orange
+				];
+
+				// Create materials
+				colors.forEach( color => {
+
+					materials.push( new THREE.MeshStandardMaterial( {
+						color: color,
+						roughness: 0.2,
+						metalness: 0.8
+					} ) );
+
+				});
+
+				// Create geometries
+				const geometries = [
+					new THREE.BoxGeometry( 3, 3, 3 ),
+					new THREE.SphereGeometry( 2, 32, 16 ),
+					new THREE.ConeGeometry( 2, 4, 8 ),
+					new THREE.CylinderGeometry( 1.5, 1.5, 4, 8 ),
+					new THREE.TorusGeometry( 2, 0.8, 8, 16 ),
+					new THREE.OctahedronGeometry( 2.5 ),
+					new THREE.IcosahedronGeometry( 2.5 ),
+					new THREE.TorusKnotGeometry( 1.5, 0.5, 64, 8 )
+				];
+
+				// Create central showcase
+				const centralGroup = new THREE.Group();
+
+				// Large central torus
+				const centralTorus = new THREE.Mesh(
+					new THREE.TorusGeometry( 5, 1.5, 16, 32 ),
+					new THREE.MeshStandardMaterial( {
+						color: 0xffffff,
+						roughness: 0.1,
+						metalness: 1,
+						emissive: 0x222222
+					} )
+				);
+				centralGroup.add( centralTorus );
+
+				// Inner rotating shapes
+				for ( let i = 0; i < 6; i++ ) {
+
+					const angle = ( i / 6 ) * Math.PI * 2;
+					const radius = 3;
+
+					const mesh = new THREE.Mesh(
+						geometries[ i % geometries.length ],
+						materials[ i % materials.length ]
+					);
+
+					mesh.position.set(
+						Math.cos( angle ) * radius,
+						0,
+						Math.sin( angle ) * radius
+					);
+
+					mesh.scale.setScalar( 0.5 );
+					centralGroup.add( mesh );
+					shapes.push( mesh );
+
+				}
+
+				mainGroup.add( centralGroup );
+				shapes.push( centralGroup );
+
+				// Create outer ring of shapes
+				const numShapes = 12;
+				const outerRadius = 15;
+
+				for ( let i = 0; i < numShapes; i++ ) {
+
+					const angle = ( i / numShapes ) * Math.PI * 2;
+					const shapesGroup = new THREE.Group();
+
+					const geometry = geometries[ i % geometries.length ];
+					const material = materials[ i % materials.length ];
+
+					const mesh = new THREE.Mesh( geometry, material );
+					mesh.castShadow = true;
+					mesh.receiveShadow = true;
+
+					shapesGroup.add( mesh );
+					shapesGroup.position.set(
+						Math.cos( angle ) * outerRadius,
+						Math.sin( i * 0.5 ) * 2,
+						Math.sin( angle ) * outerRadius
+					);
+
+					mainGroup.add( shapesGroup );
+					shapes.push( shapesGroup );
+
+				}
+
+				// Add floating particles
+				const particlesGeometry = new THREE.BufferGeometry();
+				const particlesCount = 200;
+				const positions = new Float32Array( particlesCount * 3 );
+
+				for ( let i = 0; i < particlesCount * 3; i += 3 ) {
+
+					const radius = 25 + Math.random() * 10;
+					const theta = Math.random() * Math.PI * 2;
+					const phi = Math.random() * Math.PI;
+
+					positions[ i ] = radius * Math.sin( phi ) * Math.cos( theta );
+					positions[ i + 1 ] = radius * Math.cos( phi );
+					positions[ i + 2 ] = radius * Math.sin( phi ) * Math.sin( theta );
+
+				}
+
+				particlesGeometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
+
+				const particlesMaterial = new THREE.PointsMaterial( {
+					color: 0xffffff,
+					size: 0.5,
+					sizeAttenuation: true
+				} );
+
+				const particles = new THREE.Points( particlesGeometry, particlesMaterial );
+				mainGroup.add( particles );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				const time = clock.getElapsedTime();
+
+				controls.update();
+
+				if ( params.animated ) {
+
+					// Animate individual shapes
+					mainGroup.children.forEach( ( child, index ) => {
+
+						if ( child.children.length > 0 ) {
+
+							// Central group
+							child.rotation.y = time * 0.5;
+							child.children.forEach( ( subChild, subIndex ) => {
+
+								if ( subChild.geometry ) {
+
+									subChild.rotation.x = time * ( 1 + subIndex * 0.1 );
+									subChild.rotation.z = time * ( 1 - subIndex * 0.1 );
+
+								}
+
+							} );
+
+						} else if ( child.type === 'Group' ) {
+
+							// Outer shapes
+							child.rotation.x = time * 0.5 + index;
+							child.rotation.y = time * 0.3 + index;
+							child.position.y = Math.sin( time + index ) * 2;
+
+						}
+
+					} );
+
+				}
+
+				postProcessing.render();
+			}
+
+		</script>
+
+	</body>
+</html>

+ 22 - 3
examples/webgpu_postprocessing_ssr.html

@@ -11,11 +11,13 @@
 	</head>
 
 <body>
+
 	<div id="info">
 		<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> webgpu - postprocessing - screen space reflections<br />
 		<a href="https://skfb.ly/6tqYD" target="_blank" rel="noopener">Steampunk Camera</a> by 
 		<a href="https://sketchfab.com/lumoize" target="_blank" rel="noopener">dylanheyes</a> is licensed under <a href="https://creativecommons.org/licenses/by/4.0/" target="_blank" rel="noopener">Creative Commons Attribution</a>.<br />
 	</div>
+
 	<script type="importmap">
 		{
 			"imports": {
@@ -28,8 +30,9 @@
 	</script>
 
 	<script type="module">
+
 		import * as THREE from 'three';
-		import { pass, mrt, output, transformedNormalView, metalness, blendColor, screenUV, color } from 'three/tsl';
+		import { pass, mrt, output, normalView, metalness, blendColor, screenUV, color, sample, directionToColor, colorToDirection } from 'three/tsl';
 		import { ssr } from 'three/addons/tsl/display/SSRNode.js';
 		import { smaa } from 'three/addons/tsl/display/SMAANode.js';
 
@@ -109,7 +112,7 @@
 			const scenePass = pass( scene, camera, { minFilter: THREE.NearestFilter, magFilter: THREE.NearestFilter } );
 			scenePass.setMRT( mrt( {
 				output: output,
-				normal: transformedNormalView,
+				normal: directionToColor( normalView ),
 				metalness: metalness
 			} ) );
 
@@ -118,7 +121,23 @@
 			const scenePassDepth = scenePass.getTextureNode( 'depth' );
 			const scenePassMetalness = scenePass.getTextureNode( 'metalness' );
 
-			ssrPass = ssr( scenePassColor, scenePassDepth, scenePassNormal, scenePassMetalness, camera );
+			// optimization bandwidth packing the normals and reducing the texture precision if possible
+
+			const metalnessTexture = scenePass.getTexture( 'metalness' );
+			metalnessTexture.type = THREE.UnsignedByteType;
+
+			const normalTexture = scenePass.getTexture( 'normal' );
+			normalTexture.type = THREE.UnsignedByteType;
+
+			const customNormal = sample( ( uv ) => {
+
+				return colorToDirection( scenePassNormal.sample( uv ) );
+
+			} );
+
+			//
+
+			ssrPass = ssr( scenePassColor, scenePassDepth, customNormal, scenePassMetalness, camera );
 			ssrPass.resolutionScale = 1.0;
 
 			// blend SSR over beauty

+ 2 - 2
examples/webgpu_reflection.html

@@ -26,7 +26,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { color, pass, reflector, normalWorld, texture, uv, screenUV } from 'three/tsl';
+			import { color, pass, reflector, normalWorldGeometry, texture, uv, screenUV } from 'three/tsl';
 			import { gaussianBlur } from 'three/addons/tsl/display/GaussianBlurNode.js';
 
 			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
@@ -50,7 +50,7 @@
 
 				scene = new THREE.Scene();
 				scene.fog = new THREE.Fog( 0x0487e2, 7, 25 );
-				scene.backgroundNode = normalWorld.y.mix( color( 0x0487e2 ), color( 0x0066ff ) );
+				scene.backgroundNode = normalWorldGeometry.y.mix( color( 0x0487e2 ), color( 0x0066ff ) );
 				camera.lookAt( 0, 1, 0 );
 
 				const sunLight = new THREE.DirectionalLight( 0xFFE499, 5 );

+ 167 - 0
examples/webgpu_reflection_roughness.html

@@ -0,0 +1,167 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgpu - roughness reflection</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 - roughness reflection
+		</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 { Fn, vec2, vec4, texture, uv, textureBicubic, rangeFogFactor, reflector, time } from 'three/tsl';
+
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+			import { UltraHDRLoader } from 'three/addons/loaders/UltraHDRLoader.js';
+
+			import Stats from 'three/addons/libs/stats.module.js';
+
+			let camera, scene, renderer;
+			let controls;
+			let stats;
+
+			init();
+
+			async function init() {
+
+				camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.25, 30 );
+				camera.position.set( - 4, 1, 4 );
+
+				scene = new THREE.Scene();
+
+				const loader = new UltraHDRLoader();
+				loader.setDataType( THREE.HalfFloatType );
+				loader.load( `textures/equirectangular/spruit_sunrise_2k.hdr.jpg`, function ( texture ) {
+
+					texture.mapping = THREE.EquirectangularReflectionMapping;
+					texture.needsUpdate = true;
+
+					scene.background = texture;
+					scene.environment = texture;
+
+				} );
+
+				// textures
+
+				const textureLoader = new THREE.TextureLoader();
+
+				const uvMap = textureLoader.load( 'textures/uv_grid_directx.jpg' );
+				uvMap.colorSpace = THREE.SRGBColorSpace;
+
+				const perlinMap = textureLoader.load( './textures/noises/perlin/rgb-256x256.png' );
+				perlinMap.wrapS = THREE.RepeatWrapping;
+				perlinMap.wrapT = THREE.RepeatWrapping;
+				perlinMap.colorSpace = THREE.SRGBColorSpace;
+
+				// uv box for debugging
+				
+				const mesh = new THREE.Mesh(
+					new THREE.BoxGeometry( 1, 1, 1 ),
+					new THREE.MeshStandardNodeMaterial( {
+						map: uvMap,
+						roughnessMap: uvMap,
+						emissiveMap: uvMap,
+						emissive: 0xffffff
+					} )
+				);
+				mesh.position.set( 0, 1.25, 0 );
+				mesh.scale.setScalar( 2 );
+				scene.add( mesh );
+
+				// reflection
+
+				const reflection = reflector( { resolution: .5, bounces: false, generateMipmaps: true } ); // 0.5 is half of the rendering view
+				reflection.target.rotateX( - Math.PI / 2 );
+				scene.add( reflection.target );
+
+				const animatedUV = uv().mul( 10 ).add( vec2( time.mul( .1 ), 0 ) );
+				const roughness = texture( perlinMap, animatedUV ).r.mul( 2 ).saturate();
+
+				const floorMaterial = new THREE.MeshStandardNodeMaterial();
+				floorMaterial.transparent = true;
+				floorMaterial.metalness = 1;
+				floorMaterial.roughnessNode = roughness.mul( .2 );
+				floorMaterial.colorNode = Fn( () => {
+
+					// blur reflection using textureBicubic()
+					const dirtyReflection = textureBicubic( reflection, roughness.mul( .9 ) );
+
+					// falloff opacity by distance like an opacity-fog
+					const opacity = rangeFogFactor( 7, 25 ).oneMinus();
+
+					return vec4( dirtyReflection.rgb, opacity );
+
+				} )();
+
+				const floor = new THREE.Mesh( new THREE.BoxGeometry( 50, .001, 50 ), floorMaterial );
+				floor.position.set( 0, 0, 0 );
+				scene.add( floor );
+
+				// renderer
+
+				renderer = new THREE.WebGPURenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				renderer.toneMapping = THREE.NeutralToneMapping;
+				renderer.toneMappingExposure = 1.5;
+				document.body.appendChild( renderer.domElement );
+
+				stats = new Stats();
+				document.body.appendChild( stats.dom );
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.minDistance = 1;
+				controls.maxDistance = 10;
+				controls.maxPolarAngle = Math.PI / 2;
+				controls.autoRotate = true;
+				controls.autoRotateSpeed = - .1;
+				controls.target.set( 0, .75, 0 );
+				controls.update();
+
+				// events
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate( time ) {
+
+				stats.update();
+
+				controls.update();
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 0 - 1
examples/webgpu_shadertoy.html

@@ -192,7 +192,6 @@
 
 					const encoder = new TSLEncoder();
 					encoder.iife = iife;
-					encoder.uniqueNames = true;
 
 					const jsCode = new Transpiler( decoder, encoder ).parse( glsl );
 

+ 14 - 2
examples/webgpu_skinning_points.html

@@ -78,7 +78,7 @@
 							materialPoints.sizeNode = pointSpeedAttribute.length().exp().min( 5 ).mul( 5 ).add( 1 );
 							materialPoints.sizeAttenuation = false;
 
-							materialPoints.positionNode = Fn( () => {
+							const updateSkinningPoints = Fn( () => {
 
 								const pointPosition = pointPositionArray.element( instanceIndex );
 								const pointSpeed = pointSpeedArray.element( instanceIndex );
@@ -90,9 +90,21 @@
 								pointSpeed.assign( skinningSpeed );
 								pointPosition.assign( skinningWorldPosition );
 
+							}, 'void' );
+
+							materialPoints.positionNode = Fn( () => {
+
+								updateSkinningPoints();
+
 								return pointPositionArray.toAttribute();
 
-							} )().compute( countOfPoints );
+							} )().compute( countOfPoints ).onInit( () => {
+
+								// initialize point positions and speeds
+
+								renderer.compute( updateSkinningPoints().compute( countOfPoints ) );
+
+							} );
 
 							const pointCloud = new THREE.Sprite( materialPoints );
 							pointCloud.count = countOfPoints;

+ 3 - 3
examples/webgpu_tsl_earth.html

@@ -30,7 +30,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { step, normalWorld, output, texture, vec3, vec4, normalize, positionWorld, bumpMap, cameraPosition, color, uniform, mix, uv, max } from 'three/tsl';
+			import { step, normalWorldGeometry, output, texture, vec3, vec4, normalize, positionWorld, bumpMap, cameraPosition, color, uniform, mix, uv, max } from 'three/tsl';
 
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
@@ -79,11 +79,11 @@
 				// fresnel
 
 				const viewDirection = positionWorld.sub( cameraPosition ).normalize();
-				const fresnel = viewDirection.dot( normalWorld ).abs().oneMinus().toVar();
+				const fresnel = viewDirection.dot( normalWorldGeometry ).abs().oneMinus().toVar();
 
 				// sun orientation
 
-				const sunOrientation = normalWorld.dot( normalize( sun.position ) ).toVar();
+				const sunOrientation = normalWorldGeometry.dot( normalize( sun.position ) ).toVar();
 
 				// atmosphere color
 

+ 2 - 2
examples/webgpu_tsl_editor.html

@@ -36,7 +36,7 @@
 		<div id="source"></div>
 		<div id="result"></div>
 		<div id="renderer"></div>
-		<script src="https://cdn.jsdelivr.net/npm/monaco-editor@latest/min/vs/loader.min.js"></script>
+		<script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs/loader.js"></script>
 
 		<script type="importmap">
 			{
@@ -99,7 +99,7 @@
 
 				// editor
 
-				window.require.config( { paths: { 'vs': 'https://cdn.jsdelivr.net/npm/monaco-editor@0.48.0/min/vs' } } );
+				window.require.config( { paths: { 'vs': 'https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs' } } );
 
 				require( [ 'vs/editor/editor.main' ], () => {
 

+ 2 - 4
examples/webgpu_tsl_halftone.html

@@ -145,10 +145,8 @@
 
 					// mask
 
-					const mask = gridUv
-						.sub( 0.5 )
-						.length()
-						.step( orientationStrength.mul( radius ).mul( 0.5 ) )
+					const mask = orientationStrength.mul( radius ).mul( 0.5 )
+						.step( gridUv.sub( 0.5 ).length() )
 						.mul( mix( mixLow, mixHigh, orientationStrength ) );
 
 					return vec4( color, mask );

+ 88 - 11
examples/webgpu_tsl_transpiler.html

@@ -26,7 +26,7 @@
 
 		<div id="source"></div>
 		<div id="result"></div>
-		<script src="https://cdn.jsdelivr.net/npm/monaco-editor@latest/min/vs/loader.min.js"></script>
+		<script src="https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs/loader.js"></script>
 
 		<script type="importmap">
 			{
@@ -43,40 +43,85 @@
 
 			import Transpiler from 'three/addons/transpiler/Transpiler.js';
 			import GLSLDecoder from 'three/addons/transpiler/GLSLDecoder.js';
+			import WGSLEncoder from 'three/addons/transpiler/WGSLEncoder.js';
 			import TSLEncoder from 'three/addons/transpiler/TSLEncoder.js';
 
+			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
 			init();
 
 			function init() {
 
 				// editor
 
-				window.require.config( { paths: { 'vs': 'https://cdn.jsdelivr.net/npm/monaco-editor@0.48.0/min/vs' } } );
+				window.require.config( { paths: { 'vs': 'https://cdn.jsdelivr.net/npm/monaco-editor@0.52.2/min/vs' } } );
 
 				require( [ 'vs/editor/editor.main' ], () => {
 
+					const options = {
+						decoder: 'GLSL',
+						encoder: 'TSL'
+					};
+
+					const encoderLanguages = {
+						'TSL': 'javascript',
+						'WGSL': 'wgsl'
+					};
+
 					let timeout = null;
 
 					const editorDOM = document.getElementById( 'source' );
 					const resultDOM = document.getElementById( 'result' );
 
-					const glslCode = `// Put here your GLSL code to transpile to TSL:
-
-float V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const in float dotNV ) {
+					const glslCode = `/*
+ * Perlin noise
+ * https://gist.github.com/patriciogonzalezvivo/670c22f3966e662d2f83
+ */
 
-	float a2 = pow2( alpha );
+const float PI = 3.141592653589793;
 
-	float gv = dotNL * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNV ) );
-	float gl = dotNV * sqrt( a2 + ( 1.0 - a2 ) * pow2( dotNL ) );
+float rand(vec2 c){
+	return fract(sin(dot(c.xy ,vec2(12.9898,78.233))) * 43758.5453);
+}
 
-	return 0.5 / max( gv + gl, EPSILON );
+float noise(vec2 p, float freq ){
+	float unit = 1./freq;
+	vec2 ij = floor(p/unit);
+	vec2 xy = mod(p,unit)/unit;
+	//xy = 3.*xy*xy-2.*xy*xy*xy;
+	xy = .5*(1.-cos(PI*xy));
+	float a = rand((ij+vec2(0.,0.)));
+	float b = rand((ij+vec2(1.,0.)));
+	float c = rand((ij+vec2(0.,1.)));
+	float d = rand((ij+vec2(1.,1.)));
+	float x1 = mix(a, b, xy.x);
+	float x2 = mix(c, d, xy.x);
+	return mix(x1, x2, xy.y);
+}
 
+float pNoise(vec2 p, int res){
+	float persistance = .5;
+	float n = 0.;
+	float normK = 0.;
+	float f = 4.;
+	float amp = 1.;
+	int iCount = 0;
+	for (int i = 0; i<50; i++){
+		n+=amp*noise(p, f);
+		f*=2.;
+		normK+=amp;
+		amp*=persistance;
+		if (iCount == res) break;
+		iCount++;
+	}
+	float nf = n/normK;
+	return nf*nf*nf*nf;
 }
 `;
 
 					const editor = window.monaco.editor.create( editorDOM, {
 						value: glslCode,
-						language: 'glsl',
+						language: 'c',
 						theme: 'vs-dark',
 						automaticLayout: true,
 						minimap: { enabled: false }
@@ -102,10 +147,27 @@ float V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const i
 
 						try {
 
+							let encoder;
+
+							if ( options.encoder === 'TSL' ) {
+
+								encoder = new TSLEncoder();
+
+							} else if ( options.encoder === 'WGSL' ) {
+
+								encoder = new WGSLEncoder();
+
+							} else {
+
+								throw new Error( 'Unknown encoder: ' + options.encoder );
+
+							}
+
+							//
+
 							const glsl = editor.getValue();
 
 							const decoder = new GLSLDecoder();
-							const encoder = new TSLEncoder();
 
 							const transpiler = new Transpiler( decoder, encoder );
 							const tsl = transpiler.parse( glsl );
@@ -130,6 +192,21 @@ float V_GGX_SmithCorrelated( const in float alpha, const in float dotNL, const i
 
 					} );
 
+					// gui
+
+					const gui = new GUI();
+
+					gui.add( options, 'decoder', [ 'GLSL' ] );
+					gui.add( options, 'encoder', [ 'TSL', 'WGSL' ] ).onChange( ( encoder => {
+
+						const language = encoderLanguages[ encoder ];
+
+						window.monaco.editor.setModelLanguage( result.getModel(), language );
+
+						build();
+
+					} ) );
+
 				} );
 
 			}

+ 2 - 2
examples/webgpu_tsl_vfx_flames.html

@@ -121,7 +121,7 @@
 					const gradientColor = texture( gradient.texture, vec2( shape.remap( 0, 1, 0, 1 ), 0 ) );
 
 					// output
-					const color = mix( gradientColor, vec3( 1 ), shape.step( 0.8 ).oneMinus() );
+					const color = mix( gradientColor, vec3( 1 ), shape.step( 0.8 ) );
 					const alpha = shape.smoothstep( 0, 0.3 );
 					return vec4( color.rgb, alpha );
 
@@ -162,7 +162,7 @@
 					const cellularNoise = texture( cellularTexture, cellularUv, 0 ).r.oneMinus().smoothstep( 0.25, 1 );
 
 					// shape
-					const shape = mainUv.sub( 0.5 ).mul( vec2( 6, 1 ) ).length().step( 0.5 );
+					const shape = step( mainUv.sub( 0.5 ).mul( vec2( 6, 1 ) ).length(), 0.5 );
 					shape.assign( shape.mul( cellularNoise ) );
 					shape.mulAssign( gradient3 );
 					shape.assign( step( 0.01, shape ) );

+ 2 - 2
examples/webgpu_tsl_vfx_linkedparticles.html

@@ -28,7 +28,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { atan, cos, float, max, min, mix, PI, PI2, sin, vec2, vec3, color, Fn, hash, hue, If, instanceIndex, Loop, mx_fractal_noise_float, mx_fractal_noise_vec3, pass, pcurve, storage, deltaTime, time, uv, uniform } from 'three/tsl';
+			import { atan, cos, float, max, min, mix, PI, PI2, sin, vec2, vec3, color, Fn, hash, hue, If, instanceIndex, Loop, mx_fractal_noise_float, mx_fractal_noise_vec3, pass, pcurve, storage, deltaTime, time, uv, uniform, step } from 'three/tsl';
 			import { bloom } from 'three/addons/tsl/display/BloomNode.js';
 
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
@@ -147,7 +147,7 @@
 
 				particleMaterial.opacityNode = /*#__PURE__*/ Fn( () => {
 
-					const circle = uv().xy.sub( 0.5 ).length().step( 0.5 );
+					const circle = step( uv().xy.sub( 0.5 ).length(), 0.5 );
 					const life = particlePositions.toAttribute().w;
 
 					return circle.mul( life );

+ 5 - 5
examples/webgpu_tsl_vfx_tornado.html

@@ -28,7 +28,7 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { luminance, cos, float, min, time, atan, uniform, pass, PI, PI2, color, positionLocal, oneMinus, sin, texture, Fn, uv, vec2, vec3, vec4 } from 'three/tsl';
+			import { luminance, cos, min, time, atan, uniform, pass, PI, PI2, color, positionLocal, sin, texture, Fn, uv, vec2, vec3, vec4 } from 'three/tsl';
 			import { bloom } from 'three/addons/tsl/display/BloomNode.js';
 
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
@@ -145,7 +145,7 @@
 					// outer fade
 					const distanceToCenter = uv().sub( 0.5 ).toVar();
 					const outerFade = min(
-						oneMinus( distanceToCenter.length() ).smoothstep( 0.5, 0.9 ),
+						distanceToCenter.length().oneMinus().smoothstep( 0.5, 0.9 ),
 						distanceToCenter.length().smoothstep( 0, 0.2 )
 					);
 
@@ -154,7 +154,7 @@
 
 					// output
 					return vec4(
-						emissiveColor.mul( float( 0.2 ).step( effect ) ).mul( 3 ), // Emissive
+						emissiveColor.mul( effect.step( 0.2 ) ).mul( 3 ), // Emissive
 						effect.smoothstep( 0, 0.01 ) // Alpha
 					);
 
@@ -200,7 +200,7 @@
 					// outer fade
 					const outerFade = min(
 						uv().y.smoothstep( 0, 0.1 ),
-						oneMinus( uv().y ).smoothstep( 0, 0.4 )
+						uv().y.oneMinus().smoothstep( 0, 0.4 )
 					);
 
 					// effect
@@ -251,7 +251,7 @@
 					// outer fade
 					const outerFade = min(
 						uv().y.smoothstep( 0, 0.2 ),
-						oneMinus( uv().y ).smoothstep( 0, 0.4 )
+						uv().y.oneMinus().smoothstep( 0, 0.4 )
 					);
 
 					// effect

+ 110 - 70
examples/webgpu_water.html

@@ -10,7 +10,9 @@
 
 		<div id="container"></div>
 		<div id="info">
-			<a href="https://threejs.org" target="_blank" rel="noopener noreferrer">three.js</a> - water
+			<a href="https://threejs.org" target="_blank" rel="noopener noreferrer">three.js</a> - water<br />
+			<a href="https://skfb.ly/6WOOR" target="_blank" rel="noopener">The Night Pool</a> by 
+			<a href="https://sketchfab.com/syntheticplants" target="_blank" rel="noopener">syntheticplants</a> is licensed under <a href="https://creativecommons.org/licenses/by/4.0/" target="_blank" rel="noopener">CC BY 4.0</a><br />
 		</div>
 
 		<script type="importmap">
@@ -28,81 +30,76 @@
 
 			import * as THREE from 'three';
 
+			import { pass, mrt, output, emissive, color, screenUV } from 'three/tsl';
+			import { bloom } from 'three/addons/tsl/display/BloomNode.js';
+
 			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
+
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
-			import { WaterMesh } from 'three/addons/objects/Water2Mesh.js';
+			import { UltraHDRLoader } from 'three/addons/loaders/UltraHDRLoader.js';
 
-			let scene, camera, clock, renderer, water;
+			import { WaterMesh } from 'three/addons/objects/Water2Mesh.js';
+			import { DRACOLoader } from 'three/addons/loaders/DRACOLoader.js';
+			import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
 
-			let torusKnot;
+			let scene, camera, renderer, water, postProcessing, controls;
 
 			const params = {
-				color: '#ffffff',
-				scale: 4,
+				color: '#99e0ff',
+				scale: 2,
 				flowX: 1,
 				flowY: 1
 			};
 
 			init();
 
-			function init() {
-
-				// scene
+			async function init() {
 
 				scene = new THREE.Scene();
 
-				// camera
-
-				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 200 );
-				camera.position.set( - 15, 7, 15 );
-				camera.lookAt( scene.position );
+				const loader = new UltraHDRLoader();
+				loader.setDataType( THREE.HalfFloatType );
+				loader.load( `textures/equirectangular/moonless_golf_2k.hdr.jpg`, function ( texture ) {
 
-				// clock
+					texture.mapping = THREE.EquirectangularReflectionMapping;
+					texture.needsUpdate = true;
 
-				clock = new THREE.Clock();
+					scene.background = texture;
+					scene.environment = texture;
 
-				// mesh
+				} );
 
-				const torusKnotGeometry = new THREE.TorusKnotGeometry( 3, 1, 256, 32 );
-				const torusKnotMaterial = new THREE.MeshNormalMaterial();
+				// camera
 
-				torusKnot = new THREE.Mesh( torusKnotGeometry, torusKnotMaterial );
-				torusKnot.position.y = 4;
-				torusKnot.scale.set( 0.5, 0.5, 0.5 );
-				scene.add( torusKnot );
+				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.1, 200 );
+				camera.position.set( - 20, 6, - 30 );
 
-				// ground
+				// asset loading
 
-				const groundGeometry = new THREE.PlaneGeometry( 20, 20 );
-				const groundMaterial = new THREE.MeshStandardMaterial( { roughness: 0.8, metalness: 0.4 } );
-				const ground = new THREE.Mesh( groundGeometry, groundMaterial );
-				ground.rotation.x = Math.PI * - 0.5;
-				scene.add( ground );
+				const dracoLoader = new DRACOLoader();
+				dracoLoader.setDecoderPath( 'jsm/libs/draco/gltf/' );
 
+				const gltfLoader = new GLTFLoader();
+				gltfLoader.setDRACOLoader( dracoLoader );
+			
 				const textureLoader = new THREE.TextureLoader();
-				textureLoader.load( 'textures/hardwood2_diffuse.jpg', function ( map ) {
-
-					map.wrapS = THREE.RepeatWrapping;
-					map.wrapT = THREE.RepeatWrapping;
-					map.anisotropy = 16;
-					map.repeat.set( 4, 4 );
-					map.colorSpace = THREE.SRGBColorSpace;
-					groundMaterial.map = map;
-					groundMaterial.needsUpdate = true;
 
-				} );
+				const [ gltf, normalMap0, normalMap1 ] = await Promise.all( [
+					gltfLoader.loadAsync( 'models/gltf/pool.glb' ),
+					textureLoader.loadAsync( 'textures/water/Water_1_M_Normal.jpg' ),
+					textureLoader.loadAsync( 'textures/water/Water_2_M_Normal.jpg' )
+				] );
 
-				//
+				gltf.scene.position.z = 2;
+				gltf.scene.scale.setScalar( 0.1 );
+				scene.add( gltf.scene );
 
-				const normalMap0 = textureLoader.load( 'textures/water/Water_1_M_Normal.jpg' );
-				const normalMap1 = textureLoader.load( 'textures/water/Water_2_M_Normal.jpg' );
+				// water
 
 				normalMap0.wrapS = normalMap0.wrapT = THREE.RepeatWrapping;
 				normalMap1.wrapS = normalMap1.wrapT = THREE.RepeatWrapping;
 
-				// water
-
-				const waterGeometry = new THREE.PlaneGeometry( 20, 20 );
+				const waterGeometry = new THREE.PlaneGeometry( 30, 40 );
 
 				water = new WaterMesh( waterGeometry, {
 					color: params.color,
@@ -112,40 +109,85 @@
 					normalMap1: normalMap1
 				} );
 
-				water.position.y = 1;
+				water.position.set( 0, 0.2, - 2 );
 				water.rotation.x = Math.PI * - 0.5;
+				water.renderOrder = Infinity;
 				scene.add( water );
 
-				// skybox
+				// floor
 
-				const cubeTextureLoader = new THREE.CubeTextureLoader();
-				cubeTextureLoader.setPath( 'textures/cube/Park2/' );
+				const floorGeometry = new THREE.PlaneGeometry( 1, 1 );
+				floorGeometry.rotateX( - Math.PI * 0.5 );
+				const floorMaterial = new THREE.MeshStandardMaterial( {
+					color: 0x444444,
+					roughness: 1,
+					metalness: 0,
+					side: THREE.DoubleSide
+				} );
 
-				const cubeTexture = cubeTextureLoader.load( [
-					'posx.jpg', 'negx.jpg',
-					'posy.jpg', 'negy.jpg',
-					'posz.jpg', 'negz.jpg'
-				] );
+				{
+
+					const floor = new THREE.Mesh( floorGeometry, floorMaterial );
+					floor.position.set( 20, 0, 0 );
+					floor.scale.set( 15, 1, 80 );
+					scene.add( floor );
+
+				}
+
+				{
+
+					const floor = new THREE.Mesh( floorGeometry, floorMaterial );
+					floor.position.set( - 20, 0, 0 );
+					floor.scale.set( 15, 1, 80 );
+					scene.add( floor );
+
+				}
+
+				{
 
-				scene.background = cubeTexture;
+					const floor = new THREE.Mesh( floorGeometry, floorMaterial );
+					floor.position.set( 0, 0, 30 );
+					floor.scale.set( 30, 1, 20 );
+					scene.add( floor );
 
-				// light
+				}
+
+				{
 
-				const ambientLight = new THREE.AmbientLight( 0xe7e7e7, 1.2 );
-				scene.add( ambientLight );
+					const floor = new THREE.Mesh( floorGeometry, floorMaterial );
+					floor.position.set( 0, 0, - 30 );
+					floor.scale.set( 30, 1, 20 );
+					scene.add( floor );
 
-				const directionalLight = new THREE.DirectionalLight( 0xffffff, 2 );
-				directionalLight.position.set( - 1, 1, 1 );
-				scene.add( directionalLight );
+				}
 
 				// renderer
 
-				renderer = new THREE.WebGPURenderer();
+				renderer = new THREE.WebGPURenderer( { antialias: true } );
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				renderer.setPixelRatio( window.devicePixelRatio );
 				renderer.setAnimationLoop( animate );
+				renderer.toneMapping = THREE.ACESFilmicToneMapping;
+				renderer.toneMappingExposure = 0.5;
 				document.body.appendChild( renderer.domElement );
 
+				// postprocessing
+
+				postProcessing = new THREE.PostProcessing( renderer );
+			
+				const scenePass = pass( scene, camera );
+				scenePass.setMRT( mrt( {
+					output,
+					emissive
+				} ) );
+
+				const outputPass = scenePass.getTextureNode();
+				const emissivePass = scenePass.getTextureNode( 'emissive' );
+
+				const bloomPass = bloom( emissivePass, 2 );
+
+				postProcessing.outputNode = outputPass.add( bloomPass );
+
 				// gui
 
 				const gui = new GUI();
@@ -178,9 +220,10 @@
 
 				//
 
-				const controls = new OrbitControls( camera, renderer.domElement );
-				controls.minDistance = 5;
-				controls.maxDistance = 50;
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.enableDamping = true;
+				controls.target.set( 0, 0, -5 );
+				controls.update();
 
 				//
 
@@ -198,12 +241,9 @@
 
 			function animate() {
 
-				const delta = clock.getDelta();
-
-				torusKnot.rotation.x += delta;
-				torusKnot.rotation.y += delta * 0.5;
+				controls.update();
 
-				renderer.render( scene, camera );
+				postProcessing.render();
 
 			}
 

Some files were not shown because too many files changed in this diff

粤ICP备19079148号