본문 바로가기

Game/Graphics

Learn OpenGL - Getting started : Textures

link : https://learnopengl.com/Getting-started/Textures


Textures


 객체에 더 많은 디테일을 추가하기 위해 각 정점에 색상을 사용해 흥미로운 이미지를 만들 수 있음을 알게 되었다.


그러나 사실적인 느낌을 얻기 위해서는 많은 정점을 가져야하므로 많은 색상을 지정할 수 있다.


각 모델에는 더 많은 꼭지점이 필요하고, 각 꼭지점마다 색상 속성이 필요하기 때문에


상당한 추가 오버 헤드가 요구된다.



 예술가와 프로그래머가 일반적으로 선호하는 것은 텍스처를 사용하는 것이다.


텍스처는 오브젝트에 디테일을 추가하는 데 사용되는 2D이미지이다. (1D 및 3D 텍스처도 있다)


좋은 벽돌 이미지가 있는 종이 조각처럼 텍스처를 3D 집 위에 깔끔하게 접어서 집에 돌 외관이 있는 것처럼


보이게 하라. 우리는 하나의 이미지에 많은 디테일을 삽입할 수 있기 때문에 여분의 꼭지점을 지정하지


않고도 오브젝트가 매우 세밀하게 묘사되어 있다는 착각을 하게 만들 수 있다.

이미지를 제외하고 텍스처는 많은 양의 데이터를 저장해 쉐이더에 보낼 수 있지만, 우리는 이 것을 다른 주제를 위해 남겨둘 것이다.


 아래에서 이전 튜토리얼의 삼각형에 매핑된 벽돌 벽의 텍스처 이미지를 볼 수 있다.




 텍스처를 삼각형에 매핑하기 위해서 삼각형의 각 꼭지점에 텍스처의 어느 부분이 해당하는지 알려줄


필요가 있다. 따라서 각 정점에는 샘플링 할 텍스처 이미지의 부분을 지정하는 텍스처 좌표가 있어야한다.


조각 보간은 나머지 조각들에 대해 나머지 작업을 수행한다.



 텍스처 좌표의 범위는 x와 y축에서 0에서 1까지이다. (2D 이미지를 사용함을 기억해라)


텍스처 좌표를 사용해 텍스처 색상을 가져 오는 것을 샘플링이라고 한다. 


텍스처 좌표는 텍스처 이미지의 좌상 구석의 (0,0)으로부터 우상 구석 (1,1)로 시작한다.


다음 이미지는 텍스처 좌표를 삼각형에 매핑하는 방법을 보여준다.



삼각형에 대해 3개의 텍스처 좌표 점을 지정한다. 우리는 삼각형의 왼쪽 하단이 텍스처의 왼쪽 하단과


일치하도록 삼각형의 왼쪽 하단 정점에 (0,0) 텍스처 좌표를 사용한다.


텍스처 좌표가 (1,0)인 오른쪽 하단에도 동일하게 적용된다. 삼각형의 꼭대기는 텍스처 이미지의


꼭대기와 일치해야하므로 텍스처 좌표로 (0.5, 1.0)을 취한다.


우리는 정점 쉐이더에 3개의 텍스처 좌표를 전달하기만하면 각 쉐이더를 다른 쉐이더에 전달해


각 조각의 모든 텍스처 좌표를 깔끔하게 보간한다.


결과 텍스처는 다음과 같이 보인다:


float texCoords[] = {
    0.0f, 0.0f,  // lower-left corner  
    1.0f, 0.0f,  // lower-right corner
    0.5f, 1.0f   // top-center corner
};

 텍스처 샘플링은 느슨한 해석을 가지고 있으며 여러 가지 방법으로 수행 할 수 있다.


따라서 OpenGL에게 텍스처를 샘플링하는 방법을 알려주는것이 우리의 임무이다.





Texture Warpping


 텍스처 좌표는 대개 (0,0)에서 (1,1)까지이지만 범위 밖의 좌표를 지정하면 어떻게 될까?


