应用程序使用DrawPrimitive 系列方法渲染三维场景。以下主题讨论用DrawPrimitive渲染。
本节描述Microsoft® Direct3D®中用于控制三维多边形着色的技术。
概述
·
着色模式
·
着色模式的比较
·
设置着色模式
用于渲染多边形的着色模式完全影响到渲染结果。着色模式决定在多边形表面上任意一点上颜色的强度和光照。Microsoft® Direct3D®支持两种着色模式。
在平面着色模式中,当Direct3D渲染流水线渲染一个多边形时,使用第一个顶点的材质颜色作为整个多边形的颜色。用平面着色渲染的三维物体有明显的边缘,如果它们不共面的话。
下图显示了一个用平面着色模式渲染的茶壶。我们可以清楚地看到每个多边形的轮廓。平面着色是计算量最小的着色模式。

当Direct3D使用高洛德着色渲染一个多边形时,它根据顶点法向和光照参数为每个顶点计算颜色。然后,贯穿多边形的表面对颜色进行插值。插值是线性的。例如,如果顶点1的颜色的红色分量为0.8,顶点2的颜色的红色分量为0.4,如果使用高洛德着色模式和RGB颜色模型,那么Direct3D光照模块会把红色分量0.6赋给位于这两个顶点连线中点处的像素。
下图显示了高洛德着色。茶壶由许多平直的三角形组成。但是,高洛德着色使物体的表面看起来是弯曲和光滑的。

高洛德着色也可以用于显示有清晰边缘的物体。
更多信息请参阅表面和顶点法向量。
在平面着色模式中,下面显示的金字塔在相邻表面间会有清晰的边缘。但是在高洛德着色模式中,用于着色的值是贯穿边缘插值得到的,最终看起来是一个弯曲的表面。

高洛德着色使经过光照的平直表面看起来比平面着色更真实。平面着色模式中的表面具有相同的颜色,但高洛德着色能使光线更准确地照射在表面上。如果附近有点光源,那么这种效果特别明显。
高洛德着色使在平面着色中明显可见的边缘变得光滑。但是,它会产生马克带(marc band),这是在相邻多边形间没有平滑混合的颜色带或光带。应用程序可以通过增加物体中多边形的数量,或增大屏幕分辨率,或增加颜色深度来减少马克带效应。
高洛德着色会漏掉一些细节。下图显示了这样一个例子,一个光照点完全被包含在多边形表面内。

