이제 정반사(specular) highlight를 추가하면 phong 조명 모델을 완성할 수 있습니다.
diffuse light 처럼 specular 정반사 조명도 빛이 방향 벡터와 객체의 법선 벡터가 필요합니다. 다른 점은 이번에는 관찰자가 프래그먼트를 보는 방향이 추가적으로 필요합니다. 다시 말하면 view 방향이죠. 정반사 조명은 빛의 반사적인 특성에서 나온 겁니다. 객체의 표면이 거울이라 가정하면 정반사 조명은 표면에서 빛이 반사되는 부분이 제일 강합니다.
빛은 법선 벡터를 기준으로 반사되죠. 빛의 방향과 법선 벡터를 갖고 반사 벡터를 계산할 수 있습니다. 그 다음, 반사 벡터와 뷰 방향 간의 각도 θ를 구합니다. 이 각도가 가까우면 정반사 조명이 세겠죠. 정반사의 결과, 표면을 통해서 반사된 빛이 하이라이트를 띄게 됩니다.
정반사 조명을 위해서는 뷰 벡터가 필요합니다. 뷰 벡터는 구하는 법은 관찰자의 월드 공간 위치벡터와 프래그먼트 위치 벡터의 차로 계산합니다. 그 다음 정반사 빛의 강도를 계산하고, 조명의 색과 곱해서 기존 주변광과 확산광 성분에 추가하는 식으로 하면 됩니다.
우리는 조명을 월드 공간에서 계산하지만, 보통은 뷰 공간에서 많이 계산합니다. 왜 그러냐면 뷰 공간에서는 관찰자가 항상 (0,0,0)이라서 관찰자의 위치를 이미 알게 되죠? 하지만 월드 공간에서 계산해보면서 공부를 해보는게 직관적이고 좋습니다. 뷰 공간에서 조명 계산을 하고 싶으면 벡처들을 모두 view matrix로 변환해야하죠(법선 행렬에도 view matrix를 추가해야죠).
관찰자의 월드 공간 좌표는 카메라의 위치 벡터를 가져오면 됩니다. 프래그먼트 셰이더에 uniform을 추가해서 카메라 위치 벡터를 셰이더에 전달합니다.
여기서 중요한 점은 reflect하는 함수를 사용할 때 lightDir가 (-)가 된 겁니다. 왜 반대로 방향을 바꿨냐면 지금 lightDir의 방향은 광원에서 프래그먼트로 향하는게 아니라 반대로 프래그먼트에서 광원을 향하는 방향이거든요. (이전 코드를 뒤져보면 광원벡터에서 프래그먼트 벡터를 빼서 계산했어요)
그래서 lightDir를 (-)로 방향을 반대로해서 적용하고, 두 번째 인자로 법선 벡터를 기준으로 반사하도록 norm을 줍니다.
뷰 방향과 반사 방향의 내적을 계산합니다(max로 음수는 안되도록). 그리고 pow로 32제곱을 합니다. 왜 이렇게 높은 제곱을 하냐면 눈부신 하이라이트 광택을 표현하기 위해서 그럽니다. 이 광택값이 높을수록 하이라이트의 면적이 좁아집니다. 광택 값에 따라서 하이라이트가 어떻게 변하는지 보시죠.
https://learnopengl.com/Lighting/Basic-Lighting
이제 정반사 성분을 주변광과 확산광 성분에 추가해서 결과를 객체의 색상과 곱하면 완성입니다.
초기에는 phong light model을 vertex shader에서 구현했었다고 해요. 그렇게하면 프래그먼트보다 정점의 수가 적으니까 조명 계산을 효율적으로 할 수 있다고 합니다. 하지만 vertex shader에서 계산한 색상은 그 정점에서만 색을 나타내고 주변 프래그먼트의 색은 자동으로 보간된 색상으로 결정됩니다. 그러면 정점이 엄청 많은게 아닌 이상 현실적인 조명을 표현하지는 못했죠.
사실 방금 말한 vertex shader에서 phong light 를 구현한건 phong shading이 아니라 gouraud(고러드) shading이라고 해요. 사진으로보면 조명이 좀 어색한 감이 있죠. 반면에 phong은 부드럽고요.
이제 shader가 얼마나 강력한 도구인지 알았나요? shader는 몇 가지 정보만 이렇게 넘겨주면 조명이 프래그먼트 색상에 미치는 효과를 계산해서 표현할 수 있답니다. 다음에는 조명 모델을 더 활용해보도록 합시다.