Everything you see in Doom is actually based on 2D data. Clever algorithms make it appear to be 3D.

The world in Quake 3 is described as 3D data. This is smiliar to what you are going to do in your exercises.

Btw, 3D rendering was already possible in Quake 1's time. See GLQuake by ID.

[...] a position vector [...], is a Euclidean vector that represents the position of a point P in space in relation to an arbitrary reference origin O.

Choose 3 position vectors, giving you 3 points in space. 

Connect them, and you have a triangle. 

 

Choose another point and connect it to 2 existing points

and you have 2 triangles.

 

Do this enough times in the correct order and it results in something like... 

This.

Taking the cross-product of two vectors

a and b gives you a vector that is perpendicular to both a and b.

Which direction that vector faces depends on the winding order of the vertices, i.e. in which order the vertices are given.

The OpenGL documentation tells us that vertices, who appear to be wound counter-clock-wise are facing forward.

The red arrows are the surface normals. Look at the way they are facing and how the vertices are wound from the eye's pov.

1. Define what you want to draw as points

 

2. Put those points into memory (preferably in an efficient way)

 

3. Configure OpenGL so that it does what you want

 

3. Give OpenGL the data and let it do its magic

 

4. Images happen

Because of the nature of Moore's law, anything that an extremely clever graphics programmer can do at one point can be replicated by a merely competent programmer some number of years later. 

You really need to know what your machine is doing.

struct Vec3f
{
    float x;
    float y;
    float z;
};

int main()
{
    sizeof(Vec3f); // ???

    return 0;
}

Vec3f

Vec3f.x

Vec3f.y

Vec3f.z

8

32 bit

struct Vec3f
{
    float x;
    float y;
    float z;
}

int main()
{
    Vec3f tri[3]; 

    sizeof(tri); // ???

    return 0;
}

tri[0]

Vec3f

Vec3f

Vec3f

32

96 bit

tri[1]

tri[2]


int main()
{
    glBegin(GL_TRIANGLES);
    glVertex3f(-1.0f,-0.25f,0.0f);
    glVertex3f(-0.5f,-0.25f,0.0f);
    glVertex3f(-0.75f,0.25f,0.0f);
    glEnd();
}

Bar any inherent caching, we are copying the data every frame. 

Imagine a mesh with a few million vertices.

 

There is no way we achieve real time with this.

Why? Look at this.

Imagine the GPU lives here

Imagine the GPU lives here

This is really slow!

Imagine the GPU lives here

So what should we do instead?

We use buffers! 

So what should we do instead?

So what should we do instead?

We use buffers! 

Instead of copying the data every frame, we ask the GPU to take the data once and then hold onto it for us.

 

The GPU then tells us where it stored the data so we can tell it when we want to use that buffer.

You've already seen this, these are vertex buffers.

 

OpenGL calls them VBOs.

So what should we do instead?

We use buffers! 

Instead of copying the data every frame, we ask the GPU to take the data once and then hold onto it for us.

 

The GPU then tells us where it stored the data so we can tell it when we want to use that buffer.

// This is my data.
Vec3f mesh[];

// I will use this to store the location of my data. 
// If the data type seems weird to you ask me now.
GLuint my_buffer;

// Hello OpenGL, I would like one buffer pls.
glGenBuffers(1, &my_buffer);

// I will store an array in this buffer
// and would like to write data into it now.
glBindBuffer(GL_ARRAY_BUFFER, my_buffer);

// Here's the type of data structure, how large my data is, 
// where my data is stored and how I will use the buffer.
glBufferStorage(GL_ARRAY_BUFFER, sizeof(vec3) * sizeof(mesh), 
mesh, GL_STATIC_DRAW);

// Create other buffers...

// Ok I want to use this buffer now, let's bind it.
glBindBuffer(GL_ARRAY_BUFFER, my_buffer);

The GPU now has a copy of my data. This means it lies in VRAM and does not have to be copied over PCIE!

 

If I want to draw the data, I tell the GPU the location of my data and how many times I want to draw it. 

 

I will most likely not have to copy this data again.

 

This approach is much, much, much faster and the modern way.

Ok. That's cool.

 

But we're modern kids and we use shaders now. How do we tell the shader where to find our data?

Vertex Array Object (VAO) is an OpenGL Object that stores all of the state needed to supply vertex data.

 

It stores the format of the vertex data as well as the Buffer Objects providing the vertex data arrays.

 

Note that VAOs do not copy, freeze or store the contents of the referenced buffers.

 

If you change any of the data in the buffers referenced by an existing VAO, those changes will be seen by users of the VAO.

IF YOU DONT UNDERSTAND THE DIFFERENCE BETWEEN VALUE AND REFERENCE TYPES IN C# AND I DIDNT ASK IF YOU DO:

 

PUT UP YOUR HAND NOW. 

IF YOU DONT UNDERSTAND THE DIFFERENCE BETWEEN VALUE AND REFERENCE TYPES IN C# AND I DIDNT ASK IF YOU DO:

 

PUT UP YOUR HAND NOW. 

// C#

class VBO
{
    /*
        Heap allocated data.
    */
    List<VertexData> data;
}

class VAO
{
    Dictionary<Location, VBO> boundBuffers;
}

/*
    NO THIS IS NOT HOW THE GPU WORKS, THERE ARE NO "OBJECTS" ON THERE
    BUT I WANT YOU TO UNDERSTAND THAT THE VAOS HOLD REFERENCES, NOT DATA.
*/

You dont have to know what a "location" is until you start programming your own shaders. Just know that these are where our shaders look for their data.

 

If you look at your homework, Dr. Tatzgern will most likely have noted to which locations the data shall be bound.

// position_vbo -> Buffer that holds the vertex positions.

// Hey there OpenGL, I'd like a Vertex Array Object pls.
GLuint vao;
glGenVertexArrays(1, &vao);

// I want to write into vao now.
glBindVertexArray(vao);            

// I want to write something into location 0
glEnableVertexAttribArray(0);

// I'd also like to use my buffer I just created.
glBindBuffer(BufferTarget.ArrayBuffer, position_vbo);

// Ok, I want to write something to location 0, which has 3 elements, each of whic
// is as large as a float and I dont want to normalize it.
// You can find a new element each time you make a step the size of a float.
// My data lies at the start of the buffer.
glVertexAttribPointer(0, 3, GLfloat, false, Vector3.SizeInBytes, 0);
            

mesh[0]

Vec3f

Vec3f

Vec3f

mesh[1]

mesh[2]

vao

0

1

...

Location 0 now points to our position buffer, but it does not store it

glBindVertexArray(vao);
 
GL.DrawArrays(PrimitiveType.Triangles, 0, my_buffer_size);

Now we can draw!

But hold on, where does the data go?

Thanks for listening!

CG Tut 1

By Philipp Welsch