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.

Tuesday, December 1, 2015

Shadows Update

I worked a bit more on tiled terrain shadows and fixed a few problems from the previous post, so I'll post a few updated images here.

Scenery Shadows
I forgot to add shadows for the other ground scenery, which includes rocks, stumps, and logs. This has been fixed - they now use the same shader as the tree branches. Everything in the scene now receives shadows.

Leaf Back Lighting
I took a screenshot of the leaf back lighting from the sun during sunrise or sunset (not that there's a difference between them in 3DWorld). The unshadowed leaves on the lit side of the tree transmit some light to their back sides, but the shadowed leaves on the unlit side remain dark. This creates a halo of light around the trees similar to "rim lighting" sometimes used in graphics and photography.

Back-lit trees at sunset.

Here are some examples of real photos of back-lit trees, though they were taken with brighter lighting conditions:
Example 1
Example 2
Example 3

Water Shadows
I finally fixed the mesh shadows on the water surface so that they work even when the water is deep/high above the mesh. I folded the terrain mesh into shadow map creation by determining which tiles fall within the frustum (bounding area) between the light source (sun) and the tile to be shadowed. Any tiles whose bounding volume (bounding cube in this case) intersects the light frustum may contribute shadows and need to be drawn into the shadow map. When the sun is high in the sky (large +z component), only the current tile and it's 8 neighbors in {N, NE, E, SE, S, SW, W, NW} are included. The neighbors may contain trees and other objects near/crossing the tile boundary that cast shadows on the adjacent tile. When the sun is low on the horizon, any additional strip of tiles stretching from the sun to the receiver tile are included. I was worried that this would be too many tiles, but it turns out that only 3 or 4 additional tiles are needed. The increased shadow map creation time is barely noticeable. Here is what it looks like in a screenshot where a distant peak casts a long shadow across the surface of the water.

Shadows of a distant peak on the water surface.

Mesh Shadows
3DWorld supports importing of 3D models in the Wavefront object file format. These models can be instanced in the scene with various transforms and colors. I have added three instances of the Chinese dragon statue and one instance of the Sibenik Cathedral to my scene. Each object model casts shadows on itself and on the rest of the scene, including the terrain mesh, trees, grass, and plants. Their shadows can extend far across the mesh. Here are some examples of what it looks like.

Shadows from Chinese Dragon models on the mesh, trees, and grass.
Shadows from Chinese Dragon models cast on the terrain from several tiles away.

That's it for now. I'm moving on to implementing reflections for planar surfaces that should significantly improve the wet surface rain effect. This will be the topic of a future post (assuming I get it working).