Rectangle Elements
Naturally we don't want just one triangle. When you're playing The Witcher 3, there's at least two triangles on the screen (maybe more!).
Let's move on to drawing a rectangle. For this we need a second triangle.
We could just add three more vertex entries and call it a day. If we wanted two triangles that were each on their own that's what we might do. However, since these two triangles making up our rectangle are going to be directly touching, that means we'd have six vertexes making up only four "real" points. That's 50% more space used than we want! It may seem small now, but a complete model for a tree or a person or something like that can easily end up being thousands of triangles. Making that be 50% more space used is a bad time.
Of course this problem of duplicate vertices is a fairly easy problem to solve, and GL has it covered. What we do is specify an Index Buffer. It holds the indexes of the vertex buffer entries we want to use to form each geometry element (in this case triangles). Then the vertex buffer doesn't need to have any duplicates, we just have more than one triangle index the same vertex.
Note: What we'll be drawing is usually called a "quad", because the important part is that it has four outside edges. It's not really important that the edges are in two pairs of parallel lines at right angles with each other like a true rectangle has.
Data
So we've got some new data. We're going to have 4 vertex entries that describes the points we want to use, and an index buffer with 2 entries where each entry describes a triangle using the points.
#![allow(unused)] fn main() { type Vertex = [f32; 3]; type TriIndexes = [u32; 3]; const VERTICES: [Vertex; 4] = [[0.5, 0.5, 0.0], [0.5, -0.5, 0.0], [-0.5, -0.5, 0.0], [-0.5, 0.5, 0.0]]; const INDICES: [TriIndexes; 2] = [[0, 1, 3], [1, 2, 3]]; }
Element Buffer Object
Our indexes go into a separate kind of buffer. This is that ElementArray
buffer type that I snuck into the cleanup lesson.
After we make and bind our vertex data we also bind a buffer for the element data and upload it, the code looks nearly identical:
#![allow(unused)] fn main() { let ebo = Buffer::new().expect("Couldn't make the element buffer."); ebo.bind(BufferType::ElementArray); learn::buffer_data( BufferType::ElementArray, bytemuck::cast_slice(&INDICES), GL_STATIC_DRAW, ); }
Draw It!
Finally, instead of calling glDrawArrays
, we use a separate function called glDrawElements
.
mode
: The style of drawing. We're still drawing triangles so we keep that from before.count
: The number of index elements to draw. We want two triangles to form our quad, and there's three indexes per triangle, so we put 6.type
: This is the type of the index data. Theu32
type is specified withGL_UNSIGNED_INT
. I usedu32
out of habit, we could have made our indexes beu16
oru8
as well.indices
: is a pointer to the position within the index buffer to start the drawing with. Similar to the attribute specification, you pretend the index buffer starts at address 0 and then you decide the offset you want, and then cast that to a*const
pointer.
So the usage looks like this:
#![allow(unused)] fn main() { // and then draw! unsafe { glClear(GL_COLOR_BUFFER_BIT); glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0 as *const _); } win.swap_window(); }
Bonus: Wireframe Mode
Since this lesson is really short let's look at one extra ability we can use.
You often see 3d models with just the outlines of each triangle. "Wireframe
mode" it's sometimes called. We can easily do that with
glPolygonMode
.
- We can specify the
face
, but in the Core profile the only valid value isGL_FRONT_AND_BACK
(in Compatibility profile you can also useGL_FRONT
orGL_BACK
). - We also specify the
mode
. The default isGL_FILL
, but WithGL_LINE
we get the wireframe effect.GL_POINT
is also allowed, but makes it pretty hard to see what's going on.
All this can go in our lib.rs
file:
#![allow(unused)] fn main() { /// The polygon display modes you can set. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum PolygonMode { /// Just show the points. Point = GL_POINT as isize, /// Just show the lines. Line = GL_LINE as isize, /// Fill in the polygons. Fill = GL_FILL as isize, } /// Sets the font and back polygon mode to the mode given. pub fn polygon_mode(mode: PolygonMode) { unsafe { glPolygonMode(GL_FRONT_AND_BACK, mode as GLenum) }; } }
And then before our main loop we can turn it on:
#![allow(unused)] fn main() { learn::polygon_mode(PolygonMode::Line); }
Now we get a wireframe quad! and it looks like two triangles just like it should!