Translate

How to 3D

Chapter 2: Triangles and Transformations

Translate

To translate a model, we add an offset to each of its positions. Translation is sometimes expressed as vector addition. Here position \(\mathbf{p}\) is translated by vector \(\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}$$

A translation is not achieved by permanently adding the offset to the positions stored in the vertex buffer. Vertex buffers should generally be treated as read-only data. Instead, we apply the offset to the position in the vertex shader.

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

uniform vec3 offset;
in vec3 position;

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

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

On the CPU side of our renderer, we set the uniform before drawing. Here the object is shifted a little bit to the right and a bit more up:

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

Matching Vector Types

Since we are currently confining our geometry to the \(z = 0\) plane, we may be tempted to make the offset uniform a vec2 and set the uniform with this simpler call:

shader.setUniform2f('offset', 0.1, 0.3);
shader.setUniform2f('offset', 0.1, 0.3);

The GLSL code no longer compiles because we can't add a vec3 and a vec2. One possible fix is to call the vec3 constructor explicitly and add the components together manually with scalar addition:

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

This is not an ideal solution because scalar addition is slow and verbose. Another possible fix is to duplicate the position and mutate the x- and y-components with the += operator:

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

This is still scalar addition, however. First the x-component is mutated, and then the y-component. The graphics card will perform these additions in parallel if we use vector addition. However, the vectors must be of the same type. Leaving offset as a vec3 is probably the best solution. If we have no control over the uniform type, we can convert the vec2 to a vec3 with the aid of converting constructor:

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

Now the x-, y-, and z-components are all added simultaneously. Favor vector operations over scalar operations as much as possible.

← TransformationsScale →