Post Lists

2018년 7월 1일 일요일

Lighting - Lighting maps (4)

https://learnopengl.com/Lighting/Lighting-maps
-----------------------------------------------------------------------------

Lighting maps
이전 튜토리얼에서, 우리는 각 오브젝트가 빛에 다르게 반응하는 그것 자신만의 독특한 material를 갖게 하는 가능성을 다루었었다. 이것은 각 오브젝트에게 light가 있는 scene에서 다른 오브젝트와 비교하여 독특한 외관을 주기에 훌륭하다. 그러나 여전히 한 오브젝트의 시각적인 output에 대해 너무나 많은 유연성을 제공하는 것은 아니다.

이전 튜토리얼에서 우리는 전반적으로 전체 오브젝트에 대한 material를 정의했었다. 그러나 실제 세계에서 오브젝트들은 보통 단일의 material이 아닌 몇 가지의 materials(재료들)로 구성된다. 차를 생각해 보아라: 그것의 외관은 빛나는 것으로 이루어져 있고, 그것은 부분적으로 주변환경을 반사하는 창문을 가지고 있고, 그것의 타이어들은 거의 빛나지 않는다. 그래서 그것들은 specular highlight들을 갖지 않고, 매우 빛나는 가장자리를 가지고 있다. 그 차는 또한 그 전체 오브젝트에 대해 같지 않는 diffuse, ambient 컬러들을 가지고 있다; 한 자동차는 많고 다른 ambient/diffuse 컬러들을 보여준다. 종합해서, 그러한 한 오브젝트는 그것의 다른 부분들 각각에 대해 다른 재료 특성을 가진다.

그래서 이전의 튜토리얼에서의 material system은 가장 간단한 모델들을 제외한 모든 것에는 충분하지 않다. 그래서 우리는 diffuse and specular maps을 도입하여 이전의 시스템을 확장할 필요가 있다. 이것은 우리가 한 오브젝트의 diffuse와 (그리고 간접적으로 ambient component도, 왜냐하면 그것들은 항상 어쨋든 같기 때문에) specular component를 좀 더 정확하게 영향을 미치게 해준다.

Diffuse maps
우리가 원하는 것은 한 오브젝트의 각각의 개인의 fragment에 대해 diffuse color를 설정하는 몇 가지 방법이다. 우리가 어디에서 오브젝트의 fragment의 위치를 기반으로 한 color를 가져올 수 있을까?

이것은 아마도 매우 친숙하게 들릴 것이고, 솔직히 우리는 지금까지 그러한 시스템을 사용해왔다. 이것은 이전 튜토리얼 중 하나에서 광범위하게 우리가 이야기 한 텍스쳐처럼 들린다. 그리고 그것은 기본적으로 that: 한 텍스쳐이다. 우리는 그 같은 밑에 깔려있는 원치에 대해 다른 이름을 사용할 것이다: 우리가 fragment마다, 독특한 컬러값에 대해 indexing할 수 있는 한 오브젝트에 둘러싸여진 한 이미지를 사용하여. lighted scene에서 이것은 보통 diffuse map이라고 불려진다. (이것은 일반적으로 3D 아티스트들이 그것들을 부르는 방식이다.) 왜냐하면 텍스쳐 이미지가 그 오브젝트의 diffuse color들의 모든 것을 나타내기 때문이다.

diffuse map을 보이기 위해, 우리는 철제 가장자리가 있는 나무 container의 다음 이미지를 사용할 것이다.

쉐이더에서 diffuse map을 사용하는 것은 texture tutorial과 정확히 같다. 그러나 이번에 우리는 sampler2D로서의 그 텍스쳐를  Material struct안에 저장한다. 우리는 이전에 정의된 vec3 diffuse color vector를 diffuse map으로 교체한다.

  Red Box
  sampler2D는 소위 opaque type이라는 것을 명심해라. 그것은 우리가 이러한 타입들을 instantiate 할 수 없고, 오직 그것들을 uniforms으로서 정의할 수 있다는 것을 의미한다. 만약 우리가 이 struct를 한 uniform으로서가 아니게 (함수 인자처럼) instantiate하면, GLSL은 이상한 에러를 보낼 것이다; 따라서 같은 것이 그러한 opaque type들을 가지고 있는 어느 구조체에든 해당된다.

