Saturday, November 30, 2013

Sponza рэндэрлэх оролдлого 3, Материал бүтцээ цэнэглэх

shader тооцоололд ашиглах материалын бүтэц бэлэн болсон бол одоо утга оноож хадгалах хэрэгтэй.
Модел дотор маш олон материал байж болох тул эдгээр материалуудыг хадгалах зориулалттай MaterialManager нэртэй синглтон класс үүсгэсэн байгаа. Синглтон ашиглахын давуу тал гэвэл програмын ажиллагааны туршид зөвхөн ганц л инстанс буюу объект үүсгэж түүнлүүгээр дурын газраас хандах ашиглах боломжтой юм. Програмд олон материал манажер байх шаардлагагүй тул синглтон болгосон байгаа.

Програмын ажиллагааны хувьд Assimp аар уншсан модел доторхи бүх материалын утгуудыг уншиж аваад шинээр Material классаар объект үүсгээд уншсан утгуудаараа зоон цэнэглээд MaterialManager-тээ даатгах юм. Классын устгагч функцыг бодолцсон байгаа тул санах ойд хог үлдэх талаар санаа зовох шаардлагагүй.

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

    void loadDiffuseTexture(const std::string &path);
    void loadNormalTexture(const std::string &path);
    void loadSpecularTexture(const std::string &path);
    
    void bindMaterial(GLuint programID);
    void unbindMaterial();

private:
    glm::vec4 diffuseColor;
    glm::vec3 ambientColor;
    glm::vec3 specularColor;
    glm::vec3 emissiveColor;
    float shininess;
    GLuint diffuseTextureID;
    GLuint normalTextureID;
    GLuint specularTextureID;
};

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

Material::~Material() {
    glDeleteTextures(1, &this->diffuseTextureID);
    glDeleteTextures(1, &this->normalTextureID);
    glDeleteTextures(1, &this->specularTextureID);
    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_MIPMAPS | SOIL_FLAG_INVERT_Y | SOIL_FLAG_NTSC_SAFE_RGB | SOIL_FLAG_COMPRESS_TO_DXT
            );
    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_MIPMAPS | SOIL_FLAG_INVERT_Y | SOIL_FLAG_NTSC_SAFE_RGB | SOIL_FLAG_COMPRESS_TO_DXT
            );
    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_MIPMAPS | SOIL_FLAG_INVERT_Y | SOIL_FLAG_NTSC_SAFE_RGB | SOIL_FLAG_COMPRESS_TO_DXT
            );
    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 shininessUniformID = glGetUniformLocation(programID, "material_shininess");
    glUniform1f(shininessUniformID, this->shininess);

    GLuint diffuseColorUniformID = glGetUniformLocation(programID, "material_diffuse_color");
    glUniform4fv(diffuseColorUniformID, 1, glm::value_ptr(this->diffuseColor));
}

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



MaterialManager.hpp
#ifndef MATERIALMANAGER_HPP
#define MATERIALMANAGER_HPP

#include "Material.hpp"
#include <map>
#include <string>

class MaterialManager {
public:
    ~MaterialManager();
    static MaterialManager* getSingleton();
    
    Material* createMaterial(const std::string &name, int index);
    Material* getMaterial(int index);
    Material* getMaterial(const std::string &name);
    int getMaterialIndex(const std::string &name);
protected:
    MaterialManager();
    
    std::map<int, Material*> materials;
    std::map<std::string, int> materialIndexes;
private:
    static MaterialManager *instance;
};

#endif /* MATERIALMANAGER_HPP */

MaterialManager.cpp
#include "MaterialManager.hpp"
#include <iostream>

MaterialManager* MaterialManager::instance = NULL;

MaterialManager* MaterialManager::getSingleton() {
    if (!MaterialManager::instance)
        MaterialManager::instance = new MaterialManager();
    return MaterialManager::instance;
}

MaterialManager::MaterialManager() {
}

MaterialManager::~MaterialManager() {
    std::map<int, Material *>::iterator begin = this->materials.begin();
    std::map<int, Material *>::iterator end = this->materials.end();
    while (begin != end) {
        delete begin->second;
        ++begin;
    }
    this->materials.clear();
    std::cout << "MaterialManager destructor function" << std::endl;
}

Material* MaterialManager::createMaterial(const std::string& name, int index) {
    if (this->materials.find(index) == this->materials.end() &&
            this->materialIndexes.find(name) == this->materialIndexes.end()) {
        this->materials[index] = new Material();
        this->materialIndexes[name] = index;
    }
    return this->materials[index];
}

Material* MaterialManager::getMaterial(int index) {
    if (this->materials.find(index) == this->materials.end())
        return NULL;
    return this->materials[index];
}

Material* MaterialManager::getMaterial(const std::string& name) {
    if (this->materialIndexes.find(name) == this->materialIndexes.end())
        return NULL;
    return this->materials[getMaterialIndex(name)];
}

int MaterialManager::getMaterialIndex(const std::string& name) {
    if (this->materialIndexes.find(name) == this->materialIndexes.end())
        return -1;
    else
        return this->materialIndexes[name];
}

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 "MaterialManager.hpp"
#include "Material.hpp"

using namespace std;


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

void initWindow();
void disposeWindow();
void processMaterial(aiMaterial* material, int index);

/*
 * experiment with ASSIMP
 */
int main(int argc, char** argv) {
    initWindow();
    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;
    }
    std::cout << "number of meshes " << scene->mNumMeshes << std::endl;
    std::cout << "number of materials " << scene->mNumMaterials << std::endl;
    for (unsigned int i = 0; i < scene->mNumMaterials; i++) {
        processMaterial(scene->mMaterials[i], i);
    }

    delete MaterialManager::getSingleton();
    disposeWindow();
    return 0;
}

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, normalMapTexturePath, specularTexturePath;
    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));

    // Load diffuse texture if there is one
    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->loadDiffuseTexture(diffuseTexturePath.data);
        std::cout << "diffuse texture path " << diffuseTexturePath.data << std::endl;
    }

    // Load height map texture if there is one
    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;
        }
        m->loadNormalTexture(normalMapTexturePath.data);
        std::cout << "height map texture path " << normalMapTexturePath.data << std::endl;
    }

    // Load mask texture if there is one
    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;
        }
        m->loadSpecularTexture(specularTexturePath.data);
        std::cout << "specular texture path " << specularTexturePath.data << 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();
}