Wednesday, December 4, 2013

Sponza рэндэрлэх оролдлого 8, spot light, blinn-phong

spot light хэрэгжүүлэлт






Vertex Shader
#version 330
layout(location=0) in vec4 in_position; // model-space
layout(location=1) in vec3 in_normal;   // model-space
layout(location=2) in vec2 in_texcoord;

out vec2 vTexcoord;
out vec3 vPosition; // world-space
out vec3 vNormal;   // view-space
out vec3 vEyeDir;   // view-space
out vec3 vLightDir; // view-space

uniform mat4 mvpMatrix;
uniform mat4 modelMatrix;
uniform mat4 viewMatrix;
uniform mat3 normalMatrix;

uniform vec3 lightPosition; // world-space

void main(void)
{
    gl_Position = mvpMatrix * in_position;
    
    vTexcoord = in_texcoord;
    
    vPosition = (modelMatrix * in_position).xyz;
    
    vNormal = (normalMatrix * in_normal).xyz;
    //vNormal = (viewMatrix * modelMatrix * vec4(in_normal, 0.0)).xyz;

    vec3 posInEyespace = (viewMatrix * modelMatrix * in_position).xyz;
    vEyeDir = vec3(0,0,0) - posInEyespace;

    vec3 lightPosInEyespace = (viewMatrix * vec4(lightPosition, 1.0)).xyz;
    vLightDir = lightPosInEyespace + vEyeDir;

    
}

Fragment Shader
#version 330
uniform sampler2D material_diffuse_texture;
uniform sampler2D material_normal_texture;
uniform sampler2D material_ambient_texture;
uniform sampler2D material_specular_texture;
uniform sampler2D material_opacity_texture;
uniform float     material_shininess; 
uniform vec4      material_diffuse_color;

uniform int hasOpacityMap;
uniform int hasAmbientMap;

uniform float ambientIntensity;

uniform vec3 lightPosition; // world-space
uniform vec3 spotDir;

in vec2 vTexcoord;
in vec3 vPosition; // world-space
in vec3 vNormal;   // view-space
in vec3 vEyeDir;   // view-space
in vec3 vLightDir; // view-space

out vec4 out_color;

void main(void)
{
    vec3 lightColor = vec3(1.0, 1.0, 1.0);

    vec4 diffuseColor  = texture2D(material_diffuse_texture, vTexcoord);
    vec4 ambientColor  = texture2D(material_ambient_texture, vTexcoord);
    vec4 normalColor   = vec4(texture2D(material_normal_texture , vTexcoord).xyz, 1.0);
    vec4 specularColor = texture2D(material_specular_texture, vTexcoord);
    vec4 opacityColor  = texture2D(material_opacity_texture, vTexcoord);

    if (hasAmbientMap==0) {
        ambientColor = diffuseColor;
    }

    vec4 resAmbientColor  = vec4(0, 0, 0, 0);
    vec4 resDiffuseColor  = vec4(0, 0, 0, 0);
    vec4 resSpecularColor = vec4(0, 0, 0, 0);
    vec4 resultColor      = vec4(0, 0, 0, 0);

    // ambient calculation
    float ambientFraction = 0.05;
    resAmbientColor = vec4((ambientFraction * lightColor * ambientColor.xyz).xyz, 1.0);
    
    vec3 normal = normalize(vNormal);
    vec3 lightDirection = normalize(vLightDir);

    // diffuse calculation, lambertian cosinus
    float lambertcos = max(0.0, dot(normal, lightDirection));
    resDiffuseColor = vec4((lambertcos * lightColor * diffuseColor.xyz).xyz, 1.0);

    // specular calculation, blinn-phong
    float lightDistance = length(lightPosition-vPosition);
    vec3 halfVector = normalize(normalize(vEyeDir) + lightDirection);
    // Blinn-Phong needs shininiess about 4 * Phong shininess
    float specularcoeff = pow(max(0.0, dot(normal, halfVector)), 4.0 * material_shininess); 
    resSpecularColor = vec4((specularcoeff * lightColor * specularColor.xyz).xyz, 1.0);
    
    // spot light computation
    // Cutoff angles
    float cosInner = 0.98 - 1.0 * 0.1;
    float cosOuter = cosInner - 0.03;
    float cosFromSpot = dot(lightDirection, normalize(spotDir));
    float spotamt;
    if (cosFromSpot > cosInner)
        spotamt = 1.0;
    else if (cosFromSpot < cosOuter)
        spotamt = 0.0;
    else
        spotamt = (cosFromSpot - cosOuter) / (cosInner - cosOuter);

    resultColor = clamp(resAmbientColor + spotamt*(resDiffuseColor + resSpecularColor), 0.0, 1.0);
    
    
    if (hasOpacityMap!=0){
        if (opacityColor.rgb==vec3(0,0,0))
            discard;
        else
            out_color = resultColor;
    } else {
        out_color = resultColor;
    }
    
}


Main.cpp
#include <cstdlib>
#include <string>
#include <iostream>
#include <assimp/Importer.hpp>
#include <assimp/scene.h>
#include <assimp/postprocess.h>
#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/matrix_inverse.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtx/constants.hpp>
#include <math.h>
#include <algorithm>
#include "MaterialManager.hpp"
#include "Material.hpp"
#include "ShaderManager.hpp"
#include "Shader.hpp"
#include "SceneObject.hpp"

using namespace std;


GLFWwindow *window;
int windowWidth = 1000;
int windowHeight = 800;

