갬장장이
[OpenGL] 9. Textures
갬장장이
갬장장이의 코드 대장간
갬장장이
전체
오늘
어제
  • 분류 전체보기 (216)
    • 게임 연구소 (6)
    • 게임 제작 (15)
      • We need more guns (2024~) (1)
      • Rovenhell (2023) (2)
      • Geophyte (2020~2021) (5)
      • 아드레날린 러시 (2021) (2)
      • Treadmill (2019) (1)
      • 습작들 (2019~) (2)
      • 그 외 (~2018) (2)
    • mathematics (33)
      • game mathematics (30)
      • linear algebra (3)
    • networking (3)
    • computer graphics (46)
      • 3D Graphics (23)
      • DirectX (8)
      • OpenGL (13)
      • graphics theory (2)
    • game engines (10)
      • Unity (0)
      • Unreal Engine (10)
    • os (6)
      • Linux (0)
      • operating system (1)
      • multithreading, parallel co.. (5)
    • lang (34)
      • c++ (15)
      • .NET (5)
      • python (1)
      • java (3)
      • erlang, elixir (1)
      • js, ts (7)
    • software engineering (47)
      • PS (25)
      • algorithms (15)
      • data structures (2)
      • design patterns (4)
    • cs (4)
    • database (2)
      • SQL (1)
    • web (6)
      • web (3)
      • frameworks, libraries (3)
    • finance (0)
    • 음악 제작 (1)
    • life (3)

블로그 메뉴

  • 홈
  • 태그
  • 방명록

공지사항

  • [공지] 블로그 안내

인기 글

최근 댓글

최근 글

hELLO · Designed By 정상우.
computer graphics/OpenGL

[OpenGL] 9. Textures

2022. 4. 9. 15:23

텍스쳐란?

오브젝트 표면에 입힐 수 있는 어떠한 이미지

 

