KoreanFoodie's Study

[OpenGL ES] 9강 : 라이팅, 퐁 모델(Phong Model), 난반사(Diffuse), 정반사(Specular), 주변광(Ambient), 자체 발광(Emissive) 본문

Game Dev/OpenGL ES

[OpenGL ES] 9강 : 라이팅, 퐁 모델(Phong Model), 난반사(Diffuse), 정반사(Specular), 주변광(Ambient), 자체 발광(Emissive)

GoldGiver 2023. 4. 20. 01:48

이 강의는 유투브에 무료로 공개되어 있는 한정현 교수님의 컴퓨터 그래픽스 강좌를 정리한 글입니다. 자세한 내용은 강의를 직접 들으시거나 을 구입하셔서 확인해 보세요. 강의 자료는 깃헙 링크에 올라와 있습니다.

요약 :

1. Fragment Shader 는 Texturing 과 Lighting 을 수행하는데, Lighting 의 대표적인 모델로는 퐁 라이팅 모델(Phong Lighting Model 이 있다).

2. 퐁 라이팅 모델에서, 빛은 4 가지의 요소를 더한 행렬로 표현된다 : Diffuse(난반사), Specular(정반사), Ambient(주변광), Emissive(자체 발광).

3. Specular 에 필요한 View 벡터의 경우, Vertex Shader 가 월드 공간에서 구한 View 벡터를 래스터라이저에게 넘기고, 래스터라이저는 이를 보간하여 각 점에서의 View 벡터를 Fragment Shader 에게 전달할 것이다.

라이팅(Lighting) - 퐁 라이팅 모델(Phong Lighting Model)

Fragment Shader 는 Lighting 을 수행하는데, 여기에 사용되는 모델이 70년대에 이미 만들어진 Phong Lighting Model 이다. Phong Lighting Model 은 4 가지로 구성된다 :

  • diffuse
  • specular
  • ambient
  • emissive

 

 

난반사(Diffuse)

일단 태양 처럼 각 점에 평행하게 들어오는 directional light source 의 경우를 보자. 실제로 '완벽하게' 평행은 아니겠지만, 거의 평행하게 빛이 들어온다고 가정하자 😅

 

Diffuse Reflection 은 한국말로 난반사라고 한다. 즉, 빛이 한 점에 들어왔을 때, 빛은 이곳 저곳으로 빛을 반사한다.

태양 빛이 점 p 로 들어오는 상황을 보자. 이때, l 을 태양 방향으로의 벡터, n 을 노말 벡터라고 하자. 그럼 l 과 n 의 각도 θ 에 따라 들어오는 빛의 양을 계산할 수 있다. θ 가 작아질수록, 즉 cosθ 값이 작을수록 빛을 많이 받게 될 것이다. n 과 l 이 unit vector 일 경우, 이를 그냥 n ⋅ l 로 표현할 수도 있다.

θ 가 90 도이면 빛을 제일 많이 받을 것이고, θ 가 90 도보다 커지면 음의 계수만큼 빛을 받게 되는데... 이건 말이 안 되므로, 빛을 받지 않는다고 처리한다.

 

앞서 내적을 이용해 빛의 강도를 표현했다. 그렇다면 색상은 어떨까?

색상의 경우, 빛이 (1, 1, 1), 표면이 (1, 0, 0) 이라고 하자. 그럼 그냥 각각의 항목을 곱해서 (1, 0, 0) 으로 계산하면 된다!

즉, diffuse term 은 아래와 같이 간단하게 표현 가능하다 :

max(n ⋅ l, 0) 은 빛의 강도, 뒤의 식은 색상값을 결정한다. s 가 빛의 source, m 이 material 의 데이터이다.

 

 

정반사(Specular)

Diffuse 는 정반사, Specular 는 정반사이다. Specular term 은 표면을 highlight 를 통해 빛나게 보이는데 사용되며, 이 때 reflection vector(r) 과 view vector(v) 를 필요로 한다.

이때 반사되는 벡터 r 은 어떻게 계산할 수 있을까? 바로 아래와 같이 간단히 계산하면 된다 :

뭐... 풀어 설명하자면, l 과 n 의 크기는 같은데... 중앙의 진한 파란색 벡터는 n cos θ 라고도 표현할 수 있다. l 벡터의 끝에서 해당 진파란색 벡터의 끝으로 가는 벡터는 n cos θ - l 인데... n 벡터와 대칭되게 삼각형이 하나 더 그려지게 된다(입사각 = 반사각이므로). 이때의 s 는 n cos θ 벡터의 끝에서 r 까지이므로, r - n cos θ 로 표현이 가능하다.

따라서, 두 식을 연립하면 r 벡터를 n 과 l 로 표시할 수 있다.

 

diffuse term 은 view-independent 하지만, specular term 은 view-dependent 하다. 즉, view vector 의 방향이 매우 중요하다.

빛을 완벽하게 반사하는 표면에서는, ρ 값 (반사 벡터인 r 과 view 벡터인 v 가 만드는 각도) 이 0 일 때만 highlight 된 부분이 보일 것이다.

빛을 완벽하게 반사하지 않는 경우에는, 최대 hightlight 은 ρ 가 0 일 때이지만, ρ 값이 커질 수록 hightlight 값은 급격히 감소할 것이다. 반비례 감소의 그래프를 표현할 때는 cos 함수가 매우 유용한데, 급감하는 highlight 값에 대한 계산식을 일반적으로 아래와 같이 표현한다.

