We've been using 2D textures for a while now, but there are even more texture types we haven't explored yet and in this tutorial we'll discuss a texture type that is actually a combination of multiple textures mapped into a single texture: a cube map. A cubemap is basically a texture that contains 6 individual 2D textures that each form one side of a cube: a textured cube. You might be wondering what's the point of such a cube?
Why bother with combining 6 individual textures into a single entity instead of just using 6 individual textures? Imagine we have a 1x1x1 unit cube with the origin of a direction vector residing at its center.
Sampling a texture value from the cube map with an orange direction vector looks a bit like this:. If we imagine we have a cube shape that we attach such a cubemap to, the direction vector to sample the cubemap would be similar to the interpolated vertex position of the cube.
Tutorial 5 : A Textured Cube
This way we can sample the cubemap using the cube's actual position vectors as long as the cube is centered on the origin.
We can then retrieve the texture coordinates of all vertices as the vertex positions of the cube. The result is a texture coordinate that accesses the proper individual face texture of the cubemap.
A cubemap is a texture like any other texture so to create one we generate a texture and bind it to the proper texture target before we do any further texture operations.
Because a cubemap consists of 6 textures, one for each face, we have to call glTexImage2D six times with their parameters set to values similar to the previous tutorials. This time however, we have to set the texture target parameter to a specific face of the cubemap, basically telling OpenGL which side of the cubemap we're creating a texture for.
This means we have to call glTexImage2D once for each face of the cubemap.
Since we have 6 faces OpenGL provides us with 6 special texture targets specifically for targeting a face of the cubemap:. This generates a texture for each face of the currently bound cubemap.
Because a cubemap is a texture like any other texture we will also specify its wrapping and filtering methods:. Then before drawing the objects that will use the cubemap, we activate the corresponding texture unit and bind the cubemap before rendering, not much of a difference compared to normal 2D textures.
Within the fragment shader we also have to use a different sampler of the type samplerCube that we sample from using the texture function, but this time using a vec3 direction vector instead of a vec2.
An example of fragment shader using a cubemap looks like this:.
That is still great and all, but why bother? Well, it just so happens that there are quite a few interesting techniques that are a lot easier to implement with a cubemap.
One of those techniques is creating a skybox. A skybox is a large cube that encompasses the entire scene and contains 6 images of a surrounding environment, giving the player the illusion that the environment he's in is actually much larger than it actually is.
Some examples of skyboxes used in videogames are images of mountains, of clouds or of a starry night sky. An example of a skybox, using starry night sky images, can be seen in the following screenshot of the third elder scrolls game:. You probably guessed by now that skyboxes like this suit cubemaps perfectly: we have a cube that has 6 faces and needs to be textured per face. In the previous image they used several images of a night sky to give the illusion the player is in some large universe while he's actually inside a tiny little box.
There are usually enough resources online where you could find skyboxes like these. This website for example has plenty of skyboxes. These skybox images usually have the following pattern:.
If you would fold those 6 sides into a cube you'd get the completely textured cube that simulates a large landscape. Some resources provide the skyboxes in a format like this in which case you'd have to manually extract the 6 face images, but in most cases they're provided as 6 single texture images. This particular high-quality skybox is what we'll use for our scene and can be downloaded here. Since a skybox is by itself just a cubemap, loading a skybox isn't too different from what we've seen before.
To load the skybox we're going to use the following function that accepts a vector of 6 texture locations:. The function itself shouldn't be too surprising. It is basically all the cubemap code we've seen in the previous section, but combined in a single manageable function. Then before we call this function we'll load the appropriate texture paths in a vector in the order as specified by the cubemap enums:. We now loaded the skybox as a cubemap with cubemapTexture as its id.
We can now bind it to a cube to finally replace the lame clear color we've been using as the background all this time. You can get its vertex data here.
A cubemap used to texture a 3D cube can be sampled using the positions of the cube as the texture coordinates. When a cube is centered on the origin 0,0,0 each of its position vectors is also a direction vector from the origin. This direction vector is exactly what we need to get the corresponding texture value at that specific cube's position. For this reason we only need to supply position vectors and don't need texture coordinates.
To render the skybox we'll need a new set of shaders which aren't too complicated.
Because we only have one vertex attribute the vertex shader is quite simple:. Note that the interesting part of the vertex shader is that we set the incoming position vectors as the outcoming texture coordinates for the fragment shader. The fragment shader then takes these as input to sample a samplerCube :. The fragment shader is all relatively straightforward.
We take the vertex attribute's position vectors as the texture's direction vector and use those to sample the texture values from the cubemap. Rendering the skybox is easy now that we have a cubemap texture, we simply bind the cubemap texture and the skybox sampler is automatically filled with the skybox cubemap. To draw the skybox we're going to draw it as the first object in the scene and disable depth writing.
This way the skybox will always be drawn at the background of all the other objects.
Texture cube map opengl es book
If you run this you will get into difficulties though. We want the skybox to be centered around the player so that no matter how far the player moves, the skybox won't get any closer giving the impression the surrounding environment is extremely large. The current view matrix however transforms all the skybox's positions by rotating, scaling and translating them, so if the player moves, the cubemap moves as well! We want to remove the translation part of the view matrix so movement doesn't affect the skybox's position vectors.
Creating a cubemap
You might remember from the basic lighting tutorial that we could remove the translation section of transformation matrices by taking the upper-left 3x3 matrix of the 4x4 matrix, effectively removing the translation components. We can achieve this by simply converting the view matrix to a 3x3 matrix removing translation and converting it back to a 4x4 matrix:. This removes any translation, but keeps all rotation transformations so the user can still look around the scene. The result is a scene that instantly looks enormous due to our skybox.
If you'd fly around the basic container you immediately get a sense of scale which dramatically improves the realism of the scene. The result looks something like this:. Try experimenting with different skyboxes and see how they can have an enormous impact on the look and feel of your scene.
Loading a skybox
Right now we've rendered the skybox first before we rendered all the other objects in the scene. This works great, but isn't too efficient. If we render the skybox first we're running the fragment shader for each pixel on the screen even though only a small part of the skybox will eventually be visible; fragments that could have easily been discarded using early depth testing saving us valuable bandwidth.
So to give us a slight performance boost we're going to render the skybox last. This way, the depth buffer is completely filled with all the objects' depth values so we only have to render the skybox's fragments wherever the early depth test passes, greatly reducing the calls to the fragment shader.
The problem is that the skybox will most likely fail to render since it's only a 1x1x1 cube, failing most depth tests. Simply rendering it without depth testing is not a solution since the skybox will then overwrite all the other objects in the scene. We need to trick the depth buffer into believing that the skybox has the maximum depth value of 1. We also know from the depth testing tutorial that the z component of the resulting division is equal to that vertex's depth value.
Using this information we can set the z component of the output position equal to its w component which will result in a z component that is always equal to 1. The resulting normalized device coordinates will then always have a z value equal to 1. The skybox will as a result only be rendered wherever there are no objects visible only then it will pass the depth test, everything else is in front of the skybox.
The depth buffer will be filled with values of 1. You can find the more optimized version of the source code here.
We now have the entire surrounding environment mapped in a single texture object and we could use that information for more than just a skybox. Using a cubemap with an environment, we could give objects reflective or refractive properties. Techniques that use an environment cubemap like this are called environment mapping techniques and the two most popular ones are reflection and refraction.
Subscribe to RSS
Reflection is the property that an object or part of an object reflects its surrounding environment e. A mirror for example is a reflective object: it reflects its surroundings based on the viewer's angle. The basics of reflection are not that difficult. The following image shows how we can calculate a reflection vector and use that vector to sample from a cubemap:. We can calculate this reflection vector using GLSL's built-in reflect function.
The resulting effect is that the object seems to reflect the skybox. Since we already have a skybox setup in our scene, creating reflections isn't too difficult. We'll change the fragment shader used by the container to give the container reflective properties:.
Note that we have the fragment's interpolated Normal and Position variable again so we'll need to adjust the vertex shader as well.
We're using normal vectors so we'll want to transform them with a normal matrix again. The Position output vector is a world-space position vector. This Position output of the vertex shader is used to calculate the view direction vector in the fragment shader.
Because we're using normals you'll want to update the vertex data and update the attribute pointers as well. Also make sure to set the cameraPos uniform. Compiling and running your code gives you a container that acts like a perfect mirror.
Textures in OpenGL
The surrounding skybox is perfectly reflected on the container:.