현실 세계에서 빛은 뻗어온 빛이 매질에 따라 색을 흡수하고 반사하여 우리 눈에 들어온 색을 띈다. 여기서 빛이 강하면 더욱 명도를 띄게 되고 빛이 적다면 명도가 낮아진다.
하지만 컴퓨터 그래픽에서는 빛의 반사에 따른 색과 명도를 계산할 필요가 없다. 왜냐하면 컴퓨터 세상에서 빛은 각 정점 혹은 픽셀이 가지고 있는 값에 따라 스스로 색을 띄기 때문이다.
광원 효과를 표현하는 것도 비슷하다. 우선 폴리곤을 형성하는 정점을 이용해 법선을 매핑해야 한다.
법선이라 함은 해당 정점이 바라보고 있는 방향을 가지는 벡터를 의미한다. 법선은 해당 정점 혹은 폴리곤에 부딪히는 벡터에 대해 반사각을 구할 때 사용한다. 비슷한 원리로 법선을 통해 광원이 들어오는 각도와 법선 사이의 각도를 계산해서 빛이 얼마나 들어오고 있는지 계산할 수 있는 것이다.
폴리곤은 정점 세 개가 이루는 평면의 삼각형이기 때문에 폴리곤의 각 정점의 법선은 같은 방향을 가리킨다. 법선을 계산하기 위해선 벡터의 외적을 이용해야 하며 외적할 두 벡터는 다음과 같다.
v2를 기준으로 하여, v2에서 v1으로 가는 벡터와 v2에서 v3로 가는 벡터를 외적하여 법선을 구하고 세 정점에 법선을 매핑해준다.
이렇게 법선 매핑이 끝이 나게 되면, 광원 효과를 위해 쉐이더에 어떤 방식으로 광원 효과를 만들 것인지 수식을 입력해야 한다.
밑의 그림에는 광원과 오브젝트가 있고 붉은색은 광원의 방향, 하늘색은 해당 객체 표면의 법선이다.
만약 이 오브젝트에 광원을 표시한다고 하면 어떤 식으로 표시가 될까?
우선 법선과 광원의 벡터의 각도를 계산해야 한다. 하지만 법선과 광원의 벡터는 방향만을 가지는 단위벡터이기 때문에 두 벡터를 단순히 내적을 하게 되면 우리가 원하는 각도가 나온다.
여기서 주의해야 할 점은 광원의 벡터를 그냥 넣는 것이 아닌 역벡터를 넣어야 한다. 그렇게 하는 이유는 현실 세계와 달리 컴퓨터 그래픽에서 광원 효과는 결국 해당 객체가 가지는 색을 바꾸는 것이기 때문이다.
광원과 객체의 각도는 코사인 값으로 정해지는데, 결국 평면에서 해가 떠 있을 때를 생각하면 0도에서 90도 사이가 되며, 이는 코사인 값으로는 1과 0 사이의 값으로 정의가 된다.
0에 가까울 수록 빛이 적으며, 1에 가까울 수록 빛이 더 많이 들어온다. 그리고 0 이하로 내려갈 경우에는 빛이 들어오지 않는다.
이때 광원 효과를 어떻게 만드는가에 따라 Ambient, Diffuse, Specular라는 방식으로 정의가 되는데, 그림은 다음과 같다.
Ambient
빛의 각도와는 상관 없이 주변에서 들어오는 빛을 표시하는 효과이다. 빛은 물체에 부딪혀 여러 방향으로 반사되다가 결국 직접적으로 빛이 들어오지 않는 곳에도 빛이 닿게 된다. 주변광, 간접조명 등 용어가 있지만 혼용해서 사용되므로 Ambient라고 외우자. 광원에 상관 없이 들어오는 빛이기 때문에 각도와는 무관하게 값이 적용된다.
color = material color * ambient light color
Diffuse
반사광, 분산광 등 용어가 혼용되므로 단순히 Diffuse라고 외우자. 광원에서 나오는 벡터와 물체의 표면 법선과의 각도로 계산된 값, 즉 빛의 세기에 따라 표면의 빛이 결정된다.
float diffuse = saturate(dot(light,normal));
color = float4((diffuse * input.color.rgb), 1);
return color;
여기에 빛의 세기는 역제곱 법칙에 의해 거리의 제곱에 반비례하므로 거리에 따라 값을 수정할 수 있게 식을 추가해준다.
luminocity = 1 / (distance * distance);
// 거리가 1보다 작을 때의 식
luminocity = 1 / (1 * (distance * distacne));
이 값을 위의 식에 곱해주면 최종적으로 빛의 거리에 따른 Diffues 변화도 구현이 가능하다.
float diffuse = saturate(dot(light,normal));
luminocity = 1 / (1 + (distance * distance));
color = float4((diffuse * input.color.rgb * luminocity), 1);
return color;
또 여기에 Ambient 광을 추가하여 같이 구현할 수 있는데 이 모델을 램버트 조명 모델이라고 한다.
float3 light = normalize(-float3(1, -1, 1));
float3 normal = normalize(input.Normal);
float3 diffuse = saturate(dot(light, normal)) * float3(1.0f,1.0f,1.0f);
// float3는 Ambient
return float4(input.Color.rgb * (diffuse + float3(0.5f, 0.5f, 0.5f)), 1);
Specular
정반사광이라고 표현하지만 위의 광원 효과와 마찬가지로 Specular로 외우자. Specular는 특정한 방향으로 진행하며, 표면에 닿은 빛이 한 방향으로 강하게 반사하여 특정 각도에서만 관찰할 수 있는 광원 효과이다.
물체 표면의 형태, 빛의 방향, 그리고 관찰자의 시점 등을 모두 고려해야 한다. Specular는 특히 반짝이는 표면에 빛이 반사되는 효과를 모델링하는 데 이용된다.
Specular에 필요한 것은 시야각과 반사각이다. 반사각은 hlsl 내부에 reflect라는 함수를 이용해 쉽게 구할 수 있고 시야각은 정점과 월드공간 상에서의 카메라 위치와의 차이벡터를 구하면 된다.
//world space light
float3 light = normalize(-float3(1, -1, 1));
//world space normal
float3 normal = normalize(input.Normal);
float3 i = -light;
float3 n = normal;
float3 r = reflect(i, n);
float3 toEye = normalize(input.ViewDir);
float3 diffuse = saturate(dot(light, normal)) * float3(1.0f,1.0f,1.0f);
float3 specualr = 0;
if (diffuse.x > 0)
{
specualr = saturate(dot(r, -toEye));
specualr = pow(specualr, 20.0f);
}
//float3은 Ambient 값
return float4(input.Color.rgb * (diffuse + float3(0.3f, 0.3f, 0.3f) + specualr), 1);
조금 코드가 난잡하다. 아직 제대로 정리를 하지 못한 것도 있긴 한데 Specular에 대한 이해가 모자란 것이라 생각이 든다. 어찌 되었던 이 코드의 결과물은 다음과 같다.
조명 공부를 하면서 아직 모자란 부분이 많다고 느꼈다. 엔진 파트라 다루게 된다면 얼마나 다룰지는 모르지만 어떤 식으로 게임에서 조명이 구현이 되는지 배울 수 있는 기회여서 아주 좋았다. 좀 더 공부를 하여 다채로운 조명 효과를 구현하고 싶다는 생각이 들기도 했다.
'프로그래밍 > 공부' 카테고리의 다른 글
[Graphics] 노멀매핑 & 탄젠트 공간 (0) | 2023.06.24 |
---|---|
[Graphcis] 스키닝 애니메이션 (0) | 2023.06.22 |
[DirectX3D] 3차원 공간의 충돌 (1) - 점(마우스 포인터)과 오브젝트 (0) | 2023.05.31 |
[C++] std::Function (0) | 2023.05.25 |
[Graphics] 3D 환경에서 회전 (1) | 2023.05.16 |
댓글