bool wKeyPressed;
bool sKeyPressed;
bool aKeyPressed;
bool dKeyPressed;
double mouseX, mouseY;
float speed = 150.09f;
float mouseSpeed = 0.04f;
glm::vec3 position = glm::vec3(0, 0, 5);
glm::vec3 direction;
//glm::vec3 right;
glm::vec3 up;
float horizontalAngle = 3.14159f;
float verticalAngle = 0.0f;
float fov = 60.0f;

glm::mat4 modelMatrix;
glm::mat4 viewMatrix;
glm::mat4 projectionMatrix;

void initWindow();
void disposeWindow();
void handleKeyboardInput(GLFWwindow *window, int key, int scancode, int action, int mods);
void processInput(float dt);

void processMaterial(aiMaterial* material, int index);
void processMesh(aiMesh* mesh, SceneObject *object);

/*
 * experiment with ASSIMP
 */
int main(int argc, char** argv) {
    initWindow();
    glfwSetKeyCallback(window, handleKeyboardInput);
    MaterialManager::getSingleton();
    ShaderManager* sm = ShaderManager::getSingleton();
    sm->CreateShaderProgram("phong");
    sm->AttachShader("phongVertex", VERTEX);
    sm->AttachShader("phongFragment", FRAGMENT);
    sm->LoadShaderSource("phongVertex", "shaders/phong.vert.glsl");
    sm->LoadShaderSource("phongFragment", "shaders/phong.frag.glsl");
    sm->CompileShader("phongVertex");
    sm->CompileShader("phongFragment");
    sm->AttachShaderToProgram("phong", "phongVertex");
    sm->AttachShaderToProgram("phong", "phongFragment");
    sm->LinkProgramObject("phong");
    std::cout << "ShaderProgram ID is " << (*sm)["phong"]->GetID() << std::endl;

    glEnable(GL_DEPTH_TEST);
    glDepthFunc(GL_LESS);
    glEnable(GL_CULL_FACE);
    glCullFace(GL_BACK);
    glFrontFace(GL_CCW);
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_ALPHA_TEST);
    glEnable(GL_BLEND);
    glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

    SceneObject *sceneObject = new SceneObject();
    std::string filePath = "sponza.obj";
    Assimp::Importer importer;
    const aiScene *scene = importer.ReadFile(
            filePath.c_str(),
            aiProcess_OptimizeGraph |
            aiProcess_OptimizeMeshes |
            aiProcess_ImproveCacheLocality |
            aiProcess_SplitLargeMeshes |
            aiProcess_Triangulate |
            aiProcess_JoinIdenticalVertices |
            aiProcess_SortByPType
            );
    if (!scene) {
        std::cout << "error in loading file : " << importer.GetErrorString() << std::endl;
        return 1;
    }
    for (unsigned int i = 0; i < scene->mNumMaterials; i++) {
        processMaterial(scene->mMaterials[i], i);
    }
    for (unsigned int i = 0; i < scene->mNumMeshes; i++) {
        processMesh(scene->mMeshes[i], sceneObject);
    }

    SceneObject *vehicleObject = new SceneObject();
    filePath = "phoenix_ugv.md2";
    scene = importer.ReadFile(
            filePath.c_str(),
            aiProcess_OptimizeGraph |
            aiProcess_OptimizeMeshes |
            aiProcess_ImproveCacheLocality |
            aiProcess_SplitLargeMeshes |
            aiProcess_Triangulate |
            aiProcess_JoinIdenticalVertices |
            aiProcess_SortByPType
            );
    if (!scene) {
        std::cout << "error in loading file : " << importer.GetErrorString() << std::endl;
        return 1;
    }
    for (unsigned int i = 0; i < scene->mNumMaterials; i++) {
        processMaterial(scene->mMaterials[i], i);
    }
    for (unsigned int i = 0; i < scene->mNumMeshes; i++) {
        processMesh(scene->mMeshes[i], vehicleObject);
    }

    projectionMatrix = glm::mat4(1.0f);
    viewMatrix = glm::mat4(1.0f);
    viewMatrix = glm::translate(viewMatrix, glm::vec3(0, 0, -2));
    glViewport(0, 0, (int) windowWidth, (int) windowHeight);
    projectionMatrix = glm::perspective(60.0f, (float) windowWidth / windowHeight, 1.0f, 10000.0f);
    glm::mat4 mvpMatrix;
    glm::mat3 normalMatrix;
    
    float ambientLightIntensity = 0.2;

    GLuint programID = (*sm)["phong"]->GetID();
    float deltaTime = 0.0f;
    float startTime = glfwGetTime();
    float buffTime = 0.0;
    int counter = 0;
    float animationTime = 0.0f;
    while (!glfwWindowShouldClose(window)) {
        processInput(deltaTime);
        glClearColor(0.0f, 0.5f, 1.0f, 0.0f);
        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

        glm::vec3 lightPosition = glm::vec3(0, 40, 40);
        glm::vec3 spotLightDirection = glm::vec3(360*sin(animationTime), 360*cos(animationTime), 360*cos(animationTime));
        
        glUseProgram(programID);
        glm::mat4 modelMatrix = sceneObject->getModelMatrix();
        mvpMatrix = projectionMatrix * viewMatrix * modelMatrix;
        normalMatrix = glm::inverseTranspose(glm::mat3(viewMatrix * modelMatrix));
        glUniformMatrix4fv(glGetUniformLocation(programID, "mvpMatrix"), 1, GL_FALSE, glm::value_ptr(mvpMatrix));
        glUniformMatrix4fv(glGetUniformLocation(programID, "modelMatrix"), 1, GL_FALSE, glm::value_ptr(modelMatrix));
        glUniformMatrix4fv(glGetUniformLocation(programID, "viewMatrix"), 1, GL_FALSE, glm::value_ptr(viewMatrix));
        glUniformMatrix3fv(glGetUniformLocation(programID, "normalMatrix"), 1, GL_FALSE, glm::value_ptr(normalMatrix));
        glUniform3f(glGetUniformLocation(programID, "lightPosition"), lightPosition.x, lightPosition.y, lightPosition.z);
        glUniform3f(glGetUniformLocation(programID, "spotDir"), spotLightDirection.x, spotLightDirection.y, spotLightDirection.z);
        glUniform1f(glGetUniformLocation(programID, "ambientIntensity"), ambientLightIntensity);
        sceneObject->render(programID);
        glUseProgram(0);

        glUseProgram(programID);
        static float rotationAngle = 0.0f;
        rotationAngle += 5 * deltaTime;
        modelMatrix = vehicleObject->getModelMatrix();
        modelMatrix = glm::rotate(modelMatrix, rotationAngle, glm::vec3(0, 1, 0));
        mvpMatrix = projectionMatrix * viewMatrix * modelMatrix;
        normalMatrix = glm::inverseTranspose(glm::mat3(viewMatrix * modelMatrix));
        glUniformMatrix4fv(glGetUniformLocation(programID, "mvpMatrix"), 1, GL_FALSE, glm::value_ptr(mvpMatrix));
        glUniformMatrix4fv(glGetUniformLocation(programID, "modelMatrix"), 1, GL_FALSE, glm::value_ptr(modelMatrix));
        glUniformMatrix4fv(glGetUniformLocation(programID, "viewMatrix"), 1, GL_FALSE, glm::value_ptr(viewMatrix));
        glUniformMatrix3fv(glGetUniformLocation(programID, "normalMatrix"), 1, GL_FALSE, glm::value_ptr(normalMatrix));
        glUniform3f(glGetUniformLocation(programID, "lightPosition"), lightPosition.x, lightPosition.y, lightPosition.z);
        glUniform3f(glGetUniformLocation(programID, "spotDir"), spotLightDirection.x, spotLightDirection.y, spotLightDirection.z);
        glUniform1f(glGetUniformLocation(programID, "ambientIntensity"), ambientLightIntensity);
        vehicleObject->render(programID);
        glUseProgram(0);

        glfwSwapBuffers(window);
        glfwPollEvents();
        double lastTime = glfwGetTime();
        deltaTime = lastTime - startTime;
        startTime = lastTime;
        counter++;
        buffTime += deltaTime;
        if (buffTime > 1.0f) {
            buffTime = 0.0f;
            //cout << "fps " << counter << endl;
            counter = 0;
        }
        animationTime += deltaTime;
    }

    delete sceneObject;
    delete vehicleObject;
    delete MaterialManager::getSingleton();
    delete ShaderManager::getSingleton();
    disposeWindow();
    return 0;
}

