一 : DirectX11SDK文档(五)
总结
在前面的教程中,我们成功地在应用程序窗口的中央绘制了1个三角形,我们没有吧注意力集中在顶点缓冲区里的坐标信息。在这个教程中,我们将详细描述3D坐标和Transformation。
3D空间
在前面的教程中,我们很明智地把3个顶点信息放置在屏幕上。然而,不具有一般性。那么,我们需要1个系统来表示在3D空间中的对象和1个系统将他们显示出来。
在现实世界里,对象存在于3D空间内。那就意味着,要将1个对象放置在1个特定的位置时,我们需要1个坐标系统,定义3个坐标轴来定义1个位置。在计算机图形学中,3D空间使用的是卡迪尔坐标系。在这个坐标系统中,有X,Y,Z3个坐标轴,并互相垂直,可以表示3D空间的任意点。这个坐标系统有2种表现形式,左手坐标系或者右手坐标系。如图:
既然我们讨论的3D空间的坐标系。1个点在3D空间的不同位置,将会有不同的坐标。以1D为例,假设我们有一把尺子,并且我们在上面标记了点P,P点在5英寸处。现在,如果我们把尺子向右平移1英寸,那么对于P点,现在变成了在4英寸处。通过移动尺子,这个参考系就发生了变化。所以,点并没有移动,它只是参考系变了。如图:
在3D中,1个空间由1个原点和X,Y,Z3个坐标轴定义。在计算机图形学中,使用几个空间坐标系:对象空间,世界空间,观察空间,投影空间,屏幕空间。如图:
对象空间
注意到这个立方体是以原点为中心,对象空间也叫模型空间,美工在制作模型之际就是使用对象空间。美工在制作模型之际,通常以对象空间的原点为中心,以便于进行变换:比如旋转模型。在我们讨论Transformation之际将会看到。以下是8个顶点:
(-1, 1, -1)
(1, 1, -1)
(-1, -1,-1)
( 1, -1,-1)
(-1, 1, 1)
(1, 1, 1)
(-1,-1, 1)
( 1,-1, 1)
因为对象空间是美工创建和制作模型的典型应用,所模型所保存的顶点坐标信息都是相对于对象空间。1个应用程序能够创建1个顶点缓冲区来表示1个模型,并用模型数据来初始化这个缓冲区。所以顶点缓冲区的顶点坐标也是相对于对象空间来说。也就意味着顶点渲染器所接收到的顶点都是相对于对象空间。
世界空间
在1个场景中,所有对象将共享1个世界空间。它通常用来表示要渲染的对象之间的空间关系。想象一下这个世界空间,我们可以想象我们站在1个矩形空间的西南角,并面向北面。我们定义我们所站的这个角是坐标原点(0,0,0)。X轴指向我们的右边,Y轴指向我们的上面,Z轴指向我们的前面(即我们所面向的方向)。当我们做好这些,这个空间里的每个点都可以用XYZ坐标轴定义。现在,可能有一张椅子在我们前面五步,右手边两步的位置。可能有一盏灯在椅子上面八步高的位置。那么我们即可知道椅子的坐标是(2,0,5),灯的坐标为(2,8,5)。正如我们所看到的,在世界中,对象之间的空间关系就用世界空间把他们联系起来。
观察空间
观察空间,有时也叫摄像机空间,跟世界空格键类似,是场景的典型应用。然而,在观察空间中,原点是观察者或者摄像机。摄像机所看到的方向定义为Z轴。应用程序定义了Y轴的正方向为摄像机向上的方向。如图所示:
左图显示了1个场景,该场景由1个像小人的对象和观察者组成,观察者正观察这个对象。红色部分表示使用世界空间的原点和坐标轴。右图显示观察空间和世界空间的关系。蓝色部分表示使用观察空间的坐标系。为了更清楚的说明插图,观察空间的坐标系于世界空间的坐标系有明显的不同。注意到观察空间中,观察者所朝着的方向是Z轴正方向。
投影空间
投影空间是用来将观察空间中的对象进行投影变换。在这个空间里,可见的范围是X,Y在[-1,1],Z在[0,1]。
屏幕空间
屏幕空间经常指帧缓存的位置。因为帧缓存通常是2D的纹理,所以屏幕空间是1个2D空间,左上角表示屏幕坐标原点。X水平向右,Y轴垂直向下。对于1个缓冲来说,它有w像素宽,h像素高。
空间到空间的转换
Transformation是指将顶点从1个空间到另1个空间的转换。在3D计算机图形学中,在pipeline中有三个逻辑转换:世界变换,观察变换,投影变换。每个变换操作如:平移,旋转,缩放将在下1个教程讲解。
世界变换
世界变换,顾名思义,将顶点从对象空间变换到世界空间。它通常包括1个或者多个的缩放,旋转,平移操作来变换对象,当然这些变换要根据我们要给的,大小,方向,位置信息。每个对象在场景中都有它自己的世界变换矩阵。因为每个对象都有自己的大小,方向和位置。
观察变换
在所有顶点都变换到世界空间后,世界变换将这些顶点从世界空间变换到观察空间。以观察者的视角来观察世界空间出现的对象。在观察空间,观察者在坐标系原点,观察方向为Z轴正方向。
以观察者的参考系来观察世界空间没有什么价值。观察变换矩阵是作用于顶点,而不是观察者。所以,当我对观察者进行变换时,观察变换矩阵执行相反的变换。比如,如果我们要观察者沿着Z轴的负方向移动五个单位,观察变换矩阵则被计算成沿着Z轴正方向移动五个单位。虽然摄像机向后移动,但是相对于观察者而言,顶点向前移动。在XNAMath中1个很方便地函数叫X美眉atrixLookAtLH()经常被用来计算观察矩阵。我们只要告诉它观察者的位置,观察方向,观察者向上的方向来获取观察变换矩阵。
投影变换
投影变换将顶点从3D空间(比如世界空间和观察空间)变换到投影空间。在投影空间,顶点的X,Y坐标通过3D空间中的X/Z和Y/Z的比来计算。如图:
在3D空间中,所有事物出现在视角以内。也就是说近大远小。众所周知,一棵树的顶端h单位高,d单位远,还有一棵树2h单位高,2d单位远,那么观察者将看到两棵树的顶点重合。所以,顶点出现在屏幕上的位置可以根据X/Z和Y/Z来计算。
在参数中有1个用于顶点3D空间中可见的区域,它叫FOV。当朝着特定的方向看之际,FOV表示根据对象特定的位置判断它是否可见。人又能在前面所能看到的FOV(我们看不到我我们背面的东西),并且我们看不到太近,或者太远的东西。在计算机图形学中,FOV包含1个平截体,在3D中,这个平截体由六个平面定义。在六个平面中的两个跟XY平面平行,这2个分别叫near-Z和far-Z平面。如图:
GPU过滤掉平截体外面的对象,所以不会花时间去渲染不会显示的图像。这个操作叫做裁剪。平截体的4个面像被割掉头的金字塔。根据体积进行裁剪是非常复杂的,GPU必须将每个顶点跟平截体的6个平面进行计算。换个角度,GPU一般先进行投影变换,然后再根据平截体裁剪。投影变换的效果就是将金字塔形的平截体转换成1个盒子。这是因为,正如前面提到的,在投影空间中X,Y坐标分别是根据X/Z和Y/Z计算出来的。所以点a和点b在投影空间中将是同1个点,正是这个原因平截体变成了1个盒子。
想象一下两棵树的顶点在平截体的顶部边缘,假设d =2h。那么在投影空间中,Y轴的坐标沿着ab这条线将都是0.5(因为h/d=0.5)。所以顶点的Y坐标在投影变换后大于0.5则被GPU裁剪掉。对于不同的平截体将会有不同的裁剪结果。为了使这个操作更方便,3D程序一般缩放投射的X,Y大小是它们能在[-1,1]内。换句话说,任意的X或Y的坐标在[-1,1]区间之外,将被裁剪掉。为了让它按计划工作,投影矩阵必须缩放投射过来的X,Y坐标(通过将它们乘上h/d,或者d/h),d/h也是FOV一半的余切。经过缩放,平截体的头部将变成h/d*d/h = 1。任何顶点经过投影变换后的X,Y坐标大于1的将被GPU裁剪掉。这就是我们想要的。
在投影空间中Z轴坐标也有相同的操作。在投影空间中我们更喜欢将Z坐标控制在[0,1]。在3D空间中,当Z=near-Z时,在投影空间中,Z=0;当Z=far-Z时,投影空间中,Z=1.当这个操作好之后,所有的Z值在[0,1]之外的将被GPU裁剪掉。
在Direct3D11中,获取投影矩阵对简单的方法就是调用X美眉atrixPerspectiveFovLH()方法。我们只要提供4个参数--FOVy,Aspect,Zn,Zf,函数返回1个矩阵,能实现上面所提到的功能。FOVy是指在Y轴方向的视野,Aspect指渲染目标的宽高比,根据FOVy和Aspect即可算出FOVx。Zn和Zf分别表示near-Z和far-Z。
使用变换
在前面的教程中,我们写了一段程序,将1个简单的三角形渲染在屏幕上。当我们创建顶点缓冲区时,顶点的坐标直接当做投影后的坐标来使用,以至于我们没有进行任何的变换。既然我们已经了解了3D空间和变换。我们将改写这个程序,顶点缓冲区保存的将是对象空间的顶点数据。然后,我们改写我们的顶点渲染器将顶点经过一系列的变换,从对象空间变换到投影空间。
改写顶点缓冲区
现在我们开始在三维空间定义对象,我们将前面的平面三角形改成1个立方体。这样更容易地展示这些概念。
SimpleVertex vertices[] = {
{ XMFLOAT3( -1.0f, 1.0f, -1.0f), XMFLOAT4( 0.0f, 0.0f, 1.0f, 1.0f ) },
{ XMFLOAT3( 1.0f, 1.0f, -1.0f), XMFLOAT4( 0.0f, 1.0f, 0.0f, 1.0f ) },
{ XMFLOAT3( 1.0f,1.0f, 1.0f ), XMFLOAT4( 0.0f, 1.0f, 1.0f, 1.0f )},
{ XMFLOAT3( -1.0f, 1.0f, 1.0f), XMFLOAT4( 1.0f, 0.0f, 0.0f, 1.0f ) },
{ XMFLOAT3( -1.0f, -1.0f, -1.0f ), XMFLOAT4( 1.0f, 0.0f, 1.0f, 1.0f) },
{XMFLOAT3( 1.0f, -1.0f, -1.0f), XMFLOAT4( 1.0f, 1.0f, 0.0f, 1.0f ) },
{ XMFLOAT3( 1.0f, -1.0f, 1.0f), XMFLOAT4( 1.0f, 1.0f, 1.0f, 1.0f ) },
{ XMFLOAT3( -1.0f, -1.0f, 1.0f ), XMFLOAT4( 0.0f,0.0f, 0.0f, 1.0f ) },
};
可能你会注意到,我们指定了立方体的八个顶点,但是我们并没有描述每个三角形。如果我们就这样把数据传进去,输出将不会是我们想要的结果。我们必须通过这八个点指定三角形来组成立方体。
在1个立方体上,会有许多的三角形共享同1个顶点。并且很多顶点会被重复地定义,这样会浪费内存空间。像这样的情况,我们有1个办法只要定义八个顶点即可了,然后告诉Direct3D使用那些点来组成三角形。通过索引缓存可以实现。1个索引缓存包含1个链表,它保存的是顶点缓冲区的索引,用它来指定使用那些顶点来组成三角形。代码如下:
// Create index buffer
WORD indices[] = {
3,1(www.61k.com],0,
2,1,3,
0,5,4,
1,5,0,
3,4,7,
0,4,3,
1,6,5,
2,6,1,
2,7,6,
3,7,2,
6,4,5,
7,4,6,
};
正如你所看到的,第1个三角形由点3,1,和0。这意味着第1个三角形的顶点分别在:( -1.0f, 1.0f, 1.0f ),(1.0f, 1.0f, -1.0f ), and ( -1.0f, 1.0f, -1.0f),1个立方体有六个面,每个面由三个三角形组成。那么我们将会看到这里定义了十二个三角形。
既然每个顶点已经被明确定义了,并且没有任何2个三角形没有共享边缘,这是考虑到使用三角形列。总之,包含十二个三角形的三角列,我们需要3六个顶点。
顶点索引缓冲区的创建跟顶点缓冲区的创建类似。我们提供指定的参数,比如1个结构体包含大小和类型,然后创建缓冲区。这个类型是D3D11_BIND_INDEX_BUFFER。既然我们使用DWORD来申请数组,那我们用sizeof(DWORD)。
D3D11_BUFFER_DESC bd;
ZeroMemory( &bd, sizeof(bd) );
bd.Usage = D3D11_USAGE_DEFAULT;
bd.ByteWidth = sizeof( WORD ) *36;// 36 vertices needed for 12 triangles in a triangle list
bd.BindFlags = D3D11_BIND_INDEX_BUFFER;
bd.CPUAccessFlags = 0;
一旦我们创建好缓冲区,我们就要去设置它,以至于Direct3D能知道如何去使用索引缓冲区来获得三角形。我们指定顶点的缓冲区,格式,和偏移量。代码如下:
// Set index buffer
g_pImmediateContext->IASetIndexBuffer(g_pIndexBuffer, DXGI_FORMAT_R16_UINT, 0 );
改写顶点渲染器
在前面教程中的顶点渲染器,我们以顶点坐标为输入,以相同的顶点坐标为输出,没有进行任何的修改。我们能够完成这个是因为输入的顶点坐标已经被定义在投影空间内。现在,因为我们的顶点是被定义在对象空间。在顶点渲染器输出之前,必须对顶点进行变换。我们需要三个步骤:将对象空间转换到世界空间,然后转换到观察空间,最后转换到投影空间。我们第一件事要做的是申请静态的缓冲变量。静态缓冲区用来保存应用程序需要传入渲染器的数据。在渲染之前,应用程序通常将重要的数据写入静态缓冲区,并且在渲染数据期间西欧哪个渲染器中可以获取这些数据。在1个FX文件中,静态缓冲变量就像C++结构体中的全局变量。我们要用的者三个变量分别是世界矩阵,观察矩阵,投影矩阵,在HLSL中,他们的数据类型为matrix。
一旦我们清楚了我们所需要的矩阵,我们利用这些矩阵里运行顶点渲染器来处理输入的坐标数据。1个向量将被矩阵中多个向量变换。在HLSL,通过使用内置函数mul()。代码如下:
cbufferConstantBuffer : register( b0 ){
matrix World;
matrix View;
matrix Projection;
}
//
// Vertex Shader
//
VS_OUTPUT VS( float4 Pos : POSITION, float4 Color : COLOR ){
VS_OUTPUT output = (VS_OUTPUT)0;
output.Pos = mul( Pos, World );
output.Pos = mul( output.Pos, View );
output.Pos = mul( output.Pos, Projection );
output.Color = Color;
return output;
}
在这个顶点渲染器中,每个mul()函数应用1个变换作用于输入坐标。按顺序使用世界矩阵,观察矩阵,投影矩阵。这个顺序是固定的,不允许交换。
设置矩阵
通过使用这些矩阵,我们可以更新我们的顶点渲染器,但我们也需要在程序中定义这3个矩阵。在渲染之际,这三个矩阵保存着我们需要使用的变换。在渲染之前,我们将这三个矩阵的值复制到渲染器的静态缓冲区。然后,通过调用Draw()来启动渲染,我们的顶点渲染器从静态缓冲区读出矩阵数据。除了这些矩阵,我们还需要1个ID3D11Buffer对象来表示静态缓冲区。代码如下:
ID3D11Buffer* g_pConstantBuffer = NULL;
X美眉ATRIXg_World;
X美眉ATRIX g_View;
X美眉ATRIX g_Projection;
为了创建ID3D11Buffer对象,我们使用ID3D11Device::CreateBuffer(),并指定D3D11_BIND_CONSTANT_BUFFER。
D3D11_BUFFER_DESC bd;
ZeroMemory( &bd, sizeof(bd) );
bd.Usage = D3D11_USAGE_DEFAULT;
bd.ByteWidth = sizeof(ConstantBuffer);
bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER;
bd.CPUAccessFlags = 0;
if( FAILED(g_pd3dDevice->CreateBuffer(&bd, NULL, &g_pConstantBuffer ) ))
return hr;
接下来我们需要做的就是利用这三个矩阵进行变换。我们要让三角形设置在原点,并平行于XY平面。那么相对于对象空间,这些顶点要如何保存呢?所以世界变换不需要做任何事情,我们只需要把世界矩阵设置为单位矩阵就可以。我们要设置摄像机的位置在(0,1,-5),看着(0,1,0)点,向上的向量为(0,1,0)。我们调用X美眉atrixLookAtLH()函数来方便地计算出观察矩阵。我们习惯Y轴的正方向为向上的方向。最后,计算投影矩阵,我们调用X美眉atrixPerspectiveFovLH()函数,以90°的垂直视野(即pi/2),aspect宽高比为640/480,这个数据来自背景缓冲区的宽高。Zn=0.1,Zf=110,。这意味着任何事物力摄像机的距离小于0.1或者大于110将视为不可见。这3个矩阵分别保存在全局变量g_World,g_View, and g_Projection上。
更新静态缓冲区
我们已经有了这些矩阵,现在我们必须把这些数据写入静态缓冲区,以至于GPU进行渲染之际能读到它。为了更新这个缓冲区,我们使用ID3D11DeviceContext::UpdateSubresource()函数,并传给它1个指向矩阵的指针,同样,传入指向静态缓冲区的指针。为了顺利地完成这个,我们创建1个包含于静态缓冲区同样格式的结构体。这是因为,矩阵在C++和HLSL的保存方式不一样。在更新之前,我们必须传送这些矩阵。
//
// Updatevariables
//
ConstantBuffer cb;
cb.mWorld =X美眉atrixTranspose( g_World );
cb.mView =X美眉atrixTranspose( g_View );
cb.mProjection = X美眉atrixTranspose( g_Projection );
g_pImmediateContext->UpdateSubresource(g_pConstantBuffer, 0, NULL, &cb, 0, 0 );
二 : DirectX11SDK文档(三)
总结
在前面的教程中,我们创建了1个小型的,能够显示单颜色的窗口的Direct3D11应用程序。在这个教程中,我们将扩展这个应用程序将1个三角形显示在屏幕上,我们将贯穿这个过程来创建关于三角形的数据结构。效果如上图。
三角形的元素
1个三角形由3个点组成,也可以叫做顶点。用1个顶点数组来定义1个三角形,为了GPU能够渲染这个三角形,我们必须告诉GPU三角形的3个顶点的坐标。以2D为例,对我们来讲很容易即可渲染1个像图1那样的三角形。我们要向GPU传送3个顶点(0,0),(0,1),(1,0),这样GPU有足够的信息来渲染我们想要的三角形。
所以,我们现在知道我们必须传3个顶点坐标给GUP来渲染1个三角形。那我们要如何传这些信息给GPU呢?在Direct3D11中,向顶点信息(如坐标)叫保存在1个缓冲区资源内。如果1个缓冲区用来存放顶点信息,那么它就叫做顶点缓冲区。我们必须创建1个足够大的顶点缓冲区来保存者3个顶点坐标。在Direct3D11中,创建1个顶点缓冲区必须指定大小(以byte为单位)。我们知道这个缓冲区有足够的空间来存放这个顶点,但是每个顶点有多少byte呢?回答好这个问题之前,我们需知道顶点格式。
Input Layout
1个顶点有1个坐标信息。但通常不仅仅只有顶点信息,它也可能有其他的属性。比如:向量,多个的颜色,纹理坐标等等。顶点格式将定义这些属性在内存中如何存放:每个数据属性的用法,每个数据属性的大小,还有属性之间的排列。由于属性通常有不同的类型,类似于C中的结构体,1个顶点通常用1个结构体表示。可以方便地根据1个结构体的大小来获取顶点的大小。
在这个教程中,我们只使用到坐标信息,所以我们的顶点结构体只声明1个XMFLOAT3,XMFLOAT3由3个浮点数组成,它的典型应用就是3D内表示坐标。
structSimpleVertex{
XMFLOAT3 Pos; // Position
};
现在我们有1个结构体来表示我们的顶点。在应用程序中要保存好这些顶点信息。然而,当我们需要GPU渲染包含这些顶点的顶点缓冲区之际,我们要将它填入1个内存块中。GPU也必须知道顶点格式,以便于从缓冲区中提取顶点信息。完成这个需要了解inputlayout。
在Direct3D 11中,inputlayout是Direct3D对象,它能以1种GPU能够理解的方式来描述顶点结构体。每个顶点属性能够用D3D11_INPUT_ELEME(www.61k.com]NT_DESC结构体来描述。1个应用程序定义1个或多个的D3D11_INPUT_ELEMENT_DESC结构体数组。使用这些数组来创建inputlayout对象来描述顶点。D3D11_INPUT_ELEMENT_DESC结构体细节如下:
SemanticName | SemanticName是1个用来描述属性的字符串。这个字符串表示1个单词,这个单词可以使用C能够定义的任何格式。目前对于描述position属性的1个好的SemanticName是POSITION。SemanticName不区分大小写。 |
SemanticIndex | SemanticIndex补充SemanticName。1个顶点可能有多个属性是一样的。比如:有两组纹理坐标或者有两组颜色,用数字来代替SemanticName的使用,比如:”COLOR0”和”COLOR1”。这2个元素可以共享1个SemanticName—“COLOR”,使用0和1这2个不同的索引。 |
Format | 元素使用Format定义了数据类型。目前,DXGI_FORMAT_R32G32B32_FLOAT数据格式有三个32位的浮点数,使这个元素有12字节大小。DXGI_FORMAT_R16G16B16A16_UINT有四个16字节无符号整型数,使这个元素8字节大小。 |
InputSlot | 根据以前的方法,1个Direct3D11应用程序通过使用顶点缓冲区的方式把顶点数据传给GPU,Direct3D11在中,多种顶点缓冲区能够同时被传入GPU,确切的说是十六种。每种顶点缓冲区将有1个数值在[0,15]绑定到Inputslot。这个InputSlot告诉GPU可以从哪个顶点缓冲区获取这个元素。 |
AlignedByteOffset | 1个顶点保存在1个顶点缓冲区中,这个顶点缓冲区是一块连续的内存。AlignedByteOffset告诉GPU偏移量来获取这个元素。 |
InputSlotClass | 这个通常保存有D3D11_INPUT_PER_VERTEX_DATA。当1个应用程序使用Instancing时,它能够设置1个inputlayout的InputSlotClass到D3D11_INPUT_PER_INSTANCE_DATA与包含实例数据的顶点缓冲区一起工作,Instancing是1个超前的Direct3D话题,并且不会这里做深入讨论。对于我们的教程,我们将专门使用D3D11_INPUT_PER_VERTEX_DATA。 |
InstanceDataStepRate | 这个用于Instancing,既然我们没有使用Instancing,InstanceDataStepRate没有被用到,所以必须设置为0 |
现在我们能够定义我们的D3D11_INPUT_ELEMENT_DESC数组并且创建input layout:
// Define the input layout
D3D11_INPUT_ELEMENT_DESC layout[] ={
{ "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0,D3D11_INPUT_PER_VERTEX_DATA, 0},
};
UINT numElements = ARRAYSIZE(layout);
顶点格式
在下1个教程,我们将解释这个技术对象和渲染器。现在,我们把注意力集中在创建Direct3D11顶点格式对象。然而,我们将了解到顶点渲染器将紧密地跟顶点格式联系在一起。原因在于创建1个顶点格式对象需要顶点渲染器的输入签名,我们将使用D3DX11CompileFromFile函数所返回的ID3DBlob对象来检索二进制数据,该二进制数据代表顶点渲染器的输入签名。一旦我们获得这个数据,我们就能够调用ID3D11Device::CreateInputLayout()函数来创建顶点格式对象,和调用ID3D11DeviceContext::IASetInputLayout()来激活这个顶点格式。代码如下:
// Create the input layout
if( FAILED( g_pd3dDevice->CreateInputLayout( layout,numElements,pVSBlob->GetBufferPointer(),
pVSBlob->GetBufferSize(),&g_pVertexLayout ) ) )
return FALSE;
// Set the input layout
g_pImmediateContext->IASetInputLayout(g_pVertexLayout );
创建顶点缓冲区
还有一件事在初始化时候要做的是创建顶点缓冲区保存顶点数据。在Direct3D11中,为了创建1个顶点缓冲区,我们要填充2个结构体D3D11_BUFFER_DESC和D3D11_SUBRESOURCE_DATA,然后调用ID3D11Device::CreateBuffer()函数。D3D11_BUFFER_DESC描述要创建的顶点缓冲区对象。在顶点缓冲区创建之际,D3D11_SUBRESOURCE_DATA描述在将要复制到顶点缓冲区的实际数据。顶点缓冲区在创建之际初始化,所以之后我们就不要再初始化。将要复制到顶点缓冲区的数据是顶点数组,是三个SimpleVertex结构体的数组,顶点数组中坐标的选择是为了让三角形能显示在窗口的中央。顶点缓冲区创建之后,我们可以调用ID3D11DeviceContext::IASetVertexBuffers()将它绑定到device上。代码如下:
// Create vertex buffer
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
PrimitiveTopology是关于GPU如何获得要渲染三角形的三个顶点。我们上面讨论的都是如何让渲染1个简单三角形,应用程序需要传送这个三个顶点给GPU。所以,顶点缓冲区保存有三个顶点信息。那如果我们想要渲染2个三角形,该如何做呢?1种办法就是向GPU发送六个顶点。前三个顶点定义第1个三角形,后3个顶点定义第二个三角形,这个Topology叫做三角形列。三角形列是比较好理解的,当然它并不高效。这些例子能够成功地渲染三角形。目前为止图3a显示了1个矩形由2个三角形组成:ABC和CBD(按照惯例,以顺时针的顺序依次组成三角形列)。如果我们以三角形列的方式发送这2个三角形给GPU。我们的顶点应该这样定义:
A B C C B D
注意:B和C在顶点缓冲区中出现了两次,因为他们分别被2个三角形使用。
我们能够使顶点缓冲区更小,如果告诉GPU在渲染第二个三角形之际,重复使用第1个三角形的最后2个顶点,然后再加上接下来的1个顶点组成第二个三角形。Direct3D支持这种格式,它叫做三角形带。当渲染1个三角形带时,第1个三角形由顶点数组的前3个顶点定义,那个接下来的三角形由前1个三角形的最后2个顶点和接下来的1个顶点定义。那么顶点应该这样定义:
A B C D
前3个顶点,即A B C定义第1个三角形。第二个三角形由BC(第1个三角形的最后2个顶点)加上D来定义。那个通过使用三角带,我们的顶点缓冲区从之前的六个顶点缩小为现在的四个顶点。
同理如图3b,使用三角列的话,顶点定义如下:ABC CBD CDE
如果使用三角带的话,顶点定义如下:A B C D E
你可能会注意到三角带的例子中,第二个三角形是由B CD来定义,这3个顶点的排列不是顺时针方向。使用三角形带时这是很正常的现象。这种现象只出现在第二个三角形,第4个三角形,第6个三角形,第8个三角形等等。这就确保了每个三角形的顶点都能够绕顺时针方向(顺时针,在这个例子中)。除了三角形列和三角形带,还有其他一些Direct3D支持的PrimitiveTopology,在这个教程上不做讨论。
在我们的代码里,我们只需要1个三角形,所以我们用三角形列还是三角形带都可以。但是它必须定义,所以我们使用三角形列。
// Set primitive topology
g_pImmediateContext->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST );
渲染三角形
最后的代码就是如何渲染这个三角形。我们已经创建了2个渲染器用于渲染,顶点渲染器和像素渲染器。顶点渲染器负责将三角形的各个顶点变换到正确的位置。像素渲染器负责三角形内部的最后输出颜色的计算。这个将在下1个教程详细讲解。为了使用这2个渲染器,我们必须分别调用ID3D11DeviceContext::VSSetShader()函数和ID3D11DeviceContext::PSSetShader()函数。最后我们要做的就是调用ID3D11DeviceContext::Draw()函数,该函数命令GPU使用当前的顶点缓冲区,当前的顶点格式,当前的primitivetopology进行渲染。Draw()的第1个参数要传给GPU的顶点个数。第二个参数表示顶点缓冲区的起始索引。因为我们要渲染1个三角形,并且我们从顶点缓冲区的0位置开始。我们分别用3和0来填充这2个参数。代码如下:
//Render a triangle
g_pImmediateContext->VSSetShader( g_pVertexShader,NULL, 0 );
g_pImmediateContext->PSSetShader( g_pPixelShader,NULL, 0 );
g_pImmediateContext->Draw( 3, 0 );
61阅读| 精彩专题| 最新文章| 热门文章| 苏ICP备13036349号-1