이제 본격적으로 Dx11을 이용하여 메쉬(mesh)를 만들고 화면에 그려보자.
제일 처음 해줘야 할건 정정정보를 생성해줘야 한다.
1. 삼각형을 그리기위한 정점 값들을 세팅해보자
renderer::vertexes[0].pos = Vector3(0.f, 0.5f, 0.0f);
renderer::vertexes[0].color = Vector4(0.0f, 1.0f, 0.0f, 1.0f);
renderer::vertexes[1].pos = Vector3(0.5f, -0.5f, 0.0f);
renderer::vertexes[1].color = Vector4(1.0f, 0.0f, 0.0f, 1.0f);
renderer::vertexes[2].pos = Vector3(-0.5f, -0.5f, 0.0f);
renderer::vertexes[2].color = Vector4(0.0f, 0.0f, 1.0f, 1.0f);
C++
복사
2. 셰이더를 생성해준다. ( 정점셰이더, 픽셀셰이더 )
DWORD shaderFlags = D3DCOMPILE_ENABLE_STRICTNESS;
shaderFlags |= D3DCOMPILE_DEBUG;
shaderFlags |= D3DCOMPILE_SKIP_OPTIMIZATION;
//vertex shader
{
ID3DBlob* errorBlob = nullptr;
D3DCompileFromFile(L"..\\Shaders_SOURCE\\TriangleVS.hlsl", nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE
, "main", "vs_5_0", shaderFlags, 0, &renderer::vsBlob, &errorBlob);
mDevice->CreateVertexShader(renderer::vsBlob->GetBufferPointer()
, renderer::vsBlob->GetBufferSize(), nullptr, &renderer::vsShader);
}
//pixel shader
{
ID3DBlob* errorBlob = nullptr;
D3DCompileFromFile(L"..\\Shaders_SOURCE\\TrianglePS.hlsl", nullptr, D3D_COMPILE_STANDARD_FILE_INCLUDE
, "main", "ps_5_0", shaderFlags, 0, &renderer::psBlob, &errorBlob);
mDevice->CreatePixelShader(renderer::psBlob->GetBufferPointer()
, renderer::psBlob->GetBufferSize(), nullptr, &renderer::psShader);
}
C++
복사
셰이더 오브젝트
셰이더 코드를 바이트 코드로 컴파일 하고서 바이트 코드를 가지고 셰이더 오브젝트 인터페이스를 만든다. 각 셰이더 오브젝트를 생성하는건 위와 같다.
셰이더를 생성하는 각 함수의 인자는 순서대로 바이트 코드의 포인터, 해당 사이즈, ClassLinkage 포인터 등 3가지 셰이더 포인터이다.
위의 그림처럼 컴파일된 HLSL의 바이트코드는 임의의 길이를 가지는 데이터를 리턴하기 위해 사용되는 ID3DBlob 인터페이스를 사용한다
( ID3DBlob 가 컴파일된 바이트코드를 담아두는 역할을 하며 번거롭더라도 ID3DBlob 를 경유해야 함)
//셰이더 컴파일 옵션
#if defined(DEBUG) || defined(_DEBUG)
UINT g_flagCompile = D3D10_SHADER_DEBUG | D3D10_SHADER_SKIP_OPTIMIZATION
| D3D10_SHADER_ENABLE_STRICTNESS | D3D10_SHADER_PACK_MATRIX_COLUMN_MAJOR;
#else
UINT g_flagCompile = D3D10_SHADER_ENABLE_STRICTNESS | D3D10_SHADER_PACK_MATRIX_COLUMN_MAJOR;
#endif
// 버텍스 셰이더 코드 컴파일
ID3DBlob* pBlobVS = NULL;
hr = D3DX11CompileFromFile(
L" Tutorial02.fx ", // 파일명
NULL, // 매크로정의(없음)
NULL, // 인클루드 파일 정의(없음)
"VS", // 해당 셰이더의 메인 함수 이름
"vs_4_0", // 버텍스 셰이더로 컴파일 (버전 4.0)
g_flagCompile, // 컴파일 옵션
0, // 이펙트 컴파일 옵션(없음)
NULL, // 바로 컴파일하고 나서 함수를 뺀다。
&pBlobVS, // 컴파일된 바이트 코드
NULL, // 에러 메시지는 필요없음
NULL); // 반환값
JavaScript
복사
D3D10_SHADER_DEBUG : 디버그 정보 포함
D3D10_SHADER_SKIP_OPTIMIZATION :최적화를 수행하지 않는다
D3D10_SHADER_ENABLE_STRICTNESS :구식문법을 엄밀히 체크해서 금지한다(디폴트는 체크하지 않음)
D3D10_SHADER_PACK_MATRIX_COLUMN_MAJOR : 을명시적으로 지정하지 않는한 셰이더의 행렬입출력을 열 우선으로한다
(이쪽이 벡터 행렬 연산을 효율적으로 할 수 있다)
셰이더 오브젝트를 생성한 다음에는 셰이더 오브젝트를 각 파이프라인에 설정한다.
mContext->VSSetShader(renderer::vsShader, 0, 0);
mContext->PSSetShader(renderer::psShader, 0, 0);
C++
복사
위의 그림처럼 컴파일된 HLSL의 바이트코드는 임의의 길이를 가지는 데이터를 리턴하기 위해 사용되는 ID3DBlob 인터페이스를 사용한다
( ID3DBlob 가 컴파일된 바이트코드를 담아두는 역할을 하며 번거롭더라도 ID3DBlob 를 경유해야 함)
3. 정점 정보를 Input Assembler를 생성해준다.
정정정보는 해당 정점을 어떠한 방식으로 이용하여 화면에 그려줄껀지 속성들을 세팅해준다.
// Input layout 정점 구조 정보
D3D11_INPUT_ELEMENT_DESC inputLayoutDesces[2] = {};
inputLayoutDesces[0].AlignedByteOffset = 0;
inputLayoutDesces[0].Format = DXGI_FORMAT_R32G32B32_FLOAT;
inputLayoutDesces[0].InputSlot = 0;
inputLayoutDesces[0].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
inputLayoutDesces[0].SemanticName = "POSITION";
inputLayoutDesces[0].SemanticIndex = 0;
inputLayoutDesces[1].AlignedByteOffset = 12;
inputLayoutDesces[1].Format = DXGI_FORMAT_R32G32B32A32_FLOAT;
inputLayoutDesces[1].InputSlot = 0;
inputLayoutDesces[1].InputSlotClass = D3D11_INPUT_PER_VERTEX_DATA;
inputLayoutDesces[1].SemanticName = "COLOR";
inputLayoutDesces[1].SemanticIndex = 0;
mDevice->CreateInputLayout(inputLayoutDesces, 2
, renderer::vsBlob->GetBufferPointer()
, renderer::vsBlob->GetBufferSize()
, &renderer::inputLayouts);
C++
복사
입력 어셈블러 스테이지(input Assembler [IA]) 는 렌더링 파이프라인의 젤 처음 스테이지이자 어플리케이션으로 부터 넘겨 받은 버텍스 버퍼나 인덱스 버퍼의 데이터를 읽어 들여 [점][선][면]등의 정보를 조합하여 파이프라인의 다음 스테이지로 데이터를 흘려보내는 역할을 한다.
그밖의 셰이더에서 처리에 필요한 [시스템 생성값] 들을 추가 할수도 있다.
[시스템 생성값]은 다른 입출력 Element와 마찬가지로 [시멘틱스] 를 붙여 식별하며 입력 어셈블러가 생성하는 생성값에는
버텍스ID, 프리미티브ID, 인스턴스ID등이 있다.
IA에서 다루는 입력버퍼는 [버텍스 버퍼] ,[인덱스 버퍼] 2가지 종류가 있으며 이둘은 둘다 버퍼 리소스의 한 종류이다.
버텍스 버퍼에는 버텍스 셰이더가 저장되어 있으며 IA의 입력슬롯에 설정한다.
IA에는 전부 16개(32개)의 입력슬롯이 있어 동시에 최대 16개(32개)의 버텍스 버퍼를 설정할 수 있다.
입력슬롯의 수 (D3D11_IA_VERTEXT_IPUT_RESOURCE_SLOT_COUNT는 피처레벨 10.0 이하에서는 16개 10.1.11에서는 32개 이다.
인덱스 버퍼는 버텍스 데이터의 순서를 지정하는 버퍼이며 옵션이다.
인덱스 버퍼를 생략할 경우는 버텍스 버퍼내에서의 버텍스 데이터 순서를 그대로 사용한다.
또 IA에 동시에 설정할수 있는 인덱스 버퍼는 1개뿐이니 주의하자.
또 셰이더가 [입력 버퍼]의 데이터를 필요로 하지 않으면 [버텍스 버퍼] 나 [인덱스 버퍼]를 만들 준비할 필요는 없다.
4. 마지막으로 정점 정보들 GPU로 넘겨주어야 한다.
그러기 위해서는 Buffer를 이용하여 넘겨줘야한다. 삼각형 정보들로 정점셰이더를 생성해보자.
// System -> GPU
D3D11_BUFFER_DESC bufferDesc = {};
bufferDesc.ByteWidth = sizeof(renderer::Vertex) * 3;
bufferDesc.BindFlags = D3D11_BIND_FLAG::D3D11_BIND_VERTEX_BUFFER;
bufferDesc.Usage = D3D11_USAGE::D3D11_USAGE_DYNAMIC;
bufferDesc.CPUAccessFlags = D3D11_CPU_ACCESS_FLAG::D3D11_CPU_ACCESS_WRITE;
//xyz
//rgba
renderer::vertexes[0].pos = Vector3(0.f, 0.5f, 0.0f);
renderer::vertexes[0].color = Vector4(0.0f, 1.0f, 0.0f, 1.0f);
renderer::vertexes[1].pos = Vector3(0.5f, -0.5f, 0.0f);
renderer::vertexes[1].color = Vector4(1.0f, 0.0f, 0.0f, 1.0f);
renderer::vertexes[2].pos = Vector3(-0.5f, -0.5f, 0.0f);
renderer::vertexes[2].color = Vector4(0.0f, 0.0f, 1.0f, 1.0f);
D3D11_SUBRESOURCE_DATA sub = { renderer::vertexes };
//sub.pSysMem = renderer::vertexes;
mDevice->CreateBuffer(&bufferDesc, &sub, &renderer::vertexBuffer);
C++
복사
5. 이제 해당 정보들로 화면에 그려주면 된다.
지금까지 만들어둔 gpu데이터들을 이용해서 화면에 그려주면 된다.
이렇게 하나의 파이프라인을 완성시켜 화면에 그려주는 동작을 DrawCall(드로우 콜)이라고 한다. 드로우콜은 오브젝트나 기타 요소들을 렌더링하는 횟수를 의미합니다. 간단히 생각하면 오브젝트가 많을 수록 드로우콜이 증가할 것입니다.
DirectX 11은 렌더링에 대한 보다 직접적인 접근 방식으로, 사용자가 원하는 작업을 디바이스 컨텍스트에 정확히 알려주면 해당 명령이 실행되도록 대기열에 대기하는 방식을 사용합니다.
void GraphicDevice_DX11::Draw()
{
// 화면 지우기
FLOAT backgroundColor[4] = { 0.2f, 0.2f, 0.2f, 1.0f };
mContext->ClearRenderTargetView(mRenderTargetView.Get(), backgroundColor);
mContext->ClearDepthStencilView(mDepthStencilView.Get(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.f, 0);
// 뷰포트 설정 해주기 (클라이언트에 그려질 영역 설정)
D3D11_VIEWPORT viewPort =
{
0, 0, application.GetWidth(), application.GetHeight(),
0.0f, 1.0f
};
mContext->RSSetViewports(1, &viewPort);
// 렌더타겟 설정(추후 여러개의렌더타겟도 설정 가능하다, 디퍼드렌더링등에서 활용)
mContext->OMSetRenderTargets(1, mRenderTargetView.GetAddressOf(), mDepthStencilView.Get());
// 인풋레이아웃 설정
mContext->IASetInputLayout(renderer::inputLayouts);
mContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY::D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);
// 셰이더 설정
UINT vertexSize = sizeof(renderer::Vertex);
UINT offset = 0;
mContext->IASetVertexBuffers(0, 1, &renderer::vertexBuffer, &vertexSize, &offset);
mContext->VSSetShader(renderer::vsShader, 0, 0);
mContext->PSSetShader(renderer::psShader, 0, 0);
// 렌더타겟에 물체를 그려준다.
mContext->Draw(3, 0);
// 렌더타겟에 있는 이미지를 모니터에 띄어준다.
mSwapChain->Present(1, 0);
}
C++
복사
Primitive 데이터란 렌더링이 되는 도형의 최소단위를 이야기한다.
아레 그림은 다양한 primitive 종류이다.