[PA 3] Trackball Camera + Lighting 구현하기

Update:     Updated:

Category:

Tags:



Task Lists

  1. 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

  2. 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

image

image

image

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;
}


어렵다 어려워






맨 위로 이동하기