서의 공간
[T21.2] Drawble System 본문
다음은 도형을 그리기 위한 클래스이다. 기하구조를 결정하는 클래스까지.
그리고 상기할 것은 투영 공간 변환 매트릭스를 구하고 이것이 각 정점에 곱하여 연산하는 것은 VertexShader의 역할이다. 우리는 단지 이 매트릭스를 셰이더에 제출한다. 물론 셰이더 파일에 각 정점과 이 매트릭스를 곱하라는 연산을 구현하긴 하지만 말이다.
Drawable 클래스
핵심 개념
Drawable는 모든 그리기 가능한 오브젝트의 베이스 클래스이다. 추상 클래스로서 DrawableBase가 이 클래스를 상속받고, 실제 오브젝트들은(Box와 같은) DrawableBase 클래스를 상속받는다. 함수의 역할을 하나씩 살펴본다.
GetTransformXM(): 이 함수는 오브젝트의 월드 공간 기준 매트릭스를 반환하는 함수이다.
Draw(): binds 배열에 저장되어 있는 모든 Bindable 인스턴스들을 bind 하고, staticBinds 배열에 저장되어 있는 모든 Bindable 인스턴스들을 파이프라인에 바인드 한다. staticBinds는 하위 클래스인 DrawableBase 클래스에서 정의한다.
Update(): 파라미터는 시간이고, 시간에 따른 변화를 업데이트한다.
AddBind(): Bindable 인스턴스를 binds 배열에 추가한다. 단, 인덱스 버퍼는 AddIndexBuffer() 함수를 사용한다.
AddIndexBuffer(): IndexBuffer 인스턴스를 binds 배열에 추가한다.
GetStaticBinds(): DrawableBase 클래스에서 구현하며, 이 함수는 staticBinds 배열에 Bindable 인스턴스를 저장한다.
이렇게 설계한 이유는 하나의 오브젝트 예를 들어 Box를 여러 개 그린다고 한다면, 또한 이 Box의 크기와 색상이 모두 다르다고 할 때, Box의 공통적인 부분들은 하나로 묶어야 할 필요가 있다. 예를 들어 Box를 그리기 위한 정점 이 8개라고 했을 때, 이 정점들의 상대적 위치와 기하구조는 Box를 아무리 많이 그려야 한다고 해도 달라지지 않는다. 따라서 VertexBuffer, VertexShader, PixelShader, IndexBuffer, PixelConstantBuffer, InputLayout, Topology 이 7개는 결국 Box를 그리기 위해서 공통적인 부분으로 한 번만 파이프라인에 바인드 되면 될 것이다. 따라서 이 Bindable 인스턴스들은 전부 staticBinds라는 배열에 저장하게 된다. 추가로 IndexBuffer는 그리기 함수 호출을 할 때 필요하므로 DrawablaBase 클래스의 SetIndexFromStatic() 함수에서 staticBinds 배열을 뒤져서 IndexBuffer를 찾은 후 따로 변수에 저장하여 나중에 사용한다. transformCbuf와 같은 매 프레임 투영 매트릭스를 구하고 이것을 상수 버퍼에 써야 하는 경우는 binds 배열에 저장하게 된다.
Drawable.h
#pragma once
#include "Graphics.h"
#include <DirectXMath.h>
class Bindable;
class Drawable
{
template<class T>
friend class DrawableBase;
public:
Drawable() = default;
Drawable( const Drawable& ) = delete;
virtual DirectX::XMMATRIX GetTransformXM() const noexcept = 0;
void Draw( Graphics& gfx ) const noexcept(!IS_DEBUG);
virtual void Update( float dt ) noexcept = 0;
virtual ~Drawable() = default;
protected:
void AddBind( std::unique_ptr<Bindable> bind ) noexcept(!IS_DEBUG);
void AddIndexBuffer( std::unique_ptr<class IndexBuffer> ibuf ) noexcept(!IS_DEBUG);
private:
virtual const std::vector<std::unique_ptr<Bindable>>& GetStaticBinds() const noexcept = 0;
private:
const class IndexBuffer* pIndexBuffer = nullptr;
std::vector<std::unique_ptr<Bindable>> binds;
};
Drawable.cpp
#include "Drawable.h"
#include "GraphicsThrowMacros.h"
#include "IndexBuffer.h"
#include <cassert>
#include <typeinfo>
void Drawable::Draw( Graphics& gfx ) const noexcept(!IS_DEBUG)
{
for( auto& b : binds )
{
b->Bind( gfx );
}
for( auto& b : GetStaticBinds() )
{
b->Bind( gfx );
}
gfx.DrawIndexed( pIndexBuffer->GetCount() );
}
void Drawable::AddBind( std::unique_ptr<Bindable> bind ) noexcept(!IS_DEBUG)
{
assert( "*Must* use AddIndexBuffer to bind index buffer" && typeid(*bind) != typeid(IndexBuffer) );
binds.push_back( std::move( bind ) );
}
void Drawable::AddIndexBuffer( std::unique_ptr<IndexBuffer> ibuf ) noexcept(!IS_DEBUG)
{
assert( "Attempting to add index buffer a second time" && pIndexBuffer == nullptr );
pIndexBuffer = ibuf.get();
binds.push_back( std::move( ibuf ) );
}
DrawableBase 클래스
DrawableBase.h
#pragma once
#include "Drawable.h"
#include "IndexBuffer.h"
template<class T>
class DrawableBase : public Drawable
{
protected:
static bool IsStaticInitialized() noexcept
{
return !staticBinds.empty();
}
static void AddStaticBind( std::unique_ptr<Bindable> bind ) noexcept(!IS_DEBUG)
{
assert( "*Must* use AddStaticIndexBuffer to bind index buffer" && typeid(*bind) != typeid(IndexBuffer) );
staticBinds.push_back( std::move( bind ) );
}
void AddStaticIndexBuffer( std::unique_ptr<IndexBuffer> ibuf ) noexcept(!IS_DEBUG)
{
assert( "Attempting to add index buffer a second time" && pIndexBuffer == nullptr );
pIndexBuffer = ibuf.get();
staticBinds.push_back( std::move( ibuf ) );
}
void SetIndexFromStatic() noexcept(!IS_DEBUG)
{
assert( "Attempting to add index buffer a second time" && pIndexBuffer == nullptr );
for( const auto& b : staticBinds )
{
if( const auto p = dynamic_cast<IndexBuffer*>(b.get()) )
{
pIndexBuffer = p;
return;
}
}
assert( "Failed to find index buffer in static binds" && pIndexBuffer != nullptr );
}
private:
const std::vector<std::unique_ptr<Bindable>>& GetStaticBinds() const noexcept override
{
return staticBinds;
}
private:
static std::vector<std::unique_ptr<Bindable>> staticBinds;
};
template<class T>
std::vector<std::unique_ptr<Bindable>> DrawableBase<T>::staticBinds;
IndexedTriangleList.h
핵심 개념
정점의 로컬 공간 기준 정보와 정점의 인덱스 리스트를 반환해주는 클래스이다. 그냥 서로 다른 타입인 정점들과 인덱스 리스트를 한 곳에 모아주는 구조체로 생각하면 된다. Transform 함수는 파라미터가 변환 매트릭스이고(일반적으로 월드 변환 중 스케일 변환), 각 모든 정점에 대해 스케일 변환한다.
#pragma once
#include <vector>
#include <DirectXMath.h>
template<class T>
class IndexedTriangleList
{
public:
IndexedTriangleList() = default;
IndexedTriangleList( std::vector<T> verts_in,std::vector<unsigned short> indices_in )
:
vertices( std::move( verts_in ) ),
indices( std::move( indices_in ) )
{
assert( vertices.size() > 2 );
assert( indices.size() % 3 == 0 );
}
void Transform( DirectX::FXMMATRIX matrix )
{
for( auto& v : vertices )
{
const DirectX::XMVECTOR pos = DirectX::XMLoadFloat3( &v.pos );
DirectX::XMStoreFloat3(
&v.pos,
DirectX::XMVector3Transform( pos,matrix )
);
}
}
public:
std::vector<T> vertices;
std::vector<unsigned short> indices;
};
Cube.h
핵심 개념
큐브 기하구조를 만든다.
Make() 함수는 각 오브젝트에 정의 된 Vertex의 로컬 위치를 설정한다. 즉 vertex들이 상대적으로 어떻게 위치하는지 기하구조를 만든다. 중요한 것은 이 함수를 부르는 오브젝트에서 정의된 Vertex 구조체는 항상 pos라는 이름의 위치정보를 가져야 한다. 이 부분은 나중에 한 층 더 일반화할 것이다. 정점들의 로컬 좌표를 결정하고, 정점들을 인덱스화 시킨다. 그리고 이 두 가지(정점들, 인덱스 리스트)를 IndexedTriangleList로서 반환한다.
#pragma once
#include "IndexedTriangleList.h"
#include <DirectXMath.h>
class Cube
{
public:
template<class V>
static IndexedTriangleList<V> Make()
{
namespace dx = DirectX;
constexpr float side = 1.0f / 2.0f;
std::vector<dx::XMFLOAT3> vertices;
vertices.emplace_back( -side,-side,-side ); // 0
vertices.emplace_back( side,-side,-side ); // 1
vertices.emplace_back( -side,side,-side ); // 2
vertices.emplace_back( side,side,-side ); // 3
vertices.emplace_back( -side,-side,side ); // 4
vertices.emplace_back( side,-side,side ); // 5
vertices.emplace_back( -side,side,side ); // 6
vertices.emplace_back( side,side,side ); // 7
std::vector<V> verts( vertices.size() );
for( size_t i = 0; i < vertices.size(); i++ )
{
verts[i].pos = vertices[i];
}
return{
std::move( verts ),{
0,2,1, 2,3,1,
1,3,5, 3,7,5,
2,6,3, 3,6,7,
4,5,7, 4,7,6,
0,4,2, 2,4,6,
0,1,4, 1,5,4
}
};
}
};
Box 클래스
핵심 개념
드디어 Box를 그린다.
Box는 기본생성자의 파라미터 중 랜덤 값 5개를 받는데, 각 클래스 변수들을 설정한다.
float r; // 월드 공간 원점으로부터의 x축 거리
float roll = 0.0f; // 로컬 공간 z축 회전
float pitch = 0.0f; // 로컬 공간 x축 회전
float yaw = 0.0f; // 로컬 공간 y축 회전
float theta; // 월드 공간 기준 y축 회전
float phi; // 월드 공간 기준 x축 회전
float chi; // 월드 공간 기준 z축 회전
생성자의 마지막 줄은 각 모델의 인스턴스마다 z축 비례를 달리 두기 위해서이다. 랜덤 값을 받는다.
GetTransformXM() 함수에서 월드 공간 변환 매트릭스를 계산하고 반환한다. 이것은 transformCbuf 클래스에서 쓰이고, 최종적으로 VertexShader에 상수버퍼의 매트릭스로서 제출한다.
Box.h
#pragma once
#include "DrawableBase.h"
class Box : public DrawableBase<Box>
{
public:
Box( Graphics& gfx,std::mt19937& rng,
std::uniform_real_distribution<float>& adist,
std::uniform_real_distribution<float>& ddist,
std::uniform_real_distribution<float>& odist,
std::uniform_real_distribution<float>& rdist,
std::uniform_real_distribution<float>& bdist );
void Update( float dt ) noexcept override;
DirectX::XMMATRIX GetTransformXM() const noexcept override;
private:
// positional
float r;
float roll = 0.0f;
float pitch = 0.0f;
float yaw = 0.0f;
float theta;
float phi;
float chi;
// speed (delta/s)
float droll;
float dpitch;
float dyaw;
float dtheta;
float dphi;
float dchi;
// model transform
DirectX::XMFLOAT3X3 mt;
};
Box.cpp
#include "Box.h"
#include "BindableBase.h"
#include "GraphicsThrowMacros.h"
#include "Cube.h"
Box::Box( Graphics& gfx,
std::mt19937& rng,
std::uniform_real_distribution<float>& adist,
std::uniform_real_distribution<float>& ddist,
std::uniform_real_distribution<float>& odist,
std::uniform_real_distribution<float>& rdist,
std::uniform_real_distribution<float>& bdist )
:
r( rdist( rng ) ),
droll( ddist( rng ) ),
dpitch( ddist( rng ) ),
dyaw( ddist( rng ) ),
dphi( odist( rng ) ),
dtheta( odist( rng ) ),
dchi( odist( rng ) ),
chi( adist( rng ) ),
theta( adist( rng ) ),
phi( adist( rng ) )
{
namespace dx = DirectX;
if( !IsStaticInitialized() )
{
struct Vertex
{
dx::XMFLOAT3 pos;
};
const auto model = Cube::Make<Vertex>();
AddStaticBind( std::make_unique<VertexBuffer>( gfx,model.vertices ) );
auto pvs = std::make_unique<VertexShader>( gfx,L"ColorIndexVS.cso" );
auto pvsbc = pvs->GetBytecode();
AddStaticBind( std::move( pvs ) );
AddStaticBind( std::make_unique<PixelShader>( gfx,L"ColorIndexPS.cso" ) );
AddStaticIndexBuffer( std::make_unique<IndexBuffer>( gfx,model.indices ) );
struct PixelShaderConstants
{
struct
{
float r;
float g;
float b;
float a;
} face_colors[8];
};
const PixelShaderConstants cb2 =
{
{
{ 1.0f,1.0f,1.0f },
{ 1.0f,0.0f,0.0f },
{ 0.0f,1.0f,0.0f },
{ 1.0f,1.0f,0.0f },
{ 0.0f,0.0f,1.0f },
{ 1.0f,0.0f,1.0f },
{ 0.0f,1.0f,1.0f },
{ 0.0f,0.0f,0.0f },
}
};
AddStaticBind( std::make_unique<PixelConstantBuffer<PixelShaderConstants>>( gfx,cb2 ) );
const std::vector<D3D11_INPUT_ELEMENT_DESC> ied =
{
{ "Position",0,DXGI_FORMAT_R32G32B32_FLOAT,0,0,D3D11_INPUT_PER_VERTEX_DATA,0 },
};
AddStaticBind( std::make_unique<InputLayout>( gfx,ied,pvsbc ) );
AddStaticBind( std::make_unique<Topology>( gfx,D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST ) );
}
else
{
SetIndexFromStatic();
}
AddBind( std::make_unique<TransformCbuf>( gfx,*this ) );
// model deformation transform (per instance, not stored as bind)
dx::XMStoreFloat3x3(
&mt,
dx::XMMatrixScaling( 1.0f,1.0f,bdist( rng ) )
);
}
void Box::Update( float dt ) noexcept
{
roll += droll * dt;
pitch += dpitch * dt;
yaw += dyaw * dt;
theta += dtheta * dt;
phi += dphi * dt;
chi += dchi * dt;
}
DirectX::XMMATRIX Box::GetTransformXM() const noexcept
{
namespace dx = DirectX;
return dx::XMLoadFloat3x3( &mt ) *
dx::XMMatrixRotationRollPitchYaw( pitch,yaw,roll ) *
dx::XMMatrixTranslation( r,0.0f,0.0f ) *
dx::XMMatrixRotationRollPitchYaw( theta,phi,chi ) *
dx::XMMatrixTranslation( 0.0f,0.0f,20.0f );
}
Shader
ColorIndexVS.hlsl
cbuffer CBuf
{
matrix transform;
};
float4 main( float3 pos : Position ) : SV_Position
{
return mul( float4(pos,1.0f),transform );
}
ColorIndexPS.hlsl
핵심 개념
SV_PrimitiveID는 기본도형(삼각형)마다 하나의 ID가 주어진다. 따라서 사각형의 한 면(삼각형 두 개)마다 하나의 색상을 칠하고 반환한다. face_colors 자체가 색상 데이터이다.
cbuffer CBuf
{
float4 face_colors[8];
};
float4 main( uint tid : SV_PrimitiveID ) : SV_Target
{
return face_colors[(tid/2) % 8];
}
다음은 Box를 100개 그린 결과이다.
'Graphics API > DirectX 11 - Chili' 카테고리의 다른 글
[T24.0] Lighting (0) | 2020.12.05 |
---|---|
[T21.3] Box, Hill, Sphere, Cylinder, Skull, Wave (1) | 2020.12.01 |
[T21.1] Bindable System (0) | 2020.11.29 |
[T20] Solid cube, Depth and stencil (0) | 2020.11.29 |
[T18] Constant buffer (0) | 2020.11.29 |