일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | ||||
4 | 5 | 6 | 7 | 8 | 9 | 10 |
11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | 28 | 29 | 30 | 31 |
- Perceptron
- 손실 함수
- 베버의 법칙
- vanishing gradient
- c++
- DP
- 이진 분류
- 기울기 소실
- 딥러닝
- feedforward neural network
- 알고리즘
- 동적계획법
- BOJ
- 계단 함수
- 범용 근사 정리
- dijkstra
- 3d
- 이진분류
- union-find
- 백준
- 다익스트라
- 경사하강법
- 과대적합
- bfs
- 베르누이 분포
- OpenGL
- dl
- 단층 퍼셉트론
- deep learning
- 순방향 신경망
- Today
- Total
Hello COCOBALL!
[LearnOpenGL] 27. Cubemaps 본문
큐브의 6개의 면을 형성하는 2D 텍스처를 포함하고 있는 텍스처이다.
방향벡터를 사용하여 인덱싱/샘플링 될 수 있다는 장점이 있다.
1x1x1 단위 큐브와 중앙에 위치한 방향벡터의 원점을 가질 때, cube map으로부터 텍스처를 샘플링 하는 것은 다음 그림과 같다.
방향벡터의 크기는 상관없이, 방향만 주어지면 방향벡터가 만나게 되는 텍셀을 얻는다.
cubemap을 샘플링하는 방향 벡터는 cube의 보간된 vertex 위치와 비슷하다.
다음과 같은 방식으로 큐브가 원점에 존재한다면 큐브의 위치 벡터를 이용해서 cubemap을 샘플링 할 수 있고, 큐브의 vertex 위치를 텍스처 좌표로 고려한다.
그 결과 cubemap의 각각의 face를 접근할 수 있는 텍스처 좌표를 얻을 수 있다.
Creating a cubemap
텍스처 생성, 바인딩
unsigned int textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
6개의 텍스처로 이루어져 있기 때문에 glTexImage2D 함수를 6번 호출해야한다. ( OpenGL에게 우리가 생성할 텍스처가 cubemap의 어떤 면에 해당하는지 알려주는 단계)
enum변수의 특성을 가지고 있어서 GL_TEXTURE_CUBE_MAP_POSITIVE_X로부터 시작해서 1씩 증가시키면서 루프를 돌 수 있다.
int width, height, nrChannels;
unsigned char *data;
for(GLuint i = 0; i < textures_faces.size(); i++)
{
data = stbi_load(textures_faces[i].c_str(), &width, &height, &nrChannels, 0);
glTexImage2D(
GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data
);
}
texture_faces vector는 cubemap을 위한 모든 텍스처의 위치를 순서대로 가지고 있음.
wrapping ,filthering method 지정
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
그림 그리기 전에 해당 텍스처 유닛을 활성화 하고 렌더링 전에 cubemap을 바인딩한다.
fragment shader에서는 지금까지 사용한 것과 다른 samplerCube를 사용하고, vec3를 사용한다.
in vec3 textureDir; // 3D 텍스처 좌표를 나타내는 방향 벡터
uniform samplerCube cubemap; // Cubemap 텍스처 샘플러
void main()
{
FragColor = texture(cubemap, textureDir);
}
Skybox
scene 전체를 에워싸는 6개의 이미지로 이루어진 큰 큐브이다.
Loading a skybox
unsigned int loadCubemap(vector<std::string> faces)
{
unsigned int textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_CUBE_MAP, textureID);
int width, height, nrChannels;
for (unsigned int i = 0; i < faces.size(); i++)
{
unsigned char *data = stbi_load(faces[i].c_str(), &width, &height, &nrChannels, 0);
if (data)
{
glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
0, GL_RGB, width, height, 0, GL_RGB, GL_UNSIGNED_BYTE, data
);
stbi_image_free(data);
}
else
{
std::cout << "Cubemap texture failed to load at path: " << faces[i] << std::endl;
stbi_image_free(data);
}
}
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
return textureID;
}
텍스처 경로 설정
vector<std::string> faces;
{
"right.jpg",
"left.jpg",
"top.jpg",
"bottom.jpg",
"front.jpg",
"back.jpg"
};
unsigned int cubemapTexture = loadCubemap(faces);
Displaying a skybox
skybox는 cube 위에 그려지기 때문에 VBO, VAO, vertex 데이터가 필요하다.
cube가 원점에 위치해 있다면 각각의 위치 벡터들은 원점으로부터의 방향 벡터와 동일하다.
방향 벡터는 우리가 텍스처 값을 얻기 위해 필요한 것이기 때문에 위치 벡터만을 제공하고 텍스처 좌표는 필요 없다.
//vertex shader
#version 330 core
layout (location = 0) in vec3 aPos;
out vec3 TexCoords;
uniform mat4 projection;
uniform mat4 view;
void main()
{
TexCoords = aPos;
gl_Position = projection * view * vec4(aPos, 1.0);
}
//fragment shader
#version 330 core
out vec4 FragColor;
in vec3 TexCoords;
uniform samplerCube skybox;
void main()
{
FragColor = texture(skybox, TexCoords);
}
cubemap 텍스처를 바인딩하면 skybox sample는 자동으로 skybox cubemap으로 채워진다.
scene에서 skybox를 가장 먼저 그리고 depth 작성을 비활성화 해야 한다.
glDepthMask(GL_FALSE);
skyboxShader.use();
// ... view, projection 행렬 설정
glBindVertexArray(skyboxVAO);
glBindTexture(GL_TEXTURE_CUBE_MAP, cubemapTexture);
glDrawArrays(GL_TRIANGLES, 0, 36);
glDepthMask(GL_TRUE);
// ... scene의 나머지 그리기
실행 시키면 플레이어가 움직일 때 cubemap이 같이 움직이기 때문에 view 행렬의 translation 부분을 지워줘야 skybox 위치 벡터에 영향을 주지 않는다.
4x4 행렬에서 좌측 상단 3x3 행렬을 취하면 translation 부분을 없앨 수 있다.
glm::mat4 view = glm::mat4(glm::mat3(camera.GetViewMatrix()));
translation은 제거했지만, rotation은 유지되어있다.
An optimization
지금의 skybox를 먼저 그리고 나머지 object를 나중에 그리는 방식은 그리 효율적이지 않다.
skybox를 먼저 그리면, 일부만 보이더라도 fragment shader를 화면의 각 픽셀들마다 실행해야 하기 때문이다.
early depth testing을 사용함으로 해결
성능 향상을 위해 skybox는 맨 마지막에 그린다.
- depth buffer는 다른 object 들의 depth 값으로 채워지기 때문에 우리는 earlyh depth testing을 통과한 skybox의 fragment만 렌더링하면 되고, fragment shader의 호출을 줄일 수 있다.
- skybox가 depth 값을 최댓값인 1.0을 가지게 하여 앞에 다른 object가 있으면 fail하도록 한다.
perspective division은 vertex shader가 실행된 후 gl_Position의 x,y,z 좌표를 w로 나눈다.
이 정보를 활용하여 다음과 같은 코드로 z를 w로 설정하여 z가 항상 1.0이 되도록 할 수 있다.
void main()
{
TexCoords = aPos;
vec4 pos = projection * view * vec4(aPos, 1.0);
gl_Position = pos.xyww;
}
NDC에서의 z는 1.0의 값을 가지게 된다.(depth의 최댓값)
depth buffer가 항상 1.0이 되기 때문에 depth 함수의 기본값인 GL_LESS를 GL_LEQUAL로 설정해야 한다.
Environment mapping
1. Reflection
object가 주변 환경을 반사하는 특성
viewer의 방향 벡터를 기반으로 법선 벡터에 따른 반사 벡터를 구할 수 있다. (GLSL은 이를 구하는 reflect라는 함수를 제공한다.)
vertex shader
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
out vec3 Normal;
out vec3 Position;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
Normal = mat3(transpose(inverse(model))) * aNormal;
Position = vec3(model * vec4(aPos, 1.0));
gl_Position = projection * view * model * vec4(aPos, 1.0);
}
Position은 world-space에서의 위치 벡터이고, fragment shader에서 view 방향 벡터를 계산할 때 쓰인다.
(법선 벡터는 model 행렬의 좌측 상단의 3x3 부분에 대한 역행렬에 대한 전치행렬)
fragment shader
#version 330 core
out vec4 FragColor;
in vec3 Normal;
in vec3 Position;
uniform vec3 cameraPos;
uniform samplerCube skybox;
void main()
{
vec3 I = normalize(Position - cameraPos);
vec3 R = reflect(I, normalize(Normal));
FragColor = vec4(texture(skybox, R).rgb, 1.0);
}
2. Refraction
material의 변화에 따라 빛의 방향이 바뀌는 특성
R벡터를 이용하여 cubemap을 sample 한다.(GLSL이 refract 함수 제공)
refract 함수는 normal vector, view direction, ratio를 매개변수로 받는다.
refractive index는 빛이 휘어지는 정도를 결정한다.
빛이 통과하는 두 개의 material 사이의 비율을 계산할 때 refractive index를 이용하는데 우리는 air→glass로 가기 때문에 ratio는 1.00/1.52=0.658 이 된다.
fragment shader
void main()
{
float ratio = 1.00 / 1.52;
vec3 I = normalize(Position - cameraPos);
vec3 R = refract(I, normalize(Normal), ratio);
FragColor = vec4(texture(skybox, R).rgb, 1.0);
}
실제 물리적 계산 결과를 위해서는 빛이 물체를 떠날 때 한 번 더 굴절 되어야 하지만, 지금은 간단하게 한 쪽 면만 굴절 시켰고, 이는 대부분의 목적에서 충분하다.
'OpenGL' 카테고리의 다른 글
[LearnOpenGL] 29. Advanced GLSL (1) | 2022.08.22 |
---|---|
[LearnOpenGL] 28. Advanced Data (0) | 2022.08.22 |
[LearnOpenGL] 26. Framebuffers (0) | 2022.08.22 |
[LearnOpenGL] 25. Face culling (0) | 2022.08.22 |
[LearnOpenGL] 24. Blending (0) | 2022.08.22 |