Create a Light
class and modify the Model
class to draw objects with material properties.
Details
The Light
class should:
Inherit from the
GameObject
class so that moving and turning the light should affect the light’s position and spot direction.Define the default constructor
Light()
that initializes a light with the with the color white, i.e. rgb = (1, 1, 1).Define the constructor
Light(float r, float g, float b)
that initializes a light with the properties:Ambient light turned off.
Diffuse and specular light set to the specified color.
Constant, linear, and quadratic attenuation set to 1, 0, 0, respectively.
Spot cutoff and exponent set to 180 and 1 respectively.
Position set to the origin.
Spot direction set down the negative z axis.
Light is activate, or ‘on’.
Define the setters:
void SetColor(float r, float g, float b)
that sets the light’s color to the specified rgb values. The light’s color should be used for its diffuse and specular color. The light’s ambient color should be black, rgb = (0, 0, 0). The method should assert that the input values define a valid color.void SetSpot(float cutoff, float exponent)
that sets the light’s spotlight to the specified cutoff and and exponent values. The function should assert that cutoff is a valid angle.void SetAttenuation(float constant, float linear, float quadratic)
that sets the light’s attenuation to the specified factors. The method should assert that the input values are a valid attenuations.void SetActivation(bool activate)
that sets whether the light is ‘on’, that is, whether it is used in lighting calcualtions for subsequent drawing.
Note that there is a limit of 8 lights that can be created. Creating a light after 8 have already been created should print an error and create a light that is always off. Also note that it should be possible to destroy a light and have it no longer count towards the 8 light limit.
The Model
class should be updated so that:
The
void Load(const std::string &filepath)
method should:Read Wavefront obj files that define vertex normals. It should read
vn
linesf
lines that specify vertices and normals.Assume that a obj file will only specify one type of face. Either vertex only lines (i.e. f 1 2 3) or vertex and normal lines (i.e. f 1//1 2//1 3//1).
Read Wavefront obj files that use materials. It should handle
mtllib
andusemtl
lines.The
Load
method should parse Phong reflectance components from mtl files. It can ignore all mtl lines that are notKa
,Kd
,Ks
, andNs
.If a face does not have a material specified or if the material name can not be found, or if there is an error reading an mtl file, the default material should be used:
Ka: 0.2 0.2 0.2
Kd: 0.8 0.8 0.8
Ks: 0.0 0.0 0.0
Ns: 0.0
Any components of a material that are not specified should be set to their values in the default material.The the filename of an mtl file is relative to the obj file it is used in, not to the execution directory.
The
void Draw() const
method should draw a model using materials and lights. If the model’s obj file specifies vertex only faces, then it should draw faces without lighting by using the materieal’s ambient component for the color. If the model’s obj file specifies vertex and normal faces, then it should draw faces using lighting.
Extra Credit: Drawing a model that specifies normals using glDrawElements
may require duplicating vertices and/or normals to keep the vertex and normal arrays perfectly aligned. The easy way to do this is to duplicate all vertex normal pairs so that the vertex and normal lists have (num_faces * 3 * 3) elements in them. However, this will often waste memory. For extra credit, efficiently build the vertex, normal, and index arrays such that no vertex normal pairs are duplicated.
Your code can use any library in the C++ Standard Library and the OpenGL Mathematics Library.
Your code should work on the lab computers. Test to verify.
Your code should be clean, readable, and efficient. Follow the Google C++ Style Guide for help with style. Use the cpplint tool to help check your code.
All classes should be declared inside of a namespace called engine
.
Your project should have the following directory structure:
project
├──data
| ├──model1.obj
| ├──model2.obj
| ├──model3.obj
| └──...
├──makefile
├──lib
| └──glm
| ├──gtc
| ├──gtx
| └──vec3.hpp
├──src
| └──engine
| ├──camera.cc
| ├──camera.h
| ├──game_object.cc
| ├──game_object.h
| ├──light.cc
| ├──light.h
| ├──model.cc
| ├──model.h
| ├──rigid_body.cc
| └──rigid_body.h
└──test
├──test1.cc
├──test2.cc
├──test3.cc
└──...
Your project should compile and run with this makefile.
Example
#include <cstdlib>
#include <cstdio>
#include "GLFW/glfw3.h"
#include "engine/model.h"
#include "engine/rigid_body.h"
#include "engine/camera.h"
#include "engine/light.h"
int main() {
// Initialize GLFW
if (!glfwInit()) {
fprintf(stderr, "Failed to initialize GLFW\n");
exit(EXIT_FAILURE);
}
// Create window
GLFWwindow* window = glfwCreateWindow(640, 480, "Test", NULL, NULL);
if (!window) {
glfwTerminate();
exit(EXIT_FAILURE);
}
glfwMakeContextCurrent(window);
// Initialize OpenGL
glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
glEnable(GL_DEPTH_TEST);
// create rigid body and camera
engine::Model model("data/model.obj");
engine::RigidBody rigidBody(&model);
engine::Camera camera(35.0f, 0.1f, 10.0f);
camera.LookAt(glm::vec3(0.0f, 0.0f, 5.0f), // eye
glm::vec3(0.0f, 0.0f, 0.0f), // center
glm::vec3(0.0f, 1.0f, 0.0f)); // up
engine::Light light(1.0f, 1.0f, 1.0f);
light.SetPosition(glm::vec3(0.0f, 0.0f, 10.0f));
// Loop until the user closes the window
while (glfwGetKey(window, GLFW_KEY_ESCAPE) != GLFW_PRESS &&
!glfwWindowShouldClose(window)) {
// Set the rendering viewport
int width, height;
glfwGetFramebufferSize(window, &width, &height);
glViewport(0, 0, width, height);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// Select and setup the projection matrix
glMatrixMode(GL_PROJECTION);
glLoadIdentity();
camera.MultProjectionMatrix(width, height);
// Select and setup the view matrix
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
camera.MultViewMatrix();
// Rotate and draw rigid body
rigidBody.Turn(10.0f, glm::vec3(0.0f, 1.0f, 0.0f)); // 10 deg around y axis
rigidBody.Draw();
// Prepare for next frame
glPopMatrix();
glfwSwapBuffers(window);
glfwPollEvents();
}
// Clean up
glfwDestroyWindow(window);
glfwTerminate();
exit(EXIT_SUCCESS);
}
Grading
The functionality of your code will be graded according to the following point scale:
1 | draw a model with vertex only faces and a material | 5 points |
2 | draw a model with vertex and normal faces and an ambient material | 5 points |
3 | draw a model with vertex and normal faces and a diffuse material | 5 points |
4 | draw a model with vertex and normal faces and a specular material | 5 points |
5 | draw a model with vertex and normal faces and a specular shiny material | 5 points |
6 | draw a model with vertex and normal faces and a ambient, diffuse, and specular material | 5 points |
7 | draw a model with multiple different materials | 5 points |
8 | draw a model with default material | 5 points |
9 | draw a model with default material components | 5 points |
10 | draw a model with multiple material libraries | 5 points |
11 | draw a model with the default light | 5 points |
12 | draw a model with a colored light | 5 points |
13 | draw a model with a light with a changed color | 5 points |
14 | draw a model with a spot light | 5 points |
15 | draw a model with a turned spot light | 5 points |
16 | draw a model with an attenuated light | 5 points |
17 | draw a model with an deactivated light | 5 points |
18 | draw a model with multiple lights | 5 points |
19 | draw a model with 9 lights | 5 points |
20 | draw a model after destroying a light and creating 8 lights | 5 points |
The final grade for this part of the project will be computed as:
const double final_score = fmin(fmax(functionality_score - (days_late / 21.0 * 100.0), 0.0), 125.0);
Submission
Submit a .zip file with the following structure and contents:
username
├──camera.cc
├──camera.h
├──game_object.cc
├──game_object.h
├──light.cc
├──light.h
├──model.cc
├──model.h
├──rigid_body.cc
└──rigid_body.h
where username
is your cpsc user name.
Submit your .zip file on Inquire on Friday, April 3rd.