Discussion5S16

From Immersive Visualization Lab Wiki
Jump to: navigation, search

Contents

Overview

Week 5 Discussion recap(04/25/16)

We had midterm review, and didn't actually go over the content below.

Crash Course on Textures in OpenGL

In terms of pure data type, OpenGL textures are actually quite simple to understand. They are a 2d array of pixel values(usually RGB, with each color being a byte) stored in the GPU's memory. The keyword that complicates things is that it's on the GPU's memory. Remember back to how VBOs, EBOs were complicated to handle because they lived in the GPU's memory? Well the same principle applies here. We need to use our OpenGL commands to manipulate those textures. But fear not! These commands will actually look very similar to the VBO commands.

Texture Generator

First up is the glGenTextures function. This works very much like the glGenBuffers function that was used in our buffer objects. This function needs to be called so we can generate a texture, or in other words tell the GPU to get ready because we're about to send a texture over.

GLuint textureID;
glGenTextures(1, &textureID);

Now the texture's ID—as the GPU understands it—is stored in our variable textureID. This textureID will be used to refer to our texture from on. (Hint: Better store it somewhere it will last!)

I Got a Binder Full of Textures

Next we have to effectively turn on the texture. We do this with the glBindTexture call. This is used for binding, as well as unbinding our texture to do operations on it.

glBindTexture(GL_TEXTURE_2D, textureID);
/* Do some texture operations here ... */
glBindTexture(GL_TEXTURE_2D, 0);

The reason we need the textureID is obvious, but what is that GL_TEXTURE_2D doing there? Well, OpenGL has multiple types of textures it supports; the main texture we're interested here is a 2D texture, or in OpenGL enum: GL_TEXTURE_2D. The final glBindTexture code with the id set to 0 unbinds the texture, making sure our future texture commands can start at a clean state. Be responsible and keep things clean!

Load'em Up!

So what might we do in that code block in between? Well that depends on your goal: you might load the texture, you might draw your object, or you might configure the settings of your texture(clamp mode, interpolation mode, etc.). Let's see what an example of loading the texture might look like. For now, let's imagine you have a file myTexture.ppm. Further, we're given a loadPPM function that can read a PPM and return a 2D array of RGB values for us. Let's take a look at creating this 2D texture with the glTexImage2D function:

unsigned char * image = loadPPM("myTexture.ppm", width, height);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);

Simple right? No? :( Well let's go over each of the arguments into glTexImage2D.

  • GL_TEXTURE_2D
    Our target texture is a 2D texture and we're doing a 2D texture operation to it(giving it a flat image).
  • The first 0
    This is the mipmap level we're sending data to. Since we're not interested in mipmapping here, we simply set this image as mip map level 0.
  • The first GL_RGB
    This is the internal representation of the texture once it's in our GPU's memory. Unless you're interested in having RGBA or representing depth, leave it as is.
  • width
    The width of the texture will be written to this variable
  • height
    Likewise.
  • The second 0
    This parameter exists to be 0. Seriously. Don't ask me why. Consult the documentation if you're skeptical.
  • The second GL_RGB
    This is the representation of our texture as we're giving it to OpenGL. The loadPPM function we provide makes the image a GL_RGB so best to comply with that.
  • GL_UNSIGNED_BYTE
    This is the type of the individual values in our array. We're using unsigned chars in loadPPM so we assign this to GL_UNSIGNED_BYTE
  • image
    The actual memory location of where our pixel data is stored.

All Put Together

GLuint textureID;
glGenTextures(1, &textureID);

glBindTexture(GL_TEXTURE_2D, textureID);
unsigned char * image = loadPPM("myTexture.ppm", width, height);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);

glBindTexture(GL_TEXTURE_2D, 0);

Well, not so bad right? We only have to do this once, and as long as we remember our textureID, we can use this texture by calling glBindTexture again before we draw.

So now let's move on to using these textures!

Skybox

The skybox is literally what it sounds like, a box surrounding the sky. This is very useful for making your world and sky look enormous without actually creating that much geometry for it. Some interesting properties of the skybox are:

  • It has 6 sides
    Duh! Well, this means we need 6 textures, when we've just barely learned how to do one! :O
  • It will need it's own GLSL shaders, so we get to juggle multiple GLSL shaders.
  • It will need to be scaled quite large. Maybe uniformly by 500.

So let's look at what this might look like.

Skybox Architecture

The skybox has enough unique features that we should probably encapsulate it in its own class. What do we need? Well we first need our geometry right? Hmm... I wonder where we've ever drawn a box-like object?

Leading questions aside, the skybox can have exactly the same vertices and faces data as the Cube.cpp, and create VAO, VBO, and EBO objects based on them. Now the issue is that those values are too small to simulate a sky that is very very far away. A matrix that scales this cube will be very useful. Hmm... I truly wonder where which variable we've used to ever scaled objects uniformly?

Well, that takes care of geometry. What else do we need? The 6 textures of course! We'll address this concern in another section, so for now assume we have the textures loaded.

We have the geometry and textures, now we can draw our skybox. What does that bring to mind? Well a draw method right? The skybox can be drawn much like how our cube or OBJObjects were drawn!

There's one caveat though right? We need a separate GLSL shader for our texture. The GLSL shaders are actually much simpler than Project 2 and we can simply take and modify the GLSL shaders from Learn OpenGL's Displaying a Skybox section.

How might we use a different shaderProgram only for the skybox's draw method?

Leave it to OpenGL

We still have to address our textures right? Because cube maps are actually very commonly used, OpenGL provides us a useful texture type GL_TEXTURE_CUBE_MAP that can contain 6 textures all by itself. This saves us from having to manage 6 different 2D textures manually, and also means we only need to keep track of one textureID. When we bind a GL_TEXTURE_CUBE_MAP, we get 6 additional types that we can access that we can call glTexImage2D on: GL_TEXTURE_CUBE_MAP_POSITIVE_X(right), GL_TEXTURE_CUBE_MAP_NEGATIVE_X(left), GL_TEXTURE_CUBE_MAP_POSITIVE_Y(up), GL_TEXTURE_CUBE_MAP_NEGATIVE_Y(down), GL_TEXTURE_CUBE_MAP_POSITIVE_Z(back), GL_TEXTURE_CUBE_MAP_NEGATIVE_Z(front).

So our loading call from Load'em up might expand into something like this:

unsigned char * image;
image = loadPPM("left.ppm", width, height);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
image = loadPPM("right.ppm", width, height);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
image = loadPPM("up.ppm", width, height);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
image = loadPPM("down.ppm", width, height);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
image = loadPPM("back.ppm", width, height);
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
image = loadPPM("front.ppm", width, height);
glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image);

Make sure you bind your texture as GL_TEXTURE_CUBE_MAP type as well as when configuring your texture with glTextureParameteri (as per the skybox section in project 3).