Texture

Date:     Updated:

카테고리:

태그:

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
image tex_coords
  • 먼저 이미지파일을 편하게 로딩할 수 있는 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

06_texture_wrapping

  • Wrapping에 관한 옵션은 texcoord가 [0,1] 범위를 벗어난 경우 어떻게 처리할 것인지 지정


Filtering

06_texture_filter_comparison

  • 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
Pasted image 20240719040556 Pasted image 20240719042022
  • 참고로 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
image image

위 그림은 512 x 512 크기의 격자 무늬 텍스처를 이용해 렌더링한 결과이다. Example1에서는 정상적인 격자 무늬가 보이지만, 사각형 object의 사이즈를 줄이면 이상한 무늬가 나타나게 된다. 이러한 문제는 오브젝트의 크기가 텍스처 사이즈보다 작을 때 발생하는데, 여러 텍스처 픽셀의 컬러 값이 interpolation되어 들어가기 때문이다. 따라서 해상도를 높이기 위해 무작정 큰 사이즈의 텍스처를 사용할 수는 없다.


06_mipmap

이러한 문제를 해결하기 위해 사용되는 방법이 바로 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

image


Texture 형태의 장점

Vertex의 데이터를 Texture 형태로 저장하는 방식의 장점이 무엇일까 생각해 보았다. 내가 생각한 내용들은 적은 vertex로도 높은 해상도 느낌을 낼 수 있다는 것,interpolation이 간단한 것, 그리고 모델을 생성할 때 아트분들 생산성에 관련한 부분 정도였다. 혹시 더 있을까 GPT에게 물어봤는데 생각하지 못한 내용들이 있었다.

  • 텍스처 형태의 2D데이터는 GPU 메모리 효율성이 매우 높다.
    • 일반적인 버퍼보다 훨씬 좋음
  • 필터링, 샘플링이 용이하다.
  • 렌더링 파이프라인의 여러 스테이지 사이 데이터 관리가 용이하다.
  • 특정 텍스처들을 조합해서 사용하는 등 유연성과 확장성이 높다.



맨 위로 이동하기

OpenGL 카테고리 내 다른 글 보러가기

댓글 남기기