[Learn OpenGL 번역] 3-4. 조명 - Lighting maps
Lighting maps
조명/Lighting maps
이전 강좌에서 빛에 다르게 반응하는 고유의 material을 가지고 있는 오브젝트에 대해 다루었습니다. 빛이 존재하는 scene에서 이는 각 오브젝트들이 고유하게 보일 수 있도록 해주지만 여전히 오브젝트의 시각적 출력에 대해 많은 유연함을 주진 못합니다.
이전 강좌에서 전체 오브젝트에 material을 정의했었지만 실생활의 오브젝트들은 일반적으로 하나의 material로 이루어져 있지 않고 여러가지 material로 이루어져 있습니다. 자동차를 생각해보면 외부는 빛나는 재질로 되어있고 창문은 환경을 부분적으로 반사시키고 타이어는 모두 반사시키므로 specular 하이라이트를 가지지 않습니다. 그리고 림은 아주 강하게 빛납니다. 자동차는 또한 오브젝트 전체에 대해 각자 다른 diffuse, ambient 컬러를 가지고 있습니다. 자동차는 많이 다른 ambient/diffuse 컬러들을 출력합니다. 이러한 오브젝트 모두 각 부품들마다 각자 다른 material 속성들을 가지고 있습니다.
그래서 이전 강좌의 material 시스템은 간단한 모델을 제외하고는 충분하지 않으므로 diffuse, specular maps를 이용하여 시스템을 확장시켜야 합니다. 이들은 오브젝트의 diffuse(대부분 항상 동일하기 때문에 간접적으로 ambient 컴포넌트도 포함), specular 컴포넌트를 사용하여 오브젝트를 좀 더 정확하게 묘사할 수 있도록 해줍니다.
Diffuse maps
우리가 원하는 것은 오브젝트에 대한 각각의 fragment들의 diffuse 컬러를 설정하는 방법입니다. 오브젝트의 fragment 위치를 기반으로하여 컬러 값을 얻을 수 있는 일종의 시스템입니까?
이 모든 말은 아마 친숙할 것이고 솔직하게 우리는 지금까지 이러한 시스템을 사용해 왔습니다. 이 것은 전 튜토리얼에서 폭넓게 다루었던 텍스처와 비슷합니다. 우리는 단지 동일한 근복적인 원리를 다른 이름으로 부르는 것뿐입니다. fragment마다 고유한 컬러 값으로 인덱싱할 수 있는 오브젝트를 감싸고 있는 이미지를 사용하는 원리 말입니다. 빛이 존재하는 scene에서 이는 텍스처 이미지가 오브젝트의 모든 diffuse 컬러들을 나타내기때문에 일반적으로
Diffuse map을 보여주기 위해 우리는 모서리가 철로되어있고 속은 나무로된 컴테이너의 이미지를 사용할 것입니다.
Shader에서 diffuse map을 사용하는 것은 정확히 텍스처 강좌에서와 동일합니다. 하지만 이번에는 텍스처를 sampler2D
로 저장합니다. 전에 정의한 vec3
diffuse 컬러를 diffuse map으로 수정합니다.
sampler2D
는 이 타입들을 인스턴스화할 수 없고 오직 uniform으로만 정의할 수 있는 또한 우리는 ambient 컬러는 대부분의 경우에서 diffuse 컬러와 동일하므로 별로도 저장될 필요가 없기 때문에 ambient material 컬러 벡터를 지웁니다.
struct Material {
sampler2D diffuse;
vec3 specular;
float shininess;
};
...
in vec2 TexCoords;
vec3
값을 유지해도 됩니다. 하지만 그러면 ambient 컬러는 오브젝트 전체에 동일하게 적용될 것 입니다. 각 fragment 마다 다른 ambient 값을 얻기위해 ambient 값을 위한 다른 텍스처를 사용해야 합니다.
우리는 fragment shader에 다시 텍스처 좌표가 필요하므로 추가적인 입력 변수를 선언합니다. 그런 다음 fragment의 diffuse 컬러 값을 얻기 위해 텍스처를 간단히 샘플링합니다.
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
또한 ambient material 컬러를 diffuse material 컬러와 동일하게 설정하는 것을 잊지 마세요.
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
그리고 diffuse map을 사용하기 위한 것은 이게 전부입니다. 보시다시피 새로운 것은 없습니다. 하지만 시각적인 품질에서 드라마틱한 향상을 제공합니다. 이 것이 동작하게 하기위해 우리는 vertex 데이터에 텍스처 좌표를 업데이트하고 그들을 vertex attribute에서 fragment shader로 전달해야 합니다. 텍스처를 로드하고 적절한 텍스처 유닛에 바인딩해야 합니다.
수정된 vertex 데이터는 여기에서 찾아볼 수 있습니다. 이 vertex 데이터는 지금 큐브의 각 vertex에 대해 vertex 위치, 법선 벡터, 텍스처 좌표를 포합하고 있습니다. vertex shader를 텍스처 좌표를 vertex attribute로 받을 수 있도록 수정하고 이들을 fragment shader로 전달해봅시다.
#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;
layout (location = 2) in vec2 aTexCoords;
...
out vec2 TexCoords;
void main()
{
...
TexCoords = aTexCoords;
}
두 개의 VAO 모두 새로운 vertex 데이터에 맞추어 수정하였는지 확인하고 컨테이너 이미지를 텍스처로 불러오세요. 컨테이너를 그리기 전에 우리는 텍스처 유닛을 material.diffuse uniform sampler에 할당하고 컨테이너 텍스처를 이 텍스처 유닛에 바인딩해야합니다.
lightingShader.setInt("material.diffuse", 0);
...
glActiveTexture (GL_TEXTURE0);
glBindTexture (GL_TEXTURE_2D, diffuseMap);
이제 diffuse map을 사용하여 오브젝트를 좀 더 상세히 표현하였고 이번에는 lighting을 추가하여 컨테이너가 정말로 빛나게 할 것입니다. 여러분의 컨테이너는 지금 다음과 같이 보일 것입니다.
응용 프로그램의 전체 소스 코드는 here에서 확인할 수 있습니다.
Specular maps
우리 오브젝트는 대부분이 나무로 이루어져 있고 우리가 알고 있듯이 나무는 어떤 specular 하이라이트도 가지지 않기 때문에 이 specular 하이라이트는 조금 이상해 보일 수 있습니다. 우리는 오브젝트의 specular material을 vec3(0.0)
으로 설정하면 해결할 수 있지만 이렇게 하면 컨테이너의 모서리에도 specular 하이라이트가 사라지게 됩니다. 우리는 또한 철은 specular 하이라이트를 가져야한다는 것을 알고 있습니다. 다시, 다른 세기의 specular 하이라이트를 보여줘야할 오브젝트의 부품을 관리해야합니다. 이 문제는 diffuse map에서 다루었던 것과 비슷하게 생겼습니다.
우리는 specular 하이라이트를 위한 텍스처 맵을 사용합니다. 이는 오브젝트의 각 부분의 specular 세기를 정의하는 흑백(원한다면 컬러) 텍스처를 생성해야한다는 것을 의미합니다. 예제의 specular map은 다음과 같습니다.
Specular 하이라이트의 세기는 이 이미지의 각 픽셀의 밝기에 의해 얻을 수 있습니다. specular map의 각 픽셀은 예를 들어 검정색은 vec3(0.0)
의 컬러 벡터를 나타내고 회색은 vec3(0.5)
컬러 벡터를 나타내는 컬러 벡터로 표시될 수 있습니다. fragment shader에서 해당 컬러 값을 샘플링하고 이 값과 light의 specular 세기와 곱합니다. 픽셀이 흰색과 가까울수록 최종 결과가 증가하여 오브젝트의 specular 컴포넌트가 더 밝아집니다.
이 컨테이너의 대부분이 나무로 이루어져 있기 때문에 나무 material은 specular 하이라이트를 가지고 있지 않기때문에 dffiuse 텍스처의 나무로된 전체 부분은 검정색으로 변환됩니다. 검정색 부분은 어떠한 specular 하이라이트도 가지지 않습니다. 컨테이너의 철로된 모서리는 균열이 있는지 없는지에 따라 다른 specular 세기를 가집니다.
Photoshop이나 Gimp같은 툴을 사용하여 이와같이 일정 부분을 잘라내고 흑백으로 바꾼 후 밝기/대비를 높여 diffuse 텍스처를 specular 이미지로 쉽게 변환할 수 있습니다.
Specular maps 샘플링
Specular map은 다른 텍스처들과 비슷하므로 코드도 diffuse map 코드와 비슷합니다. 적절히 이미지를 로드하고 텍스처 객체를 생성했는지 확인하세요. fragment shader에서 다른 텍스처 샘플러를 사용하기 때문에 specular map에 다른 텍스처 유닛을 사용해야합니다. 그리고 렌더링하기 전에 적절한 텍스처 유닛을 바인딩하게 합니다.
lightingShader.setInt("material.specular", 1);
...
glActiveTexture (GL_TEXTURE1);
glBindTexture (GL_TEXTURE_2D, specularMap);
그런 다음 fragmet shader의 material 속성들을 specular 컴포넌트를 vec3
대신에 sampler2D
로 받을 수 있도록 수정합니다.
struct Material {
sampler2D diffuse;
sampler2D specular;
float shininess;
};
그리고 마지막으로 fragment의 해당 specular 세기를 가져오기 위해 specular map을 샘플링합니다.
vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));
vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));
vec3 specular = light.specular * spec * vec3(texture(material.specular, TexCoords));
FragColor = vec4(ambient + diffuse + specular, 1.0);
Specular map을 사용함으로써 오브젝트의 어떠한 부분이 실제로 빛나야하는지에 대해 상세하게 지정할 수 있고 그에 따른 세기도 설정할 수 있습니다. 따라서 specular map은 diffuse map의 위에서 레이어를 추가하게 해줍니다.
이제 응용 프로그램을 실행해 보면 컨테이너의 material이 이제는 나무로된 컨테이너와 철로된 프레임이 조립되어 있는 것처럼 보이는 것을 알 수 있습니다.
응용 프로그램의 전체 소스 코드는 here에서 확인할 수 있습니다.
diffuse, specular map을 사용하여 간단 오브젝트에 비교해서 거대한 양의 디테일을 살릴 수 있습니다.
연습
- 광원의 ambient, diffuse, specular 벡터를 가지고 놀아보세요. 그리고 이들이 컨테이너의 비주얼에 어떠한 영향을 미치는지 살펴보세요.
- fragment shader에서 specular map을 반전시켜보세요. 그러면 나무는 specular 하이라이트를 가지게 될 것이고 철로된 모서리는 가지지 않게 될 것입니다(철로된 모서리의 균열 때문에 모서리는 작은 세기라도 약간의 specular 하이라이트를 가지게 될 것입니다): 해답
- Diffuse 텍스처로부터 흑백이 아닌 컬러를 사용하는 specular map을 만들어 보세요. 그리고 결과가 현실적으로 보이지 않는 것을 확인해보세요. 스스로 생성하지 못하겠다면 여러분은 colored specular map을 사용할 수 있습니다: 결과
-
또한
emission map 이라고 불리는 것도 추가해보세요.emission map 은 fragment의 emission 값을 저장하는 텍스처입니다. Emission 값은 오브젝트가 광원을 자체적으로 가지고 있다면 그 빛을 방출(emit)하는 컬러입니다. 이 방법으로 오브젝트는 빛의 상황에 상관없이 빛날 수 있습니다. Emission map은 게임에서 로봇의 눈을 빛나게 한다던지컨테이너의 빛이 나는 줄무늬같은 경우에서 종종 볼 수 있습니다. 다음의 텍스처를 컨테이너의 emission map으로 추가해서 글자들이 빛이 나게 해보세요: 해답; 결과.