실제 게임 엔진에서 png를 텍스쳐로 사용하는 경우는 많지 않지만, 본 포스트에선 편의를 위해 png 파일을 텍스쳐로 사용한다. (stb라는 라이브러리의 stb_image를 사용해 png를 로딩한다, 파일경로를 입력하면 rgba 픽셀들 array(버퍼) 포인터를 반환하는 역할을 하는 라이브러리 - https://github.com/nothings/stb) 그 후 이미지 버퍼를 opengl을 통해 gpu에 텍스쳐 값으로 전달후 pixel shader(=fragment shader)에도 전달해 렌더링한다.

 

해당 라이브러리를 사용하려면 단순히 stb_image.h를 프로젝트 어딘가에 넣으면 되는데, 이 때 해당 헤더 상단에 적혀있는 유의사항대로 사용 전 .c 또는 .cpp 파일 하나에 몇 가지 내용을 입력해두어야 한다.

라이브러리 적용이 끝났다면 코드와 함께 텍스쳐 구현을 살펴보자.

 

<Texture.h>

#pragma once

#include "Renderer.h"

class Texture
{
    private:
        unsigned int m_RendererID;
        std::string m_FilePath;
        unsigned char* m_LocalBuffer; // 텍스쳐 파일이 저장된 버퍼
        int m_Width, m_Height, m_BPP; // m_BPP = bits per pixel

    public:
        Texture(const std::string& path);
        ~Texture();

        void Bind(unsigned int slot = 0) const; 
        /*
        Bind()에 slot 파라미터 존재 이유: 
        OpenGL에서는 동시에 여러 개의 텍스쳐를 바인딩할 수 있다.
        보통 데스크탑 gpu는 32개, 모바일은 8개라고 한다.
        */
        void Unbind() const;

        inline int GetWidth() const { return m_Width; }
        inline int GetHeight() const { return m_Height; }
};

<Texture.cpp>

#include "Texture.h"

#include "vendor/stb_image/stb_image.h"

Texture::Texture(const std::string& path)
    : m_RendererID(0), m_FilePath(path), m_LocalBuffer(nullptr), m_Width(0), m_Height(0), m_BPP(0)
{
	// 이미지 로딩
    stbi_set_flip_vertically_on_load(1); 
    /*
    이미지를 버퍼로 읽을 때(로딩할 때) 세로로 뒤집힌 상태로 로딩하도록 설정함.
    OpenGl은 좌상단이 아니라 좌하단이 (0,0)임.
    일반적으로 png를 비롯한 대부분의 이미지 포맷들은 정보를 좌상단에서 우하단까지 내려가는 형태로 저장하기 때문에
    뒤집어서 읽어와야 텍스쳐가 뒤집히지 않고 제대로 출력됨.
    */
    m_LocalBuffer = stbi_load(path.c_str(), &m_Width, &m_Height, &m_BPP, 4); // 이미지 로딩
    // 해당 함수가 m_Width와 m_Height와 m_BPP에 값을 입력해줌. (Write)
    // 그 다음 파라미터인 desired channels의 경우 현재 우린 RGBA를 사용중이므로 4 입력하면 됨.
    // 자세한 건 라이브러리 내 해당 함수 참조
    
    // 텍스쳐
    GLCall( glGenTextures(1, &m_RendererID) ); // 텍스쳐 생성
    GLCall( glBindTexture(GL_TEXTURE_2D, m_RendererID) ); // 바인딩, NOTE: Bind without slot selection
	
    // 텍스쳐 설정
    /*
    glTexParameteri() - 여기서 i는 Integer 즉 정수형 파라미터를 수정하겠다는 의미이다.
    
    OpenGL에는 다양한 텍스쳐 파라미터가 있는데, 여기서는 4가지만 소개하겠다.
    주의할 점은 이 4개는 무조건 Specify해주어야 하고 안 할 경우 텍스쳐가 그냥 까맣게 출력될 수 있다!
    GL_TEXTURE_MIN_FILTER - 텍스쳐가 원본 픽셀 크기보다 더 작은 픽셀 크기로 렌더링되어야 할 때 어떤 식으로 축소할지
    만약 GL_LINEAR일 경우 선형적으로 축소시킨다.
    GL_TEXTURE_MAG_FILTER - 반대
    
    GL_TEXTURE_WRAP_S, GL_TEXTURE_WRAP_T - 텍스쳐에서의 S,T는 x, y를 의미한다.
    해당 파라미터는 텍스쳐를 가로, 세로로 입힐 때 어떻게 할지를 나타내며,
    현재는 CLAMP_TO_BORDER을 사용중이지만 TILING도 가능하다. (아래 사진 참고)
    */
    GLCall( glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR) );
    GLCall( glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR) );
    GLCall( glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE) );
    GLCall( glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE) );
	
    /*
    GLTexImage2D - 구체적인 내용은 이번 포스트의 범주를 넘어서므로 일단 다루지 않는다, 레퍼런스 참고
    */
    GLCall( glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, m_Width, m_Height, 0, GL_RGBA, GL_UNSIGNED_BYTE, m_LocalBuffer) );
    Unbind();

	// 텍스쳐를 만들었으므로 버퍼는 해제해준다
    if (m_LocalBuffer)
        stbi_image_free(m_LocalBuffer);
};

Texture::~Texture()
{
    GLCall( glDeleteTextures(1, &m_RendererID) );
}

void Texture::Bind(unsigned int slot) const
{
    GLCall( glActiveTexture(GL_TEXTURE0 + slot) ); // 텍스쳐 슬롯에 Active해준다 (GL_TEXTURE0은 첫번째 슬롯의 위치이다, 정수)
    GLCall( glBindTexture(GL_TEXTURE_2D, m_RendererID) );
}

void Texture::Unbind() const
{
    GLCall( glBindTexture(GL_TEXTURE_2D, 0) );
}

<참고사진>

이제 또 해야할 작업은 텍스쳐 좌표 (Texture Coordinates)와 Vertex로 이루어진 메쉬 좌표를 맵핑하는 것이다. 이를 텍스쳐 매핑(Texture mapping)이라고 한다.

