학습을 위해 참조한 사이트
https://learnopengl.com/Getting-started/Textures
LearnOpenGL - Textures
Textures Getting-started/Textures We learned that to add more detail to our objects we can use colors for each vertex to create some interesting images. However, to get a fair bit of realism we'd have to have many vertices so we could specify a lot of colo
learnopengl.com
텍스쳐 좌표

텍스쳐 좌표는 2D 텍스쳐에서 각 x, y축에서 0~1의 범위를 사용하여 정의한다.
샘플링(sampling) : 텍스쳐 좌표를 사용하여 텍스쳐의 색상을 검색하는 것.
우선 정점 데이터에 텍스쳐 좌표를 추가하기 위해 다음과 같이 작성한다. 텍스쳐 좌표는 x, y 두 개의 좌표를 가지며, 따라서 각 정점에 대해 텍스쳐 좌표에 해당하는 텍스쳐가 매핑된다.
float vertices[] = {
// positions // texture coords
0.5f, 0.5f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.0f, 0.0f, 1.0f
};
직사각형에 대한 정점과 텍스쳐 좌표를 바탕으로 다음의 텍스쳐를 지정하여 보자.

EBO (요소 버퍼 객체)
텍스쳐를 매핑하기에 앞서서 매핑할 경계에 해당하는 직사각형을 그리기 위해서 첫 번째 글에서 설명하지 못했던 개념인 EBO(Element Buffer Object)를 사용할 것이다. EBO는 정점 객체 버퍼인 VBO와 그리기 위한 정보를 저장한다는 점은 비슷하지만, 그릴 정점을 결정하는 데 사용하는 인덱스를 저장하기 위한 버퍼 객체이다.

VBO와 마찬가지로 VAO를 바인딩한 이후에 EBO를 정의하여 VAO를 구성할 수가 있다. EBO 생성 및 VAO에 바인딩 과정은 다음과 같은 코드로 진행한다.
unsigned int VBO, VAO, EBO;
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
EBO의 경우 GL_ELEMENT_ARRAY_BUFFER를 대상으로 바인딩된다.
이제 EBO에 그려낼 정점 인덱스 정보를 추가적으로 정의한다.
// EBO에 저장할 인덱스 데이터
unsigned int indices[] = {
0, 1, 3, // 첫번째 삼각형을 위한 index
1, 2, 3 // 두번째 삼각형을 위한 index
};
앞서 정의한 정점 데이터 vertices에서 위치 정보와 함께 생각해보면, 첫 번째 position인 (0.5, 0.5. 0.0)을 인덱스 0번째로 보았을 때, 직사각형을 그리기 위해 두 개의 삼각형으로 분할하여 인덱스를 저장하였다. 이는 그리기 순서를 지정해 준 것과 동일하다고 볼 수 있다. EBO에 따른 렌더링 결과를 확인하기 위해 두 개의 삼각형을 와이어 프레임 적용하여 출력하면 다음과 같이 출력된다.

/* ...코드 생략 */
// wireframe mode
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
while (!glfwWindowShouldClose(window)) {
processInput(window);
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwTerminate();
return 0;
}
그리기를 위한 반복문에서 VAO에 바인딩된 EBO의 인덱스 데이터를 사용하기 위해 glDrawElements 함수를 호출하는 것을 확인할 수 있다. LearnOpenGL에서 설명하는 glDrawElements에 대한 정의는 다음과 같다.
void glDrawElements(GLenum mode, GLsizei count, GLenum type, const void * indices);
현재 바인딩된 VBO 및 EBO를 가져와서 EBO에 존재하는 인덱스에 지정된 순서대로 VBO에 있는 정점을 렌더링합니다. 또한 중복된 항목을 제거하고 그리기 순서를 지정하여 다양한 객체를 생성할 수 있다는 장점이 있습니다.
삼각형 2개를 사용하여 직사각형을 구성하고자 할 때, EBO를 사용하지 않는다면, 각 삼각형 당 위치 정보 3개 * 2, 즉 6개의 위치 정보가 필요하게 된다. 그러나 직사각형의 경우 정점 4개만으로 구성할 수 있기 때문에 직사각형의 꼭짓점의 해당하는 좌표만을 구성한 뒤, EBO를 통해 그리기 순서만 지정한다면 불필요한 오버헤드를 줄일 수 있다.
텍스쳐 불러오기
이미지를 로드할 수 있는 라이브러리인 stb_image.h를 사용한다.
https://github.com/nothings/stb/blob/master/stb_image.h
텍스쳐 생성
unsigned int texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
// Texture wrapping
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
// Texture filtering
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
int width, height, nrChannels; // 너비, 높이, 컬러 채널 수
unsigned char* data = stbi_load("wall.jpg", &width, &height, &nrChannels, 0);
if (data != 0)
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
std::cout << "Failed to load tecture" << std::endl;
}
stbi_image_free(data);
텍스쳐 래핑 : 텍스쳐 좌표의 범위를 벗어나는 경우, 옵션을 지정하여 텍스쳐 처리 결정
텍스쳐 필터링 : 텍스쳐 확대 및 축소 시 필터링 옵션을 통해 어떠한 텍스쳐 픽셀(텍셀)을 매핑할 것인지 결정
GL_NEAREST : 텍스쳐 좌표에서 가장 가까운 텍셀을 선택

