In which you learn how to shift objects around through vector addition.

To translate an object, you add offsets to its position. If you want to shift a triangle around, perhaps in response to the user clicking and dragging, you add the desired offset to each of the vertex positions. However, the positions are stored in VBOs on the GPU and are not easily modified. For this reason and others, you generally don't modify the underlying vertex data of an object. Instead, you store its transformation in some companion data structure which heads to the GPU along with the vertex attributes.

For the time being, that companion data structure is a 3-vector that will be sent to the vertex shader as a uniform. That 3-vector will be added onto the position at render time. Eventually this companion data structure will be a matrix.

Translation is sometimes expressed as vector addition. Here position \(p\) is translated by \(\textrm{offset}\):

$$ \begin{bmatrix}p_x \\ p_y \\ p_z \end{bmatrix} + \begin{bmatrix} \textrm{offset}_x \\ \textrm{offset}_y \\ \textrm{offset}_z \end{bmatrix} = \begin{bmatrix}p_x + \textrm{offset}_x \\ p_y + \textrm{offset}_y \\ p_z + \textrm{offset}_z\end{bmatrix} $$

In GLSL, the offset is declared as a uniform and applied using the + operator:

uniform vec3 offset;
in vec3 position;

void main() {
  vec3 translatedPosition = position + offset;
  gl_Position = vec4(translatedPosition, 1.0);

On the CPU side of your application, you must set the uniform before drawing. Here the object is shifted a little bit to the right and a bit more up:

shaderProgram.setUniform3f('offset', 0.1, 0.3, 0);

Try adding translation to one of your renderers.

Vectors Over Scalars

Suppose offset was a vec2 instead of a vec3. The GLSL code above would not compile because the vector types do not match. In such a situation, you can add the two vectors in other ways:

// Through 3-component constructor. Slower.
vec3 translatedPosition = vec3(
  position.x + offset.x,
  position.y + offset.y,

// Through copy-and-mutate. Slower.
vec3 translatedPosition = position;
position.x += offset.x;
position.y += offset.y;

// Through converting constructor. Faster.
vec3 translatedPosition = position + vec3(offset, 0);

The last of these forms is fastest. When the GPU adds two vectors, it adds the x-, y-, and z-components all at the same time. The other two forms use scalar operations, which run in sequence. You should favor vector operations as much as possible.