EnvSim is a semi photo-realistic simulation of the boundary between a land and water mass, with the ultimate goal of rendering entire island mass, that is easily populated with models. EnvSim uses a variety of techniques to achieve various effects that one might encounter in such an environment.
Terrain is procedurally generated using the diamond-square algorithm. A quick summary of the algorithm is:
The algorithm I used is modified with a random aspect, so that each value is within a range of the average of it's component values. This produces a more realistic terrain effect. Additionally, the four initial edge values for the terrain, as well as the original terrain's middle point were seeded with random values. The particular algorithm implemented in EnvSim requires the terrain map to be a square map of (n^2 + 1) length in order to work properly.
Terrain normals are generated procedurally, per vertex. The normal of each face in the terrain mesh is calculated, and then added to the normal vector of each component vertex. Once all face normals have been calculated, all vertex normals are normalized. This results in each vertex having a normal vector that is the average of the normal vectors of each face that it is a part of.
In order to create the effect of a smooth conversion between the landmass terrain and the seabed terrain, while only drawing one continuous triangle mesh, the terrain is Multi-textured using shaders. The vertex shader for this effect simply passed the vertex positions and texture coordinates along to the fragment shader, along with the current level of the water in the scene, and the current height of the vertex being processed
The fragment shader determined which textures to apply based on the difference between the height of the fragment being processed and the water level. Any fragments above the water level were textured as a blue sky map with an overlay of a crack texture in order to create the fragmented effect. Fragments within .1 world units of the border were textured with a mix of the land texture, the cracks texture, and the sand texture, slowly fading from the land and cracks, down to just the sand. All other fragments had the sand texture applied only. A golden material was then multiplied into the texture values in order to make the terrain green (instead of the native texture's blue) and the sand a more golden, sandy color.
Note on processor load: This texture mapping is a very intensive process, requiring a fair bit of texture memory. Because shaders are run by the GPU, this shader will run especially slowly on virtualized systems that are not connected directly to a real graphics card, because the CPU is being forced to do a massive amount of extra processing that the GPU would normally be responsible for.
Because the computation of the dynamics of an animated water surface is generally an iterative and processor intensive task, I implemented shaders to animate the water surface, as well as generate the surface normals.
The water surface's deformation is a simple sine wave. The vertex shader deforms the y coordinate
vertex.y = vertex.y + sin(time + t) * .07; Where t = sqrt(square(i) * square(j))Time is an animation constant (discussed later in this page), and i and j are the x and z positions of the vertex in the terrain mesh. The multiplication by .07 is in order to scale down the sin wave to be more reasonable in size. Note that the equation for t allows for circular waves. It is possible to create straight waves with
t = (i + j) / 2instead of the given t equation.
The surface normals are also calculated in the vertex shader. The surface normal calculation is currently not mathematically accurate and is instead a mathematical approximation in order to create shadows that look similar to what one might expect from a rippling water surface. Normal calculation is also calculated in the Vertex shader using the following parameterization:
normal.x = (j/i) * (-sin(nA)); normal.y = cos(nA); normal.x = (i/j) * (-sin(nA)); where nA = arctan(cos(time + t) * .07);Time, t, i, and j are the same as above. This equation was derived using a little math and a massive amount of trial and error. I hope to implement a mathematically correct model in the future.
False reflections were implemented for EnvSim using the stencil buffer. The procedure for creating false reflections using the stencil buffer for EnvSim was as follows:
The Gyroscope objects are hierarchically modeled. Each ring has an additional rotation applied to it, creating a gyroscopic effect.
Real-Time animationThe animation of the gyroscopes and the water are based on an animation constant "time". At the beginnning of each display loop, "time" is updated by a call to glutGet(GLUT_ELAPSED_TIME);. This allows the animations to be advanced in real time, and process at the same speeds regardless of processor speed or refresh rate.
Camera Restriction (Collision Detection)In camera constraint mode, the camera is locked above the terrain and the water, and within a 15x15x15 box at all times. This is accomplished simply by checking the camera's coordinates against the 15x15x15 box, and against the height of the map vertex that the camera is currently directly above (or against the water surface, whichever is closer to the camera).
Bit-mapped textThe coordinates on the map (equivalent to which vertex in the terrain that you are hovering over), as well as the absolute world coords, are displayed using bit-mapped text, displayed under an orthographic projection, using glutBitmapCharacter();.
There is a specific bug in OpenGL's extension capabilities when running on AMD64 processors, where a call to glGetString(GL_EXTENSIONS) causes an unexpected segfault. The report of this bug can be found here. This is relevant to any OpenGL project attempting to access and use OpenGL extensions.
EnvSim uses GLee as it's library to access OpenGL extensions and versions beyond 2.0. However, it sometimes runs into unexpected problems regarding the location of certain extension function pointers. The source of this bug is currently unknown to me.