2
0
Эх сурвалжийг харах

WebGPU: Sprites and InterleavedBufferAttribute (#24117)

* WebGPUInfo: count sprite triangles

* WebGPU: Add supports to InterleavedBufferAttribute

* Nodes: Split .generateMain() to .generatePosition() and .generateDiffuseColor()

* NodeMaterial: Fix .fogNode for transparent materials

* Add SpriteNodeMaterial

* Add UserDataNode

* Add MaterialNode.ROTATION property

* add webgpu_sprites example

* add comments

* cleanup
sunag 3 жил өмнө
parent
commit
e9a39569fe

+ 2 - 1
examples/files.json

@@ -322,7 +322,8 @@
 		"webgpu_sandbox",
 		"webgpu_sandbox",
 		"webgpu_skinning",
 		"webgpu_skinning",
 		"webgpu_skinning_instancing",
 		"webgpu_skinning_instancing",
-		"webgpu_skinning_points"
+		"webgpu_skinning_points",
+		"webgpu_sprites"
 	],
 	],
 	"webaudio": [
 	"webaudio": [
 		"webaudio_orientation",
 		"webaudio_orientation",

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

@@ -43,6 +43,7 @@ import ReflectNode from './accessors/ReflectNode.js';
 import SkinningNode from './accessors/SkinningNode.js';
 import SkinningNode from './accessors/SkinningNode.js';
 import TextureNode from './accessors/TextureNode.js';
 import TextureNode from './accessors/TextureNode.js';
 import UVNode from './accessors/UVNode.js';
 import UVNode from './accessors/UVNode.js';
+import UserDataNode from './accessors/UserDataNode.js';
 
 
 // gpgpu
 // gpgpu
 import ComputeNode from './gpgpu/ComputeNode.js';
 import ComputeNode from './gpgpu/ComputeNode.js';
@@ -149,6 +150,7 @@ const nodeLib = {
 	SkinningNode,
 	SkinningNode,
 	TextureNode,
 	TextureNode,
 	UVNode,
 	UVNode,
+	UserDataNode,
 
 
 	// display
 	// display
 	ColorSpaceNode,
 	ColorSpaceNode,
@@ -251,6 +253,7 @@ export {
 	SkinningNode,
 	SkinningNode,
 	TextureNode,
 	TextureNode,
 	UVNode,
 	UVNode,
+	UserDataNode,
 
 
 	// display
 	// display
 	ColorSpaceNode,
 	ColorSpaceNode,

+ 6 - 1
examples/jsm/nodes/accessors/MaterialNode.js

@@ -12,6 +12,7 @@ class MaterialNode extends Node {
 	static ROUGHNESS = 'roughness';
 	static ROUGHNESS = 'roughness';
 	static METALNESS = 'metalness';
 	static METALNESS = 'metalness';
 	static EMISSIVE = 'emissive';
 	static EMISSIVE = 'emissive';
+	static ROTATION = 'rotation';
 
 
 	constructor( scope = MaterialNode.COLOR ) {
 	constructor( scope = MaterialNode.COLOR ) {
 
 
@@ -30,7 +31,7 @@ class MaterialNode extends Node {
 
 
 			return material.map !== null ? 'vec4' : 'vec3';
 			return material.map !== null ? 'vec4' : 'vec3';
 
 
-		} else if ( scope === MaterialNode.OPACITY ) {
+		} else if ( scope === MaterialNode.OPACITY || scope === MaterialNode.ROTATION ) {
 
 
 			return 'float';
 			return 'float';
 
 
@@ -130,6 +131,10 @@ class MaterialNode extends Node {
 
 
 			}
 			}
 
 
+		} else if ( scope === MaterialNode.ROTATION ) {
+
+			node = new MaterialReferenceNode( 'rotation', 'float' );
+
 		} else {
 		} else {
 
 
 			const outputType = this.getNodeType( builder );
 			const outputType = this.getNodeType( builder );

+ 23 - 0
examples/jsm/nodes/accessors/UserDataNode.js

@@ -0,0 +1,23 @@
+import ReferenceNode from './ReferenceNode.js';
+
+class UserDataNode extends ReferenceNode {
+
+	constructor( property, inputType, userData = null ) {
+
+		super( property, inputType, userData );
+
+		this.userData = userData;
+
+	}
+
+	update( frame ) {
+
+		this.object = this.userData !== null ? this.userData : frame.object.userData;
+
+		super.update( frame );
+
+	}
+
+}
+
+export default UserDataNode;

+ 5 - 2
examples/jsm/nodes/materials/Materials.js

@@ -3,6 +3,7 @@ import LineBasicNodeMaterial from './LineBasicNodeMaterial.js';
 import MeshBasicNodeMaterial from './MeshBasicNodeMaterial.js';
 import MeshBasicNodeMaterial from './MeshBasicNodeMaterial.js';
 import MeshStandardNodeMaterial from './MeshStandardNodeMaterial.js';
 import MeshStandardNodeMaterial from './MeshStandardNodeMaterial.js';
 import PointsNodeMaterial from './PointsNodeMaterial.js';
 import PointsNodeMaterial from './PointsNodeMaterial.js';
+import SpriteNodeMaterial from './SpriteNodeMaterial.js';
 import { Material } from 'three';
 import { Material } from 'three';
 
 
 export {
 export {
@@ -10,7 +11,8 @@ export {
 	LineBasicNodeMaterial,
 	LineBasicNodeMaterial,
 	MeshBasicNodeMaterial,
 	MeshBasicNodeMaterial,
 	MeshStandardNodeMaterial,
 	MeshStandardNodeMaterial,
-	PointsNodeMaterial
+	PointsNodeMaterial,
+	SpriteNodeMaterial
 };
 };
 
 
 const materialLib = {
 const materialLib = {
@@ -18,7 +20,8 @@ const materialLib = {
 	LineBasicNodeMaterial,
 	LineBasicNodeMaterial,
 	MeshBasicNodeMaterial,
 	MeshBasicNodeMaterial,
 	MeshStandardNodeMaterial,
 	MeshStandardNodeMaterial,
-	PointsNodeMaterial
+	PointsNodeMaterial,
+	SpriteNodeMaterial
 };
 };
 
 
 const fromTypeFunction = Material.fromType;
 const fromTypeFunction = Material.fromType;

+ 3 - 1
examples/jsm/nodes/materials/MeshStandardNodeMaterial.js

@@ -52,7 +52,9 @@ export default class MeshStandardNodeMaterial extends NodeMaterial {
 
 
 	build( builder ) {
 	build( builder ) {
 
 
-		let { colorNode, diffuseColorNode } = this.generateMain( builder );
+		this.generatePosition( builder );
+
+		let { colorNode, diffuseColorNode } = this.generateDiffuseColor( builder );
 		const envNode = this.envNode || builder.scene.environmentNode;
 		const envNode = this.envNode || builder.scene.environmentNode;
 
 
 		diffuseColorNode = this.generateStandardMaterial( builder, { colorNode, diffuseColorNode } );
 		diffuseColorNode = this.generateStandardMaterial( builder, { colorNode, diffuseColorNode } );

+ 10 - 4
examples/jsm/nodes/materials/NodeMaterial.js

@@ -2,7 +2,7 @@ import { Material, ShaderMaterial } from 'three';
 import { getNodesKeys } from '../core/NodeUtils.js';
 import { getNodesKeys } from '../core/NodeUtils.js';
 import ExpressionNode from '../core/ExpressionNode.js';
 import ExpressionNode from '../core/ExpressionNode.js';
 import {
 import {
-	float, vec4,
+	float, vec3, vec4,
 	assign, label, mul, bypass,
 	assign, label, mul, bypass,
 	positionLocal, skinning, instance, modelViewProjection, lightingContext, colorSpace,
 	positionLocal, skinning, instance, modelViewProjection, lightingContext, colorSpace,
 	materialAlphaTest, materialColor, materialOpacity
 	materialAlphaTest, materialColor, materialOpacity
@@ -24,8 +24,10 @@ class NodeMaterial extends ShaderMaterial {
 
 
 	build( builder ) {
 	build( builder ) {
 
 
+		this.generatePosition( builder );
+
 		const { lightsNode } = this;
 		const { lightsNode } = this;
-		const { diffuseColorNode } = this.generateMain( builder );
+		const { diffuseColorNode } = this.generateDiffuseColor( builder );
 
 
 		const outgoingLightNode = this.generateLight( builder, { diffuseColorNode, lightsNode } );
 		const outgoingLightNode = this.generateLight( builder, { diffuseColorNode, lightsNode } );
 
 
@@ -39,7 +41,7 @@ class NodeMaterial extends ShaderMaterial {
 
 
 	}
 	}
 
 
-	generateMain( builder ) {
+	generatePosition( builder ) {
 
 
 		const object = builder.object;
 		const object = builder.object;
 
 
@@ -69,6 +71,10 @@ class NodeMaterial extends ShaderMaterial {
 
 
 		builder.addFlow( 'vertex', modelViewProjection() );
 		builder.addFlow( 'vertex', modelViewProjection() );
 
 
+	}
+
+	generateDiffuseColor( builder ) {
+
 		// < FRAGMENT STAGE >
 		// < FRAGMENT STAGE >
 
 
 		let colorNode = vec4( this.colorNode || materialColor );
 		let colorNode = vec4( this.colorNode || materialColor );
@@ -126,7 +132,7 @@ class NodeMaterial extends ShaderMaterial {
 
 
 		// FOG
 		// FOG
 
 
-		if ( builder.fogNode ) outputNode = builder.fogNode.mix( outputNode );
+		if ( builder.fogNode ) outputNode = vec4( vec3( builder.fogNode.mix( outputNode ) ), outputNode.w );
 
 
 		// RESULT
 		// RESULT
 
 

+ 95 - 0
examples/jsm/nodes/materials/SpriteNodeMaterial.js

@@ -0,0 +1,95 @@
+import NodeMaterial from './NodeMaterial.js';
+import { SpriteMaterial } from 'three';
+import {
+	vec2, vec3, vec4,
+	assign, add, mul, sub,
+	positionLocal, bypass, length, cos, sin,
+	modelViewMatrix, cameraProjectionMatrix, modelWorldMatrix, materialRotation
+} from '../shadernode/ShaderNodeElements.js';
+
+const defaultValues = new SpriteMaterial();
+
+class SpriteNodeMaterial extends NodeMaterial {
+
+	constructor( parameters ) {
+
+		super();
+
+		this.isSpriteNodeMaterial = true;
+
+		this.lights = true;
+
+		this.colorNode = null;
+		this.opacityNode = null;
+
+		this.rotationNode = null;
+
+		this.alphaTestNode = null;
+
+		this.lightNode = null;
+
+		this.positionNode = null;
+
+		this.setDefaultValues( defaultValues );
+
+		this.setValues( parameters );
+
+	}
+
+	generatePosition( builder ) {
+
+		// < VERTEX STAGE >
+
+		let vertex = positionLocal;
+
+		if ( this.positionNode !== null ) {
+
+			vertex = bypass( vertex, assign( positionLocal, this.positionNode ) );
+
+		}
+
+		let mvPosition = mul( modelViewMatrix, vec4( 0, 0, 0, 1 ) );
+
+		const scale = vec2(
+			length( vec3( modelWorldMatrix[ 0 ].x, modelWorldMatrix[ 0 ].y, modelWorldMatrix[ 0 ].z ) ),
+			length( vec3( modelWorldMatrix[ 1 ].x, modelWorldMatrix[ 1 ].y, modelWorldMatrix[ 1 ].z ) )
+		);
+
+		const alignedPosition = mul( positionLocal.xy, scale );
+		const rotation = this.rotationNode || materialRotation;
+
+		let rotatedPosition = vec2(
+			sub( mul( cos( rotation ), alignedPosition.x ), mul( sin( rotation ), alignedPosition.y ) ),
+			add( mul( sin( rotation ), alignedPosition.x ), mul( cos( rotation ), alignedPosition.y ) )
+		);
+
+		mvPosition = vec4( add( mvPosition.xy, rotatedPosition.xy ), mvPosition.z, mvPosition.w );
+
+		const modelViewProjection = mul( cameraProjectionMatrix, mvPosition );
+
+		builder.context.vertex = vertex;
+
+		builder.addFlow( 'vertex', modelViewProjection );
+
+	}
+
+	copy( source ) {
+
+		this.colorNode = source.colorNode;
+		this.opacityNode = source.opacityNode;
+
+		this.rotationNode = source.rotationNode;
+
+		this.alphaTestNode = source.alphaTestNode;
+
+		this.lightNode = source.lightNode;
+
+		this.positionNode = source.positionNode;
+
+		return super.copy( source );
+
+	}
+
+}
+
+export default SpriteNodeMaterial;

+ 3 - 0
examples/jsm/nodes/shadernode/ShaderNodeBaseElements.js

@@ -26,6 +26,7 @@ import PositionNode from '../accessors/PositionNode.js';
 import ReferenceNode from '../accessors/ReferenceNode.js';
 import ReferenceNode from '../accessors/ReferenceNode.js';
 import StorageBufferNode from '../accessors/StorageBufferNode.js';
 import StorageBufferNode from '../accessors/StorageBufferNode.js';
 import TextureNode from '../accessors/TextureNode.js';
 import TextureNode from '../accessors/TextureNode.js';
+import UserDataNode from '../accessors/UserDataNode.js';
 import UVNode from '../accessors/UVNode.js';
 import UVNode from '../accessors/UVNode.js';
 
 
 // display
 // display
@@ -139,6 +140,7 @@ export const materialOpacity = nodeImmutable( MaterialNode, MaterialNode.OPACITY
 //export const materialSpecular = nodeImmutable( MaterialNode, MaterialNode.SPECULAR );
 //export const materialSpecular = nodeImmutable( MaterialNode, MaterialNode.SPECULAR );
 export const materialRoughness = nodeImmutable( MaterialNode, MaterialNode.ROUGHNESS );
 export const materialRoughness = nodeImmutable( MaterialNode, MaterialNode.ROUGHNESS );
 export const materialMetalness = nodeImmutable( MaterialNode, MaterialNode.METALNESS );
 export const materialMetalness = nodeImmutable( MaterialNode, MaterialNode.METALNESS );
+export const materialRotation = nodeImmutable( MaterialNode, MaterialNode.ROTATION );
 
 
 export const diffuseColor = nodeImmutable( PropertyNode, 'DiffuseColor', 'vec4' );
 export const diffuseColor = nodeImmutable( PropertyNode, 'DiffuseColor', 'vec4' );
 export const roughness = nodeImmutable( PropertyNode, 'Roughness', 'float' );
 export const roughness = nodeImmutable( PropertyNode, 'Roughness', 'float' );
@@ -148,6 +150,7 @@ export const specularColor = nodeImmutable( PropertyNode, 'SpecularColor', 'colo
 
 
 export const reference = ( name, nodeOrType, object ) => nodeObject( new ReferenceNode( name, getConstNodeType( nodeOrType ), object ) );
 export const reference = ( name, nodeOrType, object ) => nodeObject( new ReferenceNode( name, getConstNodeType( nodeOrType ), object ) );
 export const materialReference = ( name, nodeOrType, material ) => nodeObject( new MaterialReferenceNode( name, getConstNodeType( nodeOrType ), material ) );
 export const materialReference = ( name, nodeOrType, material ) => nodeObject( new MaterialReferenceNode( name, getConstNodeType( nodeOrType ), material ) );
+export const userData = ( name, inputType, userData ) => nodeObject( new UserDataNode( name, inputType, userData ) );
 
 
 export const modelViewProjection = nodeProxy( ModelViewProjectionNode );
 export const modelViewProjection = nodeProxy( ModelViewProjectionNode );
 
 

+ 1 - 1
examples/jsm/renderers/webgpu/WebGPUInfo.js

@@ -23,7 +23,7 @@ class WebGPUInfo {
 
 
 		this.render.drawCalls ++;
 		this.render.drawCalls ++;
 
 
-		if ( object.isMesh ) {
+		if ( object.isMesh || object.isSprite ) {
 
 
 			this.render.triangles += instanceCount * ( count / 3 );
 			this.render.triangles += instanceCount * ( count / 3 );
 
 

+ 15 - 16
examples/jsm/renderers/webgpu/WebGPURenderPipeline.js

@@ -46,7 +46,7 @@ class WebGPURenderPipeline {
 
 
 			vertexBuffers.push( {
 			vertexBuffers.push( {
 				arrayStride: attribute.arrayStride,
 				arrayStride: attribute.arrayStride,
-				attributes: [ { shaderLocation: attribute.slot, offset: 0, format: attribute.format } ],
+				attributes: [ { shaderLocation: attribute.slot, offset: attribute.offset, format: attribute.format } ],
 				stepMode: stepMode
 				stepMode: stepMode
 			} );
 			} );
 
 
@@ -120,19 +120,6 @@ class WebGPURenderPipeline {
 
 
 	}
 	}
 
 
-	_getArrayStride( type, bytesPerElement ) {
-
-		// @TODO: This code is GLSL specific. We need to update when we switch to WGSL.
-
-		if ( type === 'float' || type === 'int' || type === 'uint' ) return bytesPerElement;
-		if ( type === 'vec2' || type === 'ivec2' || type === 'uvec2' ) return bytesPerElement * 2;
-		if ( type === 'vec3' || type === 'ivec3' || type === 'uvec3' ) return bytesPerElement * 3;
-		if ( type === 'vec4' || type === 'ivec4' || type === 'uvec4' ) return bytesPerElement * 4;
-
-		console.error( 'THREE.WebGPURenderer: Shader variable type not supported yet.', type );
-
-	}
-
 	_getAlphaBlend( material ) {
 	_getAlphaBlend( material ) {
 
 
 		const blending = material.blending;
 		const blending = material.blending;
@@ -715,14 +702,26 @@ class WebGPURenderPipeline {
 			const type = nodeAttribute.type;
 			const type = nodeAttribute.type;
 
 
 			const geometryAttribute = geometry.getAttribute( name );
 			const geometryAttribute = geometry.getAttribute( name );
-			const bytesPerElement = ( geometryAttribute !== undefined ) ? geometryAttribute.array.BYTES_PER_ELEMENT : 4;
+			const bytesPerElement = geometryAttribute.array.BYTES_PER_ELEMENT;
 
 
-			const arrayStride = this._getArrayStride( type, bytesPerElement );
 			const format = this._getVertexFormat( type, bytesPerElement );
 			const format = this._getVertexFormat( type, bytesPerElement );
 
 
+			let arrayStride = geometryAttribute.itemSize * bytesPerElement;
+			let offset = 0;
+
+			if ( geometryAttribute.isInterleavedBufferAttribute === true ) {
+
+				// @TODO: It can be optimized for "vertexBuffers" on RenderPipeline
+
+				arrayStride = geometryAttribute.data.stride * bytesPerElement;
+				offset = geometryAttribute.offset * bytesPerElement;
+
+			}
+
 			attributes.push( {
 			attributes.push( {
 				name,
 				name,
 				arrayStride,
 				arrayStride,
+				offset,
 				format,
 				format,
 				slot
 				slot
 			} );
 			} );

BIN
examples/screenshots/webgpu_sprites.jpg


+ 175 - 0
examples/webgpu_sprites.html

@@ -0,0 +1,175 @@
+<html lang="en">
+	<head>
+		<title>three.js - WebGPU - Sprites</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 - Sprites
+		</div>
+
+		<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three-nodes/": "./jsm/nodes/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+			import * as Nodes from 'three-nodes/Nodes.js';
+
+			import { texture, uv, mul, float, color, reference, userData } from 'three-nodes/Nodes.js';
+
+			import WebGPU from './jsm/capabilities/WebGPU.js';
+			import WebGPURenderer from './jsm/renderers/webgpu/WebGPURenderer.js';
+
+			let camera, scene, renderer;
+
+			let map;
+
+			let group;
+
+			init().then( animate ).catch( error );
+
+			async function init() {
+
+				if ( WebGPU.isAvailable() === false ) {
+
+					document.body.appendChild( WebGPU.getErrorMessage() );
+
+					throw new Error( 'No WebGPU support' );
+
+				}
+
+				const width = window.innerWidth;
+				const height = window.innerHeight;
+
+				camera = new THREE.PerspectiveCamera( 60, width / height, 1, 2100 );
+				camera.position.z = 1500;
+
+				scene = new THREE.Scene();
+				scene.fogNode = new Nodes.FogRangeNode( color( 0x0000ff ), float( 1500 ), float( 2100 )  );
+
+				// create sprites
+
+				const amount = 200;
+				const radius = 500;
+
+				const textureLoader = new THREE.TextureLoader();
+
+				map = textureLoader.load( 'textures/sprite1.png' );
+
+				group = new THREE.Group();
+
+				const textureNode = texture( map );
+
+				const material = new Nodes.SpriteNodeMaterial();
+				material.colorNode = mul( textureNode, mul( uv(), 2 ) );
+				material.opacityNode = textureNode.a;
+				material.rotationNode = userData( 'rotation', 'float' ); // get value of: sprite.userData.rotation
+				material.transparent = true;
+
+				for ( let a = 0; a < amount; a ++ ) {
+
+					const x = Math.random() - 0.5;
+					const y = Math.random() - 0.5;
+					const z = Math.random() - 0.5;
+
+					const sprite = new THREE.Sprite( material );
+
+					sprite.position.set( x, y, z );
+					sprite.position.normalize();
+					sprite.position.multiplyScalar( radius );
+
+					// individual rotation per sprite
+					sprite.userData.rotation = 0;
+
+					group.add( sprite );
+
+				}
+
+				scene.add( group );
+
+				//
+
+				renderer = new WebGPURenderer( { requiredFeatures: [ 'texture-compression-bc' ] } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				document.body.appendChild( renderer.domElement );
+
+				window.addEventListener( 'resize', onWindowResize );
+
+				return renderer.init();
+
+			}
+
+			function onWindowResize() {
+
+				const width = window.innerWidth;
+				const height = window.innerHeight;
+
+				camera.aspect = width / height;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+				render();
+
+			}
+
+			function render() {
+
+				const time = Date.now() / 1000;
+
+				for ( let i = 0, l = group.children.length; i < l; i ++ ) {
+
+					const sprite = group.children[ i ];
+					const material = sprite.material;
+					const scale = Math.sin( time + sprite.position.x * 0.01 ) * 0.3 + 1.0;
+
+					let imageWidth = 1;
+					let imageHeight = 1;
+
+					if ( map && map.image && map.image.width ) {
+
+						imageWidth = map.image.width;
+						imageHeight = map.image.height;
+
+					}
+
+					sprite.userData.rotation += 0.1 * ( i / l );
+					sprite.scale.set( scale * imageWidth, scale * imageHeight, 1.0 );
+
+				}
+
+				group.rotation.x = time * 0.5;
+				group.rotation.y = time * 0.75;
+				group.rotation.z = time * 1.0;
+
+				renderer.render( scene, camera );
+
+			}
+
+			function error( error ) {
+
+				console.error( error );
+
+			}
+
+		</script>
+	</body>
+</html>

+ 2 - 1
test/e2e/puppeteer.js

@@ -57,7 +57,8 @@ const exceptionList = [
 	'webgpu_sandbox',
 	'webgpu_sandbox',
 	'webgpu_skinning_instancing',
 	'webgpu_skinning_instancing',
 	'webgpu_skinning_points',
 	'webgpu_skinning_points',
-	'webgpu_skinning'
+	'webgpu_skinning',
+	'webgpu_sprites'
 ].concat( ( process.platform === 'win32' ) ? [
 ].concat( ( process.platform === 'win32' ) ? [
 
 
 	'webgl_effects_ascii' // windows fonts not supported
 	'webgl_effects_ascii' // windows fonts not supported