본문 바로가기

ComputerScience/Computer Graphics

OpenGL - 6 Shaders (1)

728x90

* always thanks to https://learnopengl.com/

 

Learn OpenGL, extensive tutorial resource for learning Modern OpenGL

Welcome to OpenGL Welcome to the online book for learning OpenGL! Whether you are trying to learn OpenGL for academic purposes, to pursue a career or simply looking for a hobby, this book will teach you the basics, the intermediate, and all the advanced kn

learnopengl.com

6. Shaders

- 이전 장에서 말했다 시피 shader는 GPU안에 위치하는 작은 프로그램이다.

- graphics pipeline의 특정 section에서 동작하며 사실상 하는 일은 input을 받아서 ouput으로 transforming하는게 전부이다. 

6.1 GLSL

- shader를 작성하는데 사용되는 언어이다. graphics를 위해 맞춤 고안된 언어이며 matrix 조작과 vector에 특화되어있다.

- shader는 항상 version declaration으로 시작한다.

- 이어서 input, output, uniform 변수들이 정의되고 shader의 entry point인 main함수가 있다.

- main에서 input을 process해서 output을 만들어낸다.

#version version_number
in type in_variable_name;
in type in_variable_name;
out type out_variable_name;
uniform type uniform_name;
void main()
{
        // process input(s) and do some weird graphics stuff
        ...
        // output processed stuff to output variable
        out_variable_name = weird_stuff_we_processed;
}

- vertex shader를 예로 설명을 한다면, input variable이라는 건 vertex attribute이라고도 한다.

- 하드웨어에 따라서 정의할 수 있는 attributes가 제한되어있다.

- maximum number of vertex attributes를 확인하는 방법은 아래와 같다.

int nrAttributes;
glGetIntegerv(GL_MAX_VERTEX_ATTRIBS, &nrAttributes);
std::cout << "Maximum nr of vertex attributes supported: " << nrAttributes << std::endl;

6.2 Types

- GLSL가 가지고 있는 타입

  • int, float, double, uint and bool
  • vectors and matrices

6.2.1 Vectors

- component 크기(1,2,3,4)별로 있음. (x), (x,y), (x,y,z), (x,y,z,w)

  • vec(n): the default vector of (n floats.)
  • bvec(n): a vector of (n booleans.)
  • ivec(n): a vector of (n integers.)
  • uvec(n): a vector of (n unsigned integers.)
  • dvec(n): a vector of (n double components.)
vec2 someVec;
vec4 differentVec = someVec.xyxx;
vec3 anotherVec   = differentVec.zyw;
vec4 otherVec     = someVec.xxxx + anotherVec.yxzy;

- vec2, vec4 처럼 component 수에 따른 타입으로 변수 정의

- 각 컴포넌트에 접근할때 .x 처럼 사용 가능

- .xxyz처럼 연속해서 컴포넌트를 접근할 수 있다.

vec2 vect = vec2(0.5, 0.7);
vec4 result = vec4(vect, 0.0, 0.0);
vec4 otherResult = vec4(result.xyz, 1.0);

- xyzw대신 rgba(color), stpq(texture)로도 components에 접근이 가능하다.

- vec2의 경우 z는 없지만 vec4의 생성자에 vec2와 z,w를 넘겨서 vec4를 생성하는 위 문법도 가능하다.

6.3 Ins and outs

- shader는 하나의 작은 프로그램이자 전체의 한 부분이다. 따라서 각각의 shader들은 서로간 소통을 위해 input과 ouput을 갖는다.

- in, out은 그 목적을 위한 키워드이다. 각 shader의 output은 다음 shader의 input과 맞아야 한다.

- 단, vertex shader는 특별한 형태의 input을 받아야한다.

- vertex shader가 바로 vertex data를 받기 위해서는 vertex shader의 input 변수에 location metadata를 명시해주어야한다. 그래야 만이 vertex attributes를 cpu에게 알려줄 수 있다.

- 이전에 우리가 vertex shader를 만들때 layout (location = 0)을 적었던 것은 이 때문이다.

- 정리하면 vertex shader와 vertex data를 link하기 위해 vertex shader의 input에 대해서 layout specification을 해주어야 한다.

 