void processMesh(aiMesh* mesh, SceneObject *object) {
    float *normals, *textureCoordinates;
    float *vertices = (float *) malloc(4 * mesh->mNumVertices * sizeof (float));
    if (mesh->HasNormals())
        normals = (float *) malloc(3 * mesh->mNumVertices * sizeof (float));
    if (mesh->HasTextureCoords(0))
        textureCoordinates = (float *) malloc(2 * mesh->mNumVertices * sizeof (float));
    for (unsigned int i = 0; i < mesh->mNumVertices; i++) {
        vertices[4 * i + 0] = mesh->mVertices[i].x;
        vertices[4 * i + 1] = mesh->mVertices[i].y;
        vertices[4 * i + 2] = mesh->mVertices[i].z;
        vertices[4 * i + 3] = 1.0f;
        if (mesh->HasNormals()) {
            normals[3 * i + 0] = mesh->mNormals[i].x;
            normals[3 * i + 1] = mesh->mNormals[i].y;
            normals[3 * i + 2] = mesh->mNormals[i].z;
        }
        if (mesh->HasTextureCoords(0)) {
            textureCoordinates[2 * i + 0] = mesh->mTextureCoords[0][i].x;
            textureCoordinates[2 * i + 1] = mesh->mTextureCoords[0][i].y;
        }
    }
    int *faces = (int *) malloc(3 * mesh->mNumFaces * sizeof (int));
    for (unsigned int i = 0; i < mesh->mNumFaces; i++) {
        faces[3 * i + 0] = mesh->mFaces[i].mIndices[0];
        faces[3 * i + 1] = mesh->mFaces[i].mIndices[1];
        faces[3 * i + 2] = mesh->mFaces[i].mIndices[2];
    }
    object->addMesh(vertices, mesh->mNumVertices, normals, mesh->mNumVertices, textureCoordinates, mesh->mNumVertices, faces, mesh->mNumFaces, mesh->mMaterialIndex);
}

