Two Wrongs

Reading Notes: An Intro to Modern OpenGL

Reading Notes: An Intro to Modern OpenGL

I didn’t think I was going to need to make these notes, but I think I do. I am reading an introductory tutorial to slightly more modern Opengl11 Joe Groff, An Intro to Modern Opengl, 2010. Web Access. Something else you may want to check out if this interests you is the Book of Shaders22 Patricio Gonzalez Vivo and Jen Lowe, The Book Of Shaders, 2015. Web Access.

FIX ALL THE FORMATTING BADNESS :(

Creating a Window with glut

STUFF

#include <GL/glew.h>
#include <GL/freeglut.h>
glutInit(&argc, argv);
glutInitDisplayMode(GLUT_RGB | GLUT_DOUBLE);
glutInitWindowSize(400, 300);
glutCreateWindow("Window Title");
glewInit();
glutDisplayFunc(&render);
glutIdleFunc(&update);
glutMainLoop();

Checking Rendering Time

STUFF (needed for smooth animations regardless of rendering speed)

  • glutGet(GLUT_ELAPSED_TIME) returns milliseconds passed.

Opengl Objects and Types

When talking about floating point values in Opengl, we use the names

Precision: Normal Double
Regular range GLfloat GLdouble
Normalised33 I.e. a number in the range \([0,1]\) is expected. GLclampf GLclampd

Other types which signal an expectation on limitations in the value are

  • GLboolean, expected to be either GL_TRUE or GL_FALSE,
  • GLenum, expected to be some GL_* constant, and
  • GLbitfield, expected to be the bitwise combination of GL_*_BIT masks.

There is also GLsizei, which indicates that the value is supposed to be the length of some memory buffer.

Furthermore, the integer types used by Opengl include

Length: 8 bits 16 bits 32 bits
Signed GLbyte GLshort GLint
Unsigned GLubyte GLushort GLuint

And, finally, we have GLchar *, indicating a null-terminated string.

Memory Management in Opengl

Opengl expects to manage some memory on its own. The following process is used to instruct it to allocate:

  1. glGenBuffers(1, &bufd), where bufd is a GLuint that will be used to refer to the buffer from here on out.
  2. glBindBuffer(target, bufd), where target tells Opengl what kind of data we’re about to copy, and takes the value of one of the following:
    • GL_ARRAY_BUFFER, for vertices, i.e. the corners of triangles – these should preferably be GLfloat, and
    • GL_ELEMENT_ARRAY_BUFFER, for vertex indices which are used to combine vertices into triangles – these should preferably be GLushort.
  3. glBufferData(target, bufsz, bufdata, bufaccess). which copies bufsz bytes from the bufdata pointer to the last bound buffer of type target (see above). bufaccess indicates the expected access pattern, and is one of

    - Never written Changes written Writes replace in full
    Read by gpu GL_STATIC_DRAW GL_DYNAMIC_DRAW GL_STREAM_DRAW
    Read by cpu GL_STATIC_READ GL_DYNAMIC_READ GL_STREAM_READ
    Read by both GL_STATIC_COPY GL_DYNAMIC_COPY GL_STREAM_COPY

    Specifying the wrong access pattern will not break your program, but it may result in poor performance.

Storing and Loading Textures

Storing and Loading Shaders

Two important shaders in Opengl:

  1. Vertex shaders: these transform coordinates in the world space into on-screen coordinates.
  2. Fragment shaders: these put colours onto the pixels that Opengl automatically creates from the triangles of the world space.

Shaders need to be compiled, linked together and loaded as a single program. Each shader is allocated and compiled by

  1. glCreateShader(type), which returns a GLuint referring to that shader,

and takes a type which is either

  • GL_VERTEX_SHADER, or
  • GL_FRAGMENT_SHADER.
  1. glShaderSource(shaderd, 1, &source, &length), which takes the GLuint from earlier and sets the glsl source for the shader.
  2. glCompileShader(shaderd), which compiles the shader.

After this has been done for all shaders, we can link them together into a single shader program:

  1. glCreateProgram(), returning a GLuint,
  2. glAttachShader(programd, vert_shaderd),
  3. glAttachShader(programd, frag_shaderd), and then
  4. glLinkProgram(programd).

Finally, we need to ask Opengl where it stores the inputs and outputs of the shaders, which we do with

  • glGetUniformLocation(program, "value_name"), which returns a GLint representing the location for that value.
  • glGetAttribLocation, as before.

The Opengl Shading Language, glsl

Shaders are written in glsl, which is kind of a C-like language for linear algebra. Shader programs are written to do simd44 Single instruction, multiple data (simd) means the same operations are performed in parallel on all inputs. processing on input data, and this processing happens on the gpu.

A glsl program consists of the following parts:

  1. A version indication, e.g., #version 110.
  2. Input declarations, e.g. attribute vec3 coords;.
  3. Output declarations, e.g. varying vec2 mirrorpos;.
  4. A main function, void main() {}.

Vertex Shaders

Vertex shaders have two types of input:

  • uniform, i.e. data common to all vertices in the frame, and
  • attribute, i.e. data that depends on which vertex is processed.

They have one type of output:

  • varying, i.e. vertex-specific data sent to the fragment shader.

The vertex shader has one predefined output value, which should not be declared by you:

  • varying vec4 gl_Position;, which represents vertex coordinates on the screen.

The gl_Position vector stores on-screen coordinates for vertices, and they are given by four dimensions \(x\), \(y\), \(z\) and \(w\), where all values are in the range \([-1, 1]\). The lower-left corner of the screen is \((x, y) = (-1, -1)\).

The \(z\) coordinate is used for depth testing when drawing – pixels with a higher \(z\) value get overwritten by those whose \(z\) is lower.

The \(w\) coordinate of the gl_Position vector serves the purpose of making perspective transformations mathematically easier.

Fragment Shaders

Two types of input:

  • uniform, i.e. data common to all pixels of the frame, and
  • varying, i.e. output from vertex shader, potentially unique to each vertex.

One predefined output:

  • gl_FragColor, a vec4 representing output colours.

Rendering

In the rendering function55 I.e. for each frame., we

  1. glClearColor(r, g, b, 𝛼).
  2. glClear(GL_COLOR_BUFFER_BIT).
  3. glUseProgram(programd).

Then we need to bind values, which are done differently depending on the type of them. The simplest case is

  • glUniform1f(locationd, value) which assigns one-dimensional floating point value to the uniform input represented by locationd.

If we’re writing to a vertex buffer, we must

  1. glBindBuffer(GL_ARRAY_BUFFER, bufd) tells Opengl where to read data from in the following two calls.
  2. glVertexAttribPointer(locationd, vertlen, type, normp, stride, offset)
    • locationd referring to the location GLint.
    • vertlen indicating how many elements of type are used for one vertex.
    • type is an enum, not a C type. E.g. GL_FLOAT.
    • normp can easily be GL_FALSE.
    • stride is how many bytes per vertex, i.e. sizeof (type)*vertlen.
    • offset is normally NULL.
  3. glEnableVertexAttribArray(locationd).

Then we tell Opengl how to connect the dots to make triangles from the vertices with

  1. glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, bufd).
  2. glDrawElements(mode, count, type, offset).
    • mode can be GL_TRIANGLES, GL_TRIANGLE_STRIP or GL_TRIANGLE_FAN.
    • type an enum as above.
    • offset commonly NULL, as above.

Once this has been done, Opengl has started rendering. It’s a good idea to disable the vertex array with glDisableVertexAttribArray(locationd).

Finally, to swap in the new rendered image once it is complete,

  • glutSwapBuffers().