- fragment shader는 최종 output 색을 만들어내야 하므로 vec4 타입의 color output variable을 필요로한다.

- 위의 두 특수한 경우를 빼면 일반적으로 한 shader의 output은 다음 shader의 input과 비슷해야 한다.

- 따라서 두 변수의 type과 이름이 같다면 OpenGL이 알아서 그 변수들을 link해주고 이를 통해 shader간 데이터 전달이 가능하게 된다.

 

- 이전 삼각형 예제에서 vertex shader와 fragment shader를 한번 수정해보자.

#version 330 core
layout (location = 0) in vec3 aPos; // position has attribute position 0
out vec4 vertexColor; // specify a color output to the fragment shader
void main() {
       gl_Position = vec4(aPos, 1.0); // we give a vec3 to vec4’s constructor
       vertexColor = vec4(0.5, 0.0, 0.0, 1.0); // output variable to dark-red
}
#version 330 core
out vec4 FragColor;
in vec4 vertexColor; // input variable from vs (same name and type)
void main() {
       FragColor = vertexColor;
}

- vertex shader의 output은 vertexColor라는 vec4타입의 변수이다. 

- fragment shader에는 위의 output을 받기 위해 input으로 vec4타입의 vertexColor를 정의했다.

- 서로 같은 타입과 이름이므로 이를 통해 데이터를 전달할 수 있다.

- vertex shader에서 dark-red컬러로 vertexColor를 내보내고 fragment shader가 이를 input으로 받아 dark-red를 내보내자.

6.4 Uniforms

- cpu에서 돌아가는 우리 application에서 GPU에 있는 shader로 데이터를 전달하는 또 다른 방법이다.

- uniform variable은 global(전역)으로 선언된다. 유일하며 모든 shader로부터 접근이 가능하다. 또한 uniform variable의 value를 바꾸기 전까지 계속해서 유지된다. 전역변수의 성질과 비슷하다.

- 이번에는 삼각형의 색을 uniform변수를 통해 바꾸어보자.

version 330 core
out vec4 FragColor;
uniform vec4 ourColor; // we set this variable in the OpenGL code.
void main() {
            FragColor = ourColor;
}

- fragment shader에 uniform으로 vec4타입의 변수 ourColor를 선언하였고 output fragColor를 ourColor로 set했다.

- uniforms가 global 변수이기 때문에 어느 shader에서 접근이 가능하다. 따라서 fragment의 색을 바꾸기 위해서 다시 vertex shader로 들어가서 color를 바꾸고 다른 shader로 보내주지 않아도 된다. 

- vertex shader에는 uniform을 사용하지 않기 때문에 이곳에 uniform을 정의할 필요가 없다.

 

- 자 그럼 다시 코드로 돌아가서 uniform을 보자. uniform은 현재 비어있다.

- 이제 uniform에 값을 채워주어야 한다. 그럼 먼저 uniform attribute의 location/index를 알아야 할 것이다.

- 위치를 알았으면 uniform 변수에 값을 채워 줄 것 인데 이때 색을 시간에 따라 다양하게 넣어주자 아래 코드를 보자.

   float timeValue = glfwGetTime();
   float greenValue = (sin(timeValue) / 2.0f) + 0.5f;
   int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
   glUseProgram(shaderProgram);
   glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);

- glfwGetTime으로 실행시간(초)을 담는다.

- greenValue에 sin함수를 활용해서 0.0 ~ 1.0사이의 값을 담는다.

- glGetUniformLocation라는 함수로 ourColor라는 uniform변수의 location을 qurery한다. uniform 변수 이름과 shaderProgram을 넣어준다.

- 만약 glGetUniformLocation가 -1 을 반환하면 uniform변수의 위치를 못 찾은 것이다.

- 위치를 찾았으면 glUniform4f함수로 uniform변수의 값을 저장한다.

- 여기서 주의할 점은 uniform변수의 위치를 찾을 때와 달리 glUniform4f는 현재 활성화 된 shader program의 uniform변수를 update하기 때문에 glUserProgram을 먼저 호출해줘야 한다.

 