void processMaterial(aiMaterial* material, int index) {
    MaterialManager *mm = MaterialManager::getSingleton();
    std::cout << "================ ID " << index << "====================" << std::endl;

    aiString materialName;

    // Get the name for managing purpose
    if (material->Get(AI_MATKEY_NAME, materialName) != AI_SUCCESS) {
        std::cout << "ERROR: while getting name of material" << std::endl;
    }

    aiString diffuseTexturePath;
    aiString normalMapTexturePath;
    aiString specularTexturePath;
    aiString ambientTexturePath;
    aiString opacityTexturePath;
    aiColor3D diffuseColor, ambientColor, specularColor, emissiveColor;
    float opacity, shininess;

    // Get diffuse color
    if (material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuseColor) != AI_SUCCESS) {
        std::cout << "ERROR: Could not get diffuse color for Material " << materialName.data << std::endl;
    }

    // Get ambient color
    if (material->Get(AI_MATKEY_COLOR_AMBIENT, ambientColor) != AI_SUCCESS) {
        std::cout << "ERROR: Could not get ambient color for Material " << materialName.data << std::endl;
    }

    // Get diffuse color
    if (material->Get(AI_MATKEY_COLOR_SPECULAR, specularColor) != AI_SUCCESS) {
        std::cout << "ERROR: Could not get specular color for Material " << materialName.data << std::endl;
    }

    // Get opacity
    if (material->Get(AI_MATKEY_OPACITY, opacity) != AI_SUCCESS) {
        std::cout << "ERROR: Could not get opacity for Material " << materialName.data << std::endl;
    }

    // Get emissive color
    if (material->Get(AI_MATKEY_COLOR_EMISSIVE, emissiveColor) != AI_SUCCESS)
        emissiveColor = aiColor3D(0);

    // Get shininess
    if (material->Get(AI_MATKEY_SHININESS, shininess) != AI_SUCCESS) {
        std::cout << "ERROR: Could not get Shininess for Material " << materialName.data << std::endl;
    }


    // material color parameters
    std::cout << "diffuse color [" << diffuseColor.r << "," << diffuseColor.g << "," << diffuseColor.b << "] opacity=" << opacity << std::endl;
    std::cout << "ambient color [" << ambientColor.r << "," << ambientColor.g << "," << ambientColor.b << "]" << std::endl;
    std::cout << "specular color [" << specularColor.r << "," << specularColor.g << "," << specularColor.b << "]" << std::endl;
    std::cout << "emissive color [" << emissiveColor.r << "," << emissiveColor.g << "," << emissiveColor.b << "]" << std::endl;


    Material *m = mm->createMaterial(std::string(materialName.data), index);
    m->setDiffuseColor(glm::vec4(diffuseColor.r, diffuseColor.g, diffuseColor.b, opacity));
    m->setAmbientColor(glm::vec3(ambientColor.r, ambientColor.g, ambientColor.b));
    m->setSpecularColor(glm::vec3(specularColor.r, specularColor.g, specularColor.b));
    m->setEmissiveColor(glm::vec3(emissiveColor.r, emissiveColor.g, emissiveColor.b));

    // diffuse map
    if (material->GetTextureCount(aiTextureType_DIFFUSE) > 0) {
        if (material->Get(AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0), diffuseTexturePath) != AI_SUCCESS) {
            std::cout << "ERROR: Could not get Diffuse Texture for Material " << materialName.data << std::endl;
            return;
        }
        m->hasDiffuseTexture = true;
        m->loadDiffuseTexture(diffuseTexturePath.data);
        std::cout << "diffuse texture path " << diffuseTexturePath.data << std::endl;
    }

    // normal map
    if (material->GetTextureCount(aiTextureType_HEIGHT) > 0) {
        if (material->Get(AI_MATKEY_TEXTURE(aiTextureType_HEIGHT, 0), normalMapTexturePath) != AI_SUCCESS) {
            std::cout << "ERROR: Could not get Height Map Texture for Material " << materialName.data << std::endl;
            return;
        }
        m->hasNormalTexture = true;
        m->loadNormalTexture(normalMapTexturePath.data);
        std::cout << "height map texture path " << normalMapTexturePath.data << std::endl;
    }

    // specular map
    if (material->GetTextureCount(aiTextureType_SPECULAR) > 0) {
        if (material->Get(AI_MATKEY_TEXTURE(aiTextureType_SPECULAR, 0), specularTexturePath) != AI_SUCCESS) {
            std::cout << "ERROR: Could not get Mask Texture for Material " << materialName.data << std::endl;
            return;
        }
        m->hasSpecularTexture = true;
        m->loadSpecularTexture(specularTexturePath.data);
        std::cout << "specular texture path " << specularTexturePath.data << std::endl;
    } else {
        std::cout << "there is no specular texture at ID=" << index << std::endl;
    }

    // ambient map
    if (material->GetTextureCount(aiTextureType_AMBIENT) > 0) {
        if (material->Get(AI_MATKEY_TEXTURE(aiTextureType_AMBIENT, 0), ambientTexturePath) != AI_SUCCESS) {
            std::cout << "ERROR: Could not get Ambient Texture for Material " << materialName.data << std::endl;
            return;
        }
        m->hasAmbientTexture = true;
        m->loadAmbientTexture(ambientTexturePath.data);
        std::cout << "ambient texture path " << ambientTexturePath.data << std::endl;
    } else {
        std::cout << ":( THERE IS NO AMBIENT MAP" << std::endl;
    }
    // opacity map
    if (material->GetTextureCount(aiTextureType_OPACITY) > 0) {
        if (material->Get(AI_MATKEY_TEXTURE(aiTextureType_OPACITY, 0), opacityTexturePath) != AI_SUCCESS) {
            std::cout << "ERROR: Couldn't get opacity texture for material" << materialName.data << std::endl;
            return;
        }
        m->hasOpacityTexture = true;
        m->loadOpacityTexture(opacityTexturePath.data);
        std::cout << "opacity texture path" << opacityTexturePath.data << std::endl;
    } else {
        std::cout << ":( THERE IS NO OPACITY MAP" << std::endl;
    }
    std::cout << "==========================================" << std::endl;
}

