[목차]
- 라이트 추가
- 세팅
- 업데이트
- 결과
[라이트 추가]
라이트는 그래픽스에서 가장 기본적이지만 중요하고, 그래픽스를 배울 때 처음으로 접하는 부분이 아닐까 생각됩니다. 실제로 구현해보기 전 개념을 먼저 익혔고, 프레임워크에 추가했습니다.
https://umtimos.tistory.com/199
[Graphics] Lighting
[Lighting의 필요성] 그래픽스에서 라이팅(Lighting)은 화면의 분위기, 깊이감, 사실감을 결정짓는 중요한 요소입니다. 빛 처리가 없으면 아무리 3D 물체라고 해도 평면처럼 보이게 됩니다. 위의 예시
umtimos.tistory.com
[세팅]
본격적으로 픽셀 셰이더를 활용해야하는 단계입니다. 픽셀 셰이더를 위한 상수버퍼와 Light라는 개념을 정의하기 위한 Ext_Light 클래스를 추가했습니다.
////////////////////////////// Ext_Actor.h
#pragma once
#include "Ext_Actor.h"
enum class LightType
{
Directional,
Point,
Spot,
Unknown
};
struct LightData
{
float4 LightColor = { 1.0f, 1.0f, 1.0f, 0.25f };// RGB(색), w(강도)
float4 LightWorldPosition; // 라이트 월드 공간 위치
float4 LightForwardVector; // 월드 입사 벡터
float4 CameraWorldPosition; // 시점 월드 공간 위치
float NearDistance = 1.0f;
float FarDistance = 100.0f;
float AttenuationValue = 1.0f; // 거리감쇠값에 사용
int LightType = 0;
bool bIsLightSet = false;
};
constexpr unsigned int MAX_LIGHTS = 64;
struct LightDatas
{
int LightCount = 0;
LightData Lights[MAX_LIGHTS];
};
class Ext_Light : public Ext_Actor
{
public:
// constrcuter destructer
Ext_Light();
~Ext_Light() {};
// delete Function
Ext_Light(const Ext_Light& _Other) = delete;
Ext_Light(Ext_Light&& _Other) noexcept = delete;
Ext_Light& operator=(const Ext_Light& _Other) = delete;
Ext_Light& operator=(Ext_Light&& _Other) noexcept = delete;
void LightUpdate(std::shared_ptr<class Ext_Camera> _Camera, float _DeltaTime);
void SetLightType(LightType _Type) { LTData->LightType = static_cast<int>(_Type); };
void SetLightColor(float4 _Color) { LTData->LightColor = _Color; };
void SetAttenuationValue(float _Value) { LTData->AttenuationValue = _Value; };
void SetLightRange(float _Range)
{
LTData->NearDistance = 1.0f;
LTData->FarDistance = _Range;
}
std::shared_ptr<LightData> GetLightData() { return LTData; }
protected:
private:
std::shared_ptr<LightData> LTData = nullptr;
LightType Type = LightType::Unknown;
};
////////////////////////////// Ext_Actor.cpp
#include "PrecompileHeader.h"
#include "Ext_Light.h"
#include "Ext_Camera.h"
#include "Ext_Transform.h"
Ext_Light::Ext_Light()
{
LTData = std::make_shared<LightData>();
LTData->bIsLightSet = true;
}
// 여기서의 Camera는 바라보는 시선, 위치를 의미함
void Ext_Light::LightUpdate(std::shared_ptr<Ext_Camera> _Camera, float _DeltaTime)
{
LTData->LightWorldPosition = GetTransform()->GetWorldPosition();
LTData->LightForwardVector = GetTransform()->GetLocalForwardVector();
LTData->CameraWorldPosition = _Camera->GetTransform()->GetWorldPosition();
}
Light는 Actor를 상속받기 때문에 생성 시 Scene의 Actors 컨테이너에 저장됩니다. 하지만 Light들에 대해서 따로 순회를 돌며 Update를 진행해야 하는 부분이 있기 때문에, 이들에 대해서만 저장할 컨테이너를 하나 추가했습니다.
또한 MeshComponentUnit들에 대해서도 생성 시 Scene의 LightDatas 상수 버퍼를 세팅하도록 설정했습니다.
// 메시 컴포넌트 유닛 생성 시 호출, Mesh, Material, ConstantBuffer 세팅
void Ext_MeshComponentUnit::MeshComponentUnitInitialize(std::string_view _MeshName, MaterialType _SettingValue)
{
std::string MaterialName = MaterialSettingToString(_SettingValue);
Mesh = Ext_DirectXMesh::Find(_MeshName); // 메시 설정
Material = Ext_DirectXMaterial::Find(MaterialName); // 머티리얼 설정
if (nullptr == Mesh || nullptr == Material)
{
MsgAssert("존재하지 않는 메시나 머티리얼을 메시유닛에 넣을 수는 없습니다.")
}
InputLayout->CreateInputLayout(Mesh->GetVertexBuffer(), Material->GetVertexShader()); // InputLayout 설정
// 상수버퍼 세팅
// [1] 버텍스 셰이더 정보 가져오기
const Ext_DirectXBufferSetter& VertexShaderBuffers = Material->GetVertexShader()->GetBufferSetter();
BufferSetter.Copy(VertexShaderBuffers);
// [2] 픽셀 셰이더 정보 가져오기
const Ext_DirectXBufferSetter& PixelShaderBuffers = Material->GetPixelShader()->GetBufferSetter();
BufferSetter.Copy(PixelShaderBuffers);
// [3] 트랜스폼 상수버퍼 세팅하기
const TransformData& TFData = *(OwnerMeshComponent.lock()->GetTransform()->GetTransformData().get());
BufferSetter.SetConstantBufferLink("TransformData", TFData);
// [4] 빛 상수버퍼 세팅하기(스태틱, 다이나믹은 빛 연산 실시를 위해 추가 세팅)
if (_SettingValue == MaterialType::Static || _SettingValue == MaterialType::Dynamic)
{
const LightDatas& LTDatas = OwnerMeshComponent.lock()->GetOwnerScene().lock()->GetLightDataBuffer();
BufferSetter.SetConstantBufferLink("LightDatas", LTDatas);
}
//...
}
[업데이트]
Redering 단계에서 카메라의 뷰, 프로젝션 행렬을 구한 뒤 바로 Light 들의 업데이트를 진행합니다.
// Camera들의 Rendering 호출
void Ext_Scene::Rendering(float _DeltaTime)
{
// Rendering 업데이트
for (auto& CamIter : Cameras)
{
std::shared_ptr<Ext_Camera> CurCamera = CamIter.second;
CurCamera->CameraTransformUpdate(); // 카메라에 대한 뷰, 프로젝션, 뷰포트 행렬 최신화
LightDataBuffer.LightCount = 0; // 라이트 업데이트 전, 상수버퍼 갯수 초기화(순회하면서 값 넣어줘야하기 때문)
for (auto& LightIter : Lights) // 라이트들 돌면서 업데이트
{
std::shared_ptr<Ext_Light> CurLight = LightIter.second;
CurLight->LightUpdate(CurCamera, _DeltaTime);
LightDataBuffer.Lights[LightDataBuffer.LightCount] = *CurLight->GetLightData().get(); // 상수버퍼에 값 넣어주기
++LightDataBuffer.LightCount; // 다음 순회를 위해 1 올려주는 작업임
}
CurCamera->Rendering(_DeltaTime); // Camera의 MeshComponent들 Rendering
}
Ext_Imgui::Render(GetSharedFromThis<Ext_Scene>(), _DeltaTime);
}
다음으로는 Vertex Shader입니다.
cbuffer TransformData : register(b0)
{
float4 LocalPosition;
float4 LocalRotation;
float4 LocalScale;
float4 LocalQuaternion;
float4x4 LocalMatrix;
float4 WorldPosition;
float4 WorldRotation;
float4 WorldQuaternion;
float4 WorldScale;
float4x4 WorldMatrix;
float4x4 ViewMatrix;
float4x4 ProjectionMatrix;
float4x4 WorldViewMatrix;
float4x4 WorldViewProjectionMatrix;
}
struct VSInput
{
float4 Position : POSITION;
float4 TexCoord : TEXCOORD;
float4 Normal : NORMAL;
};
struct VSOutput
{
float4 Position : SV_POSITION;
float4 TexCoord : TEXCOORD;
float3 WorldPosition : POSITION;
float3 WorldNormal : NORMAL;
};
VSOutput Grapics_VS(VSInput _Input)
{
VSOutput Output;
// _Input.Position.w = 1.0f;
// Position 설정
float4 WorldPos = mul(_Input.Position, WorldMatrix);
float4 ViewPos = mul(WorldPos, ViewMatrix);
Output.Position = mul(ViewPos, ProjectionMatrix);
// UV 좌표 설정
Output.TexCoord = _Input.TexCoord;
// 월드 공간 기준으로 조명 계산을 진행하기 위해 WorldMatrix만 처리한 Position, Normal을 생성하여 Pixel Shader에 넘겨줌
Output.WorldPosition = mul(float4(_Input.Position.xyz, 1.0f), WorldMatrix).rgb;
Output.WorldNormal = mul(float4(_Input.Normal.xyz, 0.0f), WorldMatrix).rgb;
return Output;
}
VSOutput 구조체에 WorldPosition과 WorldNormal을 추가했습니다. 픽셀 셰이더에서 빛 처리를 World Space 기준으로 하기 위해서입니다.
다음은 Pixel Shader입니다.
struct LightData
{
float4 LightColor; // RGB(색), w(강도)
float4 LightWorldPosition; // 라이트 월드 공간 위치
float4 LightForwardVector; // 월드 입사 벡터
float4 CameraWorldPosition; // 시점 월드 공간 위치
float NearDistance;
float FarDistance;
float AttenuationValue;
int LightType;
bool bIsLightSet;
};
cbuffer LightDatas : register(b1)
{
int LightCount;
LightData Lights[64];
};
Texture2D BaseColorTex : register(t0); // 텍스처 자원
SamplerState Sampler : register(s0); // 샘플러
struct PSInput
{
float4 Position : SV_POSITION;
float4 TexCoord : TEXCOORD;
float3 WorldPosition : POSITION;
float3 WorldNormal : NORMAL;
};
// 각 벡터에 normalize를 해주는 이유는, 명시적으로 normalize된 벡터를 넣어줬다 하더라도
// 임의의 값이 어떻게 들어올지 모르기 때문에 그냥 해주는것(안정성을 위한 처리라고 보면 됨)
float4 Grapics_PS(PSInput _Input) : SV_TARGET
{
// 텍스처 색상
float4 Albedo = BaseColorTex.Sample(Sampler, _Input.TexCoord);
// 월드공간 기준 픽셀(표면) 위치와 법선 단위 벡터
float3 PixelPosition = _Input.WorldPosition;
float3 PixelNormal = normalize(_Input.WorldNormal);
// 누적 조명 컬러 (RGB)
float3 AccumLightColor = float3(0, 0, 0);
for (int i = 0; i < LightCount; ++i)
{
LightData LTData = Lights[i];
if (LTData.bIsLightSet == false)
{
continue;
}
// 공통분모
float3 FinalLight = float3(0.0f, 0.0f, 0.0f);
float3 LightDirection = normalize(-LTData.LightForwardVector.xyz); // 빛 계산에 사용하는 LightDirection은 "표면에서 봤을 때 빛이 표면으로 들어오는 방향"을 사용하므로 반대로 뒤집음
float Shininess = 32.0f; // Shininess는 임의로 32 고정
float AmbientIntensity = LTData.LightColor.w;
// 월드공간 기준 시선 위치와 시선의 방향 단위 벡터
float3 EyePosition = LTData.CameraWorldPosition.xyz;
float3 EyeDirection = normalize(EyePosition - _Input.WorldPosition);
if (LTData.LightType == 0) // Directional Light
{
// Diffuse Light 계산
float DiffuseLight = saturate(dot(LightDirection, PixelNormal));
// Specular Light 계산, Phong 모델을 사용하기 때문에 R = reflect(L, N)
float3 ReflectionVector = normalize(2.0f * PixelNormal * dot(LightDirection, PixelNormal) - LightDirection); // 반사벡터
float RDotV = max(0.0f, dot(ReflectionVector, EyeDirection));
float3 SpecularLight = pow(RDotV, Shininess);
// Ambient Light 계산, 강도는 LightColor의 w값 사용
float3 AmbientLight = AmbientIntensity;
AccumLightColor += LTData.LightColor.xyz * (DiffuseLight + SpecularLight + AmbientLight);
}
else if (LTData.LightType == 1) // 포인트 라이트
{
float3 LightPosition = LTData.LightWorldPosition.xyz;
// 픽셀과 위치와 광원 위치 사이 거리 및 방향 구하기
float3 PixelToLight = LightPosition - PixelPosition;
float Distance = length(PixelToLight); // Distance로 Near, Far 컬링하면 재밌는거 볼 수 있음, 밑에 주석을 사용
//if (Distance >= LTData.NearDistance && Distance <= LTData.FarDistance)
// 픽셀에서 광원으로 향하는 단위 벡터
float3 Vector = normalize(PixelToLight);
// 감쇠(Attenuation) 공식 = 1/(c0 + c1·d + c2·d²)
float C0 = 1.0f;
float C1 = 0.0f;
float C2 = LTData.AttenuationValue / (LTData.FarDistance * LTData.FarDistance); // 거리에 따른 제곱항 계수, 여기를 조정하면 감쇠가 쌔짐
float Attenuation = 1.0f / (C0 + C1 * Distance + C2 * Distance * Distance);
// Diffuse Light 계산
float DiffuseLight = saturate(dot(PixelNormal, Vector));
// Specular Light 계산, Phong 모델을 사용하기 때문에 R = reflect(L, N)
float3 ReflectionVector = normalize(2.0f * PixelNormal * dot(Vector, PixelNormal) - Vector);
float RDotV = max(0.0f, dot(ReflectionVector, EyeDirection));
float3 SpecularLight = pow(RDotV, Shininess);
// Ambient Light 계산, 강도는 LightColor의 w값 사용
float3 AmbientLight = AmbientIntensity;
// 누적
AccumLightColor += LTData.LightColor.xyz * (DiffuseLight + SpecularLight + AmbientLight) * Attenuation;
}
}
Albedo.rgb *= AccumLightColor;
return Albedo;
}
Pixel Shader에서 실시하는 것은 다음과 같습니다.
1. 기존 색상 사용
: Texture가 있다면 그 값이 기본 Color
2. 월드 공간 위치, 월드 공간 노말 값 확인
: 노말은 normalize() 실시(혹시 몰라서 그냥 해줌)
3. LightData 반복문
: 갯수는 최대 64개로 해뒀기 때문에, 64번 순회를 돌 것입니다(실제로는 2개밖에 없지만).
4. Directional Light 연산 실시
: Diffuse, Specular, Ambient 연산 실시
5. Color에 적용
: Diffuse, Specular, Ambient 세 개를 Light Color에 모두 더하고, 기본 Color와 곱해주면서 누적
+) 포인트 라이트는 감쇠를 적용해줍니다.
[결과]
디렉셔널 라이트는 기본으로 사용하고, 메인 카메라에 PointLight를 붙여서 Scene 내에 총 2개의 라이트를 사용해봤습니다. 포인트 라이트는 감쇠값을 크게 설정해서, 가까이 가야 잘보입니다.
'DirectX11 > 프레임워크 제작' 카테고리의 다른 글
[DirectX11] 디퍼드 렌더링으로 변경하기 (0) | 2025.06.11 |
---|---|
[DirectX11] 노말 매핑 해보기 (0) | 2025.06.07 |
[DirectX11] DirectX11 프레임워크를 위한 기능 추가 - 3 + 간단한 Lighting 해보기 (0) | 2025.06.04 |
[DirectX11] assimp 라이브러리를 통한 3DMesh 로드와 애니메이션 (0) | 2025.05.29 |
[DirectX11] BlendState 생성 (0) | 2025.05.28 |