프로그래밍 공부
작성일
2023. 10. 31. 17:32
작성자
WDmil
728x90

PointLight, DIrectionLight, SpotLight

 

각 라이트에 대한 Hlsl과 Buffer연산을 코드에 추가해야한다.

 

우선 각 라이트를 구현하기 위해 필요한 값을 하나하나 짚어보자.

 

PointLight

  • 빛의 좌표(Vector3 형태의 좌표)
  • 객체(Vertice)의 위치(Vector3형태의 좌표)
  • 감쇠계수(범위에 따른 감쇠값)

Directional Light

  • 빛의 방향벡터 ( 빛이 향하는 방향을 나타내는 Vector 보통 카메라에서 직교함)
  • 표면의 법선 ( 좌표평면상의 수직된 법선데이터)

SpotLight

  • 빛의 위치(Vector3형태의 좌표)
  • 객체(Vectice)의 위치(Vector3형태의 좌표)
  • 빛의 방향벡터( 빛이 향하는 방향을 나타내는 Vector 빛의 위치에서 정면으로 직교함)
  • 원뿔의 외부반경 ( 빛의 방향벡터에 외곽으로 분산되는 원뿔각도)
  • 원뿔의 내부반경 ( 빛의 방향백터에 외부로 분산되는 외부반경 안의 내부반경각도)
  • 감쇠계수  ( 범위에 따른 감쇠값)

 

위 데이터를 전부 정리해서 Buffer로 hlsl에 받아온 뒤 연산해야한다.

struct Light
{
	// 빛의 색상: RGB 색상 공간에서 빛의 색상을 나타내는 4차원 벡터입니다.
	float4 color;
	
	// 빛의 방향: 빛이 향하는 방향을 나타내는 3차원 벡터입니다. 주로 Directional Light나 Spot Light에서 사용됩니다.
	float3 direction;
	
	// 쓰레기값
	float padding;
	
	// 빛의 위치값: 3D 공간에서 빛의 위치를 나타내는 3차원 벡터입니다. 주로 Point Light나 Spot Light에서 사용됩니다.
	float3 position;
	
	// 빛의 거리: 빛이 도달할 수 있는 최대 거리를 나타냅니다. 이 값은 주로 Spot Light나 Point Light에서 사용됩니다.
	float range;
	
	// 원뿔 내부각도: Spot Light에서 빛이 원뿔 내부로 들어가는 각도를 나타냅니다. 이 값은 Spot Light에서만 사용됩니다.
	float inner;
	
	// 원뿔 외부각도: Spot Light에서 빛이 원뿔 외부로 나가는 각도를 나타냅니다. 이 값은 Spot Light에서만 사용됩니다.
	float outer;
};

cbuffer LightBuffer : register(b0)
{
	Light light;
    
	// 간접광
	float4 ambientLight;
    // 천장간접광. 천장부분의 반사정도를 따로준다.
	float4 ambientCeil; 
}

위 객체는 hlsl에 전달하는 Buffer값이다.


Spot

우선 가장 어려운 Spot연산에 대해 살펴보자.

// 스포트라이트를 계산하는 함수입니다.
float4 CalcSpot(LightData lightData, Light light)
{
	// 빛으로의 방향을 계산합니다.
	float3 toLight = lightData.worldPos - light.position;
	// 빛까지의 거리를 계산합니다.
	float distanceToLight = length(toLight);
	// 빛으로의 방향을 정규화합니다. normalize하지 않고도 어차피 거리값을 나누어버리면 됨.
	toLight /= distanceToLight;
	
	// 확산 강도를 계산합니다. Dot은 노멀벡터 빛의 방향사이의 점곱을 계산. 둘다 normalize되었음으로 cos만 남는다.
	// saturate함수는 결과데이터를 0 보다 작으면 0 1보다 크면 1로 고정한다.
	float diffuseInstensity = saturate(dot(lightData.normal, -toLight));
	
	// 최종 색상은 빛의 색상과 확산 강도의 곱입니다.
	float4 finalColor = light.color * diffuseInstensity * mDiffuse;
	
	// 확산 강도가 0보다 큰 경우에만 반사를 계산합니다. 빛이 표면에 도달하는지 정도 확인
	if (diffuseInstensity > 0)
	{
		// 반사를 계산하기 위해 중간 방향을 계산합니다.
		float3 halfWay = normalize(lightData.viewDir + toLight);
		float specular = saturate(dot(lightData.normal, -halfWay));

		finalColor += light.color * pow(specular, shininess) *
		lightData.specularIntensity * mSpecular;
	}
	
	// 빛의 방향을 정규화합니다.
	float3 dir = normalize(light.direction);
	// 빛의 방향과 표면으로의 빛방향에 대한 cos값을 저장한다.
	float cosAngle = dot(dir, toLight);
	
	// 외부 및 내부 각도를 계산합니다. 내부원뿔과 외부원뿔을 의미한다.
	float outer = cos(radians(light.outer));
	float inner = 1.0f / cos(radians(light.inner));
	
	// 스포트라이트의 원뿔형 감쇠를 계산합니다. 
	// 물체표면과 표면에 대한 빛의 Cos값에 외곽원뿔 각도를 빼주는 이유는, 각도 외부의 vertice에 대해서는 빛연산을 하지 않기 위해서 이다.
	// inner를 곱하는 이유는. 해당값에 내부원뿔의 각도를 보정해주어 내부원뿔 일 경우, 1을 출력하게 만들기 위해서 이다.
	float conAttention = saturate((cosAngle - outer) * inner);
	// 빛의 감쇠작용시 데이터의 곡선파형을 위해 제곱승을 사용한다.
	conAttention = pow(conAttention, 2);
	
	// 빛까지의 거리를 정규화합니다.
	float distanceToLightNormal = 1.0f - saturate(distanceToLight / light.range);
	// 감쇠를 계산합니다.
	float attention = pow(distanceToLightNormal, 2);

	// 최종 색상은 기본 색상과 감쇠 및 원뿔형 감쇠의 곱입니다.
	// 최종색상 * 빛의 색상 * 빛의 감쇠정도(0 ~ 1 사이의 flaot값)(빛과 물체의 거리에 해당) * 원의 외곽선에 대한 빛의 감쇠정도
	return finalColor * lightData.baseColor * attention * conAttention;
}