- OpenGL의 core는 C로 되어있기 때문에 함수 오버로딩을 지원하지 않는다. 따라서 glUniform을 호출할 때 후위식으로 타입을 표현해줘야 한다. glUniform4f는 네개의 float으로 uniform을 초기화 한다는 뜻이다.

  • f : float
  • i : int
  • ui : unsigned int
  • 3f : 3 floats
  • fv : float vector or array

- 자 이제 rendering에 uniform variable을 사용해 보자. 

- uniform변수에 실시간으로 값이 다르게 담길 수 있게 계산을 하도록 했다.

- 그럼 이 초기화 구문은 rendering이 반복될때마다 수행되어야 한다.

while(!glfwWindowShouldClose(window))
{
// input
       processInput(window);
       // render
       // clear the colorbuffer
       glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
       glClear(GL_COLOR_BUFFER_BIT);
       // be sure to activate the shader
	   glUseProgram(shaderProgram);
       // update the uniform color
       float timeValue = glfwGetTime();
       float greenValue = sin(timeValue) / 2.0f + 0.5f;
       int vertexColorLocation = glGetUniformLocation(shaderProgram,
                                                           "ourColor");
       glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);
       // now render the triangle
       glBindVertexArray(VAO);
       glDrawArrays(GL_TRIANGLES, 0, 3);
       // swap buffers and poll IO events
       glfwSwapBuffers(window);
       glfwPollEvents();
 }

- 매 frame마다 삼각형을 그리기 전에 uniform value를 update한다.

- 위의 내용을 조합해서 실행해보자

*전체코드

#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include <iostream>
#include <cmath>

void framebuffer_size_callback(GLFWwindow* window, int width, int height);
void processInput(GLFWwindow *window);

// settings
const unsigned int SCR_WIDTH = 800;
const unsigned int SCR_HEIGHT = 600;

const char *vertexShaderSource ="#version 330 core\n"
    "layout (location = 0) in vec3 aPos;\n"
    "void main()\n"
    "{\n"
    "   gl_Position = vec4(aPos, 1.0);\n"
    "}\0";

const char *fragmentShaderSource = "#version 330 core\n"
    "out vec4 FragColor;\n"
    "uniform vec4 ourColor;\n"
    "void main()\n"
    "{\n"
    "   FragColor = ourColor;\n"
    "}\n\0";