OpenGL의 기본 동작은 텍스처 이미지를 반복하는 것이다. 그러나 OpenGL에서 제공하는 더 많은 옵션이 있다.


    - GL_REPEAT : 텍스처의 기본 동작이다. 텍스처 이미지를 반복한다.


    - GL_MIRRORED_REPEAT : GL_REPEAT과 동일하지만 반복 할 때마다 이미지를 반영한다.


    - GL_CLAMP_TO_EDGE : 0과 1사이의 좌표를 꽉 문다. 결과적으로 높은 좌표가 가장자리에 고정되어 가장자리 패턴이 늘어난다.


    - GL_CLAMP_TO_BORDER : 범위 밖의 좌표에 사용자가 지정한 테두리 색이 지정된다. 기본 범위 밖의 텍스처 좌표를

                                        사용할 때 각 옵션에는 다른 시각적 출력이 있다.


샘플 텍스처 이미지에서 어떻게 보이는지 보자:

 앞서 언급한 각 옵션은 glTexParameter *  함수로 좌표축(s,t +{3D 텍스처를 사용하는 경우에는r,x,y,z 와 동등}) 별로 설정이 가능하다.

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);

 첫 번째 인수는 텍스처 대상을 짖정한다. 텍스처 타겟이 GL_TEXTURE_2D가 되도록 2D 텍스처로 작업하고 있다.


두 번째 인수는 우리가 설정하고자 하는 옵션과 텍스처 축을 알려준다. WRAP 옵션을 구성하고 S 및 T 축 모두에 대해 지정할 것이다.


마지막 인수는 우리가 원하는 텍스처 랩핑 모드를 통과해야하며, 이 경우 OpenGL은 GL_MIRRORED_REPEAT를 사용해


현재 활성 텍스처의 텍스처 랩핑 옵션을 설정한다.



 GL_CLAMP_TO_BORDER 옵션을 선택하면 테두리 색도 지정해야한다. 이것은 GL_TEXTURE_BORDER_COLOR와 함께


glTexParameter 함수에 해당하는 fv를 사용해 수행된다. 이 옵션은 테두리의 색상 값을 float 배열로 전달한다.


float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);  




Texture Filtering


 텍스처 좌표는 해상도에 의존하지 않지만 부동 소수점 값이 될 수 있으므로 OpenGL은 텍스처 좌표를


매핑할 텍스처 픽셀을 찾아야한다. (texel이라고도 한다)


매우 큰 물체와 낮은 해상도의 텍스처가 있는 경우 특히 중요하다. OpenGL에는 이 텍스처 필터링 옵션도 있다.


몇 가지 옵션을 사용할 수 있지만 가장 중요한 옵션은 GL_NEAREST 및 GL_LINEAR이다.



 GL_NEAREST(가장 가까운 이웃 필터링)는 OpenGL의 기본 텍스처 필터링 방법이다.


GL_NEAREST로 설정하면 OpenGL은 가운데가 텍스처 좌표에 가장 가까운 픽셀을 선택한다.


아래에서 십자가가 정확한 텍스처 좌표를 나타내는 4픽셀을 볼 수 있다. 왼쪽 위 텍셀의 중심은


텍스처 좌표에 가장 가깝기 때문에 샘플링된 색상으로 선택된다.



 GL_LINEAR ( (bi) 선형 필터링) 텍스처 좌표의 이웃한 텍셀에서 보간된 값을 가져와 텍셀 사이의 색상을 근사한다.


텍스처 좌표에서 텍셀의 중심까지의 거리가 작을수록 텍셀의 색상이 샘플된 색상에 더 많이 기여한다.


아래에서 우리는 인접한 픽셀의 혼합 색상이 반환됨을 알 수 있다.



 그러나 그러한 텍스처 필터링 방법의 시각 효과는 무엇인가? 대형 객체에서 해상도가 낮은 텍스처를


사용할 때 이러한 메소드가 어떻게 작동하는지 보자. (텍스처가 위쪽으로 확장되고 개별 텍셀이 눈에 보임)



 GL_NEAREST는 텍스처를 형성하는 픽셀을 명확하게 볼 수 있는 차단된 패턴을 생성하는 반면


