[DirectX11]

Light #4 - Emissive

럭키🍀 2024. 9. 9. 10:55

Emissive는 오브젝트의 외곽선을 나타낼 때 사용하며 림라이트라고 불린다.

이를 구하는 방법은 여러가지가 있는데 우선 15.EmissiveDemo,h,.cpp와 12.Lighting_Emissive.fx를 만들었다.

 

12.Lighting_Emessive.fx에서 우선 어떤 값들을 필요로 하는지 살펴보자.

우선 Emessive의 빛이 방향, 빛의 색 다 필요 없고 카메라와 물체에 둘 만 적용된다. 빛에 직접적인 연관이 없고 물체의 재질에 따라 달라질수 잇기 때문에 float4 타입의 MaterialEmessive만 하나 남겨뒀다.

 

그리고 DiffuseMap(텍스쳐)도 크게 필요 없지만 그냥 VS는 패스하고 픽셀쉐이더를 살펴보자.

// 12.Lighting_Emissive.fx

float4 MaterialEmissive;

MeshOutput VS(VertexTextureNormal input)
{
	MeshOutput output;
	output.position = mul(input.position, W);
	output.worldPosition = input.position;
	output.position = mul(output.position, VP);	
	output.uv = input.uv;
	output.normal = mul(input.normal, (float3x3)W);

	return output;
}

// Emissive
// 외각선 구할 때 사용
// (림라이트)
float4 PS(MeshOutput input) : SV_TARGET
{
	float3 cameraPosition = VINV._41_42_43;
	float3 E = normalize(cameraPosition - input.worldPosition);

	float value = saturate(dot(E, input.normal));
	float emissive = 1.0f - value;

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

	float4 color = MaterialEmissive * emissive;

	return color;
}

다시한번 Emissive는 외곽선을 구할 때 사용하며 림라이트라고 한다. 그런데 카메라랑 빛이랑 연관이 있다.

우선 카메라의 위치는 뷰행렬에서 역행렬 연산을 하고 3행 0,1,2열의 원소를 꺼내면 구해지므로 cameraPostion에 담고,

더보기

카메라의 위치를 가져오기 위해서 역행렬을 매번 GPU에서 계산하는게 아닌 CPU에서 한 프레임에 한 번 계산한 값을 가져와 쓰기 위해 GlobalBuffer상수 버퍼에 뷰행렬의 역행렬을 저장했다.

그리고 여기에 값을 넣어주기 위해 RenderManger에도 같은 타입으로 맞추고 PushGlobalData에서 가져와 쓴다.

방금 구한 카메라의 위치에서 input의 월드포지션을 빼면 방향벡터가 나오는데 input의 월드포지션에서 카메라까지의 방향벡터가 구해지는데 이걸 단위벡터로 만들어 E에 저장한다.(eye로 가는것이라 변수 이름을 E)

그리고 E와 input.normal을 우선 내적하면,,?

노멀과 카메라가 가까운지 구할 수 있다. 이 value를  1에서 뺀 값을 역으로 사용하고 또 부드럽게 사용할수 있도록 learp계열의 함수중 하나인 smoothspet함수를 사용할 것인데 얘는 min, max, 값을 넣어주면 조금 더 그래프를 부드럽게 보간해준다. min을 0, max를 1로 해서 그 사이값에 편차가 심하지 않은 값으로 보간해서 반환하는 함수인가보다.

그리고 이렇게 구한 emissive값을 바로 MaterialEmessive인 재질의 색과 곱해서 바로 반환해준다.

즉, n과 E의 내적으로 구한 값을 바로 쓰지 않고 한번 1에서 빼서 써서 바로 카메라와 노멀이 평행하면 잘보이는게 아니라 오히려 안보이고 카에라와 노멀이 수직을 이룰수록 큰 값을 갖게  되어 잘 보이게 될 것이다.

 

EmissiveDemo클래스에서도 방금 만든 '12.Lighting_Emissive.fx'를 가져오게 만들고 Update함수를 수정하였다.

