민돌이2 2022. 3. 5. 02:59

LightHelper.fx
0.01MB
LightHelper.h
0.00MB

1.조명을 위한 구조체들

c++에서 셰이더로 넘겨주기 위해서 버퍼로 보내야한다. c에서 버퍼에 넣을 구조체는 아래와 같다.

//지향광
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.
};

//점광
struct PointLight
{
	PointLight() { ZeroMemory(this, sizeof(this)); }

	XMFLOAT4 Ambient;
	XMFLOAT4 Diffuse;
	XMFLOAT4 Specular;

	// Packed into 4D vector: (Position, Range)
	XMFLOAT3 Position;
	float Range;

	// Packed into 4D vector: (A0, A1, A2, Pad)
	XMFLOAT3 Att;
	float Pad; // Pad the last float so we can set an array of lights if we wanted.
};

//점적광
struct SpotLight
{
	SpotLight() { ZeroMemory(this, sizeof(this)); }

	XMFLOAT4 Ambient;
	XMFLOAT4 Diffuse;
	XMFLOAT4 Specular;

	// Packed into 4D vector: (Position, Range)
	XMFLOAT3 Position;
	float Range;

	// Packed into 4D vector: (Direction, Spot)
	XMFLOAT3 Direction;
	float Spot;

	// Packed into 4D vector: (Att, Pad)
	XMFLOAT3 Att;
	float Pad; // Pad the last float so we can set an array of lights if we wanted.
};
  1. Ambient : 광원이 방출하는 주변광의 양
  2. Diffuse : 광원이 방출하는 분산광의 양
  3. Specular : 광원이 방출하는 반영광의 양
  4. Direction : 빛의 방향
  5. Position : 광원의 위치
  6. Range : 빛의 범위, 광원과의 거리가 범위보다 큰 점은 빛을 받지 않는다.
  7. Attenuation : 빛의 세기가 거리에 따라 감소하는 방식을 제어하는 세 개의 감쇠 상수
  8. Spot : 점적광 계산에서 점적광 원뿔을 제어하는 지수의 변수

c++로부터 받은 구조체를 담는 셰이더의 구조체는 다음과 같다.

struct DirectionalLight
{
	float4 Ambient;
	float4 Diffuse;
	float4 Specular;
	float3 Direction;
	float pad;
};

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

	float3 Position;
	float Range;

	float3 Att;
	float pad;
};

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

	float3 Position;
	float Range;

	float3 Direction;
	float Spot;

	float3 Att;
	float pad;
};

구조체의 크기는 4byte로 맞춰줘야 함을 기억해야 한다. 각 구조체를 보면 pad로 4byte 규격을 맞췄다.

 

 

2.구조체 채우기

셰이더에서 정의한 구조체들을 상수 버퍼 안에 아래와 같이 인스턴스화한다.

cbuffer cbPerFrame
{
	DirectionalLight gDirLight;
	PointLight gPointLight;
	SpotLight gSpotLight;
	float3 gEyePosW;
};

응용 프로글매에서도 대응되는 c++ 구조체 인스턴스들을 생성해야 한다. 셰이더의 변수를 설정할 때 구조체 멤버들을 일일이 따로 설정하는 것보다 구조체 인스턴슬르 통째로 셰이더 변수 인스턴스에 설정하는 것이 생각만 해도 편하다. 쉽게 말해서 사과를 하나씩 건내줄빠에 상자에 담아서 주는것이 편하다는 것이다. 구조체 인스턴스를 하나의 셰이더 변수로 설정할 때에는 아래와 같은 함수를 사용한다.

ID3DX11EffectVariable::SetRawValue(void* pData, UINT Offset, UINT Count);

DirectionalLight mDirLight;
mfxDirLight->SetRawValue(&mDirLight, 0, sizeof(mDirLight));

이 함수는 가공되지 않은 byte들을 그대로 복사할 뿐이다. 그대로 복사하기 때문에 c++쪽의 채우기(packing) 규칙이 HLSL과 일치하지 않는 오류의 근원이 될 수 있다.

HLSL에서 자료 멤버들은 4차원 벡터 단위로 채워 넣되 하나의 멤버가 두 개의 4차원 벡터에 나뉘어지면 안 된다는 제약이 있다. 이 제약을 지키기 위해서 패킹을 사용해야 한다.

