# Drawing lines using OpenGL and Rust

Sun, Aug 21, 2016I’ve been playing with graphics programming in Rust recently using glium, which is an OpenGL wrapper. One of the things that I wanted to do was draw a map using lines from a file. OpenGL works by drawing triangles, so I had to figure out a way to turn a line into one or more triangles.

After Googling a bit, I found this blog post from Mapbox about drawing antialiased lines using OpenGL. It looked like what I wanted, but it took some experimentation to figure out exactly what I needed to do to get it to work, so I decided to write it down here. I’m also just beginning to learn about OpenGL, so I’m going to explain many of the things I learned about OpenGL while figuring this out. If you already know OpenGL basics you might be able to just look at the code samples in this post.

The general concept is to think of each line segment as a very long and thin rectangle. One side is the length of the line, and the other side is the width of the line. A rectangle can be drawn as two triangles by dividing it along one of the diagonals.

Okay, so now we need to generate all the vertices (points). We could calculate all four corners right away, but since I want to be able to zoom in and out, that won’t work. When we zoom in, the lines would get longer (which we want), but also wider (which we don’t want). Instead we’re going to store the endpoints of the line, and the direction we need to move the vertices to get them in the right place. I ended up with this Vertex struct definition:

```
#[derive(Copy, Clone, Debug)]
struct Vertex {
a_pos: [f32; 2],
a_normal: [f32; 2],
a_color: [f32; 3],
}
implement_vertex!(Vertex, a_pos, a_normal, a_color);
```

The `a_pos`

attribute will contain the “start position” of the vertex before we move it to give the line width. For each line, we’ll have four vertices: two at each end.

The `a_normal`

attribute contains a normal unit vector for the line. “Normal” comes from geometry and means that the vector is perpendicular to our line. A unit vector is a vector that has length 1. In short, `a_normal`

is a vector that points in the direction we want to move `a_pos`

to move it to the corner of the rectangle. Since it’s 1 unit long, we’ll need to multiply it by half the width of the line before adding it to `a_pos`

.

Finally, the `a_color`

attribute stores the color of the line. I’m just using 24-bit colors, so using 12 bytes is a bit overkill, but I haven’t figured out how to use `u8`

s instead of `f32`

s yet.

Now that we have a data structure for the vertices, we need to make all the vertices and store them somewhere. Since we’re drawing triangles, not rectangles, each triangle will have two points that are shared with the other triangle in the rectangle. Instead of storing those vertices twice, we’ll store a list of indices, which tells OpenGL which vertices make up a triangle. Take this rectangle for example:

```
1----0 <-- This is the start of the line
| /|
| / |
| / |
|/ |
3----2 <-- This is the end of the line
```

The points 1 and 4 are the same for both triangles, so we can tell OpenGL that point 0, 1 and 3 make a triangle, and point 0, 3 and 2 make another triangle. We’ll use a `Vec<Vertex>`

to store the vertices and a `Vec<u32>`

to store the indices:

```
let vertices: Vec<Vertex> = Vec::new();
let indices: Vec<u32> = Vec::new();
```

Let’s first define a line that we want to draw. I’ll chose a line from `(-0.5, -0.5)`

to `(0.5, 0.5)`

, which will go diagonally across the screen from the lower left portion of the screen to the upper right portion of the screen:

```
let (x1, y1) = (-0.5, -0.5);
let (x2, y2) = (0.5, 0.5);
```

To make the normal unit vector, we need to know the length of the line, as well as the “length” it has along the x and the y-axis:

```
let dx = x2 - x1;
let dy = y2 - y1;
let length = (dx.powi(2) + dy.powi(2)).sqrt();
```

We actually have two normal unit vectors for each point, one in each direction perpendicular to the line. I found out that the normal unit vectors would be `(-dy/length, dx/length)`

and `(dy/length, -dx/length)`

. See the end of the blog post for a longer explanation of why that works.

```
vertices.push(Vertex {
a_pos: [x1, y1],
a_normal: [-dy / length, dx / length],
a_color: color,
});
vertices.push(Vertex {
a_pos: [x1, y1],
a_normal: [dy / length, -dx / length],
a_color: color,
});
vertices.push(Vertex {
a_pos: [x2, y2],
a_normal: [-dy / length, dx / length],
a_color: color,
});
vertices.push(Vertex {
a_pos: [x2, y2],
a_normal: [dy / length, -dx / length],
a_color: color,
});
let start = vertices.len() as u32 - 4;
indices.append(&mut vec![start + 0, start + 3, start + 2]);
indices.append(&mut vec![start + 0, start + 1, start + 3]);
```