int main()
{
    // glfw: initialize and configure
    // ------------------------------
    glfwInit();
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

#ifdef __APPLE__
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE);
#endif

    // glfw window creation
    // --------------------
    GLFWwindow* window = glfwCreateWindow(SCR_WIDTH, SCR_HEIGHT, "LearnOpenGL", NULL, NULL);
    if (window == NULL)
    {
        std::cout << "Failed to create GLFW window" << std::endl;
        glfwTerminate();
        return -1;
    }
    glfwMakeContextCurrent(window);
    glfwSetFramebufferSizeCallback(window, framebuffer_size_callback);

    // glad: load all OpenGL function pointers
    // ---------------------------------------
    if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
    {
        std::cout << "Failed to initialize GLAD" << std::endl;
        return -1;
    }

    // build and compile our shader program
    // ------------------------------------
    // vertex shader
    unsigned int vertexShader = glCreateShader(GL_VERTEX_SHADER);
    glShaderSource(vertexShader, 1, &vertexShaderSource, NULL);
    glCompileShader(vertexShader);
    // check for shader compile errors
    int success;
    char infoLog[512];
    glGetShaderiv(vertexShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(vertexShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::VERTEX::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // fragment shader
    unsigned int fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
    glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
    glCompileShader(fragmentShader);
    // check for shader compile errors
    glGetShaderiv(fragmentShader, GL_COMPILE_STATUS, &success);
    if (!success)
    {
        glGetShaderInfoLog(fragmentShader, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::FRAGMENT::COMPILATION_FAILED\n" << infoLog << std::endl;
    }
    // link shaders
    unsigned int shaderProgram = glCreateProgram();
    glAttachShader(shaderProgram, vertexShader);
    glAttachShader(shaderProgram, fragmentShader);
    glLinkProgram(shaderProgram);
    // check for linking errors
    glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
    if (!success) {
        glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
        std::cout << "ERROR::SHADER::PROGRAM::LINKING_FAILED\n" << infoLog << std::endl;
    }
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);

    // set up vertex data (and buffer(s)) and configure vertex attributes
    // ------------------------------------------------------------------
    float vertices[] = {
         0.5f, -0.5f, 0.0f,  // bottom right
        -0.5f, -0.5f, 0.0f,  // bottom left
         0.0f,  0.5f, 0.0f   // top
    };

    unsigned int VBO, VAO;
    glGenVertexArrays(1, &VAO);
    glGenBuffers(1, &VBO);
    // bind the Vertex Array Object first, then bind and set vertex buffer(s), and then configure vertex attributes(s).
    glBindVertexArray(VAO);

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

    glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
    glEnableVertexAttribArray(0);

    // You can unbind the VAO afterwards so other VAO calls won't accidentally modify this VAO, but this rarely happens. Modifying other
    // VAOs requires a call to glBindVertexArray anyways so we generally don't unbind VAOs (nor VBOs) when it's not directly necessary.
    // glBindVertexArray(0);


    // bind the VAO (it was already bound, but just to demonstrate): seeing as we only have a single VAO we can
    // just bind it beforehand before rendering the respective triangle; this is another approach.
    glBindVertexArray(VAO);


    // render loop
    // -----------
    while (!glfwWindowShouldClose(window))
    {
        // input
        // -----
        processInput(window);

        // render
        // ------
        glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
        glClear(GL_COLOR_BUFFER_BIT);

        // be sure to activate the shader before any calls to glUniform
        glUseProgram(shaderProgram);

        // update shader uniform
        float timeValue = glfwGetTime();
        float greenValue = sin(timeValue) / 2.0f + 0.5f;
        int vertexColorLocation = glGetUniformLocation(shaderProgram, "ourColor");
        glUniform4f(vertexColorLocation, 0.0f, greenValue, 0.0f, 1.0f);

        // render the triangle
        glDrawArrays(GL_TRIANGLES, 0, 3);

        // glfw: swap buffers and poll IO events (keys pressed/released, mouse moved etc.)
        // -------------------------------------------------------------------------------
        glfwSwapBuffers(window);
        glfwPollEvents();
    }

    // optional: de-allocate all resources once they've outlived their purpose:
    // ------------------------------------------------------------------------
    glDeleteVertexArrays(1, &VAO);
    glDeleteBuffers(1, &VBO);
    glDeleteProgram(shaderProgram);

    // glfw: terminate, clearing all previously allocated GLFW resources.
    // ------------------------------------------------------------------
    glfwTerminate();
    return 0;
}

// process all input: query GLFW whether relevant keys are pressed/released this frame and react accordingly
// ---------------------------------------------------------------------------------------------------------
void processInput(GLFWwindow *window)
{
    if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
        glfwSetWindowShouldClose(window, true);
}

// glfw: whenever the window size changed (by OS or user resize) this callback function executes
// ---------------------------------------------------------------------------------------------
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
    // make sure the viewport matches the new window dimensions; note that width and
    // height will be significantly larger than specified on retina displays.
    glViewport(0, 0, width, height);
}

- uniform값이 매 frame마다 변하기 때문에 실행을 시켜보면 삼각형의 색이 계속 변함을 관찰할 수 있다.

- 즉 시간에 따라서 설정이 변하도록 할 때, application과 shader가 상호작용할 때 uniform은 상당히 유용하다.

- uniform 변수를 vertex수 만큼 정의하고 값을 대입 해주면 세 꼭지점 마다 다른 색으로 삼각형이 그려질 것이다. 하지만 이것보다 더 좋은 방법을 뒤에서 살펴보자.

 

728x90
반응형

'ComputerScience > Computer Graphics' 카테고리의 다른 글

CG - 1. 컴퓨터 그래픽스?  (0) 2021.09.02
OpenGL - 6 Shaders (2)  (0) 2021.07.15
OpenGL - 5 Hello Triangle (3)  (0) 2021.06.30
OpenGL - 5 Hello Triangle (2)  (0) 2021.06.29
OpenGL - 5 Hello Triangle (1)  (0) 2021.06.29