SkyBox

 

  • 게임 내의 하늘을 구현하는 방법이다.
  • 카메라의 가시영역의 끝이 1이라고 가정할 때 거대한 큐브를 만들어 그 안에 카메라를 넣는 방식으로 구현한다.
  • 카메라를 skybox 안에 넣고 별 다른 수정이 없다면 다음과 같은 문제가 발생한다.
    1. 플레이어가 움직여 Skybox에 접근하게 될 경우 Skybox를 뚫고 지나가게 되며, Skybox에 가까워질 수록 sky에 해당하는 텍스처가 더 가까이 보이게된다.
    2. 만약 Skybox보다 먼 곳에 존재하는 물체가 존재한다면 Depth-Stencil 검사에 의해 Skybox보다 먼 곳에 위치한 물체는 그려지지 않는다.
    3. Culling에 의해서 반 시계방향으로 이루어진 정점을 그리지 않는다. 카메라는 skybox 큐브 내부에 존재하므로 카메라의 위치에서 보면 skybox는 반 시계방향으로 이루어진 정점들이다. 따라서 그려지지 않는다.
      => 카메라를 이동시켜 큐브 물체를 뚫고 들어가보면 뒷면이 보이지 않는다. 뒷면은 거꾸로 되어 정점이 반 시계방향으로 이루어져있기 때문이다.
      => 따라서 Culling을 하는 이유는 정점의 순서가 반시계 방향이라는 것은 보이지 않는 뒷면이라는 뜻이 되고, 래스터라이저는 이를 포착하여 정점 연산을 스킵하게 되며 이를 통해 연산량을 줄이게 된다.
  • 위와 같은 경우는 실제 하늘과 차이가 발생하므로 우리는 몇가지 코드상의 수정을 통해 실제 하늘과 유사하게 구현한다.
    1. player가 이동해도 관계가 없게끔 Skybox를 View Space의 정점에 위치하게끔 고정시킨다.
    2. Skybox를 카메라의 가시범위 내에서 가장 먼 위치로 그린다. 카메라가 움질일 경우에도 Skybox는 항상 가시범위내 먼 위치로 유지한다.
      => 위치행렬을 xyww로 설정하여 깊이 값이 무조건 1로 설정되도록 만든다. 즉, SkyBox는 항상 카메라의 가시범위 끝부분에 존재하게 된다.
    3. Culling 규칙을 조정하여 반시계방향의 정점 또한 그려지게 만든다.
      => RASTERIZER_TYPE::CULL_NONE, DEPTH_STENCIL_TYPE::LESS_EQUAL 을 통해 SkyBox는 컬링을 하지 않으며, 뎁스 스텐실 검사는 끝거리까지 포함하여 전부 그려주게 된다.

 

 


 

 

<Shader.h>

 

 

더보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
#pragma once
#include "Object.h"
 
enum class RASTERIZER_TYPE
{
    CULL_NONE,
    CULL_FRONT,
    CULL_BACK,
    WIREFRAME,
};
 
enum class DEPTH_STENCIL_TYPE
{
    LESS,
    LESS_EQUAL,
    GREATER,
    GREATER_EQUAL,
};
 
struct ShaderInfo
{
    RASTERIZER_TYPE rasterizerType = RASTERIZER_TYPE::CULL_BACK;
    DEPTH_STENCIL_TYPE depthStencilType = DEPTH_STENCIL_TYPE::LESS;
};
 
class Shader : public Object
{
public:
    Shader();
    virtual ~Shader();
 
    void Init(const wstring& path, ShaderInfo info = ShaderInfo());
    void Update();
 
private:
    void CreateShader(const wstring& path, const string& name, const string& version, ComPtr<ID3DBlob>& blob, D3D12_SHADER_BYTECODE& shaderByteCode);
    void CreateVertexShader(const wstring& path, const string& name, const string& version);
    void CreatePixelShader(const wstring& path, const string& name, const string& version);
 
private:
    ComPtr<ID3DBlob>                    _vsBlob;
    ComPtr<ID3DBlob>                    _psBlob;
    ComPtr<ID3DBlob>                    _errBlob;
 
    ComPtr<ID3D12PipelineState>            _pipelineState;
    D3D12_GRAPHICS_PIPELINE_STATE_DESC  _pipelineDesc = {};
};
 
 
cs

 

 

  • 컬링 설정을 위한 RASTERIZER_TYPE, 뎁스스텐실 설정을 위한 DEPTH_STENCIL_TYPE, 두 타입을 저장하기 위한 구조체 ShaderInfo가 추가되었다.
  • Init()의 매개변수에 ShaderInfo가 존재하는데, Skybox를 위한 Shader를 생성할 때 RASTERIZER_TYPE과 DEPTH_STENCIL_TYPE를 인자로 넘겨줌으로써 타입을 변경하여 생성함으로써 옵션을 변경하는 방식이다.

 

 

