If you have ever played any polished game where there is specular reflections of the game world, it was probably implemented with environment maps. An environment map can be implemented as a 6 textures mapped to the inner surface of a cube at infinity distance centered on the object being rendered.
Cube maps are handy for creating things like image based lighting and reflection. Each face of the cube map covers a 90 degree field of view both vertically and horizontally.
Loading in the cub map faces.
After setting up a Vertex array object and vertex buffer object to render a cube. You will need to load in 6 textures that will make up a cube map. I used the SOIL image library to load in my 6 images that make up the skybox cube map
GLuint loadCubemap(std::vector<const GLchar*> faces) { GLuint textureID; glGenTextures(1, &textureID); int width, height; unsigned char* image; glBindTexture(GL_TEXTURE_CUBE_MAP, textureID); for (int i = 0; i < faces.size(); i++) { image = SOIL_load_image(faces[i], &width, &height, 0, SOIL_LOAD_RGB); glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, image); SOIL_free_image_data(image); } glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE); glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE); glBindTexture(GL_TEXTURE_CUBE_MAP, 0); return textureID; }
Inside the loop iterating you when specifying the target for glTexImage2D you can see that GL_TEXTURE_CUBE_POSITIVE_X is being incremented by i, since OpenGL defines it’s target as hex numbers like:
#define GL_TEXTURE_CUBE_MAP_POSITIVE_X 0x8515
#define GL_TEXTURE_CUBE_MAP_NEGATIVE_X 0x8516
#define GL_TEXTURE_CUBE_MAP_POSITIVE_Y 0x8517
#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Y 0x8518
#define GL_TEXTURE_CUBE_MAP_POSITIVE_Z 0x8519
#define GL_TEXTURE_CUBE_MAP_NEGATIVE_Z 0x851A
You can simply increment it by the iterator counter which will get the next cube map face.
Shaders for the sky box.
We can calculate a reflection vector to sample the cube map when the camera (viewer) looks at the model surface from an angle. A reflection vector is calculated as below:
GLSL has a built in function for reflect a vector, you can view it here: Link
To read the environment map texel that will correspond to the point on the surface of the object we take the incident vector from the camera to the point on the surface of the object and it reflect it around the normal of that point. The value of the texture coordinate is found when the reflection vector intersect the cube map. This texture coordinate can be used to access the individall face texture of the cubemap.
Fragment Shader
#version 330 core in vec3 Normal; in vec3 Position; out vec4 color; uniform vec3 cameraPos; uniform samplerCube skybox; void main() { vec3 I = normalize(Position - cameraPos); vec3 R = reflect(I, normalize(Normal)); color = texture(skybox, R); }
Vertex Shader
#version 330 core layout (location = 0) in vec3 position; layout (location = 1) in vec3 normal; out vec3 Normal; out vec3 Position; uniform mat4 model; uniform mat4 view; uniform mat4 projection; void main() { gl_Position = projection * view * model * vec4(position, 1.0f); Normal = mat3(transpose(inverse(model))) * normal; Position = vec3(model * vec4(position, 1.0f)); }
I have been working on learning a lot about OpenGL lately and this is one thing i have learned so far. I plan of making a tech demo like i did for my physics engine. With lighting, shadows, model loading and other goodies.
Dynamic environment maps
I haven’t done Dynamic environment cube maps yet but i have mess around with frame buffers enough that when you could render the scene 6 times from the perspective of the Utah tea pot, in this case, and make sure the filed of view is 90 degrees to create a cube map.