[DirectX11] Lighting의 이해와 활용 Ambient, Diffuse, Specular, Emissive
DirectX 11로 게임 엔진 아키텍처 만들기
조명은 3D 그래픽에서 장면의 실감나는 표현을 위해 필수적인 요소입니다. DirectX11에서는 Ambient, Diffuse, Specular, Emissive 등 다양한 조명 기법을 지원하여 더욱 풍부한 비주얼 효과를 제공합니다. 본 글에서는 DirectX11을 사용하여 개발 중인 게임 엔진 아키텍처 내에서 Lighting의 개념을 이해하고, 이를 적용한 예제 코드를 통해 그 활용 방법을 살펴보고자 합니다.


참고강의 링크:
[게임 프로그래머 도약반] DirectX11 입문 강의 - 인프런
게임 프로그래머 공부에 있어서 필수적인 DirectX 11 지식을 초보자들의 눈높이에 맞춰 설명하는 강의입니다., [사진][사진] [사진] 게임 개발자는 Unreal, Unity만 사용할 줄 알면 되는 거 아닌가요? 엔
www.inflearn.com
Ambient Lighting (주변광) 정의:
Ambient Lighting은 객체를 둘러싼 일반적인 빛의 양을 나타냅니다. 이 빛은 모든 방향에서 동일하게 오며, 특정 방향성이 없습니다. 주변광은 장면 전체에 대한 기본 조명 수준을 설정하는 데 사용되며, 모든 객체에 균일하게 적용됩니다. 용도: 특정 빛의 방향이 없는 경우, 즉 일광이나 조명이 고르게 분포된 실내 공간에서의 장면을 모델링할 때 유용합니다. 또한, 그림자가 생성되지 않아 처리 비용이 낮은 편입니다.
// Ambient 처리
{
float4 color = GlobalLight.ambient * Material.ambient;
ambientColor = DiffuseMap.Sample(LinearSampler, uv) * color;
}
Diffuse Lighting (확산광) 정의:
빛이 객체의 표면에 닿아 여러 방향으로 확산되어 반사되는 현상을 나타냅니다. 이는 표면의 노멀 벡터와 빛의 방향에 따라 영향을 받습니다. 표면이 빛을 향해 있을수록 더 밝게 보입니다. 특징: 확산광은 표면의 기본 색상을 결정하며, 질감과 재질의 느낌을 부여하는 데 중요한 역할을 합니다. 또한, 물체의 형태와 입체감을 나타내는 데 필수적입니다.
// Diffuse 처리
{
float4 color = DiffuseMap.Sample(LinearSampler, uv);
float value = dot(-GlobalLight.direction, normalize(normal));
diffuseColor = color * value * GlobalLight.diffuse * Material.diffuse;
}
Specular Lighting (경사광) 정의:
빛이 객체의 표면에서 특정 각도로 반사되어 생성되는 밝은 하이라이트를 나타냅니다. 이는 관찰자의 시점, 빛의 방향, 그리고 표면의 재질 특성(반짝임)에 따라 변화합니다. 특징: 경사광은 표면의 반짝이는 특성을 표현하는 데 사용됩니다. 높은 광택의 표면에서는 강한 경사광이 나타나며, 이는 재질의 질감을 구분하는 데 중요한 단서를 제공합니다.
// Specular 처리
{
// 반사 벡터 계산
float3 R = GlobalLight.direction - (2 * normal * dot(GlobalLight.direction, normal));
R = normalize(R);
// 카메라 위치에서 세계 좌표까지의 방향 벡터
float3 cameraPosition = CameraPosition();
float3 E = normalize(cameraPosition - worldPosition);
float value = saturate(dot(R, E)); // 0~1 사이의 값으로 제한
float specular = pow(value, 10);
specularColor = GlobalLight.specular * Material.specular * specular;
}
Emissive Lighting (발광) 정의:
물체 자체에서 빛을 내는 현상을 표현합니다. 이 조명은 외부 빛의 영향을 받지 않고, 오로지 물체 자체의 발광 특성에 의해 결정됩니다. 특징: 발광은 화면에서 물체가 스스로 빛나는 듯한 효과를 주며, 전자 장치의 화면, 불빛, 혹은 마법 효과 등 다양한 장면에서 사용됩니다. 이는 장면에 생동감을 부여하며 시각적 포커스 역할을 합니다.
// Emissive 처리
{
float3 cameraPosition = CameraPosition();
float3 E = normalize(cameraPosition - worldPosition);
float value = saturate(dot(E, normal));
float emissive = 1.0f - value;
emissive = smoothstep(0.0f, 1.0f, emissive);
emissive = pow(emissive, 2);
emissiveColor = GlobalLight.emissive * Material.emissive * emissive;
}
Light Shader
#ifndef _LIGHT_FX_
#define _LIGHT_FX_
#include "00. Global.fx" // 전역 쉐이더 변수와 함수를 포함하는 파일을 포함
//////////
//Struct//
//////////
// 광원에 대한 설명을 포함하는 구조체
struct LightDesc {
float4 ambient; // 주변 광
float4 diffuse; // 확산 광
float4 specular; // 반사 광
float4 emissive; // 자체 발광
float3 direction; // 광원의 방향
float padding; // 패딩 (16바이트 경계를 맞추기 위함)
};
// 재질에 대한 설명을 포함하는 구조체
struct MaterialDesc {
float4 ambient; // 재질의 주변 광 속성
float4 diffuse; // 재질의 확산 광 속성
float4 specular; // 재질의 반사 광 속성
float4 emissive; // 재질의 자체 발광 속성
};
///////////////
//ConstBuffer//
///////////////
// 광원에 대한 상수 버퍼
cbuffer LightBuffer {
LightDesc GlobalLight;
};
// 재질에 대한 상수 버퍼
cbuffer MaterialBuffer {
MaterialDesc Material;
};
///////
//SRV//
///////
// 텍스처 샘플링을 위한 쉐이더 리소스 뷰(SRV)
Texture2D DiffuseMap; // 확산 맵
Texture2D SpecularMap; // 반사 맵
Texture2D NormalMap; // 정규 맵
////////////
//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;
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 = GlobalLight.direction - (2 * normal * dot(GlobalLight.direction, normal));
R = normalize(R);
// 카메라 위치에서 세계 좌표까지의 방향 벡터
float3 cameraPosition = CameraPosition();
float3 E = normalize(cameraPosition - worldPosition);
float value = saturate(dot(R, E)); // 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));
float emissive = 1.0f - value;
emissive = smoothstep(0.0f, 1.0f, emissive);
emissive = pow(emissive, 2);
emissiveColor = GlobalLight.emissive * Material.emissive * emissive;
}
return ambientColor + diffuseColor + specularColor + emissiveColor;
}
// 정규 매핑 계산 함수
void ComputeNormalMapping(inout float3 normal, float3 tangent, float2 uv)
{
// 텍스처에서 정규 벡터를 샘플링
float4 map = NormalMap.Sample(LinearSampler, uv);
if (any(map.rgb) == false) return;
float3 N = normalize(normal); // Z 방향
float3 T = normalize(tangent); // X 방향
float3 B = normalize(cross(N, T)); // Y 방향, 외적을 통해 계산
float3x3 TBN = float3x3(T, B, N); // 탄젠트 공간의 기저를 형성
// 탄젠트 공간 정규 벡터를 [-1,1] 범위로 재조정
float3 tangentSpaceNormal = (map.rgb * 2.0f - 1.0f);
float3 worldNormal = mul(tangentSpaceNormal, TBN);
normal = worldNormal;
}
#endif
프로젝트 호출
LightingDemo.h
#pragma once
#include "IExecute.h"
#include "Geometry.h"
#include "GameObject.h"
class LightingDemo : public IExecute
{
public:
void Init() override;
void Update() override;
void Render() override;
shared_ptr<Shader> _shader;
//Object
shared_ptr<GameObject> _obj;
shared_ptr<GameObject> _obj2;
shared_ptr<GameObject> _camera;
};
LightingDemo.cpp
#include "pch.h"
#include "16. LightingDemo.h"
#include "GeometryHelper.h"
#include "Camera.h"
#include "CameraScript.h"
#include "Transform.h"
#include "Texture.h"
#include "ResourceBase.h"
#include "MeshRenderer.h"
#include "GameObject.h"
//#include "RenderManager.h"
void MaterialDemo::Init()
{
RESOURCES->Init();
_shader = make_shared<Shader>(L"13. Lighting.fx");
// Material
{
shared_ptr<Material> material = make_shared<Material>();
{
material->SetShader(_shader);
}
{
auto texture = RESOURCES->Load<Texture>(L"Veigar", L"..\\Resources\\Textures\\veigar.jpg");
material->SetDiffuseMap(texture);
}
MaterialDesc& desc = material->GetMaterialDesc();
desc.ambient = Vec4(1.f);
desc.diffuse = Vec4(1.f);
desc.specular = Vec4(1.f);
RESOURCES->Add(L"Veigar", material);
}
// Camera
_camera = make_shared<GameObject>();
_camera->GetOrAddTransform()->SetPosition(Vec3{0.f, 0.f, -10.f});
_camera->AddComponent(make_shared<Camera>());
_camera->AddComponent(make_shared<CameraScript>());
// Object
_obj = make_shared<GameObject>();
_obj->GetOrAddTransform();
_obj->AddComponent(make_shared<MeshRenderer>());
{
auto mesh = RESOURCES->Get<Mesh>(L"Sphere");
_obj->GetMeshRenderer()->SetMesh(mesh);
}
{
auto material = RESOURCES->Get<Material>(L"Veigar");
_obj->GetMeshRenderer()->SetMaterial(material);
}
// Object2
_obj2 = make_shared<GameObject>();
_obj2->GetOrAddTransform()->SetPosition(Vec3{0.5f, 0.f, 2.f});
_obj2->AddComponent(make_shared<MeshRenderer>());
{
auto mesh = RESOURCES->Get<Mesh>(L"Cube");
_obj2->GetMeshRenderer()->SetMesh(mesh);
}
{
auto material = RESOURCES->Get<Material>(L"Veigar")->Clone();
MaterialDesc& desc = material->GetMaterialDesc();
//desc.ambient = Vec4(0.f);
//desc.diffuse = Vec4(0.f);
_obj2->GetMeshRenderer()->SetMaterial(material);
}
RENDER->Init(_shader);
}
void MaterialDemo::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);
}
{
_obj->Update();
}
{
_obj2->Update();
}
}
void LightingDemo::Render()
{
}
결론
3D 그래픽에서 조명은 장면의 분위기와 재질의 느낌을 결정하는 중요한 요소입니다. Ambient, Diffuse, Specular, Emissive 조명 기법을 적절히 활용함으로써, 더욱 실감나고 다양한 표현이 가능한 3D 모델과 씬을 생성할 수 있습니다.