본문 바로가기
College Study/OpenGL

[그래픽스] 파일에서 GLSL 소스코드 읽기

by 2den 2020. 6. 8.
728x90

* Computer Graphics Programming in OpenGL with C++ 책을 참고하였습니다.

* 책을 번역한 것이 아닌, 제가 독학 후 책을 참고하여 설명하는 게시물입니다. 따라서 책에 없는 부연 설명이 있기도 하며, 의역 또는 오역, 오개념이 있을 수 있습니다. 피드백은 댓글을 남겨주세요.

* 영어 용어를 최대한 한국어로 번역하지 않습니다. 처음부터 코드에서 사용되는, 또는 원서나 인터넷에서 사용되는 보편적 용어를 사용하여 개념을 잡는 것을 추천드립니다.

지난 '쉐이더' 게시글(https://goeden.tistory.com/10)에서 살펴본 [Program 2.2]는 쉐이더 코드가 main.cpp 안에 문자열로 존재했습니다. 하지만 쉐이더 코드를 따로 파일로 저장하여, main.cpp에서 불러서 사용하는 것이 일반적입니다.

 

지난 프로그램 코드 :

// main.cpp

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <iostream>

#define numVAOs 1

GLuint renderingProgram;
GLuint vao[numVAOs];

using namespace std;

GLuint createShaderProgram() {
    const char* vshaderSource =
    "#version 410 \n"
    "void main(void) \n"
    "{gl_Position = vec4(0.0, 0.0, 0.0, 1.0);}";
    const char* fshaderSource =
    "#version 410 \n"
    "out vec4 color; \n"
    "void main(void) \n"
    "{color = vec4(0.0, 0.0, 1.0, 1.0);}";

    GLuint vShader = glCreateShader(GL_VERTEX_SHADER);
    GLuint fShader = glCreateShader(GL_FRAGMENT_SHADER);

    glShaderSource(vShader, 1, &vshaderSource, nullptr);
    glShaderSource(fShader, 1, &fshaderSource, nullptr);
    glCompileShader(vShader);
    glCompileShader(fShader);

    GLuint vfProgram = glCreateProgram();
    glAttachShader(vfProgram, vShader);
    glAttachShader(vfProgram, fShader);
    glLinkProgram(vfProgram);
    
    return vfProgram;
}

void init (GLFWwindow* window) {
    renderingProgram = createShaderProgram();
    glGenVertexArrays(numVAOs, vao);
    glBindVertexArray(vao[0]);
}

void display(GLFWwindow* window, double currentTime) {
    glUseProgram(renderingProgram);
    // glPointSize(30.0f);
    glDrawArrays(GL_POINTS, 0, 1);
}

int main(void) {
    if (!glfwInit()) {exit(EXIT_FAILURE);}
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
    GLFWwindow* window = glfwCreateWindow(600, 600, "Chapter2 - program2", nullptr, nullptr);
    glfwMakeContextCurrent(window);
    if (glewInit() != GLEW_OK) {exit(EXIT_FAILURE);}
    glfwSwapInterval(1);
    
    init(window);
    
    while (!glfwWindowShouldClose(window)) {
        display(window, glfwGetTime());
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    
    glfwDestroyWindow(window);
    glfwTerminate();
    exit(EXIT_SUCCESS);
}

를 다음과 같이 main.cpp / vertShader.glsl / fragShader.glsl 로 분리해 보겠습니다.

 

[Program 2.4 Reading GLSL Source from Files]

// main.cpp

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <string>
#include <iostream>
#include <fstream>

#define numVAOs 1

GLuint renderingProgram;
GLuint vao[numVAOs];

using namespace std;

string readShaderSource(const char *filePath) {
    string content = "";
    ifstream fileStream(filePath, ios::in);
    string line = "";
    while (!fileStream.eof()) {
        getline(fileStream, line);
        content.append(line + "\n");
    }
    fileStream.close();
    return content;
}

GLuint createShaderProgram() {
    string vertShaderStr = readShaderSource("vertShader.glsl");
    string fragShaderStr = readShaderSource("fragShader.glsl");
    
    const char* vertShaderSrc = vertShaderStr.c_str();
    const char* fragShaderSrc = fragShaderStr.c_str();
    
    GLuint vShader = glCreateShader(GL_VERTEX_SHADER);
    GLuint fShader = glCreateShader(GL_FRAGMENT_SHADER);

    glShaderSource(vShader, 1, &vertShaderSrc, nullptr);
    glShaderSource(fShader, 1, &fragShaderSrc, nullptr);
    
    glCompileShader(vShader);
    glCompileShader(fShader);

    GLuint vfProgram = glCreateProgram();
    glAttachShader(vfProgram, vShader);
    glAttachShader(vfProgram, fShader);
    
    glLinkProgram(vfProgram);
    return vfProgram;
}

void init (GLFWwindow* window) {
    renderingProgram = createShaderProgram();
    glGenVertexArrays(numVAOs, vao);
    glBindVertexArray(vao[0]);
}

void display(GLFWwindow* window, double currentTime) {
    glUseProgram(renderingProgram);
    glPointSize(30.0f);
    glDrawArrays(GL_POINTS, 0, 1);
}

int main(void) {
    if (!glfwInit()) {exit(EXIT_FAILURE);}
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
    GLFWwindow* window = glfwCreateWindow(600, 600, "Chapter2 - program4", nullptr, nullptr);
    glfwMakeContextCurrent(window);
    if (glewInit() != GLEW_OK) {exit(EXIT_FAILURE);}
    glfwSwapInterval(1);
    
    init(window);
    
    while (!glfwWindowShouldClose(window)) {
        display(window, glfwGetTime());
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    
    glfwDestroyWindow(window);
    glfwTerminate();
    exit(EXIT_SUCCESS);
}


// vertShader.glsl

#version 410
void main(void) {
    gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
}


// fragShader.glsl

#version 410
out vec4 color;
void main(void) {
    color = vec4(0.0, 0.0, 1.0, 1.0);
}

 

 

코드를 분리시키는 과정에서 main.cpp의 수정, 추가된 부분은 다음과 같습니다.

// main.cpp

string readShaderSource(const char *filePath) {
    string content = "";
    ifstream fileStream(filePath, ios::in);
    string line = "";
    while (!fileStream.eof()) {
        getline(fileStream, line);
        content.append(line + "\n");
    }
    fileStream.close();
    return content;
}

GLuint createShaderProgram() {
//    const char* vshaderSource =
//    "#version 410 \n"
//    "void main(void) \n"
//    "{gl_Position = vec4(0.0, 0.0, 0.0, 1.0);}";
//    const char* fshaderSource =
//    "#version 410 \n"
//    "out vec4 color; \n"
//    "void main(void) \n"
//    "{color = vec4(0.0, 0.0, 1.0, 1.0);}";
    
    string vertShaderStr = readShaderSource("vertShader.glsl");
    string fragShaderStr = readShaderSource("fragShader.glsl");
    
    // 이하 해당사항 없음
    const char* vertShaderSrc = vertShaderStr.c_str();
    const char* fragShaderSrc = fragShaderStr.c_str();

    GLuint vShader = glCreateShader(GL_VERTEX_SHADER);
    GLuint fShader = glCreateShader(GL_FRAGMENT_SHADER);

    glShaderSource(vShader, 1, &vshaderSource, nullptr);
    glShaderSource(fShader, 1, &fshaderSource, nullptr);
    glCompileShader(vShader);
    glCompileShader(fShader);

    GLuint vfProgram = glCreateProgram();
    glAttachShader(vfProgram, vShader);
    glAttachShader(vfProgram, fShader);
    glLinkProgram(vfProgram);
    
    return vfProgram;
}

책에서는 C++의 기초이기 때문에 파일 입출력을 다루지 않았지만, 혹시나 C++ 기초가 아직 준비되지 않으신 분들을 위해 변경된 코드를 잠시 살펴보도록 하겠습니다.

 

// main.cpp createShaderProgram()

string vertShaderStr = readShaderSource("vertShader.glsl");
string fragShaderStr = readShaderSource("fragShader.glsl");

지난번 코드와는 달리, vertShaderStrfragShaderStr이라는 문자열 변수를 선언하고 있습니다. 각각의 변수에는 readShaderSource() 함수의 반환 값이 대입됩니다. 첫번째 함수실행에는 "vertShader.glsl" 문자열을, 두번째 함수실행에는 "fragShader.glsl" 문자열을 매개변수로 넘겨줍니다.

 

 

// main.cpp

string readShaderSource(const char *filePath) {
    string content = "";
    ifstream fileStream(filePath, ios::in);
    string line = "";
    while (!fileStream.eof()) {
        getline(fileStream, line);
        content.append(line + "\n");
    }
    fileStream.close();
    return content;
}

createShaderProgram() 함수에서 넘겨준 문자열 매개변수의 정체는 바로 파일의 주소입니다. (* 원래는 파일 이름만 넣으면 같은 프로젝트 폴더 안에 있는 GLSL 파일이 참조되어야 하지만, 저는 이 입력이 작동하지 않아 절대주소를 입력했습니다. 참고하세요.)

첫번째 줄에서 빈 문자열 content를 만듭니다. 읽어 올 소스코드를 담을 문자열 입니다. ifstream은 파일 입력 스트림을 생성합니다. filePath의 주소에 있는 파일을 in(입력) 파일로 설정합니다. 또 코드를 한줄씩 읽기 위해 line이라는 빈 문자열도 만듭니다.

while문의 조건을 보시면, !fileStream.eof()이라고 되어있습니다. eof 는 end of file의 약자로, 파일의 끝에 도달한 상태를 의미합니다. 그러니 파일의 끝에 도달하기 전까지, getline()함수로 한줄씩 스트림의 코드를 읽어 line에 대입, content 문자열에 읽은 코드와 줄바꿈(\n)을 추가합니다.

파일의 끝에 도달하면 파일 스트림을 닫고 코드를 저장한 문자열 content 를 반환합니다. 그럼 위에서 봤던 vertShaderStr, fragShaderStr에 vertex 쉐이더와 fragent 쉐이더가 각각 문자열 형태로 들어가게 되겠죠?

변경된 코드에 바로 이전 'OpenGL과 GLSL의 오류 검출' 게시글에서 추가했던 오류 검출 코드를 적용시키면 main.cpp는 다음과 같은 코드가 됩니다.

// main.cpp

#include <GL/glew.h>
#include <GLFW/glfw3.h>
#include <string>
#include <iostream>
#include <fstream>

#define numVAOs 1

GLuint renderingProgram;
GLuint vao[numVAOs];

using namespace std;

void printShaderLog(GLuint shader) {
    int len = 0;
    int chWrittn = 0;
    char* log;
    glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &len);
    if (len > 0) {
        log = (char*)malloc(len);
        glGetShaderInfoLog(shader, len, &chWrittn, log);
        cout << "Shader Info Log: " << log << endl;
        free(log);
    }
}

void printProgramLog(int prog) {
    int len = 0;
    int chWrittn = 0;
    char* log;
    glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &len);
    if (len > 0) {
        log = (char*)malloc(len);
        glGetProgramInfoLog(prog, len, &chWrittn, log);
        cout << "Program Info Log: " << log << endl;
        free(log);
    }
}

bool checkOpenGLError() {
    bool foundError = false;
    int glErr = glGetError();
    while (glErr != GL_NO_ERROR) {
        cout << "glError: " << glErr << endl;
        foundError = true;
        glErr = glGetError();
    }
    return foundError;
}

string readShaderSource(const char *filePath) {
    string content = "";
    ifstream fileStream(filePath, ios::in);
    string line = "";
    while (!fileStream.eof()) {
        getline(fileStream, line);
        content.append(line + "\n");
    }
    fileStream.close();
    return content;
}

GLuint createShaderProgram() {
    GLint vertCompiled;
    GLint fragCompiled;
    GLint linked;
    
    string vertShaderStr = readShaderSource("vertShader.glsl");
    string fragShaderStr = readShaderSource("fragShader.glsl");
    
    const char* vertShaderSrc = vertShaderStr.c_str();
    const char* fragShaderSrc = fragShaderStr.c_str();
    
    GLuint vShader = glCreateShader(GL_VERTEX_SHADER);
    GLuint fShader = glCreateShader(GL_FRAGMENT_SHADER);

    glShaderSource(vShader, 1, &vertShaderSrc, nullptr);
    glShaderSource(fShader, 1, &fragShaderSrc, nullptr);
    
    glCompileShader(vShader);
    checkOpenGLError();
    glGetShaderiv(vShader, GL_COMPILE_STATUS, &vertCompiled);
    if (vertCompiled != 1) {
        cout << "vertex compilation failed" << endl;
        printShaderLog(vShader);
    }

    glCompileShader(fShader);
    checkOpenGLError();
    glGetShaderiv(fShader, GL_COMPILE_STATUS, &fragCompiled);
    if (fragCompiled != 1) {
        cout << "fragment compilation failed" << endl;
        printShaderLog(fShader);
    }

    GLuint vfProgram = glCreateProgram();
    glAttachShader(vfProgram, vShader);
    glAttachShader(vfProgram, fShader);
    
    glLinkProgram(vfProgram);
    checkOpenGLError();
    glGetProgramiv(vfProgram, GL_LINK_STATUS, &linked);
    if (linked != 1) {
        cout << "linking failed" << endl;
        printProgramLog(vfProgram);
    }

    return vfProgram;
}


void init (GLFWwindow* window) {
    renderingProgram = createShaderProgram();
    glGenVertexArrays(numVAOs, vao);
    glBindVertexArray(vao[0]);
}

void display(GLFWwindow* window, double currentTime) {
    glUseProgram(renderingProgram);
    glPointSize(30.0f);
    glDrawArrays(GL_POINTS, 0, 1);
}

int main(void) {
    if (!glfwInit()) {exit(EXIT_FAILURE);}
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 1);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
    GLFWwindow* window = glfwCreateWindow(600, 600, "Chapter2 - program2", nullptr, nullptr);
    glfwMakeContextCurrent(window);
    if (glewInit() != GLEW_OK) {exit(EXIT_FAILURE);}
    glfwSwapInterval(1);
    
    init(window);
    
    while (!glfwWindowShouldClose(window)) {
        display(window, glfwGetTime());
        glfwSwapBuffers(window);
        glfwPollEvents();
    }
    
    glfwDestroyWindow(window);
    glfwTerminate();
    exit(EXIT_SUCCESS);
}

 

728x90

댓글