텍스처를 다루기 위해서는 텍스처를 어떤 방식으로 처리할지를 설정해주어아야한다.
예를들어, 텍스처가 설정된 0~1 사이의 좌표값을 벗어나는 경우에 대해 어떻게 처리할지를 설정하는 래핑 모드 혹은 샘플들의 중간값을 어떻게 계산할지를 정하는 필터링 모드 가 있다.
이러한 설정들을 관리하는 객체가 Sampler 이다.
GLSL에서는 텍스처를 유니폼으로 가져와서 사용했었다. 그 때 그 유니폼은,
uniform sampler2D tex;
이런 식으로 처리했었다.
그렇다. 난 샘플러라는 단어를 GLSL에서 처음 보았고, 텍스처와 샘플러는 동의어라고 생각을 하고 있었다.
하지만 둘은 명확히 다른 의미를 지닌다.
샘플러(Sampler)와 텍스처(Texture)는 그래픽스 프로그래밍에서 텍스처 데이터를 처리하고 화면에 표시할 때 중요한 개념이지만, 서로 다른 역할을 합니다. 그 차이를 정리하면 다음과 같습니다.
텍스처 (Texture)
• 정의: 텍스처는 실제 이미지 데이터(픽셀 또는 텍셀)를 저장하고 있는 객체.
이 데이터는 렌더링 시 표면에 입혀지거나 사용.
• 역할: 2D 이미지, 3D 볼륨 데이터 또는 다른 형태의 텍스처 데이터를 GPU에 저장
샘플러 (Sampler)
• 정의: 샘플러는 텍스처 좌표에서 텍스처 데이터를 어떻게 가져올지(샘플링할지) 정의하는 객체. 즉, 텍스처 데이터를 참조하는 방법을 제공하는 일종의 필터나 설정 모음.
• 역할: 텍스처 좌표(UV)에서 정확히 어떤 텍셀 값을 가져올지(보간, 필터링, 반복 방식 등)를 제어.
• 주요 기능:
• 필터링 모드: 텍스처를 확대 또는 축소할 때 사용하는 필터링 방법을 설정합니다. (예: GL_NEAREST, GL_LINEAR)
• 랩핑 모드: 텍스처 좌표가 0~1 범위를 벗어났을 때 어떻게 처리할지 설정합니다. (예: GL_REPEAT, GL_CLAMP_TO_EDGE)
• Mipmap: 텍스처의 여러 해상도 레벨을 저장하여 원근감을 표현할 때 사용되며, 샘플러에서 이를 사용할지 설정할 수 있습니다.
이렇듯 샘플러는 텍스처의 설정을 담는 객체 역할을 한다. 따라서 다른 객체와 마찬가지로 아래 함수들을 가지고 있다.
glGenSamplers(GLsizei n, GLuint * samples);
glSamplerParameteri(GLuint sampler, GLenum pname, GLint param);
glSamplerParameterf(GLuint sampler, GLenum pname, GLfloat param);
glBindSampler(GLuint unit, GLuint sampler);
위에 사용된 샘플러 함수들은 OpenGL에서 샘플러 오브젝트를 생성하고 설정하는데 사용된다. 샘플러 오브젝트는 텍스처의 필터링 및 래핑 모드 같은 설정을 텍스처와 독립적으로 관리할 수 있게 도와준다. 각 함수의 역할을 하나씩 살펴보자.
glGenSamplers(GLsizei n, GLuint *samplers)
• 역할: 새로운 샘플러 오브젝트를 생성하는 함수다.
• 인자:
n: 생성할 샘플러 오브젝트의 개수를 지정한다.
samplers: 생성된 샘플러 오브젝트의 ID를 저장할 변수(혹은 배열)를 가리킨다. 생성된 샘플러 ID가 sampler 변수에 저장된다.
glSamplerParameteri(GLuint sampler, GLenum pname, GLint param)
• 역할: 생성된 샘플러 오브젝트의 특정 설정(필터링 또는 래핑 모드)을 지정하는 함수.
• 인자:
sampler: 설정을 적용할 샘플러 오브젝트의 ID.
pname: 설정하려는 속성의 종류를 지정한다.
param: 속성에 적용할 값.
glBindSampler(GLuint unit, GLuint sampler)
• 역할: 특정 텍스처 유닛에 샘플러 오브젝트를 바인딩한다.
• 인자:
unit: 샘플러를 바인딩할 텍스처 유닛의 번호다.
sampler: 바인딩할 샘플러 오브젝트의 ID다. 이 샘플러가 지정한 유닛에서 텍스처의 필터링 및 래핑 모드를 정의한다.
이처럼 샘플링 객체에 샘플링 세팅을 입력해줄 수 있었다.
하지만 나는 강의에서 이런 샘플링 함수들을 사용하지 않고, glTexParameteri 와 같은 함수를 통해 직접 텍스처에 설정값을 입력했었다.
이런 방식은 텍스처 자체에 내장되어있는 샘플러에 설정값을 입력해주는 방식이다. 텍스처 각각에 샘플링방식을 내장하여 별도의 샘플러 객체 없이 사용할 수 있었다.
다소 유연성은 떨어질 수 있으나, 간편하고 빠르게 작성할 수 있는 방식이었다.
텍스처를 여러개 사용하려면 여러번 만들어서 여러번 바인딩해서 여러번 유니폼으로 보내주면 된(?)다.
너무나 당연하지만 한가지 주의할점은, 현재 컨텍스트에 텍스처를 바인딩 하기 전에 glActiveTexture 함수를 사용하여 현재 선택할 텍스처를 지정해줘야한다는 점이다.
uint32_t textures[3];
glGenTexture(3, &textures);
for (auto i = 0; i < 3; i++)
{
glActiveTexture(GL_TEXTURE0 + i);
glBindTexture(GL_TEXTURE_2D, textures[i]);
}
이후 바인딩된 텍스처와 쉐이더 내에 사용되고 있는 유니폼 샘플러와 연결해줘야한다.
glUniform1i(glGetUniformLocation(m_program->Get(), "tex1"), 0);
glUniform1i(glGetUniformLocation(m_program->Get(), "tex2"), 1);
현재 프로그램에서 사용되고 있는 쉐이더를 지정해야해서, m_program->Get()을 통해 프로그램의 주소를 첫 번째 인자로 받고, 해당 프로그램 내의 쉐이더에 담긴 유니폼의 이름을 두번째 인자로 넣어 glGetUniformLocation 함수를 동작시키자.
이에 유니폼의 주소를 리턴하게 되면, 각 텍스처가 몇번 텍스처 슬롯에 담겼는지(GL_TEXTURE0, GL_TEXTURE1 , ...) 를 명시해주기 위해 정수를 입력해준다.
예를 들어, 위의 코드는 tex1이라는 유니폼 샘플러는 GL_TEXTURE0 에 담긴 텍스처를 이용하게 하는 것이 된다.