在这种情况下,在顶点间进行插值的高洛德着色将会完全漏掉光照点,表面的渲染就好像光照点不存在一样。
Microsoft® Direct3D®允许在任一时刻选择一种着色模式。默认情况下选择高洛德着色。C++应用程序可以通过调用IDirect3DDevice9::SetRenderState方法改变着色模式。应该将State参数设为D3DRS_SHADEMODE。Value参数必须被设为D3DSHADEMODE枚举类型的成员。以下示例代码显示了如何将Direct3D应用程序当前的着色模式设置为平面或高洛德着色模式。
// 设置为平面着色。
// 本示例代码假设pDev是一个指向IDirect3DDevice9接口的有效指针。
hr = pDev->SetRenderState(D3DRS_SHADEMODE,
D3DSHADE_FLAT);
if(FAILED(hr))
{
// 这里是错误处理代码。
}
// 设置为高洛德着色。这是Direct3D的默认设置。
hr = pDev->SetRenderState(D3DRS_SHADEMODE,
D3DSHADE_GOURAUD);
if(FAILED(hr))
{
// 这里是错误处理代码。
}
本节介绍呈现(presentation)应用程序编程接口(API),并讨论了将一个场景呈现在显示设备上的相关问题。
信息被分为以下主题。
概述
呈现API是控制设备状态的一组方法,这些状态会影响用户在显示器上所看到的。这些方法包括设置显示模式和在每一帧用来把图像呈现给用户的方法等。
要理解呈现API,熟悉以下术语是必需的。
因为Microsoft® DirectX® 9.0的Microsoft® Direct3D®有一个交换链作为设备的属性,所以每个设备总是至少有一个交换链。IDirect3DDevice9接口有一组方法操作隐式交换链,这些方法是交换链自身接口的复制品。应用程序可以创建附加的交换链,但是,对一般的单窗口或全屏应用程序而言这并不是必需的。
DirectX 9.0的Direct3D API不直接暴露前缓存。因此,应用程序不能锁定或渲染到前缓存。细节请参阅存取前颜色缓存。
注意 DirectX 7.0提供了许多需要一起调用的呈现API。IDirectDraw7::SetCooperativeLevel,IDirectDraw7::SetDisplayMode和IDirectDraw7:CreateSurface序列就是很好的例子。另外,IDirectDrawSurface7::Flip和IDirectDrawSurface7::Blt方法通知将渲染完的帧数据传送到显示器。DirectX
9.0将这些不同组的API合并为两个主要的方法:Reset和Present。Reset归入了SetcooperativeLevel,SetDisplayMode,CreateSurface和Flip的一些参数。Present归入了Flip和作为呈现API使用的Blt。
对IDirect3D9::CreateDevice的调用表示隐式地重置设备。DirectX
9.0 API没有主表面的概念,应用程序无法创建一个代表主表面的对象。主表面被视为设备的内部属性。
与交换链相关的伽马(gamma)值通过IDirect3DDevice9::GetGammaRamp和IDirect3DDevice9::SetGammaRamp方法进行操作。
除了Direct3DDevice对象拥有并操纵的交换链外,应用程序可以调用IDirect3DDevice9::CreateAdditionalSwapChain方法创建附加的交换链,这样就可以从同一设备呈现到不同的视区。
一般情况下,应用程序为每个视区创建一个交换链,并将每个交换链与特定的视区相关联。应用程序将图像渲染到每个交换链的后缓存中,然后调用IDirect3DDevice9::Present方法单独呈现它们。注意同一时刻每个适配器只能有一个交换链是全屏的。
当设备成功地重置(IDirect3DDevice9::Reset)或在全屏模式下创建(IDirect3D9::CreateDevice)时,创建该设备的Microsoft® Direct3D®对象将被标记为拥有系统中所有的适配器。这种状态被称为独占模式,且Direct3D对象拥有独占模式。独占模式意味着由其它Direct3D对象创建的设备既不能进行全屏操作也不能分配显存。另外,当一个Direct3D对象进入独占模式时,除了当前进入全屏模式以外的所有设备都被置为丢失状态。细节请参阅丢失的设备。
进入独占模式时,Direct3D对象会被告知设备将要使用的焦点窗口。当Direct3D设备拥有的最后一个全屏设备被重置为窗口模式或销毁时,独占模式即被解除。
当一个Direct3D对象拥有独占模式时,可以把设备分为两类。第一类设备具有以下特点:
这类设备在被重置或创建方面没有任何限制,并且它们不会被置为丢失状态。这类设备甚至可以进入全屏模式。
不属于第一类的设备——由另一个Direct3D对象创建,用不同的焦点窗口创建,以及在一个已经存在全屏设备的适配器上创建——不能被重置,并且在独占模式退出之前一直保持丢失状态。因此,多显示器应用程序可以把几个设备置为全屏模式,不过条件是这些设备是同一个Direct3D对象在不同的适配器上创建的,并且共享同一个焦点窗口。
深度缓存与设备相关联。在设置渲染对象时,应用程序需要移动深度缓存。IDirect3DDevice9::GetDepthStencilSurface和IDirect3DDevice9::SetRenderTarget方法用于操作深度缓存。
通过IDirect3DDevice9::GetFrontBuffer方法可以存取前缓存。该方法是取得反走样场景的截图的唯一方法。
以下主题介绍IDirect3DDevice9::DrawPrimitive等渲染方法,并提供关于在应用程序中使用它们的信息。
Microsoft® Direct3D®的渲染接口包含了许多方法,这些方法用存储在一个或多个数据缓存中的顶点数据渲染图元。顶点数据由顶点元素组合而成的顶点成员组成。顶点元素是顶点的最小单位,代表诸如位置、法向和颜色的实体。
顶点成员是一个或多个顶点元素连续存储(每个顶点交叉)在单个内存缓存中。一个完整的顶点由一个或多个成员组成,每个成员位于单独的内存缓存中。要渲染一个图元,需要读取多个顶点成员并将它们组合,这样得到的完整的顶点就可以进行顶点处理。下图显示了使用顶点成员渲染图元的过程。

