Просмотр исходного кода

WebGPURenderer: Improve `PointsNodeMaterial` (#30300)

* Improve PointsNodeMaterial

* update imports

* Update webgpu_instance_points.jpg

* Removed InstancedPoints

* revision

* Delete InstancedPointsGeometry.js

* rev

* rev

* update
sunag 1 год назад
Родитель
Сommit
81a30698ff

+ 0 - 176
examples/jsm/geometries/InstancedPointsGeometry.js

@@ -1,176 +0,0 @@
-import {
-	Box3,
-	Float32BufferAttribute,
-	InstancedBufferGeometry,
-	InstancedBufferAttribute,
-	Sphere,
-	Vector3
-} from 'three';
-
-const _vector = new Vector3();
-
-class InstancedPointsGeometry extends InstancedBufferGeometry {
-
-	constructor() {
-
-		super();
-
-		this.isInstancedPointsGeometry = true;
-
-		this.type = 'InstancedPointsGeometry';
-
-		const positions = [ - 1, 1, 0, 1, 1, 0, - 1, - 1, 0, 1, - 1, 0 ];
-		const uvs = [ 0, 1, 1, 1, 0, 0, 1, 0 ];
-		const index = [ 0, 2, 1, 2, 3, 1 ];
-
-		this.setIndex( index );
-		this.setAttribute( 'position', new Float32BufferAttribute( positions, 3 ) );
-		this.setAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
-
-	}
-
-	applyMatrix4( matrix ) {
-
-		const pos = this.attributes.instancePosition;
-
-		if ( pos !== undefined ) {
-
-			pos.applyMatrix4( matrix );
-
-			pos.needsUpdate = true;
-
-		}
-
-		if ( this.boundingBox !== null ) {
-
-			this.computeBoundingBox();
-
-		}
-
-		if ( this.boundingSphere !== null ) {
-
-			this.computeBoundingSphere();
-
-		}
-
-		return this;
-
-	}
-
-	setPositions( array ) {
-
-		let points;
-
-		if ( array instanceof Float32Array ) {
-
-			points = array;
-
-		} else if ( Array.isArray( array ) ) {
-
-			points = new Float32Array( array );
-
-		}
-
-		this.setAttribute( 'instancePosition', new InstancedBufferAttribute( points, 3 ) ); // xyz
-
-		//
-
-		this.computeBoundingBox();
-		this.computeBoundingSphere();
-
-		this.instanceCount = points.length / 3;
-
-		return this;
-
-	}
-
-	setColors( array ) {
-
-		let colors;
-
-		if ( array instanceof Float32Array ) {
-
-			colors = array;
-
-		} else if ( Array.isArray( array ) ) {
-
-			colors = new Float32Array( array );
-
-		}
-
-		this.setAttribute( 'instanceColor', new InstancedBufferAttribute( colors, 3 ) ); // rgb
-
-		return this;
-
-	}
-
-	computeBoundingBox() {
-
-		if ( this.boundingBox === null ) {
-
-			this.boundingBox = new Box3();
-
-		}
-
-		const pos = this.attributes.instancePosition;
-
-		if ( pos !== undefined ) {
-
-			this.boundingBox.setFromBufferAttribute( pos );
-
-		}
-
-	}
-
-	computeBoundingSphere() {
-
-		if ( this.boundingSphere === null ) {
-
-			this.boundingSphere = new Sphere();
-
-		}
-
-		if ( this.boundingBox === null ) {
-
-			this.computeBoundingBox();
-
-		}
-
-		const pos = this.attributes.instancePosition;
-
-		if ( pos !== undefined ) {
-
-			const center = this.boundingSphere.center;
-
-			this.boundingBox.getCenter( center );
-
-			let maxRadiusSq = 0;
-
-			for ( let i = 0, il = pos.count; i < il; i ++ ) {
-
-				_vector.fromBufferAttribute( pos, i );
-				maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector ) );
-
-			}
-
-			this.boundingSphere.radius = Math.sqrt( maxRadiusSq );
-
-			if ( isNaN( this.boundingSphere.radius ) ) {
-
-				console.error( 'THREE.InstancedPointsGeometry.computeBoundingSphere(): Computed radius is NaN. The instanced position data is likely to have NaN values.', this );
-
-			}
-
-		}
-
-	}
-
-	toJSON() {
-
-		// todo
-
-	}
-
-}
-
-export default InstancedPointsGeometry;

