4.4  渲染

  Direct3D提供的DrawPrimitive方法使一个三维场景的渲染过程变得更为简单和灵活。下面我们来讨论以DrawPrimitive为中心的有关渲染的内容:

  注:尽管大部分的程序都使用DrawPrimitive方法来进行渲染,但是Direct3D仍然支持使用执行缓冲。使用执行缓冲进行渲染的方法见“执行缓冲”。

4.4.1 关于渲染

  第一个设备接口(IDirect3Ddevice)之后的Direct3D设备接口都支持DrawPrimitive渲染方法。DrawPrimitive中的方法可以渲染不同类型的图元,如点、线和三角形集合。(图元的详细讨论见“图元类型”。)

你可以通过两种方式将顶点组合成为图元:

  1. 依照次序排列在一个数组中,数组中包括了所有组成图元的顶点。
  2. 将顶点不依照次序排列在一个数组中,同时有一个由一些数值组成的一个有秩序的数组,其中的每一个值都是无序数组中的一个顶点的索引值。这种类型的图元有时被称为“索引图元(indexed primitives)”。

  使用DrawPrimitive方法,可以通过一次调用来渲染一组顶点(这些顶点组成了多个图元),也可以渲染完组成一个图元的顶点之后,再渲染组成另一个图元的顶点。详细内容见“渲染图元”。

4.4.2 开始和结束一个场景

  通过调用IDirect3DDevice3::BeginScene方法,通知Direct3D场景的渲染将要开始。BeginScene使系统检查它内部的数据结构,渲染表面的有效性和正确性,还设置一个内部标志来显示正在渲染一个场景。开始一个场景之后,可以调用不同的函数来渲染场景中的图元或组成对象的顶点。如果场景不在进行中,那么调用渲染函数的尝试会失败,并返回D3DERR_SCENE_NOT_IN_SCENE。有关渲染函数的详细内容见“渲染图元”。

  完成一个场景之后,调用IDirect3DDevice3::EndScene方法。EndScene方法会清除显示场景正在进行的那个内部标志,冲掉cache数据,并检查渲染表面是否完整。

  所有的渲染函数的调用都必须在BeginSceneEndScene之间进行。如果表面丢失或出现内部错误,函数会返回错误的值。每次调用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调用在场景函数之外。

4.4.3 清空表面

  渲染场景中的对象之前,必须清空渲染目标表面(render target surface)上的视口或视口的子集。清空视口时,系统会设置渲染目标表面的所需的部分,并将相关的深度或模板缓冲设置到所需的状态。这样就使要渲染的表面中的区域重新复位,也重新设置了相关的深度和模板缓冲。渲染目标表面的清空操作能够为需要的区域设置一种缺省的颜色或纹理。对于深度和模板缓冲,能够设置一个深度或模板值。

  IDirect3DViewport3接口提供IDirect3DViewport3::ClearIDirect3DViewport3::Clear2方法来清空视口。有关这些方法的详细信息见“视口与裁剪”中的“清空视口”部分。

4.4.4 渲染图元

下面介绍DrawPrimitive渲染方法以及它们的用法:

  1. DrawPrimitive方法

  Direct3D提供了一些方法,使用这些方法可以在一次单独的调用中渲染图元和索引图元(indexed primitive),或渲染单独的顶点。所有IDirect3DDevice3接口中的渲染方法都接受联合的可塑(flexible)顶点格式标志,这些标志用来描述程序中使用的顶点类型,还决定了系统使用几何渲染管道的哪些部分。(这一点与IDirect3DDevice2中的方法有所不同,IDirect3DDevice2中的方法只接受具体的由D3DPRIMITIVETYPE枚举类型确定的顶点格式。)要了解有关这些描述符(descriptor)的详细内容见“顶点格式”部分。

  DrawPrimitive中的方法可以按照图元的类型(非索引与索引)再进行细分。系统为两种图元类型都提供了相应的方法,并且又分为一次渲染一组顶点还是一个顶点。具体的分类如下:

  1. 非索引与索引图元方法
  2. IDirect3DDevice3::DrawPrimitive, IDirect3DDevice3::DrawPrimitiveStrided,IDirect3DDevice3::DrawPrimitiveVB渲染成组的非索引顶点,跨距顶点(strided vertice),和包含在顶点缓冲中的非索引顶点。

    IDirect3DDevice3::DrawIndexedPrimitiveIDirect3DDevice3::DrawIndexedPrimitiveStrided, IDirect3DDevice3::DrawIndexedPrimitiveVB方法渲染成组的索引顶点,跨距顶点(strided vertice),或顶点缓冲中的索引顶点。

  3. 非索引和索引顶点方法

  IDirect3DDevice3::Begin方法通知系统开始渲染顶点,然后调用IDirect3DDevice3::Vertex方法对这些顶点进行渲染。结束渲染之后,要调用IDirect3DDevice3::End method 方法。

  与非索引顶点渲染方法相似的是,系统提供了IDirect3DDevice3::BeginIndexed方法对索引顶点按照它们的索引值开始进行渲染,然后对每一个顶点调用IDirect3DDevice3::Index方法。(就向非索引顶点,结束渲染之后要调用IDirect3DDevice3::End)。

  当渲染图元时,相关的方法接受表示图元类型(例如三角带,三角形列表,或者别的图元类型)的参数、顶点格式、渲染行为标志和顶点信息。

  要注意,你提供给这些方法的顶点的数量决定于你要渲染的图元的类型。例如,如果你要渲染一条线段列表(a line list),那么你就至少要提供两个顶点来定义它,并且顶点总数必须是偶数。同样的,对于一个三角形列表(a triangle list),必须有至少三个顶点,并且总数必须能被3整除。有关其他类型的图元所需的顶点数目,见“图元类型”和“D3DPRIMITIVETYPE”。

  利用顶点缓冲进行渲染,比直接使用顶点的数据结构来渲染有更好的性能和易用性。详细内容见“顶点缓冲”。

  1. 渲染跨距顶点 Rendering Strided Vertices

  由于跨距顶点所提供的间接寻址特性(详见“跨距顶点格式”),程序必须仔细设置顶点的属性。通常,我们总是会忘记在相应的D3DDRAWPRIMITIVESTRIDEDDATA结构中包含一个顶点的各个成分。这样的失误不一定会导致渲染方法的失败,但却会造成“丢失”几何体,这样的错误一般很难查找。

