DirectX

[DirectX11] Lighting의 이해와 활용 Ambient, Diffuse, Specular, Emissive

유니얼 2024. 3. 6. 22:45
728x90

DirectX 11로 게임 엔진 아키텍처 만들기

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

ResultResult2

참고강의 링크:

https://www.inflearn.com/course/directx11-%EA%B2%8C%EC%9E%84%EA%B0%9C%EB%B0%9C-%EB%8F%84%EC%95%BD%EB%B0%98/dashboard

 

[게임 프로그래머 도약반] 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 모델과 씬을 생성할 수 있습니다.

반응형