Friday, June 16, 2017

CSE 168 final project

In the final project for CSE 168, we are tasked with creating our own scene that implements a couple features from a list of given features, then rendering it with our ray tracer.
Since my ideas had run dry, given the stress from my other classes this quarter already, I was pretty much looking everywhere for a good idea. Then one day while having lunch at Canyon Vista (dining hall) I decided on the scene I was going to render - a rocky terrain.


This basic scene will (hopefully) be rendered as follows:
  • A procedurally generated terrain - because we don't want the ground to be entirely flat
  • Displacement mapped rocks on the ground
  • Maybe a fish eye lens to capture the entire scene if time allows

1 - Procedural modeling

Of course, any terrain must start out as a piece of flat ground, except that looks horribly boring:
Even with the technique of normal mapping (to simulate bumps on the surface), the terrain itself still does not look that much more... appealing:


This is due to the fact that all the points on the sphere lie on the same plane - we are merely looking at a somewhat randomly colored rectangle. As a result, the points on the plane must actually be shifted, so that the terrain itself looks like actual terrain. We will explore a few options for this project.

1.1. Diamond Square algorithm

The idea behind this algorithm is quite simple - alternate between a square step and a diamond step, and randomly offset the midpoint of each.
A nice illustration of this algorithm is available at http://java.sys-con.com/node/46231/print:
This is the result of an initial approach at this algorithm (yes, something's broken, and I have no idea what):

Since there is no way to determine at runtime what sort of maximum offset value would make the terrain offsetting look "nice," the max offset amount must be tweaked by hand. This is the result of another attempt but with a lower offset value:
It is clear, however, that the terrain still looks unappealing due to the extremely large height differences as we move along the surface.
This leads to another tweak: only allow positive offsets.

This does not really change anything, as there are still significant height differences (except that they're now halved compared to before, which doesn't make too much of a difference). Therefore, we try another tweak: increase the minimum allowed value for the offset:
This leads to something that looks very similar to the original bump mapping image, albeit a little more convincing.

1.2. Circles algorithm

I will now try a different approach: select a point at random on the terrain surface, then bump up the points surrounding it in a circle. Points closer to the center get bumped up more. This makes it look as if a giant mosquito bite was applied to the surface.
This is the result after 200 iterations:
This now more or less resembles some boiling cheese, if anything...
A noticeable problem shows up, however, after 500 iterations:
Nevermind the fact that the circles algorithm produces something that looks more like boiling cheese, we see that after we start bumping up practically every point, their height differences start to diminish, so increasing the number of iterations doesn't quite add that much more detail to the surface.
As a result, a tweak needs to be done to this algorithm: linearly decrease the maximum displacement amount after each iteration.
Now, we do see more noticeable bumps, however after a certain number of iterations, the bumps are so small that there is barely any noticeable change to the surface. Consequently, one last tweak is made: quadratically decrease the displacement amount, disallow the displacement amount from dropping under a certain threshold, then reset the displacement amount after a random number of iterations.
While we do get some nice round hills, this algorithm is not quite suitable for generating something that looks like a rocky terrain.

1.3. Faults algorithm

As the name suggests, we will try a different approach: Introduce fault lines to the terrain. At each iteration, a fault line is drawn through the terrain. All points on one side of the fault line will be raised, while all points on the other side will be lowered. To ensure that lines in every possible direction will be created, we do the following at each iteration:
  • Generate a random number v between -π/2 and π/2
  • Set a = sin(v), b = cos(v)
  • Generate a random number c between -s/2 and s/2, where s is the size of the terrain, which we pick to be the average of the width and height.
This creates a line that satisfies the linear equation ax + by + c = 0 (or, in this project, ax + bz + c = 0). For every point on the plane, we create a vec2 out of the x and z components of it, then calculate the dot product of that with a vec2 that represents the fault line. If it's positive, it's on the raised side. If negative, it's on the dropped side. If it's zero, which is rare, it remains unchanged. After 100 iterations, we obtain this result:
Like before, we see the same "essentially flat" problem when we increase the number of iterations:
Consequently, we will try the same tweak as the circles algorithm: have a linear dropoff for the displacement amount. This is the result after 500 iterations:
After increasing to 1000 iterations:

This looks a lot better now, but let us see what happens if we have a quadratic dropoff:
And now we have something that looks a lot more convincing for a rocky terrain. One small problem remains, however: the terrain points close to the camera look like square blocks, so we need to increase the resolution of the terrain's point grid - this will be done later on.

2. Displacement mapping

Clearly, having just a terrain is itself quite boring, so we need to add stuff to the terrain. Displacement mapping seems like a good choice - we can add objects that look like rocks.
We start out with a simple geometry: a sphere
Mathematically, ray tracing is quite easy, since we need only solve a quadratic equation. Texturing the sphere depends on its normal, however a problem arises: what if we wanted to shift the surface of the sphere up/down based on a texture file?
This requires tessellation of the sphere into small triangles. The following shows a tessellation into 30 stacks (latitudes) and 30 slices (longitudes):
By further increasing the tessellation, the triangles will become so small that it is hard to notice that the sphere was composed entirely out of triangles. The following shows a highly tessellated ellipsoid:
At this point, I reckon I could have tried to find a good displacement map to render a lemon or a mango.
Nonetheless, using the same formula for calculating texture coordinates, we sample each point on the sphere/ellipsoid that the ray hits with the corresponding texel value. The rgb value of the texel will be scaled by the normal at that point on the sphere, and the sphere's point will be shifted that much.
For simplification purposes, we will use nearest neighbor filtering for texel selection. We will use the following test texture:
This yields the following result:

Clearly, the sphere looks very off, and this is due to the fact that the shifted points should have entirely different normals. We will need to rederive normals by sampling nearby displaced points, and average them together if one point belongs to multiple triangles. After fixing the normals, we try a different texture:
This yields the following better looking sphere:
A small problem is that the texture map is upside-down, so we will flip the vertical texel coordinate in the program:
One small problem remains: the points get displaced too much. A simple tweak is to scale whatever interpreted displacement from the texture file down.
The following shows a couple displacement mapped spheres using some random textures found on the internet following a google search for "displacement map cobblestone":


There is probably a lot of playing around required in order to get something that looks like an actual rocky terrain with displacement mapped spheres (rocks) on it. This is what I have so far as of this morning:



3. Changing the lens view

Since the terrain is relatively wide, it is usually nice to try to view more of it.
Let us see what happens if we were to increase the field of view value:
Or, to match the new desired field of view, we increase the image width:
One tweak that can be made instead of increasing the field of view is by changing how the rays are generated. Rather than shooting rays through a flat image plane, let us assume that the image plane is curved:
Or, bringing this to the same width as the previous approach:

One small issue is that this just looks like a stretched out version of the regular change of field of view approach, and this is likely because we also curved the image plane about the y axis. By keeping the image plane flat along the y axis and only curved along the x axis, we get something that looks similar to the result of a 2:1 panoramic shot:

The following is a comparison image between the panoramic shot (top) and the regular change in field of view (bottom):
By changing the way the camera rays are generated and how they are projected onto the image plane, we can get something that looks like it is being viewed through a convex mirror, even:





No comments:

Post a Comment