그래픽스(DirectX)

[DirectX11] Lighting - diffuse/ambient/specular

gamzachips 2023. 5. 21.

Diffuse Lighting

빛이 거친 표면의 한 점을 비추면, 광선이 여러 방향으로 랜덤하게 산란되는데, 이를 diffuse reflaction이라 한다. 

이는 눈의 위치와 상관없이 반사된 빛이 보이게 되며, 색상도 눈의 위치와 상관없이 동일하게 보인다. 

 

diffuse lighting을 계산하기 위해서 먼저 diffuse light color, diffuse material color를 구체화한다 

diffuse material은 표면이 diffuse light를 반사하고 흡수하는 양을 나타내고, 

예를 들어 diffuse material가 m_d = (0.5, 1.0, 0.75)이고 diffuse light가 l_d=(0.8, 0.8, 0.8)이면, 

점에서 반사되는 빛의 양은 둘을 outer product한 (0.4, 0.8, 0.6)이 된다. 

 

마지막으로, 표면의 normal과 벡터 사이의 각도에 따라서 표면이 빛을 얼마나 받는지를 구하기 위해서  Lambert의 코사인 공식을 inner product 하면 된다. 

 

 

Ambient Lighting

물체에서, 빛이 직접 비추는 면이 아니더라도 아예 검은 색이 아니라 보이긴 한다. 

간접적인 빛은 매우 많이 산란되고 반사되어서 모든 방향에서 동일하게 물체를 비추는데,

이러한 간접적인 빛을 표현하기 위한 것이 ambient lighting이다. 

 

마찬가지로 들어오는 ambient light를 표면이 얼마나 반사하고 흡수하는지를 나타내는 ambient material m_a과, 

광원으로부터 표면이 받는 간접적인 빛의 총량을 나타내는 ambient lightn l_a를 이용한다. 

 

ambient light는 물체를 균일하게 밝히므로 normal 벡터와의 각도에 따라 빛의 세기가 달라지지 않으므로 Lambert 공식을 적용하지 않으며, m_a와 l_a의 outer product로 구해진다. 

 

 

 

Specular Lighting

매끄러운 표면에서, 빛은 특정 방향을 통해 반사되어 나오는데, 이를 specular reflection이라 한다. 

specular lighting의 계산은 시점(눈의 위치)에 따라 달라진다. 

 

반사된 벡터 r과 눈의 위치를 향하는 view 벡터 v사이의 각도에 따라서 specular light의 밝기가 달라진다. 

Φ가 작을수록 빛의 세기가 커지고 Φmax로 가면서 빛 세기가 서서히 작아져 0이 된다. 

 

즉, Lambert의 코사인 법칙 max(cosΦ, 0)에서 Φmax에서 빛의 세기가 0이 되도록 한다. 

이때 벡터 r과 v가 단위벡터이므로, cosΦ = v·r로 구할 수 있다. 

 

 

여기서 P가 커질수록 더 급격한 지수함수가 되면서 빛의 세기가 0에 가깝게 떨어지는 Φ가 작아지게 되므로, P를 달리함으로써 간접적으로 Φmax를 조절할 수 있다. 

 

 

그러나 경우에 따라 cone의 범위가 표면을 넘어가는 경우, diffuse light는 받지 않지만 specular light는 받게 되어버린다. 이는 말이 안 되므로 diffuse light를 받지 않으면, 즉 L·n <= 0이면 k_s를 0이 되게 해야한다. 

 

slecular light의 양은 specular light인 l_s와 specular material인 m_s을 outer product하고, Lambert공식을 곱하여 얻을 수 있다. 

 

 

최종적으로, 우리의 lighting model은 아래와 같이 얻어진다. 

 

 

 

 

구현

평행하게 비추는 빛인 Directional Light를 가지고 구현해보자. 

https://gamzachips.tistory.com/74

 

[DirectX11] Lighting - Directional Light/ Pointlight / Spotlight

Directional lights(Parallel lights) Directional lights는 태양과 같이 매우 멀리 있는 광원과 비슷하다고 볼 수 있다. 즉, 모든 서로 다른 광선이 평행하게 들어온다고 볼 수 있다. 광원은 광선이 향하는 방향

gamzachips.tistory.com

 

 

//LightHelper.h
struct DirectionalLight
{
	DirectionalLight() { ZeroMemory(this, sizeof(this)); }

	XMFLOAT4 Ambient;
	XMFLOAT4 Diffuse;
	XMFLOAT4 Specular;
	XMFLOAT3 Direction;
	float Pad; // Pad the last float so we can set an array of lights if we wanted.
};

ambient, diffuse, specular, direction(빛의 방향)을 정의하는 struct를 만들고, 셰이더 파일에서도 대응되도록 만들어준다. 

 

//LightHelper.fx
struct DirectionalLight
{
	float4 Ambient;
	float4 Diffuse;
	float4 Specular;
	float3 Direction;
	float pad;
};

 

void ComputeDirectionalLight(Material mat, DirectionalLight L, float3 normal, float3 toEye,
				out float4 ambient, out float4 diffuse, out float4 spec)
{
	// Initialize outputs.
	ambient = float4(0.0f, 0.0f, 0.0f, 0.0f);
	diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f);
	spec    = float4(0.0f, 0.0f, 0.0f, 0.0f);

 

빛이 향하는 반대 방향을 가리키는 light vector를 선언한다. 

	float3 lightVec = -L.Direction;

 

여기서 A를 먼저 계산한다. 

ambient material과 ambient light를 곱한다.

	// ambient항 계산
	ambient = mat.Ambient * L.Ambient;

 

diffuse factor k_d를 구한다. 

Lambert의 코사인 법칙에 따라서, light vector와 normal을 내적한다. 

	float diffuseFactor = dot(lightVec, normal);

 

diffuse factor를 0과 비교하여 빛이 표면 위로 비치는지 체크한다. 

0보다 크면 cos값이 -90~90도 사이에 있는 것이므로 표면 위에 있는 것이다.

위에서 언급된 것처럼 diffuse를 받을 때만 specular를 받도록 해야 하므로,

비교에서 통과되면 diffuse와 specular 항을 계산한다.

	// Flatten to avoid dynamic branching.
	[flatten]
	if( diffuseFactor > 0.0f )
	{
		float3 v         = reflect(-lightVec, normal);
		float specFactor = pow(max(dot(v, toEye), 0.0f), mat.Specular.w);

먼저 반사광 v 벡터를 구하고, Lambert 코사인 공식을 적용해 specular factor를 구한다. 

이때 Specular.w가 제곱됨으로써, w가 클수록 cone의 크기가 작아져 더 좁은 영역에서 밝기 변화가 나타난다. 

diffuse와 specular 각각에 대해 material, light 값과 factor값을 곱한다. 

		diffuse = diffuseFactor * mat.Diffuse * L.Diffuse;
		spec    = specFactor * mat.Specular * L.Specular;
	}
}

 

댓글