Ambient, Diffuse, Specular, Emissive를 합쳐서 만드는 코드를 작업한다.
1. 13. Lighting.fx, 16. LightingDemo 클래스를 만들고 각각 LightingDemo::Init, Main에서 세팅하기
Shaders폴더에 12. Lighting_Emissive.fx를 복제해서 이름을 13. Lighting.fx로 한다.
Client/Shaders/Week2에 넣는다.
모든 라이팅을 합쳐서 작업할 Light.fx라는 쉐이더를 만들 것이다.
Client폴더의 15. EmissiveDemo클래스를 복제해서 16. LightingDemo 클래스를 만든다.
코드를 수정한다.
void LightingDemo::Init()
{
RESOURCES->Init();
_shader = make_shared<Shader>(L"13. Lighting.fx");
Main에서
desc.app = make_shared<LightingDemo>(); // 실행 단위
여기까지 바꿔주면 된다.
빌드해서 별 다른 문제가 없는지 확인한다.
2. Lighting의 공용부를 담을 00. Light.fx 만들기
4가지를 한 번에 통합해서 뭔가를 만들어 줘도 되겠지만, Amibient, Diffuse, Specular, Emissive를 합친 all in one 같은 느낌은 공용 쉐이더에서 이 기능을 많이 사용할 것이다. 가장 표준적인 방법이라고 할 수 있다.
1) 00. Light.fx 파일 생성하기
Global 쉐이더를 만든 거처럼 공용적으로 활용할 수 있는 무엇인가를 만들면 된다는 얘기고,
그러기 위해서 Common 필터의 00. Global.fx과 비슷한 느낌으로 라이팅과 관련된 쉐이더를 하나 추가해서 관리를 해보도록 한다. 00. Global.fx를 복제해서 이름을 00. Light.fx라 하고 Client/Shaders/Common필터에 넣는다.
2) 중복 header 가드
#ifndef _LIGHT_FX_
#define _LIGHT_FX_
#include "00. Global.fx"
#endif
내용을 지우고 header가드를 이렇게 수정하고 시작을 해본다.
3) struct LightDesc, struct MaterialDesc 정의하기
각 쉐이더 마다 Light와 Material 변수를 선언했었는데 그런 걸 통합해서 관리할 수 있다.
////////////
// Struct //
////////////
struct LightDesc
{
float4 ambient;
float4 diffuse;
float4 specular;
float4 emissive;
float3 direction;
float padding;
};
struct MaterialDesc
{
float4 ambient;
float4 diffuse;
float4 specular;
float4 emissive;
};
16바이트 정렬은 필수는 아니지만 나중에 PointLight나 다른 라이트 만들어 줄 때 조명이 여러 개 등장할 수 있다. 그럴 때 상수 버퍼에 배열 형태로 넘겨주게 될 텐데 반드시 16바이트 정렬을 지켜주지 않으면 데이터가 깨져 올 수 있기 때문에 습관을 들여놓는 게 좋다.
4) cbuffer LightBufffer, cbuffer MaterialBuffer 정의하기
////////////////////
// ConstantBuffer //
////////////////////
cbuffer LightBuffer
{
LightDesc GlobalLight;
};
cbuffer MaterialBuffer
{
MaterialDesc Material;
};
각 Shader에서 선언했던 Light, Material 등등을 세트로 묶어서 이렇게 2개의 벞로 만들어 준 것이다.
cbuffer를 2개로 분리한 이유는 Global에서와 마찬가지로 하나를 고치면 세트로 영향을 받기 때문에 성능을 살리기 위해 각각 따로 세팅할 수 있게 했다.
GlobalLight는 한 번만 설정하면 될 것이고
Material은 물체마다 다를 수 있기 때문에 그래서 구분을 했다.
5) SRV Texture2D DiffuseMap, SpecularMap, NormalMap
그다음은 SRV 텍스쳐와 관련된 아이들이 등장해야 하는데
텍스쳐 중에서 가장 대표적으로 사용했던 아이가 DiffuseMap이다.
/////////
// SRV //
/////////
Texture2D DiffuseMap;
Texture2D SpecularMap;
Texture2D NormalMap;
NormalMap은 다다음 시간에 본격적으로 따로 공부하게 될 것이다.
이렇게 기본적인 준비가 끝났다.
6) Lighting.fx 의 PS에서 4개의 Light를 통합한 color 값을 계산하는 ComputeLight 함수를 00. Light.fx에 정의하기
Lighting.fx를 열어서 몇 가지 작업을 해 볼 것이다.
float4 MaterialEmissive;
를 삭제하고
#include "00. Light.fx"
이걸 추가한다.
VS 바꿀게 없고,
PS는 일단 내용을 지우고 시작을 한다.
float4 PS(MeshOutput input) : SV_TARGET
{
float4 color = ComputeLight(input.normal, input.uv, input.worldPosition);
return color;
}
필요한 정보를 넣어서 ComputeLight함수로 연산을 할 것이고, ComputeLight는 00. Light.fx에서 정의한다.
//////////////
// Function //
//////////////
float4 ComputeLight(float3 normal, float2 uv, float3 worldPosition)
{
float4 ambientColor = 0;
float4 diffuseColor = 0;
float4 specularColor = 0;
float4 emissiveColor = 0;
// Ambient
{
float4 color = GlobalLight.ambient * Material.ambient;
//return color;
ambientColor = DiffuseMap.Sample(LinearSampler, uv) * color;
}
// Diffuse
{
float4 color = DiffuseMap.Sample(LinearSampler, uv);
float value = dot(-GlobalLight.direction, normalize(normal));
diffuseColor = color * value * GlobalLight.diffuse * Material.diffuse;
}
// Specular
{
// float3 R = reflect(GlobalLight.direction, normal);
float3 R = GlobalLight.direction - (2 * normal * dot(GlobalLight.direction, normal));
R = normalize(R);
float3 cameraPosition = CameraPosition(); // 아니면 View의 역행렬을 한 다음에 _41_42_43
float3 E = normalize(cameraPosition - worldPosition);
float value = saturate(dot(R, E)); // clamp(0~1)
float specular = pow(value, 10); // 제곱을 안하면 조금 더 영역이 넓게 되고, 좁힐 수록 효과가 커진다.
specularColor = GlobalLight.specular * Material.specular * specular;
}
// Emissive
{
float3 cameraPosition = CameraPosition();
float3 E = normalize(cameraPosition - worldPosition);
float value = saturate(dot(E, normal)); // Specular에서는 R고 E를 dot 해줬지만, 여기선 E와 normal을 해준다.
float emissive = 1.0f - value; // 1은 0, 0은 1로 바꿔준다.
// min, max, x
emissive = smoothstep(0.0f, 1.0f, emissive); // emissive가 0보다 작으면 0, 1보다 크면 1을 반환
emissive = pow(emissive, 2);
emissiveColor = GlobalLight.emissive * Material.emissive * emissive;
}
return ambientColor + diffuseColor + specularColor + emissiveColor;
}
ComputeLight에서 ambientColor, diffuseColor, specularColor, emissiveColor를 각 각 구해서 더해준 다음 return 해 주었다.
원래 float3 cameraPosition = -V._41_42_43;을 CameraPosition() 이란 함수를 이용해 구할 수 있게 하기 위해
00.Global.fx에
//////////////
// Function //
//////////////
float3 CameraPosition()
{
return -V._41_42_43;
}
이렇게 추가했다.
Lighting.fx의 VS에서
output.worldPosition = input.position.xyz;
이렇게 position의 xyz를 넣도록 xyz를 써준다.
그럼 warning이 줄어든다.
3. RenderManager에서 00. Light.fx의 cbuffer에 정보를 넣는 코드를 만들기
1) RenderManager.h에 struct LightDesc, struct MaterialDesc 정의, 초기화 하기
이제 00. Light.fx의 cbuffer에 정보를 넣어야 하는데 그걸 편리하게 하기 위해 RenderManager라는 걸 만들어 줬고,
RenderManager에 가서 방금 추가한 Lighting과 관련된 부분들을 만들어 주면 된다.
// Light
struct LightDesc
{
Color ambient = Color(1.f, 1.f, 1.f, 1.f);
Color diffuse = Color(1.f, 1.f, 1.f, 1.f);
Color specular = Color(1.f, 1.f, 1.f, 1.f);
Color emissive = Color(1.f, 1.f, 1.f, 1.f);
Vec3 direction;
float padding0;
};
struct MaterialDesc
{
Color ambient = Color(0.f, 0.f, 0.f, 1.f);
Color diffuse = Color(1.f, 1.f, 1.f, 1.f);
Color specular = Color(0.f, 0.f, 0.f, 1.f);
Color emissive = Color(0.f, 0.f, 0.f, 1.f);
};
2) PushLightData, PushMaterialData 함수 추가하기
그리고 class RenderManager에
void PushLightData(const LightDesc& desc);
void PushMaterialData(const MaterialDesc& desc);
Push 함수 2개를 넣는다.
3) LightDesc와 MaterialDesc 데이터를 담을 ConstantBuffer를 생성하고 쉐이더의 변수와 연결하기 위한 변수들을 선언하기
ightDesc와 MaterialDesc와 관련된 ConstantBuffer를 생성하는 부분까지
LightDesc _lightDesc;
shared_ptr<ConstantBuffer<LightDesc>> _lightBuffer;
ComPtr<ID3DX11EffectConstantBuffer> _lightEffectBuffer;
MaterialDesc _materialDesc;
shared_ptr<ConstantBuffer<MaterialDesc>> _materialBuffer;
ComPtr<ID3DX11EffectConstantBuffer> _materialEffectBuffer;
이렇게 넣어준다.
4) RenderManager::Init 에서 _lightBuffer와 _materialBuffer를 생성하고 _lightEffectBuffer와 _materialEffectBuffer를 셰이더 파일의 LightBuffer와 MaterialBuffer와 각각 연결하기
그리고 RenderManager::Init에서 방금 선언한 버퍼를 만들어주는 코드를 넣는다.
_lightBuffer = make_shared<ConstantBuffer<LightDesc>>();
_lightBuffer->Create();
_lightEffectBuffer = _shader->GetConstantBuffer("LightBuffer");
_materialBuffer = make_shared<ConstantBuffer<MaterialDesc>>();
_materialBuffer->Create();
_materialEffectBuffer = _shader->GetConstantBuffer("MaterialBuffer");
여기에 넣은 “LightBuffer”, “MaterialBuffer”는 00. Light.fx에서 만들어줬던 cbuffer LightBuffer, cbuffer MaterialBuffer에 맞춰준다고 보면 된다.
5) ConstantBuffer에 desc데이터를 복사하고 그 ConstantBuffer를 셰이더의 cbuffer 변수와 연결된 EffectBuffer에 세팅해서 쉐이더의 cbuffer 변수에 데이터를 넣어주는 PushLightData, PushMaterialData 함수를 정의하기
그리고 RenderManager.cpp에
void RenderManager::PushLightData(const LightDesc& desc)
{
_lightDesc = desc;
_lightBuffer->CopyData(_lightDesc);
_lightEffectBuffer->SetConstantBuffer(_lightBuffer->GetComPtr().Get());
}
void RenderManager::PushMaterialData(const MaterialDesc& desc)
{
_materialDesc = desc;
_materialBuffer->CopyData(_materialDesc);
_materialEffectBuffer->SetConstantBuffer(_materialBuffer->GetComPtr().Get());
}
를 넣어주면
기존의 GlobalBuffer를 만들 때랑 동일한 방식으로 만들었다.
이렇게 Light와 Material에 정보를 밀어 넣어줄 준비가 끝났다.
Engine을 빌드하면 잘 된다.
쉐이더도 기본적으로 준비가 되었고, LieghtingDemo에서 관련된 부분들을 설정할 일만 남았다.
4. LightingDemo::Update에서 PushLightData, PushMaterialData 해주기
1) LightDesc를 세팅 후 PushLightData 호출 하기
{
LightDesc lightDesc;
lightDesc.ambient = Vec4(0.5f);
lightDesc.diffuse = Vec4(1.f);
lightDesc.specular = Vec4(1.f, 1.f, 1.f, 1.f);
lightDesc.direction = Vec3(0.f, -1.f, 0.f);
RENDER->PushLightData(lightDesc);
}
light와 관련된 부분을 설정을 해주면 된다.
lightDesc에 기본값을 넣어 놨다. 툴을 이용하면 편리하게 작업할 수 있을 거다. 다음에 IMGUI를 사용하면 더 편리하게 테스트할 수 있다.
나중엔 Light Component에 의해 조절이 될 것이다. 지금은 일단 이렇게 한다.
2) 구와 큐브의 MaterialDesc를 세팅 후 PushMaterialData 해주기
MaterialDesc을 설정하고
{
MaterialDesc desc;
desc.ambient = Vec4(0.2f);
desc.diffuse = Vec4(1.f);
desc.specular = Vec4(1.f);
//desc.emissive = Color(0.3f, 0.f, 0.f, 0.5f);
RENDER->PushMaterialData(desc);
_obj->Update();
}
PushMaterialData를 하면 _materialBuffer에 desc의 내용을 copyData하고, Shader의 LightBuffer와 연동되어 있는 _lightEffectBuffer에 _materialBuffer가 세팅된다.
Material을 다음 시간에 만들건데 보통 MeshRenderer 쪽에 붙는 경우가 흔하다.
{
MaterialDesc desc;
//desc.ambient = Vec4(0.5f);
desc.diffuse = Vec4(1.f);
//desc.specular = Color(0.5f, 0.5f, 0.5f, 1.f);
//desc.emissive = Color(1.f, 0.f, 0.f, 1.f);
RENDER->PushMaterialData(desc);
_obj2->Update();
}
_obj2도 얘만의 정보를 기입해서 적어주면 된다.
주석처리 된 부분이 있는데 이것저것 켜 보면서 실습을 하면 된다.
5. 테스트하기
실행을 해보면
1) 큐브가 desc.ambient가 Vec(0.f, 0.f, 0.f, 1.f); 기본값 일 때
위에서 보면 큐브도 위에서 불빛이 비춰지는 부분만 보인다는 것을 알 수 있다.
여기서 여러가지 테스트를 해보면 된다.
void LightingDemo::Update()
{
_camera->Update();
RENDER->Update();
{
LightDesc lightDesc;
lightDesc.ambient = Vec4(0.5f);
lightDesc.diffuse = Vec4(1.f);
lightDesc.specular = Vec4(1.f, 1.f, 1.f, 1.f);
lightDesc.direction = Vec3(0.f, -1.f, 0.f);
RENDER->PushLightData(lightDesc);
}
{
MaterialDesc desc;
desc.ambient = Vec4(0.2f);
desc.diffuse = Vec4(1.f);
desc.specular = Vec4(1.f);
//desc.emissive = Color(0.3f, 0.f, 0.f, 0.5f);
RENDER->PushMaterialData(desc);
_obj->Update();
}
{
MaterialDesc desc;
desc.ambient = Vec4(0.5f);
desc.diffuse = Vec4(1.f);
//desc.specular = Color(0.5f, 0.5f, 0.5f, 1.f);
//desc.emissive = Color(1.f, 0.f, 0.f, 1.f);
RENDER->PushMaterialData(desc);
_obj2->Update();
}
}
2) 큐브 desc.ambient = Vec4(0.5f); 일 때
_obj2도 ambient를 조금 더 강하게 한다고 하면
desc.ambient = Vec4(0.5f);
아까완 달리 빛이 정면으로 비치지 않는 부분이라 해도 큐브가 잘 보이는 것을 알 수 있다.
은은하게 보이는 부분은 Ambient로 만들고,
일반적인 부분은 Diffuse로,
눈뽕 효과를 Specular로 만든다 라고 결론을 낼 수 있다.
6. 맺음말
따로따로 빛을 이해 했으면 3가지의 빛의 연산을 합쳐서 하는 것도 크게 어렵지 않다.
합칠 때는 모든 값들을 싸그리 더해 가지고 만들면 된다.
각각의 특성들을 다 구해준 다음에 싹 더해줘서 최종적인 빛 연산을 완성했다.
'DirectX' 카테고리의 다른 글
50. Light, Material_Normal Mapping (0) | 2024.02.17 |
---|---|
49. Light, Material_Material (0) | 2024.02.16 |
47. Light, Material_Emissive (0) | 2024.02.14 |
46. Light, Material_Specular (0) | 2024.02.13 |
45. Light, Material_Diffuse (0) | 2024.02.13 |
댓글