응용프로그램에서 위치와 방향 벡터를 갖는 구조체와 이에 대응되는 셰이더에서의 구조체를 선언했다고 가정해보자.

//c++
struct S
{
	XMFLOAT3 Pos;
	XMFLOAT3 Dir;
};

//HLSL
struct S
{
	float3 Pos;
	float3 Dir;
};

위 구조체를 4차원 벡터들에 다음과 같이 채워진다

vector 1 : (Pos.x, Pos.y, Pos.z, Dir.x)
vector 2 : (Dir.y, Dir.z, NULL, NULL)

Dir의 성분이 나뉘어 졌다는 점을 알아야한다. 이는 하나이 멤버가 4차원 벡터들의 경계에 걸치면 안된다는 HLSL의 규칙을 위반한 것이다. 규칙에 맞는 채우기 방식은 다음과 같다.

vector 1 : (Pos.x, Pos.y, Pos.z, NULL)
vector 2 : (Dir.x, Dir.y, Dir.z, NULL)

위의 방식처럼 채우기 위해서는 응용프로그램쪽에서 NULL부분에 쓸모없는 값을 넣어준다. 이를 패딩(Padding)이라고 한다.

struct S
{
	XMFLOAT3 Pos;
	float padding0;
	XMFLOAT3 Dir;
	float padding1;
};

 

 

3.지향광의 구현

아래의 코드는 재질과 지향광원, 표면 법선, 시점 벡터에 기초해서 표면 점의 조명 값을 계산하는 HLSL 함수이다.

void ComputeDirectionalLight(Material mat, DirectionalLight L, float3 normal, float3 toEye, out float4 ambient, out float4 diffuse, out float4 spec)
{
	//출력 성분들 초기화
	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);

	//빛 벡터는 광선이 나아가는 방향의 반대 방향이다.
	float3 lightVec = -L.Direction;

	//주변광 항을 더한다.
	ambient = mat.Ambient * L.Ambient;	

	//빛이 막히지 않고 표면에 도달한다는 가정 하에서
	//분산광 항과 반영광 항을 더한다.
	
	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;
	}
}

 

 

 

4.점광의 구현

아래의 코드는 재질과 점광원, 표면 점 위치, 표면 법선, 시점 벡터에 기초해서 표면 점의 조명 값을 계산한 HLSL 함수이다.

void ComputePointLight(Material mat, PointLight L, float3 pos, float3 normal, float3 toEye, out float4 ambient, out float4 diffuse, out float4 spec)
{
	//출력 성분들 초기화
	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);

	//빛 벡터 (표면 점에서 광원으로의 벡터)
	float3 lightVec = L.Position - pos;
		
	//표면 점과 광원 사이의 거리
	float d = length(lightVec);
	
	//범위 판정
	if( d > L.Range )
		return;
		
	//빛 벡터 정규화
	lightVec /= d; 
	
	//주변광 항
	ambient = mat.Ambient * L.Ambient;	

	//빛이 막히지 않고 표면에 도달한다는 가정 하에서
	//분산광 항과 반영광 항을 더한다

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

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

	diffuse *= att;
	spec    *= att;
}

 

 

5.점적광의 구현

아래의 코드는 재질과 점적광원, 표면 점 위치, 표면 법선, 시점 벡터에 기초해서 표면 점의 조명 값을 계산하는 HLSL 함수이다.

void ComputeSpotLight(Material mat, SpotLight L, float3 pos, float3 normal, float3 toEye, out float4 ambient, out float4 diffuse, out float4 spec)
{
	//출력 성분들 초기화
	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);

	//빛 벡터 (표면 점에서 광원으로의 벡터)
	float3 lightVec = L.Position - pos;
		
	//표면 점과 광원 사이의 거리
	float d = length(lightVec);
	
	//범위 판정
	if( d > L.Range )
		return;
		
	//빛 벡터 정규화
	lightVec /= d; 
	
	//주변광 항
	ambient = mat.Ambient * L.Ambient;	

	//빛이 막히지 않고 표면에 도달한다는 가정 하에서
	//분산광 항과 반영광 항을 더한다.

	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;
	}
	
	//점적광 계수를 더한다.
	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;
}
728x90