GL_LINEAR : 이중 선형 필터링을 적용하여 현재 선택된 텍셀과 이웃한 텍셀에서 보간된 값을 구해 텍셀의 색상을 근사화

텍스쳐 적용
정점 데이터에 따라, OpenGL에 텍스쳐 좌표 또한 해석하는 방법을 지정해주어야 한다. glVertexAttribPointer 함수를 호출하여 두 번째 정점 속성에 대한 형식을 지정한다.
// position attribute
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// texture coords attribute
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
이제 정점 셰이더 & 프레그먼트 셰이더에서 텍스쳐를 처리하기 위한 코드를 작성한다.
/* vertex shader */
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec2 aTexCoord;
out vec3 ourColor;
out vec2 texCoord;
void main()
{
gl_Position = vec4(aPos, 1.0);
texCoord = aTexCoord;
}
/* fragment shader */
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 texCoord;
uniform sampler2D ourTexture;
void main()
{
FragColor = texture(ourTexture, texCoord);
}
정점 셰이더에서 텍스쳐 좌표를 프래그먼트 셰이더로 전달하고, 최종 색상은 GLSL의 texture 함수에 의해 바인딩된 텍스쳐가 텍스쳐 좌표에 따라 결정된다. 텍스쳐 객체를 저장하기 위해 uniform 형식의 sampler2D 타입으로 변수가 정의된 것을 확인할 수 있고, 현재 바인딩된 텍스쳐에 의해 ourTexture에 저장된다.
최종 코드
#include <glad/glad.h>
#include <GLFW/glfw3.h>
#include <string>
#include <iostream>
#include <fstream>
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#include "shader.h"
GLuint renderingProgram;
using namespace std;
// callback 함수
void framebuffer_size_callback(GLFWwindow* window, int width, int height)
{
glViewport(0, 0, width, height);
}
void processInput(GLFWwindow* window)
{
if (glfwGetKey(window, GLFW_KEY_ESCAPE) == GLFW_PRESS)
{
std::cout << "Pressed ESC" << std::endl;
glfwSetWindowShouldClose(window, true);
}
}
float vertices[] = {
// positions // texture coords
0.5f, 0.5f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.0f, 0.0f, 1.0f
};
unsigned int indices[] = {
0, 1, 3,
1, 2, 3
};
int main() {
glfwInit();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
GLFWwindow* window = glfwCreateWindow(800, 600, "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);
if (!gladLoadGLLoader((GLADloadproc)glfwGetProcAddress))
{
std::cout << "Failed to initialize GLAD" << std::endl;
return -1;
}
unsigned int VBO, VAO, EBO;
glGenBuffers(1, &VBO);
glGenBuffers(1, &EBO);
glGenVertexArrays(1, &VAO);
glBindVertexArray(VAO);
glBindBuffer(GL_ARRAY_BUFFER, VBO);
glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_STATIC_DRAW);
glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, EBO);
glBufferData(GL_ELEMENT_ARRAY_BUFFER, sizeof(indices), indices, GL_STATIC_DRAW);
// position attribute
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)0);
glEnableVertexAttribArray(0);
// texture coords attribute
glVertexAttribPointer(1, 2, GL_FLOAT, GL_FALSE, 5 * sizeof(float), (void*)(3 * sizeof(float)));
glEnableVertexAttribArray(1);
// texture load & settings
unsigned int texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
int width, height, nrChannels;
unsigned char* data = stbi_load("wall.jpg", &width, &height, &nrChannels, 0);
if (data != 0)
{
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);
Shader ourShader("shaders/vertShader.glsl", "shaders/fragShader.glsl");
ourShader.use();
while (!glfwWindowShouldClose(window)) {
processInput(window);
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT);
glBindTexture(GL_TEXTURE_2D, texture);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
glfwSwapBuffers(window);
glfwPollEvents();
}
glfwTerminate();
return 0;
}
출력 결과는 다음과 같다.

'컴퓨터그래픽스' 카테고리의 다른 글
[OpenGL 공부] Transformations (0) | 2024.01.20 |
---|---|
[OpenGL 공부] Textures (exercises) (0) | 2024.01.16 |
[OpenGL 공부] Textures (2) (1) | 2024.01.15 |
[OpenGL 공부] Shader (0) | 2024.01.13 |
[OpenGL 공부] Hello Triangle (1) | 2024.01.06 |