Computer Graphics - Fall 2017.
gradle run
- Start off by importing the gradle project into your IDE.
- Set the
Working directory
in your build/run configurations to thecore/assets/
folder. - Build & Run
WASD
Movement.← → ↑ ↓
Look around.SHIFT
Move faster, this makes the evil snowman move faster as well.Mouse
The mouse can also be used to look around.V
God Mode, makes it possible to go through walls, fly around based on directional/looking vector and enables other buttons such as:R
Move down.F
Move up.
The goal of the game is collect all the token/balls in the maze, when that is completed you levelup into a new larger maze. Each maze is randomly generated. Watch out for the evil snowman, if he gets you, you lose. (It's possible to pass him if you keep tight up to a wall).
Due to some update lag it is eventually possible to get through walls if you're patient enough and keep colliding into them.
We found and used a random maze generator from Rosetta code. The generator uses integer values to describe each cell where bitmask operations are used to grab the info. A cell with the value 12
or 1100
in binary (NSEW)
has the North and South edge marked as open 1
but East and West as closed 0
.
Each cell has border-limits on the X and Z axis in world space, the player is not allow to exceed these borders unless the cell is open in that direction. If the cell is open we also check if the player is within the perpendicular axis. This means that if a player tries to go south in a cell which is open to the south, he also has to be within the east-west borders of the cell.
The walls have either a brick wall or wooden wall look, we spent a good time to randomly generate this look. The brick-wall consists of 72 boxes (bricks) + 1 solid wall box within.
The Evil snowman spawns randomly on a diagonal spot in the maze and travels to adjacent cells never looking back (turning around) unless hitting a dead end. At any cell if he has an option to not only move forward, his choice will be random. When the player collides with the snowman he looses the game.
We display the eyes and nose on the snowman based on the directional vector between the player and the snowman so that the snowman is always looking at the player.
Being able to look around with the mouse without affecting the movement of the player inside the maze.
- 2D camera.
- Following the player in the 2D camera but also snapping the 2D view to the edges of the maze.
- Score indicator bar as you collect the tokens/balls.
- Fix Z-fighting object jitter, avoided by creating the pillars between all the walls. Makes for nice aesthetics too.
We've spent a good amount of time tweaking our shader class and the vertex shader for some specular higlighting. We never got a satisfying look so we've just fallen back to our original basic diffuse light. We'd love to spend some more time on this, but our brains are fried and learning from 4 hour youtube videos is a terrible medium for tired minds.
So our model has only 1 light source, a light that hovers over the player and illuminates the scene. It does look pretty good though :)
We've removed the variable declerations in the shaders for less text, this is the difference between the 3 types of variables that we declear.
attribute
variables is the vertex list sent each time into main as the shaders gets executed.uniform
variables are only used within the shader. These are the common variables that we've used and have handles to in our Java program.varying
variables brought along the entire OpenGl pipeline, can send along as many as we want. v_color used in rasterization and therefor found again in the fragment shader.
void main()
{
vec4 position = vec4(a_position.x, a_position.y, a_position.z, 1.0);
position = u_modelMatrix * position; // The position of the object
vec4 normal = vec4(a_normal.x, a_normal.y, a_normal.z, 0.0);
normal = u_modelMatrix * normal; // The normal vector of the object
v_n = normal;
v_s = u_lightPosition - position; // Vector pointing to the light
position = u_viewMatrix * position; // The final position in the game
gl_Position = u_projectionMatrix * position; // Setting the position
}
Some specular calculations that we ended up not using.
// For use with specular color calculations, we calculated v
// which is the vector from surface vertex to eye.
vec4 v = u_eyePosition - position; // Vector pointing to the camera
// v_h is the vector addition of the vectors 'source to light'
// and 'source to eye' to use for specular highlight.
v_h = v_s + v;
void main()
{
// Lambert calculates the strength of the color based on the corner
// between the vertex's normal and vector from the vertex to the light source.
// This is a value between 0.0 and 1.0
float lambert = dot(v_n, v_s) / (length(v_n) * length(v_s)); // How light hits the objects
// Value as a strength unit multiplied to the color.
// Diffuse is a value independent from the position of the looking eye.
vec4 color = (lambert * u_lightDiffuse * u_materialDiffuse); // The final color of the object
gl_FragColor = color; // Setting the color
}
Specular calculations in the fragment shader.
// Like lambert, phong is the intensity value based on v_h and the viewers eye.
// It is most intense in the vertex that would reflect the light source in the surface.
float phong = dot(v_n, v_h) / (length(v_n) * length(v_h));
vec4 color = lambert * u_lightDiffuse * u_materialDiffuse;
// As material shininess is increased the specular highlight becomes
// smaller as the strength diminishes faster.
// Phong is 1 where it's the strongest but fades to zero 'fast'
// as when < 1 values are put to any power they become smaller.
color += pow(phong, u_materialShininess) * u_lightDiffuse * vec4(1.0f,1.0f,1.0f,1.0f);