+ 0 - 19
examples/jsm/objects/InstancedPoints.js

@@ -1,19 +0,0 @@
-import { Mesh, InstancedPointsNodeMaterial } from 'three/webgpu';
-
-import InstancedPointsGeometry from '../geometries/InstancedPointsGeometry.js';
-
-class InstancedPoints extends Mesh {
-
-	constructor( geometry = new InstancedPointsGeometry(), material = new InstancedPointsNodeMaterial() ) {
-
-		super( geometry, material );
-
-		this.isInstancedPoints = true;
-
-		this.type = 'InstancedPoints';
-
-	}
-
-}
-
-export default InstancedPoints;

BIN
examples/screenshots/webgpu_camera.jpg


BIN
examples/screenshots/webgpu_instance_points.jpg


BIN
examples/screenshots/webgpu_lights_custom.jpg


+ 39 - 35
examples/webgpu_instance_points.html

@@ -27,16 +27,13 @@
 		<script type="module">
 
 			import * as THREE from 'three';
-			import { color, storage, Fn, instanceIndex, sin, time, float, uniform, attribute, mix, vec3 } from 'three/tsl';
+			import { color, storage, Fn, instancedBufferAttribute, instanceIndex, sin, time, float, uniform, shapeCircle, mix, vec3 } from 'three/tsl';
 
 			import Stats from 'three/addons/libs/stats.module.js';
 
 			import { GUI } from 'three/addons/libs/lil-gui.module.min.js';
 			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
 
-			import InstancedPoints from 'three/addons/objects/InstancedPoints.js';
-			import InstancedPointsGeometry from 'three/addons/geometries/InstancedPointsGeometry.js';
-
 			import * as GeometryUtils from 'three/addons/utils/GeometryUtils.js';
 
 			let renderer, scene, camera, camera2, controls, backgroundNode;
@@ -54,13 +51,7 @@
 
 			init();
 