만약 r 과 v 가 unit vector 일 경우, cos ρ 는 이 둘의 내적이므로, 위의 식은 아래와 같이 표현할 수도 있다 :

그리고 source 의 계수와 material 의 계수를 같이 고려해주면... (색상값) 다음과 같이 표현할 수 있을 것이다.

highlight 값이 음수가 될 일은 없을 것이므로, max 를 붙여 최솟값을 0 으로 만들어 주었다! 😁  그런데 s 와 m 은 일반적으로 gray-scale 값으로 세팅되어 있다(R, G, B 값이 다 같음). 이렇게 설정되어 있는 이유는, 정반사 시 원본 색상을 얼마나 반사할지만 결정해주려 하기 때문이다. 

 

 

주변광(Ambient Light) 과 자체 발광(Emissive Light)

우리는 방에서 형광등을 키지 않아도 물체를 볼 수 있다. 즉, 직접적인 light source 가 없어도, 반사되는 빛을 통해 물체를 관찰할 수 있는 것이다. 이와 같은 빛을 간접광(indirect lighting)이라고 하며, 다양한 물체에 반사된 빛을 ambient light 라고 한다.

ambient light 의 경우, 온갖 곳에서 들어오는 빛을 모든 방향으로 동일한 세기로 반사한다고 가정한다. 식으로 표현하면 아래와 같다 : 

 

Phong Model 의 마지막 term 인 emissive term 은 표면 자체에서 발광하는 빛의 양을 정의한다. 식으로 표현하면 다음과 같다 :

 

그럼 이제 Phong Model 에서는 위의 4 가지 term 을 전부 더해서 lighting 을 처리한다!

위의 그림은 각각의 term 을 적용한 결과를 시각적으로 보여주고 있다.

 

 지금까지 우리는 Fragment Shader 가 Lighting 을 어떻게 처리하는지 살펴보았다. 중요한 벡터로는 l, n, r, v 가 있겠다.

Vertex Shader 에서 World Matrix 를 Uniform 으로 사용하듯이, Fragment Shader 도 모든 표면의 점이 동일한 l 을 사용하고 있으므로, l 벡터 또한 uniform 으로 간주할 수 있다.

반면, 각 점마다 n, r, v 는 다르다. 따라서 우리는 n 과 v 의 조합을 Fragment Shader 에게 전달할 것이다. r 같은 경우, n 과 l 로 표현이 가능하다는 것을 앞서 증명한 바 있다! 😆

 

위에서 r 은 다음과 같이 표현되었다 :

그런데, l 은 사실 World Space 에서 정의된 벡터이다. 즉, 위의 그림에서 점 p1 과 p2 에서 정의된 Object Space 에서의 normal 은 Vertex Shader 에 의해 World Space 로 변환되어야, 비로소 Fragment Shader 가 이를 활용할 수 있다.

이전에 래스터라이저는 보간을 해 주는 녀석이라고 이야기 했었다. 즉, p1 과 p2 사이에 a, b 라는 점이 있을 때, 해당 점에서의 normal 또한 보간을 통해 구할 수 있을 것이다.

 

이제 v 벡터를 보자. 즉, 카메라를 향하는 벡터를 본다는 것이다. 우리는 각각의 포인트에서의 v 벡터를 필요로 한다. 즉, Vertex Shader 는 Object Space 에서 정의된 벡터들을 World Space 로 변환해 카메라로 향하도록 해 줄 것이다.

p1 과 p2 에서의 World-Space View 벡터는 v1, v2 로 표현될 수 있는데, 래스터라이저는 이를 보간하여 a 와 b 에서의 View 벡터 Va, Vb 를 만들 것이다. 아, 참고로 View 벡터는 그냥 월드 공간내에 한 점에서 카메라까지를 이은 벡터이다.

 

자, 그럼 위에서 래스터라이저가 a, b 점에 대해 View 벡터까지를 전부 계산해서 Fragment Shader 에게 넘겨줄 것이다.

이제 남은 것은 r 인데, r 은 이전에 n 과 l 을 이용해 계산할 수 있다고 얘기했었다.

 

 

Frament Shader 의 구현 예제

이제 코드를 보자. 먼저 우리는 Vertex Shader 가 normal 벡터와 view 벡터를 어떻게 만드는지 살펴볼 것이다.

v_normal 이 적힌 줄에서 normal 벡터를 만든다. worldPos 는 입력으로 주어진 좌표값을 의미하는데, 이를 이용해 다음 줄에서 v_view, 즉 view 벡터를 만든다. eyePos 는 그냥 카메라 좌표이다.

래스터라이저는 위에서의 normal 벡터, view 벡터, texture coordianate 를 넘겨받아 이를 보간한다.

 

그럼 이제 Fragment Shader 를 보자. 변수를 보면...

  • matSpec : Specular
  • matAmbi : Ambient
  • matEmit : Emissive
  • matSh : Shininess
  • lightDir : Directional light

등등.. 인데, 주석에 다 적혀있다 😅

 

이제 각각의 계산식을 보자. 위에서 diffuse, specular, ambient 를 각각 계산한 코드를 보여주고 있다! 😄

마지막에 fragColor 에 vec4 를 써 준건... 마지막 A(Alpha, 투명도) 를 그냥 1 로 만들어준 것이다. 즉, 투명도까지 적용한 Specular, Ambient Matrix 가 필요했다면, 처음부터 vec3 가 아니라 vec4 로 행렬들이 정의되었을 것이다. 😉

 

위 값들을 계산할때, normal 벡터와 view 벡터는 normalize 를 시켜서 사용해 주자!

Comments