우리는 또한 ambient material color vector를 제거한다. 왜냐하면 ambient color는 거의 모든 경우에 diffuse color와 같기 때문이다. 그래서 그것을 개별적으로 저장할 필요가 없다:

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

in vec2 TexCoords;

  Green Box
  만약 너가 조금 완고하고 여전히 ambient color들을 다른 갑승로 설정하고 싶다면(diffuse value를 제외하고) 너는 ambient vec3를 유지할 수 있다. 하지만 그러고나서 ambient color들은 여전히 전체 object에서 갖게 남을 것이다. 각 fragment에 대해 다른 ambient value들을 얻기 위해, 너는 ambient value를 위한 다른 텍스쳐를 사용해야만 할 것이다.

우리가 fragment shader에서 텍스쳐 좌표를 또 다시 필요할 것이라는 것을 주목해라, 그래서 우리는 추가 input 변수를 선언했다. 그러고나서 우리는 간단히 텍스쳐로부터 fragment의 diffuse color value를 얻기위해 간단히 sample한다.

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

또한, ambient material 컬러를 diffuse material color와 동일하게 설정하는 것을 잊지마라:

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

그리고 그것이 diffuse map을 사용하는데 필요한 모든 것이다. 너도 보듯이 새로운 것은 없지만, 그것은 visual quality에서 극적인 증가를 제공한다. 작동시키기 위해서, 우리는 vertex data를 texture 좌표와 함께 업데이트 할 필요가 있고, 그것들을 vertex attributes로서 fragment shader에 전달하고, texture를 불러오고 texture를 적절한 texture unit에 바인드 해야 한다.

그 업데이트된 vertex data는 여기에서 볼 수 있다. vertex data는 이제 큐브의 정점들 각각에 대해 vertex positions, normal vectors and texture 좌표를 포함한다. 텍스쳐 좌표를 vertex attribute로서 얻도록 vertex shader를 업데이트하고, 그것들을 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 data가 부합되도록 vertex attribute pointer들을 업데이트하고 container image를 텍스쳐로 불러오도록 해라. 컨테이너를 그리기 전에, 우리는 선호된 texture unit에 material.diffuse uniform sampler를 할당하길 원하고, 그 컨테이너 텍스쳐를 이 텍스쳐 유닛에 바인드 하길 원한다:

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

이제 diffuse map을 사용하여, 우리는 다시 섬세한 멋진 boost를 얻고, 추가된 라이팅과 함께 이번에, 그 컨테이너는 정말 밝게 빛나기 시작한다. (꽤 말 그대로), 너의 컨테이너는 지금 아마도 이것처럼 보인다:


너는 여기에서 프로그램의 풀 소스코드를 볼 수 있다.

Specular maps
너는 아마도 specular highlight가 조금 이상해 보인다는 것을 눈치챘다. 왜냐하면 우리의 오브젝트는 주로 나무로 구성된 컨네이너기 때문에, 우리는 나무가 그러한 specular highlights를 주지 않는 것을 알기 때문이다. 우리는 이것을 그 오브젝트의 specular material을 vec3(0.0)으로 설정하면 고칠 수 있지만, 그러나 그것은 그 컨테이너의 철제 가장자리가 specular highlights를 또한 보여주는 것을 그만 둔다는 것을 의미한다. 그리고 우리는 또한 steel은 몇 가지 specular highlights를 보여준다는 것을 안다. 다시, 우리는 그 오브젝트의 부분이 다양한 강도로 specular highlight를 보여주는 것을 통제하고 싶다. 이것은 diffuse map discussion에서 친숙하게 보이는 한 문제이다. 우연의 일치인가? 나는 그렇게 생각하지 않는다.

우리는 또한 specular highlights를 위한 texture map을 사용할 수 있다. 이것은 우리가 그 오브젝트의 각 부분의 specular 강도를 정의하는 검정색, 하얀색 텍스쳐 (너가 좋아한다면 색깔들을) 만들 필요가 있다는 것을 의미한다. specular map의 한 예제는 다음의 이미지이다.