-			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 );
+			async function init() {
 
 				scene = new THREE.Scene();
 
@@ -70,19 +61,14 @@
 				camera2 = new THREE.PerspectiveCamera( 40, 1, 1, 1000 );
 				camera2.position.copy( camera.position );
 
-				controls = new OrbitControls( camera, renderer.domElement );
-				controls.enableDamping = true;
-				controls.minDistance = 10;
-				controls.maxDistance = 500;
-
 				backgroundNode = color( 0x222222 );
 
 				effectController = {
 
 					pulseSpeed: uniform( 6 ),
 					minWidth: uniform( 6 ),
-					maxWidth: uniform( 12 ),
-					alphaToCoverage: true,
+					maxWidth: uniform( 20 ),
+					alphaToCoverage: true
 
 				};
 
@@ -115,12 +101,10 @@
 
 				// Instanced Points
 
-				const geometry = new InstancedPointsGeometry();
-				geometry.setPositions( positions );
-				geometry.setColors( colors );
+				const positionAttribute = new THREE.InstancedBufferAttribute( new Float32Array( positions ), 3 );
+				const colorsAttribute = new THREE.InstancedBufferAttribute( new Float32Array( colors ), 3 );
 
 				const instanceSizeBufferAttribute = new THREE.StorageInstancedBufferAttribute( sizes, 1 );
-				geometry.setAttribute( 'instanceSize', instanceSizeBufferAttribute );
 				const instanceSizeStorage = storage( instanceSizeBufferAttribute, 'float', instanceSizeBufferAttribute.count );
 
 				computeSize = Fn( () => {
@@ -135,29 +119,48 @@
 
 				} )().compute( divisions );
 			
-				geometry.instanceCount = positions.length / 3; // this should not be necessary
+				// Material / Sprites
+
+				const attributeRange = instancedBufferAttribute( instanceSizeBufferAttribute );
+				const pointColors = mix( vec3( 0.0 ), instancedBufferAttribute( colorsAttribute ), attributeRange.div( float( effectController.maxWidth ) ) );
 
-				material = new THREE.InstancedPointsNodeMaterial( {
+				material = new THREE.PointsNodeMaterial( {
 
-					color: 0xffffff,
-					pointWidth: 10, // in pixel units
+					colorNode: pointColors,
+					opacityNode: shapeCircle(),
+					positionNode: instancedBufferAttribute( positionAttribute ),
+					// rotationNode: time,
+					sizeNode: instancedBufferAttribute( instanceSizeBufferAttribute ),
+					// size: 40, // in pixels units
 					vertexColors: true,
-					alphaToCoverage: true,
+					sizeAttenuation: false,
+					alphaToCoverage: true
 
 				} );
 
-				const attributeRange = attribute( 'instanceSize' ).sub( 1 );
+				const instancedPoints = new THREE.Sprite( material );
+				instancedPoints.count = divisions;
+				scene.add( instancedPoints );
 
-				material.pointWidthNode = attribute( 'instanceSize' );
-				material.pointColorNode = mix( vec3( 0.0 ), attribute( 'instanceColor' ), attributeRange.div( float( effectController.maxWidth.sub( 1 ) ) ) );
+				// Renderer / Controls
 
-				const instancedPoints = new InstancedPoints( geometry, material );
-				instancedPoints.scale.set( 1, 1, 1 );
-				scene.add( instancedPoints );
+				renderer = new THREE.WebGPURenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				//renderer.logarithmicDepthBuffer = true;
+				document.body.appendChild( renderer.domElement );
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.enableDamping = true;
+				controls.minDistance = 10;
+				controls.maxDistance = 500;
 
 				window.addEventListener( 'resize', onWindowResize );
 				onWindowResize();
 
+				// GUI
+
 				stats = new Stats();
 				document.body.appendChild( stats.dom );
 
@@ -169,8 +172,8 @@
 
 				} );
 
-				gui.add( effectController.minWidth, 'value', 1, 20, 1 ).name( 'minWidth' );
-				gui.add( effectController.maxWidth, 'value', 2, 20, 1 ).name( 'maxWidth' );
+				gui.add( effectController.minWidth, 'value', 1, 30, 1 ).name( 'minWidth' );
+				gui.add( effectController.maxWidth, 'value', 2, 30, 1 ).name( 'maxWidth' );
 				gui.add( effectController.pulseSpeed, 'value', 1, 20, 0.1 ).name( 'pulseSpeed' );
 
 			}
@@ -195,6 +198,7 @@
 				stats.update();
 
 				// compute
+
 				renderer.compute( computeSize );
 
 				// main scene

+ 2 - 1
src/Three.TSL.js

@@ -256,7 +256,7 @@ export const materialLineWidth = TSL.materialLineWidth;
 export const materialMetalness = TSL.materialMetalness;
 export const materialNormal = TSL.materialNormal;
 export const materialOpacity = TSL.materialOpacity;
-export const materialPointWidth = TSL.materialPointWidth;
+export const materialPointSize = TSL.materialPointSize;
 export const materialReference = TSL.materialReference;
 export const materialReflectivity = TSL.materialReflectivity;
 export const materialRefractionRatio = TSL.materialRefractionRatio;
@@ -415,6 +415,7 @@ export const shaderStages = TSL.shaderStages;
 export const shadow = TSL.shadow;
 export const shadowPositionWorld = TSL.shadowPositionWorld;
 export const sharedUniformGroup = TSL.sharedUniformGroup;
