# 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]]