*텍스쳐 좌표는 픽셀 갯수(크기) 등으로 나타내지 않고 그냥 좌하단 (0,0), 우상단(1,1) 으로 나타낸다.

 

<Application.cpp>

// Include GLEW
#include <GL/glew.h>

// Include GLFW
#include <GLFW/glfw3.h>

#include <iostream>

#include "VertexBuffer.h"
#include "VertexArray.h"
#include "IndexBuffer.h"
#include "Shader.h"
#include "Renderer.h"
#include "Texture.h"

GLFWwindow* InitWindow()
{
    // Initialise GLFW
    if( !glfwInit() )
    {
        fprintf( stderr, "Failed to initialize GLFW\n" );
        getchar();
        return nullptr;
    }

    glfwWindowHint(GLFW_SAMPLES, 4);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
    glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
    glfwWindowHint(GLFW_OPENGL_FORWARD_COMPAT, GL_TRUE); // To make MacOS happy; should not be needed
    glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);

    // Open a window and create its OpenGL context
    GLFWwindow* window = glfwCreateWindow( 1024, 768, "Tutorial 02 - Red triangle", NULL, NULL);
    if( window == NULL ){
        fprintf( stderr, "Failed to open GLFW window. If you have an Intel GPU, they are not 3.3 compatible. Try the 2.1 version of the tutorials.\n" );
        getchar();
        glfwTerminate();
        return nullptr;

    }
    glfwMakeContextCurrent(window);

    // Initialize GLEW
    glewExperimental = true; // Needed for core profile
    if (glewInit() != GLEW_OK) {
        fprintf(stderr, "Failed to initialize GLEW\n");
        getchar();
        glfwTerminate();
        return nullptr;
    }

    std::cout << "Using GL Version: " << glGetString(GL_VERSION) << std::endl;

    // Ensure we can capture the escape key being pressed below
    glfwSetInputMode(window, GLFW_STICKY_KEYS, GL_TRUE);

    return window;
}

int main( void )
{
    GLFWwindow* window = InitWindow();
    if (!window)
        return -1;

	/*
    텍스쳐 매핑을 위해 버퍼 값을 바꿔주었다.
    
    float positions[] = {
        -0.5f, -0.5f,//0
         0.5f, -0.5f,//1
         0.5f,  0.5f,//2
        -0.5f,  0.5f,//3
    };
    각 vertex 별로 2개씩 float값이 추가된 것을 볼 수 있다.
    이는 각 vertex에 텍스쳐 좌표를 매핑해준 것으로,
    좌하단 (-0.5f,-0.5f)인 0번 vertex에 텍스쳐 좌표 (0.0f, 0.0f)를 매핑해주었고,
    반대로 우상단 2번 vertex에는 (1.0f, 1.0f)를 매핑해주었음을 볼 수 있다.
    
    이렇게 버퍼 내 정보 저장하는 구조가 바뀌었으니 당연히 버퍼의 레이아웃도 바꿔주어야 한다. 이는 아래 10~20줄 가량의 코드에서 확인가능하다.
    또 Basic.shader 내에 있는 셰이더 코드에도 position 말고도 texture coordinates도 추가해주어야 한다.
    */
    float positions[] = {
        -0.5f, -0.5f, 0.0f, 0.0f, // 0
         0.5f, -0.5f, 1.0f, 0.0f, // 1
         0.5f,  0.5f, 1.0f, 1.0f, // 2
        -0.5f,  0.5f, 0.0f, 1.0f  // 3
    };

    unsigned int indices[] = {
        0, 1, 2,
        2, 3, 0
    };
	
    
    /*
    본 포스트의 범주를 넘어가지만, 아래 코드가 없으면 텍스쳐가 이상한 형태로 출력될 수 있음.
    블렌딩에 관한 내용은 다음 포스트에서 다루겠음.
    
    아래 두 줄의 코드는 OpenGL이 어떻게 Alpha 픽셀들을 블렌딩할 것인지를 정의해주는 것임.
    자세한 것은 다음 포스트에서 다루겠음
    */
    GLCall( glEnable(GL_BLEND) );
    GLCall( glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA) );


    {
        VertexArray va;
        VertexBuffer vb(positions, 4 * 4 * sizeof(float)); // 변경: 기존 4 * 2에서 증가
        IndexBuffer ib(indices, 6);

        VertexBufferLayout layout;
        layout.AddFloat(2);
        layout.AddFloat(2); // 변경: 텍스쳐좌표 float 2개가 추가되었으므로 코드 추가

        va.AddBuffer(vb, layout);

        Shader shader("res/shaders/Basic.shader");
        shader.Bind();

        Texture texture("res/textures/phone.png"); // 텍스쳐로 쓸 이미지 경로
        texture.Bind();
        shader.SetUniform1i("u_Texture", 0); // 셰이더에 uniform을 통해 텍스쳐를 전달한다. 하단의 Shader 소스코드 및 Basic.shader 참고

        Renderer renderer;

        do {
            renderer.Clear();
            renderer.Draw(va, ib, shader);

            // Swap buffers
            glfwSwapBuffers(window);
            glfwPollEvents();
        } // Check if the ESC key was pressed or the window was closed
        while( glfwGetKey(window, GLFW_KEY_ESCAPE ) != GLFW_PRESS &&
                glfwWindowShouldClose(window) == 0 );
    }

    // Close OpenGL window and terminate GLFW
    glfwTerminate();

    return 0;
}

 <Shader.cpp>