한 sepcular highlight의 강도는 그 이미지의 각 픽셀의 밝기에 의해 얻어진다. 그 specular map의 각 픽셀은 color vector로서 보여질 수 있다. 그리고 예제로 그 colorvector에서 검정색은 color vector vec3(0.0)을 의미하고, 회색은 color vector vec3(0.5)를 의미한다. fragment shader에서 우리는 그러고나서 상응하는 color 값을 sample하고 이 값을 빛의 specular intensity와 곱한다. 따라서 한 픽셀이 좀 더 '하얀색'일수록, 곱셈의 결과가 더 높고 따라서 한 오브젝트의 specular component는 더 밝다.

컨테이너는 대게 나무로 이루어져있고, material로서 wood는 어떠한 specular highlights를 가져선 안되기 때문에, 그 diffuse texture의 전체 나무 부분은 black으로 변경되어야 한다: black section들은 어떤 specular highlight를 갖지 않는다. 그 컨테이너의 철 가장자리는 다양한 specular 강도들을 그 철 자체가 상대적으로 specular highlight들을 수용가능하면서 갖는다. 그 균열들은 그렇지 않고.

  Green Box
  기술적으로 나무는 또한 specular highlights를 가지고 있다. 비록 더욱 낮은 shininess value를 가지고 (좀 더 빛이 흩뿌려지고) 덜 impact를 가졌지만, 학습 목적으로 우리는 wood가 specular light에 어떠한 작용도 갖지 않는 체 할 수 있다.

Photoshop또는 Gimp같은 툴들을 사용하여, 이처럼 몇몇 부분을 잘라서, diffuse texture를 specular image로 바꾸는 것은 쉽다. 이것은 그것을 검정색, 하얀색으로 바꾸고, 밝기/대조를 높인다.

Sampling specular maps
한 specular map은 다른 텍스쳐와 같다. 그래서 그 코드는 diffuse map code와 유사하다. 적절히 그 이미지를 불러오고, 텍스쳐 객체를 생성하도록 해라. 우리는 같은 fragment shader에서 다른 텍스쳐 sampler를 사용하고 있으니까, 우리는 다른 texture unit(see Textures)을 specular map을 위해 사용해야 한다. 그래서 그것을 렌더링하기 전에 적절한 texture에 바인드 하자.

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

그러고나서, sampler2D를 vec3 대신에 그것의 specular component로 받기위해 fragment shader의 material properties를 업데이트하자:

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

그리고 마지막으로 우리는 fragment의 상응하는 specular 강도를 얻기위해, specular map을 sample하고 싶어한다.

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

specular map을 사용하여 우리는 엄청난 세부사항을 가지고 한 오브젝트의 무슨 부분들이 실제로 빛나는 특징을 가져야 하는지를 명시할 수 있다. 그리고 우리는 심지어 그것들의 일치하는 강도를 설정할 수 있다. SPecular maps은 따라서 우리에게 diffuse map 위에 추가된 control layer를 준다.

  Green Box
  만약 너가 너무 주류과 되고 싶지 않다면, 너는 또한 specular map에서 각 fragment의 specular 강도를 설정하기 위해 실제 색을 사용할 수 있고, specular highlight의 color를 설정할 수 있다. 그러나 현실적으로, specular highlight의 색은 대게 (완전히) 광원 그 자체에 의해 결정된다. 그래서 그것은 현실적인 비쥬얼을 만들지 못할 것이다. (그것이 이미지들이 보통 검은색이고 하얀 이유이다: 우리는 오직 강도만을 신경쓴다.)

만약 너가 프로그램을 실행시킨다면, 너는 명백히 container의 material이 이제 철제 프레임이 있는 실제 나무 컨테이너의 그것과 밀접하게 닮았다는 것을 볼 수 있다:


너는 여기에서 프로그램의 풀 소스 코드를 볼 수 있다.

diffuse와 specular map을 사용하여, 우리는 정말 멋진 양의 세부사항을 상대적으로 간단한 오브젝트에 넣을 수 있다. 우리는 심지어 normal/bump map and/or reflection maps같은 다른 텍스쳐 맵을 사용하여 그 오브젝트에 좀 더 세부사항을 넣을 수 있다, 그러나 그것은 우리가 나중 튜토리얼을 위해 남겨둔 것이다. 너의 컨테이너를 모든 너의 친구들 가족에게 보여주고, 우리의 컨테이너가 어느날 심지어 이렇게 된 것보다 더 예브게 될 수 있다는 사실에 만족하자.

