[목차]

 

- 리플렉션 프로브를 위한 클래스 생성

- 결과 확인

 

 

[리플렉션 프로브를 위한 클래스 생성]

 

스카이박스에서 큐브맵을 로드하여 활용해봤습니다. 큐브맵을 여기서만 쓰는게 아니고, 리플렉션 프로브를 위해 사용할 수도 있습니다. 리플렉션 프로브는 3D 실시간 렌더링에서 간접 반사를 구현하기 위한 기술로, 오브젝트 표면에 환경이 반사되는 것처럼 보이게 만드는 데 사용됩니다.

 

실시간 반사는 성능 부담이 크기 때문에, 리플렉션 프로브를 활용하여 특정 위치의 반사 환경을 미리 저장해두고, 주변 오브젝트에 이 큐브맵을 적용하여 효율적으로 간접 반사를 구현할 수 있습니다.

 

바로 시작해보겠습니다. 클래스를 하나 만들어줍니다.

#pragma once
#include "Ext_Component.h"

class Ext_ReflectionComponent : public Ext_Component
{
public:
	// constrcuter destructer
	Ext_ReflectionComponent() {}
	~Ext_ReflectionComponent() {}

	// delete Function
	Ext_ReflectionComponent(const Ext_ReflectionComponent& _Other) = delete;
	Ext_ReflectionComponent(Ext_ReflectionComponent&& _Other) noexcept = delete;
	Ext_ReflectionComponent& operator=(const Ext_ReflectionComponent& _Other) = delete;
	Ext_ReflectionComponent& operator=(Ext_ReflectionComponent&& _Other) noexcept = delete;

	void ReflectionInitialize(std::shared_ptr<class Ext_Actor> _Owner, std::string_view _CaptureTextureName, const float4& _Scale = float4(128, 128));
	std::shared_ptr<class Ext_DirectXTexture> GetReflectionCubeTexture() { return CubeTexture; }

protected:
	
private:
	std::shared_ptr<class Ext_DirectXTexture> CubeTexture = nullptr;
	
};

 

ReflectionInitialize() 함수가 조금 중요한데, 다음과 같습니다.