GL_LINEAR은 개별 픽셀이 덜 보이는 더 매끄러운 패턴을 생성한다. GL_LINEAR은 좀 더 현실감있는 결과를 산출하지만


일부 개발자는 8-bit 룩을 선호하므로 GL_NEAREST 옵션을 선택한다.



 텍스처 필터링을 확대 및 축소 작업에 대해 설정할 수 있으므로, 예를 들어 텍스처를 아래쪽으로 축소 할 때


가장 가까운 이웃 필터링을 사용하고, up scale된 텍스처를 선형 필터링할 수 있다.


따라서 glTexParameter *를 통해 두 옵션 모두에 대한 필터링 방법을 지정해야한다. 코드는 wrapping method 설정과 유사해야한다:


glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);






Mipmaps


 수천 개의 물체가 있는 넓은 공간이 있고, 각각에 텍스처가 첨부된 경우를 상상해보아라. 뷰어 가까이에 있는 물체와


동일한 고해상도 텍스처가 부착된 멀리 있는 물체가 있을 것이다. 객체가 멀리 떨어져 있고 조각만 생성하기 때문에


OpenGL은 텍스처의 대부분을 차지하는 조각을 위한 텍스처 색상을 선택해야하므로 고해상도 텍스처에서 해당 조각의


올바른 색상 값을 가져오는데 어려움을 겪는다. 이렇게하면 작은 물체에 고해상도 텍스처를 사용하는 메모리 낭비는


물론 작은 물체에 보이는 아티팩트가 생성된다.



 이 문제를 해결하기 위해 OpenGL은 기본적으로 각 후속 텍스처가 이전 텍스처에 비해 두 배 작은 텍스처


이미지 모음인 mipmaps라는 개념을 사용한다. 밉맵의 아이디어는 이해하기 쉬어야한다.


뷰어에서 일정한 거리 임계 값이 지나면 OpenGL은 개체까지의 거리에 가장 적합한 밉맵 텍스처를 사용한다.


물체가 멀리 있기 때문에 작은 해상도는 사용자에게 눈에 띄지 않는다. 또한, 밉맵에는 성능 향상을 위한


보너스 기능이 추가되었다. 밉맵 텍스처가 어떻게 생겼는지 자세히 살펴보자:



 각 텍스처 이미지에 대한 밉맵 텍스처 컬렉션을 생성하는 것은 수동으로 하기가 번거롭지만


운좋게 OpenGL은 텍스처를 생성한 후에 glGenerateMipmaps를 한 번 호출하면 모든 작업을 수행할 수 있다.


나중에 텍스처 튜토리얼에서 이 함수의 사용법을 볼 수 있다.



 렌더링 중 밉맵 레벨을 전환할 때 OpenGL은 두 밉맵 레이어 사이에 선명한 가장자리와 같은 일부 아티팩트를


표시 할 수 있다. 일반적인 텍스처 필터링과 마찬가지로 밉맵 레벨을 전환하기 위해 NEAREST 및 LINEAR 필터링을


사용해 밉맵 레벨 사이를 필터링 할 수도 있다. 밉맵 레벨 사이의 필터링 방법을 지정하기 위해


원래의 필터링 방법을 다음 네 가지 옵션 중 하나로 대체할 수 있다.


    - GL_NEAREST_MIPMAP_NEAREST : 가장 가까운 밉맵을 픽셀 크기와 일치시키고 텍스처 샘플링에 가장 가까운 이웃 보간을 사용


    - GL_LINEAR_MIPMAP_NEAREST : 선형 보간법을 사용해 가장 가까운 밉맵 레벨과 샘플을 취함


    - GL_NEAREST_MIPMAP_LINEAR : 가장 가까운 이웃 보간법을 통해 픽셀과 샘플의 크기와 가장 근접하게 일치하는

                                                두 밉맵을 선형적으로 보간


    - GL_LINEAR_MIPMAP_LINEAR : 가장 가까운 두 밉맵 사이를 선형 보간하고 선형 보간법을 통해 텍스처를 샘플링


