[DirectX11]

Light #5 Light 통합

럭키🍀 2024. 9. 9. 12:21

앞서 배운 네 개의 조명 기법 Ambient, Diffuse, Specular, Emissive를 합쳐보자.

공용으로 사용할 쉐이더 파일 00.Lightfx 를 만들어서 Client프로젝트의 Shaders/ Common필터에 00.Global.fx와 함께 뒀다.

그리고 13.Light.fx를 만들어서 모든 연산을 다 통합해서 연산하는 쉐이더를 만들 것이다.

 

1. 00.Light.fx

우선 00.Light.fx를 보자 빛 연산은 공용적으로 여기에 저장하고 쓸것이

헤더가드

#ifndef _LIGHT_FX_
#define _LIGHT_FX_

를 해줬다.

그리고 이전에 공용으로 사용하던 00.Global.fx도 인클루드 해준다. 00.Global.fx에는 기본 쉐이더에서 쓰는 자료형(VertexData, VertexOutput)과 전역으로 쓰일 V,P,W매트릭스, SamplerState, RasterizerState 등이 이미 있고 00.Light.fx에는 이들을 사용해 Ambient, Diffuse, Specular, Emissive연산을 가능하도록 하는 hlsl형식의 함수를 넣어둘 것이다.

 

00. Light.fx에는 이전에 만들었던 09.Light_Amibent.fx부터 12.Light_Emissive.fx를 가져오는게 핵심으로 우선

// 00.Light.fx
////////////
// 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;
};

 이렇게 구조체를 정의해줬다.

빛에는 ambient, diffuse, specular, emissive 색상값이 있고 material에도 각각 이를 얼마나 받아줄지가 있으며 추가로 빛은 방향이 있어야 material의 노멀과 함께 빛 연산이 쓰인다. 또 빛구조체 LightDesc에는 direction이 float3타입이므로 float padding을 넣어 16바이트 정렬을 일단 맞춰줬다. 조명이 한 개가 아닌 여러개가 될때 constantBuffuer에 배열 형태로 조명을 묘사해 넘겨줄텐데 그때 16byte정렬을 지켜주지 않으면 깨질수도 있기때문에 항상 유의하는게 좋다.

 

이제 조명관련한 상수버퍼를 만들어보자. 바로 위에서 만든 구조체를 타입으로 공용으로 사용할 것이다.

/////////////////
// ConstBuffer //
/////////////////

cbuffer LightBuffer
{
	LightDesc GlobalLight;
};

cbuffer MaterialBuffer
{
	MaterialDesc Material;
};

위 두개의 constantBuffer를 하나로 쓰지 않고 분리한 이유는

float4 LightAmbient;

float4 LightDiffuse;

이렇게 쓰지 않는 이유와 동일한데? 전역함수, 전역변수로 사용해도 간접적으로 하나의 글로번 constantBuffer가 만들어지는데 이는 하나를 고치면 또 하나가 같이 영향을 받기 때문 성능을 위해 각각 따로 설정 가능하게끔 만든것이다.

위의 LightBuffer는 한 번만 설정하면 되고 MaterialBuffer는 오브젝트마다 설정하므로 구분해뒀다.

 

그리고 쉐이더에서 쓰이는 리소스들도 글로벌로 쓸 것인데

/////////
// SRV //
/////////

Texture2D DiffuseMap;
Texture2D SpecularMap;
Texture2D NormalMap; 

 리소스들은 텍스쳐와 관련이 있는 쉐이더 리소스 뷰(SRV)이다.

오브젝트의 표면에 입혀줄 텍스쳐가 DiffuseMap이고 오브젝트의 Geometry(기하) 모양에 따라 표면에 수직인 벡터를 저장하고 있는게 NormalMap이며 Specular맵은 후에 알아볼건데 미리 만들어둔다.

 

2. 13.Light.fx

그 다음엔 본격적으로 쉐이더에서 쓸 함수를 짜보자.

13.Light.fx에 방금 만든 00.Light.fx를 인클루드하고 버텍스쉐이더는 그대로 둔 채

PS는 다 날리고 00.Light.fx 에 함수를 만들어서 그 결과값을 불러오도록 하자. 필요한 값들을 인자로 넘기기 위

float4 PS(MeshOutput input) : SV_TARGET
{
	float4 color = ComputeLight(input.normal, input.uv, input.worldPosition);

	return color;
}

이렇게만 남겨둔다. input.worldPosition은 emissive를 계산할때 필요했다.

 

이제 00.Light.fx에 ComputeLight을 정의할 것이다.

우선 각각 ambientColor, diffuseColor, specularColor, emissiveColor를 float4타입으로 만들어 각각 구해보자.

//////////////
// 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 = reflect(GlobalLight.direction, normal);
		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)); // 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));
		float emissive = 1.0f - value;

		// min, max, x
		emissive = smoothstep(0.0f, 1.0f, emissive);
		emissive = pow(emissive, 2);

		emissiveColor = GlobalLight.emissive * Material.emissive * emissive;
	}

	return ambientColor + diffuseColor + specularColor + emissiveColor;
}

