在前面我们讲过了如何初始化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来定义一个坐标,具体代码如下:
//
3D Vector; 32 bit floating point components
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结构来进行描述。不同的顶点信息可以通过定义一个数组来解决这个问题,那样每一个顶点都能够进行描述,下面让我们来了解一下这个结构的具体属性。
typedef struct 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了,具体代码如下所示:
1
//
Define the input layout
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()方法来激活它,具体代码如下:
2
if
( FAILED( g_pd3dDevice
->
CreateInputLayout( layout, numElements, pVSBlob
->
GetBufferPointer(),
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()方法将其绑定到设备中,那样我们就可以在屏幕上呈现出来了,具体代码如下:
SimpleVertex vertices[]
=
{
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图形中也一样。我们只要设置如下代码就可以得到,如下所示:
g_pImmediateContext
->
IASetPrimitiveTopology( D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST );
接下来就是画出三角形,以上这些都是在设备初始化后进行的。而画出三角形就需要用到顶点和像素着色器,具体代码如下:
void
Render()
{
//
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浏览器里还不是很好用,第一行代码老是要跑到外面出去。