티스토리 뷰
1. Indexed Draw의 컨셉
Indexed Drawing의 컨셉은 간단하다.
다음의 도형을 하나하나 그리려면 다음과 같이 3 * 3 = 9개의 vertex data 저장이 필요할 것이다.
그 다음 [0,1,2], [3,4,5], [6,7,8] 이렇게 primitive들을 구성하여 그릴 수 있을 것이다.
하지만 실제로 vertex는 6개이기 때문에 9개의 vertex에 대한 데이터를 저장하는 것은 낭비가 된다.
이는 자연스럽게 그릴 데이터가 커지면 그 낭비 또한 커지게 되는 결과가 나타난다.
따라서 정말 필요한 6개의 vertex data만 저장하고 그를 통해 도형을 그릴 방법이 필요하다.
이건 매우 간단한 아이디어로 해결할 수 있다.
그냥 각 6개의 vertex에 대해 index를 붙인 다음, 도형을 그릴 때 index number들만 call하여 그리면 된다.
예를 들어 위 도형의 vertex에 다음과 같이 index를 붙여보자.
그 다음 이 index를 이용하여 다음과 같이 그릴 순서 배열을 구성하면 된다.
[0, 4, 3,
4, 1, 5,
3, 5, 2]
이렇게 vertex에 index를 붙여 그리는 방법을 Indexed Draw 방식이라 부르고, 위의 순서 배열을 Index Buffer로 사용하게 된다.
Draw의 두 방식 - Ordered Draw와 Indexed Draw-에 대한 자세한 차이를 알고 싶다면 아래 글을 참고하는 것도 좋다.
https://gamedev.stackexchange.com/questions/133208/difference-in-gldrawarrays-and-gldrawelements
2. Indexed Draw OpenGL에서 구현하기
한 번 다음과 같은 사면체를 indexed draw로 그려보자.
완성된 코드를 바로 보고 싶은 사람은 아래의 repo를 참고하면 된다.
2.1. Vertex 및 Index buffer 구성하기
우선 아래와 같이 vertex 정보를 구성한다.
// NDC 좌표계에서 삼각형을 구성하는 vertex들의 위치.
GLfloat vertices[] = {
-1.0f, -1.0f, 0.0f,
0.0f, -1.0f, 1.0f,
1.0f, -1.0f, 0.0f,
0.0f, 1.0f, 0.0f
};
또한 어떻게 그릴 지에 대한 index 정보를 담은 indices 배열을 구성하자.
4개 중 3개씩 뽑아 면을 하나씩 구성하면 총 4개의 면이 나올 것이다.
unsigned int indices[] = {
0, 3, 1,
1, 3, 2,
2, 3, 0,
0, 1, 2
};
이제 이 index 정보들로 index buffer object를 구성하자.
이것 역시 Buffer Object이므로 VBO를 만들듯 버퍼 생성 - 버퍼 바인딩 - 버퍼 데이터 채우기 순서로 진행하면 된다.
이 때 target parameter에 GL_ELEMENT_ARRAY_BUFFER를 사용해준다.
// Index Buffer Object 생성 및 Bind.
glGenBuffers(1, &IBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
이 과정을 담은 CreateTriangle
함수 전체는 다음과 같다.
void IndexedDrawRunner::CreateTriangle()
{
unsigned int indices[] = {
0, 3, 1,
1, 3, 2,
2, 3, 0,
0, 1, 2
};
// NDC 좌표계에서 삼각형을 구성하는 vertex들의 위치.
GLfloat vertices[] = {
-1.0f, -1.0f, 0.0f,
0.0f, -1.0f, 1.0f,
1.0f, -1.0f, 0.0f,
0.0f, 1.0f, 0.0f
};
// VAO 생성 및 Bind - 이 밑에 생성되는 VBO들을 VAO에 자동으로 Bind.
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
// Index Buffer Object 생성 및 Bind.
glGenBuffers(1, &IBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 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);
// Index Buffer Object binding 다시 정리해주기
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
// VBO binding 다시 정리해주기
glBindBuffer(GL_ARRAY_BUFFER, 0);
// VAO binding 다시 정리해주기
glBindVertexArray(0);
}
2.2. IndexBuffer 이용한 Drawing - GLDrawElements
이제 필요한 것들을 준비했으니 Draw 하면 된다.
기존에는 glDrawArrays
를 통해 context에 binding된 VBO를 그렸다.
이제는 glDrawElements
를 통해 VAO context에 binding된 VBO를 IBO에 저장된 index buffer대로 그려주면 된다.
해당 부분을 포함한 렌더링 루프는 다음과 같이 구성한다.
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); // clear color buffer and depth buffer.
// glDrawArrays(GL_TRIANGLES, 0, 3);
glDrawElements(GL_TRIANGLES, 3 * 4, GL_UNSIGNED_INT, 0);
glfwSwapBuffers(mainWindow);
}
2.3. y축으로 Rotate 시켜 모든 면이 제대로 렌더링 되었는 지 확인하기
하지만 이렇게만 구성하면 하나의 면만 보일 것이다.
당연하다. z축으로 길게 있는 면은 가려지니까.
그래서 y축을 기점으로 회전해가며 3개의 면을 볼 수 있게 코드 구성을 변경해보자.
우선 vertex shader 및 fragment shader는 xyz위치에 따라 다른 rgb 값을 갖도록 다음과 같이 구성해보자.
vertex shader에서 위치 값의 vec3를 저장해 fragment shader로 넘겨주고 그것을 color로 띄우는 형식이다.
const char* IndexedDrawRunner::vShader =
"#version 330\n"
"layout (location = 0) in vec3 pos;\n"
"uniform mat4 model;\n"
"out vec4 vCol;\n"
"void main()\n"
"{\n"
" vCol = vec4(clamp(pos, 0.0f, 1.0f), 1.0f);\n"
" gl_Position = model * vec4(pos * 0.4f, 1.0);\n"
"}\n";
const char* IndexedDrawRunner::fShader =
"#version 330\n"
"out vec4 color;\n"
"in vec4 vCol;\n"
"void main()\n"
"{\n"
" color = vCol;\n"
"}\n";
또한 Rotate 가능하도록 Rotate 하는 코드를 추가해준다.
Rotate는 행렬을 이용한 간단한 선형 변환 연산으로 구현한다.
// RotateTriangle
void IndexedDrawRunner::RotateTriangle()
{
currentAngle += 0.50f;
if (currentAngle >= 360.0f)
{
currentAngle -= 360.0f;
}
glm::mat4 translateMatrix(1.0f); // identity matrix.
translateMatrix = glm::rotate(translateMatrix, currentAngle * toRads, glm::vec3(0.0f, 1.0f, 0.0f)); // 세 번째 파라미터는 회전축. y-axis
glUniformMatrix4fv(uniformModel, 1, GL_FALSE, glm::value_ptr(translateMatrix));
}
또한 Z축의 depth test 또한 가능하도록 변경해야 회전 시 뒷 쪽에 있는 면이 제대로 렌더링 될 것이다.
그것을 위해 우선 Glfw 및 Glew 환경을 준비할 때 depth test를 enable 시켜준다.
glEnable(GL_DEPTH_TEST); // enable depth testing.
이 다음에는 Render Loop에서 color buffer 만 비우지 말고 depth buffer도 함께 비워주도록 하자.
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); // clear color buffer and depth buffer.
2.4. 전체 구성
이제 전체 코드를 구성해보면 다음과 같다.
//
// Created by ypp06 on 2025-02-02.
//
#include "IndexedDrawRunner.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 IndexedDrawRunner::WIDTH = 800;
const GLint IndexedDrawRunner::HEIGHT = 600;
GLuint IndexedDrawRunner::VAO = 0;
GLuint IndexedDrawRunner::VBO = 0;
GLuint IndexedDrawRunner::IBO = 0;
GLuint IndexedDrawRunner::uniformModel = 0;
GLuint IndexedDrawRunner::shaderProgram = 0;
// Shader 소스 코드 정의
const char* IndexedDrawRunner::vShader =
"#version 330\n"
"layout (location = 0) in vec3 pos;\n"
"uniform mat4 model;\n"
"out vec4 vCol;\n"
"void main()\n"
"{\n"
" vCol = vec4(clamp(pos, 0.0f, 1.0f), 1.0f);\n"
" gl_Position = model * vec4(pos * 0.4f, 1.0);\n"
"}\n";
const char* IndexedDrawRunner::fShader =
"#version 330\n"
"out vec4 color;\n"
"in vec4 vCol;\n"
"void main()\n"
"{\n"
" color = vCol;\n"
"}\n";
const float IndexedDrawRunner::toRads = 3.14159265f / 180.0f;
// make currentAngle = 0;
IndexedDrawRunner::IndexedDrawRunner()
{
currentAngle = 0.0f;
}
// Add Shader to Program.
// Shader에 대해 1. source 지정, 2. compile, 3. targetProgram에 attach.
void IndexedDrawRunner::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 IndexedDrawRunner::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 IndexedDrawRunner::CreateTriangle()
{
unsigned int indices[] = {
0, 3, 1,
1, 3, 2,
2, 3, 0,
0, 1, 2
};
// NDC 좌표계에서 삼각형을 구성하는 vertex들의 위치.
GLfloat vertices[] = {
-1.0f, -1.0f, 0.0f,
0.0f, -1.0f, 1.0f,
1.0f, -1.0f, 0.0f,
0.0f, 1.0f, 0.0f
};
// VAO 생성 및 Bind - 이 밑에 생성되는 VBO들을 VAO에 자동으로 Bind.
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
// Index Buffer Object 생성 및 Bind.
glGenBuffers(1, &IBO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// 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);
// Index Buffer Object binding 다시 정리해주기
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
// VBO binding 다시 정리해주기
glBindBuffer(GL_ARRAY_BUFFER, 0);
// VAO binding 다시 정리해주기
glBindVertexArray(0);
}
// RotateTriangle
void IndexedDrawRunner::RotateTriangle()
{
currentAngle += 0.50f;
if (currentAngle >= 360.0f)
{
currentAngle -= 360.0f;
}
glm::mat4 translateMatrix(1.0f); // identity matrix.
translateMatrix = glm::rotate(translateMatrix, currentAngle * toRads, glm::vec3(0.0f, 1.0f, 0.0f)); // 세 번째 파라미터는 회전축. y-axis
glUniformMatrix4fv(uniformModel, 1, GL_FALSE, glm::value_ptr(translateMatrix));
}
// return NULL if failed.
GLFWwindow* IndexedDrawRunner::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;
}
glEnable(GL_DEPTH_TEST); // enable depth testing.
// Setup Viewport size.
glViewport(0, 0, bufferWidth, bufferHeight);
return mainWindow;
}
bool IndexedDrawRunner::Run()
{
GLFWwindow* mainWindow = ReadyGlfwGlewEnv();
if (!mainWindow)
{
return false;
}
CreateTriangle();
CompileShaders();
glUseProgram(shaderProgram);
glBindVertexArray(VAO);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, IBO);
// 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 | GL_DEPTH_BUFFER_BIT); // clear color buffer and depth buffer.
RotateTriangle();
// glDrawArrays(GL_TRIANGLES, 0, 3);
glDrawElements(GL_TRIANGLES, 3 * 4, GL_UNSIGNED_INT, 0);
glfwSwapBuffers(mainWindow);
}
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
glBindVertexArray(0);
glUseProgram(0);
return true;
}
그럼 다음과 같이 회전하는 정사면체가 잘 보일 것이다.
index buffer를 이용한 다면체 렌더링이 성공했다.
Refs
'그래픽스' 카테고리의 다른 글
[그래픽스] OpenGL Uniform Variables 선언과 이용 (0) | 2025.02.15 |
---|---|
[그래픽스]Hello Triangle - OpenGL에서 삼각형 띄워보기 (0) | 2025.02.12 |
[그래픽스] Rendering Pipeline 개요 (2) | 2025.02.09 |
[그래픽스] Hello Window - GLEW/GLFW 이용하여 창 띄우기 (0) | 2025.02.06 |
[그래픽스] OpenGL과 GLEW/GLFW (0) | 2025.02.03 |
- Total
- Today
- Yesterday
- Andrew ng
- 순환 신경망
- 이미지
- 이미지처리
- ML
- 컴퓨터 과학
- 사진구조
- gradient descent
- 딥러닝
- rnn
- NLP
- 연속 신호
- CS
- Neural Network
- 인덱스 이미지
- 자연어 처리
- 컴퓨터과학
- 머신 러닝
- 신호 및 시스템
- RGB이미지
- CNN
- 이산 신호
- 영상구조
- Logistic Regression
- 신경망
- 매트랩
- 머신러닝
- 매트랩 함수
- 영상처리
- 밑바닥부터 시작하는 딥러닝
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |