본문 바로가기

OpenGL Study

OpenGL "Lighting Maps"

[LIGHTING MAPS]

- 이전 파트에서 전체 object에 대한 material을 정의했지만, 실생활의 object는 보통 하나의 material이 아닌 여러 material로 구성되어있다

: 예를 들어, 자동차의 외부는 빛나는 재질로 되어있지만 창문은 주변 환경을 부분적으로 반사시키고 타이어는 모두 반사시킨다

: 여기서, 타이어는 specular 하이라이트를 가지지 않는다

: 자동차는 object 전체에 대해 각자 다른 diffuse, ambient 색상을 가진다

- 이전 파트의 material system은 가장 단순한 model을 제외하고는 모든 model에 충분하지 않기 때문에 diffuse, specular maps를 이용하여 system을 확장시켜야 한다

: 이를 통해 object의 diffuse(대부분 diffuse와 ambient 색상은 항상 동일하므로 간접적으로 ambient component도 포함), specular component를 사용하여 object를 조금 더 정확하게 묘사할 수 있도록 한다

 

[DIFFUSE MAPS]

struct Material {
    sampler2D diffuse;
    vec3      specular;
    float     shininess;
}; 
...
in vec2 TexCoords;

- 조명이 켜진 scene에서는 texture image가 object의 모든 diffuse 색상을 나타내기 때문에 일반적으로 diffuse map이라고 부른다

- shader에서 diffuse map을 사용하는 것은 texture 파트에서의 과정과 동일하다

: 그러나 texture를 Material struct 내부에 sampler2D로 저장하고, 이전에 정의한 vec3 diffuse color를 diffuse map으로 수정한다

+) sampler2D는 opaque type으로, 사용자가 인스턴스화 할 수 없고 uniform으로만 정의할 수 있다. uniform이 아닌 다른 형태로 인스턴스화 한다면 GLSL은 이상한 오류를 생성한다. 따라서 이러한 opaque type을 가진 struct에도 동일하게 적용된다

- ambient 색상은 대부분 diffuse 색상과 동일하므로 별도로 저장할 필요가 없다

: ambient material color vector 삭제

: ambient 색상을 다른 값으로 지정하고 싶다면 amient vec3 값을 유지해도 되지만, ambient 색상은 object 전체에 동일하게 적용된다. 따라서 각 fragment 마다 다른 ambient 값을 얻기 위해 ambient 값을 위한 다른 texture를 사용해야한다.

vec3 diffuse = light.diffuse * diff * vec3(texture(material.diffuse, TexCoords));

- fagment shader에 다시 texture coordinate가 필요하므로 추가적인 입력 변수를 선언한다

: 이후 fragment의 diffuse 색상값을 얻기 위해 texture를 간단히 샘플링한다

vec3 ambient = light.ambient * vec3(texture(material.diffuse, TexCoords));

- ambient material 색상을 diffuse matrial 색상과 동일하게 설정한다

float vertices[] = {
    // positions          // normals           // texture coords
    -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 0.0f,
     0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 0.0f,
     0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 1.0f,
     0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  1.0f, 1.0f,
    -0.5f,  0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 1.0f,
    -0.5f, -0.5f, -0.5f,  0.0f,  0.0f, -1.0f,  0.0f, 0.0f,

    -0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,   0.0f, 0.0f,
     0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,   1.0f, 0.0f,
     0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,   1.0f, 1.0f,
     0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,   1.0f, 1.0f,
    -0.5f,  0.5f,  0.5f,  0.0f,  0.0f, 1.0f,   0.0f, 1.0f,
    -0.5f, -0.5f,  0.5f,  0.0f,  0.0f, 1.0f,   0.0f, 0.0f,

    -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  1.0f, 0.0f,
    -0.5f,  0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  1.0f, 1.0f,
    -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  0.0f, 1.0f,
    -0.5f, -0.5f, -0.5f, -1.0f,  0.0f,  0.0f,  0.0f, 1.0f,
    -0.5f, -0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  0.0f, 0.0f,
    -0.5f,  0.5f,  0.5f, -1.0f,  0.0f,  0.0f,  1.0f, 0.0f,

     0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 0.0f,
     0.5f,  0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  0.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  1.0f,  0.0f,  0.0f,  0.0f, 1.0f,
     0.5f, -0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  0.0f, 0.0f,
     0.5f,  0.5f,  0.5f,  1.0f,  0.0f,  0.0f,  1.0f, 0.0f,

    -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  0.0f, 1.0f,
     0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  1.0f, 1.0f,
     0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  1.0f, 0.0f,
     0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  1.0f, 0.0f,
    -0.5f, -0.5f,  0.5f,  0.0f, -1.0f,  0.0f,  0.0f, 0.0f,
    -0.5f, -0.5f, -0.5f,  0.0f, -1.0f,  0.0f,  0.0f, 1.0f,

    -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 1.0f,
     0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 1.0f,
     0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 0.0f,
     0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  1.0f, 0.0f,
    -0.5f,  0.5f,  0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 0.0f,
    -0.5f,  0.5f, -0.5f,  0.0f,  1.0f,  0.0f,  0.0f, 1.0f
};

