OpenGL/공부

[OpenGL] Textures (4) - 로드(stb_image), 생성, 바인딩(Texture Unit)

ciel45 2024. 6. 13. 17:53

이번엔 코딩을 통해 실제로 텍스쳐를 입혀볼 것이다.

 

먼저 디스크에 저장된 이미지 파일을 메인 메모리에 로드해야하는데, 처음이면서도 가장 어려운 부분이다.

다행히도 이걸 손쉽게 할 수 있게 해주는 라이브러리가 있다.

 

stb라는 라이브러리로, 헤더 파일 하나로만 이루어진 매우 가벼운 라이브러리이다.

https://github.com/nothings/stb/blob/master/stb_image.h

 

stb/stb_image.h at master · nothings/stb

stb single-file public domain libraries for C/C++. Contribute to nothings/stb development by creating an account on GitHub.

github.com

 

stb_image.h 하나만 다운로드 받아 프로젝트 내 main.cpp가 있는 폴더에 넣어주면 된다.

 

 

그리고 main.cpp 상단에 #define을 추가해준다.

#define STB_IMAGE_IMPLEMENTATION

 

STB_IMAGE_IMPLEMENTATION을 정의함으로써 전처리기가 stb_image.h를 수정하여, 중요한 definition 소스 코드만 포함하도록 한다. 이를 통해 헤더를 효과적으로 .cpp 파일로 변환한다.

 

이제 stb_image.h를 쓰고싶은 곳에 include하여 사용할 수 있다.

 

 

 

텍스쳐로 쓸 이미지로 두 분을 모셨다.

 

 

프로젝트 폴더 내 Textures 폴더를 만들고, 안에 각각 저장해두었다.

 

 

이제 앞서 만들어둔 피라미드 모델에 두 텍스쳐를 각각 적용해볼 것이다.

 

먼저 Texture 클래스를 만들었다.

 

Texture.h

#pragma once
#include <GL/glew.h>
#include "stb_image.h"

class Texture
{
public:
	Texture(const char* fileLoc);

	void LoadTexture(); // 디스크에 저장된 이미지를 메모리에 텍스쳐로 로드
	void UseTexture(); // 렌더링할 때 이 텍스쳐를 쓰겠다고 설정
	void ClearTexture(); // 메모리에서 텍스쳐 내리기

	~Texture();

private:
	GLuint textureID;
	int width, height, bitDepth;

	char fileLocation[128]; // 파일 경로
};

 

Texture.cpp

#include <string>
#include "Texture.h"

Texture::Texture(const char* fileLoc)
{
	textureID = 0;
	width = 0;
	height = 0;
	bitDepth = 0;

	strcpy(fileLocation, fileLoc);
}

void Texture::LoadTexture()
{
	// 디스크에서 이미지 로드, 포맷은 unsigned char[]
	unsigned char* texData = stbi_load(fileLocation, &width, &height, &bitDepth, 0);
	if (!texData)
	{
		printf("텍스쳐 로드 실패 : %s\n", fileLocation);
		return;
	}
	else
	{
		printf("텍스쳐 로드 성공\n");
	}

	// VRAM 내부에 텍스쳐를 담는 공간 생성
	glGenTextures(1, &textureID);
	glBindTexture(GL_TEXTURE_2D, textureID);

	// 파라미터들 설정
	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);

	// VRAM으로 텍스쳐 쏴주기
	glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, texData);

	// 밉맵 생성
	glGenerateMipmap(GL_TEXTURE_2D);

	glBindTexture(GL_TEXTURE_2D, 0);

	stbi_image_free(texData);
}

void Texture::UseTexture()
{
	glActiveTexture(GL_TEXTURE0); //0번 텍스쳐 유닛 활성화
	glBindTexture(GL_TEXTURE_2D, textureID); // VRAM 내에 있는 이 텍스쳐를 0번 텍스쳐 유닛에 bind

}

void Texture::ClearTexture()
{
	glDeleteTextures(1, &textureID);
	textureID = 0;
	width = 0;
	height = 0;
	bitDepth = 0;

	fileLocation[0] = '\0';
}

Texture::~Texture()
{
	ClearTexture();
}

 

 

LoadTexture는 이미지를 텍스쳐로써 메모리에 올리는 함수이다.

 

stbi_load 함수를 통해 이미지를 불러와 unsigned char 배열 texData에 저장하였다.

이미지는 본래 한 픽셀당 R, G, B, A 각각 한 바이트 씩 4바이트로 나타나지는 바이트의 배열이기 때문이고, char는 byte와 같은 의미이다.

 

함수 호출 시 파라미터로 width, height, bitDepth를 레퍼런스로 넘겨주었는데, 이러면 stbi_load 함수가 이미지에 맞게 저 값들을 채워준다.

 

그 아래 glGenTextures()는 glGenBuffers()랑 매우 비슷한 함수이다. VRAM에 공간을 만들어주는 역할을 한다.

원래 VRAM에 리소스를 담아놓는 것이 텍스쳐가 원조이다. 그래서 사실 glGenTextures가 glGenBuffers의 조상이다.

 

glTexParameteri는 wrapping mode, filtering method 등을 설정하는 함수이다.

 

glTexImage2D가 실제로 메모리에서 앞서 만든 VRAM상 공간으로 텍스쳐를 쏴주는 함수이다.

파라미터로 GL_RGB 포맷이 들어갔는데, 준비한 이미지는 알파 값이 없는 jpg 파일이기 때문이다.

 

항상 이미지에 맞는 포맷을 넣어주어야 하며, 잘못 지정할 시(저기에 GL_RGBA를 넣는다거나) 프로그램이 터질 수 있다.

 

minification 필터링으로 mipmap을 사용할 것이기 때문에 glGenerateMipmap을 호출하였고,

마지막엔 텍스쳐 바인딩을 풀고 texData는 이제 필요 없으므로 비워주었다.

 

 

UseTexture는 텍스쳐 유닛에 VRAM 상의 텍스쳐를 연결시켜 주는 역할을 한다.

기본적으로 GPU에서 텍스쳐들은 텍스쳐 유닛이 소유하고 있기 때문이다.

 

glActiveTexture(GL_TEXTURE0)은 0번 유닛을 활성화시켜준다는 의미이고, glBindTexture는 그 활성화된 유닛에 텍스쳐를 달아주는 일을 한다.

 

GPU에는 0번~31번 까지의 텍스쳐 유닛들이 있고, 비싼 GPU의 경우 그 수가 더 많다.

 

 

 

이렇게 텍스쳐를 로드하여 VRAM에 쏴주고, GPU 내 텍스쳐 유닛에 달아주는 일까지 하였다.

 

이제 쉐이더를 통해 직접 텍스쳐를 따와 입힐 차례이다.

 

(다음 포스팅으로 이어짐)