Direct 2D 행렬을 사용해서 게임 수학 배우기/행렬을 이용해서 출력 및 활용
행렬 - 행과 열로 이루어진 수리적인 개념
행 - 가로
열 - 세로
1 2 3
4 5 6
=> 2(행)x3(열)
행렬의 수식

덧셈뺄셈
덧셈 뺄셈이 이루어질시 행과 열이 같아야지 가능함
그리고 각 위치에 따라 덧셈 뺄셈 하면 됨
곱셈

앞의 열과 뒤에 행이 같아야지 곱셈이 가능!
2x(3) 과 (3)x2 => 행과 열이 같아야 함
게임에서의 행렬 연산
게임 수학 버텍스에서는 xyz가 있는데 3개임
게임 수학 에서는 4x4로 이루어져 있어서 맞춰서 행렬 연산이 불가능 해서
(x, y, z, 1) -> 1을 넣어줘서 계산
개념을 알았으면 코드로 적용해보자
상수버퍼
여기에서 주의해야 하는게 상수(변하지 않은 숫자) 인데 cpu에서는 값이 변하지 않는 변수로 const를 붙여서 사용한다.
이 의미랑 약간 다르고 gpu에서는 매 프레임 마다 호출하기 때문에 매 프레임에 변하지 않은 버퍼를 상수 버퍼라 한다.
실시간으로 정보를 바꾸기 위해서 사용하는거 이므로 Update함수가 들어간다.
따라서 상수 버퍼를 만들어보자 설명은 주석으로 담 !
#ConstBuffer.h
#pragma once
class ConstBuffer
{
public:
//구조체를 만드므로 구조체의 주소값과 크기를 준다.
ConstBuffer(void* data, UINT dataSize);
~ConstBuffer();
//쉐이더를 지정해서 넘겨줘야함 //slot번호도 줘야함
void SetVS(UINT slot); //버텍스쉐이더
void SetPS(UINT slot); //픽셀쉐이더
private:
ID3D11Buffer* buffer;
void* data;
UINT dataSize;
};
#ConstBuffer.cpp
#include "Framework.h"
ConstBuffer::ConstBuffer(void* data, UINT dataSize)
: data(data), dataSize(dataSize)
{
D3D11_BUFFER_DESC bufferDesc = {};
bufferDesc.Usage = D3D11_USAGE_DEFAULT; //우리 수준에선 이거 ^^
//버퍼를 쓰는 이유 : VRAM으로 넘겨야 GPU로 출력이 가능 //실시간 가능
//cpu에 할당된 메모리를 넘겨줘야함
bufferDesc.ByteWidth = dataSize;
bufferDesc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
DEVICE->CreateBuffer(&bufferDesc, nullptr, &buffer);
}
ConstBuffer::~ConstBuffer()
{
buffer->Release();
}
void ConstBuffer::SetVS(UINT slot)
{
//실시간 update
DC->UpdateSubresource(buffer, 0, nullptr, data, 0, 0);
DC->VSSetConstantBuffers(slot, 1, &buffer);
}
void ConstBuffer::SetPS(UINT slot)
{
//실시간 update
DC->UpdateSubresource(buffer, 0, nullptr, data, 0, 0);
DC->PSSetConstantBuffers(slot, 1, &buffer);
}
이런 상수 버퍼를 받아서 전역 버퍼를 만들어준다.
전역 버퍼로 자주쓰는 컬러버퍼를 생성해주고 생수버퍼를 상속해준다.
//GlobalBuffer.h
#pragma once
//Global Buffer
//전역 버퍼
//자주쓰는 상수 버퍼를 만들어줌
class ColorBuffer : public ConstBuffer
{
public:
ColorBuffer() : ConstBuffer(&color, sizeof(Float4)) {};
void SetColor(float r, float g, float b, float a = 1.0f)
{
color = { r,g,b,a };
}
Float4 GetColor() { return color; }
private:
Float4 color = {1, 1, 1, 1}; //흰색을 Default로 많이씀 //색 방해를 잘 안해줌
};