- diffuse map을 동작하게 하기 위해서는 vertex data에 texture coordinate를 수정하고 이것들을 vertex attribute에서 fragment shader로 전달하고, texture를 로드하고 texture를 적절한 texture unit에 바인딩해야한다

: 위 좌표들이 수정된 vertex data다

: 위 vertex data는 각 cube에 대해 vertex 위치, 법선 vector, texture coordinate를 포함하고 있다

#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;
}

- vertex shader를 수정하여 texture coordinate를 vertex attribute로 받아들이고 fragment shader로 전달할 수 있도록 한다

lightingShader.setInt("material.diffuse", 0);
...
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, diffuseMap);

- 컨테이너를 그리기 전에 texture unit을 material.diffuse uniformm sampler에 할당하고 컨테이너 texture를 이 unit에 바인딩해야한다

- Result

 

[SPECULAR MAPS]

- 지금까지 생성한 object는 대부분이 나무로 이루어져 있고, 나무는 어떠한 specular highlight도 가지지 않기 때문에 이 specular highlight는 조금 이상해보일 수 있다

: object의 specular material을 vec3(0.0)으로 설정하면 해결할 수 있지만, 이렇게 하면 철로 되어있는 컨테이너 모서리의 specular highlight까지 사라지게 된다

- object의 각 부분에서 다르게 표현되어야하는 specular highlight를 제어하고자 한다

: specular highlight를 위한 texture map을 사용하기 위해, object 각 부분의 specular 세기를 정의하는 texture를 생성해야한다

- specular highlight의 세기는 이미지의 각 pixel의 밝기에 의해 얻을 수 있다

: 예를 들어, 검정색은 vec3(0.0) color vector를 나타내고 회색은 vec3(0.5) color vector를 나타낸다. 그 다음 fragment shader에서 해당 색상 값을 샘플링하고 이 값과 light의 specular 세기를 곱한다. 이 때 pixel이 흰색과 가까울수록 최종 곱셈 결과가 증가하여 object의 specular component가 더 밝아진다

- 현재 생성하고 있는 컨테이너의 대부분은 나무로 이루어져 있다

: 나무 material은 specular highlight를 가지지 않기 때문에 diffuse texture의 나무로 된 전체 부분은 검정색으로 변환된다

: 컨테이너의 철로 된 모서리는 균열의 유무에 따라 다른 specular 세기를 가진다

+) 기술적으로 나무도 아주 낮은 shininess 값을 가지는 specular highlight를 가지고 있지만 학습의 목적에서 나무는 specular light에 대한 어떠한 반응도 하지 않게 한 채로 진행한다 (불필요한 일정 부분을 잘라내고 흑백으로 바꾼 후, 밝기/대비를 높여 diffuse texture를 specular 이미지로 쉽게 변환할 수 있다)

 

[SAMPLING SPECULAR MAPS]

- Specular map은 다른 texture들과 비슷하므로 코드도 diffuse map 코드와 비슷하다

- Specular map을 사용해 object의 어떤 부분이 실제로 빛나야하는지에 대해 상세하게 지정할 수 있고 그에 따른 세기도 설정할 수 있다

: 따라서 specular map은 diffuse map 위에서 레이어를 추가하게 해준다

lightingShader.setInt("material.specular", 1);
...
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, specularMap);

- fragment shader에서 다른 texture sampler를 사용하기 때문에 sepcular map에 다른 texture unit을 사용해야한다

- 이후 렌더링을 하기 전, 적절한 texture unit을 바인딩하게 한다

struct Material {
    sampler2D diffuse;
    sampler2D specular;
    float     shininess;
};

- fragment shader의 material 속성들을 specular component를 vec3 대신에 sampler2D로 받을 수 있도록 수정한다

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);

- 마지막으로 fragment의 해당 specular 세기를 가져오기 위해 specular map을 샘플링한다

 

 

[RESULT]

- 컨테이너의 각 부분의 재질에 따라 specular highlight가 다르게 표현되고 있다

'OpenGL Study' 카테고리의 다른 글

OpenGL "Multiple Lights"  (0) 2023.01.24
OpenGL "Light Casters"  (0) 2023.01.20
OpenGL "Materials"  (2) 2023.01.12
OpenGL "Colors"  (0) 2023.01.06
OpenGL "Textures"  (0) 2023.01.06