If you look at that example line again, you’ll see that the indices are added in the counter-clockwise direction:

```
1----0 <-- This is the start of the line
| /|
| / |
| / |
|/ |
3----2 <-- This is the end of the line
```

This allows us to tell OpenGL to only draw one side of the triangle (we’re just making a 2D map, so drawing the back of it isn’t necessary) using something called face culling. I’m not doing this yet, since the performance seems to be fast enough without it (again, just boring 2D instead of 3D), but at least it’s possible.

Now we need to write the shaders that take these vertices, move them all around and colors the lines. Let’s start with the vertex shader:

```
#version 150
// Version 150 was the lowest version that worked on my Mac.
// The attributes from the Vertex struct
in vec2 a_pos;
in vec2 a_normal;
in vec3 a_color;
// We'll output the color again so the fragment shader can use it.
out vec3 v_color;
// We have three uniforms set: The width of the line, the modelview
// transformation matrix and the perspective transformation matrix
// (more on the transformation matrices later).
uniform float u_linewidth;
uniform mat4 u_mv_matrix;
uniform mat4 u_p_matrix;
void main() {
// Set v_color so the fragment shader gets access to the color.
v_color = a_color;
// Multiply the normal vector by half the line width to make a
// delta vector that we can add to the position to give the line
// the right width.
vec4 delta = vec4(a_normal * (u_linewidth / 2.0), 0, 0);
// Multiply a_pos by the modelview matrix to move it to the right
// place on the screen and zoom.
vec4 pos = u_mv_matrix * vec4(a_pos, 1, 1);
// Finally, add the delta and multiply with the perspective matrix
// (which corrects for the aspect ratio).
gl_Position = u_p_matrix * (pos + delta);
}
```

Now we have all the rectangles, and we need to give them some color. Since everything has the same solid color, the fragment shader is quite simple:

```
#version 150
in vec3 v_color;
out vec4 color;
void main() {
color = vec4(v_color, 1.0);
}
```

Once we have all this, we can draw the lines using glium:

```
let vertex_buffer =
glium::VertexBuffer::new(&display, &vertices).unwrap();
let index_buffer =
glium::IndexBuffer::new(
&display,
glium::index::PrimitiveType::TrianglesList,
&indices
).unwrap();
let program =
glium::Program::from_source(
&display,
vertex_shader_src,
fragment_shader_src,
None
).unwrap();
let zoom_level = 1.0;
let center = (0.1, 0.1);
loop {
let mut target = display.draw();
target.clear_color_and_depth((0.0, 0.0, 0.0, 1.0), 1.0);
let (width, height) = target.get_dimensions();
let aspect_ratio = height as f32 / width as f32;
let line_width = 2.0f32 / (height as f32);
let perspective = [[aspect_ratio, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0f32]];
let modelview = [[zoom_level, 0.0, 0.0, 0.0],
[0.0, zoom_level, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[(-center.0 as f32), (-center.1 as f32), 0.0, 1.0f32]];
let uniforms = uniform! {
u_linewidth: line_width,
u_mv_matrix: modelview,
u_p_matrix: perspective,
};
target.draw(
&vertex_buffer,
&index_buffer,
&program,
&uniforms,
Default::default()
).unwrap();
target.finish().unwrap();
}
```

The Mapbox blog post had some details on anti-aliasing and line joins too, but I’m just drawing really thin lines (1-2 pixels wide), so 4x MSAA and no line joins looks good to my eyes.

## Making a normal unit vector

Earlier I said that the normal unit vectors would be `(-dy/length, dx/length)`

and `(dy/length, -dx/length)`

. I got this by first making the vector `(dx, dy)`

(which is a vector that has the same direction and length as the line segment we’re drawing), and divided it by the length of the line segment to make a unit vector. To make it a normal, we need to rotate it 90°, which we can do by using a rotation matrix:

Since we’re rotating by 90°, `$ \cos\theta $`

becomes 0 and `$ \sin\theta $`

becomes 1, and we end up with