// 15.EmissiveDemo.cpp
#include "pch.h"
#include "15. EmissiveDemo.h"
#include "GeometryHelper.h"
#include "Camera.h"
#include "GameObject.h"
#include "CameraScript.h"
#include "MeshRenderer.h"
#include "Mesh.h"

void EmissiveDemo::Init()
{
	RESOURCES->Init();
	_shader = make_shared<Shader>(L"12. Lighting_Emissive.fx");

	// 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>());
	{
		_obj->GetMeshRenderer()->SetShader(_shader);
	}
	{
		auto mesh = RESOURCES->Get<Mesh>(L"Sphere");
		_obj->GetMeshRenderer()->SetMesh(mesh);
	}
	{
		auto texture = RESOURCES->Load<Texture>(L"Veigar", L"..\\Resources\\Textures\\veigar.jpg");
		_obj->GetMeshRenderer()->SetTexture(texture);
	}

	// Object2
	_obj2 = make_shared<GameObject>();
	_obj2->GetOrAddTransform()->SetPosition(Vec3{0.5f, 0.f, 2.f});
	_obj2->AddComponent(make_shared<MeshRenderer>());
	{
		_obj2->GetMeshRenderer()->SetShader(_shader);
	}
	{
		auto mesh = RESOURCES->Get<Mesh>(L"Cube");
		_obj2->GetMeshRenderer()->SetMesh(mesh);
	}
	{
		auto texture = RESOURCES->Load<Texture>(L"Veigar", L"..\\Resources\\Textures\\veigar.jpg");
		_obj2->GetMeshRenderer()->SetTexture(texture);
	}

	RENDER->Init(_shader);
}

void EmissiveDemo::Update()
{
	_camera->Update();
	RENDER->Update();

	{
		Vec4 materialEmissive(1.f, 0.f, 0.f, 1.f);
		_shader->GetVector("MaterialEmissive")->SetFloatVector((float*)&materialEmissive);
		_obj->Update();
	}

	{
		Vec4 materialEmissive(0.f, 1.f, 0.f, 1.f);
		_shader->GetVector("MaterialEmissive")->SetFloatVector((float*)&materialEmissive);
		_obj2->Update();
	}	
}

void EmissiveDemo::Render()
{
	
}

 

 

Update함수에서도 LightDir, Light색을 없애주고 material값만 빨간색 1,0,0,1으로 바꿔줬다. 물론 꼭 이렇게 빛을 빨간색으로 하는 방법 외에도 빛을 흰색으로 1,1,1,1로 밀어준다음에 빛에서 얼마만큼 적용할지 1,0,0,1로 해도 되긴한다. 스피어의 색만 빨간색으로 하고 큐브것은 초록색으로 한 뒤 실행해보면

어느 방향에서 봐도 스피어의 테두리만 보인다.

큐브의 노멀맵은 우선 제대로 먹히는게 아니라 패스하고 스피어를 보면 우리가 원하던 것처럼 테두리만 빨갛게 빛나고 있다 이 효과를 좀더 강하제 주기 위해 PS에 제곱연산 pow를 해서 보면 외곽선이 더 얇고 짙어진다.

 

픽셀쉐이더에서 사용한 원리를 다시 한 번 생각해보면 카메라와 표면의 노멀벡터가 평행을 이루면 원래는 사이각이 0이라 내적한 값이 1로 제일 커야하나 1에서 빼준값이므로 0이 되어 색이 나타나지 않고 오히려 카메라와 물체의 표면이 평행에 가까운 각에 이룰수록 빛이 강해지고 카메라와 normal값이 가까울수록(수평을 이룰수록) 0이므로 보이지 않는 것이다.

 

이렇게 외곽을 구하는 방법은 어렵지 않아서 여러곳에 응용할 수 있는데 예를들면 클릭햇을 때 메쉬 외곽선이 빛나도록 하는데 쓸 수 있다. 결국 일축하면 'Eye방향과 normal방향이 멀어질 수록 그 수치를 조절해 색상을 나타낸다.' 이다. 다른 3총사에 비해 꼭 필수적인게 아니라 사용 빈도가 높지 않지만 기본적으로 쓰이므로 알아둔 Emissive였다.