티스토리 뷰

0. 개요

opengl에서 uniform variable을 이용하여 띄운 삼각형의 위치를 계속 바꾸어본다.

1. Uniform Variable이란

Uniform Variable은 전역 셰이더 변수다.
각 shader stage별로 값이 변하지 않기 때문에 uniform이라는 이름이 붙었다.
shader 내부에서 값을 변경할 수는 없고 선언 및 이용만 할 수 있다.
값 변경은 C++ 코드 사이드에서 진행한다.

2. Uniform Variable의 이용 흐름

다음과 같은 큰 흐름을 따른다.

  1. 셰이더 내부에서 Uniform Variable의 선언
  2. Uniform Variable의 location 가져오기
  3. Uniform variable에 접근하여 값 변경하기

여기서는 uniform variable을 이용하여 삼각형을 translate 해보겠다.

2.1. 셰이더 내부에서 Uniform Variable의 선언

Uniform Variable은 다음과 같이 셰이더 코드 내에서 정의할 수 있다.
uniform 커맨드를 붙여야 한다.
vertex shader 쪽에서 4x4 matrix를 선언하여 삼각형에 곱해 간단히 translate 하는 연산을 만들겠다.

// Shader 소스 코드 정의
const char* TriangleTranslationRunner::vShader =
    "#version 330\n"
    "layout (location = 0) in vec3 pos;\n"
    "uniform mat4 model;\n"
    "void main()\n"
    "{\n"
    "   gl_Position = model * vec4(0.4 * pos.x, 0.4 * pos.y, 0.4 * pos.z, 1.0);\n"
    "}\n";

2.2. Uniform Variable Location 가져오기

이제 우리가 작성할 CPU쪽에서 GPU에 선언된 model의 location을 가져와야 한다.
우선 다음과 같이 GLuint의 자료형을 가진 uniformModel을 선언하고

GLuint TriangleTranslationRunner::uniformModel = 0;

아래와 같이 model 명을 이용하여 location을 가져와야 한다.
(shaderProgram 컴파일 시점 이후에 진행 필요)

uniformModel = glGetUniformLocation(shaderProgram, "model");

2.3. Uniform variable에 접근하여 값 변경하기

이제 그러면 CPU쪽 코드에서 location에 접근하여 값을 할당할 수 있다.
나는 translate matrix를 계산하고 uniform model에 할당하는 식으로 구현했다.

glm::mat4 translateMatrix(1.0f); // identity matrix.
translateMatrix = glm::translate(translateMatrix, glm::vec3(triOffset, triOffset, 0.0f));
glUniformMatrix4fv(uniformModel, 1, GL_FALSE, glm::value_ptr(translateMatrix));

이 때 glUniform~ 머시기 함수는 gpu쪽 uniform에 값을 할당하는 함수인데uiniform의 자료형에 따라 맞춰서 쓰면 된다.

https://registry.khronos.org/OpenGL-Refpages/gl4/html/glUniform.xhtml

과정을 요약하여 나타내면 다음과 같다.

전체 코드는 다음과 같다.

헤더 파일

//
// Created by ypp06 on 2025-02-14.
//

#ifndef TRIANGLETRANSLATIONRUNNER_H
#define TRIANGLETRANSLATIONRUNNER_H

#include "../base/IBaseRunner.h"
#include <GL/glew.h>
#include <GLFW/glfw3.h>

class TriangleTranslationRunner : public IBaseRunner
{
    public:
        bool Run() override;
        ~TriangleTranslationRunner() override = default;
    private:
        GLFWwindow* ReadyGlfwGlewEnv();
        void MoveTriangle();
        void CreateTriangle();
        void AddShader(GLuint targetProgram, const char* rawShaderCode, GLenum shaderType);
        void CompileShaders();

        static const GLint WIDTH, HEIGHT;
        static GLuint VAO, VBO, shaderProgram;
        static GLuint uniformModel;
        static bool direction;
        static float triOffset, triMaxOffset, triIncrement;

        static const char* vShader;
        static const char* fShader;
};

#endif //TRIANGLETRANSLATIONRUNNER_H

소스 파일

//
// Created by ypp06 on 2025-02-02.
//

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

#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>

// 전역 변수 정의
const GLint TriangleTranslationRunner::WIDTH = 800;
const GLint TriangleTranslationRunner::HEIGHT = 600;
GLuint TriangleTranslationRunner::VAO = 0;
GLuint TriangleTranslationRunner::VBO = 0;
GLuint TriangleTranslationRunner::shaderProgram = 0;
GLuint TriangleTranslationRunner::uniformModel = 0;
bool TriangleTranslationRunner::direction = true;
float TriangleTranslationRunner::triOffset = 0.0f;
float TriangleTranslationRunner::triMaxOffset = 0.7f;
float TriangleTranslationRunner::triIncrement = 0.005f;
// Shader 소스 코드 정의
const char* TriangleTranslationRunner::vShader =
    "#version 330\n"
    "layout (location = 0) in vec3 pos;\n"
    "uniform mat4 model;\n"
    "void main()\n"
    "{\n"
    "   gl_Position = model * vec4(0.4 * pos.x, 0.4 * pos.y, 0.4 * pos.z, 1.0);\n"
    "}\n";

const char* TriangleTranslationRunner::fShader =
    "#version 330\n"
    "out vec4 color;\n"
    "void main()\n"
    "{\n"
    "   color = vec4(1.0, 0.0, 0.0, 1.0);\n"
    "}\n";

