Texture
카테고리: OpenGL
태그: Graphics
Main Reference
- Learn OpenGL
- Rinthel Kwon - OpenGL Lecture
std Library
# stb
ExternalProject_Add(
dep_stb
GIT_REPOSITORY "https://github.com/nothings/stb"
GIT_TAG "master"
GIT_SHALLOW 1
UPDATE_COMMAND ""
PATCH_COMMAND ""
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
TEST_COMMAND ""
INSTALL_COMMAND ${CMAKE_COMMAND} -E copy
${PROJECT_BINARY_DIR}/dep_stb-prefix/src/dep_stb/stb_image.h
${DEP_INSTALL_DIR}/include/stb/stb_image.h
)
set(DEP_LIST ${DEP_LIST} dep_stb)
stb Library | OpenGL TexCoord |
---|---|
- 먼저 이미지파일을 편하게 로딩할 수 있는 stb 라이브러리를 설치한다(cmake 이용).
- 아니 OpenGL은 이미지 로드 함수도 없나..?
- 참고로 stb는 사람 이름..
- 참고로 OpenGL의 texture coordinate는 왼쪽 아래가 (0,0)임
- 일반적인 이미지는 왼쪽 위가 (0,0)
- image load할 때 vertically_flip 옵션 적용해서 로드
Texture Loading
OpenGL에서 Texture를 사용하는 방법은 다음과 같다.
// texture
auto image = Image::Load("./image/container.jpg");
if (!image)
return false;
SPDLOG_INFO("image: {}x{}, {} channels", image->GetWidth(), image->GetHeight(), image->GetChannelCount());
glGenTextures(1, &m_texture);
glBindTexture(GL_TEXTURE_2D, m_texture);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image->GetWidth(), image->GetHeight(), 0,
GL_RGB, GL_UNSIGNED_BYTE, image->GetData());
1) OpenGL Texture Object 생성 및 바인딩 2) 샘플링 옵션 설정 3) 텍스처 이미지 데이터를 GPU로 복사 4) Fragment Shader에 Uniform 형태로 텍스처 이미지 전달
생성 및 바인딩
uint32_t m_texture;
glGenTextures(1, &m_texture);
glBindTexture(GL_TEXTURE_2D, m_texture);
- VBO와 마찬가지로
glGenXXXX
함수를 통해 OpenGL Object로 관리
샘플링 옵션
// 뒤에 i가 붙은 이유는 설정의 enum값들이 int형이기 때문
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
Wrapping
- Wrapping에 관한 옵션은 texcoord가 [0,1] 범위를 벗어난 경우 어떻게 처리할 것인지 지정
Filtering
- Filtering 옵션은 Mipmap에 관한 옵션 (Interpolation 어떻게 할 것인가)
- 아래에서 따로 다룰 예정..
텍스처 데이터 GPU로 보내기
void glTexImage2D(GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const void *data);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, image->GetWidth(), image->GetHeight(), 0,
GL_RGB, GL_UNSIGNED_BYTE, image->GetData());
- target : 대상이 될, 앞서 바인딩한 텍스처
- level : Mapmap에서 사용되는 level
- internalFormat : 텍스처의 픽셀 포맷
- border : 텍스처 외곽에 border설정 (pixel 크기)
- format : 입력하는 이미지 픽셀 포맷
- data : 텍스처 데이터가 저장된 메모리 주소(CPU)
정리하자면 파라미터의 앞 부분은 GPU가 어떻게 읽어야 하는가 + 뒷 부분은 텍스처 이미지 데이터의 형식.
GL_RGB | GL_RED |
---|---|
- 참고로 internalFormat값을 바꾸면 특정 채널의 값들만 넘겨줄 수 있음
Example
// pos, color, st(texcoord)
float vertices[] = {
0.5f, 0.5f, 0.0f, 1.0f, 0.0f, 0.0f, 1.0f, 1.0f,
0.5f, -0.5f, 0.0f, 0.0f, 1.0f, 0.0f, 1.0f, 0.0f,
-0.5f, -0.5f, 0.0f, 0.0f, 0.0f, 1.0f, 0.0f, 0.0f,
-0.5f, 0.5f, 0.0f, 1.0f, 1.0f, 0.0f, 0.0f, 1.0f,
};
m_vertexLayout = VertexLayout::Create();
m_vertexBuffer = Buffer::CreateWithData(GL_ARRAY_BUFFER, GL_STATIC_DRAW, vertices, sizeof(float) * 32);
m_vertexLayout->SetAttrib(0, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 8, 0);
m_vertexLayout->SetAttrib(1, 3, GL_FLOAT, GL_FALSE, sizeof(float) * 8, sizeof(float) * 3);
m_vertexLayout->SetAttrib(2, 2, GL_FLOAT, GL_FALSE, sizeof(float) * 8, sizeof(float) * 6);
- 먼저 vertices 데이터에 texCoord 데이터가 추가되므로 VAO의 setattrib이 달라진다.
- stride는 3+3+2=8, 각 offset은 3,3,2
// vertex shader
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTexCoord;
out vec4 vertexColor;
out vec2 texCoord;
void main() {
gl_Position = vec4(aPos, 1.0);
vertexColor = vec4(aColor, 1.0);
texCoord = aTexCoord;
}
// fragment shader
#version 330 core
in vec4 vertexColor;
in vec2 texCoord;
out vec4 fragColor;
uniform sampler2D tex;
void main() {
fragColor = texture(tex, texCoord);
}
- vertex shader에서는 layout location을 이용해 texcoord값 받아올 수 있음
- CPU에서
glTexImage2D
로 넘겨준 텍스처는 shader에서uniform sampler2D
로 받아올 수 있음
Mipmap
Example1 | Example2 |
---|---|
위 그림은 512 x 512 크기의 격자 무늬 텍스처를 이용해 렌더링한 결과이다. Example1에서는 정상적인 격자 무늬가 보이지만, 사각형 object의 사이즈를 줄이면 이상한 무늬가 나타나게 된다. 이러한 문제는 오브젝트의 크기가 텍스처 사이즈보다 작을 때 발생하는데, 여러 텍스처 픽셀의 컬러 값이 interpolation되어 들어가기 때문이다. 따라서 해상도를 높이기 위해 무작정 큰 사이즈의 텍스처를 사용할 수는 없다.
이러한 문제를 해결하기 위해 사용되는 방법이 바로 Mipmap이다. Mipmap은 위 그림처럼 작은 사이즈 버전의 텍스처를 미리 준비하는 기법이다.
- 가장 큰 이미지(원본)을 기본 레벨 0으로 지정
- 가로/세로 절반씩 줄인 이미지를 미리 준비 (레벨 + 1)
- 가능한 모든 레벨의 텍스처를 준비해도 원본 메모리의 1/3 이내의 메모리만 추가됨(등비수열로 계산..)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
GL_TEXTURE_MIN_FILTER
: 오브젝트가 텍스처 사이즈보다 작은 경우GL_NEAREST_MIPMAP_NEAREST
: 적합한 레벨의 텍스처를 선택한 후 가장 가까운 픽셀 선택GL_LINEAR_MIPMAP_LINEAR
: 적합한 두 레벨의 텍스처에서 각각 linear interpolation한 값을 다시 interpolation
GL_TEXTURE_MAG_FILTER
: 오브젝트가 텍스처 사이즈보다 큰 경우GL_LINEAR
: liear interpolation으로 계산
Multiple Textures
// context.cpp
auto image = Image::Load("./image/container.jpg");
m_texture = Texture::CreateFromImage(image.get());
auto image2 = Image::Load("./image/awesomeface.png");
m_texture2 = Texture::CreateFromImage(image2.get());
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_texture->Get());
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, m_texture2->Get());
m_program->Use();
glUniform1i(glGetUniformLocation(m_program->Get(), "tex"), 0);
glUniform1i(glGetUniformLocation(m_program->Get(), "tex2"), 1);
// fragment shader
uniform sampler2D tex;
uniform sampler2D tex2;
void main() {
fragColor = texture(tex, texCoord) * 0.8 + texture(tex2, texCoord) * 0.2;
}
- OpenGL에서 하나의 shader는 최대 32개의 texture 슬롯 사용
glActiveTexture(textureSlot)
함수로 현재 다루고자 하는 텍스처 슬롯 선택glBindTexture(textureType, textureId)
함수로 현재 설정중인 텍스처 슬롯에 텍스처 오브젝트 바인딩glGetUniformLocation
&glUniform1i
함수로 uniform 변수 넘겨주기- 텍스처 슬롯 인덱스 넘겨주면 sampler2D에서 잘 받아옴
Result
Texture 형태의 장점
Vertex의 데이터를 Texture 형태로 저장하는 방식의 장점이 무엇일까 생각해 보았다. 내가 생각한 내용들은 적은 vertex로도 높은 해상도 느낌을 낼 수 있다는 것,interpolation이 간단한 것, 그리고 모델을 생성할 때 아트분들 생산성에 관련한 부분 정도였다. 혹시 더 있을까 GPT에게 물어봤는데 생각하지 못한 내용들이 있었다.
- 텍스처 형태의 2D데이터는 GPU 메모리 효율성이 매우 높다.
- 일반적인 버퍼보다 훨씬 좋음
- 필터링, 샘플링이 용이하다.
- 렌더링 파이프라인의 여러 스테이지 사이 데이터 관리가 용이하다.
- 특정 텍스처들을 조합해서 사용하는 등 유연성과 확장성이 높다.
댓글 남기기