<Shader.cpp>

 

 

더보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
#include "pch.h"
#include "Shader.h"
#include "Engine.h"
 
Shader::Shader() : Object(OBJECT_TYPE::SHADER)
{
 
}
 
Shader::~Shader()
{
 
}
 
void Shader::Init(const wstring& path, ShaderInfo info)
{
    CreateVertexShader(path, "VS_Main""vs_5_0");
    CreatePixelShader(path, "PS_Main""ps_5_0");
 
    D3D12_INPUT_ELEMENT_DESC desc[] =
    {
        { "POSITION"0, DXGI_FORMAT_R32G32B32_FLOAT, 00, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
        { "TEXCOORD"0, DXGI_FORMAT_R32G32_FLOAT, 012, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
        { "NORMAL"0, DXGI_FORMAT_R32G32B32_FLOAT, 020, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
        { "TANGENT"0, DXGI_FORMAT_R32G32B32_FLOAT, 032, D3D12_INPUT_CLASSIFICATION_PER_VERTEX_DATA, 0 },
    };
 
    _pipelineDesc.InputLayout = { desc, _countof(desc) };
    _pipelineDesc.pRootSignature = ROOT_SIGNATURE.Get();
 
    _pipelineDesc.RasterizerState = CD3DX12_RASTERIZER_DESC(D3D12_DEFAULT);
    _pipelineDesc.BlendState = CD3DX12_BLEND_DESC(D3D12_DEFAULT);
    _pipelineDesc.DepthStencilState = CD3DX12_DEPTH_STENCIL_DESC(D3D12_DEFAULT);
    _pipelineDesc.SampleMask = UINT_MAX;
    _pipelineDesc.PrimitiveTopologyType = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
    _pipelineDesc.NumRenderTargets = 1;
    _pipelineDesc.RTVFormats[0= DXGI_FORMAT_R8G8B8A8_UNORM;
    _pipelineDesc.SampleDesc.Count = 1;
    _pipelineDesc.DSVFormat = GEngine->GetDepthStencilBuffer()->GetDSVFormat();
 
    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));
}
 
void Shader::Update()
{
    CMD_LIST->SetPipelineState(_pipelineState.Get());
}
 
void Shader::CreateShader(const wstring& path, const string& name, const string& version, ComPtr<ID3DBlob>& blob, D3D12_SHADER_BYTECODE& shaderByteCode)
{
    uint32 compileFlag = 0;
#ifdef _DEBUG
    compileFlag = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
#endif
 
    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);
    }
 
    shaderByteCode = { blob->GetBufferPointer(), blob->GetBufferSize() };
}
 
void Shader::CreateVertexShader(const wstring& path, const string& name, const string& version)
{
    CreateShader(path, name, version, _vsBlob, _pipelineDesc.VS);
}
 
void Shader::CreatePixelShader(const wstring& path, const string& name, const string& version)
{
    CreateShader(path, name, version, _psBlob, _pipelineDesc.PS);
}
cs

 

 

  • Init()을 할 때 rasterizerType과 depthStencilType을 switch-case 문으로 검사함으로써 만약 타입이 따로 설정되어 있다면 타입에 맞게 RasterizerState과 DepthStencilState의 옵션을 변경해주어 생성하게 된다.

 

 

<Skybox.hlsli>

 

 

더보기
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#ifndef _SKYBOX_HLSLI_
#define _SKYBOX_HLSLI_
 
#include "params.hlsli"
 
struct VS_IN
{
    float3 localPos : POSITION;
    float2 uv : TEXCOORD;
};
 
struct VS_OUT
{
    float4 pos : SV_Position;
    float2 uv : TEXCOORD;
};
 
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;
}
 
float4 PS_Main(VS_OUT input) : SV_Target
{
     float4 color = g_tex_0.Sample(g_sam_0, input.uv);
     return color;
}
 
#endif
cs

 

 

  • VS_Main() 부분에선 viewPos를 설정할 때 (input.localPos, 0)라는 설정을 통해 Translation을 제외한 Rotation만 수행하게 된다.
    이후 View 좌표에서 Projection 좌표계를 곱해줌으로써 clipSpace의 Pos를 구하게 되며
    output의 position을 clipSpace의 xyww값으로 설정하게 해준다. => z값은 z/w가 아닌 w/w가 행해지므로 항상 1이 된다.
  • PS_Main()은 그저 색상 texture를 불러와 입혀주는 역할을 수행한다.

 

 