渲染图元有两个步骤。首先,设置一个或多个顶点成员数据流;然后,调用IDirect3DDevice9::DrawPrimitive方法用那些数据流渲染。对位于这些成员数据流中的顶点元素的识别由顶点着色器指定。更多细节,请参阅顶点着色器。
IDirect3DDevice9::DrawPrimitive方法指定一个顶点数据流中的偏移量,这样就可以在每次调用时渲染一组顶点数据中的一个连续图元的子集。这使应用程序在用同一个顶点缓存渲染其中不同的图元组时,可以改变渲染状态。
Direct3D同时支持使用索引和不使用索引的绘制方法。更多信息,请参阅用顶点和索引缓存渲染。
IDirect3DDevice9::SetStreamSource方法将一个顶点缓存绑定到一个设备数据流,在顶点数据和多个数据流端口中的一个之间创建关联,这些端口给图元处理函数提供数据。对流数据的实际引用直到调用诸如IDirect3DDevice9::DrawPrimitive之类的绘制方法时才发生。
数据流被定义为由成员数据组成的统一数组,其中每个成员由一个或多个表示单个实体的元素组成,如位置、法向、颜色等等。Stride参数指定成员的大小,以字节为单位。
Microsoft® Direct3D®同时支持使用索引和不使用索引的绘制方法。使用索引的方法(以后简称为索引方法)给所有顶点成员使用单独一组索引。顶点数据以顶点缓存的形式呈现给Direct3D应用编程接口(API),给索引绘制方法使用的索引数据以索引缓存的形式呈现。更多信息,请参阅以下参考主题。
这些方法绘制当前输入数据流的数据中的图元。更多有关将索引缓存设置为当前索引数组的细节,请参阅IDirect3DDevice9::SetIndices方法。
第二组渲染接口支持直接来自用户内存的顶点和索引数据。这些接口只支持单独的数据流。更多信息,请参阅以下参考主题。
这些方法用用户内存指针渲染,而不是顶点和索引缓存。
深度缓存,常被称为z缓存或w缓存,是设备的一个属性,用来存储深度信息,给Microsoft® Direct3D®使用。当Direct3D将三维场景渲染到一个目标表面时,它可以使用与深度缓存表面相关的内存作为工作空间来决定经过光栅化的多边形的像素如何相互遮蔽。Direct3D使用一个屏外表面作为写入最后颜色的目的地。与渲染目标表面相关联的深度缓存被用于存储深度信息,告诉Direct3D场景中每个可见像素有多深。
光栅化三维场景时,如果启用了深度缓存,那么渲染表面上的每个点都要被测试。深度缓存中的值可以是一个点的z坐标值或它的齐次坐标的w值——从该点在投影空间中的位置(x,y,z,w)得到。使用z值的深度缓存通常被称为z缓存,使用w值的被称为w缓存。两种深度缓存各有利弊,这在以后讨论。
在测试开始时,深度缓存中的值被设为场景中最大可能达到的值。渲染表面的颜色值被设为背景颜色值或者背景纹理在该点处的颜色值。场景中的每个多边形都要被测试,看它有没有与渲染表面的当前坐标(x,y)相交。如果相交,那么当前点的深度值——z缓存的z坐标,或w缓存中的w坐标——被测试,看它是不是小于存储在深度缓存中的深度值。如果多边形在该点处的的深度值更小,那么它将被存储到深度缓存中,并且多边形在该点处的颜色值将被写入到渲染表面的当前点。如果多边形在该点处的深度值更大,那么(渲染)列表中的下一个多边形将被测试。这个过程如下图所示。

