Gillius's Programming

Mega Monkey Mayhem
Final Report

Last updated: Wednesday, November 19, 2003 0:17 AM

Project midterm update for the Super IsoBomb 3D project, under development by the following students

for the class Computer Graphics 2 (4003-571-01) taught by Nan Schaller.

Problem, Project Description, Approach

Mega Monkey Mayhem was a game worked on by a large number of students, but it was initiated by our team. For this class, we developed a few portions of the game for the class.

Jason

I did the cel-shader for this project, and worked on improving and refactoring the cel-shading system. All of the work for this is in the "Animation3D" folder as our project is set up in the vcproj file. The actual shader part code that I did that was entirely new for this class is contained in the ShaderCode.h file. This file along with the ToonSkinnedRenderer class is the core of the cel-shading work.

The algorithm works on the assumption that a good approximation for the silhouette is to shade black pixels where the normal at that point on the surface is roughly perpendicular to the view vector (or almost lies in the projection plane). The trick used to do this in hardware quickly, is to find the dot product of the position vector and the normal, both in view space. This results in a value from -1 to 1, which I scale into the range 0 to 1, and place that value into the alpha component. I draw the model again, this time with all black triangles, and I turn on alpha testing so that only pixels with a high enough alpha are drawn. This way, the silhouette only needs a programmable vertex shader, and not a programmable pixel shader. This allows it to be run in hardware on a larger set of cards, and takes advantage of the high-speed fixed pixel pipeline.

In addition to the cel-shading, I did a lot of refactoring so that you could have multiple "instances" of a model without completely reload it -- this is what the ModelProxy and AnimatedModelState classes are for. I also did a large amount of optimization with the skeletal animation, most particularly in the BoneInterpolator class.

Jon

The map rendering was heavily based off of 3D map rendering code written previously for Andy Phelp's 3D graphics class. Later into the project some major optimizations were made to the map rendering process. Initially, the each face on the map was generated by a combination of triangle strips with the same height and width. High walls were generated by several levels of triangle strips instead of just one. Also each face had to be rendered with a seperate call since each face could potentially have a seperate texture. This resulted in hundreds of render calls per frame.

The new optimized map code renders using one triangle list for the entire map. First each vertex in the map is added to a buffer that corresponds to a texture. Each face now consists of only 6 vertices. In contrast, the old code used at least 12 vertices per face, usually many more. After the vertices are generated, they are added to a mesh with all the vertices that are to be rendered using a particular texture added to a subset of the mesh. Finally, an optimized mesh is generated using DirectX's mesh welding and optimization functions.

The maps are generated from a text file whose format is described in the map format section of the technical documentation. The map also contains an embedded Quad Map which is used to optimize collision detection.

Peter

Hidden Surface Removal – Frustum Culling

It can be incredibly slow to let the graphics card cull each individual non-drawn polygon in a large and complex scene. For real-time 3D, polygons that are not drawn in a scene can be culled in-groups by CPU checks (by organizing the scene geometry and applying algorithms to determine what polygons to send to the graphics card). A fairly direct method is to bind groups of polygons by an object (sphere or box), and to then perform a view frustum check to see if the aggregation is in the scene.

Rendering models with our animated cel-shader is very expensive. This included the game’s animated characters and power-ups. All polygons inside a given model stay within a restricted volume. Thus, the polygons within a given model are excellent candidates for group culling. For this purpose, each model is bounded by a sphere.

To do cull the models, the 6 planes of the viewing frustum must be dynamically generated (since the camera moves). These planes are calculated from the view and projection matrices in the Camera::UpdateCullInfo method. UpdateCullInfo is only called at most once per frame and only when the camera moves.

Finally, we calculate the distance from a point to each plane using dot product in Camera::ContainsSphere. This distance tells us which side of a plane the point is on and how far away it is. For a sphere to be culled, its center point must be on the outside side of a plane by a distance greater than its radius.

The models are not grouped into hierarchical data structures, such as into boxes in an octree. This is because there are few models and the models are generally spread out. In an octree, there would be overhead for doing additional box to frustum checks and for re-sorting the character models (as they change position) in the data structure. The maps have between 15 and 30 models, and thus only 15 to 30 sphere to frustum culling checks.

Mega Monkey Mayhem’s level geometry is fairly simple - boxes with a minimal number of polygons because they are never lit or changed. And the number of these simple boxes is few by game design. In scenes with our cel-shaded models, culling level geometry wouldn’t be very significant.

Camera Algorithms

Mega Monkey Mayhem starts with a fairly standard Camera class that allows higher level manipulation of the camera matrices. This includes methods for movement and rotation that are used directly by the free flying camera. (The free flying camera is used currently by "dead" players).

The "you win" camera is a static view that is placed in front of the winning character and sets its facing direction to a 180 degrees rotation from the direction the winning character is facing.

The main camera was intended to give a similar view to that found in the original 2D isometric "Super IsoBomb", but with rotation and depth. This camera is used to view the game during normal play, and thus is positioned relative to the current position of its target (its character).

The main camera always faces its character. It is positioned in a circle around its character (change in x and z values are calculated using sin and cos with the current rotation) and up a certain height (change in y). The user can rotate the camera to lock in increments of 45 degrees. This style allows for fluid motion of the camera views that the user controls with button presses (e.g. - with a gamepad or keyboard, not with a mouse).

The main camera has a current rotation amount and a rotation goal. When the user inputs this action, the camera determines what is the next rotation goal (destination) in Camera::startIsoRotate. Each update, the camera increases or decreases the rotation amount towards that goal (done in Camera::centerOnEntity).

The main camera also allows zoom in/out. This controls the position of the camera by scaling the size of each component (x, y, and z) of the distance vector between the character and the camera.

Future Enhancements

If we were to continue working on the game there are several improvements that we would like to add. First of all, there are a few known bugs that should be corrected. The messaging system does not work correctly with players 3 and 4. Also there is one known crash bug that was not fixed in time. Second, the game was not playtested very thorougly. With more playtesting the game could have been tweaked to be more enjoyable. Finally, we want to add some sort of a player indicator that will clearly identify who each player is.

In the long term, we would like to add a network engine in the game, if the opportunity and time to do the work arises.