// 이번 포스트에서 작성한 유니폼 생성 함수 (integer 1개짜리)
void Shader::SetUniform1i(const std::string& name, int value)
{
    GLCall( glUniform1i(GetUniformLocation(name), value) );
}

<Basic.shader>

#shader vertex
#version 330 core
layout(location = 0) in vec4 position; 
/* 
참고) 앞서 우린 glVertexAttribPointer(0, ... )을 통해 0번 Attribute에 position을 배정했음.
고로 0번 attribute에서 position을 받아온다(in)는 의미임.
*/
layout(location = 1) in vec2 texCoord;

out vec2 v_TexCoord; // 1) out의 의미는, vertex shader (코드 상단)에서 값을 out시켜 fragment shader(코드 하단)에 전달한다는 의미임.

void main()
{
  gl_Position = position;
  v_TexCoord = texCoord; // 2) vertex shader가 가진 정보인 texCoord를 fragment shader에게 전달하기 위해 일단 v_TexCoord에 값을 out시킴.
}

#shader fragment
#version 330 core

in vec2 v_TexCoord; // 3) 그 후 in을 사용해 vertex shader에서 코드를 받아옴.
layout(location = 0) out vec4 color;
 
uniform sampler2D u_Texture;

void main()
{
  vec4 texColor = texture(u_Texture, v_TexCoord); // 4) 텍스쳐의 v_TexCoord 좌표 위치의 RGBA값 추출
  color = texColor; // fragment shader는 매 픽셀마다 연산된다는 것을 명심할 것. 고로 해당 픽셀의 색상을 해당 위치의 텍스쳐의 색상으로 변경해줌.
}

 

 

저작자표시 비영리 변경금지 (새창열림)

'computer graphics > OpenGL' 카테고리의 다른 글

[OpenGL] 11. OpenGL과 수학  (0) 2022.04.16
[OpenGL] 10. Blending  (0) 2022.04.16
[OpenGL] 8. 추상화(Abstraction)와 렌더러  (0) 2022.04.02
[OpenGL] 7. Vertex Array Object (VAO)  (0) 2022.03.01
[OpenGL] 6. Uniform  (0) 2022.02.27
'computer graphics/OpenGL' 카테고리의 다른 글
  • [OpenGL] 11. OpenGL과 수학
  • [OpenGL] 10. Blending
  • [OpenGL] 8. 추상화(Abstraction)와 렌더러
  • [OpenGL] 7. Vertex Array Object (VAO)
갬장장이
갬장장이
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.