+export const shapeCircle = TSL.shapeCircle;
 export const sheen = TSL.sheen;
 export const sheenRoughness = TSL.sheenRoughness;
 export const shiftLeft = TSL.shiftLeft;

+ 0 - 209
src/materials/nodes/InstancedPointsNodeMaterial.js

@@ -1,209 +0,0 @@
-import NodeMaterial from './NodeMaterial.js';
-import { attribute } from '../../nodes/core/AttributeNode.js';
-import { cameraProjectionMatrix } from '../../nodes/accessors/Camera.js';
-import { materialColor, materialOpacity, materialPointWidth } from '../../nodes/accessors/MaterialNode.js'; // or should this be a property, instead?
-import { modelViewMatrix } from '../../nodes/accessors/ModelNode.js';
-import { positionGeometry } from '../../nodes/accessors/Position.js';
-import { smoothstep, lengthSq } from '../../nodes/math/MathNode.js';
-import { Fn, vec4, float } from '../../nodes/tsl/TSLBase.js';
-import { uv } from '../../nodes/accessors/UV.js';
-import { viewport } from '../../nodes/display/ScreenNode.js';
-
-import { PointsMaterial } from '../PointsMaterial.js';
-
-const _defaultValues = /*@__PURE__*/ new PointsMaterial();
-
-/**
- * Unlike WebGL, WebGPU can render point primitives only with a size
- * of one pixel. This type node material can be used to mimic the WebGL
- * points rendering by rendering small planes via instancing.
- *
- * This material should be used with {@link InstancedPointsGeometry}.
- *
- * @augments NodeMaterial
- */
-class InstancedPointsNodeMaterial extends NodeMaterial {
-
-	static get type() {
-
-		return 'InstancedPointsNodeMaterial';
-
-	}
-
-	/**
-	 * Constructs a new instanced points node material.
-	 *
-	 * @param {Object?} parameters - The configuration parameter.
-	 */
-	constructor( parameters = {} ) {
-
-		super();
-
-		/**
-		 * This flag can be used for type testing.
-		 *
-		 * @type {Boolean}
-		 * @readonly
-		 * @default true
-		 */
-		this.isInstancedPointsNodeMaterial = true;
-
-		/**
-		 * Whether vertex colors should be used or not. If set to `true`,
-		 * each point instance can receive a custom color value.
-		 *
-		 * @type {Boolean}
-		 * @default false
-		 */
-		this.useColor = parameters.vertexColors;
-
-		/**
-		 * The points width in pixels.
-		 *
-		 * @type {Number}
-		 * @default 1
-		 */
-		this.pointWidth = 1;
-
-		/**
-		 * This node can be used to define the colors for each instance.
-		 *
-		 * @type {Node<vec3>?}
-		 * @default null
-		 */
-		this.pointColorNode = null;
-
-		/**
-		 * This node can be used to define the width for each point instance.
-		 *
-		 * @type {Node<float>?}
-		 * @default null
-		 */
-		this.pointWidthNode = null;
-
-		this._useAlphaToCoverage = true;
-
-		this.setDefaultValues( _defaultValues );
-
-		this.setValues( parameters );
-
-	}
-
-	/**
-	 * Setups the vertex and fragment stage of this node material.
-	 *
-	 * @param {NodeBuilder} builder - The current node builder.
-	 */
-	setup( builder ) {
-
-		const { renderer } = builder;
-
-		const useAlphaToCoverage = this._useAlphaToCoverage;
-		const useColor = this.useColor;
-
-		this.vertexNode = Fn( () => {
-
-			const instancePosition = attribute( 'instancePosition' ).xyz;
-
-			// camera space
-			const mvPos = vec4( modelViewMatrix.mul( vec4( instancePosition, 1.0 ) ) );
-
-			const aspect = viewport.z.div( viewport.w );
-
-			// clip space
-			const clipPos = cameraProjectionMatrix.mul( mvPos );
-
-			// offset in ndc space
-			const offset = positionGeometry.xy.toVar();
-
-			offset.mulAssign( this.pointWidthNode ? this.pointWidthNode : materialPointWidth );
-
-			offset.assign( offset.div( viewport.z ) );
-			offset.y.assign( offset.y.mul( aspect ) );
-
-			// back to clip space
-			offset.assign( offset.mul( clipPos.w ) );
-
-			//clipPos.xy += offset;
-			clipPos.addAssign( vec4( offset, 0, 0 ) );
-
-			return clipPos;
-
-		} )();
-
-		this.fragmentNode = Fn( () => {
-
-			const alpha = float( 1 ).toVar();
-
-			const len2 = lengthSq( uv().mul( 2 ).sub( 1 ) );
-
-			if ( useAlphaToCoverage && renderer.samples > 1 ) {
-
-				const dlen = float( len2.fwidth() ).toVar();
-
-				alpha.assign( smoothstep( dlen.oneMinus(), dlen.add( 1 ), len2 ).oneMinus() );
-
-			} else {
-
-				len2.greaterThan( 1.0 ).discard();
-
-			}
-
-			let pointColorNode;
-
-			if ( this.pointColorNode ) {
-
-				pointColorNode = this.pointColorNode;
-
-			} else {
-
-				if ( useColor ) {
-
-					const instanceColor = attribute( 'instanceColor' );
-
-					pointColorNode = instanceColor.mul( materialColor );
-
-				} else {
-
-					pointColorNode = materialColor;
-
-				}
-
-			}
-
-			alpha.mulAssign( materialOpacity );
-
-			return vec4( pointColorNode, alpha );
-
-		} )();
-
-		super.setup( builder );
-
-	}
-
-	/**
-	 * Whether alpha to coverage should be used or not.
-	 *
-	 * @type {Boolean}
-	 * @default true
-	 */
-	get alphaToCoverage() {
-
-		return this._useAlphaToCoverage;
-
-	}
-
-	set alphaToCoverage( value ) {
-
-		if ( this._useAlphaToCoverage !== value ) {
-
-			this._useAlphaToCoverage = value;
-			this.needsUpdate = true;
-
-		}
-
-	}
-
-}
-
-export default InstancedPointsNodeMaterial;