void initWindow() {
    if (!glfwInit()) {
        std::cout << "Error: GLFW failed to initialize.\n";
        return;
    }

    window = glfwCreateWindow(windowWidth, windowHeight, "OpenGL ASSIMP", NULL, NULL);
    if (!window) {
        std::cout << "Error: Failed to create window.\n";
        glfwTerminate();
        return;
    }
    glfwMakeContextCurrent(window);

    if (GLEW_OK != glewInit()) {
        std::cout << "Error: Failed to load OpenGL functions.\n";
        glfwTerminate();
        return;
    }
    glfwSetInputMode(window, GLFW_CURSOR, GLFW_CURSOR_HIDDEN);
}

void disposeWindow() {
    glfwDestroyWindow(window);
    glfwTerminate();
}

void handleKeyboardInput(GLFWwindow *window, int key, int scancode, int action, int mods) {
    if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
        glfwSetWindowShouldClose(window, 1);
    else if (key == GLFW_KEY_W && action == GLFW_PRESS)
        wKeyPressed = true;
    else if (key == GLFW_KEY_S && action == GLFW_PRESS)
        sKeyPressed = true;
    else if (key == GLFW_KEY_A && action == GLFW_PRESS)
        aKeyPressed = true;
    else if (key == GLFW_KEY_D && action == GLFW_PRESS)
        dKeyPressed = true;
    else if (key == GLFW_KEY_W && action == GLFW_RELEASE)
        wKeyPressed = false;
    else if (key == GLFW_KEY_S && action == GLFW_RELEASE)
        sKeyPressed = false;
    else if (key == GLFW_KEY_A && action == GLFW_RELEASE)
        aKeyPressed = false;
    else if (key == GLFW_KEY_D && action == GLFW_RELEASE)
        dKeyPressed = false;
}

void processInput(float dt) {
    static glm::vec3 right;
    if (wKeyPressed)
        position += direction * speed * dt;
    if (sKeyPressed)
        position -= direction * speed * dt;
    if (aKeyPressed)
        position -= right * speed * dt;
    if (dKeyPressed)
        position += right * speed * dt;

    direction = glm::vec3
            (
            std::cos(verticalAngle) * std::sin(horizontalAngle),
            std::sin(verticalAngle),
            std::cos(verticalAngle) * std::cos(horizontalAngle)
            );
    right = glm::vec3
            (
            std::sin(horizontalAngle - 3.14159f / 2.0f),
            0.0f,
            std::cos(horizontalAngle - 3.14159f / 2.0f)
            );
    up = glm::cross(right, direction);

    glfwGetCursorPos(window, &mouseX, &mouseY);
    glfwSetCursorPos(window, windowWidth / 2, windowHeight / 2);
    horizontalAngle += mouseSpeed * dt * float(windowWidth / 2 - mouseX);
    if (!up.y >= -0.5f)
        verticalAngle += mouseSpeed * dt * float(windowHeight / 2 - mouseY);
    else if (up.y == -1.0f)
        up = glm::vec3(up.x, 1, up.z);

    viewMatrix = glm::lookAt(position, position + direction, up);
}

Material.hpp
#ifndef MATERIAL_HPP
#define MATERIAL_HPP

#include <GL/glew.h>
#include <glm/glm.hpp>
#include <string>

class Material {
public:
    Material();
    ~Material();

    glm::vec4 getDiffuseColor() {
        return this->diffuseColor;
    };

    void setDiffuseColor(glm::vec4 color) {
        this->diffuseColor = color;
    }

    glm::vec3 getAmbientColor() {
        return this->ambientColor;
    }

    void setAmbientColor(glm::vec3 color) {
        this->ambientColor = color;
    }

    glm::vec3 getSpecularColor() {
        return this->specularColor;
    }

    void setSpecularColor(glm::vec3 color) {
        this->specularColor = color;
    }

    glm::vec3 getEmissiveColor() {
        return this->emissiveColor;
    }

    void setEmissiveColor(glm::vec3 color) {
        this->emissiveColor = color;
    }

    float getShininess() {
        return this->shininess;
    }

    void setShininess(float shininess) {
        this->shininess = shininess;
    }

    GLuint getDiffuseTextureID() {
        return this->diffuseTextureID;
    }

    void setDiffuseTextureID(GLuint diffuseTextureID) {
        this->diffuseTextureID = diffuseTextureID;
    }

    GLuint getNormalTextureID() {
        return this->normalTextureID;
    }

    void setNormalTextureID(GLuint normalTextureID) {
        this->normalTextureID = normalTextureID;
    }

    GLuint getSpecularTextureID() {
        return this->specularTextureID;
    }

    void setSpecularTextureID(GLuint specularTextureID) {
        this->specularTextureID = specularTextureID;
    }

    GLuint getAmbientTextureID() {
        return this->ambientTextureID;
    }

    void setAmbientTextureID(GLuint ambientTextureID) {
        this->ambientTextureID = ambientTextureID;
    }

    GLuint getOpacityTextureID() {
        return this->opacityTextureID;
    }

