Skip to content

Instantly share code, notes, and snippets.

Last active February 24, 2021 14:44
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
Star You must be signed in to star a gist
What would you like to do?
create InstancedGeometries from GLTF
import {
} from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
export default class Instances extends Group {
constructor(model_url) {
//load a GLTF file
let loader = new GLTFLoader();
loader.load(model_url, this.onModelLoaded.bind(this));
onModelLoaded(obj) {
//creata a point cloud to place all the objects
let cloud = this.getPointCloud(1000);
//create the values of an instanced attribute (this will position the meshes in space)
let offset = [];
cloud.forEach((v) => {
offset.push(v.x, v.y, v.z);
//this is the root of the loaded GLTF (not necessarily a Scene Object)
let scene = obj.scene;
console.log("model.scene", scene);
//find each mesh of the scene
scene.traverse((source) => {
//with each mesh
if (source.isMesh) {
//create the "blueprint" mesh, the one that is being copied
let src = source.geometry;
let geom = new InstancedBufferGeometry();
//blueprint attributes
//copies the positions from the loaded mesh
new BufferAttribute(src.getAttribute("position").array, 3)
//copies the uvs from the loaded mesh
new BufferAttribute(src.getAttribute("uv").array, 2)
//copies the normals from the loaded mesh
new BufferAttribute(src.getAttribute("normal").array, 3)
//copiesthe indices of the loaded mesh
//instances attributes
//add the instanced attribute to the geometry
new InstancedBufferAttribute(new Float32Array(offset), 3)
//here it becomes tricky, you can't reuse the loaded object's material directly
//so instead we create a new one and copy values from the loaded object's properties
let mat = source.material;
let material = new MeshStandardMaterial({
color: mat.color,
envMapIntensity: mat.envMapIntensity,
envMap: mat.envMap,
normalMap: mat.normalMap,
specularMap: mat.specularMap,
roughness: mat.roughness,
metalness: mat.metalness,
emissive: mat.emissive,
emissiveMap: mat.emissiveMap,
//second tricky part: we need a custom shader
//for this we'll just hack into an existing material but we could use a custom shader
//this site shows what the 'needles' (<common>, <begin_vertex>) are being replaced with
material.onBeforeCompile = (mat) => {
let vs = mat.vertexShader;
vs = vs.replace(
attribute vec3 offset;// add the 'offset' attribute to the vertex shader
vs = vs.replace(
transformed += offset;// this adds the offset poistion ot the model's position
mat.vertexShader = vs;
//create and add the instanced Mesh
let mesh = new Mesh(geom, material);
//have a cigar, pat self in the back
//go nuts!
getPointCloud(count) {
let points = [];
while (count--) {
let x = (Math.random() - 0.5) * 50;
let y = (Math.random() - 0.5) * 50;
let z = (Math.random() - 0.5) * 50;
points.push(new Vector3(x, y, z));
return points;
import Instances from "./Instances";
let inst = new Instances("path/to/file.glb");
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment