Texture Mapping in OpenGL
OpenGL에서 Texture Mapping을 수행해 보자.
Vertex 구조체 및 Attribute 정의
Texture Mapping을 수행할 때 필요한 vertex attribute는 vertex의 좌표와 texture coordinate이다.
struct Vertex
{
float x, y, z;
vec2 texCoord;
};
즉 위와 같이 Vertex
구조체를 정의할 수 있다.
struct Vertex
{
float x, y, z;
vec3 normal;
vec3 color;
vec2 texCoord;
};
하지만 나는 위와 같이 정의했다. x, y, z 좌표와 Vertex normal, Vertex color, Texture coordinate를 정의했다.
Vertex normal은 이전에 Phong Illumination Model을 구현할 때 사용했었다. 구현해 놓은 모델을 살리면서 동시에 Texture Mapping을 구현하기로 하였다.
Vertex color는 이전에도 사용하지 않은 attribute였지만 Texture Mapping 시 함께 활용이 가능하기에 정의해 놓았다.
여기서 각 vertex에 texture coordinate를 할당할 때에는, 이미지에서 왼쪽 아래가 원점이고 위로 갈수록 y 좌표가 증가한다. 즉, 이미지 뷰어에서 표시되는 좌표랑 y축 방향이 반전되어 있다. 이미지 뷰어에서 보여지는 좌표를 그대로 사용하려면 다음과 같이 normalization 이후 y 좌표를 1에서 빼주면 된다.
// Texture Coordinate
vertices[0].texCoord = vec2(0.0f, 0.0f); // 이미지 뷰어에서의 좌표 적용 이후 normalization (0 ~ 1사이의 값으로 변환)
vertices[0].texCoord.y = vertices[0].texCoord.y - 1.0f; // y축 반전
텍스쳐 불러오기
// Texture
GLuint color_tex;
먼저 텍스쳐를 저장할 글로벌 변수를 선언한다.
이후 init()
함수(main()
내에서 초기에 호출됨)에 다음과 같이 코드를 작성하여 텍스쳐를 불러왔다.
// Load image for texture -- change this code to load different images
int width, height;
uchar4 *dst;
LoadBMPFile(&dst, &width, &height, "../mob.bmp");
// Create an RGBA8 2D texture, 24 bit depth texture, 256x256
glGenTextures(1, &color_tex);
glBindTexture(GL_TEXTURE_2D, color_tex);
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_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, dst);
LoadBMPFile
은 교수님께서 제공해 주신 텍스쳐 파일 로딩 함수이다. 참고로 텍스쳐 파일을 로딩하는 함수를 제공하는 외부 라이브러리가 여럿 있고, 직접 구현도 가능하다.
glGenTextures
함수는 텍스쳐를 생성하는 함수이다. 이때 텍스쳐를 몇 개나 생성할 것인지 첫 번째 argument로 지정해 줄 수 있다. 위와 같이 1
을 줬을 경우 두 번째 argument는 텍스쳐 id를 저장할 unsigned int
변수가 들어간다. 2
이상의 경우 배열을 전달해 줘야 한다. glBindTexture
함수는 텍스쳐를 바인딩하여 이후 텍스쳐를 핸들링할 때 여기에 바인딩된 텍스쳐가 사용되도록 한다. 여기서는 텍스쳐로 2D 이미지를 사용할 것이기에 첫 번째 argument로 GL_TEXTURE_2D
를 전달하였다.
glTexParameter*
함수로 텍스쳐 관련 parameter들을 설정할 수 있다. 파라미터의 의미는 다음과 같다.
-
GL_TEXTURE_WRAP_S
: 텍스쳐의 x축 wrap 방식을 설정한다.GL_REPEAT
으로 설정하면 텍스쳐의 x축이 1을 넘어가면 다시 0부터 시작하여 반복된다.GL_CLAMP_TO_EDGE
로 설정하면 텍스쳐의 x축이 1을 넘어가면 1로 고정된다. -
GL_TEXTURE_WRAP_T
: 텍스쳐의 y축 wrap 방식을 설정한다.GL_REPEAT
으로 설정하면 텍스쳐의 y축이 1을 넘어가면 다시 0부터 시작하여 반복된다.GL_CLAMP_TO_EDGE
로 설정하면 텍스쳐의 y축이 1을 넘어가면 1로 고정된다. -
GL_TEXTURE_MIN_FILTER
: 텍스쳐의 크기가 축소될 때 사용할 필터를 설정한다.GL_NEAREST
로 설정하면 가장 가까운 텍셀1의 색상을 사용한다.GL_LINEAR
로 설정하면 가장 가까운 텍셀의 색상을 선형 보간하여 사용한다.
GL_TEXTURE_MAG_FILTER
: 텍스쳐의 크기가 확대될 때 사용할 필터를 설정한다.GL_NEAREST
로 설정하면 가장 가까운 텍셀의 색상을 사용한다.GL_LINEAR
로 설정하면 가장 가까운 텍셀의 색상을 선형 보간하여 사용한다.
glTexImage2D
함수는 텍스쳐를 생성하고, 텍스쳐 데이터를 로딩하는 함수이다. 여기서는 dst
변수에 텍스쳐가 저장된다. 또한 이 함수를 호출함으로써 현재 바인딩된 텍스쳐에 우리가 불러온 이미지 데이터가 로딩된다.
추가로 glGenerateMipmap
을 호출하여 Mipmap을 정의할 수도 있지만 여기서는 생략한다.
Shader 코드 작성 및 Vertex Attribute 활성화
#version 140
#extension GL_ARB_compatibility: enable
attribute vec3 vColor;
attribute in vec2 vTexCoord;
out vec4 objectColor;
out vec2 texCoord;
...
void main()
{
...
objectColor = vec4(vColor,1.0f);
texCoord = vTexCoord;
...
}
Vertex shader에는 위와 같이 attribute를 정의 및 out
변수를 정의하여 fragment shader에 텍스쳐 좌표와 vertex color를 넘겨준다. 참고로, 텍스쳐 좌표는 2D 이미지 상의 좌표이므로 2차원 벡터로 정의되었다.
...
uniform sampler2D myTexture;
in vec4 objectColor;
in vec2 texCoord;
...
void main()
{
...
vec4 texColor = texture(myTexture, texCoord);
gl_FragColor = vec4((ambient + 1.0*(diffuse + specular)) * texColor.xyz, 1.0f);
...
}
Fragment Shader에서는 텍스쳐를 불러오기 위해 sampler2D
타입의 uniform
변수를 선언하였다.
여기서 myTexture
texture sampler에는 location 값이 할당되는데, 이 값은 texture unit이라는 용어로 알려져 있다. 기본값은 0
으로 설정되어 있는데 별도로 지정하지 않으면 이 기본값에 해당하는 texture unit이 기본 텍스쳐로써 활성화가 된다. 그래서 텍스쳐를 하나만 사용할 경우 이 uniform
변수에 별다른 값을 넘겨주지 않아도 쉐이더 프로그램에서 바인드한 텍스쳐가 사용된다.
그리고 내장 함수 texture
를 사용하여 텍스쳐를 불러와서 Vertex Shader로부터 전달된 텍스쳐 좌표에 맵핑하였다. 이렇게 생성된 texture color와 Phong Illumination Model을 통해 계산된 color를 곱하여 최종적인 fragment의 color를 계산하였다. 참고로 여기서는 object color는 사용되지 않았다. 또한 Phong Illumination Model 등 빛의 반사를 구현하는 모델이 정의되지 않았다면 단순히 gl_FragColor
변수에 texColor
를 대입하면 된다.
이제 다시 메인 프로그램 코드로 넘어가자.
// Color
GLuint colorAttribLocation = glGetAttribLocation(p, "vColor");
glVertexAttribPointer(colorAttribLocation, 3, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)offsetof(Vertex, color));
glEnableVertexAttribArray(colorAttribLocation);
// Texture Coordinate
GLuint texAttribLocation = glGetAttribLocation(p, "vTexCoord");
glVertexAttribPointer(texAttribLocation, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), (GLvoid*)offsetof(Vertex, texCoord));
glEnableVertexAttribArray(texAttribLocation);
프로그램의 main
함수에서 위와 같이 glVertexAttribPointer
함수를 호출하여 Vertex Shader에서 이용한 vTexCoord
및 vColor
attribute를 활성화하였다. 물론 여기서 vColor
는 실제로 fragment color를 정의하는 데에 사용되지 않아서, 렌더링에 아무런 관여도 하지 않았지만 말이다.
참고문헌
-
텍스쳐 이미지 공간(Texture Space)상의 한 점(s, t) ↩