Post Lists

2018년 6월 25일 월요일

Lighting - Basic Lighting (2)

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

---------------------------------------------------
Basic Lighting
실제 세계에서의 조명은 매우 복잡하고 우리가 가진 제한된 연산력으로 계산할 수 없는 어떤 너무 많은 요소들에 의존한다. OpenGL에서의 조명은 그러므로 처리하기에 더 쉽고 상대적으로 유사하게 보이는 간단화된 모델을 사용하여 현실에 대한 근사를 기반을 한다. 이러한 조명 모델들은 우리가 그것을 이해하듯이 빛의 물리학에 기반을 둔다. 그러한 모델들 중에 하나는 Phong lighting model이다. Phong model의 주된 구성요소는 3가지 요소로 이루어져 있다: ambient, diffuse 그리고 specular 조명. 아래에서 너는 이러한 조명 요소들인 실제로 어떻게 보이는지 볼 수 있다.


  • Ambient lighting : 심지어 어두울 때도, 보통 여전히 세계 어딘가에는 몇 몇 빛이 있다. (달, 먼 거리의 빛) 그래서 오브젝트들은 거의 결코 완전히 검정색이 아니다. 이것을 재현하기 위해서 우리는 오브젝트에게 항상 어떤 색을 주는 ambient lighting constant를 사용한다.
  • Diffuise lighting : light object가 한 오브젝트에게 갖는 방향의 영향을 재현한다. 이것은 조명 모델에서 가장 시각적으로 중요한 요소이다. 한 오브젝트의 한 부분이 광원을 더 볼 수록, 그것은 더 밝아진다.
  • Specular lighting : 빛나는 물체에 나타나는 빛의 밝은 지점을 재현한다. Specular highlight는 종종 객체의 색이기 보다는 빛의 색으로 기울어진다.
시각적으로 흥미로운 scene을 만들기 위해서, 우리는 적어도 이러한 세 가지의 요소들을 재현하고 싶다. 우리는 가장 간단한 것으로 시작할 것이다 : ambient lighting

Ambient lighting
빛은 보통 단일의 광원으로부터 오는게 아니라, 우리 주변에 흩어져 있는 많은 광원으로부터 온다. 심지어 그것들이 직접적으로 눈에 보이지 않을 때에도. 빛의 특징 중 하나는 그것이 흩뿌려지고 그것의 직접적인 부근이 아닌 지점에도 다가가면서 여러 방향으로 튄다는 것이다. 빛은 따라서 다른 표면들에 반사될 수 있고, 한 오브젝트의 조명에 대해 간접적인 영향을 가질 수 있다. 이것을 고려하는 알고리즘들이 global illumination algorithms라고 불려지지만, 이러한 것들은 expensive하고 복잡하다.

복잡하고 expensive한 알고리즘을 좋아하지 않기 때문에, 우리는 ambient lighting이라는 global illumination 의 매우 간단한 모델을 사용하여 시작할 것이다. 이전 섹션에서 너가 봤듯이, 우리는  object의 fragments의 최종 결과 color에 추가한 작은 constant (light) color를 사용한다. 그래서 그것은 항상 흩뿌려진 빛이 있는 것처럼 보인다. 심지어 직접적인 광원이 없을 때도.

scene에 ambient lighting을 더하는 것은 정말 쉽다. 우리는 빛 의 값을 받아서, 그것을 작은 상수 ambient factor와 곱하고, 이것을 객체의 색과 곱한다. 그리고 그것을 fragment의 색으로 사용한다 : 

void main()
{
    float ambientStrength = 0.1;
    vec3 ambient = ambientStrength * lightColor;
    vec3 result = ambient * objectColor;
    FragColor = vec4(result, 1.0);
}

만약 너가 프로그램을 실행시킨다면, 조명의 첫 번째 단계까 성공적으로 너의 오브젝트에 적용된 것을 볼 것이다. 그 오브젝트는 꽤 어둡다, 그러나 완전히 그렇지는 않은데, ambient lighting이 적용되고 있기 때문이다. (light cube는 영향을 받지 않는다는 것을 주목해라 왜냐하면 우리는 다른 쉐이더를 쓰고있기 때문이다.) 그것은 이것처럼 보인다:


Diffuse lighting
Ambient lighting은 그자체로 가장 흥미로운 결과를 만들어 내지 않지만, diffuse lighting은 오브젝트에 중요한 시각적 영향을 주기 시작할 것이다. Diffuse lighting은 오브젝트에게 더 많은 밝기를 주는데, 그것의 fragments들이 광원으로 부터 나온 광선에 더욱 가깝게 정렬되어 있으면 그렇다. 너에게 diffuse lighting을 더 잘 이해시키기 위해, 다음의 이미지를 봐보자:

왼쪽에서, 우리는 우리의 오브젝트의 하나의 fragment를 타겟으로 하여 광선이 있는 광원을 볼 수 있다. 그리고나서 우리는 광선이 fragment를 무슨 각에서 만나는지를 측정할 필요가 있다. 만약 광선이 오브젝트의 표면에 수직이라면, 그 빛은 가장 큰 영향을 갖게 된다. 광선과 fragment 사이의 각을 측정하기 위해서, 우리는 fragment의 표면에 수직한 (여기에서 노란색 화살표로 그려진) normal vector라고 불려지는 것을 사용한다. 우리는 나중에 그것에 대해 알아볼 것이다. 두 벡터 사이의 각은 내적으로 쉽게 계산 되어진다.

너는 transformation tutorial에서 두 단위 벡터가의 각도가 작을수록, 내적의 값이 1에 좀 더 가까워진다는 것을 기억할 지도 모른다. 두 벡터사이의 각이 90도 일 때, 내적은 0이 된다. 같은 것이 theta에 적용된다. theta가 크면 클수록 빛이 fragment의 색에 가질 영향이 덜하다.

  Green Box
  두 벡터 사이의 각의 코사인 값을 얻기위해, 우리는 단위 벡터로 (길이가 1인) 작업할 것이라는 것에 주목해라. 그래서 우리는 모든 벡터들을 표준화 시킬 필요가 있다. 그렇지 않으면, 내적은 cosine 값 이상의 값을 반환한다.

내적의 결과는 따라서 우리가 fragment의 color에 빛의 영향을 계산할 수 있도록 사용할 수 있는 scalar 값을 반환한다. 이것은 다르게 밝은 fragment를 만들어낸다. 빛쪽으로 향하는 방향에 의해서.

그래서, 우리는 diffuse lighting을 계산하기 위해서 무엇을 필요하는가?

  • Normal vector : vertex의 표면에 수직인 한 벡터
  • The directed light ray(방향이 있는 광선) : 빛의 위치와 fragment의 위치 사이의 차이 벡터인 방향 벡터이다. 이 광선을 계산하기 위해서, 우리는 빛의 위치 벡터와 fragment의 위치벡터가 필요하다.
Normal vectors
normal vector는 vertex의 표면에 수직인 (단위) 벡터이다. 한 vertex는 그 자체로 표면이 없기 때문에 (그것은 공간에서 그냥 하나의 점이다)  우리는 vertex의 표면을 알기 위해, 그것의 주변 정점들을 사용하여 normal vector를 얻어낸다. 우리는 외적을 사용하여 모든 큐브의 정점들에 대해 normal vector를 계산하는 작은 트릭을 사용할 수 있지만, 3D cube는 복잡한 도형이 아니기 때문에, 우리는 간단히 그것들을 vertex data에 추가할 수 있다. 업데이트된 vertex data array는 여기에서 볼 수 있다. normals들이 참으로 cube의 평면의 표면에 수직인 벡터가 되도록 시각화 하려 해보아라. (큐브는 6개의 면으로 구성되어 있다.

우리는 vertex array에 데이터를 추가 했으니, 우리는 lighting의 vertex shader를 업데이트 해야한다:

#version 330 core
layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;

우리는 정점의 각각에 대해 normal vector를 추가했고 vertex shader를 업데이트 했으니, vertex attribute pointer들을 또한 업데이트 해야한다. lamp object는 그것의 vertex data에 대해 같은 vertex array를 사용한다는 것에 주목해라. 그러나, lamp shader는 새로이 추가된 normal vector를 쓰지 않는다. 우리는 lamp의 shader 또는 attribute configurations을 업데이트 할 필요 없지만, 우리는 적어도 vertex attribute pointer를 새로운 vertex array의 사이즈를 반영하도록 수정해야만 한다.

우리는 오직 각 vertex에 대해 처음 3개의 것만을 사용하기를 원하고 마지막 3개를 무시하기를 원한다. 그래서 우리는 오직 stride parameter만을 업데이트 해주면 된다. float 크기의 6배를 곱해서. 그러면 됐다.

  Green box
  lamp shader에 의해 완전히 사용되지 않은 vertex data를 사용하는 것이 비효율적으로 보이지만, vertex data는 container object로 부터 이미 GPU의 메모리에 저장되어 있다. 그래서 우리는 GPU 메모리에 새로운 데이터를 저장할 필요가 없다. 이것은 실제로 lamp에 특정하게 새로운 VBO를 할당하는 것과 비교해서 더욱 효율적으로 만든다.

모든 조명 계산들은 fragment shader에서 된다. 그래서 우리는 normal vector들을 vertex shader에서 fragment shader로 보낼 필요가 있다. 그것을 하자:

out vec3 Normal;

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);
    Normal = aNormal;
}

해야할 남은 것은 fragment shader에 그에 상응하는 input 변수를 선언하는 것이다.

  in vec3 Normal;

Calculating the diffuse color
우리는 이제 각 vertex에 대해 normal vector를 가졌지만, 여전히 light의 position vector와 fragment의 위치 벡터를 필요로 한다. 빛의 위치는 그냥 정적 변수이기에, 우리는 간단히 그것을 fragment shader에서 uniform으로 선언할 수 있다.

  uniform vec3 lightPos;

그러고나서 game loop에서 uniform을 업데이트 하자. (또는 변하지 않으니까 밖에서) 우리는 이전 튜토리얼에서 광원의 위치로서 선언한 lightPos 벡터를 사용한다:

  lightingShader.setVec3("lightPos", lightPos);

그러노가서 우리가 필요한 마지막 것은 실제 fragment의 위치이다. 우리는 world space에서의 모든 계산을 할 것이다. 그래서 우리는 world space에 있는 vertex position이 필요하다. 우리는 이것을 model matrix로 (view나 projection matrix가 아닌) vertex position attribute을 곱하여 할 수 있다. vertex position attribute를 world space 좌표로 바꾸기 위해서. 이것은 쉽게 vertex shader에서 이루어질 수 있다. 그래서 output 변수를 선언하고 그것의 world space 좌표를 계산하자.

out vec3 Normal;
out vec3 FragPos;

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);
    FragPos = vec3(model * vec4(aPos, 1.0));
    Normal = aNormal;
}

마지막으로, fragment shader에 상응하는 input 변수를 추가하자:

  in vec3 FragPos;

요구되는 모든 변수가 설정되었으니, 우리는 fragment shader에서 조명 계산을 시작할 수 있다.

우리가 계산할 필요가 있는 첫 번째 것은 광원과 fragment 위치 사이의 방향 벡터이다. 우리는 빛의 방향 벡터가 빛의 위치벡터와 fragment의 위치벡터 사이의 차라고 언급했었다. transformation tutorial에서 너가 기억하듯이, 우리는 두 벡터를 빼서 이 차이를 쉽게 계산할 수 있다. 우리는 또한 모든 관련된 벡터들이 단위 벡터가 되기를 원한다. 그래서 우리는 normal과 결과 방향 벡터 둘 다 표준화 한다.

vec3 norm = normalize(Normal);
vec3 lightDir = normalize(lightPos - FragPos);

  Green Box
  조명을 계산할 때, 우리는 보통 벡터 또는 위치의 크기를 신경쓰지 않는다. 우리는 오직 방향만을 신경쓴다. 왜냐하면 우리는 그들의 방향만을 신경쓰기 떄문에, 거의 모든 계산은 단위벡터로 된다. 그것이 대부분의 계산을 간단하게 한다. (내적처럼) 그래서 조명 계산을 할 때, 너가 항상 관계된 벡터가 단위 벡터이도록 하여 표준화하도록 해라. 한 벡터를 표준화하는 것을 잊는 것은 자주 있는 실수이다.

다음으로 우리는 light가 현재 fragment에 대해 갖는 실제 diffuse impact를 계산하기를 원한다. 이것은 norm과 lightDir 벡터를 내적하여 된다. 그 결과값은 diffuse component를 얻기위해 그러고나서 빛의 색과 곱해진다. 이것은 두 벡터 사이의 각이 클수록, 더 어두운 diffuse component를 만들어 낸다:

float diff = max(dot(norm, lightDir), 0.0);
vec3 diffuse = diff * lightColor;

만약 두 벡터 사이의 각이 90도 이상이라면, 그 내적의 결과는 항상 음수가 된다. 그리고 음의 diffuse component를 얻게 된다. 그 이유 때문에, 우리는 두 인자 중에서 가장 높은 값을 반환하는 max function을 사용한다. 이것은 diffuse component가 (따라서 color가) 결코 음수가 되지 않도록 하기 위해서 이다. 음수 color에 대한 조명은 실제로 정의되지 않는다. 그래서 그것으로부터 멀어지는게 최상이다. 만약 너가 이상한 예술가 중 하나가 아니라면.

우리는 ambient와 diffuse component를 가졌으니, 서로에 대해 두 color를 더하고, 그러고나서 결과 fragment output color를 얻기위해 object color와 result를 곱한다 :

vec3 result = (ambient + diffuse) * objectColor;
FragColor = vec4(result, 1.0);

만약 너의 프로그램 (그리고 쉐이더가) 성공적으로 컴파일 됐다면 너는 이것과 같은 것을 볼 것이다:



너는 diffuse lighting으로 cube가 실제 cube 처럼 보이기 시작한다는 것을 볼 수 있다. 너의 머리속에 normal vector를 시각화 하려고 해보고  큐브를 움직여라, 빛의 방향과 normal vector들 사이의 각이 클수록 fragment가 더 어두워지는 것을 보기 위해서.

만약 너가 막혔다면, 완전한 소스 코드와 너의 코드를 자유롭게 비교해 보아라.

One last thing
이제야, 우리는 vertex shader로부터 fragment shader로 normal vector들을 보내고 있다. 그러나, fragment shader에서 하고 있는 우리의 계산들은 모두 world space 좌표에서 처리된다. 그래서 우리는 그 normal vector들도 world space 좌표로 또한 변환해야 되지 않을까? 기본적으로 그렇다, 그러나, model matrix를 곱해서 하는 것만큼 간단하지 않다.

우선적으로, 모든 normal 벡터들이 오직 방향 벡터이고 공간에서 특정 위치를 나타내지 않는다. 또한, normal 벡터들은 동차 좌표를 갖지 않는다. (vertex position의 w 요소) 이것은 평행이동이 normal(법선) 벡터들에 영향을 미쳐선 안된다는 것을 의미한다. 그래서 만약 우리가 normal vector에 model matrix를 곱하길 원한다면, 우리는 model matrix의 왼쪽 상단의 3x3 matrix를 취하여 translation part를 없애고 싶어한다. (우리는 또한 법선 벡터의 w component를 0으로 설정할 수 있고, 4x4 matrix와 곱할 수 있다는 것에 주목해라; 이것은 또한 translation을 제거한다) 우리가 normal vector에 적용하고 싶은 transformation은 scale과 rotation transformation이다.

두 번째로, 만약 model matrix가 non-uniform scale을 수행한다면, 정점들은 normal vector가 표면에 더이상 수직이 아니게 되도록 바뀔 것이고, 그래서 우리는 normal vector들을 그러한 model matrix를 가지고 변환할 수 없게 된다. 다음의 이미지는 그러한 model matrix (균일하지 않은 스케일링을 가진)가 normal vector에 갖는 영향을 보여준다.

우리가 non-uniform scale을 적용할 때 마다, (주의 : uniform scale들은 그들의 방향이 바뀌지 않기 때문에, normals을 건드리지 않을 것이다. 그냥 그것들의 크기만을 바꾼다. 그리고 이것은 표준화하여 쉽게 고쳐진다) 법선 벡터들은 더 이상 상응하는 표면에 수직이 아니게 되고, 조명을 왜곡하게 된다.

이 행동을 수정하는 트릭은 normal vector에 맞게 만들어진 다른 model matrix를 사용하는 것이다. 이 matrix는 normal matrix라고 불려지고 normal vector들을 잘못 스케일링 하는 효과를 제거하는 몇 가지 선형 대수 연산을 사용한다. 만약 너가 이 행렬이 어떻게 계산되어지는지를 알고 싶다면, 나는 다음의 자료를 제시한다.


normal matrix는 model matrix의 왼쪽 상단 모서리의 역행렬의 전치행렬로 정의된다. 휴, 꽤 복잡하다 그리고 만약 너가 그것이 무엇을 의미하는지 모른다면, 걱정하지 말라; 우리는 아직 역행렬과 전치행렬을 이야기하지 않았다. 대부분의 자원들이 normal matrix를 model-view matrix에 적용되는 이러한 연산들로 정의된다는 것을 알아라, 그러나 우리는 world space에서 작업하기 때문에 (view space가 아니라) 우리는 오직 model matrix를 사용한다.

