Textures
각 모델에는 더많은 vertex들이 필요, 각 vertex는 컬러 attributes이 필요 => 오버헤드
텍스처 : 2D, 1D, 3D image
vertex를 추가하지 않아도 오브젝트가 매우 세밀하게 묘사된것처럼
텍스쳐는 많은 양의 데이터를 저장하여 shader에 보낼 수 있음.
삼각형에 매핑
각 vertex에 텍스처의 어느 부분이 해당하는지 알려주어야함.
각 vertex에는 샘플링할 texture coorfinate 가 있어야함
fragment 보간을 통해 텍스처 좌표 를 보간
텍스처 좌표의 범위
- x와 y축상의 0~1
텍스쳐 좌표를 사용하여 텍스쳐 컬러를 가져오는것을 sampling이라고 함.
좌측하단 vertex : (0, 0)
우측하단 vertex : (1, 0)
중앙상단 vertex : (0.5, 1.0)
vertex shader에 3개의 텍스처 좌표를 전달
fragment shader에 전달 => 모든 텍스처 좌표를 각 fragment에 보간
1
2
3
4
5
float texCoords[] = {
0.0f, 0.0f, // lower-left corner
1.0f, 0.0f, // lower-right corner
0.5f, 1.0f // top-center corner
};
- OpenGL에게 sample 하는 방법을 알려줘야함
Texture Wrapping
텍스처 좌표의 범위는 (0, 0) ~ (1, 1) 이다
OpenGL의 기본 동작은 텍스처 이미지를 반복하는 것.
기본적으로 텍스처 좌표에 정수 부분을 무시.
아래와 같은 옵션을 사용할 수 있다.
GL_REPEAT
: 텍스처의 기본 동작, 이미지를 반복GL_MIRRORED_REPEAT
: GL_REPEAT와 같지만 반복할때마다 이미지를 반대로 뒤집음GL_CLAMP_TO_EDGE
: 0과 1 사이의 좌표를 고정, 결과적으로 큰 좌표가 가장자리에 고정되어 가장자리의 패턴이 늘어남.GL_CLAMP_TO_BORDER
: 범위 밖의 좌표에 사용자가 지정한 테두리 색이 지정.
= 기본 범위 밖의 좌표를 사용할 때 각 옵션을 통해 출력 형식 설정
= 각 옵션들은 glTexParameter* 함수를 사용하여 좌표축별로 설정 가능
1
2
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_MIRRORED_REPEAT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_MIRRORED_REPEAT);
파라미터 1 : 텍스처 타겟을 지정, 2D = GL_TEXTURE_2D
파라미터 2 : 설정할 옵션과 어떤 축에 적용할 것인지 지정 (s, t, r == x, y , z)
- WRAP 옵션을 s, t 에
파라미터 3 : wrapping 모드를 설정
- GL_MIRRORED_REPEAT : 현재 활성화된 텍스처의 옵션을 설정
GL_CLAMP_TO_BORDER
옵션인 경우 테두리 색 정해줘야함.- fv를 사용하는 glTexParameter 함수를 호출 => 파라미터로
GL_TEXTURE_BORDER_COLOR
옵션 설정해야됨
- fv를 사용하는 glTexParameter 함수를 호출 => 파라미터로
1
2
float borderColor[] = { 1.0f, 1.0f, 0.0f, 1.0f };
glTexParameterfv(GL_TEXTURE_2D, GL_TEXTURE_BORDER_COLOR, borderColor);
Texture Filtering
좌표는 해상도에 의존하지 않음.
하지만 실수 값이 될 수 있음
OpenGL은 텍스처 좌표를 매핑할 텍스쳐 픽셀 (텍셀(texel)) 을 찾아야함
매우 큰 물체에 낮은 해상도의 텍스처가 있는 경우 특히 중요.
이를 위한게 texture filtering 옵션
GL_NEAREST, GL_LINEAR
GL_NEAREST
nearest neighbor filtering
기본적인 필터링 방법
텍셀의 중심이 텍스처 좌표에 가장 가까운 텍셀을 선택
GL_LINEAR
bilinear filtering
텍스처 좌표의 이웃한 텍셀에서 보간된 값을 가져와 텍셀 사이의 색상의 근사치를 가져온다.
텍셀의 중심까지의 거리가 가까울수록 그 색이 더 강함
큰 오브젝트에 해상도가 낮은 텍스처를 사용할 때 (텍스처 스케일 업, 텍셀들이 눈에 띔)
Nearest 는 blocked pattern 명확히 볼 수 있음 (8-bit look)
Linear 은 smoother pattern 로 개별 픽셀들이 덜 보임.
확대(magnifying), 축소(minifying) 작업에 대해 설정
축소 : nearest
확대 : linear
glTexParameter* 함수를 통해 필터링 방법을 지정해야함.
1
2
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
Mipmaps
수천개의 오브젝트가 있는 넓은 공간에 각각에 텍스처가 첨부될 경우
오브젝트가 멀리 떨어진것 : 몇개의 fragment만 생성,
- 텍스쳐의 대부분을 차지하는 fragment를 위한 텍스처 색상을 선택
고해상도 텍스처에서 해당 fragment의 올바른 색상 값을 가져오는데 어려움을 겪는다.
작은 물체에 고해상도 텍스처를 사용하여 메모리 낭비, 물체에 결함이 보일 수 있음
mipmaps
텍스처의 집합, 순차적으로 이전 텍스처보다 2배씩 작아지는 텍스처들
특정 거리 임계값을 넘으면 적합한 mipmap 텍스처를 사용하게됨
작은 해상도의 텍스처는 사용자 눈에 잘띄지않음
성능 향상에 도움 (적은 캐시 메모리의 사용)
수작업 : cumbersome (성가신)
OpenGL 은
glGenerateMipmaps
함수를 통해 mipmaps을 생성렌더링 중 mipmap을 전환할때 OpenGL은 mipmap레이어 사이에, 가장자리가 선명하게 나타날 수 있는 결함이 생길 수 있음
nearest, linear 필터링을 사용하여 mipmap 레벨 사이를 필터링 할 수 있음.
mipmap레벨 사이의 필터링 방법을 지정하기 위해 필터링 방법을 4가지 옵션중 하나로 대체가능
glTexParameteri 사용
GL_NEAREST_MIPMAP_NEAREST
- nearest neighbor 보간법으로 mipmap을 필터링하고, 텍스처 샘플링도 nearest neghbor 보간법을 사용
GL_LINEAR_MIPMAP_NEAREST:
- nearest neighbor 보간법으로 mipmap을 필터링, 텍스처 샘플링은 linear 보간법을 사용
GL_NEAREST_MIPMAP_LINEAR:
- linear 보간법으로 mipmap을 필터링, 텍스처 샘플링은 nearest neighbor 보간법을 사용
GL_LINEAR_MIPMAP_LINEAR:
- linear 보간법으로 mipmap을 필터링, 텍스처 샘플링도 linear 보간법을 사용
1
2
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
**주의**: mipmap 필터링 옵션중 하나를 확대 필터로 설정하는것=> mipmap은 축소될때 주로 사용, 확대인 경우 효과 없음=> GL_INVALID_ENUM 오류코드
Loading and creating textures
응용프로그램에 텍스처를 로드
텍스처 이미지는 수십가지 파일 형식으로 저장될 수 잇음
각 형식은 고유한 구조와 데이터 순서로 되어있음
해결책1: 파일 형식을 선택(png), 이미지 형식을 큰 바이트 배열로 변환하는 이미지 로더 작성
해결책2: 더 많은 파일 형식을 지원해야하는것 => 이미지로더를 형식에 맞게 작성 => stb_image.h 라이브러리 이용
stb_image.h
= cpp에 아래 코드 추가.
1
2
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
- STB_IMAGE_IMPLEMENTATION : 전처리기(preprocessor) 는 헤더 파일을 관련된 정의 소스코드만 포함하도록함.
1
2
3
int width, height, nrChannels;
unsigned char *data = stbi_load("container.jpg", &width, &height, &nrChannels, 0);
// stbi_load(filename_, &tex_width_, &tex_height_, &tex_channels_, STBI_rgb_alpha); 이미지 포맷을 기본적으로 rgba로 읽는 코드
- 텍스처를 생성하기 위해서, width 와 height 정보가 필요함.
Generating a texture
- 이전 객체들과 마찬가지로 ID로 참조
glGenTextures 함수
파라미터 1: 생성할 객체의 크기
파라미터 2: ID 배열
1
2
unsigned int texture;
glGenTextures(1, &texture);
glBindTexture 함수
- 텍스처를 바인딩, 그 후 명령이 현재 바인딩된 텍스처를 대상으로 설정
1
glBindTexture(GL_TEXTURE_2D, texture);
glTexImage2D
이전에 로드된 이미지 데이터를 사용하여, 텍스처를 생성할 수 있음
한번 호출하면 현재 바인딩된 객체가 첨부된 이미지를 가지게됨
mipmap을 사용하고 싶으면
glGenerateMipmap
=> 현재 바인딩된 텍스처에 대해 자동 생성
1
2
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, GL_TEXTURE_3D 로 바인딩된 객체에는 아무런 영향을 끼치지 않음
파라미터 2 : mipmap 레벨을 수동으로 지정 (수작업으로 mipmap 만들었을 경우)
파라미터 3 : 텍스처가 어떤 포멧을 가져야하는지, 다룰 이미지는 RGB 값만 가지므로 GL_RGB
파라미터 4~5 : 텍스처의 너비와 높이를 설정
파라미터 6 : 항상 0 의 값이 되어야함. (legacy stuff)
파라미터 7~8 : 원본 이미지의 포맷과 데이터 타입 지정, RGB값이 있는 이미지를 로드, chars(bytes)로 저장한것
파라미터 9 : 실제 이미지의 데이터
이미지의 메모리 반환(텍스처와 mipmap들을 생성한 후)
1
stbi_image_free(data);
전체적인 과정
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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
- OpenGL에게 텍스처를 샘플하는 방법을 알려주어야하므로, 텍스처 좌표를 vertex 데이터에 추가해야함
1
2
3
4
5
6
7
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
};
- vertex attribute를 추가했기 때문에, OpenGL에게 새로운 vertex 포맷을 다시 알려주어야함.
텍스쳐 좌표 : stride = 8, offset = 6
1
2
glVertexAttribPointer(2, 2, GL_FLOAT, GL_FALSE, 8 * sizeof(float), (void*)(6 * sizeof(float)));
glEnableVertexAttribArray(2);
vertex shader 에 텍스처 좌표값 추가
1
2
3
4
5
6
7
8
9
10
11
12
13
14
#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;
}
fragment shader 에 sample 넘겨줌
fragment shader 는 텍스처 객체에 접근해야함.
sampler 텍스처 객체 타입 사용 : sampler1D, sampler2D, sampler3D
텍스처를 집어넣을 uniform sampler2D를 선언 : fragment shader에 전달 할 수 있음
1
2
3
4
5
6
7
8
9
10
11
12
#version 330 core
out vec4 FragColor;
in vec3 ourColor;
in vec2 TexCoord;
uniform sampler2D ourTexture;
void main()
{
FragColor = texture(ourTexture, TexCoord);
}
texture 함수
앞서 설정했던 텍스처 파라미터를 사용하여 해당 컬러값을 샘플링
출력은 보간된 텍스처 좌표에서 필터링된 텍스처의 컬러값임.
파라미터 1 : 텍스처 sampler
파라미터 2 : 텍스처 좌표
glDrawElements
- 호출하기 전에 텍스처를 바인딩, 텍스처를 fragment shader의 sampler로 자동으로 할당하게 된다.
1
2
3
glBindTexture(GL_TEXTURE_2D, texture);
glBindVertexArray(VAO);
glDrawElements(GL_TRIANGLES, 6, GL_UNSIGNED_INT, 0);
- 최종 텍스처 컬러와 vertex 컬러를 혼합할 수 있음
1
FragColor = texture(ourTexture, TexCoord) * vec4(ourColor, 1.0);
Texture Units
glUniform
함수를 사용하지 않음에도sampler2D
변수가 uniform 인지glUniform1i함수
를 사용하여 실제 텍스처 sampler에 위치 값을 할당하여 fragment shader에서 동시에 여러 텍스처들을 설정할 수 있음이 텍스처 위치는 texture unit이라고 불림
기본 텍스처 유닛은 0
이는 기본으로 활성화된 텍스처 유닛이므로, 앞에서는 할당할 필요가 없었음.
모든 그래픽 드라이버가 기본 텍스처 유닛을 할당하는 것이 아님.
텍스처 유닛의 주 목적 : shader에서 하나 이상의 텍스처를 사용할 수 있도록.
sampler 에 텍스처 유닛을 할당함으로, 해당 텍스처 유닛을 활성화하기만 하면
- 여러 텍스처들을 동시에 바인딩 가능
glActiveTexture 함수
1
2
glActiveTexture(GL_TEXTURE0); // activate the texture unit first before binding texture
glBindTexture(GL_TEXTURE_2D, texture);
바인딩하기전 텍스처 유닛을 전달하여 활성화
활성화한 후에 호출되는 bind 함수는 현재 활성화된 텍스처 유닛에 바인딩.
OpenGL은 최소 16개의 텍스처 유닛을 가지고 있음
GL_TEXTURE0에서부터 GL_TEXTURE15 순서대로 선언되어 있음
= GL_TEXTURE8 == GL_TEXTURE0 + 8
fragment shader 수정
mix 함수
1
2
3
4
5
6
7
8
9
10
#version 330 core
...
uniform sampler2D texture1;
uniform sampler2D texture2;
void main()
{
FragColor = mix(texture(texture1, TexCoord), texture(texture2, TexCoord), 0.2);
}
두개의 텍스처를 혼합
파라미터 3 : 0.0 -> 첫번째 텍스처 , 1.0 -> 두번째 텍스처 , 0.2 -> 첫번째 80% 두번째20%
다른 텍스처를 로드하고 생성
- RGBA를 사용하여 alpha 채널을 포함하고 있는 이미지임을 명시
1
2
3
4
5
6
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);
}
- 두개의 텍스처를 해당 텍스처 유닛에 모두 바인딩해야함.
1
2
3
4
5
6
7
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);
- 각 sampler를 설정함으로써 OpenGL에게 각 sampler가 속하는 텍스처 유닛이 어떤것인지 알려줘야한다.
1
2
3
4
5
6
7
8
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(...)
{
[...]
}
OpenGL은 Y축의 0.0좌표를 이미지의 아래쪽으로 인식
하지만 대부분의 이미지는 0.0좌표를 Y축의 맨 위를 가리킴.
- 이미지를 로드하기전 다음 코드를 추가하면 뒤집을 수 있음.
1
stbi_set_flip_vertically_on_load(true);