Basic Cubemaps


Cubemaps

 Topic of this short blog I will talk about cubemap basics and an OpenGL program that renders a scene with cube mapped background and mirror objects which reflects both the environment and the other objects in the scene.

 

Tools and Resources

To render the scene I will be talking about I have written a program in C++ using OpenGL graphics API and glfw, glew, glm and stbimage libraries. I also want to thank my professor Ahmet Oğuz Akyüz for letting me share his lecture slides (CENG 469 – Computer Graphics 2) which I've used for referencing theoretical parts of this little project.


What is a Cubemap?

In our rendered scenes sometimes our objects may not fill the whole window and the remaining spaces can remain black or some other color which is not visually pleasing. In orders to solve that we can render a background texture that is infinitely far away from camera so that it would not overwrite any other objects pixels and it can fill the remaining unpleasant black pixels. 

Ideally this environment would be sphere so that all of the points would be equally far away from the camera but unlike ray tracing we usually do not have sphere as a primitive and rendering a sphere sampled triangles for background would be extremely costly in each frame. Fortunately using a cube with an appropriate texture also creates pretty cool results so we use a cube for our environment and wrap a texture inside it.

The texture should have a geometry of an unfolded cube like below to fit in a cube obviously.

 

There are two ways to generate this cubemap first one is using a special hdr camera and taking a 360 degree photograph then using geometry to calculate this cubemap or in an virtual environment rendering each of the six faces using an already existing environment. I will not talk about the first method because I do not have an hdr camera, instead I will use one of the popular cubemaps out there. As for the second method I will be briefly mentioning it while rendering mirror objects.

 

How to render a cubemap environment?
Just like 2D textures we can actually map texture shaped like an unfolded cube into a cube but this time instead of using 2 texture coordinates we will need three.



Using these textures we can easily compute which color should be our pixel. To implement this I have used OpenGL's built-in cubemap support and just rendered a small cube with around origin. Which gave an output as below

 


 

One needs to careful when writing a shader for this cubemap. It is fairly simple but there is an important point which is where the camera is this cube still should be centered around origin in camera coordinates but still should be effected by cameras pitch and yaw movements.  In order to do I have calculated the gl_position in shaders with the following line.

gl_Position = projectionMatrix * vec4((viewingMatrix * vec4(inVertex, 0.f)).xyz, 1.0f);


as one can see before applying the viewing matrix I have set w to 0 which made our coorinates indifferent to translation and before applying projection transformation I have set it back to 1 so that projection would work properly. Before applying this trick I have obtained I few funny images like below.




Moral of the story: be careful while writing your transformations.


Adding Other Objects

After this we can freely add other objects to our scene.



One important thing here is that since this cube is going to be our environment so it should not occlude other objects. In order to do that one might think that "Then I am going to place this cube really really far away like billions of units away". Ok that might work but this is clearly not a good solution because it still doesn't solve the general problem. One odd scene may not work with an implementation like this. Instead I have used another solution. 

After rendering an actually really smal cube, we can fill the depth buffer with 1's which is the largest value possible and after that any object we draw would overwrite the environment cubemap.



Adding Mirrors

Now that we have rendered our environment with objects, it is time to make it a little bit fancier. We are going to add a mirror object. In order to do that we are going to create a cubemap on the run. Remember that I said we can actually render the cubemap ourselves. Doing that is actually fairly simple. We just need to place a camera where the reflecting object is and take a render in 6 different directions each facing toward another face of the cube with 90 degree fov and 1 aspect ratio (so that when they come together they can form a cube). This will create a cube map that is generated form our mirror objects perspective which contains other objects in the scene.



To implement this in OpenGL I have again used cubemap textures but this time I have first bound them as frame buffers and did 6 off-screen renders and after my textures was complete I was ready to render the mirror object. All I needed to do was for each fragment (point in mirror object) calculate the incident ray using camera position and fragment position then using that I needed to calculate the reflecting ray so that I could find which cubemap coordinate would be shown to screen on the mirror object.

After a few lines of code my program was complete.

 



One Final note

I also want to mention 2 other tools that helped me a lot in this process. First one is gdb (GNU debugger) which was great for debugging the CPU side of my program but when it comes to GPU side which is a little powerless. In order to solve this problem I have used
renderdoc which is a great tool for debugging GPU API's like D3D, OpenGL or Vulkan. Unfortunately it had no support for line by line debugging in openGL shaders but still it allowed me to inspect all the buffers, shader inputs, shader outputs and program state. For instance after completing off-screen renders I wasn't exactly sure how to check if my off-screen rendering code was correct. So I used renderdoc's buffer inspecting feature to be sure and as can be seen below it made me sure that it was indeed working flawlessly.

Below you can see the faces of the cubemap texture being inspected in renderdoc after offscreen rendering is complete.







So if you are not using debuggers, check them out they are really usefull for both CPU and GPU programming.

Thank you for reading.






Comments