Exercises
  • 광원의 ambient, diffuse 그리고 specular vector를 가지고 놀고, 그것들이 어떻게 container의 시각적 결과에 영향을 미치는지 보아라.
ambient 1.f 1.f 1.f
diffuse 1.f 1.f 1.f
specular 1.f 1.f 1.f
ambient를 1.f로 다 끌어올리니 확실히 빛을 받지 않은 부분도 많이 밝아 졌다.

ambient 0.2 0.2 0.2
diffuse 0.1 0.1 0.1
specular 1.0 1.0 1.0
diffuse를 매우 줄이니 빛을 받는 부분도 매우 어두워졌고,  specular map부분만 빛을 반사하고 있다.

ambient 0.2 0.2 0.2
diffuse 0.5 0.5 0.5
specular 0.3 0.3 0.3
specular를 줄이니 확실히 철제 프레임의 반사광도 줄어들었다.



  • fragment shader에서 specular map의 컬러값을 조사하려고 해보아라. 그 나무는 specular highlight를 보여주고, steel border들을 그렇지 않도록 해보아라. (철제 가장자리에서의 갈라진 곳 때문에, 철제는 여전히 specular highlight를 보여줄 것을 유의해라. 비록 덜한 강도를 가지지만);

#version 330 core
struct Material
{
    sampler2D diffuse;
    sampler2D specular;
    float shininess;
};

struct Light
{
    vec3 postion;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

in vec2 TexCoords;
in vec3 Normal;
in vec3 FragPos;
in vec3 LightPos;

out vec4 FragColor;

uniform Material material;
uniform Light light;

void main()
{
    // Common Lighting Factors
    vec3 lightDir = normalize(LightPos - FragPos); // from fragment to light source
    vec3 surface_norm = normalize(Normal);
    vec3 viewDir = normalize(-FragPos);
    vec3 reflectDir = reflect(-lightDir, surface_norm);  // change the direction into from light source to fragment

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

    // Diffuse Lighting
    float diff = max(dot(surface_norm, lightDir), 0.0);
    vec3 diffuse = diff * vec3(texture(material.diffuse, TexCoords))  * light.diffuse;

    // Specular Lighting
    float specularStrength = 0.5;
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), material.shininess);
    vec3 specular = spec * (vec3(1.0) - vec3(texture(material.specular, TexCoords))) * light.specular;

    // Result
    vec3 result = (ambient + diffuse + specular);
    FragColor = vec4(result, 1.0);
}


  • black and white 대신에 실제 색들을 사용한 diffuse texture로부터 specular map을 만들려고 해보아라. 그리고 그 결과가 너무 현실적으로 보이지 않다는 것을 보아라. 너는 이 colored specular map을 사용할 수 있다. 만약 너가 만들수 없다면.
올려놓은 이미지 사용..

  • 또한 fragment마다 emission 값을 저장하는 텍스쳐인 emission map부르는 것을 추가해라. Emission value들을 한 오브젝트가 방출하는 컬러인데, 마치 그것은 그 자체로 광권을 가지고 있는 것처럼 보인다; 이 방식으로 한 오브젝트는 빛의 상태와 상관없이 빛날 수 있다. Emission maps은 종종 너가 한 게임에서 오브젝트가 빛날 때 보는 것이다. (로봇의 눈들, 또는 컨테이너에 있는 빛의 띠들). 다음의 텍스쳐를 emssion map으로서 컨테이너에 추가해라 마치 그 문자가 빛을 방출하는 것처럼.

이유는 모르겠는데, glActiveTexture와 glBindTexture를 0부터 사용하면 원하는 텍스쳐가 안골라진다. 그러니까 저 emission 이미지가 선택이 안되서, 그냥 밝은 나무상자가 나온다.
그런데, 1부터 지정을 하니 원하는 대로 됐다. 왜이러는지 알아볼 필요가 있다.

댓글 없음:

댓글 쓰기