여러가지 기능을 구현하면서 오브젝트들을 여러개 꺼내 테스트해보니 그림자가 없어서 물체들의 거리감이 느껴지지가 않았다. 게임은 비주얼적인 부분이 아주 중요하다고 생각하기 때문에 내가 불편하다 느끼면 바로 고쳐야 직성이 풀린다. 조금 우여곡절이 있긴 했지만 섀도우맵은 구현해냈다. 골격을 사용하는 모델의 경우에도 잘 적용되도록 Skinned Mesh에 대한 확장성도 고려해서 쉐이더를 작성해줬다.
문제는 여기서부터다. 안그래도 예전부터 애니메이션이 적용된 테스트 모델을 불러올 때면 프레임이 기하급수적으로 떨어졌다. 뭐 별거 하는거도 없는데 평균 130 중반정도. 거기에 더해 모델의 움직임을 구현하면 10프레임정도 더 떨어졌다. 움직이면 프레임이 추가적으로 더 떨어지는걸 보고 당연히 Transform의 갱신과 관련된 문제라고 생각은 들었지만 맘대로 건드리기가 참 겁났다. 일단 가장 수학이 많이 적용되는 컴포넌트기도 하고, 부모자식 관계를 고려하다보면 진짜 머리가 아파지기 때문.
그래서 처음에는 움직임에 초점을 맞추지 않고 애니메이션에 초점을 맞춰서 최적화를 진행했다. CPU단에서 처리하는 부분에 한정해서 할 수 있는 최적화 작업은 다 진행했다고 생각됐지만.. 솔직히 프레임 확보에는 완전히 실패한듯 보였다. 여전히 평균 프레임은 130 중반대를 기록하고 있었기 때문이다. 난 각오를 하고는 Transform의 최적화 작업에 돌입했다.
이것저것 알아보다가 알아낸 방법은 Layz Update(간혹 Propagation이라고도 부르는 것 같다) 라고 불리는 알고리즘이였다. 직역하면 게으른 갱신 정도가 될듯 싶은데, 말 그대로다. 갱신이 필요한 객체는 곧바로 계산을 하지 않고 별도의 Flag값을 갖는다. 그리고 나중에 해당 객체의 값을 필요로할 때 그제서야 변화된 값을 계산하는 것.
말은 쉬운데 별도의 Flag 값을 갖는다는 데에서 조금 헷갈렸다. 왜냐하면 지금도 Frame Resource를 사용하면서 FramesDirtyFlag 값을 가지기 때문이다. 그래서 그냥 이 값을 사용하면 되는거 아닌가? 싶었는데 두 값은 완전히 구분해서 생각하는게 맞았다. 한 쪽은 특정 FrameResource에서 해당 객체를 GPU로 넘겨줄 때 값의 갱신이 필요한지, 그냥 있는 값을 쓰면 되는건지 확인하기위한 Flag 값이고 한 쪽은 어떤 시점에서든 객체의 값을 참조할 때 갱신이 필요한지 확인하기 위함이다.
이런 식으로 구현해두면 상속관계가 복잡한 어떤 한 객체의 값을 참조하려고 할 때 딱 필요한 최소한의 연산만으로 갱신된 값을 구해낼 수 있고, 평소에는 연산을 하지 않아 비용을 절약할 수 있다. Transform 코드는 가장 마지막에 첨부한다. 일단은 최적화 진행 전과 후를 비교하자면 다음과 같다.


사진에서 볼 수 있다싶이 평균 130 중반정도의 fps를 보여준다.


