# OpenGL Introduction
A brief overview of OpenGL can be found on [wikipedia](https://en.wikipedia.org/wiki/OpenGL), so this note skips such introduction entirely. Let's go directly into the OpenGL rendering pipeline and API.
---
## 1 OpenGL Rendering Pipeline
A detailed overview is available at https://open.gl/drawing.
![[Pasted image 20221217113345.png]]
```
Vertex Data -> Vertex Shader -> [...] -> Rasterize -> Fragment Shader -> Final Image
(programmable) ↑ (programmable)
(image Data to pixel operatios)
```
- **Vertex Shader**: Takes as input vertex data as issued by a drawing command. It is programmable and can be simple (e.g. *pass-through shader*) or complex (e.g. one that computes vertices' screen position / color / etc.). Note that the optional and none-programmable modules in the pipeline is committed, but they are owed a brief introduction:
- *Tessellation Control/Evaluation Shaders*: [Tesselate](https://en.wikipedia.org/wiki/Tessellation) is a fancy way of saying "tiling without overlapping". The tessellation shader breaks down input object into *patches* (*splines*) to be tessellated for a better rendered result.
- *Geometry Shader*: For additional processing of geometric primitives (e.g. adding new ones)
- **Rasterize** (i.e. ***scan-convert***): Converts geometric primitives (vertex, line) into corresponding pixel locations through linear interpolation. This part is non-programmable and the exact implementation is machine-dependent, i.e. different GPUs does rasterization differently.
- **Fragment Shader**: Color pixels appropriately (e.g. texture-mapping) to produce an image.
Note that the rendered image is sent to a ***frame buffer*** that's managed by GPU for feeding frames to the display device. Buffering is common throughout the OpenGL pipeline, which naturally motivates the following discussion on ***buffers***.
### 1.1 Buffers
***Frame Buffers***: The collection of color buffer, depth buffer, and stencil buffer.
- ***Color Buffer*** (front, back, left, right): Contain RGB /sRGB / alpha value for each pixel in the frame buffer.
- Front/back buffers are used for [double buffering](https://www2.cs.arizona.edu/classes/cs433/spring02/opengl/dblbuffer.html) (comes in handy for animation). Left/right buffers are used for rendering [stereo](https://derivative.ca/UserGuide/OpenGL_Stereo) frames where left and right frames need to be different.
- ***Depth Buffer (i.e. [[4_opengl_more#Z-Buffering|z-buffer]])***: Store depth value for each pixel for determining occlusion.
- ***Stencil Buffer***: Restrict drawing to certain part of the screen.
### 1.2 Windowing (or, lack thereof)
Windowing is excluded from OpenGL for portability. Luckily, GLUT or similar libs can be used to implement that. Callbacks can be used to implement mouse/keyboard interaction.
---
## 2 OpenGL Viewing Pipeline
There are two steps, repeatedly applied for each level of hierarchy in the [[1_transformations#Scene Graphs: Combining Transformations|scene graph]] :
1. **Object positioning**: using [[1_transformations#4.2 `gluLookAt`: mode view transformation matrix|model view transformation matrix]]
2. **View projection**: projection transformation matrix
Such "repeated application of matrices" is made efficient by using [***matrix stack***](http://what-when-how.com/opengl-programming-guide/manipulating-the-matrix-stacks-viewing-opengl-programming/), for the same reason why stacks are useful for [DFS](http://web.cs.unlv.edu/larmore/Courses/CSC477/bfsDfs.pdf). Old OpenGL use `GL_MODELVIEW_MATRIX, GL_PROJECTION_MATRIX` fro matrix stacks, while new openGL uses C++ STL templates to create stacks as needed:
```cpp
stack <mat4> modelview;
modelview.push(mat4(1.0));
```
There are also 3 important conventions to note:
1. **Camera** always at the origin, pointing in the -z direction
2. **Transformations** move objects relative to the camera
3. **Matrices** are ***column-major*** and therefore ***right-multiplied***. (Last transform at the top of stack is first actually applied)
4. **Field of view** needs to be in radian.
The following are code templates for initialization, viewing, and main function.
### 2.1 Initialization
```cpp
#include <GL/glut.h> //also <GL/glew.h>; <GLUT/glut.h> for Mac OS
#include <stdlib.h> //also stdio.h, assert.h, glm, others
int mouseoldx, mouseoldy ; // For mouse motion
GLfloat eyeloc = 2.0 ; // Where to look from; initially 0 -2, 2
glm::mat4 projection, modelview; // The mvp matrices themselves
void init (void) {
/* select clearing color */
glClearColor (0.0, 0.0, 0.0, 0.0);
/* initialize viewing values */
projection = glm::mat4(1.0f); // The identity matrix
// Think about this. Why is the up vector not normalized?
modelview = glm::loakAt(glm::vec3(0,-eyeloc,eyeloc),
glm::vec3(0,0,0), glm::vec3(0,1,1)) ;
// (To be cont’’d). Geometry and shader set up later ...
}
```
### 2.2 Window Interaction (Using GLUT)
The following [callback function](https://developer.mozilla.org/en-US/docs/Glossary/Callback_function) are used to handle evens such as keyboard, mouse, window open/init/resize.
- `glutDisplayFunc(display)`
- `glutReshapeFunc(reshape)`
- `glutKeyboardFunc(keyboard)`
- `glutMouseFunc(mouse)`
- `glutMotionFunc(mousedrag)`
#### 2.2.1 Keyboard Interaction
```cpp
/* Defines what to do when various keys are pressed */
void keyboard (unsigned char key, int x, int y){
switch (key){
case 27: // Escape to quit
exit(O);
break;
default:
break;
}
}
```
#### 2.2.2 Window Resizing
```cpp
/* Reshapes the window appropriately */
void reshape(int w, int h){
glViewport (0, 0, (GLsizei) w, (GLsizei) h);
projection = glm::perspective(
30.Of/180.Of * glm::pi<float>(), // fovy = 30° in rad, fovy takes in a radian angle
(GLfloat)w / (GLfloat)h, // aspect ratio
1.Of, // near
10.Of // far
);
/* To send the projection matrix to the shader */
glUniformMatrix4fv( // https://registry.khronos.org/OpenGL-Refpages/gl4/html/glUniform.xhtml
projectionPos, // position in the shader of the location of the projection matrix
1, // we're passing only 1 matrix
GL_FALSE, // we're not transposing the matrix
&projection[0][0] // location of the projection matrix
);
}
```
#### 2.2.3 Mouse Clicking & Dragging
```cpp
/** Mouse callback that:
- Sets the mosue state when left mouse button is clicked (i.e. state == GLUT_DOWN)
The mouse state is used by mouse drag to zoom in
- Reset gluLookAt when right mouse button is clicked
*/
void mouse(int button, int state, int x, int y) {
if (button == GLUT_LEFT_BUTTON){
if (state == GLUT_UP){} // Do nothing
else if (state == GLUT_D0WN){ // move wrt x, y
mouseoldx = x; mouseoldy = y;
}
/* Reset gluLookAt when right button is clicked*/
else if (button == GLUT_RIGHT_BUTTON && state == GLUT_D0WN){
eyeloc = 2.0 ;
modelview = glm::lookAt(
glm::vec3(0, -eyeloc, eyeloc), // looking from
glm::vec3(0,0,0), // looking at
glm::vec3(0,1,1) // camera up direction
);
// Send updated matrix to the shader
glUniformMatrix4fv(modelviewPos,1,GL_FALSE,Smodelview[0][0]);
glutPostRedisplay(); // Redraw scene
}
}
/* Zoom in/out at mouse moves (i.e. y-coord changes) */
void mousedrag(int x, int y){
int yloc = y-mouseoldy; // use y coord to zoom in/out
eyeloc += 0.005*yloc; // where do we look from
if (eyeloc<0) eyeloc = 0.0;
mouseoldy = y;
// Set eye location
modelview = glm::lookAt(glm::vec3(0, -eyeloc, eyeloc),
glm::vec3(0,0,0,), glm:vec3(0,1,1));
// Send updated matrix to shader
glUniformMatrix4fv(modelviewPos,1,GL_FALSE, &modelview[0][0]);
glutPostRedisplay();
}
```
### 2.3 Main Function Setup
```cpp
int main (int arg, char** argv){
glutInit (&argc, argv) ;
// Requests the type of buffers (Single, RGB).
// Think about what buffers you would need...
glutInitDisplayMode (GLUT SINGLE | GLUT RGB) ;
// Need to add GLUT_3_2_CORE_PROFILE for Apple/Mac OS glutInitWindowSize (500, 500) ;
glutInitWindowPosition (100, 100);
glutCreateWindow ("Simple Demo with Shaders");
// glewInit (); // GLEW related stuff for non-Apple systems
init (); // Always initialize first
/* callbacks and functions for various tasks.*/
glutDisplayFun(display);
glutReshapeFunc(reshape);
glutKeyboardFunc(keyboard);
glutMouseFunc(mouse);
glutMotionFunc(mousedrag);
glutMainLoop(); // Start the main code
deleteBuffers(); // Termination. Delete buffers generated in init()
return 0; // ANSI C requires main to return int.
}
```
---
## 3 OpenGL Primitives and Drawing
![[Pasted image 20221217111116.png]]
OpenGL includes 7 geometric primitives: points, lines, strips, loops, triangle, triangle strip, triangle fan. Older OpenGL also include quad/polygon, but they are removed because of redundancy (since quad/polygons can be easily decomposed into adjacent triangles). GLUT provides more complex primitives (sphere, teapot, cube, ...), but they must be converted into triangles. More precisely, the 7 geometric primitives are:
```cpp
GL_POINTS, GL_LINES, GL_LINE_STRIP, GL_LINE_LOOP,
GL_TRIANGLES, GL_TRIANGLE_STRIP, GL_TRIANGLE_FAN
```
The example code below shows the steps involved in drawing a square. Notice that since OpenGL doesn't have a primitive for a square, we'll have to **break it down into triangles** to draw it.
![[3_opengl_intro 2023-01-26 08.51.20.excalidraw.png|600]]
### 3.1 Floor Specification using vertex array
We need 3 arrays to specify the square shown above:
1. **Vertex array**: contains coordinates of the 4 vertices
2. **Color array**: contains colors of the 4 vertices
3. **Triangle index array**: contains, for each triangle, a list of vertex ids.
```cpp
/* Draw a square in the z=0.0 plane (this is shown in the figure above) */
const GLfloat floorverts[4][3] = { // Note that coords are relative to obj. center
{0.5, 0.5, 0.0}, // Top right
{-0.5, 0.5, 0.0}, // Top left
{-0.5, -0.5, 0.0}, // Bottom left
{0.5, -0.5, 0.0} // Bottom right
}
const GLfloat floorcol[4][3] = {
{1.0, 0.0, 0.0}, // red
{0.0, 1.0, 0.0}, // green
{0.0, 0.0, 1.0}, // blue
{1.0, 1.0, 1.0} // write
}
const GLubyte floorinds[1][6] = {{0,1,2 ,0,2,3}} // triangles
/* Draw another square in a separate (z=1.0) plane */
const GLfloat floorverts2[4][3]{
{0.5, 0.5, 1.0},
{-0.5, 0.5, 1.0},
{-0.5, -0.5, 1.0},
{0.5, -0.5, 1.0},
}
const GLfloat florcol[4][3] = {
{1.0, 0.0, 0.0}, // red
{1.0, 0.0, 0.0},
{1.0, 0.0, 0.0},
{1.0, 0.0, 0.0},
}
const GLubyte floorinds2[1][6] = {{0,1,2 ,0,2,3}} // triangles
```
### 3.2 Vertex array objects ([VAO](https://www.khronos.org/opengl/wiki/Vertex_Specification#Vertex_Array_Object)) & Vertex buffer objects ([VBO](https://www.khronos.org/opengl/wiki/Vertex_Specification#Vertex_Buffer_Object))
```cpp
/* Vertex Array Objects */
const int numobjects = 2; // number of objects for buffer (e.g. 2 for 2 triangles)
const int numperobj = 3; // indices per object (for vertices, colors)
GLuint VAOs[numobjects]; // A Vertex Array Object per object
GLuint buffers[numperobj*numobjects] ; // List of buffers geometric data
GLuint objects[numobjects]; // Array of objects
GLenum PrimType[numobjects]; // Primitive Type (triangles, strips)
GLsizei NumElems[numobjects];// Number of geometric elements
// Floor Geometry is specified with a vertex array
enum [Vertices, Colors, Elements}; // For arrays for object
enum [FLOOR, FLOOR2}; // For objects, for the floor
//----- In init below (creates buffer objects for later use) -----
glGenVertexArrays(numobjects, VAOs); // create unique identifiers for each VAO
glGenBuffers(numperobj*numobjects, buffers); // ... and for buffers
void deleteBuffers(){ // Like a destructor
glDeleteVertexArrays(numobjects, VAOs);
glDeleteBuffers(numperobj)
}
```
### 3.3 Initialize object by binding to OpenGL buffer
```cpp
void initobject (
GLuint object, // Integer id of object
GLfloat *vert, GLint sizevert, // Vertex array and its size
GLfloat *col, GLint sizecol, // Color array and its size
GLubyte *inds, GLint sizeind, // Triangle indices array and its size
GLenum type // Type of OpenGL geometry primitive
){
int offset = object * numperobj;
glBindVertexArray(VAOs[object]);
glBindBuffer(GL_ARRAY_BUFFER, buffers[Vertices+offset]) ;
glBufferData(GL_ARRAY_BUFFER, sizevert, vert,GL_STATIC_DRAW);
// Interact with shader: Use layout location 0 for the vertices
glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), 0);
glBindBuffer(GL_ARRAY_BUFFER, buffers[Colors+offset]) ;
glBufferData(GL_ARRAY_BUFFER, sizecol, col,GL_STATIC_DRAW);
// Use layout location 1 for the colors
glEnableVertexAttribArray(1);
glVertexAttribPointer(1, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(GLfloat), 0);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, buffers[Elements+offset]) ;
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeind, inds,GL_STATIC_DRAW);
PrimType[object] = type;
NumElems[object] = sizeind;
// Prevent further modification of this VAO by unbinding it
glBindVertexArray(0);
}
```
### 3.4 Drawing and displaying vertex object
After `initobject()`, we are setup to draw objects using `drawobject()` and `display()`
```cpp
void drawobject(GLuint object) {
glBindVertexArray(VAOs[object]);
glDrawElements(PrimType[object], NumElems[object], GL_UNSIGNED_BYTE, 0);
glBindVertexArray(0); // unbind
}
void display(void) {
glClear(GL_COLOR_BUFFER_BIT); // clear all pixels
drawobject(FLOOR);
drawobject(FLOOR2);
glFlush(); // start processing buffered OpenGL commands
```
### 3.5 Initialization for Drawing & Shading
```cpp
#include "shaders.h"
GLuint vertexshader, fragmentshader, shaderprogram ; // shaders
// Initialization in init() for Drawing
glGenVertexArrays(numobjects, VAOs);
glGenBuffers(numperobj*numobjects, buffers);
initobject(FLOOR, (GLfloat *) floorverts, sizeof(floorverts),
(GLfloat *) floored, sizeof(floorcol),
(GLubyte *) floorinds, sizeof(floorinds), GL_TRIANGLES);
initobject(FLOOR2,(GLfloat *) floorverts2, sizeof(floorverts2),
(GLfloat *) floorcol2, sizeof(floorcol2),
(GLubyte *) floorinds2, sizeof(floorinds2), GL_TRIANGLES);
// In init() for Shaders, discussed next
vertexshader = initshaders(GL_VERTEX_SHADER, "shaders/nop.vert") ;
fragmentshader = initshaders(GL_FRAGMENT_SHADER, "shaders/nop.frag") ;
shaderprogram = initprogram(vertexshader, fragmentshader) ;
```
The ***shader*** here deserves a section dedicated to it, which is coming up next.
---
## 4 Shader
Setting up a shader require the following steps:
1. Create shader (Vertex and Fragment)
2. Compile shader (shader source is just a sequence of strings, compiling shader is similar to compiling a normal program)
3. Attach shader to program
4. Link program
5. Use program
### 4.1 Step 1-2: Create, compile shader
```cpp
GLuint initshaders (
GLenum type, // vertex or fragment
const char *filename // read shader program from the specified file
) {
// OpenGL book, C2 Shader Fundamentals: Comliling shaders
GLuint shader = glCreateShader(type);
GLint compiled;
string str = textFileRead(filename);
const GLchar *cstr = str.c_str();
glShaderSource(shader, 1, Scstr, NULL);
glCompileShader(shader);
glGetShaderiv(shader, GL_COMPILE_STATUS, Scompiled); // Check compilation status
if (!compiled) {
shadererrors(shader);
throw 3;
}
cout << "Shader file " << filename << "successfully compiled." << endl;
return shader;
}
```
### 4.2 Step 3-4: Attach and link shader program
```cpp
GLuint initprogram (GLuint vertexshader, GLuint fragmentshader){
GLuint program = glCreateProgram() ;
GLint linked ;
glAttachShader(program, vertexshader) ;
glAttachShader(program, fragmentshader) ; glLinkProgram(program) ;
glGetProgramiv(program, GL_LINK_STATUS, &linked) ;
if (linked) glUseProgram(program) ;
else { programerrors(program) ; throw 4 ; }
cout << "Shader program successfully attached and linked." << endl;
return program ;
}
```
### 4.3 Sidenote: Basic (nop) shader programs
Shaders are written in GLSL (GL Shading Language), and they should locate at
- `"shaders/nop.vert.glsl"
- ``"shaders/nop.frag.glsl"`.
-
***Vertex Shader*** (basic, nop) (out values interpolated to fragment)
They are user-specified and are called by OpenGL in parallel to transform vertex (model-view, projection, etc.)
```cpp
# version 330 core //Do not modify version directive to anything older.
/* Shader inputes */
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 color;
/* Shader outputs, if any */
out vec3 Color;
/* Uniform variables*/
uniform mat4 modelview;
uniform mat4 projection;
void main() (
gl_Position = projection * modelview * vec4(position, 1.Of);
Color = color; // forward this color to the fragment shader
}
```
***Fragment Shader*** (basic, nop)
They are user-specified and are called by OpenGL in parallel to apply shading and lighting calculations.
```cpp
# version 330 core
in vec3 Color; // Fragment shader inputs are outputs of same name from vertex shader
/* Uniform variables (none) */
/* Shader utput */
out vec4 fragColor;
void main (void){
fragColor = vec4(Color, 1.Of);
}
```