+ 0 - 1
src/materials/nodes/NodeMaterials.js

@@ -3,7 +3,6 @@
 export { default as NodeMaterialObserver } from './manager/NodeMaterialObserver.js';
 
 export { default as NodeMaterial } from './NodeMaterial.js';
-export { default as InstancedPointsNodeMaterial } from './InstancedPointsNodeMaterial.js';
 export { default as LineBasicNodeMaterial } from './LineBasicNodeMaterial.js';
 export { default as LineDashedNodeMaterial } from './LineDashedNodeMaterial.js';
 export { default as Line2NodeMaterial } from './Line2NodeMaterial.js';

+ 109 - 7
src/materials/nodes/PointsNodeMaterial.js

@@ -1,4 +1,10 @@
-import NodeMaterial from './NodeMaterial.js';
+import SpriteNodeMaterial from './SpriteNodeMaterial.js';
+import { viewport } from '../../nodes/display/ScreenNode.js';
+import { positionGeometry, positionLocal, positionView } from '../../nodes/accessors/Position.js';
+import { modelViewMatrix } from '../../nodes/accessors/ModelNode.js';
+import { materialPointSize } from '../../nodes/accessors/MaterialNode.js';
+import { rotate } from '../../nodes/utils/RotateNode.js';
+import { float, vec2, vec3, vec4 } from '../../nodes/tsl/TSLBase.js';
 
 import { PointsMaterial } from '../PointsMaterial.js';
 
