注:尽管大部分的程序都使用DrawPrimitive方法来进行渲染,但是Direct3D仍然支持使用执行缓冲。使用执行缓冲进行渲染的方法见“执行缓冲”。
4.4.1 关于渲染第一个设备接口(IDirect3Ddevice)之后的Direct3D设备接口都支持DrawPrimitive渲染方法。DrawPrimitive中的方法可以渲染不同类型的图元,如点、线和三角形集合。(图元的详细讨论见“图元类型”。)
你可以通过两种方式将顶点组合成为图元:
使用
DrawPrimitive方法,可以通过一次调用来渲染一组顶点(这些顶点组成了多个图元),也可以渲染完组成一个图元的顶点之后,再渲染组成另一个图元的顶点。详细内容见“渲染图元”。 4.4.2 开始和结束一个场景通过调用IDirect3DDevice3::BeginScene方法,通知Direct3D场景的渲染将要开始。BeginScene使系统检查它内部的数据结构,渲染表面的有效性和正确性,还设置一个内部标志来显示正在渲染一个场景。开始一个场景之后,可以调用不同的函数来渲染场景中的图元或组成对象的顶点。如果场景不在进行中,那么调用渲染函数的尝试会失败,并返回D3DERR_SCENE_NOT_IN_SCENE。有关渲染函数的详细内容见“渲染图元”。
完成一个场景之后,调用IDirect3DDevice3::EndScene方法。EndScene方法会清除显示场景正在进行的那个内部标志,冲掉cache数据,并检查渲染表面是否完整。
所有的渲染函数的调用都必须在BeginScene和EndScene之间进行。如果表面丢失或出现内部错误,函数会返回错误的值。每次调用BeginScene方法,都必须调用EndScene方法,即使BeginScene方法失败了。我们来看下面的例子:
HRESULT hr; if(SUCCEEDED(lpD3D->BeginScene())) { // Render primitives only if the scene // started successfully. } // Always close the scene, even if BeginScene fails. hr = lpD3D->EndScene(); if(FAILED(hr)) return hr; |
我们不能再场景中嵌入另一个场景;也就是说,必须渲染完一个场景之后,才能开始渲染另一个场景。没有IDirect3DDevice3::BeginScene方法就调用了IDirect3DDevice3::EndScene方法会返回一个D3DERR_SCENE_NOT_IN_SCENE错误值。同样的,在前一个场景没有结束之前就调用BeginScene方法,也会导致D3DERR_SCENE_IN_SCENE错误。
当一个场景正在进行时,在DirectDraw表面(例如渲染目标或纹理)不能调用GDI函数。如果这样做了,会使GDI操作的结果不可见。如果程序要使用GDI函数,一定要确保所有GDI调用在场景函数之外。
渲染场景中的对象之前,必须清空渲染目标表面(render target surface)上的视口或视口的子集。清空视口时,系统会设置渲染目标表面的所需的部分,并将相关的深度或模板缓冲设置到所需的状态。这样就使要渲染的表面中的区域重新复位,也重新设置了相关的深度和模板缓冲。渲染目标表面的清空操作能够为需要的区域设置一种缺省的颜色或纹理。对于深度和模板缓冲,能够设置一个深度或模板值。
IDirect3DViewport3接口提供IDirect3DViewport3::Clear和IDirect3DViewport3::Clear2方法来清空视口。有关这些方法的详细信息见“视口与裁剪”中的“清空视口”部分。
下面介绍DrawPrimitive渲染方法以及它们的用法:
Direct3D提供了一些方法,使用这些方法可以在一次单独的调用中渲染图元和索引图元(indexed primitive),或渲染单独的顶点。所有IDirect3DDevice3接口中的渲染方法都接受联合的可塑(flexible)顶点格式标志,这些标志用来描述程序中使用的顶点类型,还决定了系统使用几何渲染管道的哪些部分。(这一点与IDirect3DDevice2中的方法有所不同,IDirect3DDevice2中的方法只接受具体的由D3DPRIMITIVETYPE枚举类型确定的顶点格式。)要了解有关这些描述符(descriptor)的详细内容见“顶点格式”部分。 DrawPrimitive中的方法可以按照图元的类型(非索引与索引)再进行细分。系统为两种图元类型都提供了相应的方法,并且又分为一次渲染一组顶点还是一个顶点。具体的分类如下:IDirect3DDevice3::DrawPrimitive, IDirect3DDevice3::DrawPrimitiveStrided,
和IDirect3DDevice3::DrawPrimitiveVB渲染成组的非索引顶点,跨距顶点(strided vertice),和包含在顶点缓冲中的非索引顶点。IDirect3DDevice3::DrawIndexedPrimitive
,IDirect3DDevice3::DrawIndexedPrimitiveStrided, 和IDirect3DDevice3::DrawIndexedPrimitiveVB方法渲染成组的索引顶点,跨距顶点(strided vertice),或顶点缓冲中的索引顶点。与非索引顶点渲染方法相似的是,系统提供了
IDirect3DDevice3::BeginIndexed方法对索引顶点按照它们的索引值开始进行渲染,然后对每一个顶点调用IDirect3DDevice3::Index方法。(就向非索引顶点,结束渲染之后要调用IDirect3DDevice3::End)。当渲染图元时,相关的方法接受表示图元类型(例如三角带,三角形列表,或者别的图元类型)的参数、顶点格式、渲染行为标志和顶点信息。
要注意,你提供给这些方法的顶点的数量决定于你要渲染的图元的类型。例如,如果你要渲染一条线段列表(
a line list),那么你就至少要提供两个顶点来定义它,并且顶点总数必须是偶数。同样的,对于一个三角形列表(a triangle list),必须有至少三个顶点,并且总数必须能被3整除。有关其他类型的图元所需的顶点数目,见“图元类型”和“D3DPRIMITIVETYPE”。利用顶点缓冲进行渲染,比直接使用顶点的数据结构来渲染有更好的性能和易用性。详细内容见“顶点缓冲”。
Rendering Strided Vertices由于跨距顶点所提供的间接寻址特性(详见“跨距顶点格式”),程序必须仔细设置顶点的属性。通常,我们总是会忘记在相应的
D3DDRAWPRIMITIVESTRIDEDDATA结构中包含一个顶点的各个成分。这样的失误不一定会导致渲染方法的失败,但却会造成“丢失”几何体,这样的错误一般很难查找。下面的代码展示了如何来设置和渲染跨距点:
//--------------------------------------------- // Make an array of
custom vertices. // (vertex at index
0) // Construct
strided vertices vertex using the array of g_StridedData.position.lpvData
= &g_avWallVertices[24].x; |
Direct3D
可以创建和操作下面几种图元类型: Point Lists使用
IDirect3DDevice3::DrawPrimitive或IDirect3DDevice3::DrawIndexedPrimitive方法来渲染这些类型的图元。详细内容见“渲染”。1)点列表
一个点列表是一些独立的顶点的集合。在三维场景中,它们可以用来作为星空或是表面上的点画线。下面的代码展示了如何填充一个顶点数组来创建一个点列表:
const DWORD TOTAL_VERTS=6; D3DVERTEX lpVerts[TOTAL_VERTS]; lpVerts[0] = D3DVERTEX(D3DVECTOR(-5,-5,0),D3DVECTOR(0,0,-1),0,0); lpVerts[1] = D3DVERTEX(D3DVECTOR(0,5,0),D3DVECTOR(0,0,-1),0,0); lpVerts[2] = D3DVERTEX(D3DVECTOR(5,-5,0),D3DVECTOR(0,0,-1),0,0); lpVerts[3] = D3DVERTEX(D3DVECTOR(10,5,0),D3DVECTOR(0,0,-1),0,0); lpVerts[4] = D3DVERTEX(D3DVECTOR(15,-5,0),D3DVECTOR(0,0,-1),0,0); lpVerts[5] = D3DVERTEX(D3DVECTOR(20,5,0),D3DVECTOR(0,0,-1),0,0); |
使用IDirect3DDevice3::DrawPrimitive方法来渲染点列表。下面的代码使用IDirect3DDevice3::DrawPrimitive方法来画出上例中的点列表。所有的IDirect3DDevice3::DrawPrimitive调用都要在IDirect3DDevice3::BeginScene和IDirect3DDevice3::EndScene之间。
HRESULT hResult; // This code fragment assumes that lpDirect3DDevice3 is a valid // pointer to an IDirect3DDevice3 interface. hResult = lpDirect3DDevice3->DrawPrimitive(D3DPT_POINTLIST, D3DFVF_VERTEX, lpVerts, TOTAL_VERTS, D3DDP_WAIT); |
下图是得到的结果:
程序可以对点列表使用材质和纹理。材质或纹理的颜色只能出现在有点的地方,不会出现在两个点之间。
2)直线列表一个直线列表是一些独立的直线段的集合。在三维场景中,线队列可以用来增加雨雪效果。
下面的代码创建了一个直线列表。一个直线列表中顶点的数量必须大于或等于2,并且必须是偶数。
const DWORD TOTAL_VERTS=6;
D3DVERTEX lpVerts[TOTAL_VERTS];
lpVerts[0] = D3DVERTEX(D3DVECTOR(-5,-5,0),D3DVECTOR(0,0,-1),0,0);
lpVerts[1] = D3DVERTEX(D3DVECTOR(0,5,0),D3DVECTOR(0,0,-1),0,0);
lpVerts[2] = D3DVERTEX(D3DVECTOR(5,-5,0),D3DVECTOR(0,0,-1),0,0);
lpVerts[3] = D3DVERTEX(D3DVECTOR(10,5,0),D3DVECTOR(0,0,-1),0,0);
lpVerts[4] = D3DVERTEX(D3DVECTOR(15,-5,0),D3DVECTOR(0,0,-1),0,0);
lpVerts[5] = D3DVERTEX(D3DVECTOR(20,5,0),D3DVECTOR(0,0,-1),0,0);
使用IDirect3DDevice3::DrawPrimitive方法来渲染直线列表。下面的例子使用IDirect3DDevice3::DrawPrimitive方法画出了上面定义的直线列表。
HRESULT hResult;
// This code fragment assumes that lpDirect3DDevice3 is a valid
// pointer to an IDirect3DDevice3 interface.
hResult = lpDirect3DDevice3 ->DrawPrimitive(D3DPT_LINELIST,
D3DFVF_VERTEX,
lpVerts,
TOTAL_VERTS,
D3DDP_WAIT);
下图为得到的结果:
对直线列表也可以使用材质和纹理。材质或纹理的颜色只能表现在线上,不会出现在线以外的点上。
3)直线带直线带由一些相连的直线段组成。直线带可以用来创建不闭合的多边形。闭合多边形的最后一个顶点与第一个顶点通过一条线段相连在一起。如果程序使用直线带来创建多边形,那么得到的顶点将不一定是共面的。
下面我们来创建一个直线带:
const DWORD TOTAL_VERTS=6;
D3DVERTEX lpVerts[TOTAL_VERTS];
lpVerts[0] = D3DVERTEX(D3DVECTOR(-5,-5,0),D3DVECTOR(0,0,-1),0,0);
lpVerts[1] = D3DVERTEX(D3DVECTOR(0,5,0),D3DVECTOR(0,0,-1),0,0);
lpVerts[2] = D3DVERTEX(D3DVECTOR(5,-5,0),D3DVECTOR(0,0,-1),0,0);
lpVerts[3] = D3DVERTEX(D3DVECTOR(10,5,0),D3DVECTOR(0,0,-1),0,0);
lpVerts[4] = D3DVERTEX(D3DVECTOR(15,-5,0),D3DVECTOR(0,0,-1),0,0);
lpVerts[5] = D3DVERTEX(D3DVECTOR(20,5,0),D3DVECTOR(0,0,-1),0,0);
使用
IDirect3DDevice3::DrawPrimitive方法来渲染直线带。下面的代码使用IDirect3DDevice3::DrawPrimitive画出了上面定义的直线带。
HRESULT hResult;
// This code fragment assumes that lpDirect3DDevice3 is a valid
// pointer to an IDirect3DDevice3 interface.
hResult = lpDirect3DDevice3 ->DrawPrimitive(D3DPT_LINESTRIP,
D3DFVF_VERTEX,
lpVerts,
TOTAL_VERTS,
D3DDP_WAIT);
下图为得到的结果:
4)三角形列表
一个三角形列表是一系列的独立的三角形。它们或者相邻或者不相邻。一个三角形列表至少要有三个顶点,并且顶点的总数要能被
3整除。当你要创建一个由分散的小片(
disjoint pieces)组成的对象时,可以使用三角形列表。例如,一个三维游戏中的作战掩体就可以用由不相接的小三角形组成的队列来创建。然后对这个三角形列表使用材质和纹理,使它散发出光线。这样,掩体上的每一个三角形就会发出光线。掩体后面的场景只能透过三角形的缝隙看到一部分。这样就能得到我们所期望的作战掩体。三角形列表还常常用来创建具有尖利边缘的使用
Gouraud明暗处理方法的图元。下面我们来创建一个三角形列表:
const DWORD TOTAL_VERTS=6;
D3DVERTEX lpVerts[TOTAL_VERTS];
lpVerts[0] = D3DVERTEX(D3DVECTOR(-5,-5,0),D3DVECTOR(0,0,-1),0,0);
lpVerts[1] = D3DVERTEX(D3DVECTOR(0,5,0),D3DVECTOR(0,0,-1),0,0);
lpVerts[2] = D3DVERTEX(D3DVECTOR(5,-5,0),D3DVECTOR(0,0,-1),0,0);
lpVerts[3] = D3DVERTEX(D3DVECTOR(10,5,0),D3DVECTOR(0,0,-1),0,0);
lpVerts[4] = D3DVERTEX(D3DVECTOR(15,-5,0),D3DVECTOR(0,0,-1),0,0);
lpVerts[5] = D3DVERTEX(D3DVECTOR(20,5,0),D3DVECTOR(0,0,-1),0,0);
使用IDirect3DDevice3::DrawPrimitive来渲染三角形列表。下面的例子使用IDirect3DDevice3::DrawPrimitive画出了上面顶一个三角形列表。
HRESULT hResult;
// This code fragment assumes that lpDirect3DDevice3 is a valid
// pointer to an IDirect3DDevice3 interface.
hResult = lpDirect3DDevice3 ->DrawPrimitive(D3DPT_TRIANGLELIST,
D3DFVF_VERTEX,
lpVerts,
TOTAL_VERTS,
D3DDP_WAIT);
下图为得到的结果:
5)三角带
一个三角带是一些相互连接的三角形。因为这些三角形相互连接,所以程序不必再重复说明每个三角形中的三个顶点。例如下面的三角带,我们只需要说明
7个顶点就够了。
使用顶点
v1、v2和v3画出第一个三角形,使用v2、v4和v3画出第二个三角形,使用v3、v4和v5画出第三个三角形,用v4、v6和v5画出第四个三角形,等等。注意第二和第四个三角形的顶点顺序与数字顺序不一致,这是因为三角形的绘制是按照顺时针方向来进行的。三维场景中的对象大多数都是由三角带组成的。这主要是因为,在使用三角带创建复杂对象时,能够更有效的使用内存和处理时间。三角带比应用于执行缓冲的
D3DTRIANGLE结构更容易使用。下面我们来创建一个三角带:
const DWORD TOTAL_VERTS=6;
D3DVERTEX lpVerts[TOTAL_VERTS];
lpVerts[0] = D3DVERTEX(D3DVECTOR(-5,-5,0),D3DVECTOR(0,0,-1),0,0);
lpVerts[1] = D3DVERTEX(D3DVECTOR(0,5,0),D3DVECTOR(0,0,-1),0,0);
lpVerts[2] = D3DVERTEX(D3DVECTOR(5,-5,0),D3DVECTOR(0,0,-1),0,0);
lpVerts[3] = D3DVERTEX(D3DVECTOR(10,5,0),D3DVECTOR(0,0,-1),0,0);
lpVerts[4] = D3DVERTEX(D3DVECTOR(15,-5,0),D3DVECTOR(0,0,-1),0,0);
lpVerts[5] = D3DVERTEX(D3DVECTOR(20,5,0),D3DVECTOR(0,0,-1),0,0);
程序使用IDirect3DDevice3::DrawPrimitive来渲染三角带。下面的例子使用IDirect3DDevice3::DrawPrimitive绘制上面定义的三角带。
HRESULT hResult;
// This code fragment assumes that lpDirect3DDevice3 is a valid
// pointer to an IDirect3DDevice3 interface. hResult = lpDirect3DDevice3 ->DrawPrimitive(D3DPT_TRIANGLESTRIP,
D3DFVF_VERTEX,
lpVerts,
TOTAL_VERTS,
D3DDP_WAIT);
结果如下:
6)三角扇
三角扇与一个三角带相类似,区别仅在于三角扇中的三角形都共用一个顶点,如下图所示:
系统用顶点
v2、v3和v1来绘制第一个三角形,用v3、v4和v1绘制第二个三角形,等等。(使用平面明暗处理模式时,系统用第一个顶点的颜色值来处理三角形。)下面我们来创建一个三角扇:
const DWORD TOTAL_VERTS=6;
D3DVERTEX lpVerts[TOTAL_VERTS];
lpVerts[0] = D3DVERTEX(D3DVECTOR(0,0,0),D3DVECTOR(0,0,-1),0,0);
lpVerts[1] = D3DVERTEX(D3DVECTOR(-5,-5,0),D3DVECTOR(0,0,-1),0,0);
lpVerts[2] = D3DVERTEX(D3DVECTOR(-3,7,0),D3DVECTOR(0,0,-1),0,0);
lpVerts[3] = D3DVERTEX(D3DVECTOR(0,10,0),D3DVECTOR(0,0,-1),0,0);
lpVerts[4] = D3DVERTEX(D3DVECTOR(3,7,0),D3DVECTOR(0,0,-1),0,0);
lpVerts[5] = D3DVERTEX(D3DVECTOR(5,5,0),D3DVECTOR(0,0,-1),0,0);
下面的例子使用IDirect3DDevice3::DrawPrimitive来绘制上面定义的三角扇。
结果如下图:
HRESULT hResult;
// This code fragment assumes that lpDirect3DDevice3 is a valid
// pointer to an IDirect3DDevice3 interface.
hResult = lpDirect3DDevice3->DrawPrimitive(D3DPT_TRIANGLEFAN,
D3DFVF_VERTEX,
lpVerts,
TOTAL_VERTS,
D3DDP_WAIT);