[DirectX11 3D] Light3D Component

한창희·2024년 5월 16일

DirecX3D 엔진 만들기

목록 보기
4/14

📌Light3D 컴포넌트 만들기

쉐이더상에서 임시로 존재했던 광원들을 컴포넌트화 시켜 여러개의 광원을 적용시켜보자.

#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를 넣어준 모습

0개의 댓글