쉐이더상에서 임시로 존재했던 광원들을 컴포넌트화 시켜 여러개의 광원을 적용시켜보자.
#pragma once
#include "CComponent.h"
class CLight3D :
public CComponent
{
private:
tLightInfo m_Info;
public:
const tLightInfo& GetLightInfo() { return m_Info; }
void SetLightColor(Vec3 _vColor) { m_Info.vColor = _vColor; }
void SetSpecular(Vec3 _vSpec) { m_Info.vSpecular = _vSpec; }
void SetAmbient(Vec3 _vAmb) { m_Info.vAmbient = _vAmb; }
Vec4 GetLightColor(Vec3 _vColor) { return m_Info.vColor; }
Vec4 GetSpecular(Vec3 _vSpec) { return m_Info.vSpecular; }
Vec4 GetAmbient(Vec3 _vAmb) { return m_Info.vAmbient; }
void SetLightType(LIGHT_TYPE _type);
void SetRadius(float _Radius) { m_Info.fRadius = _Radius; }
void SetAngle(float _Angle) { m_Info.fAngle = _Angle; }
LIGHT_TYPE GetLightType() { return (LIGHT_TYPE)m_Info.LightType; }
float GetRadius() { return m_Info.fRadius; }
float GetAngle() { return m_Info.fAngle; }
public:
virtual void finaltick() override;
virtual void SaveToFile(FILE* _File) override;
virtual void LoadFromFile(FILE* _File) override;
CLONE(CLight3D);
public:
CLight3D();
~CLight3D();
};
Light2D와 크게 다를거 없이 광원에 대한 정보를 저장하고, 정보를 설정해 줄수 있는 함수들을 만들었다.
void CLight3D::finaltick()
{
m_Info.vWorldDir = Transform()->GetWorldDir(DIR_TYPE::FRONT);
m_Info.vWorldPos = Transform()->GetWorldPos();
CRenderMgr::GetInst()->RegisterLight3D(this);
}
각각 컴포넌트는 final tick에서 자신의 정보를 업데이트해서 RenderComponent에게 전달하고
class CRenderMgr :
public CSingleton<CRenderMgr>
{
SINGLE(CRenderMgr);
private:
vector<CCamera*> m_vecCam;
CCamera* m_EditorCam;
Ptr<CTexture> m_PostProcessTex;
CStructuredBuffer* m_Light2DBuffer;
vector<CLight2D*> m_vecLight2D;
CStructuredBuffer* m_Light3DBuffer;
vector<CLight3D*> m_vecLight3D;
list<tDebugShapeInfo> m_DbgShapeInfo;
CGameObject* m_pDebugObj;
bool m_DebugPosition;
// NoiseTexture
vector<Ptr<CTexture>> m_vecNoiseTex;
// render function pointer
typedef void(CRenderMgr::*RENDER_FUNC)(void);
RENDER_FUNC m_RenderFunc;
랜더 컴포넌트에 Light를 담을 vector와 구조화된 버퍼를 추가한다.
void CRenderMgr::UpdateData()
{
g_global.g_Light2DCount = (int)m_vecLight2D.size();
g_global.g_Light3DCount = (int)m_vecLight3D.size();
// 전역 데이터 업데이트
static CConstBuffer* pCB = CDevice::GetInst()->GetConstBuffer(CB_TYPE::GLOBAL_DATA);
pCB->SetData(&g_global);
pCB->UpdateData();
pCB->UpdateData_CS();
// 2D 광원정보 업데이트
static vector<tLightInfo> vecLight2DInfo;
for (size_t i = 0; i < m_vecLight2D.size(); ++i)
{
const tLightInfo& info = m_vecLight2D[i]->GetLightInfo();
vecLight2DInfo.push_back(info);
}
if (!vecLight2DInfo.empty())
{
m_Light2DBuffer->SetData(vecLight2DInfo.data(), (UINT)vecLight2DInfo.size());
}
m_Light2DBuffer->UpdateData(11);
vecLight2DInfo.clear();
// 3D 광원정보 업데이트
static vector<tLightInfo> vecLight3DInfo;
for (size_t i = 0; i < m_vecLight3D.size(); ++i)
{
const tLightInfo& info = m_vecLight3D[i]->GetLightInfo();
vecLight3DInfo.push_back(info);
}
if (!vecLight3DInfo.empty())
{
m_Light3DBuffer->SetData(vecLight3DInfo.data(), (UINT)vecLight3DInfo.size());
}
m_Light3DBuffer->UpdateData(12);
vecLight3DInfo.clear();
}
그리고 각 프레임마다 광원의 정보를 업데이트시켜 구조화된 버퍼로 넘겨준다.
float4 PS_Std3D(VTX_OUT _in) : SV_Target
{
float4 vOutColor = float4(0.f, 0.f, 0.f, 1.f);
// 물체 색상
float4 ObjectColor = float4(0.7f, 0.7f, 0.7f, 1.f);
// 출력 텍스쳐가 바인딩 되어있다면, 텍스쳐의 색상을 사용한다.
if (g_btex_0)
{
ObjectColor = g_tex_0.Sample(g_sam_0, _in.vUV);
}
float3 vViewNormal = _in.vViewNormal;
// 노말 텍스쳐가 바인딩 되어있다면, 노말맵핑을 진행한다.
if (g_btex_1 && g_int_0)
{
// 색상의 범위는 0~1 이지만, 저장된 값은 방향벡터를 뜻하기 때문에 원래 의도한 값으로 바꾸기 위해서
// 값의 0 ~ 1 범위를 -1.f ~ 1.f 로 변경한다.
float3 vNormal = g_tex_1.Sample(g_sam_0, _in.vUV).rgb;
vNormal = vNormal * 2.f - 1.f;
float3x3 matRot =
{
_in.vViewTangent,
-_in.vViewBinormal,
_in.vViewNormal,
};
vViewNormal = normalize(mul(vNormal.xyz, matRot));
}
tLightColor LightColor = (tLightColor) 0.f;
for (int i = 0; i < g_Light3DCount; ++i)
{
CalculateLight3D(i, _in.vViewPos, vViewNormal, LightColor);
}
// 최종 색상 == 물체 색 x (광원의 색 x 표면의 광원 세기)
// + 물체 색 x (환경광 세기)
// + (빛의 색 x 빛의 반사광 감소비율 x 반사세기(카메라랑 반사벡터가 서로 마주보는 정도))
vOutColor.xyz = ObjectColor.xyz * LightColor.vColor.rgb
+ ObjectColor.xyz * LightColor.vAmbient.rgb
+ LightColor.vSpecular.rgb;
return vOutColor;
}
쉐이더 코드에서는 전역 데이터로 사용하던 광원을 지우고 구조화된 버퍼에 있는 광원을 가져와서 픽셀셰이더 단계에서 빛을 적용시켜준다.
void CalculateLight3D(int _LightIdx, float3 _vViewPos, float3 _vViewNormal, inout tLightColor _LightColor)
{
// 광원의 정보를 확인
tLightInfo Light = g_Light3D[_LightIdx];
// 광원이 물체를 향하는 방향벡터
float3 vViewLightDir = (float3) 0.f;
float fDistanceRatio = 1.f;
// Directional Light
if(0 == Light.LightType)
{
// 광원 연산이 ViewSpace 에서 진행되기로 했기 때문에,
// 광원이 진입하는 방향도 View 공간 기준으로 변경함
vViewLightDir = normalize(mul(float4(Light.vWorldDir, 0.f), g_matView).xyz);
}
// Point Light
else if( 1 == Light.LightType)
{
float3 vLightViewPos = mul(float4(Light.vWorldPos, 1.f), g_matView).xyz;
vViewLightDir = _vViewPos - vLightViewPos;
// 광원과 물체 사이의 거리
float fDistance = length(vViewLightDir);
vViewLightDir = normalize(vViewLightDir);
// 광원 반경과 물체까지의 거리에 따른 빛의 세기
fDistanceRatio = saturate(1.f - (fDistance / Light.fRadius));
}
// Spot Light
else
{
}
// ViewSpace 에서 광원의 방향과, 물체 표면의 법선를 이용해서 광원의 진입 세기(Diffuse) 를 구한다.
float LightPow = saturate(dot(_vViewNormal, -vViewLightDir));
// 빛이 표면에 진입해서 반사되는 방향을 구한다.
float3 vReflect = vViewLightDir + 2 * dot(-vViewLightDir, _vViewNormal) * _vViewNormal;
vReflect = normalize(vReflect);
// 카메라가 물체를 향하는 방향
float3 vEye = normalize(_vViewPos);
// 시선벡터와 반사벡터 내적, 반사광의 세기
float ReflectPow = saturate(dot(-vEye, vReflect));
ReflectPow = pow(ReflectPow, 20.f);
_LightColor.vColor += Light.Color.vColor * LightPow * fDistanceRatio;
_LightColor.vAmbient += Light.Color.vAmbient;
_LightColor.vSpecular += Light.Color.vColor * Light.Color.vSpecular * ReflectPow * fDistanceRatio;
}
빛을 계산하는 코드는 기존의 코드를 가져와 함수화시켜 주었다.

색이 다른 두개의 Point Light를 넣어준 모습