下面的代码展示了如何来设置和渲染跨距点:

//---------------------------------------------
// A custom vertex format that includes XYZ, a
// diffuse color & two sets of texture coords.
//---------------------------------------------
struct MTVERTEX
{
 FLOAT x, y, z;
 DWORD dwColor;
 FLOAT tuBase, tvBase;
 FLOAT tuLightMap, tvLightMap;
};

// Make an array of custom vertices.
MTVERTEX g_avVertices[36];
// Fill the array.

// (vertex at index 0)
// .
// .
// .
// (vertex at index 35)

// Construct strided vertices vertex using the array of
// custom vertices already defined.
D3DDRAWPRIMITIVESTRIDEDDATA g_StridedData;
// Assign the addresses of the various interleaved components
// to their corresponding strided members.

g_StridedData.position.lpvData = &g_avWallVertices[24].x;
g_StridedData.diffuse.lpvData = &g_avWallVertices[24].dwColor;
g_StridedData.textureCoords[0].lpvData = &g_avWallVertices[24].tuBase;
g_StridedData.textureCoords[1].lpvData = &g_avWallVertices[24].tuLightMap;
g_StridedData.position.dwStride = sizeof(MTVERTEX);
g_StridedData.diffuse.dwStride = sizeof(MTVERTEX);
g_StridedData.textureCoords[0].dwStride = sizeof(MTVERTEX);
g_StridedData.textureCoords[1].dwStride = sizeof(MTVERTEX);
// Render the vertices with multiple texture blending (Modulate).
g_pd3dDevice->SetTexture平台State( 1, D3DTSS_COLOROP, D3DTOP_MODULATE );
g_pd3dDevice->SetTexture( 0, g_BaseTextureMap);
g_pd3dDevice->SetTexture( 1, g_LightMap);
g_pd3dDevice->DrawPrimitiveStrided( D3DPT_TRIANGLELIST,
                 D3DFVF_XYZ|D3DFVF_DIFFUSE|D3DFVF_TEX2,
                 &g_StridedData, 12, NULL );

 

4.4.5 图元类型

Direct3D可以创建和操作下面几种图元类型:

  使用IDirect3DDevice3::DrawPrimitiveIDirect3DDevice3::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::BeginSceneIDirect3DDevice3::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);

下图是得到的结果:

pic31.gif (1896 bytes)

  程序可以对点列表使用材质和纹理。材质或纹理的颜色只能出现在有点的地方,不会出现在两个点之间。

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);

下图为得到的结果:

pic32.gif (2292 bytes)

  对直线列表也可以使用材质和纹理。材质或纹理的颜色只能表现在线上,不会出现在线以外的点上。

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);

下图为得到的结果:

pic33.gif (3050 bytes)

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);

下图为得到的结果:

pic34.gif (2704 bytes)

5)三角带

  一个三角带是一些相互连接的三角形。因为这些三角形相互连接,所以程序不必再重复说明每个三角形中的三个顶点。例如下面的三角带,我们只需要说明7个顶点就够了。

pic35.gif (3473 bytes)

  使用顶点v1v2v3画出第一个三角形,使用v2v4v3画出第二个三角形,使用v3v4v5画出第三个三角形,用v4v6v5画出第四个三角形,等等。注意第二和第四个三角形的顶点顺序与数字顺序不一致,这是因为三角形的绘制是按照顺时针方向来进行的。

  三维场景中的对象大多数都是由三角带组成的。这主要是因为,在使用三角带创建复杂对象时,能够更有效的使用内存和处理时间。三角带比应用于执行缓冲的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);

结果如下:

pic36.gif (3676 bytes)


6)三角扇

  三角扇与一个三角带相类似,区别仅在于三角扇中的三角形都共用一个顶点,如下图所示:

pic37.gif (3886 bytes)

  系统用顶点v2v3v1来绘制第一个三角形,用v3v4v1绘制第二个三角形,等等。(使用平面明暗处理模式时,系统用第一个顶点的颜色值来处理三角形。)

下面我们来创建一个三角扇:

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);

结果如下图:

pic38.gif (4560 bytes)

 

上一页 | 目录 | 下一页