For this assignment, you will be creating a simple procedurally modeled mountainous island on a smooth blue sea. For the mountain, you will use the random fractal subdivision algorithm on a hexagonal grid, with the added constraint that points on the perimeter each iteration that would be placed above sea level are clamped to sea level.
To get you started, a simple OpenGL application has been committed to the GLapp directory in your git repository. This application renders two objects: a textured plane with a sphere floating over it. It uses three external libraries that should work on Windows, Mac, and Linux: GLFW provides window creation and input handling; GLEW manages the OpenGL functions and extensions; and GLM provides vector and matrix types and functions.
For Windows, I have precompiled all three libraries into a zip, together with a batch script that will set the environment variables necessary for CMake to be able to find them. For Linux, you should be able to find all three packages in the package manager from your distribution. On Mac, I recommend installing them with the homebrew package manager.
You will need a computer capable of running an interactive application with at least OpenGL 3.0. If yours will not work, we have a limited number of lab computers that can be used remotely for this and future projects, but only by one person at a time. Contact me if you think you need to use one.
Your sea can be based on the existing Plane sample, but you will need to make it larger and solid blue instead of textured. You can make the sea blue by creating a solid blue texture (this is what I'd recommend, make the blue image at least 4x4). You could also modify the existing object shader to switch from texture to color based on some shader input, or create a new specialized sea shader.
Your fractal island should be a hexagonal grid of regular triangles, using the same rocks texture as the Plane example. The initiator is a six-triangle hexagon. The texture coordinates should be scaled so the left edge is at
u=0 and the right edge at
u=1. The center three vertices should be at
v=0.5. Since these triangles have a base size of 0.5 in texture space, the height of each triangle would be
0.5*sqrt(3)/2, which puts the top two vertices at
v=0.5-0.25*sqrt(3), and the bottom two at
The x/y coordinates of initiator level should be a simple scale and translation of the texture coordinates to make an island similar in size to the initial Plane. The center initial vertex should be at
z=size.z and the outer six vertices at
Each iteration adds one vertex to the middle of each edge, replacing each triangle with four smaller triangles. The coordinates of the new vertex in an edge should be the average of the two endpoints, with z offset up or down by a random amount proportional to 2-level. Any new points on perimeter edges should be clamped to sea level if they end up above.
The normal at each vertex should be the area-weighted average of normals of all of the faces that share that vertex. Since the magnitude of the cross product of two edges is proportional to the area of the triangle, you can compute the area-weighted average by adding up the un-normalized cross products for each face, then normalizing the total. For this to work, you should make sure the vertex indices for each triangle end up in counter-clockwise order when seen from above, and make sure your cross product produces the upward-facing normal with
Take a subset of the obj model file format as your initiator. Specifically, you should support lines that start with "v" as a vertex coordinate, lines that start with "vt" as a texture coordinate, and lines that start with "f" with three vertex/texture pairs defining the triangles. Ignore any other lines, and any additional index associated with each vertex on the "f" lines. Beware, vertex and texture coordinate indices in this file format start with 1 instead of 0. I've created a sample file equivalent to the base assignment.
Add new key responses so '+' increases the subdivision level, '-' reduces the subdivision level, and 'N' regenerates a new island at the current level with a new random seed
Students in CMSC 435 can do the grad (CMSC 634) portion for up to 15 points of extra credit. Extra credit is only available if you are not accruing any late penalty. That is that you are either on submitting on time or using free late day(s).
For cross-platform random number generation, the classic rand() function is a rather poor quality linear congruential generator (though good enough for this project) that generates a random integer. The drand48() function returns a double between 0 and 1. C++ STL has a set random number generators (see
The OpenGL application uses vertex and index arrays for rendering. The vertex position and texture coordinate arrays for each level of the island consists of all of the same vertices as the previous levels, plus new ones introduced by the next level. The normal and index arrays are different for each level. One of the tricky parts of this project will be assuring that the vertices from previous levels stay where they were, so are either re-used, are regenerated in the same order to produce the same values from your chosen random number generator; and that any new vertices are generated only once to be shared by any triangles that use them.
There are three primary approaches you could use in keeping the vertices shared and consistent:
- Come up with equations for the number of vertices and indices at each level and canonical vertex and index ordering. These would be based on deriving some recurrence relations each level. For these, you can look at what happens in each triangle independently, or pairs of triangles forming a stretched rectangular grid. If converted to closed form, this approach would allow you to generate new points in an O(N) pass, and generate the index list in another O(N) pass.
- Build a map (aka C++ associative array) from UV coordinate to index. If a given UV coordinate is not yet in the map, you know you need generate a new vertex there. If it is in the map, you know it has already been generated and can reuse the previous vertex index. Since floating point comparisons are inexact, you'll need to implement your own comparison or hashing operators to deal with that (for example, by rounding to a grid or including an uncertainty epsilon in your comparison operators).
- Build an auxiliary half-edge data structure to track all edge and vertex sharing, and only build the final vertex or index arrays from that. Note that the data structure may be internally inconsistent in the middle of a level generation, and your algorithm will have to ensure it is consistent when the level is complete.
Whenever you change any of the array data, you will need to re-send it to the GPU with glBindBuffer and glBufferData calls, as are used in the object constructors. The glBufferSubData call used to pass data to the shaders only works if the size of the buffer remains the same.
What to turn in
Turn in this assignment electronically by pushing your source code to your class git repository by 11:59 PM on the day of the deadline and tagging the commit
assn3. Do your development in the
GLapp directory so we can find it.
Also include an
assn3.txt file at the top level of your repository telling us about your assignment. Tell us what works, and what does not. Also tell us what (if any) help you received from books, web sites, or people other than the instructor and TA.
You must make multiple commits along the way with useful checkin messages. We will be looking at your development process, so a complete and perfectly working assignment submitted in a single checkin one minute before the deadline will NOT get full credit. Individual checkins of complete files one at a time will not count as incremental commits. Do be sure to check in all of your source code, but no build files, log files, generated images, zip files, libraries, or other non-code content.