Grahpics
[Graphics] DirectX12 - Lighting
송만덕
2022. 2. 7. 05:05
Lighting
- Lighting 개요의 내용을 실습.
- 빛의 종류인 Diffuse, Ambient, Specular에 대해서 구현을 해보며
- 조명의 종류인 Dirctional Light, Point Light, Spot Light에 대해서도 구현한다.
- 또한 셰이더 프로그래밍을 위한 고정적인 자원의 저장을 위해 ConstantBuffer와 TableDescripotrHeap 등의 구조를 변경 하며
- Pixel Shader를 통해서 조명에 대한 연산을 수행한다.
Transform.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
|
#include "pch.h"
#include "Transform.h"
#include "Engine.h"
#include "Camera.h"
Transform::Transform() : Component(COMPONENT_TYPE::TRANSFORM)
{
}
Transform::~Transform()
{
}
void Transform::FinalUpdate()
{
Matrix matScale = Matrix::CreateScale(_localScale);
Matrix matRotation = Matrix::CreateRotationX(_localRotation.x);
matRotation *= Matrix::CreateRotationY(_localRotation.y);
matRotation *= Matrix::CreateRotationZ(_localRotation.z);
Matrix matTranslation = Matrix::CreateTranslation(_localPosition);
_matLocal = matScale * matRotation * matTranslation;
_matWorld = _matLocal;
shared_ptr<Transform> parent = GetParent().lock();
if (parent != nullptr)
{
_matWorld *= parent->GetLocalToWorldMatrix();
}
}
void Transform::PushData()
{
TransformParams transformParams = {};
transformParams.matWorld = _matWorld;
transformParams.matView = Camera::S_MatView;
transformParams.matProjection = Camera::S_MatProjection;
transformParams.matWV = _matWorld * Camera::S_MatView;
transformParams.matWVP = _matWorld * Camera::S_MatView * Camera::S_MatProjection;
CONST_BUFFER(CONSTANT_BUFFER_TYPE::TRANSFORM)->PushData(&transformParams, sizeof(transformParams));
}
|
cs |
- PushData()에서 기존엔 변환을 위한 matWVP만 따로 상수버퍼로써 넘겨주었지만
- 이제는 셰이더의 연산에서 필요한 World, View, Projection, WV, WVP를 모두 넘겨준다.
Light.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
50
51
52
53
54
55
56
57
58
59
60
61
|
#pragma once
#include "Component.h"
enum class LIGHT_TYPE : uint8
{
DIRECTIONAL_LIGHT,
POINT_LIGHT,
SPOT_LIGHT,
};
struct LightColor
{
Vec4 diffuse;
Vec4 ambient;
Vec4 specular;
};
struct LightInfo
{
LightColor color;
Vec4 position;
Vec4 direction;
int32 lightType;
float range;
float angle;
int32 padding;
};
struct LightParams
{
uint32 lightCount;
Vec3 padding;
LightInfo lights[50];
};
class Light : public Component
{
public:
Light();
virtual ~Light();
virtual void FinalUpdate() override;
public:
const LightInfo& GetLightInfo() { return _lightInfo; }
void SetLightDirection(const Vec3& direction) { _lightInfo.direction = direction; }
void SetDiffuse(const Vec3& diffuse) { _lightInfo.color.diffuse = diffuse; }
void SetAmbient(const Vec3& ambient) { _lightInfo.color.ambient = ambient; }
void SetSpecular(const Vec3& specular) { _lightInfo.color.specular = specular; }
void SetLightType(LIGHT_TYPE type) { _lightInfo.lightType = static_cast<int32>(type); }
void SetLightRange(float range) { _lightInfo.range = range; }
void SetLightAngle(float angle) { _lightInfo.angle = angle; }
private:
LightInfo _lightInfo = {};
};
|
cs |
- Type을 지정하기 위한 LIGHT_TYPE, 각 빛의 수치를 위한 LightColor, 조명의 정보를 위한 LightInfo, 셰이더의 b0 register에 넣어주기 위한 LightParams가 존재한다.
- 이후 나머지의 함수들은 빛이나 조명의 정보들을 Get과 Set 하는 함수이며
- FinalUpdate()는 조명의 WorldPosition을 받아와주는 역할을 한다.
Scene.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
|
#include "pch.h"
#include "Scene.h"
#include "GameObject.h"
#include "Camera.h"
#include "Engine.h"
#include "ConstantBuffer.h"
#include "Light.h"
void Scene::Awake()
{
for (const shared_ptr<GameObject>& gameObject : _gameObjects)
{
gameObject->Awake();
}
}
void Scene::Start()
{
for (const shared_ptr<GameObject>& gameObject : _gameObjects)
{
gameObject->Start();
}
}
void Scene::Update()
{
for (const shared_ptr<GameObject>& gameObject : _gameObjects)
{
gameObject->Update();
}
}
void Scene::LateUpdate()
{
for (const shared_ptr<GameObject>& gameObject : _gameObjects)
{
gameObject->LateUpdate();
}
}
void Scene::FinalUpdate()
{
for (const shared_ptr<GameObject>& gameObject : _gameObjects)
{
gameObject->FinalUpdate();
}
}
void Scene::Render()
{
PushLightData();
for (auto& gameObject : _gameObjects)
{
if (gameObject->GetCamera() == nullptr)
continue;
gameObject->GetCamera()->Render();
}
}
void Scene::PushLightData()
{
LightParams lightParams = {};
for (auto& gameObject : _gameObjects)
{
if (gameObject->GetLight() == nullptr)
continue;
const LightInfo& lightInfo = gameObject->GetLight()->GetLightInfo();
lightParams.lights[lightParams.lightCount] = lightInfo;
lightParams.lightCount++;
}
CONST_BUFFER(CONSTANT_BUFFER_TYPE::GLOBAL)->SetGlobalData(&lightParams, sizeof(lightParams));
}
void Scene::AddGameObject(shared_ptr<GameObject> gameObject)
{
_gameObjects.push_back(gameObject);
}
void Scene::RemoveGameObject(shared_ptr<GameObject> gameObject)
{
auto findIt = std::find(_gameObjects.begin(), _gameObjects.end(), gameObject);
if (findIt != _gameObjects.end())
{
_gameObjects.erase(findIt);
}
}
|
cs |
- SceneManager에서 하던 Render를 Scene이 맡아서 하게 되었다.
- Render를 실행하면서 PushLightData()도 함께해주는데, PushLightData()는 생성한 조명들의 정보를 Params에 넣어준 뒤 상수버퍼로 사용하기 위해 b0레지스터에 넘겨주는 작업을 한다.
dafault shader
더보기
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
|
#ifndef _DEFAULT_HLSLI_
#define _DEFAULT_HLSLI_
#include "params.hlsli"
#include "utils.hlsli"
struct VS_IN
{
float3 pos : POSITION;
float2 uv : TEXCOORD;
float3 normal : NORMAL;
};
struct VS_OUT
{
float4 pos : SV_Position;
float2 uv : TEXCOORD;
float3 viewPos : POSITION;
float3 viewNormal : NORMAL;
};
VS_OUT VS_Main(VS_IN input)
{
VS_OUT output = (VS_OUT)0;
output.pos = mul(float4(input.pos, 1.f), g_matWVP);
output.uv = input.uv;
output.viewPos = mul(float4(input.pos, 1.f), g_matWV).xyz;
output.viewNormal = normalize(mul(float4(input.normal, 0.f), g_matWV).xyz);
return output;
}
float4 PS_Main(VS_OUT input) : SV_Target
{
//float4 color = g_tex_0.Sample(g_sam_0, input.uv);
float4 color = float4(1.f, 1.f, 1.f, 1.f);
LightColor totalColor = (LightColor)0.f;
for (int i = 0; i < g_lightCount; ++i)
{
LightColor color = CalculateLightColor(i, input.viewNormal, input.viewPos);
totalColor.diffuse += color.diffuse;
totalColor.ambient += color.ambient;
totalColor.specular += color.specular;
}
color.xyz = (totalColor.diffuse.xyz * color.xyz)
+ totalColor.ambient.xyz * color.xyz
+ totalColor.specular.xyz;
return color;
}
#endif
|
cs |
- VS_Main() = 빛연산을 위해 투영 좌표계가 아닌 WV만 곱해진 ViewSpace에서의 Pos를 구해주며 ViewSpace에서의 법선벡터를 정규화 하여 구해주는 모습을 볼 수 있다.
- PS_Main() = 조명의 갯수만큼 반복을 하며 각 조명의 빛의 수치만큼 더해준 뒤 합쳐진 빛을 픽셀의 color에 연산을 해주며 return 해주는 방식이다.
- 또 params.hlsli를 include 해주는 모습이 보이는데, 이는 register 등 global 자원등을 따로 관리해주기 위한 셰이더 파일이다.
util shader
더보기
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
|
#ifndef _UTILS_HLSLI_
#define _UTILS_HLSLI_
LightColor CalculateLightColor(int lightIndex, float3 viewNormal, float3 viewPos)
{
LightColor color = (LightColor)0.f;
float3 viewLightDir = (float3)0.f;
float diffuseRatio = 0.f;
float specularRatio = 0.f;
float distanceRatio = 1.f;
if (g_light[lightIndex].lightType == 0)
{
// Directional Light
viewLightDir = normalize(mul(float4(g_light[lightIndex].direction.xyz, 0.f), g_matView).xyz);
diffuseRatio = saturate(dot(-viewLightDir, viewNormal));
}
else if (g_light[lightIndex].lightType == 1)
{
// Point Light
float3 viewLightPos = mul(float4(g_light[lightIndex].position.xyz, 1.f), g_matView).xyz;
viewLightDir = normalize(viewPos - viewLightPos);
diffuseRatio = saturate(dot(-viewLightDir, viewNormal));
float dist = distance(viewPos, viewLightPos);
if (g_light[lightIndex].range == 0.f)
distanceRatio = 0.f;
else
distanceRatio = saturate(1.f - pow(dist / g_light[lightIndex].range, 2));
}
else
{
// Spot Light
float3 viewLightPos = mul(float4(g_light[lightIndex].position.xyz, 1.f), g_matView).xyz;
viewLightDir = normalize(viewPos - viewLightPos);
diffuseRatio = saturate(dot(-viewLightDir, viewNormal));
if (g_light[lightIndex].range == 0.f)
distanceRatio = 0.f;
else
{
float halfAngle = g_light[lightIndex].angle / 2;
float3 viewLightVec = viewPos - viewLightPos;
float3 viewCenterLightDir = normalize(mul(float4(g_light[lightIndex].direction.xyz, 0.f), g_matView).xyz);
float centerDist = dot(viewLightVec, viewCenterLightDir);
distanceRatio = saturate(1.f - centerDist / g_light[lightIndex].range);
float lightAngle = acos(dot(normalize(viewLightVec), viewCenterLightDir));
if (centerDist < 0.f || centerDist > g_light[lightIndex].range) // 최대 거리를 벗어났는지
distanceRatio = 0.f;
else if (lightAngle > halfAngle) // 최대 시야각을 벗어났는지
distanceRatio = 0.f;
else // 거리에 따라 적절히 세기를 조절
distanceRatio = saturate(1.f - pow(centerDist / g_light[lightIndex].range, 2));
}
}
float3 reflectionDir = normalize(viewLightDir + 2 * (saturate(dot(-viewLightDir, viewNormal)) * viewNormal));
float3 eyeDir = normalize(viewPos);
specularRatio = saturate(dot(-eyeDir, reflectionDir));
specularRatio = pow(specularRatio, 2);
color.diffuse = g_light[lightIndex].color.diffuse * diffuseRatio * distanceRatio;
color.ambient = g_light[lightIndex].color.ambient * distanceRatio;
color.specular = g_light[lightIndex].color.specular * specularRatio * distanceRatio;
return color;
}
#endif
|
cs |
- CalculateLightColor() = 각 조명의 Type에 따라 diffuseRatio, distanceRatio 등을 계산해주며 이후 공통적으로 반사광과 시각벡터의 내적을 통해 specularRatio를 구해준 뒤 결과 수치에 따라서 Diffuse, Ambient, Specular 값을 설정해 최종 color를 반환해준다.
- Directional Light = Dirction을 ViewSpace로 이동시켜준 뒤 정규화 시켜준 것을 viewLightDir로 설정해주며, viewLightDir의 역방향과 viewNormal을 내적해준 값을 0~1로 보정해준 뒤 diffuseRatio를 설정해준다.
- Point Light = Position을 ViewSpace로 변환시켜준 것을 viewLightPos로, 물체의 픽셀과 조명의 위치를 뺀 것을 viewLightDir로, 마지막으로 viewLightDir의 역방향과 viewNormal을 내적해준 값을 0~1로 보정해준 뒤 diffuseRatio를 설정해준 뒤 픽셀과 조명 위치의 거리에 따라서 distanceRatio를 설정해준다.
- Spot Light = viewLightPos, viewLightDir, diffuseRatio를 구하는 방법은 Point Light와 동일하지만 물체와 조명의 거리나 각도에 따라서 조명이 닿는지 닿지 않는지를 판별하여 거리에 알맞는 distanceRatio를 설정해준다.
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
130
131
132
133
134
135
|
#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 Sphere
{
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)->LoadSphereMesh();
meshRenderer->SetMesh(sphereMesh);
}
{
shared_ptr<Shader> shader = make_shared<Shader>();
shared_ptr<Texture> texture = make_shared<Texture>();
shader->Init(L"..\\Resources\\Shader\\default.hlsli");
texture->Init(L"..\\Resources\\Texture\\Manduk.png");
shared_ptr<Material> material = make_shared<Material>();
material->SetShader(shader);
material->SetTexture(0, texture);
meshRenderer->SetMaterial(material);
}
sphere->AddComponent(meshRenderer);
scene->AddGameObject(sphere);
}
#pragma endregion
#pragma region 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, -1.f, 1.f));
light->GetLight()->SetLightType(LIGHT_TYPE::DIRECTIONAL_LIGHT);
light->GetLight()->SetDiffuse(Vec3(1.f, 1.f, 1.f));
light->GetLight()->SetAmbient(Vec3(0.1f, 0.1f, 0.1f));
light->GetLight()->SetSpecular(Vec3(0.1f, 0.1f, 0.1f));
scene->AddGameObject(light);
}
#pragma endregion
#pragma region Point Light
{
shared_ptr<GameObject> light = make_shared<GameObject>();
light->AddComponent(make_shared<Transform>());
light->GetTransform()->SetLocalPosition(Vec3(150.f, 150.f, -150.f));
light->AddComponent(make_shared<Light>());
//light->GetLight()->SetLightDirection(Vec3(0.f, -1.f, 0.f));
light->GetLight()->SetLightType(LIGHT_TYPE::POINT_LIGHT);
light->GetLight()->SetDiffuse(Vec3(1.f, 0.1f, 0.1f));
light->GetLight()->SetAmbient(Vec3(0.1f, 0.f, 0.f));
light->GetLight()->SetSpecular(Vec3(0.15f, 0.15f, 0.15f));
light->GetLight()->SetLightRange(400.f);
//light->GetLight()->SetLightAngle(XM_PI / 4);
scene->AddGameObject(light);
}
#pragma endregion
#pragma region Spot Light
{
shared_ptr<GameObject> light = make_shared<GameObject>();
light->AddComponent(make_shared<Transform>());
light->GetTransform()->SetLocalPosition(Vec3(-150.f, 0.f, 150.f));
light->AddComponent(make_shared<Light>());
light->GetLight()->SetLightDirection(Vec3(1.f, 0.f, 0.f));
light->GetLight()->SetLightType(LIGHT_TYPE::SPOT_LIGHT);
light->GetLight()->SetDiffuse(Vec3(0.f, 0.1f, 1.f));
//light->GetLight()->SetAmbient(Vec3(0.f, 0.f, 0.1f));
light->GetLight()->SetSpecular(Vec3(0.1f, 0.1f, 0.1f));
light->GetLight()->SetLightRange(10000.f);
light->GetLight()->SetLightAngle(XM_PI / 3);
scene->AddGameObject(light);
}
#pragma endregion
return scene;
}
|
cs |
- 조명을 받기 위한 구체 하나와 종류별의 조명을 1개씩 생성해주어 Scene에 넣어주는 모습을 볼 수 있다.
- 구체의 위치 = (0, 0, 150)
- Directional Light = 방향 (1, -1, 1), Diffuse (1, 1, 1), Ambient (0.1, 0.1, 0.1), Specular (0.1, 0.1, 0.1)
- Point Light = 위치 (150, 150, -150), Diffuse (1, 0.1, 0.1), Ambient (0.1, 0, 0), Specular (0.15, 0.15, 0.15), 범위 (400)
- Spot Light = 위치 (-150, 0, 150), 방향 (1,0,0), Diffuse (0.1, 0.1, 1), Specular (0.1, 0.1, 0.1), 범위 (10000), 각도 (60)
결과
Directional Light
- Directional Light는 조명의 Position에 관계 없이 (1, -1, 1) 방향으로 흰색 빛을 비추며 그에 따라서 각 Ambient, Diffuse, Specular의 모습을 확인할 수 있다.
- 구체의 위치는 (0, 0, 150)이다.
Point Light
- (150, 150, -150) 위치에 존재하는 400.f 범위의 적색 Point Light가 구를 비추어 빨갛게 보이며, 각 Ambient, Diffuse, Specular의 모습을 확인할 수 있다.
- Point Light는 방향에 관계없이 범위 내에 존재하면 모두 빛이 비춰지며 완전히 검게 보이는 부분은 Point Light의 범위가 닿지 않아 Ambient의 최솟값이 보장되지 않았기 때문이다.
Spot Light
- Spot Light는 (-150, 0, 150)좌표에서 오른쪽을 향해 60도 각도로 비추게 하였다.
- 그에 따라 구체는 좌측에만 빛을 받아 파랗게 보이는 것을 확인할 수 있으며, 가장 가까운 부분은 가장 밝고 거리가 벌어질수록 어두워지며 환경광이 존재하지 않아 빛이 닿지 않는 부분은 전혀 보이지 않는것을 볼 수 있다.
셋을 조합하면 이와 같은 모습이 된다.