1)Ambient(주변광, 환경광)는 빛과 재질의 ambient값을 곱한값을 텍스쳐 DiffuseMap에서 uv를 샘플림한 값에 곱해서 구했다. 조명의 방향이나 노멀값과는 관계없이 오브젝트의 모든 곳에 같은 값을 갖게된다.

2)Diffuse(분산광,난반사)값은 조명의 방향에 -를 한값과 재질의 표면에 수직하는 노멀값을 내적하여 구한 뒤

 텍스쳐에서 샘플링한 color값과 조명의 색,재질에서 조명을 얼마나 받아줄지 값을 곱해준다. 디퓨즈값은 람베르트공식을 이용해 구한다는 특징이 있었다.(조명의 방향이 수직이 되어 노멀벡터와 빛 방향이 일치할 때 가장 큰 값을 가지는 것이고 아니면 점점 옅어짐)

3) Specular는 우선 빛이 튕겨져서 반사가 되는 방향벡터를 먼저 구한 다음에 우리가 바라보는 카메라 기준(Eye) 카메라랑 일치했을때 가장 강렬하게 오는,, 눈뽕을 만드는 빛이었다..!특히 metalic물체가 빛반사가 심해 눈에 크리링처럼 하얗게 보이는 특성이 있었다.

반사광의 방향벡터 R을 reflect함수로 구해도 되고 직접구해도 됐엇다. 오브젝트의 표면에서 카메라까지의 방향벡터는 카메라의 위치로 구하는데 View행렬의 역행렬의 마지막행값이었다. 00.Global.fx에

float3 CameraPosition() { return VINV._41_42_42; } 

를 추가하고 이를 가져오게했다. 카메라의 위치에서 오브젝트의 표면의 위치(정점의 worldPosition)을 빼면 방향벡터가 구해지므로 이를 eye direction, E라고 카메라까지 방향벡터이다.

이 R,E를 내적한값을 완만하게 saturate로 보강하고 좁은영역에만 더 빛이 강하도록 pow 10을 넣어주었다.

4)Emissive는 림라이트방식으로 위에서 구한 E(오브젝트에서 카메라까지 방향벡터)와 normal벡터를 내적한 값을 1에서 빼서 뒤집은 다음 사용했는데 이러면 노멀이랑 카메라가 바라보는 방향 벡터가 90도를 이룰수록 더 강해져서 외곽선을 그리려고 시도하는데 쓰였다.

 

그리고 이렇게 구한 네가지 조명 값들은 서로 영향을 주는값이 아니므로 곱이 아닌 합 연산을 한다. 예를들어 어디선 빨간빛을 받앗고 어디선 보라빛을 받았으면 이들은 합쳐지는 것이다. 그래서 네가지 조명값의 합이 float4타입이므로 가장 강한 rgba중 하나의 값이 눈에 띄게될 것이다.

 

3. LightDemo클래스

16.LightDemo.h, cpp파일도 만들어주었다.

그리고 이들을 채워넣어주는 부분을 LightDemo클래서에서

// 16.LightDemo.cpp

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();
	}	
}

이렇게 Update에서 카메라 위치 업데이트 후 빛과 각 오브젝트에 대한 값들을 설정해줬다.

 

여기 00.Light.fx에서 쓰려고 정의한 LightDesc와 MaterialDesc상수 버퍼에 정보를 밀어 넣기 위해 RenderManager에도 똑같이 만들어줬다.

// RenderManager.h
// 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);
};

class RenderManager
{
// ...
	void PushLightData(const LightDesc& desc);
	void PushMaterialData(const MaterialDesc& desc);
// ...
	LightDesc _lightDesc;
	shared_ptr<ConstantBuffer<LightDesc>> _lightBuffer;
	ComPtr<ID3DX11EffectConstantBuffer> _lightEffectBuffer;

	MaterialDesc _materialDesc;
	shared_ptr<ConstantBuffer<MaterialDesc>> _materialBuffer;
	ComPtr<ID3DX11EffectConstantBuffer> _materialEffectBuffer;
}

여기서 Color타입은 DirectX에서 Float4타입이었다.

// 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());
}

그리고 두 물체의 amibient, diffuse 등 값을 조절하거나 안준다거나 해보고 lightDesc의 Direction값을 조절하면서 테스트해볼 수 있다.

큐브와 스피어가 은은하게 보이고 있고 조명이 위에서 아래로 수직으로 오므로 이를 받는 부분이 밝게 빛나고 있다.

 

LightDesc의 값을 ambient를 0.7f로, direction 값을 (-1.f,-1.f,0.f)로 바꾸니 나온 값이다.

'[DirectX11]' 카테고리의 다른 글

Normal Mapping  (0) 2024.09.18
Material  (0) 2024.09.10
Light #4 - Emissive  (0) 2024.09.09
Light #3 - Specular  (0) 2024.09.02
Light #2 - Diffuse  (0) 2024.08.30