void Ext_ReflectionComponent::ReflectionInitialize(std::shared_ptr<class Ext_Actor> _Owner, std::string_view _CaptureTextureName, const float4& _Scale/* = float4(128, 128)*/)
{
	Base_Directory Dir;
	Dir.MakePath("../Resource/FX/ReflectionTexture");
	std::string Path = Dir.GetPath();

	std::shared_ptr<Ext_DirectXRenderTarget> CaptureTarget = nullptr;

	if (nullptr == Ext_DirectXTexture::Find(_CaptureTextureName.data() + std::string("_Forword.png")))
	{
		if (0 == _Scale.x || 0 == _Scale.y)
		{
			MsgAssert("ReflectionProbe : 캡쳐할 텍스쳐의 크기가 0 입니다");
			return;
		}

		if (nullptr == CaptureTarget)
		{
			CaptureTarget = Ext_DirectXRenderTarget::CreateRenderTarget(DXGI_FORMAT::DXGI_FORMAT_R16G16B16A16_UNORM, _Scale, float4::ZERONULL);
		}

		float4 CenterPos = _Owner->GetTransform()->GetWorldPosition();
		float4 CenterRot = float4::ZERO;

		auto Scene = _Owner->GetOwnerScene().lock();
		if (Scene == nullptr)
		{
			MsgAssert("Scene이 유효하지 않습니다.");
			return;
		}

		// Forward
		Scene->GetMainCamera()->CaptureCubemap(CenterPos, CenterRot, float4(700, 700));
		CaptureTarget->RenderTargetClear();
		CaptureTarget->Merge(Scene->GetMainCamera()->GetCameraRenderTarget());
		Ext_ScreenShoot::RenderTargetShoot_DxTex(CaptureTarget, Path, _CaptureTextureName.data() + std::string("_Forword.png"));

		// Back
		Scene->GetMainCamera()->CaptureCubemap(CenterPos, CenterRot + float4(0, 180, 0), float4(512, 512));
		CaptureTarget->RenderTargetClear();
		CaptureTarget->Merge(Scene->GetMainCamera()->GetCameraRenderTarget());
		Ext_ScreenShoot::RenderTargetShoot_DxTex(CaptureTarget, Path, _CaptureTextureName.data() + std::string("_Back.png"));

		// Right
		Scene->GetMainCamera()->CaptureCubemap(CenterPos, CenterRot + float4(0, 90, 0), float4(512, 512));
		CaptureTarget->RenderTargetClear();
		CaptureTarget->Merge(Scene->GetMainCamera()->GetCameraRenderTarget());
		Ext_ScreenShoot::RenderTargetShoot_DxTex(CaptureTarget, Path, _CaptureTextureName.data() + std::string("_Right.png"));

		// Left
		Scene->GetMainCamera()->CaptureCubemap(CenterPos, CenterRot + float4(0, -90, 0), float4(512, 512));
		CaptureTarget->RenderTargetClear();
		CaptureTarget->Merge(Scene->GetMainCamera()->GetCameraRenderTarget());
		Ext_ScreenShoot::RenderTargetShoot_DxTex(CaptureTarget, Path, _CaptureTextureName.data() + std::string("_Left.png"));

		// Top
		Scene->GetMainCamera()->CaptureCubemap(CenterPos, CenterRot + float4(-90, 0, 0), float4(512, 512));
		CaptureTarget->RenderTargetClear();
		CaptureTarget->Merge(Scene->GetMainCamera()->GetCameraRenderTarget());
		Ext_ScreenShoot::RenderTargetShoot_DxTex(CaptureTarget, Path, _CaptureTextureName.data() + std::string("_Top.png"));

		// Bottom
		Scene->GetMainCamera()->CaptureCubemap(CenterPos, CenterRot + float4(90, 0, 0), float4(512, 512));
		CaptureTarget->RenderTargetClear();
		CaptureTarget->Merge(Scene->GetMainCamera()->GetCameraRenderTarget());
		Ext_ScreenShoot::RenderTargetShoot_DxTex(CaptureTarget, Path, _CaptureTextureName.data() + std::string("_Bottom.png"));
		CaptureTarget->RenderTargetClear();

		Ext_DirectXTexture::LoadTexture(Path + "\\" + _CaptureTextureName.data() + "_Forword.png");
		Ext_DirectXTexture::LoadTexture(Path + "\\" + _CaptureTextureName.data() + "_Back.png");
		Ext_DirectXTexture::LoadTexture(Path + "\\" + _CaptureTextureName.data() + "_Right.png");
		Ext_DirectXTexture::LoadTexture(Path + "\\" + _CaptureTextureName.data() + "_Left.png");
		Ext_DirectXTexture::LoadTexture(Path + "\\" + _CaptureTextureName.data() + "_Top.png");
		Ext_DirectXTexture::LoadTexture(Path + "\\" + _CaptureTextureName.data() + "_Bottom.png");
	}

	CubeTexture = Ext_DirectXTexture::Find(_CaptureTextureName);

	if (nullptr == CubeTexture)
	{
		std::vector<std::shared_ptr<Ext_DirectXTexture>> CubeTextures;
		CubeTextures.reserve(6);

		CubeTextures.push_back(Ext_DirectXTexture::Find(_CaptureTextureName.data() + std::string("_Right.png")));
		CubeTextures.push_back(Ext_DirectXTexture::Find(_CaptureTextureName.data() + std::string("_Left.png")));
		CubeTextures.push_back(Ext_DirectXTexture::Find(_CaptureTextureName.data() + std::string("_Top.png")));
		CubeTextures.push_back(Ext_DirectXTexture::Find(_CaptureTextureName.data() + std::string("_Bottom.png")));
		CubeTextures.push_back(Ext_DirectXTexture::Find(_CaptureTextureName.data() + std::string("_Forword.png")));
		CubeTextures.push_back(Ext_DirectXTexture::Find(_CaptureTextureName.data() + std::string("_Back.png")));

		CubeTexture = Ext_DirectXTexture::LoadCubeMap(_CaptureTextureName, CubeTextures);
	}
}

 

미리 준비된 Texture가 없으면 스크린샷을 찍어서 Texture들을 6개 만들고, 그걸로 바로 Load를 실시해서 CubeMap을 만들어줍니다.

 

CaptureCubeMap() 함수는 그냥 해당 방향의 장면을 RenderTarget에 그리는 함수입니다. 이후 이 값을 가지고 Ext_ScreenShoot::RenderTargetShoot_DxTex()를 실시합니다.

HRESULT Ext_ScreenShoot::RenderTargetShoot_DxTex(std::shared_ptr<Ext_DirectXRenderTarget> _CaptureTarget, std::string_view _Path, std::string_view _TextureName)
{
    if (nullptr == _CaptureTarget)
    {
        return S_FALSE;
    }

    ID3D11Texture2D* Resource = _CaptureTarget->GetTexture(0)->GetTexture2D();

    if (nullptr != Resource)
    {
        wchar_t PrevPath[255];
        GetCurrentDirectoryW(255, PrevPath); // 기존 경로 저장
        SetCurrentDirectoryW(Base_String::AnsiToUniCode(_Path).data()); // 경로 변경

        DirectX::ScratchImage image;
        HRESULT result = CaptureTexture(Ext_DirectXDevice::GetDevice(), Ext_DirectXDevice::GetContext(), Resource, image);
        if (SUCCEEDED(result))
        {
            result = DirectX::SaveToWICFile(image.GetImages(), image.GetImageCount(), DirectX::WIC_FLAGS_NONE, GUID_ContainerFormatPng, Base_String::AnsiToUniCode(_TextureName).data());
        }
        
        SetCurrentDirectoryW(PrevPath); // 이전 경로 되돌리기
    }

    return S_FALSE;
}

 

