Geometry 정의
물체를 그리기 위해 물체의 정보가 있어야 한다.
vertices 정보 ( position, color, normal 등) 와 indices 정보를 넣어준다.
Buffer 생성
GPU로 데이터를 옮기기 위해
정점 정보를 가진 VertexBuffer, index 정보를 가진 IndexBuffer, 변환 정보를 가진 ConstantBuffer을 생성한다.
CreateVertexBuffer
Geometry 정의할 때 만든 vertices 벡터를 GPU에서 사용하기 위한 D3D11Buffer로 만드는 과정이다.
D3D11_BUFFER_DESC 구조체를 통해 D3D11Buffer의 정보를 세팅한다.
정점 정보는 GPU에서 수정할 일이 없으므로 Usage를 IMMUTABLE (GPU에서 읽기만 가능) 로 설정한다.
그리고 GPU로 어떤 데이터를 어떻게 보낼지 D3D11_SUBRESOURCE_DATA 구조체를 통해 설정한다.
vertexBufferData.pSysMem = vertices.data();는 vertices의 시작점부터 데이터를 보내라는 뜻이다.
CreateIndexBuffer도 형식은 CreateVertexBuffer와 같다.
CreateConstantBuffer
차이점은 매프레임마다 변환 정보를 CPU에서 GPU로 보내줘야 하기 때문에 Usage를 DYNAMIC으로 설정하고
CPUAccessFlags를 CPU_ACCESS_WRITE로 설정한다. CPU가 GPU에 쓸(write) 수 있다는 의미이다.
INPUT_ELEMENT_DESC
이제 쉐이더를 만들어야 하는 데, 어떤 데이터를 넣어줘야 하는지 작성해야 한다.
vector<D3D11_INPUT_ELEMENT_DESC> inputElements = {
{"POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0},
{"COLOR", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 4 * 3, D3D11_INPUT_PER_VERTEX_DATA, 0},
};
위 코드는 정점에 위치 정보와 색 정보를 넣어주는 예시이다. 각각 float 3개를 가지는 형태로 되어있으므로
R32(4바이트),G32(4바이트),B32(4바이트)_FLOAT으로 되어 있고 POSITION은 0, COLOR은 POSITION이 4*3바이트의
크기이고 그 다음에 들어가므로 그 위치인 4*3을 넣어주고 있다.
CreateVertexShader, CreateInputLayout
D3DCompileFromFile함수를 통해 쉐이더를 컴파일하고 난 데이터를 임시버퍼 ShaderBlob, ErrorBlob에 저장한다.
그리고 ShaderBlob을 이용해 VertexShader와 InputLayout을 생성한다. InputLayout은 VertexShader에 어떤 데이터가 들어가는지 지정해준다. (float3 pos, float2 texcoord...) 그래서 vertexshader와 같이 만든다.
------- 여기까지가 Initialize에서 진행되는 상황이고 이제 Update에서 ConstantBuffer에 변환 행렬을 집어넣는다.
CPU에서 물체가 어떻게 움직일지 계산하고 GPU로 ConstantBuffer로 행렬을 보내는 것이 일반적이다.
그런데 DirectX Simple Math (CreateRotation, CreateTranslation..)는 Row-Major을 사용하고 HLSL은
Column-Major을 사용한다. 그래서 ConsantBuffer로 행렬을 보낼 때 Transpose를 해서 보내야 한다.
Model 변환 (World)
m_constantBufferData.model = Matrix::CreateScale(0.5f) * Matrix::CreateRotationY(rot) *
Matrix::CreateTranslation(Vector3(0.0f, -0.3f, 1.0f));
m_constantBufferData.model = m_constantBufferData.model.Transpose();
DirectX Simple Math는 scale, rotation, translation변환을 함수로 row-major 행렬로 만들어준다.
그 행렬을 곱한 것들을 constantBuffer의 model에 저장할 때 Transpose한다.
시점 변환(View)
m_constantBufferData.view =
XMMatrixLookAtLH({0.0f, 0.0f, -1.0f}, {0.0f, 0.0f, 1.0f}, {0.0f, 1.0f, 0.0f});
m_constantBufferData.view = m_constantBufferData.view.Transpose();
시점 변환은 카메라는 가만히 있고 model 행렬을 역으로 곱해주는 방식으로 진행된다.
XMMatrixLookAtLH 함수를 통해 간단하게 실행할 수 있는데, 카메라의 위치, 카메라가 바라보는 방향 (look),
카메라의 위쪽 방향 (up)을 넣어주면 시점 변환을 할 수 있다.
투영 변환(Projection)
const float aspect = AppBase::GetAspectRatio();
if (m_usePerspectiveProjection) {
const float fovAngleY = 70.0f * XM_PI / 180.0f;
m_constantBufferData.projection =
XMMatrixPerspectiveFovLH(fovAngleY, aspect, 0.01f, 100.0f);
} else {
m_constantBufferData.projection =
XMMatrixOrthographicOffCenterLH(-aspect, aspect, -1.0f, 1.0f, 0.1f, 10.0f);
}
m_constantBufferData.projection = m_constantBufferData.projection.Transpose();
투영 변환은 직교인지 원근인지에 따라 다른 함수를 사용하는데, 원근일 경우
시야각, 비율, near/far clip plane을 넣고 직교일 경우엔 상,하,좌,우와 near/far clip plane을 넣어준다.
template <typename T_DATA>
void UpdateBuffer(const T_DATA &bufferData, ComPtr<ID3D11Buffer> &buffer) {
D3D11_MAPPED_SUBRESOURCE ms;
m_context->Map(buffer.Get(), NULL, D3D11_MAP_WRITE_DISCARD, NULL, &ms);
memcpy(ms.pData, &bufferData, sizeof(bufferData));
m_context->Unmap(buffer.Get(), NULL);
}
업데이트 될때 마다 바뀌는 ConstantBuffer을 CPU에서 GPU로 복사해줘야 한다.
------- Update가 끝나면 Render이 시작된다.
Input Assembler(IA)
입력된 정점들을 출력할 수 있는 최소 단위까지 모아 결합한다.
예를 들어 정점 3개를 모아 삼각형 하나로 결합시키는 것이다.
Vertex Shader(VS)
그리고 ConstantBuffer에서 받은 변환 정보를 이용해
정점들의 변환을 진행한다.
Tessellation, GeometryShader
입력 받은 정점들을 더 자세히 만들어 주는 것이다.
단순한 모델 -> 상세한 모델
Rasterization(RS)
기하 정보를 픽셀의 집합으로 만든다.
Pixel Shader(PS)
픽셀 단위로 색깔을 결정한다.
Output Merger Stage(OM)
실제 색을 결정한다.
ex) 겹쳐 있을 경우 어떤 색으로 표현할지 결정
렌더링할 때 함수들의 순서가 렌더링 파이프라인의 순서와 같지는 않다.
Set함수들은 렌더링을 어떻게 해야할지 옵션만 설정해 주는 것이며 그 옵션을 사용해 렌더링하는 것은
DrawIndexed 함수가 호출된 이후에 진행된다.
m_context->RSSetViewports(1, &m_screenViewport);
먼저 Rasterize 단계에서 사용되는 Viewport를 설정한다.
m_context->ClearRenderTargetView(m_renderTargetView.Get(), clearColor);
m_context->ClearDepthStencilView(m_depthStencilView.Get(),
D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
그리고 프레임을 그리기전에 RenderTargetView와 DepthStencilView를 지워 이전에 그렸던 것을 지워준다.
m_context->OMSetRenderTargets(1, m_renderTargetView.GetAddressOf(), m_depthStencilView.Get());
m_context->OMSetDepthStencilState(m_depthStencilState.Get(), 0);
RenderTargetView와 DepthStencilView를 다시 설정해주고 DepthStencilState까지 설정해준다.
m_context->VSSetShader(m_colorVertexShader.Get(), 0, 0);
m_context->VSSetConstantBuffers(0, 1, m_constantBuffer.GetAddressOf());
m_context->PSSetShader(m_colorPixelShader.Get(), 0, 0);
m_context->RSSetState(m_rasterizerSate.Get());
그 후 VSShader, ConstantBuffer, PSShader을 세팅해주고 RasterizerState를 세팅한다.
m_context->IASetInputLayout(m_colorInputLayout.Get());
m_context->IASetVertexBuffers(0, 1, m_vertexBuffer.GetAddressOf(), &stride, &offset);
m_context->IASetIndexBuffer(m_indexBuffer.Get(), DXGI_FORMAT_R16_UINT, 0);
m_context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
m_context->DrawIndexed(m_indexCount, 0, 0);
InputLayout, VertexBuffer, IndexBuffer을 설정한다. 그리고 PrimitiveTopology를 설정해야 하는데 정점을 보내는 방식이다.
TRIANGLELIST는 정점을 세개씩 묶어 삼각형의 형태로 보내는 것이고 TRIANGLESTRIP은 삼각형을 하나 더 그릴 때마다 index를 한개씩만 추가하면 다음 삼각형을 그려주는 형식이다. DrawIndexed를 실행하면 렌더링이 시작된다.
마지막에 swapchain->Present를 실행하면 화면에 렌더링한 결과를 보여준다.
'그래픽스' 카테고리의 다른 글
| [그래픽스] Texturing (0) | 2023.07.18 |
|---|---|
| [그래픽스] 투영 변환에서 z값을 나누는 시점 (0) | 2023.07.18 |
| [그래픽스] DirectX 초기화 (0) | 2023.07.18 |
| [그래픽스] non-uniform scaling에서 normal vector 변환 (0) | 2023.06.04 |
| [그래픽스] Row-Major vs Column-Major (0) | 2023.05.30 |