Ducks in a row anyone?

Hi,

I’m trying to generate some ducks in a row using an InstancedMesh of the duck glTF (I started this example using the damaged helmet example but I’m hoping to create an arcade style game). I’m getting the necessary draw calls (3) and the increase in the triangle count of 1,000 = 4,210,000 triangles, but I’m unfortunately not able to see the ducks. All of this works just fine outside of the loop, but once the instanceMesh is added inside of the loop, the duck model(s) disappear. Any suggestions?

Your help would be greatly appreciated!

import "./style.css";
import * as THREE from "three";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls.js";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js";
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader.js";

let container, controls;
let camera, scene, renderer;

init();
render();

function init() {
  container = document.createElement('div');
  document.body.appendChild(container);

  scene = new THREE.Scene();

  const axesHelper = new THREE.AxesHelper(20);
  scene.add(axesHelper);

  camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 1000);
  camera.position.set(48, 30, -48);

  new RGBELoader()
    .setDataType(THREE.UnsignedByteType)
    .load('/textures/environmentMaps/royal_esplanade_1k.hdr', (texture) => {
      var envMap = pmremGenerator.fromEquirectangular(texture).texture;

      scene.environment = envMap;

      // After cube textures are generated, then this can be disposed of.
      texture.dispose();
      pmremGenerator.dispose();

      render();

      const dummyObject = new THREE.Object3D();

      let count = 1000;
      let xDistance = 5;
      let zDistance = 5;
      let xOffset = 1;

      var loader = new GLTFLoader();
      loader.load('/models/glTF/Duck.glb', (gltf) => {
        gltf.scene.traverse(function (child) {
          if (child.isMesh) {

            const instancedMesh = new THREE.InstancedMesh(child.geometry, child.material, count);
            instancedMesh.setMatrixAt(0, dummyObject.matrix);

            // Iterate from here:
            for (let i = 0; i < count; i++) {
              for (let j = 0; j < count; j++) {
                instancedMesh.position.x = i;
                instancedMesh.position.z = j;
                instancedMesh.position.x = xDistance * i + xOffset;
                instancedMesh.position.z = zDistance * j;
                instancedMesh.setMatrixAt(i + j * count, dummyObject.matrix);
                instancedMesh.scale.x = 10;
                instancedMesh.scale.y = 10;
                instancedMesh.scale.z = 10;
                instancedMesh.rotation.y = THREE.Math.degToRad(0);
                instancedMesh.rotation.x = THREE.Math.degToRad(90);
                instancedMesh.rotation.z = THREE.Math.degToRad(-90);
                scene.add(instancedMesh);
                instancedMesh.matrix.needsUpdate = true;
              }
            }
          }
        });
        render();
      });
    });

  renderer = new THREE.WebGLRenderer({ antialias: true });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(window.innerWidth, window.innerHeight);
  renderer.physicallyCorrectLights = true;
  renderer.outputEncoding = THREE.sRGBEncoding;
  renderer.toneMapping = THREE.ReinhardToneMapping;
  renderer.toneMappingExposure = 1;
  container.appendChild(renderer.domElement);

  var pmremGenerator = new THREE.PMREMGenerator(renderer);
  pmremGenerator.compileEquirectangularShader();

  controls = new OrbitControls(camera, renderer.domElement);
  controls.addEventListener('change', render);
  controls.minDistance = 5;
  controls.maxDistance = 10000;
  controls.update();

  window.addEventListener('resize', onWindowResize, false);
}


function onWindowResize() {

  camera.aspect = window.innerWidth / window.innerHeight;
  camera.updateProjectionMatrix();
  renderer.setSize(window.innerWidth, window.innerHeight);

  render();
}


function render() {

  renderer.render(scene, camera);

}

An InstancedMesh can only be added to the scene once, and has only a single position, rotation, and scale property. Think of it as the “group” containing all instances, and changing its position with position.y++ will shift all instances up (+Y) by 1 unit.

Perhaps this example would be more helpful as a reference?

https://threejs.org/examples/?q=instanc#webgl_instancing_dynamic

The loop should look something like this:

const count = 5;
const spacing = 5;

const mesh = new THREE.InstancedMesh(geometry, material, count);
const dummy = new THREE.Object3D();

for (let i = 0; i < count; i++) {
  for (let j = 0; j < count; j++) {
    dummy.position.set(i * spacing, j * spacing, 0);
    mesh.setMatrixAt(i * count + j, dummy.matrix);
  }
}

scene.add(mesh);
mesh.instanceMatrix.needsUpdate = true;
3 Likes

Thank you Don!