# Instanced Transform

info

The tutorial pages have not yet been updated for luma.gl v9.

• TransformFeedback based examples are temporarily disabled until `Transform` class is ported to luma.gl v9

## Note: Transform examples temporarily disabled

In this final tutorial, we'll pull together almost everything we've learned in past tutorials into a single scene: lighing, textures, geometry, shader modules, instancing and transform feedback. Whew! This will build on the previous tutorial, so it might be helpful to start with its source code.

We'll be drawing 4 instanced cubes, textured and lit in the same way as in the lighting tutorial, but with the animations updated by transform feedback.

The `Transform` class is the only addition we need for our imports:

``import {AnimationLoop, Model, Transform, CubeGeometry} from '@luma.gl/engine';import {Buffer, Texture2D, clear, setParameters} from '@luma.gl/webgl';import {phongLighting} from '@luma.gl/shadertools';import {Matrix4} from '@math.gl/core';``

The vertex shader for our transform feedback is quite simple. It just increments a scalar rotation value on each run:

``const transformVs = `  in float rotations;  out float vRotation;  void main() {    vRotation = rotations + 0.01;  }`;``

In order to handle rotation updates in the transform feedback, we have to move construction of the rotation matrix into the vertex shader. We'll use an axis-angle, passing the axis and rotation angle as instanced ins (with the rotation angles being updated by transform feedback):

