소스 검색

GLTFLoader: Add EXT_mesh_gpu_instancing built-in plugin (#24528)

* GLTFLoader: Add EXT_mesh_gpu_instancing built-in plugin

* GLTFLoader: No Points or Lines + Instancing

Currenlty Three.js does not seem to support Points or Lines +
Instancing so GLTFLoader GPU instancing extension handler
ignores the extension if primitive draw mode is not triangle.

* GLTFLoader: Minor clean up

Co-authored-by: Don McCurdy <[email protected]>

* GLTFLoader: Minor clean up

Co-authored-by: Don McCurdy <[email protected]>

* Update examples/jsm/loaders/GLTFLoader.js

Co-authored-by: Don McCurdy <[email protected]>

* Update examples/jsm/loaders/GLTFLoader.js

Co-authored-by: Don McCurdy <[email protected]>

* GLTFLoader: Clean up

Co-authored-by: Don McCurdy <[email protected]>
Takahiro 2 년 전
부모
커밋
0432e2f1e7

+ 1 - 0
docs/examples/en/loaders/GLTFLoader.html

@@ -48,6 +48,7 @@
 			<li>KHR_texture_transform<sup>2</sup></li>
 			<li>EXT_texture_webp</li>
 			<li>EXT_meshopt_compression</li>
+			<li>EXT_mesh_gpu_instancing</li>
 		</ul>
 
 		<p>

+ 1 - 0
docs/examples/zh/loaders/GLTFLoader.html

@@ -46,6 +46,7 @@
 			<li>KHR_texture_transform<sup>2</sup></li>
 			<li>EXT_texture_webp</li>
 			<li>EXT_meshopt_compression</li>
+			<li>EXT_mesh_gpu_instancing</li>
 		</ul>
 
 		<p>

+ 1 - 0
examples/files.json

@@ -86,6 +86,7 @@
 		"webgl_loader_gcode",
 		"webgl_loader_gltf",
 		"webgl_loader_gltf_compressed",
+		"webgl_loader_gltf_instancing",
 		"webgl_loader_gltf_iridescence",
 		"webgl_loader_gltf_sheen",
 		"webgl_loader_gltf_transmission",

+ 162 - 1
examples/jsm/loaders/GLTFLoader.js

@@ -12,6 +12,7 @@ import {
 	FrontSide,
 	Group,
 	ImageBitmapLoader,
+	InstancedMesh,
 	InterleavedBuffer,
 	InterleavedBufferAttribute,
 	Interpolant,
@@ -147,6 +148,12 @@ class GLTFLoader extends Loader {
 
 		} );
 
+		this.register( function ( parser ) {
+
+			return new GLTFMeshGpuInstancing( parser );
+
+		} );
+
 	}
 
 	load( url, onLoad, onProgress, onError ) {
@@ -468,7 +475,8 @@ const EXTENSIONS = {
 	KHR_MESH_QUANTIZATION: 'KHR_mesh_quantization',
 	KHR_MATERIALS_EMISSIVE_STRENGTH: 'KHR_materials_emissive_strength',
 	EXT_TEXTURE_WEBP: 'EXT_texture_webp',
-	EXT_MESHOPT_COMPRESSION: 'EXT_meshopt_compression'
+	EXT_MESHOPT_COMPRESSION: 'EXT_meshopt_compression',
+	EXT_MESH_GPU_INSTANCING: 'EXT_mesh_gpu_instancing'
 };
 
 /**
@@ -1384,6 +1392,159 @@ class GLTFMeshoptCompression {
 
 }
 
+/**
+ * GPU Instancing Extension
+ *
+ * Specification: https://github.com/KhronosGroup/glTF/tree/master/extensions/2.0/Vendor/EXT_mesh_gpu_instancing
+ *
+ */
+class GLTFMeshGpuInstancing {
+
+	constructor( parser ) {
+
+		this.name = EXTENSIONS.EXT_MESH_GPU_INSTANCING;
+		this.parser = parser;
+
+	}
+
+	createNodeMesh( nodeIndex ) {
+
+		const json = this.parser.json;
+		const nodeDef = json.nodes[ nodeIndex ];
+
+		if ( ! nodeDef.extensions || ! nodeDef.extensions[ this.name ] ||
+			nodeDef.mesh === undefined ) {
+
+			return null;
+
+		}
+
+		const meshDef = json.meshes[ nodeDef.mesh ];
+
+		// No Points or Lines + Instancing support yet
+
+		for ( const primitive of meshDef.primitives ) {
+
+			if ( primitive.mode !== WEBGL_CONSTANTS.TRIANGLES &&
+				 primitive.mode !== WEBGL_CONSTANTS.TRIANGLE_STRIP &&
+				 primitive.mode !== WEBGL_CONSTANTS.TRIANGLE_FAN &&
+				 primitive.mode !== undefined ) {
+
+				return null;
+
+			}
+
+		}
+
+		const extensionDef = nodeDef.extensions[ this.name ];
+		const attributesDef = extensionDef.attributes;
+
+		// @TODO: Can we support InstancedMesh + SkinnedMesh?
+
+		const pending = [];
+		const attributes = {};
+
+		for ( const key in attributesDef ) {
+
+			pending.push( this.parser.getDependency( 'accessor', attributesDef[ key ] ).then( accessor => {
+
+				attributes[ key ] = accessor;
+				return attributes[ key ];
+
+			}));
+
+		}
+
+		if ( pending.length < 1 ) {
+
+			return null;
+
+		}
+
+		pending.push( this.parser.createNodeMesh( nodeIndex ) );
+
+		return Promise.all( pending ).then( results => {
+
+			const nodeObject = results.pop();
+			const meshes = nodeObject.isGroup ? nodeObject.children : [ nodeObject ];
+			const count = results[ 0 ].count; // All attribute counts should be same
+			const instancedMeshes = [];
+
+			for ( const mesh of meshes ) {
+
+				// Temporal variables
+				const m = new Matrix4();
+				const p = new Vector3();
+				const q = new Quaternion();
+				const s = new Vector3( 1, 1, 1 );
+
+				const instancedMesh = new InstancedMesh( mesh.geometry, mesh.material, count );
+
+				for ( let i = 0; i < count; i ++ ) {
+
+					if ( attributes.TRANSLATION ) {
+
+						p.fromBufferAttribute( attributes.TRANSLATION, i );
+
+					}
+
+					if ( attributes.ROTATION ) {
+
+						q.fromBufferAttribute( attributes.ROTATION, i );
+
+					}
+
+					if ( attributes.SCALE ) {
+
+						s.fromBufferAttribute( attributes.SCALE, i );
+
+					}
+
+					instancedMesh.setMatrixAt( i, m.compose( p, q, s ) );
+
+				}
+
+				// Add instance attributes to the geometry, excluding TRS.
+				for ( const attributeName in attributes ) {
+
+					if ( attributeName !== 'TRANSLATION' &&
+						 attributeName !== 'ROTATION' &&
+						 attributeName !== 'SCALE' ) {
+
+						mesh.geometry.setAttribute( attributeName, attributes[ attributeName ] );
+
+					}
+
+				}
+
+				// Just in case
+				Object3D.prototype.copy.call( instancedMesh, mesh );
+
+				// https://github.com/mrdoob/three.js/issues/18334
+				instancedMesh.frustumCulled = false;
+				this.parser.assignFinalMaterial( instancedMesh );
+
+				instancedMeshes.push( instancedMesh );
+
+			}
+
+			if ( nodeObject.isGroup ) {
+
+				nodeObject.clear();
+
+				nodeObject.add( ... instancedMeshes );
+
+				return nodeObject;
+			}
+
+			return instancedMeshes[ 0 ];
+
+		} );
+
+	}
+
+}
+
 /* BINARY EXTENSION */
 const BINARY_EXTENSION_HEADER_MAGIC = 'glTF';
 const BINARY_EXTENSION_HEADER_LENGTH = 12;

+ 287 - 0
examples/models/gltf/DamagedHelmet/glTF-instancing/DamagedHelmetGpuInstancing.gltf

@@ -0,0 +1,287 @@
+{
+    "extensionsUsed" : [
+	    "EXT_mesh_gpu_instancing"
+    ],
+    "extensionsRequired" : [
+	    "EXT_mesh_gpu_instancing"
+    ],
+    "accessors" : [
+        {
+            "bufferView" : 0,
+            "componentType" : 5123,
+            "count" : 46356,
+            "max" : [
+                14555
+            ],
+            "min" : [
+                0
+            ],
+            "type" : "SCALAR"
+        },
+        {
+            "bufferView" : 1,
+            "componentType" : 5126,
+            "count" : 14556,
+            "max" : [
+                0.9424954056739807,
+                0.8128451108932495,
+                0.900973916053772
+            ],
+            "min" : [
+                -0.9474585652351379,
+                -1.18715500831604,
+                -0.9009949564933777
+            ],
+            "type" : "VEC3"
+        },
+        {
+            "bufferView" : 2,
+            "componentType" : 5126,
+            "count" : 14556,
+            "max" : [
+                1.0,
+                1.0,
+                1.0
+            ],
+            "min" : [
+                -1.0,
+                -1.0,
+                -1.0
+            ],
+            "type" : "VEC3"
+        },
+        {
+            "bufferView" : 3,
+            "componentType" : 5126,
+            "count" : 14556,
+            "max" : [
+                0.9999759793281555,
+                1.998665988445282
+            ],
+            "min" : [
+                0.002448640065267682,
+                1.0005531199858524
+            ],
+            "type" : "VEC2"
+        },
+	{
+	    "bufferView" : 4,
+	    "componentType" : 5126,
+	    "count" : 64,
+	    "type" : "VEC3",
+	    "max" : [
+		8.0,
+		8.0,
+		0.0
+	    ],
+	    "min" : [
+		0.0,
+		0.0,
+		0.0
+	    ]
+	},
+	{
+	    "bufferView" : 5,
+	    "componentType" : 5126,
+	    "count" : 64,
+	    "type" : "VEC4"
+	},
+	{
+	    "bufferView" : 6,
+	    "componentType" : 5126,
+	    "count" : 64,
+	    "type" : "VEC3",
+	    "max" : [
+		0.125,
+		0.125,
+		0.125
+	    ],
+	    "min" : [
+		0.0,
+		0.0,
+		0.0
+	    ]
+	}
+    ],
+    "asset" : {
+        "generator" : "Khronos Blender glTF 2.0 exporter",
+        "version" : "2.0"
+    },
+    "bufferViews" : [
+        {
+            "buffer" : 0,
+            "byteLength" : 92712,
+            "byteOffset" : 0,
+            "target" : 34963
+        },
+        {
+            "buffer" : 0,
+            "byteLength" : 174672,
+            "byteOffset" : 92712,
+            "target" : 34962
+        },
+        {
+            "buffer" : 0,
+            "byteLength" : 174672,
+            "byteOffset" : 267384,
+            "target" : 34962
+        },
+        {
+            "buffer" : 0,
+            "byteLength" : 116448,
+            "byteOffset" : 442056,
+            "target" : 34962
+        },
+	{
+	    "buffer": 1,
+	    "byteOffset": 0,
+	    "byteLength": 768,
+	    "name": "gpuInstancingTranslation"
+	},
+	{
+	    "buffer": 2,
+	    "byteOffset": 0,
+	    "byteLength": 1024,
+	    "name": "gpuInstancingRotation"
+	},
+	{
+	    "buffer": 3,
+	    "byteOffset": 0,
+	    "byteLength": 768,
+	    "name": "gpuInstancingScale"
+	}
+    ],
+    "buffers" : [
+        {
+            "byteLength" : 558504,
+            "uri" : "../glTF/DamagedHelmet.bin"
+        },
+	{
+	    "uri": "GpuInstancingTranslation.bin",
+	    "byteLength": 768
+	},
+	{
+	    "uri": "GpuInstancingRotation.bin",
+	    "byteLength": 1024
+	},
+	{
+	    "uri": "GpuInstancingScale.bin",
+	    "byteLength": 768
+	}
+    ],
+    "images" : [
+        {
+            "uri" : "../glTF/Default_albedo.jpg"
+        },
+        {
+            "uri" : "../glTF/Default_metalRoughness.jpg"
+        },
+        {
+            "uri" : "../glTF/Default_emissive.jpg"
+        },
+        {
+            "uri" : "../glTF/Default_AO.jpg"
+        },
+        {
+            "uri" : "../glTF/Default_normal.jpg"
+        }
+    ],
+    "materials" : [
+        {
+            "emissiveFactor" : [
+                1.0,
+                1.0,
+                1.0
+            ],
+            "emissiveTexture" : {
+                "index" : 2
+            },
+            "name" : "Material_MR",
+            "normalTexture" : {
+                "index" : 4
+            },
+            "occlusionTexture" : {
+                "index" : 3
+            },
+            "pbrMetallicRoughness" : {
+                "baseColorTexture" : {
+                    "index" : 0
+                },
+                "metallicRoughnessTexture" : {
+                    "index" : 1
+                }
+            }
+        }
+    ],
+    "meshes" : [
+        {
+            "name" : "mesh_helmet_LP_13930damagedHelmet",
+            "primitives" : [
+                {
+                    "attributes" : {
+                        "NORMAL" : 2,
+                        "POSITION" : 1,
+                        "TEXCOORD_0" : 3
+                    },
+                    "indices" : 0,
+                    "material" : 0
+                }
+            ]
+        }
+    ],
+    "nodes" : [
+        {
+	    "extensions" : {
+	      "EXT_mesh_gpu_instancing" : {
+		"attributes" : {
+		  "TRANSLATION" : 4,
+		  "ROTATION" : 5,
+		  "SCALE" : 6
+		}
+	      }
+	    },
+            "mesh" : 0,
+            "name" : "node_damagedHelmet_-6514",
+            "rotation" : [
+                0.7071068286895752,
+                0.0,
+                -0.0,
+                0.7071068286895752
+            ]
+        }
+    ],
+    "samplers" : [
+        {}
+    ],
+    "scene" : 0,
+    "scenes" : [
+        {
+            "name" : "Scene",
+            "nodes" : [
+                0
+            ]
+        }
+    ],
+    "textures" : [
+        {
+            "sampler" : 0,
+            "source" : 0
+        },
+        {
+            "sampler" : 0,
+            "source" : 1
+        },
+        {
+            "sampler" : 0,
+            "source" : 2
+        },
+        {
+            "sampler" : 0,
+            "source" : 3
+        },
+        {
+            "sampler" : 0,
+            "source" : 4
+        }
+    ]
+}

BIN
examples/models/gltf/DamagedHelmet/glTF-instancing/GpuInstancingRotation.bin


BIN
examples/models/gltf/DamagedHelmet/glTF-instancing/GpuInstancingScale.bin


BIN
examples/models/gltf/DamagedHelmet/glTF-instancing/GpuInstancingTranslation.bin


BIN
examples/screenshots/webgl_loader_gltf_instancing.jpg


+ 118 - 0
examples/webgl_loader_gltf_instancing.html

@@ -0,0 +1,118 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - GLTFloader + Instancing</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> - GLTFLoader + <a href="https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/EXT_mesh_gpu_instancing/README.md" target="_blank" rel="noopener">EXT_mesh_gpu_instancing</a><br />
+			Battle Damaged Sci-fi Helmet by
+			<a href="https://sketchfab.com/theblueturtle_" target="_blank" rel="noopener">theblueturtle_</a><br />
+			<a href="https://hdrihaven.com/hdri/?h=royal_esplanade" target="_blank" rel="noopener">Royal Esplanade</a> by <a href="https://hdrihaven.com/" target="_blank" rel="noopener">HDRI Haven</a>
+		</div>
+
+		<!-- Import maps polyfill -->
+		<!-- Remove this when import maps will be widely supported -->
+		<script async src="https://unpkg.com/[email protected]/dist/es-module-shims.js"></script>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+
+			import { OrbitControls } from './jsm/controls/OrbitControls.js';
+			import { GLTFLoader } from './jsm/loaders/GLTFLoader.js';
+			import { RGBELoader } from './jsm/loaders/RGBELoader.js';
+
+			let camera, scene, renderer;
+
+			init();
+			render();
+
+			function init() {
+
+				const container = document.createElement( 'div' );
+				document.body.appendChild( container );
+
+				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 0.25, 20 );
+				camera.position.set( - 0.90, 0.41, -0.89 );
+
+				scene = new THREE.Scene();
+
+				new RGBELoader()
+					.setPath( 'textures/equirectangular/' )
+					.load( 'royal_esplanade_1k.hdr', function ( texture ) {
+
+						texture.mapping = THREE.EquirectangularReflectionMapping;
+
+						scene.background = texture;
+						scene.environment = texture;
+
+						render();
+
+						// model
+
+						const loader = new GLTFLoader().setPath( 'models/gltf/DamagedHelmet/glTF-instancing/' );
+						loader.load( 'DamagedHelmetGpuInstancing.gltf', function ( gltf ) {
+
+							scene.add( gltf.scene );
+
+							render();
+
+						} );
+
+					} );
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.toneMapping = THREE.ACESFilmicToneMapping;
+				renderer.toneMappingExposure = 1;
+				renderer.outputEncoding = THREE.sRGBEncoding;
+				container.appendChild( renderer.domElement );
+
+				const controls = new OrbitControls( camera, renderer.domElement );
+				controls.addEventListener( 'change', render ); // use if there is no animation loop
+				controls.minDistance = 0.2;
+				controls.maxDistance = 10;
+				controls.target.set( 0, 0.25, 0 );
+				controls.update();
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				render();
+
+			}
+
+			//
+
+			function render() {
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>