[DirectX] 원형 메쉬 및 좌표 변환
in Study on WinAPI&DirectX
원 그려보기
Circle Mesh(원형 메쉬)를 추가해보자.
Circle Mesh는 원 모양의 피자를 쪼갠 것 처럼
가운데의 점을 중심으로 삼각형 메쉬들이 원형으로 모여있는 형태로 만들어진다.
그런데 원 모양이 나오도록 일일이 점을 모두 찍을 수는 없다. 그럼 어떻게 해야할까?
원형 좌표계
이때, 직교좌표계 x,y를 반지름(r)과 중심각(θ)을 사용하는 원형 좌표계로 표현하면 (rcosθ, rsinθ)로 나타낼 수 있다.
이를 이용하면
// CResMgr.cpp 파일 중에서
// 반지름
float fRadius = 0.5f; // 반지름이 0.5
// 각도
UINT Slice = 40; // 40 등분을 할 경우
float fTheta = XM_2PI / (float)Slice;
// 중심점
v.vPos = Vec3(0.f, 0.f, 0.f);
v.vColor = Vec4(1.f, 1.f, 1.f, 1.f);
v.vUV = Vec2(0.5f, 0.5f);
vecVtx.push_back(v);
for (UINT i = 0; i < Slice; ++i)
{
v.vPos = Vec3(fRadius * cosf(fTheta * (float)i), fRadius * sinf(fTheta * (float)i), 0.f);
v.vUV = Vec2(vPos.x + 0.5f, -vPos.y + 0.5f);
vecVtx.push_back(v);
}
// 인덱스 설정
for (UINT i = 0; i < Slice - 1; ++i)
{
vecIdx.push_back(0);
vecIdx.push_back(i + 2);
vecIdx.push_back(i + 1);
}
// 마지막 삼각형
vecIdx.push_back(0);
vecIdx.push_back(1);
vecIdx.push_back(Slice);
위와 같은 과정을 통해 원형 메쉬를 만들 수 있다.
투영 좌표계 및 좌표 변환
만약에 카메라가 추가된다면, 카메라가 보고 있는 시점 기준으로 NDC가 설정되어야 한다.
그렇게 되면 원점이 바뀌게 된다.
이렇게 되면 기준이 달라지고 좌표계가 바뀌게 된다.
본래 위치는 같지만 화면 상에 찍혀야하는 위치가 바뀌는 것이다.
1차적으로 Local로부터 World에 물체를 배치하고(1차 변환), 2차적으로 카메라가 보는 시점으로 다시 좌표를 배치한다.(2차 변환)
2차 변환 때부터는 카메라가 원점역할을 한다.
쉐이더 코드의 변화
월드 변환(1차 변환)에서 버퍼가 넘겨주어야 하는 자료가 좌표 변화량 뿐만 아니라 크기 변화량, 회전 변화량을 넘겨주어야 한다.
여기서, 3D에서는 행렬 형태로 자료를 넘긴다.
Vec3 m_vRelativePos;
Vec3 m_vRelativeScale;
Vec3 m_vRelativeRot;
위의 자료를
Matrix m_matWorld;
위와 같은 행렬로 넘긴다. 크기, 회전, 좌표 이동 정보를 모아놓은 것이다.
그리고 카메라 기준으로 좌표를 바뀌도록 행렬도 설정해주어야한다.
또한, 이렇게 바뀐 좌표를 NDC좌표로 바꿔주는 행렬도 필요하다.
cbuffer TRANSFORM : register(b0)
{
matrix g_matWorld;
matrix g_matView;
matrix g_matProj;
}
그래서 쉐이더 코드 중에서 TRANSFORM 버퍼 내용이 위와 같이 행렬을 3개로 받는 구조로 바뀐다.
struct tTransfrom
{
Matrix g_matWorld;
Matrix g_matView;
Matrix g_matProj;
}
그리고 위와 같이 구조체를 만들어준다.
또한, 행렬이 전치되는 것을 방지하기 위해서 아래와 같이 코드를 작성한다.
cbuffer TRANSFORM : register(b0)
{
row_major matrix g_matWorld;
row_major matrix g_matView;
row_major matrix g_matProj;
}
변화한 쉐이더코드는 아래와 같다.
#ifndef _TEST
#define _TEST
cbuffer TRANSFORM : register(b0)
{
row_major matrix g_matWorld;
row_major matrix g_matView;
row_major matrix g_matProj;
};
cbuffer MATERIAL : register(b1)
{
int g_int_0;
int g_int_1;
int g_int_2;
int g_int_3;
float g_float_0;
float g_float_1;
float g_float_2;
float g_float_3;
float2 g_vec2_0;
float2 g_vec2_1;
float2 g_vec2_2;
float2 g_vec2_3;
float4 g_vec4_0;
float4 g_vec4_1;
float4 g_vec4_2;
float4 g_vec4_3;
matrix g_mat_0;
matrix g_mat_1;
matrix g_mat_2;
matrix g_mat_3;
};
Texture2D g_tex_0 : register(t0);
Texture2D g_tex_1 : register(t1);
Texture2D g_tex_2 : register(t2);
Texture2D g_tex_3 : register(t3);
Texture2D g_tex_4 : register(t4);
Texture2D g_tex_5 : register(t5);
Texture2D g_tex_6 : register(t6);
Texture2D g_tex_7 : register(t7);
SamplerState g_sam_0 : register(s0);
SamplerState g_sam_1 : register(s1);
// VS 입력 구조체
struct VS_IN
{
float3 vPos : POSITION; // semantic
float4 vColor : COLOR;
float2 vUV : TEXCOORD;
};
struct VS_OUT
{
float4 vPosition : SV_Position;
float4 vOutColor : COLOR;
float2 vOutUV : TEXCOORD;
};
// vertex shader
// LocalSpace 물체를 NDC 좌표계로 이동
VS_OUT VS_Test(VS_IN _in)
{
VS_OUT output = (VS_OUT) 0.f;
// 입력으로 들어온 정점좌표에 상수버퍼 값을 더해서 출력
output.vPosition = mul(float4(_in.vPos, 1.f), g_matWorld);
output.vOutColor = _in.vColor;
output.vOutUV = _in.vUV;
return output;
}
// pixel shader
float4 PS_Test(VS_OUT _in) : SV_Target
{
float4 vColor = (float4) 0.f;
if(g_int_0 == 0)
vColor = g_tex_0.Sample(g_sam_0, _in.vOutUV);
else if(g_int_0 == 1)
vColor = g_tex_0.Sample(g_sam_1, _in.vOutUV);
return vColor;
}
#endif
CDevice.cpp의 변화
그리고 위에서 만들어준 구조체를 받는 코드로 바꾸어준다.
#include "pch.h"
#include "CDevice.h"
#include "CEngine.h"
#include "CConstBuffer.h"
CDevice::CDevice()
: m_hWnd(nullptr)
, m_ViewPort{}
, m_arrConstBuffer{}
{
}
CDevice::~CDevice()
{
Safe_Del_Array(m_arrConstBuffer);
}
int CDevice::init(HWND _hWnd, UINT _iWidth, UINT _iHeight)
{
m_hWnd = _hWnd;
m_vRenderResolution = Vec2((float)_iWidth, (float)_iHeight);
int iFlag = 0;
#ifdef _DEBUG
iFlag = D3D11_CREATE_DEVICE_DEBUG;
#endif
D3D_FEATURE_LEVEL eLevel = D3D_FEATURE_LEVEL::D3D_FEATURE_LEVEL_11_0;
if (FAILED(D3D11CreateDevice(nullptr, D3D_DRIVER_TYPE_HARDWARE
, nullptr, iFlag
, nullptr, 0
, D3D11_SDK_VERSION
, m_Device.GetAddressOf(), &eLevel
, m_Context.GetAddressOf())))
{
MessageBox(nullptr, L"Device, Context 생성 실패", L"Device 초기화 에러", MB_OK);
return E_FAIL;
}
if (FAILED(CreateSwapChain()))
{
MessageBox(nullptr, L"스왚체인 생성 실패", L"Device 초기화 에러", MB_OK);
return E_FAIL;
}
if (FAILED(CreateView()))
{
MessageBox(nullptr, L"View 생성 실패", L"Device 초기화 에러", MB_OK);
return E_FAIL;
}
// 출력 타겟 설정
m_Context->OMSetRenderTargets(1, m_RTV.GetAddressOf(), m_DSV.Get());
// ViewPort 설정
m_ViewPort.TopLeftX = 0.f;
m_ViewPort.TopLeftY = 0.f;
Vec2 vWindowResol = CEngine::GetInst()->GetWindowResolution();
m_ViewPort.Width = vWindowResol.x;
m_ViewPort.Height = vWindowResol.y;
m_ViewPort.MinDepth = 0.f;
m_ViewPort.MaxDepth = 1.f;
m_Context->RSSetViewports(1, &m_ViewPort);
// 샘플러 생성
if (FAILED(CreateSampler()))
{
MessageBox(nullptr, L"샘플러 생성 실패", L"Device 초기화 에러", MB_OK);
return E_FAIL;
}
// 상수버퍼 생성
CreateConstBuffer();
return S_OK; // E_FAIL;
}
int CDevice::CreateSwapChain()
{
// 스왚체인 설정
DXGI_SWAP_CHAIN_DESC tDesc = {};
tDesc.OutputWindow = m_hWnd; // 출력 윈도우
tDesc.Windowed = true; // 창모드, 전체화면 모드
tDesc.BufferCount = 1;
tDesc.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
tDesc.BufferDesc.Width = (UINT)m_vRenderResolution.x;
tDesc.BufferDesc.Height = (UINT)m_vRenderResolution.y;
tDesc.BufferDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
tDesc.BufferDesc.RefreshRate.Denominator = 1;
tDesc.BufferDesc.RefreshRate.Numerator = 60;
tDesc.BufferDesc.Scaling = DXGI_MODE_SCALING_UNSPECIFIED;
tDesc.BufferDesc.ScanlineOrdering = DXGI_MODE_SCANLINE_ORDER::DXGI_MODE_SCANLINE_ORDER_UNSPECIFIED;
tDesc.SwapEffect = DXGI_SWAP_EFFECT::DXGI_SWAP_EFFECT_DISCARD;
tDesc.SampleDesc.Count = 1;
tDesc.SampleDesc.Quality = 0;
tDesc.Flags = 0;
// 스왚체인 생성
ComPtr<IDXGIDevice> pDXGIDevice;
ComPtr<IDXGIAdapter> pAdapter;
ComPtr<IDXGIFactory> pFactory;
HRESULT hr = S_OK;
hr = m_Device->QueryInterface(__uuidof(IDXGIDevice), (void**)pDXGIDevice.GetAddressOf());
hr = pDXGIDevice->GetParent(__uuidof(IDXGIAdapter), (void**)pAdapter.GetAddressOf());
hr = pAdapter->GetParent(__uuidof(IDXGIFactory), (void**)pFactory.GetAddressOf());
hr = pFactory->CreateSwapChain(m_Device.Get(), &tDesc, m_SwapChain.GetAddressOf());
return hr;
}
int CDevice::CreateView()
{
m_SwapChain->GetBuffer(0, __uuidof(ID3D11Texture2D), (void**)m_RTTex.GetAddressOf());
if (FAILED(m_Device->CreateRenderTargetView(m_RTTex.Get(), nullptr, m_RTV.GetAddressOf())))
{
return E_FAIL;
}
// DepthStencil 용도 텍스쳐 생성
D3D11_TEXTURE2D_DESC tDesc = {};
tDesc.Format = DXGI_FORMAT_D24_UNORM_S8_UINT;
// 반드시 렌더타겟과 같은 해상도로 설정해야 함
tDesc.Width = (UINT)m_vRenderResolution.x;
tDesc.Height = (UINT)m_vRenderResolution.y;
tDesc.ArraySize = 1;
tDesc.BindFlags = D3D11_BIND_DEPTH_STENCIL;
tDesc.Usage = D3D11_USAGE_DEFAULT;
tDesc.CPUAccessFlags = 0;
tDesc.MipLevels = 1; // 원본만 생성
tDesc.SampleDesc.Count = 1;
tDesc.SampleDesc.Quality = 0;
if (FAILED(m_Device->CreateTexture2D(&tDesc, nullptr, m_DSTex.GetAddressOf())))
{
return E_FAIL;
}
// DepthStencilView 생성
if (FAILED(m_Device->CreateDepthStencilView(m_DSTex.Get(), nullptr, m_DSV.GetAddressOf())))
{
return E_FAIL;
}
return S_OK;
}
int CDevice::CreateSampler()
{
D3D11_SAMPLER_DESC tSamDesc = {};
tSamDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
tSamDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
tSamDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
tSamDesc.Filter = D3D11_FILTER_ANISOTROPIC;
DEVICE->CreateSamplerState(&tSamDesc, m_Sampler[0].GetAddressOf());
tSamDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
tSamDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
tSamDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
tSamDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_POINT;
DEVICE->CreateSamplerState(&tSamDesc, m_Sampler[1].GetAddressOf());
CONTEXT->VSSetSamplers(0, 1, m_Sampler[0].GetAddressOf());
CONTEXT->HSSetSamplers(0, 1, m_Sampler[0].GetAddressOf());
CONTEXT->DSSetSamplers(0, 1, m_Sampler[0].GetAddressOf());
CONTEXT->GSSetSamplers(0, 1, m_Sampler[0].GetAddressOf());
CONTEXT->PSSetSamplers(0, 1, m_Sampler[0].GetAddressOf());
CONTEXT->VSSetSamplers(1, 1, m_Sampler[1].GetAddressOf());
CONTEXT->HSSetSamplers(1, 1, m_Sampler[1].GetAddressOf());
CONTEXT->DSSetSamplers(1, 1, m_Sampler[1].GetAddressOf());
CONTEXT->GSSetSamplers(1, 1, m_Sampler[1].GetAddressOf());
CONTEXT->PSSetSamplers(1, 1, m_Sampler[1].GetAddressOf());
return S_OK;
}
void CDevice::ClearTarget(float(&_color)[4])
{
m_Context->ClearRenderTargetView(m_RTV.Get(), _color);
m_Context->ClearDepthStencilView(m_DSV.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.f, 0);
}
void CDevice::CreateConstBuffer()
{
m_arrConstBuffer[(UINT)CB_TYPE::TRANSFORM] = new CConstBuffer((UINT)CB_TYPE::TRANSFORM);
m_arrConstBuffer[(UINT)CB_TYPE::TRANSFORM]->Create(sizeof(tTransform), 1);
m_arrConstBuffer[(UINT)CB_TYPE::MATERIAL] = new CConstBuffer((UINT)CB_TYPE::MATERIAL);
m_arrConstBuffer[(UINT)CB_TYPE::MATERIAL]->Create(sizeof(tMtrlConst), 1);
}
동차좌표
차원을 동일하게 만들어주기 위해서 늘려주는 좌표이다.
행렬의 연산을 진행할 때 첫번째 행렬의 행의 수와 두번째 행렬의 열의 수가 맞아야 연산이 가능해진다.
하지만 그렇지 않다면 임의의 값을 추가로 넣어서 행의 수나 열의 수를 늘려준다.
이 때 사용되는 추가로 사용되는 좌표를 동차좌라고 한다.
크기, 위치, 회전 변환의 순서
먼저 결론을 이야기 하면 크기, 회전, 위치 순으로 진행한다.
만약에 위치를 바꾸고 크기 변환을 하면 도형의 중심에서 크기가 늘어나는 것이 아니라, 로컬 좌표계의 원점을 기준으로 크기가 늘어나는 것이기 때문에 의도한 바와 달리 좌표가 뒤틀리는 현상이 발생한다.
따라서 크기를 먼저 변환시킨 다음에 이동을 진행하는 것이다.
그리고 행렬의 곱셈은 교환법칙이 성립하지 않는다.
이에 따라 크기를 먼저 변환하느냐, 위치를 먼저 변환하느냐 여부에 따라서 결과 값이 달라질 수 있다.
회전, 이동 간의 관계도 마찬가지이다.
이동을 먼저 해버리면 원래 위치가 아니라 이상한 곳으로 물체가 옮겨지게 된다.
크기, 회전, 이동을 모두 포함하는 행렬을 World 행렬이라고 한다.
CTransform 클래스의 변화
위에 언급한 대로 크기, 회전, 이동 정보를 행렬 정보로 받는다.
이때 Matrix는 4 x 4 행렬로 구성된다.
그리고 cpp파일의 finaltick에서 world 행렬을 계산할 것이다.
// 헤더파일
#pragma once
#include "CComponent.h"
class CTransform :
public CComponent
{
private:
Vec3 m_vRelativePos;
Vec3 m_vRelativeScale;
Vec3 m_vRelativeRot;
Matrix m_matWorld; // 크기, 회전, 이동 정보를 합쳐놓음
public:
void SetRelativePos(Vec3 _vPos) { m_vRelativePos = _vPos; }
void SetRelativeScale(Vec3 _vScale) { m_vRelativeScale = _vScale; }
void SetRelativeRot(Vec3 _vRot) { m_vRelativeRot = _vRot; }
void SetRelativePos(float _x, float _y, float _z) { m_vRelativePos = Vec3(_x, _y, _z); }
void SetRelativeScale(float _x, float _y, float _z) { m_vRelativeScale = Vec3(_x, _y, _z); }
void SetRelativeRot(float _x, float _y, float _z) { m_vRelativeRot = Vec3(_x, _y, _z); }
Vec3 GetRelativePos() { return m_vRelativePos; }
Vec3 GetRelativeScale() { return m_vRelativeScale; }
Vec3 GetRelativeRot() { return m_vRelativeRot; }
public:
virtual void finaltick() override;
void UpdateData();
CLONE(CTransform);
public:
CTransform();
~CTransform();
};
// cpp파일
#include "pch.h"
#include "CTransform.h"
#include "CDevice.h"
#include "CConstBuffer.h"
CTransform::CTransform()
: CComponent(COMPONENT_TYPE::TRANSFORM)
, m_vRelativeScale(Vec3(1.f, 1.f, 1.f))
{
}
CTransform::~CTransform()
{
}
void CTransform::finaltick()
{
Matrix matScale = XMMatrixIdentity();
XMMatrixScaling(m_vRelativeScale.x, m_vRelativeScale.y, m_vRelativeScale.z);
Matrix matRot = XMMatrixIdentity();
matRot = XMMatrixRotationX(m_vRelativeRot.x);
matRot *= XMMatrixRotationY(m_vRelativeRot.y);
matRot *= XMMatrixRotationZ(m_vRelativeRot.z);
Matrix matTranslation = XMMatrixTranslation(m_vRelativePos.x, m_vRelativePos.y, m_vRelativePos.z);
m_matWorld = matScale * matRot * matTranslation;
}
void CTransform::UpdateData()
{
// 위치값을 상수버퍼에 전달 및 바인딩
CConstBuffer* pTransformBuffer = CDevice::GetInst()->GetConstBuffer(CB_TYPE::TRANSFORM);
pTransformBuffer->SetData(&m_matWorld, sizeof(Matrix));
pTransformBuffer->UpdateData();
}
CPlayerScript.cpp의 변화
그리고 잘 적용되었는지 확인하기 위해서 코드를 아래와 같이 바꾼다.
A키를 누르면 회전할 것이다.
#include "pch.h"
#include "CPlayerScript.h"
#include "CMeshRender.h"
#include "CMaterial.h"
CPlayerScript::CPlayerScript()
{
}
CPlayerScript::~CPlayerScript()
{
}
void CPlayerScript::tick()
{
Vec3 vCurPos = Transform()->GetRelativePos();
if (KEY_PRESSED(KEY::UP))
{
for (int i = 0; i < 4; ++i)
{
vCurPos.y += DT * 1.f;
}
}
if (KEY_PRESSED(KEY::DOWN))
{
for (int i = 0; i < 4; ++i)
{
vCurPos.y -= DT * 1.f;
}
}
if (KEY_PRESSED(KEY::LEFT))
{
for (int i = 0; i < 4; ++i)
{
vCurPos.x -= DT * 1.f;
}
}
if (KEY_PRESSED(KEY::RIGHT))
{
for (int i = 0; i < 4; ++i)
{
vCurPos.x += DT * 1.f;
}
}
Transform()->SetRelativePos(vCurPos);
if (KEY_PRESSED(KEY::A))
{
Vec3 vRot = Transform()->GetRelativeRot();
vRot.z += DT * XM_PI;
Transform()->SetRelativeRot(vRot);
}
if (KEY_TAP(KEY::_1))
{
int a = 0;
MeshRender()->GetMaterial()->SetScalarParam(INT_0, &a);
}
else if (KEY_TAP(KEY::_2))
{
int a = 1;
MeshRender()->GetMaterial()->SetScalarParam(INT_0, &a);
}
}