텍스처 필터링과 마찬가지로 glTexParameteri를 사용해 앞에서 설명한 4가지 방법 중 하나로 필터링 방법을 설정할 수 있다.

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

 일반적인 실수는 밉맵 필터링 옵션 중 하나를 확대 필터로 설정하는 것이다. 밉맵은 텍스처가 축소될 때 주로 사용되기


때문에 효과가 없다. 텍스처 확대는 밉맵을 사용하지 않으며 밉맵 필터링 옵션을 지정하면 OpenGL GL_INVAILD_ENUM 오류가 생성된다.




Loading and creating textures


 실제로 텍스처를 사용하기 위해 해야할 첫 번째 작업은 응용 프로그램에 텍스처를 로드하는 것이다.


텍스처 이미지는 수십 가지 파일 형식으로 저장할 수 있다. 각 형식은 고유한 구조와 데이터 순서로 되어 있으므로


응용 프로그램에서 이러한 이미지를 어떻게 가져올까? 한 가지 해결책은 우리가 사용하고자하는 파일 형식을 선택하고,


.PNG라고 말하면서 이미지 형식을 큰 바이트 배열로 변환하는 자체 이미지 로더를 작성하는 것이다.


자신의 이미지 로더를 작성하는 것을 그리 어렵지 않지만 여전히 성가시다.


더 많은 파일 형식을 지원하려면 어떻게 해야 할까? 지원하려는 각 형식에 대한 이미지 로더를 작성해야한다.



 또 다른 방법은 여러 대중적인 형식을 지원하는 이미지 로딩 라이브러리를 사용하는 것이다. (stb_image.h와 같은)




stb_image.h


 stb_image.h는 가장 인기있는 파일 형식을 로드할 수 있고, 프로젝트에 쉽게 통합할 수 있는 Sean Barrett의


매우 인기있는 단일 헤더 이미지로드 라이브러리이다. stb_image.h는 [https://learnopengl.com/Getting-started/Textures]에서


다운로드할 수 있따. 단일 헤더 파일을 다운로드해 프로젝트에 추가하고 다음 코드로 추가 C++ 파일을 만든다.


#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"

 STB_IMAGE_IMPLEMENTATION을 정의함으로써 전처리기는 헤더 파일을 관련 정의 소스코드만 포함하도록 수정해


헤더 파일을 효과적으로 .cpp 파일로 변환한다. 이제 stb_image.h를 프로그램의 어딘가에 포함시키고 컴파일해라.



 다음 텍스처 섹션에서는 나무 컨테이너 이미지를 사용한다. stb_image.h를 사용해 이미지를 로드하려면


stbi_load함수를 사용한다.


int width, height, nrChannels;
unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0); 

 이 함수는 먼저 입력으로 이미지 파일의 위치를 취한다.


그런 다음 stb_image.h가 결과 이미지의 폭, 높이 및 색상 채널 수로 채울 두 번째, 세 번째 및 네 번째 인자로 세 개의


int를 제공할 것으로 예상한다. 나중에 텍스처를 생성하려면 이미지의 너비와 높이가 필요하다.




Generating a texture


 OpenGL의 이전 객체와 마찬가지로 텍스처도 ID로 참조된다. 하나 만들어 보자:


unsigned int texture;
glGenTextures(1, &texture);  

 glGenTextures 함수는 먼저 우리가 생성하고자 하는 텍스처의 수를 입력으로 받아서 두 번째 인수로 주어진


unsigned int 배열에 저장한다. 다른 객체와 마찬가지로 바인드해야하므로 이후의 모든 텍스처 명령이 현재 바운드 텍스처를 구ㅜ성한다.

glBindTexture(GL_TEXTURE_2D, texture);  

 텍스처가 바인딩 되었으므로 이전에 로드된 이미지 데이터를 사용해 텍스처를 생성할 수 있다.


텍스처는 glTexImage2D로 생성된다.


glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
glGenerateMipmap(GL_TEXTURE_2D);

 이것은 꽤 많은 매개변수가 있는 큰 함수이므로 단계별로 살펴보겠다.


