그래픽스(DirectX)

[DirectX11] Lighting - Directional Light/ Pointlight / Spotlight

gamzachips 2023. 5. 22. 10:13

Directional lights(Parallel lights)

Directional lights는 태양과 같이 매우 멀리 있는 광원과 비슷하다고 볼 수 있다. 

즉, 모든 서로 다른 광선이 평행하게 들어온다고 볼 수 있다. 

 

 

광원은 광선이 향하는 방향을 나타내는 하나의 vector로 정의될 수 있다.

 

light vector는 광원이 향하는 방향의 반대 방향을 향한다. 

 

Directional lights는 pointlights, spotlights보다 비용이 적게 든다

 

 

구현

//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;

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

	// 빛이 표면 위로 비치는지 체크 후
	// diffuse항과 specular 항 계산
	float diffuseFactor = dot(lightVec, normal);
    
    // 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);
					
		diffuse = diffuseFactor * mat.Diffuse * L.Diffuse;
		spec    = specFactor * mat.Specular * L.Specular;
	}
}

 

 

 

Point lights

Point lights의 좋은 예는 전구이다. 전구는 모든 방향으로 구형으로 빛을 방출한다. 

 

지점 P로부터 point light의 위치로서 광선의 기원이 되는 Q를 향하는 벡터를 light vector로 정의한다. 

Parallel lights의 light vector는 수평하지만, point light의 light vector는 지점에 따라서 달라진다. 

 

빛의 세기는 물리적으로 거리 빛의 세기에 비례하고 거리의 제곱에 반비례한다. 

그러나 이러한 공식을 적용하면 미적으로 좋아보이진 않다. 따라서 아래와 같은 공식을 이용한다.

a0,a1,a2는 attenuation parameters(감쇠 계수)로서, 아티스트나 프로그래머에 의해서 정해져야 할 값이다. 

예를 들어 실제 거리에 따른 빛의 세기 공식을 적용하고 싶으면 a0=0, a1=0, a2=1로 설정하면 된다. 

 

이러한 감쇄 효과를 우리의 빛 공식에 적용하면 아래와 같다. 

이때 ambient는 간접적인 빛이기 때문에 감쇠가 적용되지 않는다.

point light에서는 range(범위) 계수를 추가적으로 사용한다. 

광원으로부터 특정 범위보다 더 먼 거리의 지점에는 빛이 도달하지 않도록 하는 것이다. 

특정 영역만 비추고자 할 때 정확하게 빛의 최대 범위를 정의할 수 있고, 셰이더 최적화에도 유용하게 사용된다.

 

 

Pointlight는 거리를 계산해야 하므로 directional light보다 비용이 많이 든다.

 

 

구현

struct PointLight
{ 
	float4 Ambient;
	float4 Diffuse;
	float4 Specular;

	float3 Position;
	float Range;

	float3 Att;
	float pad;
};


void ComputePointLight(Material mat, PointLight L, float3 pos, 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.Position - pos;
		
	// 표면으로부터 광원까지의 거리
	float d = length(lightVec);
	
    //거리가 Range 안에 있어야 빛을 받음
	if( d > L.Range )
		return;
		
	// Normalize the light vector
	lightVec /= d; 
	
	// Ambient항 계산
	ambient = mat.Ambient * L.Ambient;	

	// 빛이 표면 위로 비치는지 체크 후
	// diffuse항과 specular 항 계산
	float diffuseFactor = dot(lightVec, normal);

	// 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);
					
		diffuse = diffuseFactor * mat.Diffuse * L.Diffuse;
		spec    = specFactor * mat.Specular * L.Specular;
	}

	// 감쇠 효과
	float att = 1.0f / dot(L.Att, float3(1.0f, d, d*d));

	diffuse *= att;
	spec    *= att;
}

 

 

Spotlights

Spotlights의 좋은 예는 손전등이다. 

spotlight는 위치 Q, 향하는 방향 d, 원뿔을 통해 방출되는 을 가진다. 

