: World 전체를 SkyBox로 감싸 배경을 그려주는 컴포넌트

// ============
// SkyBoxShader
// ============
pShader = new CGraphicsShader;
pShader->CreateVertexShader(L"shader\\skybox.fx", "VS_SkyBox");
pShader->CreatePixelShader(L"shader\\skybox.fx", "PS_SkyBox");
pShader->SetRSType(RS_TYPE::CULL_FRONT);
// SkyBox 는 최대깊이인 1 로 그려질 예정, 따라서 초기화 값인 Depth 1 이랑 같은 경우까지도 통과시켜줘야 한다.
pShader->SetDSType(DS_TYPE::LESS_EQUAL);
pShader->SetDomain(SHADER_DOMAIN::DOMAIN_OPAQUE);
AddAsset(L"SkyBoxShader", pShader.Get());
스카이박스는 월드를 감싸는 개념이므로, 항상 안쪽에서 바깥쪽을 보게 된다.
그래서 레스터라이져 스테이트를 Cull Front로 하여 안쪽만 렌더링되도록 해야한다.
그리고 스카이박스는 오브젝트가 아무것도 없는 곳에 배경으로 그려야 하므로 뎊스를 1로 주어서 화면에 그리도록 한다. 이 때 뎊스 스텐실 스테이트가 LESS 라면 초기화 값인 1이랑 같아서 깊이 테스트를 통과할수 없기 때문에 뎊스 스텐실 스테이트르 LESS_EQUAL로 하여 최대깊이라면 SkyBox가 그려지도록 해야한다.
#pragma once
#include "CRenderComponent.h"
enum class SKYBOX_TYPE
{
SPHERE,
CUBE,
};
class CSkyBox :
public CRenderComponent
{
private:
SKYBOX_TYPE m_SkyBoxType;
Ptr<CTexture> m_SphereTex;
Ptr<CTexture> m_CubeTex;
public:
void SetSkyBoxType(SKYBOX_TYPE _Type);
void SetSphereTexture(Ptr<CTexture> _Texture) { m_SphereTex = _Texture; }
void SetCubeTexture(Ptr<CTexture> _Texture) { m_CubeTex = _Texture; }
public:
virtual void finaltick() override;
virtual void UpdateData() override;
virtual void render() override;
public:
CLONE(CSkyBox);
CSkyBox();
~CSkyBox();
};
SkyBox 클래스를 생성한다.
SkyBox는 구모양과, 박스모양 두가지가 있으니 enum class로 정의하고 객체마다 타입을 정해줄 수 있도록 하였다.
그리고 Type에 따라 다른 Texture를 사용해야 하기 때문에, 텍스쳐를 나누어서 멤버 변수로 갖고 있다.
#include "pch.h"
#include "CSkyBox.h"
#include "CAssetMgr.h"
#include "CMesh.h"
#include "CTransform.h"
CSkyBox::CSkyBox()
: CRenderComponent(COMPONENT_TYPE::SKYBOX)
, m_SkyBoxType(SKYBOX_TYPE::SPHERE)
{
SetSkyBoxType(m_SkyBoxType);
SetMaterial(CAssetMgr::GetInst()->FindAsset<CMaterial>(L"SkyBoxMtrl"));
}
CSkyBox::~CSkyBox()
{
}
void CSkyBox::SetSkyBoxType(SKYBOX_TYPE _Type)
{
m_SkyBoxType = _Type;
if (SKYBOX_TYPE::SPHERE == m_SkyBoxType)
{
SetMesh(CAssetMgr::GetInst()->FindAsset<CMesh>(L"SphereMesh"));
}
else if (SKYBOX_TYPE::CUBE == m_SkyBoxType)
{
SetMesh(CAssetMgr::GetInst()->FindAsset<CMesh>(L"CubeMesh"));
}
}
void CSkyBox::finaltick()
{
}
void CSkyBox::UpdateData()
{
Transform()->UpdateData();
GetMaterial()->SetScalarParam(SCALAR_PARAM::INT_0(int)m_SkyBoxType);
if (SKYBOX_TYPE::SPHERE == m_SkyBoxType)
{
GetMaterial()->SetTexParam(TEX_PARAM::TEX_0, m_SphereTex);
}
else if (SKYBOX_TYPE::CUBE == m_SkyBoxType)
{
GetMaterial()->SetTexParam(TEX_PARAM::TEXCUBE_0, m_CubeTex);
}
GetMaterial()->UpdateData();
}
void CSkyBox::render()
{
UpdateData();
GetMesh()->render();
}
SkyBox Type을 정해주는 함수에는 각 타입에 맞춰 매쉬를 세팅해주고, 매 프레임마다 자신의 타입과 자신에 타입에 맞는 Texture를 바인딩 시키도록 한다.
#ifndef _SKYBOX
#define _SKYBOX
#include "value.fx"
#include "func.fx"
struct VS_SKYBOX_IN
{
float3 vPos : POSITION;
float2 vUV : TEXCOORD;
};
struct VS_SKYBOX_OUT
{
float4 vPosition : SV_Position;
float2 vUV : TEXCOORD;
float3 vUV_Dir : POSITION;
};
VS_SKYBOX_OUT VS_SkyBox(VS_SKYBOX_IN _in)
{
VS_SKYBOX_OUT output = (VS_SKYBOX_OUT) 0.f;
// 로컬 스페이스의 메쉬가 이미 카메라(View) 스페이스의 원점이 있는것으로 가정
float3 vLocalPos = _in.vPos * 2.f;
float3 vViewPos = mul(float4(vLocalPos, 0.f), g_matView);
float4 vPosition = mul(float4(vViewPos, 1.f), g_matProj);
vPosition.z = vPosition.w;
// Skybox 가 Cube 타입인 경우
if(1 == g_int_0)
{
output.vUV_Dir = _in.vPos;
}
output.vPosition = vPosition;
output.vUV = _in.vUV;
return output;
}
float4 PS_SkyBox(VS_SKYBOX_OUT _in) : SV_Target
{
float4 vOutColor = float4(0.2f, 0.2f, 1.f, 1.f);
if( 0 == g_int_0)
{
if (g_btex_0)
{
vOutColor = g_tex_0.Sample(g_sam_0, _in.vUV);
}
}
else if(1 == g_int_0)
{
if(g_btexcube_0)
{
float3 vUV = normalize(_in.vUV_Dir);
vOutColor = g_texcube_0.Sample(g_sam_0, vUV);
}
}
return vOutColor;
}
#endif
SkyBox의 경우 카메라기준으로 항상 가잔 뒤쪽에 그려져야 한다.
이를 구현하기 위해서는 두가지 방법이 존재하는데 첫 번째는, 카메라가 랜더링하기 전 SkyBox의 위치를 카메라 중앙으로 옮겨주고, 카메라의 최대범위 만큼 SkyBox의 크기를 키워줄 수 있다.

근데 이 방법의 경우 SkyBox가 구 모양일 때, 위와 같이 카메라와 SkyBox가 존재하고 빨간부분에 오브젝트가 있다면 SkyBox가 해당 물체를 가리게 된다.
그래서 쉐이더 상에서 SkyBox의 깊이를 최대 깊이로 조작하여 마치 ndc좌표에서 가장 뒤에있는 것처럼 뎊스테스트를 통과하게 만들어야 한다.
레이터라이져 단계에서 SkyBox는 항상 뎊스가 최대 깊이여야 하므로 z값을 w랑 맞추어 ndc좌표계에서는 항상 1이 나오도록 한다.
Cube SkyBox의 경우 로컬에서의 방향벡터를 기준으로 샘플링 해야하기 때문에 로컬에서의 좌표를 그대로 픽셀셰이더로 넘겨준다.
픽셸셰이더에서 자신의 타입에 맞춰 넘겨받은 UV를 기준으로 샘플링하여 화면에 그리면 항상 뎊스가 1로 계산되어 Skybox가 완성된다.