1. 첫 번째 인수는 텍스처 대상을 지정한다. 이것을 GL_TEXTURE_2D로 설정하면 이 작업은 현재 대상에 있는 바운드 텍스처

   개체에 텍스처를 생성한다. (대상 GL_TEXTURE_1D 또는 3D에 바인딩 된 텍스처는 영향을 받지 않는다)


2. 두 번째 인수는 각 밉맵 레벨을 수동으로 설정하려는 경우 텍스처를 만드려는 밉맵 레벨을 지정하지만 기본 레벨은 0이다.


3. 세 번째 인수는 OpenGL에게 우리가 텍스처를 저장하는 형식의 종류를 알려준다. 우리의 이미지는 RGB 값만을 가지므로

   RGB 값을 가진 텍스처도 저장하게 된다.


4,5. 네 번째와 다섯 번째 인수는 결과 텍스처의 너비와 높이를 설정한다. 이미지를 로드할 때 이전에 저장했으므로 해당 변수를 사용한다.


6. 이 인수는 항상 0이다.


7,8. 일곱 여덟번째 인수는 원본 이미지의 형식과 데이터 유형을 지정한다. 이미지에 RGB 값을 로드하고 

      문자(byte)로 저장해 해당 값을 전달한다.


9. 마지막 인수는 실제 이미지 데이터이다.


 일단 glTexImage2D가 호출되면 현재 바인딩 된 텍스처 객체는 이제 텍스처 이미지를 첨부한다. 그러나 현재는 텍스처 이미지의


기본 수준만 로드되어 있으므로 밉맵을 사용하려면 수동적으로 모든 다른 이미지를 지정해야한다. 또는, 텍스처를 생성한 후에


glGenerateMipmap을 호출 할 수 있다. 이렇게하면 현재 바인딩된 텍스처에 필요한 모든 밉맵이 자동으로 생성된다.



 텍스처와 해당 밉맵 생성이 끝나면 이미지 메모리를 해제하는 것이 좋다.


stbi_image_free(data);

 따라서 텍스처를 생성하는 전체 프로세스는 다음과 같이 보인다:

:


unsigned int texture;
glGenTextures(1, &texture);
glBindTexture(GL_TEXTURE_2D, texture);
// set the texture wrapping/filtering options (on the currently bound texture object)
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);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
// load and generate the texture
int width, height, nrChannels;
unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
if (data)
{
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data);
    glGenerateMipmap(GL_TEXTURE_2D);
}
else
{
    std::cout << "Failed to load texture" << std::endl;
}
stbi_image_free(data);





Applying textures


 다음 섹션에서는 Hello Triangle 튜토리얼의 마지막 부분에서 glDrawElements로 그려진 사각형 모양을 사용한다.


텍스처를 샘플링하는 방법을 OpenGL에 알릴 필요가 있으므로 꼭지점 데이터를 텍스처 좌표로 업데이트해야한다.


float vertices[] = {
    // positions          // colors           // texture coords
     0.5f,  0.5f, 0.0f,   1.0f, 0.0f, 0.0f,   1.0f, 1.0f,   // top right
     0.5f, -0.5f, 0.0f,   0.0f, 1.0f, 0.0f,   1.0f, 0.0f,   // bottom right
    -0.5f, -0.5f, 0.0f,   0.0f, 0.0f, 1.0f,   0.0f, 0.0f,   // bottom left
    -0.5f,  0.5f, 0.0f,   1.0f, 1.0f, 0.0f,   0.0f, 1.0f    // top left 
};

 추가 정점 속성을 추가 했으므로 OpenGL에 새로운 정점 형식을 다시 통지해야한다.

Image of VBO with interleaved position, color and texture data with strides and offsets shown for configuring vertex attribute pointers.


glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);  

 앞의 두 정점 속성의 보폭 매개 변수를 8 * sizeof(float)로 조정해야한다.



 다음으로 텍스처 좌표를 정점 속성으로 받아들이도록 정점 쉐이더를 변경해 그 좌표를 프래그먼트 쉐이더에 전송해야한다:


#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aColor;
layout (location = 2) in vec2 aTexCoord;

out vec3 ourColor;
out vec2 TexCoord;

