레이 트레이싱 (4)

Phong reflection 쉐이딩

2020년 12월 12일

Phong reflection

3차원 공간에서 입체적인 물체를 바라볼 때 물체의 부분마다 조금씩 색의 차이를 보입니다. 빛이 많이 비치는 부분은 밝고, 덜 비치는 부분은 상대적으로 어둡습니다. 어떤 물체는 표면이 아주 매끄러워 반짝반짝 빛나기도 합니다. 이번 포스트에서는 물체의 색이 빛과의 관계에 따라, 물체 본연의 성질에 따라 색이 달라질 수 있도록 하는 쉐이딩 기법, Phong reflection 을 알아보겠습니다.

Phong reflection 은 ambient (주변광), diffuse (확산광), specular (반사광) 을 조합하는 기법입니다. 식으로 쓰면 아래와 같습니다.

I=Iaka+m=light(Imdkd(nl)+Imsks(rv)α)I = I_a k_a + \sum_{m=light}(-I_{md} k_d (n \cdot l) + I_{ms} k_s (r \cdot v)^\alpha)
  • IaI_a : ambient 빛의 세기
  • ImdI_{md} : mm번째 광원의 diffuse 빛의 세기
  • ImsI_{ms} : mm번째 광원의 specular 빛의 세기
  • kak_a : 물체의 ambient 반사 상수
  • kdk_d : 물체의 diffuse 반사 상수
  • ksk_s : 물체의 specular 반사 상수
  • α\alpha : 물체의 반짝이는 정도

위 상수들은 각각의 물체, 빛에 대한 속성을 나타낸 것으로 변하지 않는 값입니다. 속성들 중 α\alpha 는 스칼라이고 그 외 나머지는 RGB 삼요소를 가진 벡터로 각각의 색에 대해 0 ~ 1 로 정도를 표현합니다. 예를 들어 초록색에 가까운 색은 [0.0, 0.8, 0.0] 와 같이 표현합니다. 각각의 상수의 역할은 이후 ambient, diffuse, specular 에 관한 절에서 자세히 살펴보겠습니다.

상수들 외에 nn, ll, rr, vv 는 쉐이딩의 계산에 필요한 벡터들이며 아래 그림과 같습니다.





Ambient

공간에 광원이 없음에도 일정 세기의 빛이 항상 존재한다고 할 때 이를 ambient, 주변광이라 합니다. 집 안의 불을 전부 끈 상태에서 눈을 떠보면 처음엔 칠흑 같지만 이후 물체들의 윤곽이 조금씩 보이기 시작합니다. ambient 는 바로 이 때 보이는 물체의 희미한 색입니다.

Phong reflection 식에서 ambient 에 의한 물체의 색은 IakaI_a k_a 로 나타냅니다. IaI_a 는 공간 안의 빛의 개수에 비례하여 설정할 수도 있고 개수에 상관없이 상수로 설정할 수도 있습니다. 중요한 것은 매우 어두운 공간에서 물체의 윤곽만을 쉐이딩하는 역할을 하므로 매우 작은 값으로 설정합니다. (e.g. [0.07, 0.07, 0.07])

각각의 물체의 ambient 반사 상수 kak_aIaI_a 의 곱을 이용하면 다음과 같은 렌더 결과를 얻을 수 있습니다.





Diffuse

diffuse 는 빛의 각도에 따라 물체의 어둡고 밝음을 표현하는 쉐이딩입니다. diffuse 에 의한 물체의 색은 Idkd(nl)-I_d k_d (n \cdot l) 로 나타냅니다. 이 때 IdI_d 는 광원의 diffuse 빛의 세기를 RGB 벡터로 나타낸 값이고 각각의 광원은 다른 IdI_d 값을 가질 수 있습니다. 예를 들어 광원이 푸른 빛을 띄도록 하려면 IdI_d 의 RGB 값 중 B 값을 높이 설정하면 됩니다.

diffuse 는 물체 표면과 광원의 위치 관계를 반영합니다. 빛을 정면에서 받는 물체는 비스듬히 받는 물체보다 더 밝을 것입니다. 여기서 정면비스듬히의 차이점을 좀 더 정량적으로 살펴 보겠습니다.

위 그림은 같은 면적이지만 그 각도에 따라 단면적당 받는 빛의 세기가 달라짐을 보여줍니다. 면적 1의 두 지점 A, B 가 빛을 받을 때 A 지점은 광원으로부터 빛이 수직으로 전달됩니다. 이 때 A 를 비추고 있는 광선의 가로 길이는 지점 A 의 길이와 같은 1입니다. 반면 B 를 비추는 광선의 가로 길이는 cosθcos\theta 이고 cosθ1cos\theta \le 1 임을 고려할 때 A 지점보다 빛을 덜 받고 있다는 것을 알 수 있습니다. diffuse 는 이처럼 물체 표면에서의 빛의 각도를 이용합니다.

