Normal Mapping
카테고리: OpenGL
태그: Graphics
Main Reference
- Learn OpenGL
- Rinthel Kwon - OpenGL Lecture
- OGLDEV - OpenGL Intermediate
- GetIntoGameDev - Tangent Space
Normal Map
Diffuse Texture | Normal Texture |
---|---|
2 개의 삼각형으로 이루어진 사각형에 벽면 텍스처를 입혀 벽면을 만들었다고 해보자. 이런 경우 벽면을 이루는 모든 픽셀의 normal은 같은 방향을 가리킬 것이다. 조명에 의한 물체 표면의 디테일을 결정하는 요소는 normal이기 때문에, 입체적인 디테일을 얻기는 힘들 것이다. 이런 경우 유용하게 사용하는 것이 바로 normal texture인데, 실제 vertex의 갯수를 늘리지 않고도 높은 수준의 디테일을 표현할 수 있게 해준다.
Tangent Space
Normal Texture | Tangent Space |
---|---|
이때 주의할 점은 normal texture에 저장된 normal vector는 wolrd space에서의 좌표계가 아니라는 것이다. Texture Coordinate를 갖는 특정 vertex의 Tangent Space 기준으로 표기된 좌표이기 때문에 좌표계 변환을 통해 vector 값을 변환해야 한다.
void Mesh::ComputeTangents(
std::vector<Vertex>& vertices,
const std::vector<uint32_t>& indices) {
auto compute = [](
const glm::vec3& pos1, const glm::vec3& pos2, const glm::vec3& pos3,
const glm::vec2& uv1, const glm::vec2& uv2, const glm::vec2& uv3)
-> glm::vec3 {
auto edge1 = pos2 - pos1;
auto edge2 = pos3 - pos1;
auto deltaUV1 = uv2 - uv1;
auto deltaUV2 = uv3 - uv1;
float det = (deltaUV1.x * deltaUV2.y - deltaUV1.y * deltaUV2.x);
if (det != 0.0f) {
auto invDet = 1.0f / det;
return invDet * (deltaUV2.y * edge1 - deltaUV1.y * edge2);
}
else {
return glm::vec3(0.0f, 0.0f, 0.0f);
}
};
// initialize
std::vector<glm::vec3> tangents;
tangents.resize(vertices.size());
memset(tangents.data(), 0, tangents.size() * sizeof(glm::vec3));
// accumulate triangle tangents to each vertex
for (size_t i = 0; i < indices.size(); i += 3) {
auto v1 = indices[i ];
auto v2 = indices[i+1];
auto v3 = indices[i+2];
tangents[v1] += compute(
vertices[v1].position, vertices[v2].position, vertices[v3].position,
vertices[v1].texCoord, vertices[v2].texCoord, vertices[v3].texCoord);
tangents[v2] = compute(
vertices[v2].position, vertices[v3].position, vertices[v1].position,
vertices[v2].texCoord, vertices[v3].texCoord, vertices[v1].texCoord);
tangents[v3] = compute(
vertices[v3].position, vertices[v1].position, vertices[v2].position,
vertices[v3].texCoord, vertices[v1].texCoord, vertices[v2].texCoord);
}
// normalize
for (size_t i = 0; i < vertices.size(); i++) {
vertices[i].tangent = glm::normalize(tangents[i]);
}
}
- Tangent Space의 N은 이미 알고있기 때문에 T, B만 구하면 됨
- 일반적으로 indices 버퍼 안에는 삼각형 단위로 3개의 vertex index가 들어감
- triangle을 이루는 3개의 vertex로 edge1, edge2 정의
- triangle을 이루는 3개의 vertex로 delta_du, delta_dv 정의
- T, B 벡터 계산
Normal Texture | Tangent Space |
---|---|
- 결과적으로 vertex마다 Tangent Space를 정의할 수 있고
- Shader를 통과하면 pixel 단위로 tangent space 정의 가능
- 좌표계 변환을 통해 Normal Texture에 정의된 normal을 world space에 얹을 수 있게 됨
Shader
// vertex shader
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoord;
layout (location = 3) in vec3 aTangent;
uniform mat4 transform;
uniform mat4 modelTransform;
out vec2 texCoord;
out vec3 position;
out vec3 normal;
out vec3 tangent;
void main() {
gl_Position = transform * vec4(aPos, 1.0);
texCoord = aTexCoord;
position = (modelTransform * vec4(aPos, 1.0)).xyz;
mat4 invTransModelTransform = transpose(inverse(modelTransform));
normal = (invTransModelTransform * vec4(aNormal, 0.0)).xyz;
tangent = (invTransModelTransform * vec4(aTangent, 0.0)).xyz;
}
- 기존의 vertex shader와 동일
- cpu에서 넘겨준 tangent vector를 world 기준으로 바꿔서 전달하는 부분 추가
// fragment shader
// ...
void main() {
vec3 texColor = texture(diffuse, texCoord).xyz;
vec3 texNorm = texture(normalMap, texCoord).xyz * 2.0 - 1.0;
vec3 N = normalize(normal);
vec3 T = normalize(tangent);
vec3 B = cross(N, T);
mat3 TBN = mat3(T, B, N);
vec3 pixelNorm = normalize(TBN * texNorm);
vec3 ambient = texColor * 0.2;
vec3 lightDir = normalize(lightPos - position);
float diff = max(dot(pixelNorm, lightDir), 0.0);
vec3 diffuse = diff * texColor * 0.8;
vec3 viewDir = normalize(viewPos - position);
vec3 reflectDir = reflect(-lightDir, pixelNorm);
float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
vec3 specular = spec * vec3(0.5);
fragColor = vec4(ambient + diffuse + specular, 1.0);
}
- texNorm : Normal Texture에 저장된 normal vector
- TNB : 좌표계 변환 행렬 (tangent -> world)
- pixelNorm : texNorm 벡터를 world space상의 normal 벡터로 변환
- pixelNorm을 바탕으로 color 연산..
Result
댓글 남기기