# OpenGL (more details) After an introduction on OpenGL, we can now introduce more advanced topics. This note is a bit ad-hoc. It covers code examples, Z-buffering, animation, and texture mapping. Let's review the [[2_viewing#Viewing Pipeline|viewing pipeline]] before we start. To transform the homogeneous $[x,y,z,w]^T$ object coordinates into $[x,y]$ screen coordinates, the object needs to go through several transformations: 1. **Object**: **Model-view matrix** places geometry in world space. 2. **Perspective Projection**: **Projection matrix** transforms world space to camera coordinates 3. **Dehomogenization**: Transform camera coordinates to normalized device coordinates using dehomogenization. 4. **Viewport**: Transform device coordinates to window coordinates This note incrementally builds up a display program that shows a teapot and 4 pillars on a floor. The starter code is as follows: ```cpp #include “shaders.h” #include “geometry.h” int mouseoldx, mouseoldy ; // For mouse motion GLfloat eyeloc = 2.0 ; // Where to look from; initially 0 -2, 2 GLfloat teapotloc = -0.5 ; // Teapot location GLint animate = 0 ; // ** NEW ** whether to animate or not GLuint vertexshader, fragmentshader, shaderprogram ; // shaders const int DEMO = 0 ; // Turn on and off features ``` ## Double buffering Recall previously we briefly introduce [[3_opengl_intro#Buffers|double buffering]], here is a code example. ```cpp // Change in main glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGB | GLUT_DEPTH; // Change in display glutSwapBuffers(); // This implements double buffering glFlush(); ``` ## Geometry setup ![[4_opengl_more 2023-02-03 13.16.16.excalidraw]] ```cpp const int numobjects = 2; // Floors and cubes const int numperobj = 3; // for vertices and colors const int ncolors = 4; // 4 pillars having different colors GLUint VAOs[numobjects+ncolors], teapotVAO; // VAO for each primitive object GLuint buffers[numperobj*numobjects+ncolors], teapotbuffers[3] ; // List of buffers for geometric data GLuint objects[numobjects] ; // For each object in GLenum PrimType[numobjects] ; GLsizei NumElems[numobjects]; // For the geometry of the teapot std::vector <glm::vec3> teapotVertices; std::vector <glm::vec3> teapotNormals; std::vector <unsigned int> teapotIndices; // Matrix stack for modelview matrices std::vector <glm::mat4> modelviewStack; enum {Vertices, Colors, Elements} ; // VAOs for teapot enum {FLOOR, CUBE} ; // VAOs for floor and pillars // Define floor geometry and color const GLubyte floorinds[1][6] = { {0, 1, 2, 0, 2, 3} } ; const GLfloat floorverts[4][3] = { // vertices {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 GLfloat floorcol[4][3] = { // colors {1.0, 1.0, 1.0}, {1.0, 1.0, 1.0}, {1.0, 1.0, 1.0}, {1.0, 1.0, 1.0} }; const GLfloat floortex[4][2] = { // texture map coordinates {1.0, 1.0}, {0.0, 1.0}, {0.0, 0.0}, {1.0, 0.0} }; // Define pillar geomtry const GLfloat wd = 0.1; const GLfloat ht = 0.5; // width & height const GLfloat _cubecol[4][3] = { {1.0, 0.0, 0.0}, {0.0, 1.0, 0.0}, {0.0, 0.0, 1.0}, {1.0, 1.0, 0.0} }; const GLfloat cubeverts[8][3] = { {-wd, -wd, 0.0}, {-wd, wd, 0.0}, {wd, wd, 0.0}, {wd, -wd, 0.0}, {-wd, -wd, ht}, {wd, -wd, ht}, {wd, wd, ht}, {-wd, wd, ht} }; GLfloat cubecol[8][3] ; const GLubyte cubeinds[12][3] = { // There are 6*2=12 triangles {0, 1, 2}, {0, 2, 3}, // BOTTOM {4, 5, 6}, {4, 6, 7}, // TOP {0, 4, 7}, {0, 7, 1}, // LEFT {0, 3, 5}, {0, 5, 4}, // FRONT {3, 2, 6}, {3, 6, 5}, // RIGHT {1, 7, 6}, {1, 6, 2} // BACK }; void initobject( GLuint object, GLfloat * vert, GLint sizevert, GLfloat * col, GLint sizecol, GLubyte * inds, GLint sizeind, GLenum type ) { int offset = object * numperobj ; glBindVertexArray(VAOs[object]); glBindBuffer(GL_ARRAY_BUFFER, buffers[Vertices + offset]); // Use layout location 0 for the vertices glEnableVertexAttribArray(0); glBufferData(GL_ARRAY_BUFFER, sizevert, vert, GL_STATIC_DRAW); 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); } void initcubes(GLuint object, GLfloat * vert, GLint sizevert, GLubyte * inds, GLint sizeind, GLenum type) { for (int i = 0; i < ncolors; i++){ for (int j = 0; j < 8; j++){ for (int k = 0; k < 3; k++) cubecol[j][k] = _cubecol[i][k]; glBindVertexArray(VAOs[object + i]); int offset = object * numperobj; int base = numobjects * numperobj; glBindBuffer(GL_ARRAY_BUFFER, buffers[Vertices + offset]); glBufferData(GL_ARRAY_BUFFER, sizevert, vert, GL_STATIC_DRAW); // 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[base + i]); glBufferData(GL_ARRAY_BUFFER, sizeof(cubecol), cubecol, 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); } } } initobject(FLOOR, (GLfloat *) floorverts, sizeof(floorverts), (GLfloat *) floorcol, sizeof (floorcol), (GLubyte *) floorinds, sizeof (floorinds), GL_TRIANGLES); initcubes(CUBE, (GLfloat *)cubeverts, sizeof(cubeverts), (GLubyte *)cubeinds, sizeof(cubeinds), GL_TRIANGLES); loadteapot(); // Drawing with/without colors drawcolor(GLuint object, GLuint color) { glBindVertexArray(VAOs[object + color]); 0); glDrawElements(PrimType[object], NumElems[object], GL_UNSIGNED_BYTE, glBindVertexArray(0); } void drawobject(GLuint object) { glBindVertexArray(VAOs[object]); glDrawElements(PrimType[object], NumElems[object], GL_UNSIGNED_BYTE, glBindVertexArray(0); } void loadteapot() // See source code for details if interested ``` ## Matrix Stack ```cpp // Push matrix onto modelview stack void pushMatrix(glm::mat4 mat) { modelviewStack.push_back(glm::mat4(mat)); } // This function pops a matrix from the modelview stack void popMatrix(glm::mat4& mat) { if (modelviewStack.size()) { mat = glm::mat4(modelviewStack.back()); modelviewStack.pop_back(); } else {mat = glm::mat4(1.0f);} } ``` ## Z-Buffering Before we start explaining z-buffering, this should not be confused with z-mapping in deriving[[2_viewing#The Projection Matrix $P$|projection matrix]]. Now what's z-buffering? - For each pixel, store the nearest z-value relative to camera. - If new fragment is closer, then it replaces old z-value (thereby updating the corresponding fragment/fragment). This is called ***depth test***. You might wonder how to we get per-fragment/pixel depth value. Well, interpolation to the rescue - just treat the z-coordinate at each vertex as an attribute (like color), then interpolate. ### Depth test ```cpp // Add GLUT_DEPTH, GL_DEPTH_BUFFER_BIT to the main function glutInitDisplayMode (GLUT_SINGLE | GLUT_RGB | GLUT_DEPTH); glClear (GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // In init glEnable(GL_DEPTH_TEST); glDepthFunc(GL_LESS); ``` ### Fragment vs. Pixel Previously we said fragment is like a generalized notion of pixel. Lets take a closer look at how they differ. It's useful to review [[3_opengl_intro#OpenGL Rendering Pipeline|OpenGL rendering pipeline]] before we start: - **Fragment**: The corresponding color emitted by the scene geometry for a given pixel. Fragment and pixel has a 1-to-1 correspondence. This is the output of rasterizer. - **Pixel**: A component of hardware display, i.e. what finally gets displayed. This is the output of fragment shader, which intercepts fragments and perform e.g. [antialiasing](https://learnopengl.com/Advanced-OpenGL/Anti-Aliasing) and other post-processing then yield the final pixels to display --- ## Animation Animation behavior can be defined by how the scene respond to ***callbacks*** as a state machine. - Pass to an idle function the callback handle, which defines what to do when nothing is happening. ### Keyboard callback example ```cpp GLint animate = 0 ; // ** NEW ** whether to animate or not // ** NEW ** in this assignment, is an animation of a teapot // Hitting p will pause this animation; see keyboard callback /* Update the teapot's state, thereby changing its appreance as rendered by the display function */ void animation(void) { teapotloc = teapotloc + 0.005 ; if (teapotloc > 0.5) teapotloc = -0.5 ; glutPostRedisplay(); } void keyboard (unsigned char key, int x, int y){ switch (key) { case 27: // Escape to quit exit(0); break; case 'p': // ** NEW ** to pause/restart animation animate = !animate ; if (animate) glutIdleFunc(animation); else glutIdleFunc(NULL); break; case 't': // ** NEW ** to turn on/off texturing ; texturing = !texturing ; glutPostRedisplay(); break; case 's': // ** NEW ** to turn on/off shading (always smooth) ; lighting = !lighting ; glutPostRedisplay() ; break; default: break; } } // ** NEW ** Put a teapot in the middle that animates pushMatrix(modelview); modelview = modelview * glm::translate(identity, glm::vec3(teapotloc, 0.0, 0.0)); // The following two transforms set up and center the teapot // Transforms right-multiply the modelview matrix (top of the stack) modelview = modelview * glm::translate(identity, glm::vec3(0.0,0.0, 0.1)); modelview = modelview * glm::rotate(identity, glm::pi<float>()/2.0f, glm::vec3(1.0,0.0, 0.0)); float size = 0.235f; // Teapot size modelview = modelview * glm::scale(identity, glm::vec3(size, size, size)); glUniformMatrix4fv(modelviewPos, 1, GL_FALSE, &(modelview)[0][0]); drawteapot(); popMatrix(modelview); void drawteapot() { // drawteapot() function in geometry.h glBindVertexArray(teapotVAO); glDrawElements(GL_TRIANGLES, teapotIndices.size(), GL_UNSIGNED_INT, 0); glBindVertexArray(0); } // In mytest3.cpp GLubyte woodtexture[256][256][3] ; // texture (from grsites.com) GLuint texNames[1] ; // texture buffer GLuint istex ; // blend parameter for texturing GLuint islight ; // for lighting GLint texturing = 1 ; // to turn on/off texturing GLint lighting = 1 ; // to turn on/off lighting // In Display glUniform1i(islight,0) ; // Turn off lighting (except on teapot, later) glUniform1i(istex,texturing) ; drawtexture(FLOOR,texNames[0]) ; // Texturing floor // drawobject(FLOOR) ; glUniform1i(istex,0) ; // Other items aren't textured ``` ## Texture Mapping (Briefly) ![[Pasted image 20221221164845.png|600]] Implementing texture mapping roughly consists of 3 steps: 1. Each vertex is assigned (through mapping $\phi$ in the figure) a $[s,t]$ coordinate on the texture map. 2. Vertex shader looks up the [s,t] location on texture map, then rasterizer interpolate the value using Gouraud shading to define per-fragment color. 3. Fragment shader now has access to per-fragment color. ### Code Example ```cpp // In mytest3.cpp GLubyte woodtexture[256][256][3] ; // texture (from grsites.com) GLuint texNames[1] ; // texture buffer GLuint istex ; // blend parameter for texturing GLuint islight ; // for lighting GLint texturing = 1 ; // to turn on/off texturing GLint lighting = 1 ; // to turn on/off lighting // In Display glUniform1i(islight,0) ; // Turn off lighting (except on teapot, later) glUniform1i(istex,texturing) ; drawtexture(FLOOR,texNames[0]) ; // Texturing floor // drawobject(FLOOR) ; glUniform1i(istex,0) ; // Other items aren't textured // Toggles for keyboard case 't': // ** NEW ** to turn on/off texturing ; texturing = !texturing ; glutPostRedisplay() ; break ; case 's': // ** NEW ** to turn on/off shading (always smooth) ; lighting = !lighting ; glutPostRedisplay() ; break ; /* Setting up texture */ inittexture("wood.ppm", shaderprogram) ; // in init() // Very basic code to read a ppm file // And then set up buffers for texture coordinates void inittexture (const char * filename, GLuint program) { int i,j,k ; FILE * fp ; assert(fp = fopen(filename,"rb")) ; fscanf(fp,"%*s %*d %*d %*d%*c") ; for (i = 0 ; i < 256 ; i++) for (j = 0 ; j < 256 ; j++) for (k = 0 ; k < 3 ; k++) fscanf(fp,"%c",&(woodtexture[i][j][k])) ; fclose(fp) ; // Set up Texture Coordinates glGenTextures(1, texNames) ; glBindVertexArray(VAOs[FLOOR]); glBindBuffer(GL_ARRAY_BUFFER, buffers[numobjects*numperobj+ncolors]) ; glBufferData(GL_ARRAY_BUFFER, sizeof (floortex), floortex,GL_STATIC_DRAW); // Use layout location 2 for texcoords glEnableVertexAttribArray(2); glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), 0); glActiveTexture(GL_TEXTURE0) ; glEnable(GL_TEXTURE_2D) ; glBindTexture (GL_TEXTURE_2D, texNames[0]) ; /* Specifying the texure image https://online.ucsd.edu/assets/courseware/v1/ed5e927c60a42ab8d196926d2aec4c14/asset-v1:CSE+167+1T2022+type@asset+block/ucsdopengl3.pdf target is GL_TEXTURE_2D level is (almost always) 0 components = 3 or 4 (RGB/RGBA) width/height MUST be a power of 2 border = 0 (usually) format = GL_RGB or GL_RGBA (usually) type = GL_UNSIGNED_BYTE, GL_FLOAT, etc... glTexImage2D(target, level, components, width height, border, format, type, data) */ // Testure image and bind to shader glTexImage2D(GL_TEXTURE_2D,0,GL_RGB, 256, 256, 0, GL_RGB, GL_UNSIGNED_BYTE, woodtexture); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT); // Define a sampler. See page 709 in red book, 7th ed. GLint texsampler ; texsampler = glGetUniformLocation(program, "tex"); glUniform1i(texsampler,0) ; // Could also be GL_TEXTURE0 istex = glGetUniformLocation(program,"istex"); } // Drawing with texture, similar to darobjects void drawtexture(GLuint object, GLuint texture) { glBindTexture(GL_TEXTURE_2D, texture); glBindVertexArray(VAOs[object]); glBindVertexArray(VAOs[object]); glDrawElements(PrimType[object], NumElems[object], GL_UNSIGNED_BYTE, 0); glBindVertexArray(0); } // Final steps for drawing layout (location = 2) in vec2 texCoords; out vec2 texcoord; // similar definitions for positions and normals uniform int istex; void main() { gl_Position = projection * modelview * vec4(position, 1.0f); mynormal = mat3(transpose(inverse(modelview))) * normal ; myvertex = modelview * vec4(position, 1.0f) ; texcoord = vec2 (0.0, 0.0); // Default value just to prevent errors if (istex != 0){ texcoord = texCoords;} } /* Fragment shader */ uniform sampler2D tex ; uniform int istex ; void main (void) { if (istex > 0) fragColor = texture(tex, texcoord); } ``` ### Displacement, Illumination, Environment Mapping Displacement maps can be provided as a texture: ![[Pasted image 20221221165933.png|200]]