그래픽스 렌더링에서 빛의 조명은 다음과 같이 총 3가지로 구분할 수 있습니다.
1. Directional Light
Directional Light은 태양을 빗대서 만든 방식입니다. 따라서 빛이 어디에서 시작하는지를 구분하지않고, 빛을 맞는 지점마다 빛의 세기에 차이를 두지 않습니다. 즉 Direction(방향)과 빛 자체의 세기만 고려합니다.
코드는 다음과 같습니다.
vec3 BlinnPhong(vec3 lightStrength, vec3 lightVec, vec3 normal, vec3 toEye,
Material mat) {
vec3 halfway = normalize(toEye + lightVec);
vec3 specular =
mat.specular * pow(glm::max(dot(halfway, normal), 0.0f), mat.shininess);
return mat.ambient + (mat.diffuse + specular) * lightStrength;
}
vec3 ComputeDirectionalLight(Light L, Material mat, vec3 normal, vec3 toEye) {
vec3 lightVec = -L.direction;
float ndotl = glm::max(dot(lightVec, normal), 0.0f);
vec3 lightStrength = L.strength * ndotl;
// Luna DX12 책에서는 Specular 계산에도
// Lambert's law가 적용된 lightStrength를 사용합니다.
return BlinnPhong(lightStrength, lightVec, normal, toEye, mat);
}
ComputeDirectionalLight함수의 lightVec을 보면 빛의 방향만 고려하고 있습니다. 블린퐁모델에 관해서는 이전의 글
을 참고하면 좋을 것 같습니다. https://pdy0930.tistory.com/70
평면위에 나타낸 모습입니다.
2. Point Light
전구를 빗대서 만든 방식입니다. 빛이 사방으로 뻗어 나가는 방식이죠. 따라서 Directional Light와 달리 빛 자체의 Direction을 고려하는게 아니라, 빛의 방향은 빛의 시작지점과 빛을 맞는 지점을 통해 빛의 방향을 구합니다. 또한 빛을 맞는 지점이 빛의 시작지점과 얼마나 떨어져 있느냐에 따라 빛의 세기를 조절합니다. 즉 Directional Light와의 차이점은 거리에 따른 빛의 세기가 다르고, 빛을 맞는 방향이 일정하지 않다는 점입니다.
거리에 따른 빛의 세기는 다음의 공식을 이용합니다.
여기서 fallOffStart는 빛의 세기가 감소하는 지점이고, falloffEnd는 최대 거리값을 제한합니다. 뿐만 아니라, 거리에 따라 빛이 얼마나 천천히 감소하는지에도 영향을 미칩니다. 왜냐하면 다음 그림과 같이 fallOffEnd값에 따라 같은 x라도 값이 달라지기 때문입니다.
예를들어 다음 두 그림은 fallOffStart는 고정하고, fallOffEnd의 값을 변화시킨 값입니다.
그렇다면 fallOffStart만 증가시킨다면 어떻게 될까요?
일정 거리 이하의 지점은 모두 1로 가져가고, 전체적으로 더 밝아지게 됩니다.
코드는 다음과 같습니다.
float Saturate(float x) { return glm::max(0.0f, glm::min(1.0f, x)); }
float CalcAttenuation(float d, float falloffStart, float falloffEnd) {
// Linear falloff
return Saturate((falloffEnd - d) / (falloffEnd - falloffStart));
}
vec3 ComputePointLight(Light L, Material mat, vec3 pos, vec3 normal,
vec3 toEye) {
vec3 lightVec = L.position - pos;
// 쉐이딩할 지점부터 조명까지의 거리 계산
float d = length(lightVec);
// 너무 멀면 조명이 적용되지 않음
if (d > L.fallOffEnd)
return vec3(0.0f);
lightVec /= d; // 유닛벡터로만듦
float ndotl = glm::max(dot(lightVec, normal), 0.0f);
vec3 lightStrength = L.strength * ndotl;
float att = CalcAttenuation(d, L.fallOffStart, L.fallOffEnd);
lightStrength *= att;
return BlinnPhong(lightStrength, lightVec, normal, toEye, mat);
}
여기서 마지막에 왜 또 블린퐁을 적용하는지 의아해 할 수도 있는데, 그냥 Directional Light방식에 빛의 세기와 빛의 방향만 바꾼 상태로 블린퐁 함수를 호출한다고 보시면 됩니다.
3. Spot Light
Spot Light는 스포트라이트 즉 한 곳에 집중적으로 빛을 쏘는 것입니다. 따라서 태양과 전구가 아닌 손전등과 같은 방식입니다. 따라서 빛 자체의 Direction이 필요합니다. 그런데 태양이 아니기 때문에 거리에 따른 빛의 세기 또한 필요합니다.
따라서 Point Light의 개념이 이용됩니다. 구하는 방법은 일단 Point Light와 똑같이 계산합니다. 그리고 빛 자체의 방향 Direction과 빛과 물체가 맞는 방향이 얼마나 일치하는지를 내적을 통해 구합니다. 그 값을 지수함수를 통해 더 차이를 분명하게 합니다.
다음 그림과 같이 빛의 방향과 빛과 물체가 맞는 방향을 계산해야 스포트라이트를 적용할 수 있음을 알 수 있습니다.
전구에 direction을 내적하여 손전등을 만든 방식이라고 생각하면 됩니다.
코드는 다음과 같습니다.
vec3 ComputeSpotLight(Light L, Material mat, vec3 pos, vec3 normal,
vec3 toEye) {
vec3 lightVec = L.position - pos;
// 쉐이딩할 지점부터 조명까지의 거리 계산
float d = length(lightVec);
// 너무 멀면 조명이 적용되지 않음
if (d > L.fallOffEnd)
return vec3(0.0f);
lightVec /= d;
float ndotl = glm::max(dot(lightVec, normal), 0.0f);
vec3 lightStrength = L.strength * ndotl;
float att = CalcAttenuation(d, L.fallOffStart, L.fallOffEnd);
lightStrength *= att;
// pow(glm::max(dot(halfway, normal), 0.0f), mat.shininess);
// direction이 있어야함
float spotFactor =
pow(glm::max(dot(-lightVec, L.direction), 0.0f), L.spotPower);
lightStrength *= spotFactor;
return BlinnPhong(lightStrength, lightVec, normal, toEye, mat);
}
적용 하면 다음과 같습니다.
강의 출처 : 홍정모 그래픽스 새싹코스 Part 2
'Graphics Techniques' 카테고리의 다른 글
[렌더링파이프라인] 구 Mapping의 원리 (0) | 2024.12.05 |
---|---|
[렌더링 파이프라인] Subdivision의 개념 (0) | 2024.12.04 |
[Rasterization] Blinn-Phone 모델(블린 퐁 모델) (0) | 2024.11.05 |
[Rasterization] 버텍스 쉐이딩과 픽셀 쉐이딩 개념 + 뒷면 제거 (0) | 2024.10.29 |
[Rasterization] 원 그리기와 vertex 2차원 변환 (1) | 2024.10.28 |