설명은 주석에 달려있으니 간략하게 설명한다.

 

객체의 WorldPosition과 빛의 psoition을 사용해 빛이 향하는 방향을 계산한다.

해당 값을 제곱하여 현재 빛과 Vertice의 거리를 계산한다.

 

방향에 거리를 나누어서 Normalize해준다.

 

확산강도를 정한다.

확산강도는 현재 lightData의 normal값(법선)과 Light의 cos각도를 계산한다.

제곱하였는데, 둘다 Normalize되었음으로 cos만 남는다.(0~1사이로 제한)

해당 값을 사용해서 finalColor를 계산한다. 라이트의 빛과 확산강도 그리고 면의 color값을 참조한다.

 

만약, 확산강도가 0보다 클 경우.(Spot라이트의 범위 안에 있을경우) 빛에 중첩연산하여. 밝게 한다.

 

연산된 데이터에 빛의 방향과 외부원뿔 내부원뿔에 비례하여 빛의 밝기를 조절해준다.

 

최종결과에 빛의 베이스컬러 그리고 감쇠값을 곱하여 도출한다.


// 포인트 라이트를 계산하는 함수입니다.
float4 CalcPoint(LightData lightData, Light light)
{
	// 빛으로의 방향을 계산합니다.
	float3 toLight = lightData.worldPos - light.position;
	// 빛까지의 거리를 계산합니다.
	float distanceToLight = length(toLight);
	// 빛으로의 방향을 정규화합니다.
	toLight /= distanceToLight;
	
	// 확산 강도를 계산합니다.
	float diffuseInstensity = saturate(dot(lightData.normal, -toLight));
	// 최종 색상은 빛의 색상과 확산 강도의 곱입니다.
	float4 finalColor = light.color * diffuseInstensity * mDiffuse;
	
	// 확산 강도가 0보다 큰 경우에만 반사를 계산합니다.
	if (diffuseInstensity > 0)
	{
		// 반사를 계산하기 위해 중간 방향을 계산합니다.
		float3 halfWay = normalize(lightData.viewDir + toLight);
		float specular = saturate(dot(lightData.normal, -halfWay));

		finalColor += light.color * pow(specular, shininess) *
		lightData.specularIntensity * mSpecular;
	}
	
	// 빛까지의 거리를 정규화합니다.
	float distanceToLightNormal = 1.0f - saturate(distanceToLight / light.range);
	// 감쇠를 계산합니다.
	float attention = pow(distanceToLightNormal, 2);

	// 최종 색상은 기본 색상과 감쇠의 곱입니다.
	return finalColor * lightData.baseColor * attention;
}

포인트 라이트는 Spot에 원뿔이 없어지면 바로 포인트라이트가 된다!

위 수식을 살펴보면 더 쉽게 이해할 수 있다.


float4 CalcDirectional(LightData lightData, Light light)
{
	float3 toLight = normalize(light.direction);
	
	float diffuseInstensity = saturate(dot(lightData.normal, -toLight));
	float4 finalColor = light.color * diffuseInstensity * mDiffuse;
	
	if (diffuseInstensity > 0)
	{
		float3 halfWay = normalize(lightData.viewDir + toLight);
		float specular = saturate(dot(lightData.normal, -halfWay));

		finalColor += light.color * pow(specular, shininess) *
		lightData.specularIntensity * mSpecular;
	}

	return finalColor * lightData.baseColor;
}

이 코드는 태양이 빛을 비추는 정도로 생각하면된다.

 

vertice의 법선 데이터와 빛의 각도에 따른 cos보정값을 곱해주는것 밖에 없다.

 

가장 쉬운 빛 연산.

 


LightPixelInput VS(VertexUVNormalTangent input)
{
	LightPixelInput output;
	output.pos = mul(input.pos, world);
	output.worldPos = output.pos;
	output.viewPos = invView._41_42_43;
	
	output.pos = mul(output.pos, view);
	output.pos = mul(output.pos, projection);
	
	output.uv = input.uv;
	
	output.normal = mul(input.normal, (float3x3) world);
	output.tangent = mul(input.tangent, (float3x3) world);
	output.binormal = cross(output.normal, output.tangent);
	
	return output;
}

float4 PS(LightPixelInput input) : SV_TARGET
{
	//float4 baseColor = diffuseMap.Sample(samp, input.uv);	
	LightData lightData = GetLightData(input);
	
	float4 ambient = CalcAmbient(lightData);
	//float4 result = CalcDirectional(lightData, light);
	float4 result = CalcPoint(lightData, light);
	//float4 result = CalcSpot(lightData, light);
	
	return ambient + result;
}

위 함수값을 전부 계산하여 PS에 반환하고 결과를 반환하는 함수이다.

728x90