Skip to content

Instantly share code, notes, and snippets.

@nicoptere
Last active February 24, 2021 14:44
Show Gist options
  • Save nicoptere/5f79ed905f473269b897147e50dc7ab1 to your computer and use it in GitHub Desktop.
Save nicoptere/5f79ed905f473269b897147e50dc7ab1 to your computer and use it in GitHub Desktop.
create InstancedGeometries from GLTF
import {
BufferAttribute,
Group,
InstancedBufferAttribute,
InstancedBufferGeometry,
Mesh,
MeshStandardMaterial,
Vector3,
} from "three";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
export default class Instances extends Group {
constructor(model_url) {
super();
//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
geom.setAttribute(
"position",
new BufferAttribute(src.getAttribute("position").array, 3)
);
//copies the uvs from the loaded mesh
geom.setAttribute(
"uv",
new BufferAttribute(src.getAttribute("uv").array, 2)
);
//copies the normals from the loaded mesh
geom.setAttribute(
"normal",
new BufferAttribute(src.getAttribute("normal").array, 3)
);
//copiesthe indices of the loaded mesh
geom.setIndex(src.getIndex());
//instances attributes
//add the instanced attribute to the geometry
geom.setAttribute(
"offset",
new InstancedBufferAttribute(new Float32Array(offset), 3)
);
//material:
//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({
map: mat.map,
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
// https://ycw.github.io/three-shaderlib-skim/#/latest/standard/vertex
material.onBeforeCompile = (mat) => {
let vs = mat.vertexShader;
vs = vs.replace(
"<common>",
`<common>
attribute vec3 offset;// add the 'offset' attribute to the vertex shader
`
);
vs = vs.replace(
"<begin_vertex>",
`<begin_vertex>
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);
this.add(mesh);
}
});
//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");
scene.add(inst);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment