[PA 3] Trackball Camera + Lighting 구현하기
Category: Computer Graphics
Task Lists
- Trackball Camera 구현하기
- Fix the lookat position to world origin
- Rotate your camera by dragging [12 Points]
-
Dolly in and dolly out by scrolling [3 Points]
Move your camera toward/backward to lookat direction.
-
Zoom in and zoom out [3 Points]
Use key callback to do this. “q” for zoom in, “w” for zoom out
- Lighting 효과 구현하기
- apply phong shading model (ambient/diffuse/specular)
-
Set one point light [5 Point]
ON : key “1”, OFF : key “2”
-
Set one directional light [5 Point]
ON : key “3”, OFF : key “4”
Result Image
Code Implementation
main.cpp
#include <iostream>
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <tinyobjloader/tiny_obj_loader.h>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtx/string_cast.hpp>
#include <cmath>
#include <glm/gtx/quaternion.hpp>
// Function prototypes (callback : usually user interactive)
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode);
void mouse_button_callback(GLFWwindow* window, int button, int action, int mode);
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset);
// Window dimensions
const GLuint WIDTH = 1280, HEIGHT = 720;
const float RADIUS = 1.0f;
float fov = 60.f; // set extent of field of view(시야) to 60 degrees
glm::vec3 lookAt_cur = glm::vec3(0.f, 4.f, 4.f); // current position of camera
glm::vec3 lookAt_init = glm::vec3(0.f, 4.f, 4.f); // initial position of camera
glm::vec3 pos_1 = glm::vec3(0.f, 0.f, 0.f);
glm::vec3 pos_prev = glm::vec3(0.f, 0.f, 0.f); // previous position
glm::mat4 cur_rotation = glm::mat4(1.0f); // initialize to no rotation
bool mouseHold = false; // check if mouse button is clicked
// Define the viewport dimensions
glm::mat4 matModel = glm::identity<glm::mat4>(); // 4x4 identity matrix to represent transformation
glm::mat4 matView = glm::lookAt(lookAt_cur, glm::vec3(0, 0, 0), glm::vec3(0, 1, 0)); // constructs a view matrix
glm::mat4 matProj = glm::perspective(glm::radians(fov), (float)WIDTH / HEIGHT, 0.1f, 100.0f); // constructs a projection matrix
glm::quat RotationBetweenVectors(glm::vec3 start, glm::vec3 dest) {
// calculates and returns a quaternion representing the rotation from start to dest.
start = glm::normalize(start);
dest = glm::normalize(dest);
float cosTheta = dot(start, dest);
glm::vec3 rotationAxis;
if (cosTheta < -1 + 0.001f) {
rotationAxis = cross(glm::vec3(0.0f, 0.0f, 1.0f), start);
if (glm::length2(rotationAxis) < 0.01)
rotationAxis = cross(glm::vec3(1.0f, 0.0f, 0.0f), start);
rotationAxis = normalize(rotationAxis);
return glm::angleAxis(glm::radians(180.0f), rotationAxis);
}
rotationAxis = cross(start, dest);
float s = sqrt((1 + cosTheta) * 2);
float invs = 1 / s;
return glm::quat(s * 0.5f, rotationAxis.x * invs, rotationAxis.y * invs, rotationAxis.z * invs);
}
// called whenever a mouse button is pressed
void mouse_button_callback(GLFWwindow* window, int button, int action, int mode) {
if (button == GLFW_MOUSE_BUTTON_LEFT) {
if (action == GLFW_PRESS) {
mouseHold = true; // set True
double xpos, ypos;
float pixels[3];
glfwGetCursorPos(window, &xpos, &ypos); // retrieves the current cursor
xpos = (2 * xpos - WIDTH) / WIDTH; // calculates the normalized x-coordinate
ypos = (HEIGHT - 2 * ypos) / HEIGHT; // calculates the normalized y-coordinate
double zpos = sqrt(1 - xpos * xpos - ypos * ypos); // calculates the z-coordinate
pos_1 = glm::vec3(xpos, ypos, zpos);
// 디버깅 용
std::cout << pos_1.x << " " << pos_1.y << " " << pos_1.z << std::endl;
}
else if (action == GLFW_RELEASE) {
pos_1 = glm::vec3(0.f, 0.f, 0.f); // resets the variable
mouseHold = false; // set False
}
}
}
// is called whenever a key is pressed or released
void key_callback(GLFWwindow* window, int key, int scancode, int action, int mode)
{
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
glfwSetWindowShouldClose(window, GL_TRUE);
if (key == GLFW_KEY_1) {
glEnable(GL_LIGHT0); // enables first light source
}
if (key == GLFW_KEY_2) {
glDisable(GL_LIGHT0); // disables first light source
}
if (key == GLFW_KEY_3) {
glEnable(GL_LIGHT1); // enables second light source
}
if (key == GLFW_KEY_4) {
glDisable(GL_LIGHT1); // disables second light source
}
if (key == GLFW_KEY_Q) { // zoom in
if (fov > 10) fov -= 5;
else {
fov = 10;
}
// recalculates the projection matrix
matProj = glm::perspective(glm::radians(fov), (float)WIDTH / HEIGHT, 0.1f, 100.0f);
}
if (key == GLFW_KEY_W) { // zoom out
if (fov < 100) fov += 5;
else {
fov = 100;
}
// recalculates the projection matrix
matProj = glm::perspective(glm::radians(fov), (float)WIDTH / HEIGHT, 0.1f, 100.0f);
}
}
// is called when a scroll event occurs
void scroll_callback(GLFWwindow* window, double xoffset, double yoffset)
{
if (yoffset > 0) // dolly out
lookAt_cur = lookAt_cur - glm::vec3(0.1) * glm::normalize(lookAt_init);
else // dolly in
lookAt_cur = lookAt_cur + glm::vec3(0.1) * glm::normalize(lookAt_init);
// recalculates the view matrix
matView = glm::lookAt(lookAt_cur, glm::vec3(0, 0, 0), glm::vec3(0, 1, 0));
}
- dolly operation : view matrix를 재계산해서 카메라의 위치를 조정
- zoom operation : projection matrix를 통해 fov 값을 바꿔서 보여지는 시야를 조정
// The MAIN function, from here we start the application and run the game loop
int main()
{
std::cout << "Starting GLFW context, OpenGL 3.1" << std::endl;
// Init GLFW
glfwInit();
// Set all the required options for GLFW
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 2);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 0);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_ANY_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
// Create a GLFWwindow object that we can use for GLFW's functions
GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "glskeleton", NULL, NULL);
glfwMakeContextCurrent(window);
if (window == NULL)
{
std::cout << "Failed to create GLFW window" << std::endl;
glfwTerminate();
return -1;
}
// load model
tinyobj::attrib_t attr;
std::vector< tinyobj::shape_t > shaps;
std::vector< tinyobj::material_t > mats;
std::string inputfile = "./bunny.obj";
std::string warn;
std::string err;
bool ret = tinyobj::LoadObj(&attr, &shaps, &mats, &warn, &err, inputfile.c_str());
if (!err.empty()) { // `err` may contain warning message.
std::cerr << err << std::endl;
}
if (!ret) {
exit(1);
}
// Set the required callback functions
glfwSetKeyCallback(window, key_callback);
glfwSetMouseButtonCallback(window, mouse_button_callback);
glfwSetScrollCallback(window, scroll_callback);
// glfwSetCursorPosCallback(window, cursor_position_callback);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize OpenGL context" << std::endl;
return -1;
}
// Define the viewport dimensions
glViewport(0, 0, WIDTH, HEIGHT);
// glEnable(GL_DEPTH_TEST);
// Lighting
glShadeModel(GL_SMOOTH);
glEnable(GL_DEPTH_TEST);
glEnable(GL_SMOOTH);
// glFrontFace(GL_CCW);
// glEnable(GL_CULL_FACE);
// glEnable(GL_DEPTH_TEST);
GLfloat dir_light_pos[] = { 1.0,-1.0,1.0,0.0 };
GLfloat pt_light_pos[] = { 1.0,1.0,10.0,1.0 };
GLfloat pt_La[] = { 0.1,0.1,0.1,1.0 };
GLfloat pt_Ld[] = { 0.7,0.7,0.7,1.0 };
GLfloat pt_Ls[] = { 0.7,0.7,0.7,1.0 };
GLfloat pt_ka[] = { 0.5,0.5,0.5,1.0 };
GLfloat pt_kd[] = { 0.5,0.5,0.5,1.0 };
GLfloat pt_ks[] = { 0.5,0.5,0.5,1.0 };
GLfloat pt_sh[] = { 20.0 };
glMaterialfv(GL_FRONT, GL_AMBIENT, pt_ka);
glMaterialfv(GL_FRONT, GL_DIFFUSE, pt_kd);
glMaterialfv(GL_FRONT, GL_SPECULAR, pt_ks);
glMaterialfv(GL_FRONT, GL_SHININESS, pt_sh);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT0);
glLightfv(GL_LIGHT0, GL_POSITION, pt_light_pos);
glLightfv(GL_LIGHT0, GL_AMBIENT, pt_La);
glLightfv(GL_LIGHT0, GL_DIFFUSE, pt_Ld);
glLightfv(GL_LIGHT0, GL_SPECULAR, pt_Ls);
glEnable(GL_LIGHTING);
glEnable(GL_LIGHT1);
glLightfv(GL_LIGHT1, GL_POSITION, dir_light_pos);
glLightfv(GL_LIGHT1, GL_AMBIENT, pt_La);
glLightfv(GL_LIGHT1, GL_DIFFUSE, pt_Ld);
glLightfv(GL_LIGHT1, GL_SPECULAR, pt_Ls);
glEnable(GL_NORMALIZE);
glEnable(GL_AUTO_NORMAL);
glShadeModel(GL_SMOOTH);
glEnable(GL_DEPTH_TEST);
glEnable(GL_SMOOTH);
glDepthFunc(GL_LEQUAL);
// glEnable(GL_DEPTH_TEST);
// Render loop
while (!glfwWindowShouldClose(window))
{
// glfwWaitEvents(); //waits for input
// Check if any events have been activated (key pressed, mouse moved etc.) and call corresponding response functions
glfwPollEvents();
if (mouseHold) {
double xpos, ypos;
float pixels[3];
glfwGetCursorPos(window, &xpos, &ypos);
xpos = (2 * xpos - WIDTH) / WIDTH;
ypos = (HEIGHT - 2 * ypos) / HEIGHT;
double zpos = sqrt(1 - xpos * xpos - ypos * ypos);
glm::vec3 pos_2 = glm::vec3(xpos, ypos, zpos);
const glm::mat4 R = glm::toMat4(RotationBetweenVectors(pos_1, pos_2));
cur_rotation = R * cur_rotation;
matView = glm::lookAt(lookAt_cur, glm::vec3(0, 0, 0), glm::vec3(0, 1, 0)) * cur_rotation;
pos_1 = pos_2;
}
// Clear the colorbuffer
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
// set projection matrix for this frame
glMatrixMode(GL_PROJECTION); // set projection matrix
// use either of following lines to set the value of projection matrix
glLoadMatrixf(glm::value_ptr(matProj)); // you should include glm/gtc/type_ptr.hpp for glm::value_ptr
// glLoadMatrixf(&matProj[0][0]); // you can use this also.
// set model view matrix for the model1
glm::mat4 modelView = matView * matModel;
glMatrixMode(GL_MODELVIEW);
glLoadMatrixf(glm::value_ptr(matView));
glBegin(GL_TRIANGLES);
for (size_t s = 0; s < shaps.size(); s++) {
// Loop over faces (polygon)
size_t index_offset = 0;
for (size_t i = 0; i < shaps[s].mesh.num_face_vertices.size(); i++) {
int num_f = shaps[s].mesh.num_face_vertices[i];
// Loop over vertices in the face
for (size_t j = 0; j < num_f; j++) {
// access to vertex
tinyobj::index_t idx = shaps[s].mesh.indices[index_offset + j];
tinyobj::real_t vx = attr.vertices[3 * idx.vertex_index + 0];
tinyobj::real_t vy = attr.vertices[3 * idx.vertex_index + 1];
tinyobj::real_t vz = attr.vertices[3 * idx.vertex_index + 2];
tinyobj::real_t nx = attr.normals[3 * idx.normal_index + 0];
tinyobj::real_t ny = attr.normals[3 * idx.normal_index + 1];
tinyobj::real_t nz = attr.normals[3 * idx.normal_index + 2];
glNormal3f(nx, ny, nz);
glVertex3f(vx, vy, vz);
}
index_offset += num_f;
}
}
glEnd();
// Swap the screen buffers
glfwSwapBuffers(window);
}
// Terminates GLFW, clearing any resources allocated by GLFW.
glfwTerminate();
return 0;
}
어렵다 어려워