注意 虽然大多数应用程序不使用这个特性,但是应用程序可以改变Direct3D使用的比较函数,这个比较函数被用来决定哪个值将被放在深度缓存及随后的渲染表面中。要使用这个特性,应该改变D3DRS_ZFUNC渲染状态的值。
市面上所有的三维加速卡都支持z缓存,这使z缓存成为当今最普遍的深度缓存类型。类型但无论如何普遍,z缓存有它的缺点。因为涉及到的数学计算,z缓存中生成的z值在z缓存的范围内(一般来说从0.0到1.0,闭区间)不是平均分布的。特别是远裁剪平面和近裁剪平面的比值严重影响到z值的不平均分布。如果远/近裁剪平面的比值为100,那么深度缓存中90%的范围被用在场景中最开始10%的深度范围。一个一般的室外场景的娱乐或视觉模拟应用程序经常需要远/近裁剪平面的比值在1000到10000之间。如果比值是1000,那么98%的范围被用在场景中最开始2%的深度范围,更高的比值会导致分布情况变得更糟。这会导致远处的物体的隐藏面遗留物,尤其是使用16位深度缓存时,而这也是被广泛支持的位深度。
另一方面,基于w的深度缓存,相比z缓存更为平均地分布在远近裁剪平面之间。最大的好处在于远/近裁剪平面的比值不再是问题。这使应用程序可以支持很大的最大范围,同时在视点附近保持相当的精确度。基于w的深度缓存并非完美,有时会出现近处物体的隐藏面遗留物。w缓存方法的另一个缺点与硬件支持相关:w缓存没有z缓存那样广泛的硬件支持。
Z缓存在渲染过程中需要一些开销。使用z缓存时,可以使用多种方法进行优化。更多细节,请参阅Z缓存的性能。
注意 对深度值的解释取决于三维渲染器。
本节提供有关使用深度缓存进行隐藏线和隐藏面消除的信息。
与任何特性一样,应用程序使用的驱动程序可能不支持所有类型的深度缓存。应该总是检查驱动程序的能力。虽然大多数驱动程序支持基于z的深度缓存,但并不都支持基于w的深度缓存。如果应用程序试图启用不支持的类型,那么驱动程序不会失败,它们会退而使用另一种缓存类型,或者有时完全禁用深度缓存,这将导致渲染出来的场景有极多的深度排序遗留物。
应用程序可以在创建Direct3D设备前,通过Microsoft® Direct3D®查询应用程序将要使用的显示设备,以检查对深度缓存的常规支持。如果Direct3D对象报告设备支持深度缓存,那么从该Direct3D对象创建的硬件设备支持z缓存。
要查询对深度缓存的支持,可以使用IDirect3D9::CheckDeviceFormat方法,如以下示例代码所示。
// 以下示例代码假设pCaps是一个指向D3DCAPS9结构的有效指针。
if( FAILED( m_pD3D->CheckDeviceFormat(
pCaps->AdapterOrdinal,
pCaps->DeviceType,
AdapterFormat,
D3DUSAGE_DEPTHSTENCIL,
D3DRTYPE_SURFACE,
D3DFMT_D16 ) ) )
return E_FAIL;
IDirect3D9::CheckDeviceFormat允许应用程序根据设备的能力选择要创建的设备。在这种情况下,不支持16位深度缓存的设备被抛弃。
另外,可以使用IDirect3D9::CheckDepthStencilMatch方法检测某一被支持的深度/模板缓存的格式与当前显示模式中的渲染目标是否兼容,特别是深度缓存的格式必须与渲染目标的格式相同时。
以下示例代码显示了使用IDirect3D9::CheckDepthStencilMatch方法检测深度缓存/模板缓存与渲染目标之间的兼容性。
// 抛弃不能创建渲染目标为RTFormat,同时后缓存为RTFormat,
// 深度缓存/模板缓存至少为8位的设备。
if( FAILED( m_pD3D->CheckDepthStencilMatch(
pCaps->AdapterOrdinal,
pCaps->DeviceType,
AdapterFormat,
RTFormat,
D3DFMT_D24S8 ) ) )
return E_FAIL;
当知道驱动程序支持深度缓存时,可以检验对w缓存的支持。虽然所有软件光栅化器都支持深度缓存,但是只有参照光栅化器支持w缓存,而参考光栅化器不适合用于现实应用程序。无论应用程序使用何种类型的设备,都应该在试图启用基于w的深度缓存之前检验对w缓存的支持。
深度缓存是设备的一项属性。要创建一个由Microsoft® Direct3D®管理的深度缓存,应该如以下示例代码所示设置D3DPRESENT_PARAMETERS结构的相应成员。
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory( &d3dpp, sizeof(d3dpp) );
d3dpp.Windowed
= TRUE;
d3dpp.SwapEffect
= D3DSWAPEFFECT_COPY;
d3dpp.EnableAutoDepthStencil = TRUE;
d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
通过把EnableAutoDepthStencil成员设置为TRUE,应用程序指示Direct3D为它管理深度缓存。注意AutoDepthStencilFormat必须被设置为一个有效的深度缓存格式。如果设备支持,那么可以用D3DFMT_D16标志指定一个16位深度缓存。
以下对IDirect3D9::CreateDevice方法的调用创建一个设备,该设备随后创建一个深度缓存。
if( FAILED( g_pD3D->CreateDevice(
D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING,
&d3dpp, &d3dDevice ) ) )
return E_FAIL;
深度缓存被自动设为设备的渲染目标。当设备被重置时,深度缓存被自动销毁然后用新的大小重建。
要创建一个新的深度缓存表面,应该使用IDirect3DDevice9::CreateDepthStencilSurface方法。
要给设备设置一个新的深度缓存表面,应该使用IDirect3DDevice9::SetRenderTarget方法。
要在应用程序中使用深度缓存,需要启用深度缓存。更多细节,请参阅启用深度缓存。
在如创建深度缓存中所描述的那样创建完深度缓存之后,应该通过调用IDirect3DDevice9::SetRenderState方法启用深度缓存。要启用深度缓存,应该设置D3DRS_ZENALBE渲染状态。使用D3DZBUFFERTYPE枚举类型的D3DZB_TRUE成员(或TRUE)启用z缓存,D3DZB_USEW启用w缓存,或D3DZB_FALSE(或FALSE)禁用深度缓存。
注意 要使用w缓存,即使不使用Microsoft® Direct3D®变换流水线,应用程序也必须设置合适的投影矩阵。关于如何提供合适的投影矩阵的信息,请参阅W友好投影矩阵。什么是投影变换?中描述的投影矩阵是合适的。
以下示例代码显示了如何使用IDirect3DDevice9::GetDepthStencilSurface方法取得设备拥有的深度缓存。
LPDIRECT3DSURFACE9 pZBuffer;
m_d3dDevice->GetDepthStencilSurface(
&pZBuffer );
许多C++程序在渲染新的一帧前清除深度缓存。可以通过调用IDirect3DDevice9::Clear方法,将Flags参数指定为D3DCLEAR_ZBUFFER让Microsoft®
Direct3D®清除深度缓存。IDirect3DDevice9::Clear方法允许给Z参数指定任意的深度值。
默认情况下,允许Microsoft® Direct3D®系统写入深度缓存。虽然大多数应用程序允许写入深度缓存,但是也可以通过不允许Direct3D系统写入深度缓存以实现某种特殊效果。
C++应用程序可以通过调用IDirect3DDevice9::SetRenderState方法,将Stage参数设置为D3DRS_ZWRITEENABLE,Value参数设置为0,以禁止写入深度缓存。
默认情况下,在对一个渲染目标进行深度测试时,如果一个点的深度值(z或w)小于深度缓存中的值,那么Microsoft® Direct3D®系统会相应地更新渲染目标表面。C++应用程序可以通过调用IDirect3DDevice9::SetRenderState方法,将State参数设为D3DRS_ZFUNC,以改变系统比较深度值的方式。Value参数应该被设为D3DCMPFUNC枚举类型值。
可以通过给多边形添加z偏移,使三维场景中共面的多边形看起来不共面。这是一种通常用来保证场景中的影子正确显示的技术。例如,墙上的影子很可能和墙有相同的深度值。如果先渲染墙后渲染影子,那么影子可能会看不见,或者可能看见深度遗留物。如果希望得到相反的效果,可以交换渲染共面物体的顺序,但是仍有可能有深度遗留物。
当渲染一组共面的多边形时,C++应用程序可以通过给系统使用的z值添加一个偏移来保证共面的多边形正确渲染。要给一组多边形添加一个偏移,应该在渲染它们之前调用IDirect3DDevice9::SetRenderState方法,将State参数设为D3DRS_DEPTHBIAS,将Value参数设为从0到16闭区间中的值。在与其它共面的多边形一起显示时,更高的z偏移值增加了正在渲染的多边形可见的可能性。
给三维场景添加雾可以增强真实感,创造环境氛围,并减少有时由于远处几何体进入视线时造成的残留物。Microsoft® Direct3D® 支持两种类型的雾——像素雾和顶点雾——每种都有各自的特性和编程接口。
本质上讲,雾是通过根据场景中物体的深度或与视点间的距离,将场景中物体的颜色与选定的雾的颜色进行混合实现的。如果物体越来越远,它们的本色和雾的颜色混合的越多,造成物体愈加被场景中悬浮的微粒模糊的假象。下图显示了一个没有使用雾渲染的场景,和一个类似的使用雾渲染的场景。

在这幅图中,左边的场景有明显的地平线,在此之后就没有东西可见了,即便在现实世界中会是可见的。右边的场景通过使用与背景色相同颜色的雾使地平线变得模糊,使多边形淡出到远处。通过将离散的雾效果和有创意的场景设计相结合,应用程序可以添加气氛或使场景中物体的颜色变得柔和。
Direct3D提供两种方法将雾添加到场景中,顶点雾和像素雾,由雾效果如何作用而命名。细节请参阅像素雾和顶点雾。简言之,像素雾——也被称为查找表雾——在设备驱动程序中实现,而顶点雾则在Direct3D光照引擎中实现。
注意 不论应用程序使用何种类型的雾——像素雾或顶点雾,为了确保雾效果被正确应用,应用程序必须提供相容的透视矩阵。这个限制甚至对不使用Direct3D变换和光照引擎的应用程序同样适用。有关如何提供相应矩阵的更多细节,请参阅W友好投影矩阵。
以下主题介绍了雾并介绍了如何在Direct3D应用程序中使用各种雾特性的信息。
雾混合由渲染状态控制,不是可编程像素流水线的一部分。