C++Directx11开发笔记三:绘制图形
在前面我们讲过了如何初始化D3D11Device设备初始化等等,这里所讲的绘制图形将在上一篇文章的项目里进行扩展,在屏幕中绘制图形。在3D的呈现中最小的单位为三角形,无论我们看到的是多么大或多么小的,都是有一个或很多个三角形通过各种方向,角度构成的,当然这会涉及到很多数学中的几何学问题,最悲剧的就是我在大学里却没学好代数以及几何学,有学也忘记了。不过Directx SDK中以及为我们解决了很多几何上的问题,通过他们的方法就可以得到结果,说了这么多目的就是我告诉大家,要掌握高阶运用,必然要学会基础知识,所以我们这里就来学习一下如何在屏幕上绘制一个三角形,并涂上颜色。
一个三角形由三个点组成,也可以说坐标。在坐标系里,三个不同的点就可以组成一个唯一的三角形,当然也就是唯一的面,我想3D图形是由很多个面组成的,这也就是最小单位为三角形的原因。为了能够让GPU(就是显卡中的CPU,简单的认为一下,(*^__^*) )呈现三角形,我们必须告诉他三个点的坐标,那样他才能够在屏幕中显示出来。例如:在2D中,我们要在屏幕中画出如下的图示的三角形,就必须告诉GPU他们三个顶点(0,0),(0,1),(1,0)的坐标,那样GPU才能够画出他们。
我们已经知道要把顶点告诉GPU,但是如何告诉他们呢?在Direct3D 11中,顶点信息如三角形三个顶点的坐标是存储在一个缓存资源中的,叫做顶点缓存(Vertex Buffer)。我们必须创建一个足够大的顶点缓存,让他能够承载三角形的三个顶点坐标信息。
INPUT LAYOUT
一个顶点不只包含一个坐标,还可能包含一个或多个颜色值,纹理坐标等等,而Input Layout就是定义这些信息如何在内存中存储:不同数据类型将会有不同的大小, 当然不同的大小也决定着不同的存储顺序。和C语言很像,一个顶点一般使用一个结构来定义。在这里,我们只需要定义一个三角形顶点的坐标,所以我们只要使用XMFLOAT3来定义一个坐标,具体代码如下:
typedef struct _XMFLOAT3
{
FLOAT x;
FLOAT y;
FLOAT z;
#ifdef __cplusplus
_XMFLOAT3() {};
_XMFLOAT3(FLOAT _x, FLOAT _y, FLOAT _z) : x(_x), y(_y), z(_z) {};
_XMFLOAT3(CONST FLOAT *pArray);
_XMFLOAT3& operator= (CONST _XMFLOAT3& Float3);
#endif // __cplusplus
} XMFLOAT3;
// 以上是XMFLOAT3的结构信息,在3D中坐标的结构
struct SimpleVertex
{
XMFLOAT3 Pos; // Position
};
我们定义了一个SimpleVertex结构,就是为了存储三角形的顶点坐标,为了能够让GPU了解并且能够在内存中获得相关信息,我们就必须使用到Input Layout。在Direct 3D 11中,一个Input Layout被定义成能够让GPU识别的结构信息,每一个顶点属性可以使用一个叫D3D11_INPUT_ELEMENT_DESC结构来进行描述。不同的顶点信息可以通过定义一个数组来解决这个问题,那样每一个顶点都能够进行描述,下面让我们来了解一下这个结构的具体属性。
LPCSTR SemanticName;
UINT SemanticIndex;
DXGI_FORMAT Format;
UINT InputSlot;
UINT AlignedByteOffset;
D3D11_INPUT_CLASSIFICATION InputSlotClass;
UINT InstanceDataStepRate;
} D3D11_INPUT_ELEMENT_DESC;
属性说明:
- SemanticName:用来描述目的或名称的字符,只要任何符合C语言结构的字符串都可以使用,并且忽略大小写,比如用于描述顶点坐标可以使用“POSITION”字符串。
- SemanticIndex:这个用来描述索引,当SemanticName相同的情况下就可以使用索引来描述到底哪个才是当前需要的。因为一个顶点可能包含多个颜色,或纹理坐标等等,例如多个颜色可以使用如COLOR0,COLOR1来描述,也可以都是用COLOR来描述,使用0和1来填充SemanticIndex。
- Format:描述这个数据的数据类型,如:DXGI_FORMAT_R32G32B32_FLOAT描述3个32位的float数据类型,即12字节长度;而DXGI_FORMAT_R16G16B16A16_UINT表示4个16位的uint数据类型,即8字节长度。
-
InputSlot:输入槽,在前面提到过的,每一个顶点信息都通过一个Vertex Buffer输入让GPU识别,在Direct 3D 11中多个顶点信息可以同时的输入,最多可以有16个,这样就需要让GPU知道当前将使用哪个Vertex Buffer信息,也就是这个值将在0到15之间了。
-
AlignedByteOffset:内存偏移量,告诉GPU当前缓存内存起始偏移量。
-
InputSlotClass:这个一般使用D3D11_INPUT_PER_VERTEX_DATA来填充,当使用实例数据类型时,将使用D3D11_INPUT_PER_INSTANCE_DATA,这个是比较高级的或许在我们以后的学习当中会遇到,这里我们不大清楚,就先放过。
-
InstanceDataStepRate:这个用于D3D11_INPUT_PER_INSTANCE_DATA时候,如果不是则必须将其设置为0。
了解了上面的信息,我们就可以定义我们自己的
D3D11_INPUT_ELEMENT_DESC了,具体代码如下所示:2 D3D11_INPUT_ELEMENT_DESC layout[] =
3 {
4 { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
5 };
6 UINT numElements = ARRAYSIZE( layout );
Vertex Layout
顶点布局主要就是为了给顶点着色器(Vertex Shader)提供计算的【注:也许这个描述不正确】,为了创建一个顶点布局就需要顶点着色器输入签名,我们使用ID3DBlob接口对象来描述,而ID3DBlob接口通过D3DX11CompileFromFile来检索顶点着色器包含签名的二进制数据。只要我们有了这个数据,就可以通过ID3D11Device::CreateInputLayout()方法来创建我们的Vertex Layout对象,和使用ID3D11DeviceContext::IASetInputLayout()方法来激活它,具体代码如下:
3 pVSBlob->GetBufferSize(), &g_pVertexLayout ) ) )
4 return FALSE;
5 // Set the input layout
6 g_pImmediateContext->IASetInputLayout( g_pVertexLayout );
创建Vertex Buffer
知道了上面的内容我们还需要做一件事,在初始化时我们必须创建一个Vertex Buffer并且承载了这个顶点的数据信息。为了创建Vertex Buffer,必须填充两个结构D3D11_BUFFER_DESC和D3D11_SUBRESOURCE_DATA,然后使用ID3D11Device::CreateBuffer()方法进行创建。D3D11_BUFFER_DESC结构对Vertex Buffer要创建的内容对象进行描述,而
D3D11_SUBRESOURCE_DATA则是包含了具体的数据信息,这些数据信息在创建时会进行拷贝。创建缓存和初始化是在同一时间完成的,在创建后我们可以使用ID3D11DeviceContext::IASetVertexBuffers()方法将其绑定到设备中,那样我们就可以在屏幕上呈现出来了,具体代码如下:{
XMFLOAT3( 0.0f, 0.5f, 0.5f ),
XMFLOAT3( 0.5f, -0.5f, 0.5f ),
XMFLOAT3( -0.5f, -0.5f, 0.5f ),
};
D3D11_BUFFER_DESC bd;
ZeroMemory( &bd, sizeof(bd) );
bd.Usage = D3D11_USAGE_DEFAULT;
bd.ByteWidth = sizeof( SimpleVertex ) * 3;
bd.BindFlags = D3D11_BIND_VERTEX_BUFFER;
bd.CPUAccessFlags = 0;
bd.MiscFlags = 0;
D3D11_SUBRESOURCE_DATA InitData;
ZeroMemory( &InitData, sizeof(InitData) );
InitData.pSysMem = vertices;
if( FAILED( g_pd3dDevice->CreateBuffer( &bd, &InitData, &g_pVertexBuffer ) ) )
return FALSE;
// Set vertex buffer
UINT stride = sizeof( SimpleVertex );
UINT offset = 0;
g_pImmediateContext->IASetVertexBuffers( 0, 1, &g_pVertexBuffer, &stride, &offset );
Primitive Topology (原型拓扑结构)
从上面我们可以得知,如果要呈现一个三角形那样就需要将三个顶点信息告知GPU,那样如果两个三角形就必须告诉GPU6个顶点的信息,如果一个四边形(两个三角形组成),也就是说有两个顶点是共有的,PrimitiveTopology就是为了解决这个问题的。在四边形中,只要传入是个顶点信息,就可以画出四边形了,如图所示,就可以很好的理解了。
如上图,如果要呈现3a中的图,只要告诉GPU是个顶点,GPU就会直接画出这个四边形了,当然也要注意一下顺序:A B C D,其实在Vertex Buffer中描述的是A B C和B C D,这样 B C两点是共用的,当然在3b图形中也一样。我们只要设置如下代码就可以得到,如下所示:
接下来就是画出三角形,以上这些都是在设备初始化后进行的。而画出三角形就需要用到顶点和像素着色器,具体代码如下:
{
// Clear the back buffer
float ClearColor[4] = { 0.0f, 0.125f, 0.3f, 1.0f }; // red,green,blue,alpha
g_pImmediateContext->ClearRenderTargetView( g_pRenderTargetView, ClearColor );
// Render a triangle
g_pImmediateContext->VSSetShader( g_pVertexShader, NULL, 0 );
g_pImmediateContext->PSSetShader( g_pPixelShader, NULL, 0 );
g_pImmediateContext->Draw( 3, 0 );
// Present the information rendered to the back buffer to the front buffer (the screen)
g_pSwapChain->Present( 0, 0 );
}
最终显示结果如下:
写到了这里差点浏览器挂掉,OK今天就到这里,发现这个博客园的代码编辑器在Google浏览器里还不是很好用,第一行代码老是要跑到外面出去。