Thursday, March 10, 2016

Cube Map Reflections - Part 2

Here is the second part of my cube map reflections post. This is going to be mostly images, and a few videos. Most of the work I've done since my previous post from a few days ago was just tweaking things and fixing minor bugs. Many of the issues were related to dynamic/moving reflective objects, which is one of the reasons I left the videos for this post. I also made some optimizations to the rendering, which is nice but still doesn't make scenes with multiple dynamic reflective objects render in realtime. On the other hand, reflections on static objects work just fine and cost almost no frame time. I can likely have over a hundred of these objects, assuming there is enough GPU memory, though I haven't tested a scene with more than 20 of them.

[For reference, each of the 6 cube map faces is up to 768x768 pixels of 8-bit RGB image data. That's 6*768*768*3 = 10MB of GPU data. My GPU has 2GB of memory, so I can have up to 200 reflective cube map objects if I use all of it. In reality, the rest of the scene needs around 200MB, so I can only have 180 objects max.]

To start off, here is the gold dragon, but this time in silver, with a few minor improvements.

Reflective silver dragon drawn in realtime.

I created a test scene with two rows of 8 reflective silver spheres. This was mostly for testing object inter-reflections, but also made for a good performance test. Dynamic rendering has to be turned on so that the nested reflections of other spheres are correctly created. It seems to converge after around 1-2 seconds, or 10-20 frames at 10 FPS. Once the images stabilize the rendering mode can be switched back to static to recover the realtime framerate for viewing the scene.

Gold dragon and 16 mirrored spheres reflecting each other in realtime (after several seconds of processing).

The reflections are close to physically accurate, and look like what you would get out of ray tracing. There are many levels of nested reflections of spheres within spheres. This works because each frame, the spheres are iterated over for creation of the reflection images of their 6 faces. In the first frame/pass, none of the spheres start reflective as their images haven't been created yet. They're just white spheres. On the second pass, the reflections are present, but they show the nearby spheres reflected as pure white. On the third pass, the reflection images pick up nearby spheres whose reflections contain other white spheres. This continues until the user stops the simulation, where each new frame increases the level of nested reflections by one. Think of it as simulating once bounce of light per frame. After a short period of time, the nested reflections become so small that the difference is not visible and the images no longer change.

[This reminds me of something. When I was a kid, I had a yellow bucket that I used for playing with sand and water. The bucket had a sheet of paper glued to it that had a picture of another yellow bucket. The bucket in the picture also had a (smaller) picture of a bucket on it. There were 4 or 5 levels of nested pictures of buckets that I could see. I always wondered how they created this (and why). This was back before the time of computer graphics, so it was probably done using cameras. Someone took a picture of a bucket, printed it, put it on another bucket, then took a picture of that ... repeated 4-5 times. Interesting.]

Here is another view showing how the gold dragon also reflects in the 16 spheres.

Closeup of gold dragon reflected in metal spheres.

Next, I changed the material properties of the spheres in the config text file, added a torus and a cube, and reloaded the scene. The new scene contains a combination of shapes, materials, and colors:

Various reflective materials and shapes. They even cast faint shadows on the floor.

The materials shown here are:
  • Gold dragon (center)
  • Silver torus (above) - not self reflective, sorry
  • Silver cube (on the floor in the back)
  • Marble spheres (left; front to back: black, blue, red, white)
  • Metal spheres (right; front to back: gold, brass, copper, silver)
I used real values for index of refraction and metal specular colors that I found online. Here is the scene from another angle.
Closeup of reflective silver torus and other shapes.

I added a reflective torus and some other objects to the Crytek Sponza atrium scene as well, to convince readers that this works on more than one scene. It should work on just about anything, including outdoor scenes full of grass and trees.

Reflective metal torus and other shapes in the Crytek Sponza atrium scene.

I made one of the metal cubes movable so that the player can push it around the scene. Every time the object moves, the reflection textures are regenerated so that they're always correct. I pushed the cube around the office building and into the basement to make sure the dynamic spotlights and shadows work correctly with it. Everything looks correct in this screenshot.

Movable cube casting a dynamic light shadow and reflecting dynamic lights and shadows

Someone asked me about the lack of player reflection in my previous post. Before I added reflections, there was no need to create a player model because it could never be seen. 3DWorld is a first person game engine (except for universe mode), so the camera is always at the position of the player's eyes and therefore can't see the player's own face and body. I decided that it would be nice to have a player model for reference, even if it was very simple. I used the smiley face enemy AI model because it was already available, but I'll likely replace it with something better in the future. The user can toggle drawing of the player model on and off. This is what it looks like:


Reflections of the player as a simple smiley face 3D model.

If you look closely, you'll see that the smiley is actually floating off the ground. This is because I have the camera height set higher than the AI enemy smiley height in this scene, as it feels more natural when walking around the building. That way, the player's eyes are actually above the desk and tables. The player model is a capsule shape rather than a sphere, but the visual model is spherical, so there is a mismatch.

Videos

I created some short videos showing off reflections with dynamic objects and view directions. The first video is of the camera moving around the reflective objects in realtime after the reflections have been computed.


The second video is of me pushing a reflective cube around the scene to show off the dynamic reflection updates. Note that the sun flare reflection wasn't working at this point, so the sun appears as a small yellow circle in the top of the cube.



Rendering Bugs

Here are some failed attempts at reflective sphere rendering. I'm throwing these in just for fun, because they looked silly and it took awhile for me to figure out what went wrong.

What do you suppose happened to this reflective sphere? Did someone wrap a leaf around it?

Reflection of a ... leaf?

This is what you get when enabling a specular map texture but not binding it to the correct texture unit. The specular map uses a leaf texture, which was the last texture bound to that slot from the previous tree rendering pass. Oops. This object doesn't even use or need a specular map.


What about this one? Surely this is how the reflection is supposed to look in a sphere, right?

Simulation of a micro black hole.
I had the wrong sign/direction for the reflection vector, and light was reflected into the center of the sphere rather than away from it. I encountered several variants of this bug while trying to make the shader code correct. Oops again.


Future Work

I wanted to add a video of me throwing a bouncy reflective ball, but I haven't gotten that working yet. The problem is that the ball uses a different type of physics enabled scene object than the other reflective items I've shown. Each ball is converted to a collision object (the type that can be made reflective) so that it participates in collision detection, but it's added and removed every frame. In fact, its collision model isn't valid at the point where the reflection images are created. A second problem is that physics objects aren't drawn with a shader that supports reflections. I'll have to come up with some solution to these issues later.

All of the images shown here have a high specular exponent and are very glossy. I haven't implemented low-gloss surfaces yet. The traditional way to do this is to filter the reflection texture down to a lower resolution, which blurs the colors together. The simplest way to do this is to use mipmaps on the GPU and select a mipmap level based on a material glossiness parameter. I would like to do this, but I haven't gotten mipmaps to work yet on my cube map render-to-texture flow. Maybe there is an online tutorial that can help me with this problem.

Another interesting effect to simulate would be refraction. Unfortunately, this seems difficult to get right without knowing the geometry of the material for computing the optical thickness effects, etc. I have it working somewhat for a sphere (most people wouldn't know it was wrong), and objects with smooth curved surfaces in general, but it doesn't work at all for cubes. I'll have to think about this some more.

Finally, I need to work on creating a better player model.

No comments:

Post a Comment