spotlight의 light vector는 다음과 같다. P는 비춰지는 점의 위치이고 Q는 spotlight의 위치이다.

 

 

 

지점 P가 spotlight의 원뿔 안에 있으면 d와 -L 사이의 각 Φ가 Φmax보다 작아야 한다

또한 원뿔 안에서 빛의 세기가 동일하지는 않다. 중앙부(Φ=0)에서 가장 밝고 Φ가 Φmax에 가까워질수록 감소해 0이 된다.

 

 

 

원뿔에서 Φ에 따른 빛의 세기 변화와 원뿔의 크기 조절을 위해서, specular cone에서와 같은 식을 사용할 수 있다. 

지수 s를 늘림으로써 Φmax를 간접적으로 감소시킬 수 있다. 즉, s를 조절함으로써 spotlight를 줄이거나 늘릴 수 있다. 

 

 

spotlight의 공식은 아래와 같다. point light와 비슷하지만, cone내부에서의 위치에 따른 빛의 세기 조절 요소가 곱해진다.

즉, spotlight에서는 k_spot을 추가적으로 계산하고 곱해야하므로 point light보다 비용이 많이 든다. 가장 비용이 많이 드는 광원이다. 

구현

struct SpotLight
{
	float4 Ambient;
	float4 Diffuse;
	float4 Specular;

	float3 Position;
	float Range;

	float3 Direction;
	float Spot;

	float3 Att;
	float pad;
};


void ComputeSpotLight(Material mat, SpotLight L, float3 pos, 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.Position - pos;
		
	// 표면으로부터 광원까지의 거리
	float d = length(lightVec);
	
    //거리가 Range 안에 있어야 빛을 받음
	if( d > L.Range )
		return;
        
	// Normalize the light vector
	lightVec /= d; 
    
	// Ambient항 계산
	ambient = mat.Ambient * L.Ambient;	

	// 빛이 표면 위로 비치는지 체크 후
	// diffuse항과 specular 항 계산
	float diffuseFactor = dot(lightVec, normal);

	[flatten]
	if( diffuseFactor > 0.0f )
	{
		float3 v         = reflect(-lightVec, normal);
		float specFactor = pow(max(dot(v, toEye), 0.0f), mat.Specular.w);
					
		diffuse = diffuseFactor * mat.Diffuse * L.Diffuse;
		spec    = specFactor * mat.Specular * L.Specular;
	}
	
	// spotlight factor
	float spot = pow(max(dot(-lightVec, L.Direction), 0.0f), L.Spot);

	// 감쇠 효과
	float att = spot / dot(L.Att, float3(1.0f, d, d*d));

	ambient *= spot;
	diffuse *= att;
	spec    *= att;
}

 

 

Pixel Shader

float4 PS(VertexOut pin) : SV_Target
{
	// Interpolating normal can unnormalize it, so normalize it.
	pin.NormalW = normalize(pin.NormalW);

	float3 toEyeW = normalize(gEyePosW - pin.PosW);

	// Start with a sum of zero. 
	float4 ambient = float4(0.0f, 0.0f, 0.0f, 0.0f);
	float4 diffuse = float4(0.0f, 0.0f, 0.0f, 0.0f);
	float4 spec = float4(0.0f, 0.0f, 0.0f, 0.0f);

	// Sum the light contribution from each light source.
	float4 A, D, S;

	ComputeDirectionalLight(gMaterial, gDirLight, pin.NormalW, toEyeW, A, D, S);
	ambient += A;
	diffuse += D;
	spec += S;

	ComputePointLight(gMaterial, gPointLight, pin.PosW, pin.NormalW, toEyeW, A, D, S);
	ambient += A;
	diffuse += D;
	spec += S;

	ComputeSpotLight(gMaterial, gSpotLight, pin.PosW, pin.NormalW, toEyeW, A, D, S);
	ambient += A;
	diffuse += D;
	spec += S;

	float4 litColor = ambient + diffuse + spec;

	// Common to take alpha from diffuse material.
	litColor.a = gMaterial.Diffuse.a;

	return litColor;
}