공부 좀 해라★彡/DirectX

Direct 2D 행렬을 사용해서 게임 수학 배우기/행렬을 이용해서 출력 및 활용

요미 ★ 2024. 3. 3. 21:47

행렬 - 행과 열로 이루어진 수리적인 개념

행 - 가로

열 - 세로

 

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로 많이씀 //색 방해를 잘 안해줌
};

Pixel.hlsl에 등록해줘야 쓸수 있음 / PS쪽 color 곱하는 것도 잊지 말자


우리가 지금 출력한 공간은 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; //마우스포스 

이렇게 해주면 마우스 잘 따라다니는 모습을 볼 수 있음!


최종파일

Framework.zip
13.61MB