조명(Lighting)
Lighting
조명은 기본적으로 아래 세 가지 요소로 이루어진다.
- Ambient
- Diffuse
- Specular
그리고 이 조명을 적용하는 셰이더 방식은 두 가지가 있다.
- Forward Lighting: 메쉬마다 조명 적용.
- Deffered Lighting: MRT에 빛 연산 중간 내용들을 저장 후 마지막에 적용.
포워드 라이팅은 각 드로우 콜(draw call)마다 빛에 의한 색상 변화를 화면에 나타날 최종 이미지에 더하기 때문에 성능 측면에서 많이 무겁다. N개의 광원과 M개의 메시가 있다면 N*M 만큼 연산을 하기 때문에 요즘에는 잘 쓰지 않지만 그래도 알아두는 것이 좋다.
조명 종류(directional light, point light, spot light)에 대해서는 생략함. 아래 조명은 point light로만 구현
Ambient
주변광(환경광; ambient light)은 특정 광원에서 직접 나온다고 정의할 수 없는, 씬 전체에 영향을 미치는 빛을 말한다. 반사되고, 반사되고, 반사되어 비추는 빛이나 최소한의 빛을 일일이 계산하게 되면 비용이 많이 들고 복잡하기 때문에 실질적인 계산없이 씬 전체에 균일하게 빛을 준다.
FLOAT3 ambient = FLOAT3(255.0f, 0.0f, 0.0f);
Diffuse
분산광(난반사광; diffuse light)은 일정한 방향으로 빛이 들어와 물체의 표면에서 여러 방향으로 분산되는 빛의 형태를 말한다. 분산광은 램버트 코사인 법칙(lambert's consine law)으로 구한다.
노멀 벡터(normal vector)는 객체에 접하는 평면에 수직인 벡터를 말한다.
Lamert's cosine law
어떤 픽셀에 대해서 광원 벡터 L과 노멀벡터 N이 있을 때 우측처럼 L과 N이 수직일 경우 해당 픽셀은 광원의 빛을 온전히 받게 된다. 만약 좌측 그림처럼 광원이 노멀 벡터에서 θ만큼 기울어져 있다면 해당 픽셀은 빛을 cosθ(L, N은 단위벡터. (1 * 1 * cosθ))만큼만 받게된다. 광원과 노멀 벡터 사이의 각도가 커질수록 빛의 세기(intensity)는 반비례함을 알 수 있다.
https://iskim3068.tistory.com/76
램버시안 코사인 법칙(Lambert's cosine law)이란
램버시안 코사인 법칙에 대해 알아보자 램버시안 코사인 법칙이란 램버시안 반사율을 가지는 램버시안 표면에서 어떠한 빛이 들어오는 각도에 따라 반사되는 빛의 intensity가 다르다라는 것이
iskim3068.tistory.com
// HLSL
// view 좌표 기준으로 하면 Camera의 좌표가 원점이 되어 계산하기 편하므로
// 정점, 픽셀, 위치, 방향 등 모두 View 좌표 기준으로 했다.
// 통일만 해준다면 world좌표를 써도 무관하다.
float3 viewLightDir = 0.0f; // 빛의 방향
// 픽셀 당 빛 세기
float diffuseRatio = 0.0f;
float specularRatio = 0.0f;
float distanceRatio = 1.0f;
// Point Light
float3 viewLightPos = normalize(mul(_light.direction.xyz, 1.0f), g_matView).xyz); // 광원 위치(view 좌표)
viewLightDir = normalize(viewPos - viewLightPos); // 빛 벡터 = 픽셀위치 - 광원위치
diffuseRatio = saturate(dot(-viewLightDir, viewNormal)); // 픽셀이 받는 diffuse 세기 = cosθ(==L, N 내적)
// 빛의 범위(거리)에 따른 감쇠량
float dist = distance(viewPos, viewLightPos);
distanceRatio = saturate(1.0f - pow(dist / _light.range, 2));
Specular
정반사광(반영광; specular light)은 specular highlight라고도 한다. 정반사광은 거울이나 광택이 잘되고, 반사되는 금속 표면 등에서 빛을 반사시키는데 일반적으로 사용된다.
여기서 R벡터와 V벡터 사이의 각 θ가 커질수록 반사광의 양이 줄어든다(fresnel effect). 반사된 빛의 양이
이면 굴절된 빛의 양은
이다. (RF는 3차원 벡터)
완전한 프레넬 방식은 상당히 복잡하기 때문에 다음과 같은 슐릭 근사(Shlick approximation)가 흔히 쓰인다.
// diffuse 코드 아래 추가. 위의 공식 그대로. 매질(RF)는 imgui로 임의의 값 주었음
float3 reflectionDir = normalize(viewLightDir + 2 * (saturate(dot(-viewLightDir, viewNormal)) * viewNormal));
float3 eyeDir = normalize(viewPos);
specularRatio = pow(saturate(dot(-eyeDir, reflectionDir)), 5);
color.diffuse = _light.color.diffuse * diffuseRatio * distanceRatio;
color.ambient = _light.color.ambient * distanceRatio;
color.specular = _light.color.specular * specularRatio * distanceRatio;
return color;