void main()
{
    gl_Position = vec4(aPos, 1.0);
    ourColor = aColor;
    TexCoord = aTexCoord;

 프래그먼트 쉐이더는 TexCoord 출력 변수를 입력 변수로 받아 들여야한다.



 프래그먼트 쉐이더는 텍스처 오브젝트에도 접근해야하지만 텍스처 오브젝트를 프래그먼트 쉐이더로 전달하는 방법은 무엇인가?


GLSL은 우리가 원하는 텍스처 타입을 포스트픽스 (postfix)로 취하는 샘플러(sampler)라 불리는 텍스처 객체를 위한


데이터 타입을 내장하고 있다. (sampler1D, sampler3D 또는 우리의 경우에는 sampler2D)


나중에 텍스처를 할당할 uniform sampler2D를 선언함으로써 프래그먼트 쉐이더에 텍스처를 추가할 수 있다.


#version 330 core
out vec4 FragColor;
  
in vec3 ourColor;
in vec2 TexCoord;

uniform sampler2D ourTexture;

void main()
{
    FragColor = texture(ourTexture, TexCoord);
}

  텍스처의 색상을 샘플링하기 위해 GLSL의 내장 텍스처 함수를 사용한다. 이 함수는 첫 번째 인수로 텍스처 샘플러를,


두 번째 인수로 해당 텍스처 좌표를 취한다. 그런 다음 텍스처 함수는 이전에 설정한 텍스처 매개 변수를 사용해


해당 색상 값을 샘플링한다. 이 프래그먼트 쉐이더의 출력은 텍스처 좌표에서 텍스처의 색상이 된다.



 이제 할 일은 glDrawElements를 호출하기 전에 텍스처를 바인딩하는 것이다. 그러면 텍스처가 프래그먼트 쉐이더의


샘플러에 자동으로 할당된다.


glBindTexture(GL_TEXTURE_2D, texture);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);

 모든 것을 제대로 했다면 다음 이미지가 나온다:



직사각형이 완전히 흰색이거나 검은 색이면 실수 한 것이다. 쉐이더 로그를 확인하고 코드를 확인해라.

텍스처 코드가 작동하지 않거나 완전히 검은색으로 표시되면 계속해서 읽고 마지막으로 작동해야하는 예제로 이동해라. 일부 드라이버의 경우 항상 이 튜토리얼에서 더 자세히 설명할 각 샘플러 유니폼에 텍스처 단위를 지정해야한다.

 조금 펑키한 이미지를 얻으려면 결과 텍스처 색상과 정점 색상을 섞을 수 있다. 결과 텍스처 컬러와 프래그먼트 쉐이더의


정점 컬러를 곱하면 두 색상을 혼합할 수 있다.


FragColor = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0); 

 결과는 꼭지점의 색상과 텍스처의 색상이 혼합되어 있어야한다.






Texture Units


 glUinform으로 값을 할당하지 않은 경우 sampler2D 변수가 왜 유니폼인지 궁금할 것이다.


glUniform1i를 사용하면 실제로 조각 샘플러에 위치 값을 할당할 수 있으므로 프래그먼트 쉐이더에서 여러 텍스처를


한 번에 설정할 수 있다. 텍스처의 이 위치는 일반적으로 텍스처 유닛으로 알려져있다.


텍스처의 기본 텍스처 단위는 기본 액티브 텍스처 단위인 0이다. 그래서 이전 섹션에서 위치를 지정하지 않아도된다.


모든 그래픽 드라이버가 기본 텍스처 단위를 할당하지는 않으므로 이전 섹션이 렌더링되지 않았을 수 있습니다.


glActiveTexture(GL_TEXTURE0); // activate the texture unit first before binding texture
glBindTexture(GL_TEXTURE_2D, texture);

 텍스처 유닛을 활성화 한 후, 이후의 glBindTexture 호출은 그 텍스처를 현재 활성화된 텍스처 유닛에 바인딩한다.


텍스처 유닛 GL_TEXTURE0는 항상 기본적으로 활성화되어 있기 때문에 glBindTexture를 사용할때 이전 예제에서 텍스처 유닛을