// Add Shader to Program.
// Shader에 대해 1. source 지정, 2. compile, 3. targetProgram에 attach.
void TriangleTranslationRunner::AddShader(GLuint targetProgram, const char* rawShaderCode, GLenum shaderType)
{
    GLuint targetShader = glCreateShader(shaderType);

    const GLchar* code[1];
    code[0] = rawShaderCode;

    GLint codeLength[1];
    codeLength[0] = strlen(rawShaderCode);

    glShaderSource(targetShader, 1, code, codeLength);
    glCompileShader(targetShader);

    GLint result = 0;
    GLchar eLog[1024] = { 0 };

    glGetShaderiv(targetShader, GL_COMPILE_STATUS, &result);
    if (!result)
    {
        glGetShaderInfoLog(targetShader, sizeof(eLog), NULL, eLog);
        printf("Error compiling the %d shader: '%s'\n", shaderType, eLog);
        return;
    }

    glAttachShader(targetProgram, targetShader);
    return;
}

void TriangleTranslationRunner::CompileShaders()
{
    shaderProgram = glCreateProgram();
    if (!shaderProgram)
    {
        printf("Error creating shader program!\n");
        return;
    }

    AddShader(shaderProgram, vShader, GL_VERTEX_SHADER);
    AddShader(shaderProgram, fShader, GL_FRAGMENT_SHADER);

    GLint result = 0;
    GLchar eLog[1024] = { 0 };

    glLinkProgram(shaderProgram);

    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &result);
    // 제대로 링크되지 않았다면.
    if (!result)
    {
        glGetProgramInfoLog(shaderProgram, sizeof(eLog), NULL, eLog);
        printf("Error linking program: '%s'\n", eLog);
        return;
    }

    // Validate program.
    glValidateProgram(shaderProgram);

    glGetProgramiv(shaderProgram, GL_VALIDATE_STATUS, &result);
    if (!result)
    {
        glGetProgramInfoLog(shaderProgram, sizeof(eLog), NULL, eLog);
        printf("Error validating program: '%s'\n", eLog);
        return;
    }
    uniformModel = glGetUniformLocation(shaderProgram, "model");
}

void TriangleTranslationRunner::CreateTriangle()
{
    // NDC 좌표계에서 삼각형을 구성하는 vertex들의 위치.
    GLfloat vertices[] = {
        -1.0f, -1.0f, 0.0f,
        1.0f, -1.0f, 0.0f,
        0.0f, 1.0f, 0.0f
    };

    // VAO 생성 및 Bind - 이 밑에 생성되는 VBO들을 VAO에 자동으로 Bind.
    glGenVertexArrays(1, &VAO);
    glBindVertexArray(VAO);

    // VBO 생성 및 Bind.
    glGenBuffers(1, &VBO);
    glBindBuffer(GL_ARRAY_BUFFER, VBO);

    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);

    // Attribute 선언 및 활성화
    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 0, 0);
    glEnableVertexAttribArray(0);

    // VBO binding 다시 정리해주기
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    // VAO binding 다시 정리해주기
    glBindVertexArray(0);
}

// return NULL if failed.
GLFWwindow* TriangleTranslationRunner::ReadyGlfwGlewEnv()
{
    // init GLFW.
    if(!glfwInit())
    {
        printf("GLFW init fail");
        glfwTerminate();
        return NULL;
    }

    // Setup GLFW window properties.
    // OpenGL Version.
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);

    // Core profile = No Backwards Compatible.
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
    // Allow forward compatible.
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);

    GLFWwindow* mainWindow = glfwCreateWindow(WIDTH, HEIGHT, "Hello Window", NULL, NULL);
    if (!mainWindow)
    {
        printf("glfwCreateWindow failed!");
        glfwTerminate();
        return NULL;
    }

    // Get Buffer Size Info.
    int bufferWidth, bufferHeight;
    glfwGetFramebufferSize(mainWindow, &bufferWidth, &bufferHeight);

    // Set Context for GLEW to use.
    glfwMakeContextCurrent(mainWindow);

    // Allow modern extension feats.
    glewExperimental = GL_TRUE;

    if (glewInit() != GLEW_OK)
    {
        printf("glew init failed");
        glfwDestroyWindow(mainWindow); // 이 시점에서 window가 생성되었으니 지워야 한다.
        glfwTerminate();
        return NULL;
    }

    // Setup Viewport size.
    glViewport(0, 0, bufferWidth, bufferHeight);

    return mainWindow;
}

void TriangleTranslationRunner::MoveTriangle()
{
    if (direction)
    {
        triOffset += triIncrement;
    }
    else
    {
        triOffset -= triIncrement;
    }
    if (abs(triOffset) >= triMaxOffset)
    {
        direction = !direction;
    }
    glm::mat4 translateMatrix(1.0f); // identity matrix.
    translateMatrix = glm::translate(translateMatrix, glm::vec3(triOffset, triOffset, 0.0f));
    glUniformMatrix4fv(uniformModel, 1, GL_FALSE, glm::value_ptr(translateMatrix));
}

bool TriangleTranslationRunner::Run()
{
    GLFWwindow* mainWindow = ReadyGlfwGlewEnv();
    if (!mainWindow)
    {
        return false;
    }

    CreateTriangle();
    CompileShaders();
    glUseProgram(shaderProgram);
    glBindVertexArray(VAO);
    // loop til window closed.
    while (!glfwWindowShouldClose(mainWindow))
    {
        // Get + handle user input events.
        glfwPollEvents();

        // Clear window.
        glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        MoveTriangle();

        glDrawArrays(GL_TRIANGLES, 0, 3);
        glfwSwapBuffers(mainWindow);
    }
    glBindVertexArray(0);
    glUseProgram(0);
    return true;
}

Refs

https://registry.khronos.org/OpenGL-Refpages/gl4/html/glGetUniformLocation.xhtml

댓글