vertex shader에서, 우리는 어떤 matrix type에 든지 작동하는 vertex shader에 있는 inverse와 transpose 함수들을 이용하여, normal matrix를 스스로 만들어 낼 수 있다:

  Normal = mat3(transpose(inverse(model)) * aNormal;

diffuse lighting section에서 조명은 그냥 괜찮다. 왜냐하면 우리는 객체에게 그 자체로 어떤 scaling operation을 하지 않았기 때문이다. 그래서 normal matrix를 사용할 필요는 정말 없다. 그리고 그냥 model matrix로 normal에 곱할 수 있다. 그러나 너가 만약 non-uniform scale을 한다면, 너가 normal matrix로 normal vector에 곱해야 한다는 것은 필수적이다.

  Red Box
  행렬을 역행렬 하는 것은 심지어 쉐이더에게도 비싼 연산이다. 그래서 가능하다면, 쉐이더에서 역행렬 연산하는 것을 피하려고 해라. 왜냐하면 너의 scene의 각 vertex에 대해서 처리될 것이기 때문이다. 학습의 목적으로 이것은 괜챃지만, 효율적인 프로그램을 위해서, 너는 CPU에서 normal matrix를 계산하기를 원할 것이고, 그것을 그리기 전에 uniform을 통해서 shader들을 보내기를 원할 것이다. (model matrix 처럼)

(그래서 나는, 
glm::mat3 normalM = glm::mat3(glm::transpose(glm::inverse(modelM)));
lighting_Shader.setMat3("normalMatrix", normalM); 
이렇게 했다.)

Specular Lighting
만약 너가 모든lighting 계산으로 벌써 힘들지 않다면, 우리는 specular highlights를 추가하여 Phong lighting model를 마무리하는 것을 시작할 수 있다.

diffuse lighting 처럼, specular lighting은 빛의 방향 벡터와 오브젝트의 법선 벡터에 기반을 두지만, 이번에 그것은 또한 view dirction에 기반을 둔다. 예를들어 player가 fragment를 무슨 방향에서 보고있는지로부터. Specular lighting은 빛의 반사 성질에 기반을 둔다. 만약 우리가 오브젝트의 표면을 거울이라고 생각한다면, specular lighting은 우리가 표면에서 반사된 빛을 보는 곳에서 가장 강하다. 너는 이 효과를 다음의 이미지에서 볼 수 있다.

우리는 reflection vector(반사 벡터)를 normal vector와 관련하여 light direction을 반사시켜 구할 수 있다. 그러고나서 우리는 이 반사 벡터와 view direction 사이의 angular distance를 구한다. 그리고 그것들 사이의 각이 가까우면 가까울 수록, specular light의 영향은 더욱 커진다. 최종 효과는 우리는 오브젝트를 통해 반사된 빛의 방향을 바라볼 때, 조금의 highlight를 보게 되는 것이다.

view vector는 specular lighting을 위해 우리가 필요한 추가 여분의 변수이다. 그리고 우리는 viewer의 world space position과 fragment의 position을 이용하여 그것을 계산할 수 있다. 그러고나서 우리는 specular의 강도를 계산하고, 이것을 light color와 곱하고, 최종 ambient, diffuse component에 더한다.

  Green box
  우리는 world space에서 조명 계산하는 것을 선택했지만, 대부분의 사람들은 view space에서 조명 계산 하는 것을 선호하는 경향이 있다. view space에서 계산하는 것의 추가 이점은 view의 위치가 항상 (0,0,0)이라는 것이다. 그래서 너는 이미 무료로 view의 위치를 얻었다. 그러나 나는 world space에서 조명을 계산하는 것이 학습의 목적으로 더욱 직관적이라는 것을 안다. 만약 너가 여전히 view space에서 조명을 계산하기를 원한다면, 너는 모든 관련된 벡터들을 또한 view matrix로 바꿔야 한다. (normal matrix도 바꾸는 것을 잊지마라.)

viewer의 world space coordinate를 얻기위해, 우리는 간단히 camera object의 position vector를 가져온다. (물론 이것이 viewer이다.) 그래서 fragment shader에 또 다른 uniform을 추가하고, 사응하는 camera position vector를 fragment shader로 넘기자.

  uniform vec3 viewPos;
  
  lightingShader.setVec3("viewPos", camera.Position);

우리는 모든 요구되는 변수들을 가졌으니, 우리는 specular intensity를 계산할 수 있다. 처음에 우리는 specular highlihgt에 중간 정도의 밝은 색을 주기 위해 specular intensity value를 정의한다. 이것은 너무 많은 영향을 갖지 않기 위해서이다:

  float specularStrength = 0.5;

만약 우리가 이것은 1.0f로 설정한다면, 우리는 coral cube에 정말 밝은 specular component를 얻게 된다. 다음 튜토리얼에서, 우리는 적절히 모든 이러한 조명 강도를 설정하는 것에 대해 이야기할 것이다. 그리고 그것들이 어떻게 오브젝트들에 영향을 미치는지에 대해 설명할 것이다. 다음에 우리는 view direction vector를 계산한다. 그리고 normal axis에 따라 상응하는 reflect vector(반사 벡터)를 계산한다:

  vec3 viewDir = normalize(vuewPos - FragPos);
  vec3 reflectDir = reflect(-lightDir, norm);

(
reflect함수는 위의 링크를 통해서 어떻게 구현되는지 볼 수 있다.

위의 글을 이해하려면 벡터 투영에 대해서 이해해야 한다.
벡터 투영이란 A라는 벡터를 B라는 벡터의 방향으로 길이를 유지한채 가져가는 것이다.
그래서 위 링크의 그림에 따라, A라는 벡터를 B라는 벡터에 투영했을 때, 삼각법에 의해서
|A|cosΘ 를 해야, cosΘ = A를 B에 투영한 길이/ |A| 이므로,
|A|cosΘ = A를 B에 투영한 길이가 된다.
그리고 B의 단위벡터로 방향을 구하고, 그 방향에 길이를 구해주면, A를 B에 투영한 벡터가 된다.
그래서 이 투영벡터 원리를 이용해서, 입사벡터를 N에 투영시키고, I 만큼 움직인후, 그 투영벡터를 두 번 더해주면 반사벡터가 된다. 그런데, 입사벡터를 N에 투영시킬 때, 입사벡터와 법선벡터가 이루는 각이 둔각이 되므로, N의 반대방향을 향하게 되므로, -을 해주어야 한다.

그래서 일반식은, I가 입사벡터, N이 법선 벡터라 한다면
reflectDir = l - 2 * dot(l, N) * N;가 된다.
근데 코드에서 입사벡터 l은 -lightDir 이므로, (lightDir를 표면에서 광원 방향으로 구해서)
vec3 reflectDir = -lightDir + 2 * dot(lightdir, norm) * norm;이 된다.
)

우리가 lightDir vector를 음수화 시켰다는 것에 주목해라. reflect 함수는 첫 번째 vector가 광원으로부터 fragment의 위치쪽으로 향하기를 기대하지만, lightDir은 현재 fragment에서 광원쪽으로 향하도록 되어 있다. (우리가 lightDir 벡터를 계산할 때 이전에 빼기의 순서에 의존한다.) 우리가 올바른 reflect vector를 얻도록 하기 위해, 우리는 lightDir 벡터를 처음에 음수화 시켜 그것의 방향을 반대로 바꾼다. 두 번째 인자는 normal vector를 요구한다. 그래서 우리는 normalized된 norm vector를 준다.

그러고나서, 해야할 남은 것은 실제로 specular component를 계산하는 것이다. 이것은 다음의 공식으로 이루어진다.

  float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
  vec3 specular = specularStrength * spec * lightColor;

우리는 처음에 view direction과 reflect direction 사이의 내적을 계산한다. (음수가 아니게끔 해야한다, 즉 max 사용했다는 말). 그리고 그것을 32승 해준다. 이 32 값은 highlight의 shininess value이다. 한 오브젝트의 shininess value가 높으면 높을수록, 주변에 빛을 흩뿌리고 highlight가 더 작아지는 대신에 그것은 적절히 빛을 더 반사시킨다. 아래에서 너는 다른 shininess value의 시각적 효과를 보여주는 이미지를 볼 수 있다.:

우리는 specular component가 너무 우세한 것을 원하지 않는다. 그래서 32의 지수로 유지한다. 해야할 유일하게 남은 것은 그것을 ambient와 diffuse component에 더하고, object의 color에 최종적으로 곱하는 것이다:

  vec3 result = (ambient + diffuse + specular) * objectColor;
  FragColor = vec4(result, 1.0);

우리는 이제 Phong lighting model의 모든 조명 요소들을 계산했다. 너의 관점으로 기반으로하여 너는 이것과 같은 것을 볼 것이다:


너는 여기에서 프로그램의 완전한 소스코드를 발견할 수 있다.


  Green Box
  lighting shader의 초기 시절에, 개발자들은 vertex shader에 Phong lighting model을 구현하곤 했었다. vertex shader에서 조명을 계산하는 이점은 일반적으로 fragments 보다, 많이 적은 정점들이 있기 때문에 더 효율적이라는 것이다. 그래서 (비싼) 조명 계산은 덜 자주 했었다. 그러나, vertex shader에 있는 결과 컬러 값은 오직 그 vertex에 대한 최종 lighting color이고, 주변의 fragment들의 컬러값들은 interpolated된 lighting color들의 결과이다. 그 결과는 많은 양의 정점들을 사용하지 않으면 조명이 매우 현실적이지 않았다는 것이다:

Phong lighting model이 vertex shader에서 구현될 때, Phong shading 대신에 Gouraud shading이라고 불려진다. interpolation 때문에, lighting이 조금 덜해 보인다.Phong shading은 더 부드러운 조명 결과를 준다.

이제야, 너는 쉐이더들이 얼마나 강력하지 보기 시작할 것이다. 적은 정보로, 쉐이더들은 조명이 어떻게 모든 객체의 fragment의 color들에 영향을 미치는지 계산할 수 있다. 다음 튜토리얼에서 우리는 우리가 lighting model 가지고 할 수 있는 것에 조금 더 파고 들어갈 것이다.

Exercises
  • 지금 당장 광원은 움직이지 않는 지루한 static light source이다. scene에서 시간에 따라 sin 또는 cos를 사용하여, 광원을 움직이도록 해라. 시간에 따라 빛이 변하는 것을 보는 것은 너에게 Phong의 lighting model에 대해 좋은 이유를 줄 것이다.
이미 위에서 움직이게 해놓았다.
vec3 real_lightPos = vec3(lightmodel * vec4(lightPos, 1.0));
움직임에 따라 lightPos가 바뀌니 이것을 lightsource의 model matrix를 lighting shader에 uniform으로 보내어, 실제 lightPos를 구한다.
  • 다른 ambient, diffuse, specular strength를 가지고 놀아라. 그것들이 어떻게 결과에 영향을 미치는지 보아라. 또한 shininess factor를 가지고 실험해라. 왜 어떤 값들이 어떤 시각적 output을 가지는 이해하려고 해라.
ambient = 0.1
diff = non-change
specularStrength = 0.5
shininess = 32



ambient = 0.5
diff = non-change
specularStrength = 0.5
shininness = 32
ambient 값을 높였다. global illumination을 siplified 한 것이기 때문에, 확실히 이 값을 조정함에 따라, 빛을 비추지 않는 부분이 좀 더 명확하게 보인다.

이 블로그에서 한 자료를 번역했는데 Ambient, Diffuse, Specular의 개념이 명확히 잡혀있어서 그것을 나도 공부 겸 써본다.

빛의 종류는 3가지로 추상화하는데

Ambient Light(주변광) : 수많은 반사를 거쳐서 광원이 불분명한 빛이다. 물체를 덮고 있는 빛이며 일정한 밝기와 색으로 표현된다. 그러니까 LearnOpenGL에서 global illumination이라는 것이다.

Diffuse Light(분산광) : 물체의 표면에서 분산되어 눈으로 바로 들어오는 빛이다. 각도에 따라 발기가 다르다. 광원이 어떤 방향으로 빛을 쏘았을 때, 그 빛을 맞은 표면에 빛이 분산되어지는데, 그 분산되는 정도는, 표면에서 빛을 향하는 벡터와 표면의 법선벡터 사이의 각을 통해서 그 빛의 분산되는 정도를 결정한다. 그래서 그 각이 작아질 수록, 분산되는 정도가 작아서 더 그쪽은 밝아지게 된다.

Specular Light(반사광) : 분산광과 달리 한 방향으로 완전히 반사되는 빛이다. 반사되는 부분은 흰색의 광으로 보인다. 빛이 반사되는 성질 때문에, 빛의 색이 나에게로 들어오게 된다. 이 때, 광원에서 표면으로 향하는 벡터와 표면의 법선벡터로, 반사 벡터를 구하고, 그 반사 벡터와 보는 사람의 표면에 대한 방향 벡터 사이의 각으로 그 빛의 강도를 조절한다.

ambient = 0.5
diff *= 0.5
specularStrength = 0.5
shininness = 32
diffuse 값을 곱해주니 1/2로 나눠주니 확실히 ambient 때문에 높아진 값이 줄어들었다. 분산광의 영향을 줄인것이다. 이것을 1.5로 곱해주었더니 이전의 결과처럼 다시 밝아졌었다.

ambient = 0.1
diff = non-change
specularStrength = 0.5
shininess = 256

ambient = 0.1
diff = non-change
specularStrength = 0.5
shininess = 32
확실히 shininness를 높여주니 반사되는 광이 좀 더 조밀하게 모이고, 낮게하니, 그게 더 퍼져있다. specular lighting은 반사광의 정도이니,

float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32); 의 코드에서 shininness 가 32이인데, viewDir과 reflectDir의 내적을하면은 cos(theta)값이 된다. 여기에서 cos(theta값은 max함수에 의해서 0과 1사이의 값이 되는데 , 0과 1사이의 값을 지수승하게 되면, spec의 값이 줄어든다. 그래서, shininess가 낮을수록 반사광이 퍼져보이고, shininess가 높을수록 반사광이 집중되어 보인다. 왜냐하면, shininness가 낮으면, theta가 크더라도, 적게 지수승하게 되고, lightColor를 object에 더 추가하게 되는데, shininess가 높으면, theta가 작더라도, 크게 지수승하게 되므로, lightColor를 object에 덜 추가하게 된다.

vec3 specular = specularStrength *spec * lightColor;
이기 때문에, shininess를 낮추는 것은, specularStrength를 높이는 효과와 같고
shininess를 높이는 것은 sepcularStrength를 낮추는 효과와 같다.

이 정도면은 ambient, diffuse, specular lighting system. phong lighting model에 대한 이해를 했다고 말할 수 있겠다.

  • world space 대신에 view space에서 Phong shading을 해라.
#version 330 core

layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;

out vec3 Normal;
out vec3 FragPos;
out vec3 LightPos;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform mat3 normalMatrix;
uniform vec3 vertex_lightPos;
uniform mat4 light_model;

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);
    FragPos = vec3(view * model * vec4(aPos, 1.0));
    Normal = normalMatrix * aNormal;
    LightPos = vec3(view * light_model * vec4(vertex_lightPos, 1.0));
}

#version 330 core

in vec3 Normal;
in vec3 FragPos;
in vec3 LightPos;

out vec4 FragColor;

uniform vec3 objectColor;
uniform vec3 lightColor;

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
    float ambientStrength = 0.1;
    vec3 ambient = ambientStrength * lightColor;

    // Diffuse Lighting
    float diff = max(dot(surface_norm, lightDir), 0.0);
    vec3 diffuse = diff * lightColor;

    // Specular Lighting
    float specularStrength = 0.5;
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
    vec3 specular = specularStrength * spec * lightColor;

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

  • Phong shading 대신에 Gouraud shading을 구현해라. 만약 너가 올바르게 했다면, 조명은 큐브 오브젝트와 함께 조금 이상해 보일 것이다. (특히 specular highlights와). 왜 그것이 매우 이상해 보이는지 추론하려고 해라.

#version 330 core

layout (location = 0) in vec3 aPos;
layout (location = 1) in vec3 aNormal;

out vec3 LightingColor;

uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
uniform mat3 normalMatrix;
uniform vec3 vertex_lightPos;
uniform mat4 light_model;
uniform vec3 lightColor;

void main()
{
    gl_Position = projection * view * model * vec4(aPos, 1.0);
    vec3 FragPos = vec3(view * model * vec4(aPos, 1.0));
    vec3 Normal = normalMatrix * aNormal;
    vec3 LightPos = vec3(view * light_model * vec4(vertex_lightPos, 1.0));

    // 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
    float ambientStrength = 0.1;
    vec3 ambient = ambientStrength * lightColor;

    // Diffuse Lighting
    float diff = max(dot(surface_norm, lightDir), 0.0);
    vec3 diffuse = diff * lightColor;

    // Specular Lighting
    float specularStrength = 0.5;
    float spec = pow(max(dot(viewDir, reflectDir), 0.0), 32);
    vec3 specular = specularStrength * spec * lightColor;

    LightingColor = ambient + diffuse + specular;
}

#version 330 core

in vec3 LightingColor;

out vec4 FragColor;

uniform vec3 objectColor;

void main()
{
    FragColor = vec4(LightingColor * objectColor, 1.0);
}

interpolation..

댓글 없음:

댓글 쓰기