최적화를 진행하고 나서는 평균 330정도의 fps를 보여준다. 단순 계산만으로도 2.5배정도의 성능 향상이 이뤄졌다. 물론 최적화를 빡세게 한다고 하면 여기저기 손볼 곳이 많겠지만 가장 큰 문제였던 부분을 손봤기 때문에 당분간은 이정도로 괜찮지 않을까 싶다.
Transform.h
#pragma once
#include "Component.h"
class Transform : public Component
{
using Super = Component;
public:
Transform();
virtual ~Transform();
void Update() override;
void Render() override;
public:
Vector3 GetLocalPosition() {
if (_isDirty)
UpdateTransform();
return _localPosition;
}
void SetLocalPosition(const Vector3& position) {
_localPosition = position;
SetDirtyFlag();
}
// Get/Set Local Rotation With Degree
Vector3 GetLocalRotation() {
if (_isDirty)
UpdateTransform();
return MathHelper::RadianToDegree(_localRotation);
}
void SetLocalRotation(const Vector3& rotation) {
SetLocalRotationRadian(MathHelper::DegreeToRadian(rotation));
}
// Get/Set Local Rotation With Radian
Vector3 GetLocalRotationRadian() {
if (_isDirty)
UpdateTransform();
return _localRotation;
}
void SetLocalRotationRadian(const Vector3& rotation);
Vector4 GetLocalQuaternion() {
if (_isDirty)
UpdateTransform();
return _quaternion;
}
void SetLocalQuaternion(const Vector4& quaternion);
Vector3 GetLocalScale() {
if (_isDirty)
UpdateTransform();
return _localScale;
}
void SetLocalScale(const Vector3& scale) {
_localScale = scale;
SetDirtyFlag();
}
Vector3 GetPosition() {
if (_isDirty)
UpdateTransform();
return _position;
}
void SetPosition(const Vector3& worldPosition);
// Get/Set Rotation With Degree
Vector3 GetRotation() {
if (_isDirty)
UpdateTransform();
return MathHelper::RadianToDegree(_rotation);
}
void SetRotation(const Vector3& worldRotation) { SetRotationRadian(MathHelper::DegreeToRadian(worldRotation)); }
// Get/Set Rotation With Radian
Vector3 GetRotationRadian() {
if (_isDirty)
UpdateTransform();
return _rotation;
}
void SetRotationRadian(const Vector3& worldRotation);
Vector3 GetScale() {
if (_isDirty)
UpdateTransform();
return _scale;
}
void SetScale(const Vector3& worldScale);
XMVECTOR GetQuaternion() {
if (_isDirty)
UpdateTransform();
return XMLoadFloat4(&_quaternion);
}
XMMATRIX GetRotationMatrix();
Vector3 GetRight();
Vector3 GetLeft();
Vector3 GetUp();
Vector3 GetDown();
Vector3 GetLook();
Vector3 GetBack();
void Translate(const Vector3& moveVec);
void Rotate(const Vector3& angle);
void Rotate(const XMVECTOR& angle);
void LookAt(const Vector3& targetPos);
void SetLocalMatrix(XMFLOAT4X4 mat);
void SetWorldMatrix(XMFLOAT4X4 mat);
XMFLOAT4X4 GetLocalMatrix();
XMFLOAT4X4 GetWorldMatrix();
shared_ptr<Transform> GetParent() { return _parent; }
void SetParent(shared_ptr<Transform> parent);
const vector<shared_ptr<Transform>>& GetChilds() { return _childs; }
void AddChild(shared_ptr<Transform> child);
bool HasParent() { return _parent != nullptr; }
void SetDirtyFlag();
private:
void UpdateTransform();
private:
bool _isDirty = false;
Vector3 _localPosition;
Vector3 _localRotation;
Vector3 _localScale;
Vector3 _position;
Vector3 _rotation;
Vector3 _scale;
Vector4 _quaternion;
XMFLOAT4X4 _matLocal;
XMFLOAT4X4 _matWorld;
shared_ptr<Transform> _parent;
vector<shared_ptr<Transform>> _childs;
};
Transform.cpp
#include "pch.h"
#include "Transform.h"
Transform::Transform() : Super(ComponentType::Transform)
{
_localPosition = { 0.0f, 0.0f, 0.0f };
_localRotation = { 0.0f, 0.0f, 0.0f };
_localScale = { 1.0f, 1.0f, 1.0f };
_quaternion = { 0.0f, 0.0f, 0.0f ,1.0f };
_parent = nullptr;
_isDirty = true;
UpdateTransform();
}
Transform::~Transform()
{
}
void Transform::Update()
{
}
void Transform::Render()
{
}
void Transform::UpdateTransform()
{
// 앞에서 조건 다 걸러내긴 하는데 혹시 몰라서 한번 더 확인
if (!_isDirty)
return;
XMMATRIX matScale = XMMatrixScaling(_localScale.x, _localScale.y, _localScale.z);
XMMATRIX matRotation = XMMatrixRotationQuaternion(XMLoadFloat4(&_quaternion));
XMMATRIX matTranslation = XMMatrixTranslation(_localPosition.x, _localPosition.y, _localPosition.z);
XMMATRIX matTransform = matScale * matRotation * matTranslation;
XMStoreFloat4x4(&_matLocal, matTransform);
if (HasParent())
{
XMMATRIX parentWorld = XMLoadFloat4x4(&_parent->GetWorldMatrix());
XMStoreFloat4x4(&_matWorld, matTransform * parentWorld);
}
else
{
_matWorld = _matLocal;
}
_isDirty = false;
XMVECTOR scale;
XMVECTOR quaternion;
XMVECTOR position;
XMMatrixDecompose(
&scale,
&quaternion,
&position,
XMLoadFloat4x4(&_matWorld));
XMStoreFloat3(&_scale, scale);
_rotation = MathHelper::ConvertQuaternionToEuler(quaternion);
XMStoreFloat3(&_position, position);
for (auto& child : _childs)
{
child->SetDirtyFlag();
}
}
void Transform::SetLocalRotationRadian(const Vector3& rotation)
{
_localRotation = rotation;
XMStoreFloat4(&_quaternion, XMQuaternionNormalize(XMQuaternionRotationRollPitchYaw(rotation.x, rotation.y, rotation.z)));
SetDirtyFlag();
}
void Transform::SetLocalQuaternion(const Vector4& quaternion)
{
XMStoreFloat4(&_quaternion, XMQuaternionNormalize(XMLoadFloat4(&quaternion)));
SetDirtyFlag();
}
void Transform::SetPosition(const Vector3& worldPosition)
{
if (HasParent())
{
XMMATRIX inverseWorldMat = XMMatrixInverse(nullptr, XMLoadFloat4x4(&_parent->GetWorldMatrix()));
Vector3 position;
XMStoreFloat3(&position, XMVector3Transform(XMLoadFloat3(&worldPosition), inverseWorldMat));
SetLocalPosition(position);
}
else
{
SetLocalPosition(worldPosition);
}
}
void Transform::SetRotationRadian(const Vector3& worldRotation)
{
XMVECTOR qWorld = XMQuaternionRotationRollPitchYaw(
worldRotation.x,
worldRotation.y,
worldRotation.z);
Vector3 localEuler = worldRotation;
if (HasParent())
{
XMMATRIX parentRotMat = _parent->GetRotationMatrix();
XMVECTOR qParent = XMQuaternionRotationMatrix(parentRotMat);
XMVECTOR qLocal = XMQuaternionMultiply(qWorld, XMQuaternionInverse(qParent));
localEuler = MathHelper::ConvertQuaternionToEuler(qLocal);
}
SetLocalRotationRadian(localEuler);
}
void Transform::SetScale(const Vector3& worldScale)
{
if (HasParent())
{
Vector3 parentScale = _parent->GetScale();
Vector3 scale = worldScale;
scale.x /= parentScale.x;
scale.y /= parentScale.y;
scale.z /= parentScale.z;
SetLocalScale(scale);
}
else
{
SetLocalScale(worldScale);
}
}
XMMATRIX Transform::GetRotationMatrix()
{
return XMMatrixRotationQuaternion(XMLoadFloat4(&_quaternion));
}
Vector3 Transform::GetRight()
{
if (_isDirty)
UpdateTransform();
return Vector3(-_matLocal._11, -_matLocal._21, _matLocal._31);
}
Vector3 Transform::GetLeft()
{
if (_isDirty)
UpdateTransform();
return Vector3(_matLocal._11, _matLocal._21, -_matLocal._31);
}
Vector3 Transform::GetUp()
{
if (_isDirty)
UpdateTransform();
return Vector3(_matLocal._12, _matLocal._22, -_matLocal._32);
}
Vector3 Transform::GetDown()
{
if (_isDirty)
UpdateTransform();
return Vector3(-_matLocal._12, -_matLocal._22, _matLocal._32);
}
Vector3 Transform::GetLook()
{
if (_isDirty)
UpdateTransform();
return Vector3(-_matLocal._13, -_matLocal._23, _matLocal._33);
}
Vector3 Transform::GetBack()
{
if (_isDirty)
UpdateTransform();
return Vector3(_matLocal._13, _matLocal._23, -_matLocal._33);
}
void Transform::Translate(const Vector3& moveVec)
{
_localPosition = _localPosition + moveVec;
SetDirtyFlag();
}
void Transform::Rotate(const Vector3& angle)
{
XMVECTOR currentQuat = XMLoadFloat4(&_quaternion);
XMVECTOR deltaQuat = XMQuaternionRotationRollPitchYaw(angle.x, angle.y, angle.z);
XMVECTOR newQuat = XMQuaternionNormalize(XMQuaternionMultiply(currentQuat, deltaQuat));
_quaternion = { newQuat.m128_f32[0], newQuat.m128_f32[1], newQuat.m128_f32[2], newQuat.m128_f32[3] };
_localRotation = MathHelper::ConvertQuaternionToEuler(newQuat);
SetDirtyFlag();
}
void Transform::Rotate(const XMVECTOR& angle)
{
Vector3 f3_angle;
XMStoreFloat3(&f3_angle, angle);
Rotate(f3_angle);
}
void Transform::LookAt(const Vector3& targetPos)
{
XMVECTOR targetVec = XMLoadFloat3(&(targetPos - GetPosition()));
XMVECTOR sideVec = XMVector3Normalize(XMVector3Cross(targetVec, XMVECTOR({ 0.0f, 1.0f, 0.0f })));
XMVECTOR upVec = XMVector3Normalize(XMVector3Cross(targetVec, sideVec));
if (upVec.m128_f32[1] < 0.0f)
upVec = -upVec;
upVec = { 0.0f, 1.0f, 0.0f };
XMMATRIX viewMat = XMMatrixLookAtLH(XMLoadFloat3(&GetPosition()), XMLoadFloat3(&targetPos), upVec);
viewMat = XMMatrixTranspose(viewMat);
XMVECTOR quat = XMQuaternionRotationMatrix(viewMat);
Vector3 euler = MathHelper::ConvertQuaternionToEuler(quat);
SetRotationRadian(euler);
SetDirtyFlag();
}
void Transform::SetLocalMatrix(XMFLOAT4X4 mat)
{
_matLocal = mat;
XMVECTOR scale;
XMVECTOR quaternion;
XMVECTOR position;
XMMatrixDecompose(
&scale,
&quaternion,
&position,
XMLoadFloat4x4(&_matLocal));
XMStoreFloat3(&_localScale, scale);
_localRotation = MathHelper::ConvertQuaternionToEuler(quaternion);
XMStoreFloat3(&_localPosition, position);
SetDirtyFlag();
}
void Transform::SetWorldMatrix(XMFLOAT4X4 mat)
{
_matWorld = mat;
if (HasParent())
XMStoreFloat4x4(&_matLocal, XMLoadFloat4x4(&_matWorld) * XMMatrixInverse(nullptr, XMLoadFloat4x4(&_parent->GetWorldMatrix())));
else
_matLocal = _matWorld;
SetLocalMatrix(_matLocal);
}
XMFLOAT4X4 Transform::GetLocalMatrix()
{
if (_isDirty)
UpdateTransform();
return _matLocal;
}
XMFLOAT4X4 Transform::GetWorldMatrix()
{
if (HasParent())
{
XMMATRIX parent = XMLoadFloat4x4(&_parent->GetWorldMatrix());
if (_isDirty)
UpdateTransform();
XMMATRIX local = XMLoadFloat4x4(&_matLocal);
XMFLOAT4X4 result;
XMStoreFloat4x4(&result, local * parent);
return result;
}
else
{
if (_isDirty)
UpdateTransform();
return _matWorld;
}
}
void Transform::SetParent(shared_ptr<Transform> parent)
{
_parent = parent;
if (HasParent())
XMStoreFloat4x4(&_matLocal, XMLoadFloat4x4(&_matWorld) * XMMatrixInverse(nullptr, XMLoadFloat4x4(&_parent->GetWorldMatrix())));
else
_matLocal = _matWorld;
_parent->AddChild(static_pointer_cast<Transform>(shared_from_this()));
SetLocalMatrix(_matLocal);
}
void Transform::AddChild(shared_ptr<Transform> child)
{
_childs.push_back(child);
}
void Transform::SetDirtyFlag()
{
_isDirty = true;
GetGameObject()->SetFramesDirty();
for (auto& child : _childs)
child->SetDirtyFlag();
}
'공부 > DirectX12' 카테고리의 다른 글
| DirectX12 콜라이더 디버깅을 위한 직선 렌더링 시스템 구현 (1) | 2025.07.09 |
|---|---|
| std::max()에 3개 이상의 인자를 사용하는 방법이 안될 때 (0) | 2025.05.25 |
| DirectX12 ImGUI 라이브러리 세팅 (0) | 2025.04.15 |
| 메시와 머터리얼의 관계에 따른 문제 발생 및 해결 (0) | 2025.04.10 |
| 툰 셰이딩에서 얼굴 명암 처리에 대해서 (0) | 2025.04.04 |