활성화할 필요가 없었습니다.

OpenGL은 GL_TEXTURE0에서 GL_TEXTURE15를 사용해 활성화할 수 있는 최소 16개의 텍스처 단위를 사용해야한다. 그것들은 순서대로 정의되어 GL_TEXTURE0 + 8을 통해 GL_TEXTURE8을 얻을 수 있따. 이는 여러 텍스처 단위로 반복해야할 때 유용하다.

 그러나 우리는 여전히 다른 샘플러를 수용하기 위해 프래그먼트 쉐이더를 편집해야한다. 이것은 지금 비교적 간단해야한다:


#version 330 core
...

uniform sampler2D texture1;
uniform sampler2D texture2;

void main()
{
    FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}

 최종 출력 색상은 이제 두 개의 텍스처 조회 조합이다. GLSL의 내장된 혼합 함수는 두 개의 값을 입력으로 사용하며


세 번째 인수를 기준으로 두 값을 선형적으로 보간한다. 세번째 값이 0.0이면 첫 번째 입력을 반환한다.


1.0이면 두 번째 입력값을 반환한다. 값 0.2는 첫 번째 입력 색삿ㅇ의 80%와 두 번째 입력 색상의 20%를 반환해


두 텍스처를 혼합한다.



 이제 다른 텍스처를 로드하고 생성하려고 한다. 지금 단계를 잘 알고 있어야한다. glTexImage2D를 사용해 또 다른


텍스처 오브젝트를 생성하고 이미지를 로드하고 최종 텍스처를 생성해라. 두 번째 텍스처의 경우 OpenGL을 학습하면서


표정 이미지를 사용한다.


unsigned char *data = stbi_load("awesomeface.png", &width, &height, &nrChannels, 0);
if (data)
{
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, data);
    glGenerateMipmap(GL_TEXTURE_2D);
}

  알파 채널을 포함하는 .png 이미지를 로드한다. 즉, GL_RGBA를 사용해 이미지 데이터에 알파 채널이 포함되도록


지정해야한다. 그렇지 않으면 OpenGL이 이미지 데이터를 잘못 해석한다.



 두 번째 텍스처를 사용하려면 두 텍스처를 해당 텍스처 유닛에 바인딩해 렌더링 절차를 약간 변경해야한다.


glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, texture1);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, texture2);

glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0); 

 또한 glUniform1i를 사용해 각 샘플러를 설정해 각 쉐이더 샘플러가 속한 텍스처 단위로 OpenGL에 알려야한다.


한 번만 설정하면 되기 때문에 렌더 루프를 시작하기 전에 이렇게 할 수 있다.


ourShader.use(); // don't forget to activate the shader before setting uniforms!  
glUniform1i(glGetUniformLocation(ourShader.ID, "texture1"), 0); // set it manually
ourShader.setInt("texture2", 1); // or with shader class
  
while(...) 
{
    [...]
}

 glUniform1i를 통해 샘플러를 설정함으로써 각 유니폼 샘플러가 적절한 텍스처 단위에 해당하는지 확인한다.


다음 결과를 얻어라:



 텍스처가 거꾸로 뒤집혀 있는 것을 볼 수 있다. 이것은 OpenGL에서 y축의 0.0 좌표가 이미지의 아래쪽에 있다고


예상하기 때문에 발생하지만 이미지의 y축 위쪽에 0.0이 표시된다. 다행스럽게도 stb_image.h는 이미지 로드 전에


다음 명령을 추가해 이미지 로드 중에 y축을 뒤집을 수 있다.


stbi_set_flip_vertically_on_load(true);  

 이미지를 로드할 때 stb_image.h에서 y축을 뒤집어서 다음 결과를 얻어야한다:








** ** ** ** ** ** ** ** ** **


음, 이번 강의는 좀 짧다. 텍스처를 입히는 것은 여러번 봐도 구조가 아직 익숙하지 않다.


프로젝트를 진행하면서 여러번 반복하다보면 익숙해 지겠지? 어렵지 않게 이번 튜토리얼을 마친다.