    void setOpacityTextureID(GLuint opacityTextureID) {
        this->opacityTextureID = opacityTextureID;
    }

    void loadDiffuseTexture(const std::string &path);
    void loadNormalTexture(const std::string &path);
    void loadSpecularTexture(const std::string &path);
    void loadAmbientTexture(const std::string &path);
    void loadOpacityTexture(const std::string &path);

    void bindMaterial(GLuint programID);
    void unbindMaterial();

    bool hasDiffuseTexture;
    bool hasNormalTexture;
    bool hasSpecularTexture;
    bool hasAmbientTexture;
    bool hasOpacityTexture;
    
private:
    glm::vec4 diffuseColor;
    glm::vec3 ambientColor;
    glm::vec3 specularColor;
    glm::vec3 emissiveColor;
    float shininess;
    GLuint diffuseTextureID;
    GLuint normalTextureID;
    GLuint specularTextureID;
    GLuint ambientTextureID;
    GLuint opacityTextureID;
    
};

#endif /* MATERIAL_HPP */

Material.cpp
#include "Material.hpp"
#include "SOIL.h"
#include <glm/gtc/type_ptr.hpp>
#include <iostream>

Material::Material() {
    this->diffuseColor = glm::vec4(0.5, 0.5, 0.5, 1.0); // Medium-gray
    this->ambientColor = glm::vec3(0.5); // Medium-gray 
    this->specularColor = glm::vec3(0.0); // No specular
    this->emissiveColor = glm::vec3(0.0); // No emission
    this->shininess = 0.5;
    this->hasAmbientTexture  = false;
    this->hasDiffuseTexture  = false;
    this->hasNormalTexture   = false;
    this->hasOpacityTexture  = false;
    this->hasSpecularTexture = false;
}

Material::~Material() {
    glDeleteTextures(1, &this->diffuseTextureID);
    glDeleteTextures(1, &this->normalTextureID);
    glDeleteTextures(1, &this->specularTextureID);
    glDeleteTextures(1, &this->ambientTextureID);
    glDeleteTextures(1, &this->opacityTextureID);
    std::cout << "Material destructor function" << std::endl;
}

void Material::loadDiffuseTexture(const std::string& path) {
    this->diffuseTextureID = SOIL_load_OGL_texture(
            path.c_str(),
            SOIL_LOAD_AUTO,
            SOIL_CREATE_NEW_ID,
            SOIL_FLAG_POWER_OF_TWO
            | SOIL_FLAG_MIPMAPS
            | SOIL_FLAG_MULTIPLY_ALPHA
            | SOIL_FLAG_COMPRESS_TO_DXT
            | SOIL_FLAG_DDS_LOAD_DIRECT
            | SOIL_FLAG_INVERT_Y
            );
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);

    glBindTexture(GL_TEXTURE_2D, 0);
}

void Material::loadNormalTexture(const std::string& path) {
    this->normalTextureID = SOIL_load_OGL_texture(
            path.c_str(),
            SOIL_LOAD_AUTO,
            SOIL_CREATE_NEW_ID,
            SOIL_FLAG_POWER_OF_TWO
            | SOIL_FLAG_MIPMAPS
            | SOIL_FLAG_MULTIPLY_ALPHA
            | SOIL_FLAG_COMPRESS_TO_DXT
            | SOIL_FLAG_DDS_LOAD_DIRECT
            | SOIL_FLAG_INVERT_Y
            );
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glBindTexture(GL_TEXTURE_2D, 0);
}

void Material::loadSpecularTexture(const std::string& path) {
    this->specularTextureID = SOIL_load_OGL_texture(
            path.c_str(),
            SOIL_LOAD_AUTO,
            SOIL_CREATE_NEW_ID,
            SOIL_FLAG_POWER_OF_TWO
            | SOIL_FLAG_MIPMAPS
            | SOIL_FLAG_MULTIPLY_ALPHA
            | SOIL_FLAG_COMPRESS_TO_DXT
            | SOIL_FLAG_DDS_LOAD_DIRECT
            | SOIL_FLAG_INVERT_Y
            );
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glBindTexture(GL_TEXTURE_2D, 0);
}

void Material::loadAmbientTexture(const std::string& path) {
    this->ambientTextureID = SOIL_load_OGL_texture(
            path.c_str(),
            SOIL_LOAD_AUTO,
            SOIL_CREATE_NEW_ID,
            SOIL_FLAG_POWER_OF_TWO
            | SOIL_FLAG_MIPMAPS
            | SOIL_FLAG_MULTIPLY_ALPHA
            | SOIL_FLAG_COMPRESS_TO_DXT
            | SOIL_FLAG_DDS_LOAD_DIRECT
            | SOIL_FLAG_INVERT_Y
            );
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glBindTexture(GL_TEXTURE_2D, 0);
}

void Material::loadOpacityTexture(const std::string& path) {
    this->opacityTextureID = SOIL_load_OGL_texture(
            path.c_str(),
            SOIL_LOAD_AUTO,
            SOIL_CREATE_NEW_ID,
            SOIL_FLAG_POWER_OF_TWO
            | SOIL_FLAG_MIPMAPS
            | SOIL_FLAG_MULTIPLY_ALPHA
            | SOIL_FLAG_COMPRESS_TO_DXT
            | SOIL_FLAG_DDS_LOAD_DIRECT
            | SOIL_FLAG_INVERT_Y
            );
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
    glBindTexture(GL_TEXTURE_2D, 0);
}