@@ -7,14 +13,9 @@ const _defaultValues = /*@__PURE__*/ new PointsMaterial();
 /**
  * Node material version of `PointsMaterial`.
  *
- * Since WebGPU can render point primitives only with a size of one pixel,
- * this material type does not evaluate the `size` and `sizeAttenuation`
- * property of `PointsMaterial`. Use {@link InstancedPointsNodeMaterial}
- * instead if you need points with a size larger than one pixel.
- *
  * @augments NodeMaterial
  */
-class PointsNodeMaterial extends NodeMaterial {
+class PointsNodeMaterial extends SpriteNodeMaterial {
 
 	static get type() {
 
@@ -31,6 +32,14 @@ class PointsNodeMaterial extends NodeMaterial {
 
 		super();
 
+		/**
+		 * This node property provides an additional way to set the point size.
+		 *
+		 * @type {Node<vec2>?}
+		 * @default null
+		 */
+		this.sizeNode = null;
+
 		/**
 		 * This flag can be used for type testing.
 		 *
@@ -46,6 +55,99 @@ class PointsNodeMaterial extends NodeMaterial {
 
 	}
 
+	setupPositionView() {
+
+		const { positionNode } = this;
+
+		return modelViewMatrix.mul( vec3( positionNode || positionLocal ) ).xyz;
+
+	}
+
+	setupVertex( builder ) {
+
+		const mvp = super.setupVertex( builder );
+
+		// skip further processing if the material is not a node material
+
+		if ( builder.material.isNodeMaterial !== true ) {
+
+			return mvp;
+
+		}
+
+		// ndc space
+
+		const { rotationNode, scaleNode, sizeNode } = this;
+
+		const alignedPosition = positionGeometry.xy.toVar();
+		const aspect = viewport.z.div( viewport.w );
+
+		// rotation
+
+		if ( rotationNode && rotationNode.isNode ) {
+
+			const rotation = float( rotationNode );
+
+			alignedPosition.assign( rotate( alignedPosition, rotation ) );
+
+		}
+
+		// point size
+
+		let pointSize = sizeNode !== null ? vec2( sizeNode ) : materialPointSize;
+
+		if ( this.sizeAttenuation === true ) {
+
+			pointSize = pointSize.mul( pointSize.div( positionView.z.negate() ) );
+
+		}
+
+		// scale
+
+		if ( scaleNode && scaleNode.isNode ) {
+
+			pointSize = pointSize.mul( vec2( scaleNode ) );
+
+		}
+
+		alignedPosition.mulAssign( pointSize.mul( 2 ) );
+
+		alignedPosition.assign( alignedPosition.div( viewport.z ) );
+		alignedPosition.y.assign( alignedPosition.y.mul( aspect ) );
+
+		// back to clip space
+		alignedPosition.assign( alignedPosition.mul( mvp.w ) );
+
+		//clipPos.xy += offset;
+		mvp.addAssign( vec4( alignedPosition, 0, 0 ) );
+
+		return mvp;
+
+	}
+
+	/**
+	 * Whether alpha to coverage should be used or not.
+	 *
+	 * @type {Boolean}
+	 * @default true
+	 */
+	get alphaToCoverage() {
+
+		return this._useAlphaToCoverage;
+
+	}
+
+	set alphaToCoverage( value ) {
+
+		if ( this._useAlphaToCoverage !== value ) {
+
+			this._useAlphaToCoverage = value;
+			this.needsUpdate = true;
+
+		}
+
+	}
+
 }
 
 export default PointsNodeMaterial;

+ 1 - 1
src/materials/nodes/SpriteNodeMaterial.js

@@ -113,7 +113,7 @@ class SpriteNodeMaterial extends NodeMaterial {
 
 		if ( scaleNode !== null ) {
 
-			scale = scale.mul( scaleNode );
+			scale = scale.mul( float( scaleNode ) );
 
 		}
 

+ 3 - 0
src/nodes/TSL.js

@@ -142,6 +142,9 @@ export * from './pmrem/PMREMUtils.js';
 // procedural
 export * from './procedural/Checker.js';
 
+// shapes
+export * from './shapes/Shapes.js';
+
 // materialX
 export * from './materialx/MaterialXNodes.js';
 

+ 0 - 37
src/nodes/accessors/InstancedPointsMaterialNode.js

@@ -1,37 +0,0 @@
-import MaterialNode from './MaterialNode.js';
-import { nodeImmutable } from '../tsl/TSLBase.js';
-
-/** @module InstancedPointsMaterialNode **/
-
-/**
- * An extension of material node to provide pre-defined
- * TSL objects in context of `InstancedPointsNodeMaterial`.
- *
- * @augments module:MaterialNode~MaterialNode
- */
-class InstancedPointsMaterialNode extends MaterialNode {
-
-	static get type() {
-
-		return 'InstancedPointsMaterialNode';
-
-	}
-
-	setup( /*builder*/ ) {
-
-		return this.getFloat( this.scope );
-
-	}
-
-}
-
-InstancedPointsMaterialNode.POINT_WIDTH = 'pointWidth';
-
-export default InstancedPointsMaterialNode;
-
-/**
- * TSL object that represents the point width of the current points material.
- *
- * @type {InstancedPointsMaterialNode<float>}
- */
-export const materialPointWidth = /*@__PURE__*/ nodeImmutable( InstancedPointsMaterialNode, InstancedPointsMaterialNode.POINT_WIDTH );

+ 3 - 3
src/nodes/accessors/MaterialNode.js

@@ -435,7 +435,7 @@ MaterialNode.LINE_DASH_SIZE = 'dashSize';
 MaterialNode.LINE_GAP_SIZE = 'gapSize';
 MaterialNode.LINE_WIDTH = 'linewidth';
 MaterialNode.LINE_DASH_OFFSET = 'dashOffset';
-MaterialNode.POINT_WIDTH = 'pointWidth';
+MaterialNode.POINT_SIZE = 'size';
 MaterialNode.DISPERSION = 'dispersion';
 MaterialNode.LIGHT_MAP = 'light';
 MaterialNode.AO = 'ao';
@@ -690,11 +690,11 @@ export const materialLineWidth = /*@__PURE__*/ nodeImmutable( MaterialNode, Mate
 export const materialLineDashOffset = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.LINE_DASH_OFFSET );
 
 /**
- * TSL object that represents the point width of the current points material.
+ * TSL object that represents the point size of the current points material.
  *
  * @type {Node<float>}
  */
-export const materialPointWidth = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.POINT_WIDTH );
+export const materialPointSize = /*@__PURE__*/ nodeImmutable( MaterialNode, MaterialNode.POINT_SIZE );
 
 /**
  * TSL object that represents the dispersion of the current material.

+ 33 - 0
src/nodes/shapes/Shapes.js

@@ -0,0 +1,33 @@
+import { Fn, float } from '../tsl/TSLBase.js';
+import { lengthSq, smoothstep } from '../math/MathNode.js';
+import { uv } from '../accessors/UV.js';
+
+/** @module Shapes **/
+
+/**
+ * Generates a circle based on the uv coordinates.
+ *
+ * @method
+ * @param {Node<vec2>} coord - The uv to generate the circle.
+ * @return {Node<float>} The circle shape.
+ */
+export const shapeCircle = Fn( ( [ coord = uv() ], { renderer, material } ) => {
+
+	const alpha = float( 1 ).toVar();
+	const len2 = lengthSq( coord.mul( 2 ).sub( 1 ) );
+
+	if ( material.alphaToCoverage && renderer.samples > 1 ) {
+
+		const dlen = float( len2.fwidth() ).toVar();
+
+		alpha.assign( smoothstep( dlen.oneMinus(), dlen.add( 1 ), len2 ).oneMinus() );
+
+	} else {
+
+		len2.greaterThan( 1.0 ).discard();
+
+	}
+
+	return alpha;
+
+} );

粤ICP备19079148号