Thursday, December 10, 2015

Reflective Surfaces

This past week I decided to go back and work on rain effects again. The ground doesn't look "wet enough" - it needs to be more reflective. So I reused the plane reflection technique from tiled terrain water to get a reflective wet surface effect in ground mode.

I specify a reflection cube in the config file to enable reflective surfaces in the scene. Any objects with a flat top surface (currently cubes and vertical cylinders) that intersects this cube are treated as reflective. The cube is very narrow in Z height so that all of the objects have their top surfaces close to a single Z-value and can use the same reflection plane. For the office building scene, this includes the first floor walkable area inside and outside of the building. This covers most of the surfaces that get wet in the rain.

The general idea is to draw the scene twice in different modes. In the first pass, the reflection pass, the scene is drawn from the eye position mirrored about the reflection plane, which puts the eye/camera below the reflective surface. This image is stored in a texture at half screen resolution to reduce runtime and memory usage and improve framerate. Then the scene is rendered as usual. The reflective surfaces are drawn using a special shader that looks up the reflection texture and computes a Fresnel reflection term for the material.

I'm using the normal maps of the surfaces for the reflection normal, which effects the reflectivity through the Fresnel equations. Wet surfaces with accumulated water have a smoother, planar water surface (ignoring ripples), so their normal is close to +Z. I blend between the normal map normal and +Z depending on the water depth, which produces an effect of water filling the cracks between the concrete blocks and also a thin layer of water on top of the concrete. The idea was taken from this blog post. Take a look at these screenshots:

Reflections on the wet concrete block floor. Water has accumulated in the cracks between the blocks.

Reflections on wet concrete from another angle.

Reflections also work on smaller objects, even dynamic objects that can be moved by the player, as long as their top surfaces are near the Z reflection plane. Here I have pushed a glass block down the stairs so that it's top surface is at the right height for reflections. The glowing, light emitting spheres are there to test dynamic object reflections and dynamic lighting on reflected objects.

Reflections of the scene with glowing spheres on the top of a glass block.

Reflections are not only for wet surfaces. Shiny indoor surfaces such as tile and marble can also reflect the scene. This greatly improves the realism of these materials, at the cost of increased GPU time and lower frame rates. This tile floor is reflective with a normal map that results in the Fresnel reflection term varying over different parts of the tile, changing the reflectivity per-pixel. The raised marble platform is a smooth reflective surface (like a mirror).

Reflections of the scene with glowing spheres on shiny tile and marble floor.

I ran into a few problems using this approach. First of all, there are several objects at slightly different heights that all use the same reflection texture with the same mirrored Z-value. In the office building scene, there is a raised marble platform slightly above the tile floor, which itself is slightly above the outside concrete. All three surfaces are at different Z values and reflective/visible at the same time when rain is enabled. I use the average Z value from the top surface heights of all visible reflective surfaces. When there is only a single reflective surface it works great, but it's not always so simple. This can produce a small amount of light leaking from under an object that is occasionally visible, but difficult to remove. Here is an example image where the very bottom of the windows are visible in the reflection on the marble, just under the desk. It makes the desk look like it's floating a few inches in the air, because the reflection plane is off by a few inches from the surface. The only real fix for this is to render the scene at multiple Z values in multiple passes, which is slow.

Light leaking under the desk due to top marble surface slightly above the reflection Z plane.

Another bug I ran into is related to objects below the reflection plane that are incorrectly rendered into the reflection image. Think of it like this: The view vector and camera frustum are reflected about the top surface of the floor. This is effectively like moving the camera down by twice the distance it is above the floor, so that the reflection camera is under the ground. What do you see when under the ground? Tree roots! Take a look at this screenshot:

Incorrect reflection of roots under the ground resulting from missing Z clip of geometry.
The problem here is that the tree roots aren't actually visible because they're occluded by the reflective top surface itself, which isn't drawn in the reflection pass. This fix is to clip/discard all object pixels below the reflection Z value in the fragment shader when rendering the reflected scene. This contributes a bit more light leaking to reflective surfaces that are misaligned slightly with the reflection plane, but otherwise fixes the problem.

I found a third bug, where the bottom of the columns that support the stairs appeared to be misaligned with the top of the floor. Here is what it looks like. Notice the apparent gap between the columns and there floor where you can see the reflection of the bottom of the columns.

Small gap between the bottom of vertical columns and the floor exposed by reflections.

At first, I thought this was a problem with the reflection plane similar to the screenshot of the desk above. But the tile floor is the only visible reflective surface in this screenshot (since rain is disabled), so the reflection plane Z should be exactly at the floor height. I spent quite a while trying to debug this. It turns out that it was really an error in the scene geometry - there actually was a small gap between the bottom of the columns and the floor! I could only see it by disabling collision detection so that I could move the camera to near floor height. Once I figured it out, the problem was trivial to fix by editing the scene geometry.

Reflections can be enabled in all scenes that contain flat horizontal surfaces. Here is a view of the wet road and driveway surfaces in the house scene. The reflections are perturbed by the road's normal map to create a rough/bumpy wet surface effect.

Reflections of the house, fence, and trees on the wet road and driveway surfaces.

I suppose the next step is to implement reflections for more than one plane. The goal is to find a way to do this without needing to render the scene reflected about more than one Z value, and to allow reflections in other directions. A further improvement would be to support reflections on curved surfaces. This likely requires rendering to the six faces of an environment cube map. Environment mapping techniques work well for static scenes, but are more challenging when dynamic objects are involved. It's probably too slow to render the scene multiple times (3?) each frame for the visible faces of the cube map. I'll have to put more thought into it.


2 comments:

  1. You might consider screen-space reflections as another way to get fast believable reflections. Works for curved and non-horizontal surfaces without adding full-scene render passes.

    ReplyDelete
  2. That's a good suggestion, but I've never gotten that to work. SS reflections are easier to do with a deferred rendering pipeline, and difficult to do with the way I have things set up. Too many different shaders, lots of partially transparent objects, etc. Maybe I can get it to work without the normals texture and just the depth information. How good would it be calculating the normal using screen space derivatives? I don't know, I was never able to get SSAO and some other effects to work. I know there can be artifacts though.

    ReplyDelete