``const vs = `\  in vec3 positions;  in vec3 normals;  in vec2 texCoords;  in vec2 offsets;  in vec3 axes;  in float rotations;  uniform mat4 uView;  uniform mat4 uProjection;  out vec3 vPosition;  out vec3 vNormal;  out vec2 vUV;  void main(void) {    float s = sin(rotations);    float c = cos(rotations);    float t = 1.0 - c;    float xt = axes.x * t;    float yt = axes.y * t;    float zt = axes.z * t;    float xs = axes.x * s;    float ys = axes.y * s;    float zs = axes.z * s;    mat3 rotationMat = mat3(        axes.x * xt + c,        axes.y * xt + zs,        axes.z * xt - ys,        axes.x * yt - zs,        axes.y * yt + c,        axes.z * yt + xs,        axes.x * zt + ys,        axes.y * zt - xs,        axes.z * zt + c    );    vPosition = rotationMat * positions;    vPosition.xy += offsets;    vNormal = rotationMat * normals;    vUV = texCoords;    gl_Position = uProjection * uView * vec4(vPosition, 1.0);  }``

We also pass an `offsets` instanced in to position each cube.

Our fragment shader doesn't change at all:

``const fs = `\  precision highp float;  uniform sampler2D uTexture;  uniform vec3 uEyePosition;  in vec3 vPosition;  in vec3 vNormal;  in vec2 vUV;  void main(void) {    vec3 materialColor = texture2D(uTexture, vec2(vUV.x, 1.0 - vUV.y)).rgb;    vec3 surfaceColor = lighting_getLightColor(materialColor, uEyePosition, vPosition, normalize(vNormal));    gl_FragColor = vec4(surfaceColor, 1.0);  }`;``

Our `onInitialize` method will need several updates. First we create buffers for our instanced data:

``const offsetBuffer = device.createBuffer(new Float32Array([3, 3, -3, 3, 3, -3, -3, -3]));const axisBufferData = new Float32Array(12);for (let i = 0; i < 4; ++i) {  const vi = i * 3;  const x = Math.random();  const y = Math.random();  const z = Math.random();  const l = Math.sqrt(x * x + y * y + z * z);  axisBufferData[vi] = x / l;  axisBufferData[vi + 1] = y / l;  axisBufferData[vi + 2] = z / l;}const axisBuffer = device.createBuffer(axisBufferData);const rotationBuffer = device.createBuffer(  new Float32Array([    Math.random() * Math.PI * 2,    Math.random() * Math.PI * 2,    Math.random() * Math.PI * 2,    Math.random() * Math.PI * 2  ]));``

The `offsetBuffer` sets positions so the cubes will be in a square formation. The `axisBuffer` looks more complicated, but its simply a set of 4 normalized vectors about which we'll rotate our cubes. Finally, the `rotationBuffer` simply starts with 4 random angles between 0 and 2π.

The `Transform` is straightforward to set up, simply taking the `rotationBuffer` and our vertex shader as input:

``const transform = new Transform(device, {  vs: transformVs,  sourceBuffers: {    rotations: rotationBuffer  },  feedbackMap: {    rotations: 'vRotation'  },  elementCount: 4});``

And the `Model` needs to be updated to take the instanced ins and `instanceCount`:

``const model = new Model(device, {  vs,  fs,  geometry: new CubeGeometry(),  ins: {    offsets: [offsetBuffer, {divisor: 1}],    axes: [axisBuffer, {divisor: 1}],    rotations: [rotationBuffer, {divisor: 1}]  },  uniforms: {    uTexture: texture,    uEyePosition: eyePosition,    uView: viewMatrix  },  modules: [phongLighting],  moduleSettings: {    material: {      specularColor: [255, 255, 255]    },    lights: [      {        type: 'ambient',        color: [255, 255, 255]      },      {        type: 'point',        color: [255, 255, 255],        position: [4, 8, 4]      }    ]  },  instanceCount: 4});``

Our `onRender` needs an update to perform the transform feedback and pass the transformed rotation buffer to the `Model`:

``  override onRender({device, aspect, model, transform, projectionMatrix}) {    projectionMatrix.perspective({fovy: Math.PI / 3, aspect});    transform.run();    clear(device, {color: [0, 0, 0, 1], depth: true});    model      .setAttributes({rotations: [transform.getBuffer('vRotation'), {divisor: 1}]})      .setUniforms({uProjection: projectionMatrix})      .draw();    transform.swap();  }``

If all went well, you should see 4 rotating cubes. This scene is significantly more complex than anything we've seen before, so take some time to play around with it and get to know the various parts.

``import {AnimationLoop, Model, Transform, CubeGeometry} from '@luma.gl/engine';import {Buffer, Texture2D, clear, setParameters} from '@luma.gl/webgl';import {phongLighting} from '@luma.gl/shadertools';import {Matrix4} from '@math.gl/core';const transformVs = `  in float rotations;  out float vRotation;  void main() {    vRotation = rotations + 0.01;  }`;const vs = `\  in vec3 positions;  in vec3 normals;  in vec2 texCoords;  in vec2 offsets;  in vec3 axes;  in float rotations;  uniform mat4 uView;  uniform mat4 uProjection;  out vec3 vPosition;  out vec3 vNormal;  out vec2 vUV;  void main(void) {    float s = sin(rotations);    float c = cos(rotations);    float t = 1.0 - c;    float xt = axes.x * t;    float yt = axes.y * t;    float zt = axes.z * t;    float xs = axes.x * s;    float ys = axes.y * s;    float zs = axes.z * s;    mat3 rotationMat = mat3(        axes.x * xt + c,        axes.y * xt + zs,        axes.z * xt - ys,        axes.x * yt - zs,        axes.y * yt + c,        axes.z * yt + xs,        axes.x * zt + ys,        axes.y * zt - xs,        axes.z * zt + c    );    vPosition = rotationMat * positions;    vPosition.xy += offsets;    vNormal = rotationMat * normals;    vUV = texCoords;    gl_Position = uProjection * uView * vec4(vPosition, 1.0);  }`;const fs = `\  precision highp float;  uniform sampler2D uTexture;  uniform vec3 uEyePosition;  in vec3 vPosition;  in vec3 vNormal;  in vec2 vUV;  void main(void) {    vec3 materialColor = texture2D(uTexture, vec2(vUV.x, 1.0 - vUV.y)).rgb;    vec3 surfaceColor = lighting_getLightColor(materialColor, uEyePosition, vPosition, normalize(vNormal));    gl_FragColor = vec4(surfaceColor, 1.0);  }`;const loop = new AnimationLoop({  override onInitialize({gl}) {    setParameters(gl, {      depthTest: true,      depthFunc: gl.LEQUAL    });    const offsetBuffer = device.createBuffer(new Float32Array([3, 3, -3, 3, 3, -3, -3, -3]));    const axisBufferData = new Float32Array(12);    for (let i = 0; i < 4; ++i) {      const vi = i * 3;      const x = Math.random();      const y = Math.random();      const z = Math.random();      const l = Math.sqrt(x * x + y * y + z * z);      axisBufferData[vi] = x / l;      axisBufferData[vi + 1] = y / l;      axisBufferData[vi + 2] = z / l;    }    const axisBuffer = device.createBuffer(axisBufferData);    const rotationBuffer = device.createBuffer(      new Float32Array([        Math.random() * Math.PI * 2,        Math.random() * Math.PI * 2,        Math.random() * Math.PI * 2,        Math.random() * Math.PI * 2      ])    );    const texture = device.createTexture({data: 'vis-logo.png'});    const eyePosition = [0, 0, 10];    const viewMatrix = new Matrix4().lookAt({eye: eyePosition});    const projectionMatrix = new Matrix4();    const transform = new Transform(device, {      vs: transformVs,      sourceBuffers: {        rotations: rotationBuffer      },      feedbackMap: {        rotations: 'vRotation'      },      elementCount: 4    });    const model = new Model(device, {      vs,      fs,      geometry: new CubeGeometry(),      ins: {        offsets: [offsetBuffer, {divisor: 1}],        axes: [axisBuffer, {divisor: 1}],        rotations: [rotationBuffer, {divisor: 1}]      },      uniforms: {        uTexture: texture,        uEyePosition: eyePosition,        uView: viewMatrix      },      modules: [phongLighting],      moduleSettings: {        material: {          specularColor: [255, 255, 255]        },        lights: [          {            type: 'ambient',            color: [255, 255, 255]          },          {            type: 'point',            color: [255, 255, 255],            position: [4, 8, 4]          }        ]      },      instanceCount: 4    });    return {      model,      transform,      projectionMatrix    };  },  override onRender({device, aspect, model, transform, projectionMatrix}) {    projectionMatrix.perspective({fovy: Math.PI / 3, aspect});    transform.run();    clear(device, {color: [0, 0, 0, 1], depth: true});    model      .setAttributes({rotations: [transform.getBuffer('vRotation'), {divisor: 1}]})      .setUniforms({uProjection: projectionMatrix})      .draw();    transform.swap();  }});loop.start();``