위 그림에 벡터 nnll 를 추가해 보겠습니다. nn 은 물체 표면에서의 법선 벡터를, ll 은 광선의 진행 방향을 나타내는 벡터입니다. nnll 은 모두 단위 벡터입니다.

그림에서 알 수 있듯 cosθ=nlcos\theta = - n \cdot l 입니다. 이 성질을 diffuse 관련 상수와 조합해 다음과 같이 diffuse 쉐이딩 식을 이끌어 낼 수 있습니다.

Idkdcosθ=Idkd(nl)I_d k_d cos\theta = - I_d k_d (n \cdot l)

주의할 점은 cosθcos \theta 가 음수 일 때 즉, 90<θ<18090^{\circ} < \theta < 180^{\circ} 일 때 입니다. 90<θ<18090^{\circ} < \theta < 180^{\circ} 은 물체가 빛을 등지고 있다는 의미이며 아무 조건 없이 식을 계산하면 Idkdcosθ<0I_d k_d cos\theta < 0 입니다. RGB 의 값은 음수가 될 수 없으므로 diffuse 의 값은 max(Idkd(nl),0)max(- I_d k_d (n \cdot l), 0) 이라 할 수 있습니다.

이제 ambient 와 diffuse 를 동시에 적용해 렌더해 보겠습니다.





Specular

specular 는 광원의 색이 관찰자의 눈에 그대로 보여지는 효과를 줍니다. 때문에 specular 는 광원과 물체 뿐만 아니라 관찰자의 위치도 고려합니다.

specular 는 Isks(rv)αI_s k_s (r \cdot v)^\alpha 로 나타내며 이 때 각각의 벡터는 아래와 같습니다.

그림을 보면 r=l2(ln)nr = l - 2(l \cdot n)n 로 쓸 수 있습니다. rvr \cdot v 는 반사된 빛의 방향 벡터 rr 과 관찰자를 향하는 벡터 vv 의 내적이고 길이가 1인 rrvv 에 대해 rv=cosθr \cdot v = cos\theta 입니다. 반사된 빛이 관찰자의 눈에 직접 향한다면 cos0cos0^{\circ} 을 의미하므로 Isks(rv)αI_s k_s (r \cdot v)^\alpha 이 최대가 됩니다. 이 때 α\alpha 는 물체 표면의 반짝이는 정도를 나타냅니다. 아래 그림은 각 물체의 α\alpha 의 크기에 따라 달라지는 렌더 결과를 보여줍니다.

위 이미지는 diffuse 를 제외한 ambient 와 specular 값 만으로 렌더한 결과입니다. 사실 위 이미지들은 물체의 반짝이는 정도의 차이를 확연히 보여주지 못합니다. 왼쪽의 경우 α\alpha 값이 작은 것 치고 지나치게 밝은 느낌이 있습니다. Phong reflection 에서는 이와 같은 specular 의 왜곡 문제 때문에 물체의 specular 반사 상수 ksk_s 의 값을 α\alpha 에 맞춰 조절합니다. 아래는 물체의 ksk_s 값을 조절한 뒤 비교해본 결과입니다.

지금까지 Phong reflection 의 ambient, diffuse, specular 를 구하는 과정을 모두 알아보았습니다. 아래는 세 개의 쉐이딩을 모두 더한 결과 이미지 입니다.

Phong reflection 쉐이딩 식을 다시 한번 보면,

I=Iaka+m=light(Imdkd(nl)+Imsks(rv)α)I = I_a k_a + \sum_{m=light}(-I_{md} k_d (n \cdot l) + I_{ms} k_s (r \cdot v)^\alpha)

ambient 를 제외한 나머지 diffuse, specular 에 대해 m=light\sum_{m=light} 가 존재합니다. 이는 diffuse, specular 는 공간을 비추고 있는 모든 광원에 대해 그 값을 합해야 함을 뜻합니다.

광원이 하나인 경우
광원이 둘인 경우



Phong reflection 반영한 레이 트레이싱을 의사 코드로 쓰면 아래와 같습니다.

def render_ray_tracing_image():
  for y in range(0, height):
    for x in range(0, width):
      ray = # x, y 를 이용한 광선 객체
      grid[y][x] = ray_trace(ray)

def ray_trace(ray):
  obj = # 부딪힌 물체들 중 가장 가까운 물체
  if obj is None:
    return background_color
  shade = # ambient 쉐이딩
  for light in lights:
    shade += # diffuse 쉐이딩
    shade += # specular 쉐이딩
  return shade

이제 Phong reflection 식을 적용해 물체를 렌더할 수 있게 되었습니다. 하지만 식을 완성하기 위한 하나의 미지수를 아직 구하지 못했습니다. 다음 포스트 에서는 Phone reflection 식과 이후 반사, 굴절을 위해 사용되는 법선 벡터 nn 을 찾는 방법을 알아보겠습니다.





출처 및 참고