본문 바로가기
게임 공부/게임 개발 일지

세 종류의 라이팅 구현 (3) - Point Light 편

by woohyeon 2021. 8. 13.
반응형

개념이나 기술적으로 틀린 부분이 있을 수 있습니다. 있다면 알려주세요 :)

- 세 종류의 라이팅 구현(1) - 소개 편

https://woo-dev.tistory.com/269

 

세 종류의 라이팅 구현 (1) - 소개 | Directional Light, Point Light, Spot Light

게임 엔진에 구현된 대표적인 조명 3가지인 Directional Light, Point Light, Spot Light를 구현해볼 예정이다. 조명의 특징과 기능? 들은 언리얼 엔진을 참고할 예정. 세세한 기능보단 핵심적인 특징 구현에

woo-dev.tistory.com


두 번째로 구현해 볼 라이팅은 Point Light이다. 한글로는 점광원이라고도 한다. Point Light는 어떤 Point로부터 모든 방향으로 동일한 세기의 빛을 뿜는 광원을 말한다. 주로 전구와 같은 것을 표현하기에 적합하다.

Point Light


Point Light는 Directional Light처럼 빛을 한 방향으로만 비추는 것이 아니기 때문에 방향 벡터는 필요하지 않다. 또한 Directional Light와 달리 광원의 위치가 조명 연산에 영향을 미친다. 따라서 방향 벡터 대신 광원의 위치를 셰이더로 가져와 광원과 현재 프래그먼트의 관계를 통해 빛 벡터를 얻는다.

Point Light는 광원에서 멀리 떨어질 수록 받게 되는 빛의 세기가 줄어든다는 특징이 있다. 이를 감쇠 효과(attenuation)라고 하는데, 이러한 연산이 추가로 필요하다.

처음엔 단순히 거리에 따라 빛의 강도를 조절하면 될 줄 알았는데, 찾아보니 생각처럼 그리 간단하지 않았다. 그렇다고 엄청 복잡한 것은 아니다.

attenuation의 정도를 계산하기 위해선 다음과 같은 데이터가 필요하다.

  • 빛과 정점(혹은 프래그먼트)와의 거리 d -> 거리에 따라 달라지므로 당연히 필요한 데이터.
  • 상수항 k0 -> 말그대로 상수값 k0이다. 상수항은 영어로 constant term이라고도 한다. 
  • 일차항 k1 -> 일차항에 붙는 값이다. 영어로 linear term이라고도 한다.
  • 이차항 k2 -> 이차항에 붙는 값이다. 영어로 quadratic term이라고도 한다.

위 4개의 데이터를 사용한 attenuation의 공식은 다음과 같다. attenuation equation(감쇠 방정식)이라고도 한다.


k0는 항상 1.0으로 고정인데, 이는 거리가 0일 경우 1로 맞춰 주기 위한 용도라 생각된다. k1과 k2는 모두 양수이다. d 또한 거리이므로 항상 양수이다. 따라서 위 결과는 0~1 사이의 값이 나올 것이고, 이를 Intensity처럼 조명의 결과에 곱해주면 된다. 

현실 세계에서 조명은 선형적으로 어두워지는 것이 아니라 곡선 형태로 감소한다고 한다. 이러한 자연스러운 감소를 위해 위의 방정식의 k1, k2 값들에 대해 테스트 된 데이터가 존재한다.

물론 다른 값들이 존재할 수 있지만, 나는 위 데이터를 참고하였다. 위 데이터의 출처는 오거 엔진 사이트다. 

Range는 위 방정식을 적용할 최대 거리이다. 즉 Range가 100이라면 100을 초과하는 거리에선 att가 0이 되어 조명이 영향을 끼치지 않는다.

나는 200, 1.0, 0.022, 0.0019의 값들을 사용했다. 이 경우 다음과 같은 att가 나온다.

distance: att 형태의 출력이다. 거리 1에선 빛의 대부분 98% 정도를 받고, 20에선 반도 안되는 45%, 70부턴 1% 대이다. 용도에 따라 원하는대로 계수들을 수정하면서 확인해보는 것을 추천한다.

셰이더에서 Directional Light와 다른 부분은 빛 벡터를 구하는 부분과 감쇠 효과를 적용하는 부분이다. 이를 제외하면 거의 동일하므로 이 부분만 작성한다.

정점 셰이더가 하는 동작은 동일하다. 정점, 노멀 등을 적절한 공간으로 이동시킨다.

픽셀 셰이더에선 기존 동작에 다음과 같은 추가 동작을 한다.

  • light의 위치를 가져와 현재 프래그먼트 위치와 함께 빛 벡터 l을 구한다.
  • light의 위치와 현재 프래그먼트의 거리(d)를 구한다.
  • attenuation terms 4가지를 가져와 d와 함께 att를 계산한다.
  • 최종 컬러에 att를 추가로 곱해준다.
float4 main(PS_INPUT input) : SV_TARGET
{
	// Get Diffuse
	float3 WorldLightDir = normalize(input.WPos - pLightPosition).xyz;
	float3 WorldNormal = normalize(input.Normal);
	float  Diffuse = max(dot(-WorldLightDir, WorldNormal), 0);
	float3 PhongD = Diffuse * (pLightColor.rgb * input.Color);
	
	float3 PhongS = float3(0.f, 0.f, 0.f);
    
	// Get distance from the light to current fragment.
	float Distance = distance(pLightPosition, input.WPos);
	float att = 0.f;
	if (Diffuse > 0.f)
	{
		float3 ViewDir = normalize(input.WPos.xyz - CameraPosition);
		float3 ReflectionVec = normalize(reflect(WorldLightDir.xyz, WorldNormal));
		float Specular = max(dot(ReflectionVec, -ViewDir), 0);
		Specular = pow(Specular, 30.f);
		PhongS = Specular * (pLightColor.rgb * float3(0.7f, 0.7f, 0.7f));
		
		// Calculate attenuation
		// pAttenuationForms.x: range
		// pAttenuationForms.y: constant term
		// pAttenuationForms.z: linear term
		// pAttenuationForms.w: quadratic term
		if (Distance <= pAttenuationForms.x)
		{
			att = 1.f / (pAttenuationForms.y + (pAttenuationForms.z * Distance) +
				(pAttenuationForms.w * Distance * Distance));
		}
	}
	
	// insert att in based.
	float3 FinalColor = att * pLightIntensity * (PhongD + PhongS);
	
	return float4(FinalColor, 1.f);
}

 


그런데 찾아보니 이처럼 단순하지 않고 더 복잡한 point light도 있는 것 같다. 감쇠와 관련된 더욱 복잡한 것들이 있다. 일단은 간단하게만 적용하였는데, 우선 기본적인 라이팅들을 먼저 구현하고 더 알아볼 생각이다.

위와 같이 구현된 point light는 다음과 같다. 자연스러운 light인지 잘 모르겠다..

 

Reference

 

 

 

 

 

 

 

 

 

 

 




댓글