light 클래스
enum class LIGHT_TYPE : uint8
{
DIRECTIONAL_LIGHT,
POINT_LIGHT,
SPOT_LIGHT,
};
빛의 3가지 타입 정의
struct LightColor
{
Vec4 diffuse;
Vec4 ambient;
Vec4 specular;
};
빛의 3 속성을 담는 데이터 정의
struct LightInfo
{
LightColor color;
Vec4 position;
Vec4 direction;
int32 lightType; // for shader padding
float range;
float angle;
int32 padding;
};
쉐이더에서 계산시 필요한 정보들을 모두 담고있는 데이터
lightType이 uint8이 아니라 int32인 이유는 쉐이더 데이터 패딩 때문
padding 또한 데이터를 16바이트 배수로 만들기 위한 값
struct LightParams
{
uint32 lightCount;
Vec3 padding;
LightInfo lights[50];
};
쉐이더에는 한번에 최대 50개의 빛 정보를 넘겨줌
프레임마다 빛에 대한 정보를 한번 업데이트 해주면
객체들의 transform이나 material처럼 매번 업데이트할 필요가 없기 때문
=>
CBV를 따로 만들어 프레임마다 한번만 업로드하는 버퍼로 이용
class Light : public Component
{
public:
...
virtual void FinalUpdate() override;
public:
const LightInfo& GetLightInfo() { return _lightInfo; }
...
private:
LightInfo _lightInfo = {};
};
light 클래스 정의
FinalUpdate 함수 실행 시 LightInfo의 position에 광원의 월드 좌표를 넣어줌
GPU b0 버퍼를 광원 정보를 넘겨줄 때 사용하기 위한
root signature 수정
void RootSignature::CreateRootSignature()
{
CD3DX12_DESCRIPTOR_RANGE ranges[] =
{
CD3DX12_DESCRIPTOR_RANGE(D3D12_DESCRIPTOR_RANGE_TYPE_CBV, CBV_REGISTER_COUNT - 1, 1), // b1~b4
CD3DX12_DESCRIPTOR_RANGE(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, SRV_REGISTER_COUNT, 0), // t0~t4
};
CD3DX12_ROOT_PARAMETER param[2]{};
param[0].InitAsConstantBufferView(static_cast<uint32>(CBV_REGISTER::b0));
param[1].InitAsDescriptorTable(_countof(ranges), ranges);
...
}
InitAsConstantBufferView로 b0는 CBV로 사용할 것이라고 서명
descriptor heap 수정
void TableDescriptorHeap::Init(uint32 count)
{
_groupCount = count;
D3D12_DESCRIPTOR_HEAP_DESC desc = {};
desc.NumDescriptors = count * (REGISTER_COUNT - 1);
...
_groupSize = _handleSize * (REGISTER_COUNT - 1);
}
b0를 제외한 레지스터 개수로 수정
D3D12_CPU_DESCRIPTOR_HANDLE TableDescriptorHeap::GetCPUHandle(uint8 reg)
{
...
D3D12_CPU_DESCRIPTOR_HANDLE handle = _descHeap->GetCPUDescriptorHandleForHeapStart();
handle.ptr += _currentGroupIndex * _groupSize;
handle.ptr += (reg - 1) * _handleSize;
return handle;
}
b0는 table descriptor에서 빠졌기 때문에
테이블에서 1번 레지스터를 찾을 때 b1을 반환할 수 있도록 수정
void TableDescriptorHeap::CommitTable()
{
...
CMD_LIST->SetGraphicsRootDescriptorTable(1, handle);
...
}
table index가 1이기 때문에 1로 수정
void Engine::Init(const WindowInfo& info)
{
...
CreateConstantBuffer(CBV_REGISTER::b0, sizeof(LightParams), 1);
CreateConstantBuffer(CBV_REGISTER::b1, sizeof(TransformParams), 256);
CreateConstantBuffer(CBV_REGISTER::b2, sizeof(MaterialParams), 256);
...
}
b0의 버퍼는 1개면 된다.
constant buffer
enum class CONSTANT_BUFFER_TYPE : uint8
{
GLOBAL,
TRANSFORM,
MATERIAL,
END,
};
광원 정보를 넣을 버퍼의 타입을 GLOBAL로 정의
void ConstantBuffer::SetGlobalData(void* buffer, uint32 size)
{
assert(_elementSize == ((size + 255) & ~255));
::memcpy(&_mappedBuffer[0], buffer, size);
CMD_LIST->SetGraphicsRootConstantBufferView(0, GetGpuVirtualAddress(0));
}
CBV 버퍼는 1개만 있기 때문에 항상 0번을 사용하도록 구현
scene 수정
void Scene::Redner()
{
PushLightData();
for (auto& gameObject : _gameObjects)
{
...
gameObject->GetCamera()->Render();
}
}
기존 에서 렌더하던 부분을 scene에서 하도록 수정
void Scene::PushLightData()
{
...
for (auto& gameObject : _gameObjects)
{
...
const LightInfo& lightInfo = gameObject->GetLight()->GetLightInfo();
lightParams.lights[lightParams.lightCount] = lightInfo;
lightParams.lightCount++;
}
CONST_BUFFER(CONSTANT_BUFFER_TYPE::GLOBAL)->SetGlobalData(&lightParams, sizeof(lightParams));
}
light 컴포넌트를 들고있는 오브젝트를 찾고
해당 오브젝트들을 lightParams에 넣어 GPU로 업로드 요청
transform 수정
struct TransformParams
{
Matrix matWorld;
Matrix matView;
Matrix matProjection;
Matrix matWV;
Matrix matWVP;
};
void Transform::PushData()
{
...
transformParams.matWorld = _matWorld;
transformParams.matView = Camera::S_MatView;
transformParams.matProjection = Camera::S_MatProjection;
transformParams.matWV = _matWorld * Camera::S_MatView;
transformParams.matWVP = _matWorld * Camera::S_MatView * Camera::S_MatProjection;
...
}
쉐이더에서 사용할만한 행렬들을 모두 전달하도록 수정
쉐이더 파일 수정
쉐이더 파일이 길어짐에 따라 여러개로 분리 후 include 하여 사용
쉐이더 헤더 파일은 pragma를 전처리 할 수 없을 수 있음
=>
#ifndef _DEFAULT_HLSLI_
#define _DEFAULT_HLSLI_
...
#endif
ifndef와 define를 사용하여 헤더 중복 검사
struct LightColor
{
float4 diffuse;
float4 ambient;
float4 specular;
};
struct LightInfo
{
LightColor color;
float4 position;
float4 direction;
int lightType;
float range;
float angle;
int padding;
};
cbuffer GLOBAL_PARAMS : register(b0)
{
int g_lightCount;
float3 g_lightPadding;
LightInfo g_light[50];
}
cbuffer TRANSFORM_PARAMS : register(b1)
{
row_major matrix g_matWorld;
row_major matrix g_matView;
row_major matrix g_matProjection;
row_major matrix g_matWV;
row_major matrix g_matWVP;
};
light 관련 데이터들을 정의
constant buffer view data rules
// 2 x 16byte elements
cbuffer IE
{
float4 Val1;
float2 Val2; // starts a new vector
float2 Val3;
};
// 3 x 16byte elements
cbuffer IE
{
float2 Val1;
float4 Val2; // starts a new vector
float2 Val3; // starts a new vector
};
쉐이더에서는 데이터 크기가 16byte를 넘을 수 없으며 이보다 작을 시 packing 하여 처리
packing 시 16byte boundary를 넘을 수 없음
=>
쉐이더 파일에서 사용할 데이터를 설계할 때 이를 고려 해야 함
struct VS_OUT
{
float4 pos : SV_Position;
float2 uv : TEXCOORD;
float3 viewPos : POSITION;
float3 viewNormal : NORMAL;
};
vertex 쉐이더에서 view 좌표계의 position과 normal 벡터를 반환해주도록 구현
빛 계산 시 position과 normal 벡터 필요
=>
world 좌표계나 view 좌표계 하나를 정하여 계산
=>
view 좌표계 이용 시 카메라의 position이 필요 없게 됨 (카메라 좌표가 원점)
float4 PS_Main(VS_OUT input) : SV_Target
{
...
LightColor totalColor = (LightColor)0.f;
for (int i = 0; i < g_lightCount; ++i)
{
LightColor color = CalculateLightColor(i, input.viewNormal, input.viewPos);
totalColor.diffuse += color.diffuse;
totalColor.ambient += color.ambient;
totalColor.specular += color.specular;
}
color.xyz = (totalColor.diffuse.xyz * color.xyz)
+ totalColor.ambient.xyz * color.xyz
+ totalColor.specular.xyz;
return color;
}
빛 연산은 정해진 공식이 없고 빠르게 계산하면서 진짜처럼 보이기만 하면 됨(?)
specular는 현재 픽셀의 색과 관련이 없이 더해지게 됨
directional light
LightColor CalculateLightColor(int lightIndex, float3 viewNormal, float3 viewPos)
{
...
float diffuseRatio = 0.f;
float specularRatio = 0.f;
float distanceRatio = 1.f;
if (g_light[lightIndex].lightType == 0)
{
viewLightDir = normalize(mul(float4(g_light[lightIndex].direction.xyz, 0.f), g_matView).xyz);
diffuseRatio = saturate(dot(-viewLightDir, viewNormal));
}
...
float3 reflectionDir = normalize(viewLightDir + 2 * (saturate(dot(-viewLightDir, viewNormal)) * viewNormal));
float3 eyeDir = normalize(viewPos);
specularRatio = saturate(dot(-eyeDir, reflectionDir));
specularRatio = pow(specularRatio, 2);
color.diffuse = g_light[lightIndex].color.diffuse * diffuseRatio * distanceRatio;
color.ambient = g_light[lightIndex].color.ambient * distanceRatio;
color.specular = g_light[lightIndex].color.specular * specularRatio * distanceRatio;
return color;
}
viewLightDir : 빛의 방향 벡터
diffuseRatio :
빛의 방향을 -를 곱하여 반대로 향하게 한 후, (물체에서 나가는 방향으로)
normal 벡터와 내적하여 를 구함
saturate 함수로 0과 1사이의 값이 나오도록 clamp
reflectionDir :
diffuseRatio와 동일한 계산을 하여 를 구함 = normal 벡터로 투영된 벡터
빛 벡터에 normal 벡터 방향으로 곱한 투영 벡터를 2번 더 함 = 반사된 벡터
이를 normalize하여 방향만 추출
eyeDir :
view 좌표계 이용으로 카메라 좌표가 원점이므로
물체의 position을 normalize하여 원점에서 물체로의 방향 벡터를 구함
specularRatio :
eyeDir 방향 벡터에 - 값을 곱하여 물체에서 나가는 방향으로 만든 후,
반사된 빛의 방향 벡터와 내적하고 이 가 0과 1사이의 값이 나오도록 clamp
pow 함수를 사용하는 이유:
specular가 적용되는 면적을 줄이고 빛의 세기를 강하게 하도록 만듬
point Light
LightColor CalculateLightColor(int lightIndex, float3 viewNormal, float3 viewPos)
{
...
else if (g_light[lightIndex].lightType == 1)
{
float3 viewLightPos = mul(float4(g_light[lightIndex].position.xyz, 1.f), g_matView).xyz;
viewLightDir = normalize(viewPos - viewLightPos);
diffuseRatio = saturate(dot(-viewLightDir, viewNormal));
float dist = distance(viewPos, viewLightPos);
if (g_light[lightIndex].range == 0.f)
distanceRatio = 0.f;
else
distanceRatio = saturate(1.f - pow(dist / g_light[lightIndex].range, 2));
}
...
}
광원에서 물체로 향하는 방향 벡터를 가지고 diffuse를 계산
거리에 따라 빛이 약해지도록 distanceRatio를 계산
pow 함수를 사용하여 멀어질수록 급격히 세기가 약해지도록 구현
spot light
LightColor CalculateLightColor(int lightIndex, float3 viewNormal, float3 viewPos)
{
...
float3 viewLightPos = mul(float4(g_light[lightIndex].position.xyz, 1.f), g_matView).xyz;
viewLightDir = normalize(viewPos - viewLightPos);
diffuseRatio = saturate(dot(-viewLightDir, viewNormal));
if (g_light[lightIndex].range == 0.f)
distanceRatio = 0.f;
else
{
float halfAngle = g_light[lightIndex].angle / 2;
float3 viewLightVec = viewPos - viewLightPos;
float3 viewCenterLightDir = normalize(mul(float4(g_light[lightIndex].direction.xyz, 0.f), g_matView).xyz);
float centerDist = dot(viewLightVec, viewCenterLightDir);
distanceRatio = saturate(1.f - centerDist / g_light[lightIndex].range);
float lightAngle = acos(dot(normalize(viewLightVec), viewCenterLightDir));
if (centerDist < 0.f || centerDist > g_light[lightIndex].range)
distanceRatio = 0.f;
else if (lightAngle > halfAngle)
distanceRatio = 0.f;
else
distanceRatio = saturate(1.f - pow(centerDist / g_light[lightIndex].range, 2));
}
...
}
viewLightVec : 광원에서 물체로 가는 벡터
viewCenterLightDir : 광원의 방향 벡터
centerDist :
광원에서 물체로 가는 벡터를 광원 방향으로 투영하여 그 크기를 구함
lightAngle :
광원에서 물체로 가는 방향 벡터와 광원의 방향 벡터를 내적 후 acos 함수로 각도 를 구함
=>
거리와 각도에 따라 distanceRatio를 설정
if (FAILED(::D3DCompileFromFile(path.c_str(), nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE
, name.c_str(), version.c_str(), compileFlag, 0, &blob, &_errBlob)))
{
::MessageBoxA(nullptr, "Shader Create Failed !", nullptr, MB_OK)
}
쉐이더 create 가 실패하면 보통 쉐이더 파일의 문법적 오류일 확률이 높음