게임과 엔진에서 흔히 볼수 있는 하늘은 Skybox를 이용한 것이다. 이것은 어떻게 만들까? 단순히 플레이어 주변에
하늘 texture을 가진 구라고 생각하면 문제가 발생한다. 먼저 플레이어가 이동하다보면 skybox와 부딪힐 수 있다는 것이다. 하늘은 저 멀리 무한히 가도 도달하지 못하도록 표현되어야 한다. 그리고 skybox 뒤에 있는 물체는 표현할 수 없는 문제가 발생한다. 그래서 view좌표계 기준 depth가 1로 항상 하늘은 맨 뒤에 있어야 한다. 그리고 skybox를 카메라 위치에 두고 translation, scale 없이 rotation만 이용한다.
explicit CD3DX12_RASTERIZER_DESC( CD3DX12_DEFAULT )
{
FillMode = D3D12_FILL_MODE_SOLID;
CullMode = D3D12_CULL_MODE_BACK;
FrontCounterClockwise = FALSE;
...
먼저 래스터라이즈 설정에 대해 알아야한다. CullMode는 FRONT,BACK,NONE이 있는데 BACK으로 설정했고
FrontCounterClockWise = FALSE는 Front가 반시계 방향이 아니다, 즉 시계방향이라는 것을 의미한다. CullMode는 정점을 스킵하고 BACK을 스킵한다고 했으니 시계방향일 경우(FRONT) 스킵하지 않고 반시계일 경우(BACK) 스킵한다.
// 앞면
vec[0] = Vertex(Vec3(-w2, -h2, -d2), Vec2(0.0f, 1.0f), Vec3(0.0f, 0.0f, -1.0f), Vec3(1.0f, 0.0f, 0.0f));
vec[1] = Vertex(Vec3(-w2, +h2, -d2), Vec2(0.0f, 0.0f), Vec3(0.0f, 0.0f, -1.0f), Vec3(1.0f, 0.0f, 0.0f));
vec[2] = Vertex(Vec3(+w2, +h2, -d2), Vec2(1.0f, 0.0f), Vec3(0.0f, 0.0f, -1.0f), Vec3(1.0f, 0.0f, 0.0f));
vec[3] = Vertex(Vec3(+w2, -h2, -d2), Vec2(1.0f, 1.0f), Vec3(0.0f, 0.0f, -1.0f), Vec3(1.0f, 0.0f, 0.0f));
// 앞면
idx[0] = 0; idx[1] = 1; idx[2] = 2;
idx[3] = 0; idx[4] = 2; idx[5] = 3;
12
03
위에서는 Vertex Buffer과 Index Buffer 모두 정점을 시계방향으로 저장하고 있다. 그래서 앞에서 봤을 때는 정점을 스킵하지 않지만 뒤에서 봤을 때는 뒷면이 반시계 방향이므로 래스터라이즈 단계에서 정점을 계산하지 않고 스킵한다. 그래서 보이지 않는 부분을 계산을 하지 않아 최적화에 도움을 준다. 하지만 큐브의 내부로 들어가면 정점들이 그려지지 않기 때문에 skybox를 만들기 위해 구의 내부로 들어가서 하늘이 그려지도록 해야 한다.
enum class DEPTH_STENCIL_TYPE
{
LESS,
LESS_EQUAL,
GREATER,
GREATER_EQUAL,
};
skybox의 depth가 1이어야 한다고 했는데 진짜 1이 되어버리면 depth 기본값이 1이므로 아무것도 없는 상태와 같아 하늘이 그려지지 않는다. 그래서 LESS_EQUAL을 만들어 1과 같을 때도 하늘을 그려주도록 한다.
void Shader::Init(const wstring& path, ShaderInfo info)
{
...
switch (info.rasterizerType)
{
case RASTERIZER_TYPE::CULL_BACK:
_pipelineDesc.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
_pipelineDesc.RasterizerState.CullMode = D3D12_CULL_MODE_BACK;
break;
case RASTERIZER_TYPE::CULL_FRONT:
_pipelineDesc.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
_pipelineDesc.RasterizerState.CullMode = D3D12_CULL_MODE_FRONT;
break;
case RASTERIZER_TYPE::CULL_NONE:
_pipelineDesc.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
_pipelineDesc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
break;
case RASTERIZER_TYPE::WIREFRAME:
_pipelineDesc.RasterizerState.FillMode = D3D12_FILL_MODE_WIREFRAME;
_pipelineDesc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
break;
}
switch (info.depthStencilType)
{
case DEPTH_STENCIL_TYPE::LESS:
_pipelineDesc.DepthStencilState.DepthEnable = TRUE;
_pipelineDesc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_LESS;
break;
case DEPTH_STENCIL_TYPE::LESS_EQUAL:
_pipelineDesc.DepthStencilState.DepthEnable = TRUE;
_pipelineDesc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL;
break;
case DEPTH_STENCIL_TYPE::GREATER:
_pipelineDesc.DepthStencilState.DepthEnable = TRUE;
_pipelineDesc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_GREATER;
break;
case DEPTH_STENCIL_TYPE::GREATER_EQUAL:
_pipelineDesc.DepthStencilState.DepthEnable = TRUE;
_pipelineDesc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_GREATER_EQUAL;
break;
}
DEVICE->CreateGraphicsPipelineState(&_pipelineDesc, IID_PPV_ARGS(&_pipelineState));
Rasterizer type 과 Depth stencil type 을 ShaderInfo 구조체에 담아 Shader에 전달하고 Shader::Init할 때 Rasterizer type과 Depth stencil type에 맞춰 설정해준다.
shared_ptr<GameObject> skybox = make_shared<GameObject>();
skybox->AddComponent(make_shared<Transform>());
shared_ptr<MeshRenderer> meshRenderer = make_shared<MeshRenderer>();
{
shared_ptr<Mesh> sphereMesh = GET_SINGLE(Resources)->LoadSphereMesh();
meshRenderer->SetMesh(sphereMesh);
}
{
shared_ptr<Shader> shader = make_shared<Shader>();
shared_ptr<Texture> texture = make_shared<Texture>();
shader->Init(L"..\\Resources\\Shader\\skybox.hlsli",
{ RASTERIZER_TYPE::CULL_NONE, DEPTH_STENCIL_TYPE::LESS_EQUAL });
texture->Init(L"..\\Resources\\Texture\\Sky01.jpg");
shared_ptr<Material> material = make_shared<Material>();
material->SetShader(shader);
material->SetTexture(0, texture);
meshRenderer->SetMaterial(material);
}
skybox->AddComponent(meshRenderer);
scene->AddGameObject(skybox);
그리고 Scene에 skybox를 만들어준다. 구 내부에서 보는 것이므로 반시계 시계 둘다 허용하는 CULL_NONE이나 반시계만 허용하는 CULL_FRONT를 사용하고 Depth Stencil Type 은 Less Equal을 사용하면 된다.
VS_OUT VS_Main(VS_IN input)
{
VS_OUT output = (VS_OUT)0;
// Translation은 하지 않고 Rotation만 적용한다
float4 viewPos = mul(float4(input.localPos, 0), g_matView);
float4 clipSpacePos = mul(viewPos, g_matProjection);
// w/w=1이기 때문에 항상 깊이가 1로 유지된다
output.pos = clipSpacePos.xyww;
output.uv = input.uv;
return output;
}
skybox의 쉐이더 코드에서는 viewPos를 계산할 때 (x,y,z,w)의 w를 0으로 설정해 Translation을 하지 않고 g_matView에 곱한다. clipSpacePos는 g_matProjection을 곱해서 구할 수 있고 래스터라이즈 단계에서 (x,y,z,w)를 w로 나누는데 세번째 요소가 깊이를 의미하므로 z/w가 1이 되려면 z = w 이면 된다. (Projection 변환 행렬 참조)
'그래픽스' 카테고리의 다른 글
| [DX12] Orthographic Projection (0) | 2022.07.28 |
|---|---|
| [DX12] Quaternion #1 (0) | 2022.07.27 |
| [DX12] Lighting 코드 #2 (0) | 2022.07.25 |
| [DX12] Lighting 코드 #1 (0) | 2022.07.25 |
| [DX12] Camera (0) | 2022.07.24 |