void Material::bindMaterial(GLuint programID) {
    GLuint diffuseTextureUniformID = glGetUniformLocation(programID, "material_diffuse_texture");
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, this->diffuseTextureID);
    glUniform1i(diffuseTextureUniformID, 0);

    GLuint normalTextureUniformID = glGetUniformLocation(programID, "material_normal_texture");
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, this->normalTextureID);
    glUniform1i(normalTextureUniformID, 1);

    GLuint specularTextureUniformID = glGetUniformLocation(programID, "material_specular_texture");
    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, this->specularTextureID);
    glUniform1i(specularTextureUniformID, 2);

    GLuint ambientTextureUniformID = glGetUniformLocation(programID, "material_ambient_texture");
    glActiveTexture(GL_TEXTURE3);
    glBindTexture(GL_TEXTURE_2D, this->ambientTextureID);
    glUniform1i(ambientTextureUniformID, 3);
    
    GLuint opacityTextureUniformID = glGetUniformLocation(programID, "material_opacity_texture");
    glActiveTexture(GL_TEXTURE4);
    glBindTexture(GL_TEXTURE_2D, this->opacityTextureID);
    glUniform1i(opacityTextureUniformID, 4);

    GLuint shininessUniformID = glGetUniformLocation(programID, "material_shininess");
    glUniform1f(shininessUniformID, this->shininess);

    GLuint diffuseColorUniformID = glGetUniformLocation(programID, "material_diffuse_color");
    glUniform4fv(diffuseColorUniformID, 1, glm::value_ptr(this->diffuseColor));
    
    GLuint hasOpacityMapUniformID = glGetUniformLocation(programID, "hasOpacityMap");
    if (this->hasOpacityTexture) {
        glUniform1i(hasOpacityMapUniformID, 1);
    } else {
        glUniform1i(hasOpacityMapUniformID, 0);
    }
    GLuint hasAmbientMapUniformID = glGetUniformLocation(programID, "hasAmbientMap");
    if (this->hasAmbientTexture) {
        glUniform1i(hasAmbientMapUniformID, 1);
    } else {
        glUniform1i(hasAmbientMapUniformID, 0);
    }
}

void Material::unbindMaterial() {
    glActiveTexture(GL_TEXTURE0);
    glBindTexture(GL_TEXTURE_2D, 0);
    glActiveTexture(GL_TEXTURE1);
    glBindTexture(GL_TEXTURE_2D, 0);
    glActiveTexture(GL_TEXTURE2);
    glBindTexture(GL_TEXTURE_2D, 0);
    glActiveTexture(GL_TEXTURE3);
    glBindTexture(GL_TEXTURE_2D, 0);
    glActiveTexture(GL_TEXTURE4);
    glBindTexture(GL_TEXTURE_2D, 0);
}


SceneObject.hpp
#ifndef SCENEOBJECT_HPP
#define SCENEOBJECT_HPP

#include <GL/glew.h>
#include <glm/glm.hpp>
#include <string>
#include <vector>

typedef struct {
    GLuint vertexID;
    GLuint normalID;
    GLuint texcoordID;
    GLuint vaoID;
    int numFaces;
    int *faces;
    int material;
    glm::vec3 boundingBoxMin;
    glm::vec3 boundingBoxMax;
} Mesh;

class SceneObject {
public:
    SceneObject();
    virtual ~SceneObject();
    void render(GLuint programID);
    void update(float dt);
    void addMesh(float *vertices, int numVertices, float *normals, int numNormals, float *textureCoordinates, int numTextureCoordinates, int *faces, int numFaces, int materialIndex);
    void addMesh(float *vertices, int numVertices, float *normals, int numNormals, float *textureCoordinates, int numTextureCoordinates, int *faces, int numFaces, const std::string &materialName);
    glm::vec3 getBoundingBoxMin();
    glm::vec3 getBoundingBoxMax();
    
    glm::mat4 getModelMatrix();
protected:
    std::vector<Mesh> meshes;
    glm::mat4 modelMatrix;
    glm::mat4 parentMatrix;
    glm::vec3 boundingBoxMin;
    glm::vec3 boundingBoxMax;
};

#endif /* SCENEOBJECT_HPP */


SceneObject.cpp
#include "SceneObject.hpp"
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtc/matrix_inverse.hpp>
#include "MaterialManager.hpp"
#include "ShaderManager.hpp"

SceneObject::SceneObject() {
    this->modelMatrix = glm::mat4(1.0f);
    this->boundingBoxMin = glm::vec3(FLT_MAX);
    this->boundingBoxMax = glm::vec3(FLT_MIN);
}

SceneObject::~SceneObject() {
    std::cout << "desctructor function of the SceneObject" << std::endl;
    for (std::vector<Mesh>::iterator it = this->meshes.begin(); it != this->meshes.end(); it++) {
        Mesh mesh = (*it);
        glDeleteBuffers(1, &mesh.vertexID);
        glDeleteBuffers(1, &mesh.normalID);
        glDeleteBuffers(1, &mesh.texcoordID);
        glDeleteVertexArrays(1, &mesh.vaoID);
    }
}

void SceneObject::addMesh(float *vertices, int numVertices, float *normals, int numNormals, float *textureCoordinates, int numTextureCoordinates, int *faces, int numFaces, const std::string &materialName) {
    MaterialManager *mm = MaterialManager::getSingleton();
    int idx = mm->getMaterialIndex(materialName);

    this->addMesh(vertices, numVertices, normals, numNormals, textureCoordinates, numTextureCoordinates, faces, numFaces, idx);
}