우리가 지금 출력한 공간은 ndc 좌표 3차원 공간 -> 스크린공간
3가지 변환이 이루어진다.
버텍스가 아마 몇십만개 일텐데 이거를 하나의 행렬로 묶어버림
정점을 하나의 메쉬로 먹는게 world 변환
카메라를 비추는게 view변환
카메라한 절두체를 스크린에 출력하는게 projection변환
projection은 Orthographic(원근감이 없음/2D, UI) 과 Perspective(사각뿔같은 원근감이 살아남/3D)로 나뉜다.
세개의 변환 과정을 행렬의 곱셈이 이루어지고 4x4 곱셈이 이루어짐
따라서 이 세개를 VS에 선언 해준다
//Vertex.hlsl
cbuffer WorldBuffer : register(b0) // PS랑 같아도 됨
{
matrix world; //64byte
}
cbuffer ViewBuffer : register(b1)
{
matrix view;
}
cbuffer ProjectionBuffer : register(b2)
{
matrix projection;
}
struct Input
{
float4 pos : POSITION;
float4 color : COLOR;
};
struct Output
{
float4 pos : SV_POSITION;
float4 color : COLOR;
};
Output VS(Input input)
{
Output output;
//순서 중요
output.pos = mul(input.pos, world);
output.pos = mul(output.pos, view);
output.pos = mul(output.pos, projection);
output.color = input.color;
return output;
}
Rs - 뷰표트,폴리곤 픽셀 선형구간
항등행렬 - 주대각선의 원소가 모두 1이며 나머지 원소는 모두 0인 정사각 행렬
행우선연산 열이 더 효율적
gpu에선 열 우선적
따라서 gpu에게 넘길때 전치행렬로 바꿔줘서 넘겨줘야함
전치행렬 - 행과 열이 바뀐 행렬
따라서 이걸 관리하는 MatrixBuffer을 만들어줌
//GlobalBuffer.h
#pragma once
//Global Buffer
//...
class MatrixBuffer : public ConstBuffer
{
public:
MatrixBuffer() : ConstBuffer(&matrix, sizeof(Matrix))
{
matrix = XMMatrixIdentity();
}
void Set(Matrix value)
{
//전치행렬
matrix = XMMatrixTranspose(value);
}
private:
Matrix matrix;
};
활용 하기 위해서 scene에 선언
//TutorialScene.h
#pragma once
class TutorialScene : public Scene
{
public:
TutorialScene();
~TutorialScene();
virtual void Update() override;
virtual void Render() override;
virtual void PostRender() override;
private:
VertexShader* vertexShader;
PixelShader* pixelShader;
VertexBuffer* vertexBuffer;
IndexBuffer* indexBuffer;
ColorBuffer* colorBuffer;
MatrixBuffer* worldBuffer;
MatrixBuffer* viewBuffer;
MatrixBuffer* projectionBuffer;
XMFLOAT4X4 worldMatrix;
//float value = 1.0f;
vector<VertexColor> vertices;
vector<UINT> indices;
};
//TutorialScene.cpp
#include "Framework.h"
#include "TutorialScene.h"
TutorialScene::TutorialScene()
{
vertexShader = Shader::AddVS(L"Vertex.hlsl");
pixelShader = Shader::AddPS(L"Pixel.hlsl");
UINT count = 100;
//Polygon : 정점 3개로 이루어진 3차원 상의 평면
//정점순서의 시계방향을 앞면으로 하며 앞면만 출력
float stepAngle = XM_2PI / count;
VertexColor center(0, 0, 1, 1, 1);
float radius = 100.0f;
vertices.push_back(center);
FOR(count)
{
float angle = stepAngle * i;
vertices.emplace_back(cos(angle) * radius, sin(angle) * radius, 0, 0, 1);
}
vertices.emplace_back(cos(0)* radius, sin(0) * radius, 0, 0, 1);
vertexBuffer = new VertexBuffer(vertices.data(), sizeof(VertexColor), vertices.size());
FOR(count)
{
indices.push_back(0);
if (i + 2 > count)
{
indices.push_back(1);
}
else
{
indices.push_back(i + 2);
}
indices.push_back(i + 1);
}
indexBuffer = new IndexBuffer(indices.data(), indices.size());
colorBuffer = new ColorBuffer();
worldBuffer = new MatrixBuffer();
viewBuffer = new MatrixBuffer();
projectionBuffer = new MatrixBuffer();
//projection에 맞추면 됨
//마우스 좌표랑 맞춰줌
//뷰왼쪽값,뷰반쪽값,바텀,위,왼쪽 ~ 오른쪽
Matrix orthographic = XMMatrixOrthographicOffCenterLH(0.0f, SCREEN_WIDTH,
0.0f, SCREEN_HEIGHT, -1.0f, 1.0f);
projectionBuffer->Set(orthographic);
}
TutorialScene::~TutorialScene()
{
delete vertexBuffer;
delete indexBuffer;
delete colorBuffer;
delete worldBuffer;
delete viewBuffer;
delete projectionBuffer;
}
void TutorialScene::Update()
{
//무작위 컬러 설정
float r = Random(0.0f, 1.0f);
float g = Random(0.0f, 1.0f);
float b = Random(0.0f, 1.0f);
colorBuffer->SetColor(r, g, b);
}
void TutorialScene::Render()
{
worldBuffer->SetVS(0);
viewBuffer->SetVS(1);
projectionBuffer->SetVS(2);
vertexBuffer->Set(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
indexBuffer->Set();
colorBuffer->SetPS(0); //슬롯 번호 //쉐이더에 있는 번호랑 맞춰야 함
vertexShader->Set();
pixelShader->Set();
DC->DrawIndexed(indices.size(), 0, 0);
//DC->Draw(vertices.size(), 0);
}
void TutorialScene::PostRender()
{
}
WorldMatirx
11 12 13 14
21 22 23 24
31 32 33 34
41 42 43 44
로 이루어져있다
11 22 33 44 를 1로 해줘야지 화면 출력이 되고
41 42 는 x랑 y좌표의 pos
11 22 은 x와 y좌표의 스케일
22 23 32 33 x축의 회전
11 13 31 33 y축의 회전
근데 x y 는 필요 없다
11 12 21 22 z축의 회전

따라서 키 설정 해주면 도형이 위치,크기,회전을 줄 수 있다.
//TutorialScene.h
//...
XMFLOAT4X4 worldMatrix;
//TutorialScene.cpp
TutorialScene::TutorialScene()
{
//...
//단위행렬로 만들어줌
worldMatrix._11 = 1;
worldMatrix._22 = 1;
worldMatrix._33 = 1;
worldMatrix._44 = 1;
//객체를 건드는건 41 42
worldMatrix._41 = SCREEN_WIDTH;
worldMatrix._42 = CENTER_Y;
}
//...
void TutorialScene::Update()
{
if (KEY->Press('W'))
worldMatrix._42 += 100.0f * DELTA;
if (KEY->Press('S'))
worldMatrix._42 -= 100.0f * DELTA;
if (KEY->Press('A'))
worldMatrix._41 -= 100.0f * DELTA;
if (KEY->Press('D'))
worldMatrix._41 += 100.0f * DELTA;
//11 = x scale
//22 = y scale
if (KEY->Press('T'))
worldMatrix._22 += 1.0f * DELTA;
if (KEY->Press('G'))
worldMatrix._22 -= 1.0f * DELTA;
if (KEY->Press('F'))
worldMatrix._11 -= 1.0f * DELTA;
if (KEY->Press('H'))
worldMatrix._11 += 1.0f * DELTA;
//회전
//API 픽셀 개념
//다이렉트 정점 기반
static float angle = 1.0f;
worldMatrix._11 = cos(angle);
worldMatrix._12 = sin(angle);
worldMatrix._21 = -sin(angle);
worldMatrix._22 = -cos(angle);
if (KEY->Press(VK_UP))
angle += DELTA;
if (KEY->Press(VK_DOWN))
angle -= DELTA;
//...
}
쉽게 쓰려고 바꿔줌
//TutorialScene.h
Vector2 pos;
Vector2 scale = {1.0f, 1.0f};
float angle = 0.0f;
//TutorialScene.cpp
//Update
if (KEY->Press('W'))
pos.y += 100.0f * DELTA;
if (KEY->Press('S'))
pos.y -= 100.0f * DELTA;
if (KEY->Press('A'))
pos.x -= 100.0f * DELTA;
if (KEY->Press('D'))
pos.x += 100.0f * DELTA;
Matrix T = XMMatrixTranslation(pos.x, pos.y, 0.0f);
if (KEY->Press('T'))
scale.y += 1.0f * DELTA;
if (KEY->Press('G'))
scale.y-= 1.0f * DELTA;
if (KEY->Press('F'))
scale.x-= 1.0f * DELTA;
if (KEY->Press('H'))
scale.x += 1.0f * DELTA;
Matrix S = XMMatrixScaling(scale.x, scale.y, 1.0f);
if (KEY->Press(VK_UP))
angle += DELTA;
if (KEY->Press(VK_DOWN))
angle -= DELTA;
Matrix R = XMMatrixRotationZ(angle);
//World변환
worldBuffer->Set(S*R*T);//순서중요
pos = mousePos; //마우스포스
이렇게 해주면 마우스 잘 따라다니는 모습을 볼 수 있음!
최종파일