본문 바로가기

컴퓨터그래픽스

[OpenGL 공부] Textures (1)

학습을 위해 참조한 사이트

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
};

직사각형에 대한 정점과 텍스쳐 좌표를 바탕으로 다음의 텍스쳐를 지정하여 보자.

wall.jpg

EBO (요소 버퍼 객체)

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

VAO, VBO, EBO의 관계

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_NEAREST 적용 시

 

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

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