# 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); } ```