-
[DirectX11] 직교투영(Orthographic Projection)과 UI2024년 03월 14일
- 유니얼
-
작성자
-
2024.03.14.:29
728x90DirectX 11로 게임 엔진 아키텍처 만들기
3D 그래픽스와 게임 개발에서 직교투영(Orthographic Projection)은 세상을 2D 평면에 표현하는 중요한 방법 중 하나입니다. 직교투영은 모든 프로젝션 라인이 서로 평행하며, 관찰자와의 거리에 상관없이 객체의 크기가 일정하게 유지되도록 하는 특징을 가지고 있습니다. 이는 주로 2D 게임, CAD 프로그램, 일부 3D 게임에서 사용되며, 사용자에게 변경되지 않는 정확한 크기와 형태를 전달할 수 있습니다.
참고강의 링크:
직교투영의 개념
직교투영은 3D 공간의 객체를 관찰자와의 상대적 위치 변화 없이 2D 평면에 표현합니다. 이는 관찰자가 무한히 멀리서 물체를 바라보는 것과 유사하며, 결과적으로 모든 프로젝션 라인이 평행하고, 물체의 크기가 시점에 관계없이 일정하게 유지됩니다. 이러한 특성 때문에 직교투영은 건축도면, 엔지니어링 도면, 토폴로지 맵 등에서 널리 사용됩니다.
DirectX 11에서 직교투영 구현하기
DirectX 11에서 직교투영 행렬을 생성하는 것은 간단합니다. XMMatrixOrthographicLH 함수를 사용하면 원하는 폭(width), 높이(height), 근접 클리핑 평면(nearZ), 및 원거리 클리핑 평면(farZ)을 기반으로 직교투영 행렬을 쉽게 생성할 수 있습니다.
ProjectionType _type = ProjectionType::Perspertive; // 투영 타입 Matrix _matView = Matrix::Identity; // 뷰 매트릭스 Matrix _matProjection = Matrix::Identity; // 투영 매트릭스 // 카메라 관련 설정 변수들 float _near = 1.0f; float _far = 1000.f; float _fov = XM_PI / 4.0f; // 기본적으로 45도 설정 float _width = 0.f; float _height = 0.f; // 카메라의 뷰 및 투영 매트릭스를 업데이트하는 함수 void Camera::UpdateMatrix() { Vec3 eyePosition = GetTransform()->GetWorldPosition(); // 카메라의 위치 Vec3 focusPosition = eyePosition + GetTransform()->GetLook(); // 카메라가 바라보는 방향 Vec3 upDirection = GetTransform()->Getup(); // 카메라의 상단 방향 // 뷰 매트릭스 계산 _matView = ::XMMatrixLookAtLH(eyePosition, focusPosition, upDirection); // 투영 매트릭스 계산 if (_type == ProjectionType::Perspertive) { // 원근 투영을 사용하는 경우 _matProjection = ::XMMatrixPerspectiveFovLH(_fov, _width / _height, _near, _far); } else { // 직교 투영을 사용하는 경우 _matProjection = ::XMMatrixOrthographicLH(_width, _height, _near, _far); } }
이 함수는 폭과 높이를 통해 화면을 커버하는 뷰 볼륨을 정의하고, nearZ와 farZ 값은 뷰 볼륨의 깊이를 결정합니다. 이 행렬은 그 후 렌더링 파이프라인에서 사용될 수 있으며, 모든 3D 객체를 2D 평면에 직교투영으로 렌더링하는 데 필요한 변환을 제공합니다.
Camera.h
#pragma once #include "Component.h" // 카메라 투영 타입을 정의하는 열거형 enum class ProjectionType { Perspertive, // 원근 투영 Orthographic, // 직교 투영 }; // Component 클래스를 상속받는 Camera 클래스 정의 class Camera : public Component { using Super = Component; // 부모 클래스를 Super로 별칭 정의 public: Camera(); // 생성자 virtual ~Camera(); // 가상 소멸자 virtual void Update() override; // 컴포넌트의 Update 메소드 오버라이드 // 투영 타입 설정 및 반환 void SetProjectionType(ProjectionType type) { _type = type; } ProjectionType GetProjectionType() { return _type; } void UpdateMatrix(); // 뷰 및 투영 매트릭스 업데이트 // 카메라 설정 관련 메소드들 void SetNear(float value) { _near = value; } void SetFar(float value) { _far = value; } void SetFov(float value) { _fov = value; } // Field of View void SetWidth(float value) { _width = value; } void SetHeight(float value) { _height = value; } // 매트릭스 반환 메소드들 Matrix& GetViewMatrix() { return _matView; } Matrix& GetProjectionMatrix() { return _matProjection; } // 너비와 높이 반환 메소드들 float GetWidth() { return _width; } float GetHeight() { return _height; } private: ProjectionType _type = ProjectionType::Perspertive; // 투영 타입 Matrix _matView = Matrix::Identity; // 뷰 매트릭스 Matrix _matProjection = Matrix::Identity; // 투영 매트릭스 // 카메라 관련 설정 변수들 float _near = 1.0f; float _far = 1000.f; float _fov = XM_PI / 4.0f; // 기본적으로 45도 설정 float _width = 0.f; float _height = 0.f; public: static Matrix S_MatView; // 정적 뷰 매트릭스 static Matrix S_MatProjection; // 정적 투영 매트릭스 public: void SortGameObject(); // 게임 오브젝트 정렬 void Render_Forward(); // 포워드 렌더링 실행 // 렌더링 시 셀링 마스크 관련 메소드 void SetCullingMaskLayerOnOff(uint8 layer, bool on) { if (on) _cullingMask |= (1 << layer); else _cullingMask &= ~(1 << layer); } void SetCullingMaskAll() { SetCullingMask(UINT32_MAX); } // 모든 레이어를 켬 void SetCullingMask(uint32 mask) { _cullingMask = mask; } // 셀링 마스크 설정 bool IsCulled(uint8 layer) { return (_cullingMask & (1 << layer)) != 0; }; // 특정 레이어가 컬링됐는지 확인 private: uint32 _cullingMask = 0; // 렌더링할 레이어를 결정하는 비트 마스크 vector<shared_ptr<GameObject>> _vecForward; // 포워드 렌더링할 게임 오브젝트 목록 };
Camera.cpp
#include "pch.h" #include "Camera.h" #include "Scene.h" // 정적 멤버 변수 초기화 Matrix Camera::S_MatView = Matrix::Identity; Matrix Camera::S_MatProjection = Matrix::Identity; // 카메라 생성자 Camera::Camera() : Super(ComponentType::Camera) { // 게임 화면의 너비와 높이로 카메라의 초기 크기 설정 _width = static_cast<float>(GAME->GetGameDesc().width); _height = static_cast<float>(GAME->GetGameDesc().height); } // 카메라 소멸자 Camera::~Camera() { } // 매 프레임마다 호출되는 업데이트 함수 void Camera::Update() { UpdateMatrix(); // 매트릭스 업데이트 // 전역 데이터로 뷰 매트릭스와 투영 매트릭스를 렌더러에 푸시 //RENDER->PushGlobalData(Camera::S_MatView, Camera::S_MatProjection); } // 카메라의 뷰 및 투영 매트릭스를 업데이트하는 함수 void Camera::UpdateMatrix() { Vec3 eyePosition = GetTransform()->GetWorldPosition(); // 카메라의 위치 Vec3 focusPosition = eyePosition + GetTransform()->GetLook(); // 카메라가 바라보는 방향 Vec3 upDirection = GetTransform()->Getup(); // 카메라의 상단 방향 // 뷰 매트릭스 계산 _matView = ::XMMatrixLookAtLH(eyePosition, focusPosition, upDirection); // 투영 매트릭스 계산 if (_type == ProjectionType::Perspertive) { // 원근 투영을 사용하는 경우 _matProjection = ::XMMatrixPerspectiveFovLH(_fov, _width / _height, _near, _far); } else { // 직교 투영을 사용하는 경우 _matProjection = ::XMMatrixOrthographicLH(_width, _height, _near, _far); } } // 게임 오브젝트를 정렬하는 함수 void Camera::SortGameObject() { shared_ptr<Scene> scene = CUR_SCENE; // 현재 씬 unordered_set<shared_ptr<GameObject>>& gameObjects = scene->GetObjects(); // 씬에 있는 게임 오브젝트들 _vecForward.clear(); // 정렬된 오브젝트 목록 초기화 for (auto& gameobject : gameObjects) { if (IsCulled(gameobject->GetLayerIndex())) // 레이어에 따라 컬링 여부 결정 continue; // 렌더링 가능한 컴포넌트가 없는 오브젝트는 무시 if (gameobject->GetMeshRenderer() == nullptr && gameobject->GetModelRenderer() == nullptr && gameobject->GetModelAnimator() == nullptr) continue; _vecForward.push_back(gameobject); // 렌더링 대상 목록에 추가 } } // 포워드 렌더링을 실행하는 함수 void Camera::Render_Forward() { S_MatView = _matView; // 정적 뷰 매트릭스 업데이트 S_MatProjection = _matProjection; // 정적 투영 매트릭스 업데이트 // 인스턴싱 매니저를 통해 렌더링 실행 GET_SINGLE(InstancingManager)->Render(_vecForward); }
UI 버튼의 구현
UI 버튼은 사용자로부터의 입력을 받고, 특정 작업을 수행하기 위해 클릭할 수 있는 인터페이스 요소입니다. DirectX 또는 다른 그래픽스 라이브러리에서 UI 버튼을 구현할 때, 직교투영을 사용하여 2D 인터페이스를 3D 환경 위에 렌더링할 수 있습니다. UI 버튼은 텍스처, 텍스트 레이블, 클릭 이벤트 핸들러 등으로 구성될 수 있으며, 사용자가 버튼 위로 마우스를 이동하거나 클릭했을 때 시각적 피드백을 제공합니다.
DirectX 11에서의 UI 버튼 예제
DirectX 11을 사용하여 UI 버튼을 생성하고 관리하기 위해서는 스프라이트 배치, 텍스처, 마우스 입력 처리 등을 관리할 수 있는 시스템이 필요합니다. 직교투영을 사용하여 2D UI 요소를 렌더링할 수 있으며, 버튼의 클릭 이벤트를 처리하기 위한 로직도 구현해야 합니다.
Button.h
#pragma once #include "Component.h" // Component 클래스를 상속받는 Button 클래스 선언 class Button : public Component { using Super = Component; // 부모 클래스 Component에 대한 별칭으로 Super를 사용 public: Button(); // Button 클래스의 생성자 virtual ~Button(); // Button 클래스의 가상 소멸자 // 화면상의 위치에 대한 픽킹(선택) 검사를 수행하는 함수 bool Picked(POINT screenPos); // 버튼을 생성하는 함수. 화면 위치, 크기, 사용할 재질을 매개변수로 받음 void Create(Vec2 screenPos, Vec2 size, shared_ptr<class Material> material); // 클릭 이벤트에 대한 콜백 함수를 추가하는 함수 void AddOnClickedEvent(std::function<void(void)> func); // 클릭 이벤트 발생 시 등록된 콜백 함수를 호출하는 함수 void InvokeOnClicked(); private: std::function<void(void)> _onClicked; // 클릭 이벤트에 대한 콜백 함수를 저장하는 멤버 변수 RECT _rect; // 버튼의 화면상 위치와 크기를 나타내는 RECT 구조체 };
Button.cpp
#include "pch.h" #include "Button.h" #include "MeshRenderer.h" #include "Material.h" // Button 클래스의 생성자 Button::Button() : Super(ComponentType::Button) { // 기본 컴포넌트 타입을 Button으로 설정합니다. } // Button 클래스의 소멸자 Button::~Button() { // 리소스 해제 또는 정리가 필요한 경우 여기에 작성합니다. } // Button이 클릭되었는지 판단하는 함수 bool Button::Picked(POINT screenPos) { // 클릭된 화면 좌표가 버튼 영역 안에 있는지 확인합니다. return ::PtInRect(&_rect, screenPos); } // Button을 생성하고 초기화하는 함수 void Button::Create(Vec2 screenPos, Vec2 size, shared_ptr<class Material> material) { auto go = _gameObject.lock(); // 소유한 GameObject에 대한 약한 참조를 얻습니다. float height = GRAPHICS->GetViewport().GetHeight(); // 뷰포트 높이 float width = GRAPHICS->GetViewport().GetWidth(); // 뷰포트 너비 // 화면 좌표를 월드 좌표로 변환합니다. float x = screenPos.x - width / 2; float y = height / 2 - screenPos.y; Vec3 position = Vec3(x, y, 0); // GameObject의 위치와 크기를 설정합니다. go->GetOrAddTransform()->SetWorldPosition(position); go->GetOrAddTransform()->SetWorldScale(Vec3(size.x, size.y, 1)); // GameObject의 레이어를 UI로 설정합니다. go->SetLayerIndex(Layer_UI); // MeshRenderer 컴포넌트를 추가하거나 가져옵니다. if (go->GetMeshRenderer() == nullptr) go->AddComponent(make_shared<MeshRenderer>()); // MeshRenderer에 Material을 설정합니다. go->GetMeshRenderer()->SetMaterial(material); // 기본적으로 "Quad" 메쉬를 사용하여 버튼을 표현합니다. auto mesh = RESOURCES->Get<Mesh>(L"Quad"); go->GetMeshRenderer()->SetMesh(mesh); go->GetMeshRenderer()->SetPass(0); // 버튼의 픽킹 영역을 설정합니다. _rect.left = screenPos.x - size.x / 2; _rect.right = screenPos.x + size.x / 2; _rect.top = screenPos.y - size.y / 2; _rect.bottom = screenPos.y + size.y / 2; } // 클릭 이벤트에 콜백 함수를 추가하는 함수 void Button::AddOnClickedEvent(std::function<void(void)> func) { _onClicked = func; // 등록된 클릭 이벤트 콜백 함수를 설정합니다. } // 클릭 이벤트 콜백 함수를 호출하는 함수 void Button::InvokeOnClicked() { if (_onClicked) // 클릭 이벤트 콜백 함수가 설정되어 있다면 _onClicked(); // 해당 함수를 호출합니다. }
프로젝트 호출
ButtonDemo.h
#pragma once #include "IExecute.h" class ButtonDemo :public IExecute { public: void Init() override; void Update() override; void Render() override; private: shared_ptr<Shader> _shader; };
ButtonDemo.cpp
#include "pch.h" #include "RawBuffer.h" #include "TextureBuffer.h" #include "Material.h" #include "ButtonDemo.h" #include "GeometryHelper.h" #include "Camera.h" #include "GameObject.h" #include "CameraScript.h" #include "MeshRenderer.h" #include "Mesh.h" #include "Material.h" #include "Model.h" #include "ModelRenderer.h" #include "ModelAnimator.h" #include "Mesh.h" #include "Transform.h" #include "VertexBuffer.h" #include "IndexBuffer.h" #include "Light.h" #include "Graphics.h" #include "SphereCollider.h" #include "Scene.h" #include "AABBBoxCollider.h" #include "OBBBoxCollider.h" #include "Terrain.h" #include "Camera.h" #include "Button.h" void ButtonDemo::Init() { _shader = make_shared<Shader>(L"23. RenderDemo.fx"); // Camera { auto camera = make_shared<GameObject>(); camera->GetOrAddTransform()->SetWorldPosition(Vec3{ 0.f, 0.f, -5.f }); camera->AddComponent(make_shared<Camera>()); //camera->AddComponent(make_shared<CameraScript>()); camera->GetCamera()->SetCullingMaskLayerOnOff(Layer_UI, true); CUR_SCENE->Add(camera); } // UI_Camera { auto camera = make_shared<GameObject>(); camera->GetOrAddTransform()->SetWorldPosition(Vec3{ 0.f, 0.f, -5.f }); camera->AddComponent(make_shared<Camera>()); camera->GetCamera()->SetProjectionType(ProjectionType::Orthographic); camera->GetCamera()->SetNear(1.f); camera->GetCamera()->SetFar(100.f); camera->AddComponent(make_shared<CameraScript>()); camera->GetCamera()->SetCullingMaskAll(); camera->GetCamera()->SetCullingMaskLayerOnOff(Layer_UI, false); CUR_SCENE->Add(camera); } // Light { auto light = make_shared<GameObject>(); light->AddComponent(make_shared<Light>()); LightDesc lightDesc; lightDesc.ambient = Vec4(0.4f); lightDesc.diffuse = Vec4(1.f); lightDesc.specular = Vec4(0.1f); lightDesc.direction = Vec3(1.f, 0.f, 1.f); light->GetLight()->SetLightDesc(lightDesc); CUR_SCENE->Add(light); } // Material { shared_ptr<Material> material = make_shared<Material>(); material->SetShader(_shader); auto texture = RESOURCES->Load<Texture>(L"UnityLogo", L"..\\Resources\\Texture\\UnityLogo.png"); 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"UnityLogo", material); } // Mesh { auto obj = make_shared<GameObject>(); obj->AddComponent(make_shared<Button>()); obj->GetButton()->Create(Vec2(100, 100), Vec2(100, 100), RESOURCES->Get<Material>(L"UnityLogo")); obj->GetButton()->AddOnClickedEvent([obj]() { CUR_SCENE->Remove(obj); }); CUR_SCENE->Add(obj); } // Mesh { auto obj = make_shared<GameObject>(); obj->GetOrAddTransform()->SetLocalPosition(Vec3(0.f)); obj->GetOrAddTransform()->SetWorldScale(Vec3(2.f)); obj->AddComponent(make_shared<MeshRenderer>()); { obj->GetMeshRenderer()->SetMaterial(RESOURCES->Get<Material>(L"UnityLogo")); } { auto mesh = RESOURCES->Get<Mesh>(L"Sphere"); obj->GetMeshRenderer()->SetMesh(mesh); obj->GetMeshRenderer()->SetPass(0); } CUR_SCENE->Add(obj); } } void ButtonDemo::Update() { } void ButtonDemo::Render() { }
결론
직교투영은 3D 세계를 2D 평면에 표현할 때 중요한 도구입니다. 건축, 엔지니어링, 그리고 게임 디자인 등 다양한 분야에서 유용하게 사용되며, DirectX 11과 같은 그래픽 API를 통해 쉽게 구현할 수 있습니다. 직교투영을 사용함으로써 개발자는 관찰자의 시점 변경에 상관없이 일정한 크기와 형태를 유지하는 2D 이미지를 생성할 수 있으며, 이는 특정 응용 프로그램에서 매우 유용할 수 있습니다.
반응형다음글이전글이전 글이 없습니다.댓글