* always thanks to https://learnopengl.com/
5.4 Fragment shader
- 이번에는 최소한의 구현 조건 두번째, fragment Shader를 정의한다.
- fragment shader는 pixel의 color output을 계산한다. 구현을 쉽게 하기 위해 우리가 만들 shader는 orange-ish color만 내보낸다.
- computer graphics는 color를 4개의 원소(0.0 ~ 1.0 사이의 값)를 가진 배열로 표현한다. RGBA(red/ green/ blue/ alpha(opacity) component)
#version 330 core
out vec4 FragColor;
void main() {
FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);
}
- fragment shader는 오직 하나의 output variable만을 요구한다. size 4 vector로 최종 output의 color를 정의한다.
- "out" 키워드로 선언하고 이름은 FragColor라고 하자. 그리고 FragColor를 orange-ish color로 set한다.
- 컴파일 과정은 이전 vertex shader와 같다. 단 GL_FRAGMENT_SHADER로 shader type을 바꿔주자.
unsigned int fragmentShader;
fragmentShader = glCreateShader(GL_FRAGMENT_SHADER);
glShaderSource(fragmentShader, 1, &fragmentShaderSource, NULL);
glCompileShader(fragmentShader);
5.4.1 Shader program
- shader program object는 여러 shader를 combine한 최종 linked version이다.
- 가장 최근에 컴파일된 shader를 사용하기 위해서 하나의 shader progrm object로 link하고 rendering할 때 activate해야한다.
- 각각의 shader로부터의 output을 다음 shader의 input으로 linking 해야한다.
- program object를 하나 만든다.
unsigned int shaderProgram;
shaderProgram = glCreateProgram();
- glCreateProgram은 프로그램 객체를 만들고 ID reference를 반환한다.
- 이전에 컴파일했던 shader들을 program object에 attach, link 한다. (glAttachShader, glLinkProgram)
glAttachShader(shaderProgram, vertexShader);
glAttachShader(shaderProgram, fragmentShader);
glLinkProgram(shaderProgram);
- attach, link의 fail에 대비해서 check하는 코드도 작성한다.
glGetProgramiv(shaderProgram, GL_LINK_STATUS, &success);
if(!success) {
glGetProgramInfoLog(shaderProgram, 512, NULL, infoLog);
...
}
- 이렇게 만든 program object를 glUseProgram으로 activate한다.
glUseProgram(shaderProgram);
- glUseProgram 호출 이후의 shader, rendering call은 이 program object를 쓰게 될 것이다.
- program object 와 shader objects link가 끝나고 나면 더이상 shader objects가 필요없기 때문에 delete 해준다.
glDeleteShader(vertexShader);
glDeleteShader(fragmentShader);
- 이렇게 input vertex data를 GPU에게 전달했다. 그리고 GPU에게 어떻게 vertex data를 process할 것인지 fragment shader로 알려주었다.
- 하지만 아직 OpenGL이 모르는 게 한가지 있다. 메모리에 있는 vertex data를 어떻게게 해석할 것인가? vertex data를 shader's attributes와 어떻게 연결할 것인지?를 알려주어야한다.
5.5 Linking Vertex Attributes
- rendering 전에 OpenGL에게 vertex shader와 VBO에 있는 vertex data를 연결해주어야한다.
- vertex buffer data formatted
- position data : 32-bit(4byte) floating point values.
- 각 position은 세개의 value들로 구성
- array의 세 values사이에 빈 공간은 없음
- 첫번째 value는 buffer의 첫번째에 위치
- 위 정보들과 glVertexAttribPointer를 가지고 OpenGL에게 어떻게 VBO의 vertices data를 접근해서 좌표들을 해당하는 attributes로 해석하라고 할지 알려줄 수 있다.
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
- 첫번째 인자는 configure하고자 하는 vertex attribute를 가리킨다. vertex shader에서 vertex attribute의 위치를 0으로 하였던것을 기억해보자. (layout location = 0). 따라서 0을 넘겨줌으로써 data를 vertex attribute에게 넘겨줄 수 있게 된다.
- 즉 OpenGL이 vertex data를 vertex attribute마다 어떻게 해석해야 하는지 알 수 있게 된다.
- 두번째 인자는 vertex attribute의 크기이다. vec3는 세개의 value로 구성되어있기 때문에 3을 전달한다.
- 세번째 인자는 data의 type이다. GL_FLOAT
- 네번째 인자는 data normalization과 관계가 있다. 만약 input data가 int나 byte로 되어있다면 normalize해서 -1~1 사이의 float으로 convert해줘야 한다. 이럴때 GL_TRUE를 넣어준다. 반면 input data가 -1~1 사이의 float으로 normalize되었다면 GL_FALSE를 넣어주면 된다.
- 다섯번째 인자는 stride이다. 연속된 vertex attributes사이의 공간이다. 만약 위의 경우처럼 array가 tightly pack되어있다면 vertex attribute사이에 공간이 없기 때문에 0을 전달해도 된다. 하지만 다른 방법으로 다음 position data가 float크기 * 3 만큼 떨어져있기 때문에 이 값을 전달해도 된다. (위 그림 참조)
- 마지막인자는 offset인데 이는 buffer에서 position data의 시작 위치를 말한다. void* 타입으로 인자를 받는다. data array의 시작위치에 position data가 있기때문에 0을 void* type으로 cast해서 넣어준다. 자세한 내용은 뒤에서 알아보자.
- 이제 OpenGL은 vertex data를 어떻게 interpret해야하는지 알고 있다. 다음으로 glEnableVertexAttribArray로 vertex attribute loacation을 인자로 주어야 한다. 이는 vertex attributes는 기본적으로 disabled되어있기 때문이다.
- 이제 모든 준비는 끝났다. 일련의 과정을 간략하게 정리해보자
- vertex buffer object로 buffer에 vertex data를 초기화 한다.
- vertex shader, fragment shader 정의
- OpenGL에게 어떻게 vertex data를 vertex shader의 vertex attributes와 link시킬 건지 알려준다.
// 0. copy our vertices array in a buffer for OpenGL to use
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 1. then set the vertex attributes pointers
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// 2. use our shader program when we want to render an object
glUseProgram(shaderProgram);
// 3. now draw the object
someOpenGLFunctionThatDrawsOurTriangle();
- 도형을 그릴때마다 위의 일련의 과정들을 계속해서 반복하게 될 것이다.
- vertex의 수가 많아질수록 buffer object를 binding하고 모든 vertex attribute을 configure하는 과정은 꽤나 버거운 일이 될 것이다. 그래서 이 과정에서 state configuration들을 저장할 수 있는, bind만 하면 바로 사용가능한 object가 존재한다.
5.5.1 Vertex Array Object
- vertex array object (VAO)도 VBO처럼 bind될 수 있다. 모든 point의 vertex attribute call은 VAO에 저장된다.
- bind가 되고나면 함수 호출 한번만으로 모든 vertex attribute을 불러올 수 있다.
- vertex array object에 저장되는 내용들
- Calls to glEnableVertexAttribArray or glDisableVertexAttribArray.
- Vertex attribute configurations via glVertexAttribPointer.
- Vertex buffer objects associated with vertex attributes by calls to glVertexAttribPointer.
- core OpenGL은 VAO를 사용하기를 요구한다. 사용자가 vertex input으로 무엇을 하려는지 알기 위해서다. VAO bind가 실패하면 OpenGL은 그리기를 거부한다.
- VAO를 만드는 과정은 VBO와 유사하다.
unsigned int VAO;
glGenVertexArrays(1, &VAO);
- glGenVertexArrays로 VAO를 bind한다.
- VBO(s)와 attribute pointer(s)를 bind/configure하고 다른 VAO의 나중사용을 위해 다시 unbind한다.
- 도형을 그리기 전에 VAO를 bind하기만 하면 된다.
// ..:: Initialization code (done once (unless your object frequently changes)) :: ..
// 1. bind Vertex Array Object
glBindVertexArray(VAO);
// 2. copy our vertices array in a buffer for OpenGL to use
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
// 3. then set our vertex attributes pointers
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 3 * sizeof(float),(void*)0);
glEnableVertexAttribArray(0);
[...]
// ..:: Drawing code (in render loop) :: ..
// 4. draw the object
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
someOpenGLFunctionThatDrawsOurTriangle();
- VBO가 사용할 vertex attribute configuration을 VAO가 저장하고 있다.
- 만약 여러 도형을 그리게 되는 경우, 모든 VAO를 generate/configure하고 추후 사용을 위해 저장하고 있으면 된다.
- 정리하면 지금 그리고 싶은 도형의 사용 전에 그 특정 VAO를 bind하고 그다음 도형을 그리고 unbind하면 된다.
5.5.2 The triangle we’ve all been waiting for
- 도형을 그릴때 OpenGL은 glDrawArrays를 제공한다. 이 함수를 통해 현재 active한 shader, 미리 전에 정의된 vertex attribute configuration 그리고 VBO's vertex data를 가지고 primitives를 그릴 수 있다.
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glDrawArrays(GL_TRIANGLES, 0, 3);
- glDrawArrays의 첫번째 인자는 우리가 그리고자 하는 OpenGL pritimitive type이다. 삼각형을 그릴 것 이기 때문에 GL_TRIANGLES를 전달한다.
- 두번째 인자는 그리고자 하는 vertex array의 첫번째 index이다.
- 마지막 인자는 몇개의 vertex를 그리고 싶은지 이다.
- 전체 소스코드
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <iostream>
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.x, aPos.y, aPos.z, 1.0);\n"
"}\0";
const char *fragmentShaderSource = "#version 330 core\n"
"out vec4 FragColor;\n"
"void main()\n"
"{\n"
" FragColor = vec4(1.0f, 0.5f, 0.2f, 1.0f);\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, // left
0.5f, -0.5f, 0.0f, // right
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);
// note that this is allowed, the call to glVertexAttribPointer registered VBO as the vertex attribute's bound vertex buffer object so afterwards we can safely unbind
glBindBuffer(GL_ARRAY_BUFFER, 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);
// uncomment this call to draw in wireframe polygons.
//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
// render loop
// -----------
while (!glfwWindowShouldClose(window)) {
// input
// -----
processInput(window);
// render
// ------
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
// draw our first triangle
glUseProgram(shaderProgram);
glBindVertexArray(VAO); // seeing as we only have a single VAO there's no need to bind it every time, but we'll do so to keep things a bit more organized
glDrawArrays(GL_TRIANGLES, 0, 3);
// glBindVertexArray(0); // no need to unbind it every time
// 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);
}
'ComputerScience > Computer Graphics' 카테고리의 다른 글
OpenGL - 6 Shaders (1) (0) | 2021.07.06 |
---|---|
OpenGL - 5 Hello Triangle (3) (0) | 2021.06.30 |
OpenGL - 5 Hello Triangle (1) (0) | 2021.06.29 |
OpenGL - 4 Hello Window (0) | 2021.06.25 |
OpenGL - 3 Creating a window (0) | 2021.06.22 |