ImGui 활용한 DirectX 11 3D를 이용한 2D를 만드는 내용입니다.
본 내용은 똑같이 사용할 수 없음을 알립니다. (참고및 공부자료로 만들어진 내용임을 밝힙니다.)
전 과정에 대해 이해를 해야 다음 이 부분에 대한 이해를 할 수 있음을 밝힙니다.
이전과정) https://ppatabox.tistory.com/24
목차 )
1) 사각형 월드에 집어넣기 구현원리
2) 랜덤으로 돌리는 사각형
--------------------------------------------------------------------------------------------------------------
1)Rect.cpp 에서 Rect::Rect()부분에서 변경을 해준다.
그러하면 끝에 사각형이 나오는걸 볼 수 있는데
0~ 1까지 꽉채워놨는데
월드를 쓰면 월드에 대한 상대의 크기로 결정 되어서 이렇게 화면에 꽉차지 않는다 .
그럼이제 월드를 넣어보자
Rect::Rect()
{
float minusX = +0;
float plusX = +1;
float minusY = +0;
float plusY = +1; //변경부분
vertices[0] = Vertex(D3DXVECTOR3(minusX, minusY, 0), D3DXCOLOR(0, 1, 0, 1));
vertices[1] = Vertex(D3DXVECTOR3(minusX, plusY, 0), D3DXCOLOR(1, 0, 0, 1));
vertices[2] = Vertex(D3DXVECTOR3(plusX, minusY, 0), D3DXCOLOR(0, 0, 1, 1));
vertices[3] = Vertex(D3DXVECTOR3(plusX, minusY, 0), D3DXCOLOR(0, 0, 1, 1));
vertices[4] = Vertex(D3DXVECTOR3(minusX, plusY, 0), D3DXCOLOR(1, 0, 0, 1));
vertices[5] = Vertex(D3DXVECTOR3(plusX, plusY, 0), D3DXCOLOR(1, 0, 0, 1));
.
.
.
일단 main.cpp에 해두고 분리를 하자
우리가 어떤 위치가 하나 있다고 치자
정점하나의 위치를 처리한다고 생각해보면 W V P(3D)공간이여서 VS에서 처리를 한다(정점이기 때문에)
View port라는 것이 있는데 (RS= 3D 에서 2D로 바꿔주는것 리니어 인터폴레이션 일어남)
2D(PS) 형식의 순서로 처리 된다.
world, view, projection은 변환행렬이라고 부른다
뭔가 공간을 변환하기 위한 행렬 (Tansform행렬) world만
world는 아래 빨간색이 위치를 결정하고
이것을 넣기 위해서 4X4의 변환행렬을 만들것이다.
자료형을 다 만들면 답이 없기에
D3DX = 9.0이하버전
11에서도 가끔 쓴다 수학클래스가 11로 다루면 복잡하다
9.0버전은 간단
D3DXMATRIX = 4X4행렬을 지원해주는 자료형이다 (D3DX랑)
D3D11이 =11버전
2) main.cpp에서
D3DXMATRIX world; 를 상단에 만들어준다 .
매트릭스의 초기화는 기존의 방법이랑은 다르다
제로벡터 를 쓰면 저게 전부다 0인 행렬이 된다
대각선으로 1 1 1 xyz가 되어있는데
전부 0이 되니까 크기 부분이 아무것도 안나온다
1이면 1배 / 0이면 0배
그래서 매트릭스 초기화 할때는 제로는 안되고
벡터는 가능하다
D3DXMatrixIdentity(&world); // 매트릭스를 초기화 해주는 함수
월드를 넣으려면 버퍼가 하나 필요하다 버퍼도 하나 만들어 주자
ID3D11Buffer* constantBuffer;(버텍스 버퍼 처럼)
또한 Buffer.cpp를 보고 main.cpp와 비교해 보자
옆에 D3D11_BUFFER_DESC desc = { 0 }; 부분이 있다.
정의할려고 설정해야 하니까 Buffer하려면 이게 필요했는데
이걸 복사 해오자 똑같은 버퍼인데 용도만 다를 뿐 이다.
한번 들어가면 안바꿀 것이다. 그러면
이 world를 이용한 정점의 위치를 안건드릴 것이다.(한번만 쓰고 묶어버리는 것)
Default는 CPU에서 쓰는거 불가 GPU에서 쓸수 있다
우리는 CPU에서 GPU로 보내는 것
현재까지는 CPU<->RAM 사이간의 데이터를
GPU<->VRAM 으로만 일방적으로 보내는 걸 사용하고 있었다
그런데 Default를 쓰면 불가능 하다고 했는데
(GPU에서 CPU로는 가능 하지만 CPU에서 GPU로는 불가능하다 원래는)
그런데 예외 사항이 있다 (UpdateSubResource사용할 수 있다 )
크기는 알수 없는데 아까 만들어둔 world를 구조체 안에 넣어보자
그리고 View와 Projection도 구조체 안에 넣어두자
ID3D11Buffer* constantBuffer;
struct ConstantBufferDesc
{
D3DXMATRIX World;
D3DXMATRIX View;
D3DXMATRIX Projection;//3D에서의 개념을 다루는 것
};
이제 3개다 초기화를 해주어야 하니까
ConstantBufferDesc bufferDesc; 변수하나 만들고
ID3D11Buffer* constantBuffer;
struct ConstantBufferDesc
{
D3DXMATRIX World;
D3DXMATRIX View;
D3DXMATRIX Projection;//3D에서의 개념을 다루는 것
};
Rect* rect = NULL;
void InitScene()
{
ConstantBufferDesc bufferDesc; //변수선언
D3DXMatrixIdentity(&bufferDesc.World);
D3DXMatrixIdentity(&bufferDesc.View);
D3DXMatrixIdentity(&bufferDesc.Projection); //초기화
이렇게 하고 나서 이것의Default의 ByteWidth는 이것이 들어가고
버퍼의 데이터가 될 것이다.
그다음 bine.flags는 버텍스 말고 컨스탄트 버퍼(상수버퍼)
D3D11_BUFFER_DESC desc = { 0 };
desc.Usage = D3D11_USAGE_DEFAULT;
desc.ByteWidth = sizeof(ConstantBufferDesc);// 이것이 들어가고 버퍼의 데이터가 될 것이다.
desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER; //상수버퍼 사용
그다음 Device이용해서 CreateBuffer해준다.
D3D11_BUFFER_DESC desc = { 0 };
desc.Usage = D3D11_USAGE_DEFAULT;
desc.ByteWidth = sizeof(ConstantBufferDesc);
desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
HRESULT hr = Device->CreateBuffer(&desc, NULL, &constantBuffer); // (desc들어가고 , 서브리소스 데이터(NULL), 우리가 만들버퍼)
assert(SUCCEEDED(hr));
- AT : 카메라가 바라보는 지점
- EYE : 카메라의 위치
- UP : 카메라의 상향 벡터
//예시
D3DXMATRIX* D3DXMatrixLookAtLH(
_Inout_ D3DXMATRIX *pOut, // 카메라 변환 행렬
_In_ const D3DXVECTOR3 *pEye, // 카메라 위치
_In_ const D3DXVECTOR3 *pAt, // 카메라가 바라보는 지점
_In_ const D3DXVECTOR3 *pUp // 카메라의 상향 벡터
);
이제 월드 셋팅에 들어갈 것이다.
View먼저 하자
View는 눈위치가 필요하다 눈위치에서 어느 방향을 바라보는 것
그릴려고 하는 원점을 0이라고 해보자
Z이 들어갈수록 플러스 Z는 0에 있는 것이다.
그래서 눈위치를 살짝 뒤로 뺀 것
lookAt 바라볼 초점 (위치) 바로 앞에 있는 걸 쳐다 볼 것
계산 할때 필요한 것이 있는데 눈이 같은 0 이였다고 치지만
눈 바로앞에서 볼 수 있는데 아래를 쳐다 볼 수 도 있다
그래서 머리의 수직각도 가 중요 바로 정면의 90도를 바라볼거 라서 고개 회전이 없는(위)
방향을 쓸때는 단위벡터를 흔히 사용한다
D3DXMatrixLookAtLH = View를 자동으로 계산해주는 함수 위 eye , lookAt, up 을 가지고 계산함
D3DXVECTOR3 eye(0, 0, -1); //카메라 위치
D3DXVECTOR3 lookAt(0, 0, 0); // 카메라가 바라보는 지점
D3DXVECTOR3 up(0, 1, 0); // 카메라의 상향벡터
D3DXMatrixLookAtLH(&bufferDesc.View, &eye, &lookAt, &up);
중요)
행렬을 다룰때 2가지가 있다
일단 2D에서 행우선을 다룰 것인데 열우선으로 바꿀 것이다.
크기는 X, Y, Z 동일 한데
위치는 열단위에서
행우선이 계산하기 편함
언리얼도 행우선
열우선(원래는 시스템 적으로 열우선이 더 빠르고 OpenGL은 열우선 사용
DX는 빠져나와서 독자적인 함수를 사용 (원본 기술을 뒤집어 둔다)
프로그래머가 계산 할때는 행우선이 편함
시스템 입장에서는 열우선이 빠르다
그래서 내부적으로 보통 행우선을 쓰면
열우선으로 바꾸는 옵션을 넣던가
아니면 바꿔서 데이터를 입력해준다.(넣어준다)
예시로 왜 열우선이 빠른지 보면 ( SISD, SIMD, MISD, MIMD )
우리가 그래픽 카드에서 쓰는 형태가 SIMD 방식이다. (cpu도 SIMD방식이 있긴한데 거의 SISD)
I(Instruction): 명령
D(Data)
CPU / (SISD) : 하나의 명령어로 하나의 데이터를 처리하겠다는 의미
GPU / (SIMD) : 하나의 명령어로 여러개의 데이터를 처리하겠다.
MISD : 여러개의 명령어로 하나의 데이터를 처리하겠다.(이론상으로만 존재)
데이터 하나 나올것인데 왜 명령어 여러개가 들어가나?
MIMD : 여러개의 명령어로 여러개의 데이터를 다루겠다.(양자컴퓨터 쯤 가야.. 실현?)
SIMD 데이터를 처리할때 모든 하드웨어는 데이터를 오른쪽에서 부터 읽어드린다
행우선이면 완성이 될까?
X방향 Y방향 성립이 될 수 있긴한데.
하드웨어는 오른쪽 부터 읽는다고 했다.
Z축방향 | 위치 | ||||
X | 1 | 0 | 0 | 0 | 하 |
Y | 0 | 1 | 0 | 0 | 드 |
Z | 0 | 0 | 1 | 0 | 웨 |
0 | 0 | 0 | 1 | 어 | |
X | Y | Z |
오른쪽 열부터 읽었을 텐데 X Y Z순서인데 성립 할 수가 없다.
우린 프로그래머 기준으로 넣었으니까.
열우선은 그대로 오른쪽에서 부터 열로 넣게 되면 성립을 하게된다.
행렬이 뒤집어져 있으니까.
그래서 열로만 읽었을때 위치를 다은칸에서 z축을
SIMD를 보았을때 열우선으로 쓰는게 빠르다 .
오른쪽에서 부터
DX9은 행우선이였는데
DX10 열우선으로 바뀜
그럼에도 행우선을 쓰면 더 익숙하게 받아들일 수 있고
책 저자들이 거의다 행우선으로 쓰고 있음
추가 설명은 맨아래 참조
행우선을 열우선으로 바꿔주겠다는 (뒤집는 작업 (Transpose) )
D3DXMatrixTranspose(&bufferDesc.View, &bufferDesc.View);
DX에서는 포인터 인데 리턴해주는 값은 앞에 out이라고 붙여 둔다(나오는 값이라고)
그다음 프로젝션
우리가 화면에 그려진 영역(3D에서)
맥락정도만 이해해자
프로젝션 기법 2가지
예를들어 우리가 어떤 물체가 있다고 치자
보면 물체에 원근감이 느껴진다 .
3D에서 쓰는 perspective(관점)
원근투영법 perspective 방식
2D게임은 멀어지고 작아지면 어색하다.
거리에 따른 크기 차이 변화가 없는 Orthographic Projection (직교투영)
거리에 따른 크기차이 변화가 없는 방식을 사용할 것이다.
원근감이 없는
Ortho(직교투영 약자로 쓰임)
offCenter( 우리가 기준점 중심을 잡을 것 )
D3DXMatrixOrthoOffCenterLH(&bufferDesc.Projection, 0, (float)Width, 0, (float)Height, -1, +1);
D3DXMatrixTranspose(&bufferDesc.Projection, &bufferDesc.Projection);
왼쪽 하단을 0으로 회전높이 Height
깊이 /
전치로 프로젝션 해준다.
-part2-
이것을 GPU쪽으로 밀어줘야 한다.
ConstantBuffer는 밀어주는 방식이 좀 다르다.
main.cpp
IA는 시작할때 넣는 데이터가 되는데
Constantbuffer는 쉐이더에 넣는 거기에 IA가 안붙는다.
지금은 GPU에 바로 넣는것
DeviceContext->VSSetConstantBuffers(0, 1, &constantBuffer);
정점처리 쉐이더를 작성하자
우리가 뭔가 집어넣고 이동하고 회전하는 것을
공간변환 이라고 말한다 .
우리가 어떤 (0, 0, 0) 의 어떠한 위치가 있다고 생각해보자
벡터는 위치를 표기하거나 방향을 표기 한다.
이 위치를 어느 공간에 변환 시키면 변환된 위치가 나온다
벡터는 행렬
4X4행렬을 tansform이라 얘기 했는데
다른 말로 공간이라고도 말한다.
벡텨와 행렬곱
행렬곱을 하면 공간변환이 된 결과가 나온다.
행렬변환을 쉐이더에서 하는것 행렬곱을 수행하는 명령어
mul(벡터(위치벡터) , 행렬) 이 나옴
effect.hlsl에서 보면
input.position (위치)
정점 하나당 이 쉐이더 한번을 col해야함
정점이 6개 이니까 6개가 col됨
각 위치 마다 mul
Effect.hlsl
struct VertexInput
{
float4 Position : POSITION0;
float4 Color : COLOR0;
};
struct PixelInput
{
float4 Position : SV_POSITION;
float4 Color : COLOR0;
};
cbuffer VS_Wvp : register(b0) //constantbuffer를 다뤄줘야하는데
{ //cbuffer는 구조체 모양인데 이 안에가 전역으로 취급됨 월드라고 전역이라고 취급된다.
matrix World; //
matrix View;
matrix Projection;
}
PixelInput VS(VertexInput input)
{
PixelInput output;
output.Position = mul(input.Position, World); //월드 해주기 (전역변수)
output.Position = mul(output.Position, View); // 월드 변환된 결과 (output.position에서 월드에서 뷰로 변환
output.Position = mul(output.Position, Projection); //뷰로 변환된 결과를 프로젝션 해주는 것
output.Color = input.Color;
return output;
}
float4 PS(PixelInput input) : SV_TARGET
{
return input.Color;
}
main.cpp에서
초기데이터 설정하는 것을 넣어주자.(수정부분)
void InitScene() 부분
D3D11_SUBRESOURCE_DATA subResource = { 0 };
subResource.pSysMem = &bufferDesc;
만약 실행하면 하단 크기에서 보이게 된다 .
그리고 원래는 가운데를 0으로 맞춘다.
이제 이것을 이동시킬려고 한다.
정점을 다 옮기면 연산량이 많기때문에
월드에 넣어주는 것인데.
그래서 월드의 값을 바꿔줘야하는데.
문제는 CPU에서 GPU로 보내는게 불가능한데 예외사항이 하나 있다.
UpdateSubresource를 사용하는 방법이 있다.
DeviceContext->UpdateSubresource(constantBuffer, 0, NULL, &bufferDesc, 0, 0);
CPU에서 GPU로 보내는 과정
main.cpp
#include "stdafx.h"
#include "System/Device.h"
#include "Utilities/Math.h"
#include "Text.h"
#include "Rect.h"
ID3D11Buffer* constantBuffer;
struct ConstantBufferDesc
{
D3DXMATRIX World;
D3DXMATRIX View;
D3DXMATRIX Projection;
} bufferDesc;
float x = 0;
float y = 0;
D3DXMATRIX world[10];
Rect* rect = NULL;
void InitScene()
{
D3DXMatrixIdentity(&bufferDesc.World);
D3DXMatrixIdentity(&bufferDesc.View);
D3DXMatrixIdentity(&bufferDesc.Projection);
D3D11_BUFFER_DESC desc = { 0 };
desc.Usage = D3D11_USAGE_DEFAULT;
desc.ByteWidth = sizeof(ConstantBufferDesc);
desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
bufferDesc.World._41 = 300; //비율
bufferDesc.World._42 = 300;
D3DXMatrixTranspose(&bufferDesc.World, &bufferDesc.World);
D3DXVECTOR3 eye(0, 0, -1);
D3DXVECTOR3 lookAt(0, 0, 0);
D3DXVECTOR3 up(0, 1, 0);
D3DXMatrixLookAtLH(&bufferDesc.View, &eye, &lookAt, &up);
D3DXMatrixTranspose(&bufferDesc.View, &bufferDesc.View);
D3DXMatrixOrthoOffCenterLH(&bufferDesc.Projection, 0, (float)Width, 0, (float)Height, -1, +1);
D3DXMatrixTranspose(&bufferDesc.Projection, &bufferDesc.Projection);
D3D11_SUBRESOURCE_DATA subResource = { 0 };
subResource.pSysMem = &bufferDesc;
HRESULT hr = Device->CreateBuffer(&desc, &subResource, &constantBuffer);
assert(SUCCEEDED(hr));
rect = new Rect();
}
void DestroyScene()
{
SAFE_DELETE(rect);
}
void Update()
{
}
void Render()
{
DeviceContext->UpdateSubresource(constantBuffer, 0, NULL, &bufferDesc, 0, 0); //CPU에서 GPU로 보내줄 수 있는 과정(예외사항처리)
rect->Render();
}
bufferDesc.World._41 = 300;
bufferDesc.World._42 = 300;
화면 비율을 재볼 수 있다.
위치가 맞는지 의아 할때 이런식으로 확인하면 된다.
포토피아가 아니더라도. 캡쳐 뜨고서 그림판을 통해 해도 괜찮다.
선택하여서 사각형의 중점으로 부터 화면 끝부분을 확인해 보면 된다.
다음으로 main.cpp에서
float x = 0;
float y = 0;
D3DXMATRIX world[10];
Rect* rect = NULL;
void InitScene()
{
D3DXMatrixIdentity(&bufferDesc.World);
D3DXMatrixIdentity(&bufferDesc.View);
D3DXMatrixIdentity(&bufferDesc.Projection);
D3D11_BUFFER_DESC desc = { 0 };
desc.Usage = D3D11_USAGE_DEFAULT;
desc.ByteWidth = sizeof(ConstantBufferDesc);
desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
bufferDesc.World._41 = 300;
bufferDesc.World._42 = 300;
D3DXMatrixTranspose(&bufferDesc.World, &bufferDesc.World);
D3DXVECTOR3 eye(0, 0, -1);
D3DXVECTOR3 lookAt(0, 0, 0);
D3DXVECTOR3 up(0, 1, 0);
D3DXMatrixLookAtLH(&bufferDesc.View, &eye, &lookAt, &up);
D3DXMatrixTranspose(&bufferDesc.View, &bufferDesc.View);
D3DXMatrixOrthoOffCenterLH(&bufferDesc.Projection, 0, (float)Width, 0, (float)Height, -1, +1);
D3DXMatrixTranspose(&bufferDesc.Projection, &bufferDesc.Projection);
D3D11_SUBRESOURCE_DATA subResource = { 0 };
subResource.pSysMem = &bufferDesc;
HRESULT hr = Device->CreateBuffer(&desc, &subResource, &constantBuffer);
assert(SUCCEEDED(hr));
rect = new Rect();
}
void DestroyScene()
{
SAFE_DELETE(rect);
}
void Update()
{
D3DXMatrixTranslation(&bufferDesc.World, 100, 100, 0); //100 100 만큼 움직여줌 이걸 이용해서 키보드로 움직이는 것도 가능하다
D3DXMatrixTranspose(&bufferDesc.World, &bufferDesc.World); //움직였으니까
}
void Render()
{
DeviceContext->UpdateSubresource(constantBuffer, 0, NULL, &bufferDesc, 0, 0);
rect->Render();
}
D3DXMatrixTranslation(&bufferDesc.World, 100, 100, 0); 이걸 이용해서 키보드로 움직이는 것도 가능하다.
void Update()
{
if (Key->Press('D'))
x += 1;
else if (Key->Press('A'))
x -= 1;
if (Key->Press('W'))
y += 1;
else if (Key->Press('S'))
y -= 1;
D3DXMatrixTranslation(&bufferDesc.World, x, y, 0);
D3DXMatrixTranspose(&bufferDesc.World, &bufferDesc.World);
}
현실에서는 불가능 하다
물체하나를 월드하나에 담지만
같은 물체를 여러개 담을 수 있는게 게임이다.
월드 위치가 바뀌면서 찍히는 걸 볼 수 있다.
#include "stdafx.h"
#include "System/Device.h"
#include "Utilities/Math.h"
#include "Text.h"
#include "Rect.h"
ID3D11Buffer* constantBuffer;
struct ConstantBufferDesc
{
D3DXMATRIX World;
D3DXMATRIX View;
D3DXMATRIX Projection;
} bufferDesc;
float x = 0;
float y = 0;
D3DXMATRIX world[10]; //제한
Rect* rect = NULL;
void InitScene()
{
D3DXMatrixIdentity(&bufferDesc.World);
D3DXMatrixIdentity(&bufferDesc.View);
D3DXMatrixIdentity(&bufferDesc.Projection);
D3D11_BUFFER_DESC desc = { 0 };
desc.Usage = D3D11_USAGE_DEFAULT;
desc.ByteWidth = sizeof(ConstantBufferDesc);
desc.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
bufferDesc.World._41 = 300;
bufferDesc.World._42 = 300;
D3DXMatrixTranspose(&bufferDesc.World, &bufferDesc.World);
D3DXVECTOR3 eye(0, 0, -1);
D3DXVECTOR3 lookAt(0, 0, 0);
D3DXVECTOR3 up(0, 1, 0);
D3DXMatrixLookAtLH(&bufferDesc.View, &eye, &lookAt, &up);
D3DXMatrixTranspose(&bufferDesc.View, &bufferDesc.View);
D3DXMatrixOrthoOffCenterLH(&bufferDesc.Projection, 0, (float)Width, 0, (float)Height, -1, +1);
D3DXMatrixTranspose(&bufferDesc.Projection, &bufferDesc.Projection);
D3D11_SUBRESOURCE_DATA subResource = { 0 };
subResource.pSysMem = &bufferDesc;
HRESULT hr = Device->CreateBuffer(&desc, &subResource, &constantBuffer);
assert(SUCCEEDED(hr));
rect = new Rect();
for (int i = 0; i < 10; i++)
{
x = Math::RandomFloat(0, (float)Width);
y = Math::RandomFloat(0, (float)Height);
D3DXMatrixTranslation(&world[i], x, y, 0);
D3DXMatrixTranspose(&world[i], &world[i]);
}
}
void DestroyScene()
{
SAFE_DELETE(rect);
}
void Update()
{
//if (Key->Press('D'))
// x += 1;
//else if (Key->Press('A'))
// x -= 1;
//if (Key->Press('W'))
// y += 1;
//else if (Key->Press('S'))
// y -= 1;
//D3DXMatrixTranslation(&bufferDesc.World, x, y, 0);
//D3DXMatrixTranspose(&bufferDesc.World, &bufferDesc.World);
}
void Render()
{
DeviceContext->VSSetConstantBuffers(0, 1, &constantBuffer);
for (int i = 0; i < 10; i++)
{
bufferDesc.World = world[i];
DeviceContext->UpdateSubresource(constantBuffer, 0, NULL, &bufferDesc, 0, 0);
rect->Render();
}
}
그러면 실행할때 마다 바뀌는 것을 볼 수 있다.
행 우선(Row-Major)
row 단위로 저장 하겠다는 뜻
하나의 row를 완전히 저장하고 다음 row를 저장할 것
a11 이 저장된 메모리 다음 칸에는 a12가 저장될 것이다.
a13이 저장된 메모리 다음 row의 첫번째 값인 a21이 저장될 것이다.
따라서 메모리에는 다음과 같이 1차원으로 처져서 저장 된다.
[ a11, a12, a13, a21, a22, a23, a31, a32, a33 ]
우리가 주어진 행렬을 화면에 출력하는 프로그램을 짠다고 생각해보자
상식적으로 윗줄부터 오른쪽으로 끝까지 쓰고 개행한 뒤 다음줄을 써내려가는 방식이 되어야 할텐데,
그렇다면 대부분의 프로그래밍 언어들도 콘솔창 위에서 아래로 내려가기 때문에 행우선을 선택하는 편이 자연스럽다.
열 우선(Column-Major)
row와 같은 맥락 col단위로 저장하겠다는 의미
하나의 온전한 col을 저장한 뒤 다음 col을 저장하겠다는 것
a11,을 저장한 메모리 다음 칸에는 a21이 올 것이다.
이와 마찬가지로, a32를 저장한 메모리 다음 칸에는
다음 column의 첫번째 원소인 a13이 저장될 것이다.
따라서, 메모리에는 다음과 같이 1차원으로 퍼져서 저장된다.
[ a11, a21 a31, a12, a22, a32, a13, a23, a33 ]
포트란, 매트랩, R, 줄리아(과학 관련분야 프로그래밍언어)
계산 쪽의 언어들이 열우선을 채택한 것을 볼 수 있는데
행렬이라는 것의 기원이 연립방정식의 축약임을 생각해 보면 당연한 것이다.
세상에 행렬을 가로 방향으로 읽는 수학자는 없다.
행을 따라 위에서 아래로, 열우선으로 읽는게 정상이다.
행우선과 열우선을 벤치마크 하였을때 (줄리아 언어기준) 행/렬 순서만 바꾸고 행을 먼저 읽었을때 약 36초
열을 먼저 읽었을때 20초가 걸려 거의 2배 수준의 성능 차이가 있음을 알 수 있다.
데이터 레이아웃은 다른 프로그래밍 언어로 작성된 프로그램간에 배열을 올바르게 전달하는 데 중요하다
최신 CPU는 비 순차적 데이터보다 순차 데이터를 더 효율적으로 처리하기 때문에 어레이 를 순회할때 성능에도 중요하다
어찌되었든 행 중심 환경에서 열 중심 순서를 사용하거나 , 그 반대의 경우, 한가지 해결방법은
인덱스에 비 전통적인 역할을 할당하는 것(열에는 첫번째 인덱스를 사용, 행에는 두번째 인덱스를 사용)
또는 1차원 배열의 위치를 명시 적으로 계산하여 언어 구문을 우회하는 것
(물론 관습에서 벗어나면 취약성이 증가할 뿐만 아니라 기존언어 기능 및 기타 코드와의 필요한 상호 작용 정도에 따라 비용이 증가 할 수 있다)
다차원 배열을 지원하는 프로그래밍 언어 또는 표준 라이브러리는
일반적으로 이허나 배열에 대한 기본 행 중심 또는 열 중심 저장 순서를 갖는다
행 우선 순서는 다음에서 사용됩니다. 씨/C ++/목표 -C (C 스타일 배열의 경우), PL / I,[3] 파스칼,[4] Speakeasy,[인용 필요] SAS,[5] 과 Rasdaman.[6]
열 주요 순서는 다음에서 사용됩니다. 포트란, MATLAB,[7] GNU 옥타브, S-Plus,[8] 아르 자형,[9] 줄리아,[10] 과 Scilab.[11]
다음시간)
각주)
'🅿🆁🅾🅶🆁🅰🅼🅼🅸🅽🅶 > DɪʀᴇᴄᴛX 2D' 카테고리의 다른 글
[C++] DirectX 원과 회전 Rotation (0) | 2021.11.22 |
---|---|
[C++] DirectX Texture _ Uv (0) | 2021.11.19 |
[C++] DirectX Constant_Buffer (0) | 2021.11.18 |