<SceneManager.cpp>

 

 

더보기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
#include "pch.h"
#include "SceneManager.h"
#include "Scene.h"
 
#include "Engine.h"
#include "Material.h"
#include "GameObject.h"
#include "MeshRenderer.h"
#include "Transform.h"
#include "Camera.h"
#include "Light.h"
 
#include "TestCameraScript.h"
#include "Resources.h"
 
void SceneManager::Update()
{
    if (_activeScene == nullptr)
        return;
 
    _activeScene->Update();
    _activeScene->LateUpdate();
    _activeScene->FinalUpdate();
}
 
// TEMP
void SceneManager::Render()
{
    if (_activeScene)
        _activeScene->Render();
}
 
void SceneManager::LoadScene(wstring sceneName)
{
    // TODO : 기존 Scene 정리
    // TODO : 파일에서 Scene 정보 로드
 
    _activeScene = LoadTestScene();
 
    _activeScene->Awake();
    _activeScene->Start();
}
 
shared_ptr<Scene> SceneManager::LoadTestScene()
{
    shared_ptr<Scene> scene = make_shared<Scene>();
 
#pragma region Camera
    shared_ptr<GameObject> camera = make_shared<GameObject>();
    camera->AddComponent(make_shared<Transform>());
    camera->AddComponent(make_shared<Camera>()); // Near=1, Far=1000, FOV=45도
    camera->AddComponent(make_shared<TestCameraScript>());
    camera->GetTransform()->SetLocalPosition(Vec3(0.f, 0.f, 0.f));
    scene->AddGameObject(camera);
#pragma endregion
 
#pragma region SkyBox
    {
        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);
    }
#pragma endregion
 
#pragma region Cube
    {
        shared_ptr<GameObject> sphere = make_shared<GameObject>();
        sphere->AddComponent(make_shared<Transform>());
        sphere->GetTransform()->SetLocalScale(Vec3(100.f, 100.f, 100.f));
        sphere->GetTransform()->SetLocalPosition(Vec3(0.f, 0.f, 150.f));
        shared_ptr<MeshRenderer> meshRenderer = make_shared<MeshRenderer>();
        {
            shared_ptr<Mesh> sphereMesh = GET_SINGLE(Resources)->LoadCubeMesh();
            meshRenderer->SetMesh(sphereMesh);
        }
        {
            shared_ptr<Shader> shader = make_shared<Shader>();
            shared_ptr<Texture> texture = make_shared<Texture>();
            shared_ptr<Texture> texture2 = make_shared<Texture>();
            shader->Init(L"..\\Resources\\Shader\\default.hlsli");
            texture->Init(L"..\\Resources\\Texture\\Metal.jpg");
            texture2->Init(L"..\\Resources\\Texture\\Metal_Normal.jpg");
            shared_ptr<Material> material = make_shared<Material>();
            material->SetShader(shader);
            material->SetTexture(0, texture);
            material->SetTexture(1, texture2);
            meshRenderer->SetMaterial(material);
        }
        sphere->AddComponent(meshRenderer);
        scene->AddGameObject(sphere);
    }
#pragma endregion
 
#pragma region Green Directional Light
    {
        shared_ptr<GameObject> light = make_shared<GameObject>();
        light->AddComponent(make_shared<Transform>());
        //light->GetTransform()->SetLocalPosition(Vec3(0.f, 150.f, 150.f));
        light->AddComponent(make_shared<Light>());
        light->GetLight()->SetLightDirection(Vec3(1.f, 0.f, 1.f));
        light->GetLight()->SetLightType(LIGHT_TYPE::DIRECTIONAL_LIGHT);
        light->GetLight()->SetDiffuse(Vec3(0.5f, 0.5f, 0.5f));
        light->GetLight()->SetAmbient(Vec3(0.1f, 0.1f, 0.1f));
        light->GetLight()->SetSpecular(Vec3(0.3f, 0.3f, 0.3f));
 
        scene->AddGameObject(light);
    }
 
#pragma endregion
 
    return scene;
}
cs

 

 

 

  • SkyBox를 생성하는 부분이 추가되었다.
  • Sphere (구체)로 SkyBox를 생성하였고 shader를 Init 할 때
    RASTERIZER_TYPE::CULL_NONE, DEPTH_STENCIL_TYPE::LESS_EQUAL를 인수로 넘겨줌으로써 래스터라이저와 뎁스스텐실 타입을 변경하였다.

 

 

 


 

 

결과

 

 

Skybox가 부착된 결과물

 

  • 여태 배경화면이 그저 검은색이었던 것과 달리 물체의 뒤가 밤하늘과 같은 배경이 되었음을 볼 수 있다.

+ Recent posts