void SceneObject::addMesh(float *vertices, int numVertices, float *normals, int numNormals, float *textureCoordinates, int numTextureCoordinates, int *faces, int numFaces, int materialIndex) {
    MaterialManager* mm = MaterialManager::getSingleton();
    GLuint programID = (*ShaderManager::getSingleton())["phong"]->GetID();

    GLuint positionAttrib = glGetAttribLocation(programID, "in_position");
    GLuint normalAttrib = glGetAttribLocation(programID, "in_normal");
    GLuint texcoordAttrib = glGetAttribLocation(programID, "in_texcoord");

    Mesh m;

    m.material = materialIndex;
    m.boundingBoxMin = glm::vec3(FLT_MAX);
    m.boundingBoxMax = glm::vec3(FLT_MIN);

    Material *mat = mm->getMaterial(materialIndex);

    glGenVertexArrays(1, &m.vaoID);
    glBindVertexArray(m.vaoID);

    if (numVertices > 0) {
        for (int i = 0; i < numVertices; i++) {
            if (vertices[4 * i + 0] < this->boundingBoxMin.x)
                this->boundingBoxMin.x = vertices[4 * i + 0];
            if (vertices[4 * i + 0] > this->boundingBoxMax.x)
                this->boundingBoxMax.x = vertices[4 * i + 0];

            if (vertices[4 * i + 1] < this->boundingBoxMin.y)
                this->boundingBoxMin.y = vertices[4 * i + 1];
            if (vertices[4 * i + 1] > this->boundingBoxMax.y)
                this->boundingBoxMax.y = vertices[4 * i + 1];

            if (vertices[4 * i + 2] < this->boundingBoxMin.z)
                this->boundingBoxMin.z = vertices[4 * i + 2];
            if (vertices[4 * i + 2] > this->boundingBoxMax.z)
                this->boundingBoxMax.z = vertices[4 * i + 2];

            if (vertices[4 * i + 0] < m.boundingBoxMin.x)
                m.boundingBoxMin.x = vertices[4 * i + 0];
            if (vertices[4 * i + 0] > m.boundingBoxMax.x)
                m.boundingBoxMax.x = vertices[4 * i + 0];

            if (vertices[4 * i + 1] < m.boundingBoxMin.y)
                m.boundingBoxMin.y = vertices[4 * i + 1];
            if (vertices[4 * i + 1] > m.boundingBoxMax.y)
                m.boundingBoxMax.y = vertices[4 * i + 1];

            if (vertices[4 * i + 2] < m.boundingBoxMin.z)
                m.boundingBoxMin.z = vertices[4 * i + 2];
            if (vertices[4 * i + 2] > m.boundingBoxMax.z)
                m.boundingBoxMax.z = vertices[4 * i + 2];

        }

        glGenBuffers(1, &m.vertexID);
        glBindBuffer(GL_ARRAY_BUFFER, m.vertexID);
        glBufferData(GL_ARRAY_BUFFER, numVertices * 4 * sizeof (GLfloat), vertices, GL_STATIC_DRAW);
        glEnableVertexAttribArray(positionAttrib);
        glVertexAttribPointer(positionAttrib, 4, GL_FLOAT, GL_FALSE, 0, 0);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
    }

    if (numNormals > 0) {
        glGenBuffers(1, &m.normalID);
        glBindBuffer(GL_ARRAY_BUFFER, m.normalID);
        glBufferData(GL_ARRAY_BUFFER, numNormals * 3 * sizeof (GLfloat), normals, GL_STATIC_DRAW);
        glEnableVertexAttribArray(normalAttrib);
        glVertexAttribPointer(normalAttrib, 3, GL_FLOAT, GL_FALSE, 0, 0);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
    }

    if (numTextureCoordinates > 0) {
        glGenBuffers(1, &m.texcoordID);
        glBindBuffer(GL_ARRAY_BUFFER, m.texcoordID);
        glBufferData(GL_ARRAY_BUFFER, numTextureCoordinates * 2 * sizeof (GLfloat), textureCoordinates, GL_STATIC_DRAW);
        glEnableVertexAttribArray(texcoordAttrib);
        glVertexAttribPointer(texcoordAttrib, 2, GL_FLOAT, GL_FALSE, 0, 0);
        glBindBuffer(GL_ARRAY_BUFFER, 0);
    }

    m.faces = faces;
    m.numFaces = numFaces;

    glBindVertexArray(0);

    this->meshes.push_back(m);
}

void SceneObject::update(float dt) {

}

glm::vec3 SceneObject::getBoundingBoxMin() {
    return this->boundingBoxMin;
}

glm::vec3 SceneObject::getBoundingBoxMax() {
    return this->boundingBoxMax;
}

void SceneObject::render(GLuint programID) {
    MaterialManager *mm = MaterialManager::getSingleton();
    for (std::vector<Mesh>::iterator it = this->meshes.begin(); it != this->meshes.end(); it++) {
        Mesh m = (*it);
        Material *mat = mm->getMaterial(m.material);
        mat->bindMaterial(programID);
        glBindVertexArray(m.vaoID);
        glDrawElements(GL_TRIANGLES, 3 * m.numFaces, GL_UNSIGNED_INT, m.faces);
        glBindVertexArray(0);
        mat->unbindMaterial();
    }
}

glm::mat4 SceneObject::getModelMatrix() {
    return this->modelMatrix;
}