해당 함수는 마이크로소프트에서 만든 Capture() 함수를 프레임워크에 맞게 조금 변형한 함수인데, 이 함수를 쓰기 위해서는 DirectXTK 라이브러리가 필요합니다. 라이브러리 적용은 기존에 제가 설명드렸던 방식대로 동일하게 진행하면 됩니다.

 

위 과정을 진행하면 스크린샷이 생성됩니다.

 

이제 이 Texture들로 스카이박스때와 마찬가지로 Texture를 Load 해줍니다.

 

이 기능을 사용하려면 Scene에서 Actor를 Create()하고, 위치를 정해준 다음 SetReflection() 함수를 호출해주면 됩니다.

// 리플렉션
std::shared_ptr<ReflectionActor> ReflectionActor1 = CreateActor<ReflectionActor>("ReflectionActor1");
ReflectionActor1->GetTransform()->SetLocalPosition({ 0.f, 100.f, -100.f});
ReflectionActor1->SetReflection();

 

Actor 내부에 임의로 구현한 코드인데, 위에 기능들을 만들어놨으니 이대로 사용하면 됩니다.

void ReflectionActor::SetReflection()
{
	Reflection = std::make_shared<Ext_ReflectionComponent>();
	static int n = 0;
	MeshComp->SetSampler("CubeMapSampler", "CubeMapSampler");
	Reflection->ReflectionInitialize(GetSharedFromThis<Ext_Actor>(), "TestReflection" + std::to_string(n++), float4(512, 512));
	MeshComp->SetTexture(Reflection->GetReflectionCubeTexture(), "ReflectionTexture");
}

 

리플렉션용 CubeMap Pixel Shader는 기존 Path와 분리해놨습니다.

#include "LightData.fx"

Texture2D BaseColorTex : register(t0); // 텍스처 자원
TextureCube ReflectionTexture : register(t1);
SamplerState Sampler : register(s0); // 샘플러
SamplerState CubeMapSampler : register(s1); // 샘플러

struct PSInput
{
    float4 Position : SV_POSITION;
    float2 TexCoord : TEXCOORD;
    float3 WorldPosition : POSITION0;
    float3 WorldNormal : NORMAL;
    float3 WorldTangent : TANGENT;
    float3 WorldBinormal : BINORMAL;
    float4 CameraWorldPosition : POSITION1;
};

struct PSOutPut
{
    float4 MeshTarget : SV_TARGET0;
    float4 PositionTarget : SV_TARGET1; // World Position Target
    float4 NormalTarget : SV_TARGET2; // World Normal Target
};

 // 각 벡터에 normalize를 해주는 이유는, 명시적으로 normalize된 벡터를 넣어줬다 하더라도 
 // 임의의 값이 어떻게 들어올지 모르기 때문에 그냥 해주는것(안정성을 위한 처리라고 보면 됨)
PSOutPut GraphicsCUBE_PS(PSInput _Input) : SV_TARGET
{
    PSOutPut Output = (PSOutPut) 0;

    float3 CameraPos = _Input.CameraWorldPosition;
    float3 ViewDir = normalize(CameraPos - _Input.WorldPosition);
    float3 Normal = normalize(_Input.WorldNormal);
    
    float3 ReflectDir = normalize(2.0f * Normal * dot(ViewDir, Normal) - ViewDir);
    float4 ReflectionColor = ReflectionTexture.Sample(CubeMapSampler, ReflectDir);
    
    float metallic = 1.0f;

    Output.MeshTarget = BaseColorTex.Sample(Sampler, _Input.TexCoord);
    Output.MeshTarget += float4(lerp(float3(0, 0, 0), ReflectionColor.rgb * 0.5f, metallic), 0.0f);

    Output.PositionTarget = float4(_Input.WorldPosition, 1.0f);
    Output.NormalTarget = float4(_Input.WorldNormal, 1.0f);

    return Output;
}

 

이렇게 해주면 내부에서 반사값이 적용된 뒤, 물체에 CubeMap 씌워집니다. 공식은 언리얼의 리플렉션 프로브와 동일합니다.

 

 

[결과 확인]

 

구체와 네모(Rect) 물체에 대해 적용하여 확인해봤습니다.

 

+ Recent posts