下面我们来讨论
Direct3D立即模式中的材质及其用法: 什么是材质7.1 什么是材质
材质用来描述多边形如何反射光线或看起来会发出光线。本质上来说,材质属性就是在渲染时通知Direct3D象下面这样来处理多边形:
Direct3D立即模式使用D3DMATERIAL结构来描述材质属性。
材质属性详细描述了材质的漫反射(diffuse reflection)、环境反射(ambient reflection)、光线辐射(light emission)和镜面高光(specular highlighting)等特性。Direct3D使用D3DMATERIAL结构来携带这些属性信息,同时还包含了仅由ramp仿真(相关的纹理句柄和ramp调色板大小)所使用的一些信息。材质属性会对颜色产生影响,Direct3D用这些颜色来对使用材质的多边形进行光栅处理。除了镜面属性之外,每种属性都用RGBA形式来进行描述,也就是表示一个给定类型的光线中有多少红、绿、蓝的颜色成分被反射了,也包括一个alpha混合因子。材质的镜面属性由两部分来进行描述:颜色和能量(power)。
D3DMATERIAL结构的dcvDiffuse和dcvAmbient成员描述了一个材质如何反射环境光和漫射光。因为大多数的场景包含的漫射光要比环境光多,所以在决定颜色时,漫反射的作用更大一些。另外,因为漫射光有方向性,所以漫射光的入射角将影响反射的亮度。当与顶点法向量平行的光线照射到一个顶点时,漫反射达到最大值。随着角度的增大,漫反射的效果逐渐减小。反射的光线的量等于入射光线与顶点法向量之间夹角的余弦值。
环境反射是没有方向的。它对渲染对象的表面颜色影响较小,但是它可以影响所有的颜色,特别是在漫射光较微弱或没有时效果明显。通过调用
IDirect3DDevice3::SetLightState方法,并使用D3DLIGHTSTATE_AMBIENT标志,一个场景的环境光设置可以影响环境反射。漫反射和环境反射相互作用,一同来决定对象的感官颜色,并且它们通常有相同的值。例如,要渲染一个蓝色的水晶物体,可以创建一个材质,只反射漫射光和环境光的蓝色成分。当我们在空间中放置一个白色光源时,这个水晶体看起来就会是蓝色的。但是,如果空间中只有红光,那么水晶体就会是黑色的,这是因为我们所设置的材质不会反射红光。
7.2.2 光线辐射
我们也可以使用材质来使一个对象看起来自己会发光。D3DMATERIAL结构的dcvEmissive成员用来描述辐射出的光线的颜色和透明度。光线辐射可以影响一个对象的颜色,也可以使一个漆黑的材质变得明亮。
使用材质的辐射属性,可以在不增加光源的情况下,使一个对象看起来好像能够发出光线。如果我们对上例中的水晶体使用辐射属性,那么这个水晶看起来就会象是被点着了,但是实际上并没有光线会传播到其它对象。记住,材质的辐射属性并不真正的发出能被场景中的其它对象反射的光线。要想获得能被其他对象反射的光线,必须在场景添加额外的光源。
7.2.3 镜面反射
镜面反射会在对象表面产生高光,使它看起来闪闪发光。D3DMATERIAL结构包含了两个成员来描述镜面高光的颜色和整个材质的光亮度。将dcvSpecular成员设置为需要的RGBA值可以建立镜面高光的颜色——最常用的颜色是白色或浅灰色。dvPower成员的值用来控制镜面效果的尖锐程度。
镜面高光也可以产生动态效果。我们还以蓝水晶为例:dvPower的值越大,所产生的镜面高光就越明亮,从而使水晶看起来象是在闪光。较小的值会使产生效果的范围扩大,也会使水晶看起来好像覆盖了一层霜。要是对象的表面看起来一点也不光滑,可以将dvPower成员设置为0,并将dcvSpecular的颜色设为黑色。下图中展示了两个相同的模型,左边的使用的镜面反射量为10;右面的则没有镜面反射。
7.2.4 关联的纹理
在使用ramp仿真时,将纹理句柄设置为D3DMATERIAL结构的hTexture成员,可以将纹理分配给材质。如果不使用ramp仿真,或者没有纹理分配给材质时,可以将这个成员设置为NULL。
调用IDirect3DTexture2::GetHandle方法可以得到纹理句柄。详细内容见“纹理”。
当
Direct3D使用ramp仿真的明暗处理模式进行明暗处理时,dwRampSize成员提供了如何绘制材质的有关信息。(与RGB仿真或硬件加速相对应。)特别是这个成员是一个整型值,当Direct3D渲染经过明暗处理的多边形时,使用这个值可以知道应该创建多少调色板入口。对于大多数的材质来说,这个值一般设置为16。Direct3D自动决定所使用的明暗处理。我们推荐你对所有的材质使用相同的
ramp大小。在增加一个新的材质时,如果Direct3D超出了调色板入口的范围,它会在调色板中选择一个最接近的材质来代替它。为了相互匹配,材质应该有相同的ramp大小。这样就能得到更好的颜色匹配,从而得到更好的结果。注:视口中单独用作背景的材质的dwRampSize值应该为1。这是因为背景不需要进行明暗处理,不用考虑程序中设定的明暗处理模式。对背景进行额外的明暗处理不会得到任何的好处,只会浪费内存。
7.3 使用材质
下面我们来介绍如何使用材质:
7.3.1 准备使用材质
下面是必须的步骤:
创建一个材质对象时,
通过设置材质属性,可以定义材质如何反射光线,在使用
ramp仿真时如何使用材质的颜色。获得材质对象的句柄可以在材质与
Direct3D设备间建立相应的联系。7.3.2 创建一个材质
下图展示了创建材质的一般步骤:
调用
IDirect3D3::CreateMaterial方法来创建材质。它的第一个参数是一个变量的地址,如果调用成功的话,这个变量包含了一个正确的IDirect3DMaterial3接口指针。第二个参数没有用处,应被设为NULL。// // The g_lpD3D variable is a global variable that // contains a pointer to an IDirect3D3 interface. // LPDIRECT3DMATERIAL3 lpMat3; HRESULT hr; hr = g_lpD3D->CreateMaterial(&lpMat3, NULL); if(SUCCEEDED(hr)) { // Set the material properties. } |
7.3.3 设置材质属性
创建了材质对象之后,要设置它的属性。首先要准备好D3DMATERIAL结构的值,然后调用IDirect3DMaterial3::SetMaterial方法。
下面的例子中,在D3DMATERIAL结构中设置了一个紫色的材质,有明亮的白色镜面高光,和16色的ramp(Direct3D中只被用于ramp仿真):
D3DMATERIAL mat; // Initialize the structure for use. // Set the RGBA for ambient reflection. // Set the color and sharpness of specular
highlights. // Use a 16 entry color ramp, in case |
准备好
D3DMATERIAL结构之后,调用IDirect3DMaterial3::SetMaterial方法来设置材质属性。这个方法的唯一的参数就是D3DMATERIAL结构的指针。也可以使用新的信息来调用SetMaterial方法,从而对材质属性进行修正。成功设置了一个材质属性之后,就可以得到它的材质句柄,在选择材质时,要用到材质句柄。
7.3.4 获得材质句柄设置完一个材质对象的属性之后,可以调用IDirect3DMaterial3::GetHandle方法来得到它的材质句柄。Direct3D立即模式使用D3DMATERIALHANDLE数据类型来声明材质句柄变量。
得到材质句柄,就可以在材质和具体的Direct3D设备之间建立相应的联系。材质句柄代表了这一联系——这个句柄只能由响应的设备才能使用。要想另一个设备使用这个材质,必须要得到与另一个设备相对应的句柄。
GetHandle方法由两个参数:一个是IDirect3DDevice3接口的指针,另一个是包含材质句柄的变量的地址。
创建完一个材质对象之后,设置它的属性并得到材质句柄,然后就要准备渲染了。调用设备的IDirect3DDevice3::SetLightState方法,并使用适当的标志,将材质选择到一个设备中。SetLightState方法是由多种任务的方法,因此在调用它来选择渲染材质时,要将第一个参数设置为D3DLIGHTSTATE_MATERIAL,第二个参数设置为想要选择的材质的句柄。代码如下:
HRESULT hr; // Set the
current material. |
注:一个Direct3D设备一次只能渲染一个材质。设置了当前材质之后,Direct3D将使用这个材质来对所有的多边形进行渲染,直到选择另外一个材质为止。我们可以任意的改变当前使用的材质。
7.3.6 获得材质属性
调用IDirect3DMaterial3::GetMaterial方法可以得到一个材质对象的属性。和使用IDirect3DMaterial3::SetMaterial方法时不同,GetMaterial不需要很多的准备。在调用之前,将一个D3DMATERIAL结构的成员初始化为0,并将dwSize成员设置为结构的大小(以字节形式)。GetMaterial方法接收一个已经初始化过的D3DMATERIAL结构的地址,并将描述当前材质属性的信息填入提供的结构中。
. Direct3D光线的数学运算 Direct3D通过对真实光线的估计来模拟光照效果。Direct3D的光线模型跟踪光线的颜色、方向和光线传播的距离、观察者的位置以及材质的特性,通过这些,来计算每一个顶点的两个颜色成分。
注:所有的运算都在模型空间中进行,通过变换灯光的位置和方向,还包括摄像机位置的变换,还使用了世界矩阵的逆阵,然后,还要进行反变换。Direct3D用这些颜色成分来计算要绘制的颜色。这样一来,如果世界矩阵或视矩阵有不同的缩放比例,那得到的光照效果就会发生错误。
这一部分中,我们会看到有关
Direct3D用来产生漫射和镜面成分所用到的一些公式及技术。通过这些了解,可以更好的掌握光线模型。Direct3D的光线模型时非常精确、有效的,并且容易使用。但是,如果Direct3D所使用的公式并不符合你的需要,那么你可以执行你自己的光线模型。8.1 光线的衰减
Direct3D用下面的公式来对距离进行归一化,即将从光源到顶点的距离规格化为从0.0到1.0:
Dx=(R-D)/R
公式中,
Dn是归一化距离,R是光线的范围,D是从光源到顶点的距离。(当D比R大时,系统认为光线不能照到这个顶点。)光源处的归一化距离是1.0,光线最大范围处的归一化距离为0.0。距离归一化之后,
Direct3D用下面的公式来计算点光源和聚光灯的光线衰减(方向光和平行点光不考虑光线的衰减问题):A=dvAttenuation0+Dx×Attenuation1+Dx2×Attenuation2
公式中,
A时计算得到的总的衰减量,Dn是从光源到顶点的归一化的距离。dvAttenuation0、dvAttenuation1和dvAttenuation2分别代表光线的常量因子、线性衰减因子和平方衰减因子,它们由一个灯光对象的D3DLIGHT2结构的成员进行声明。(与它们相应的成员分别是dvAttenuation0、dvAttenuation1和dvAttenuation2。一般情况下,它们的值在0.0到1.0之间。)这三个衰减因子在公式中作为系数来使用,通过对它们的调整,可以得到范围更广的衰减曲线。大部分程序都将线性衰减因子设为
1.0,将其它两个因子设为0.0。下图中展示了三种最常见到的衰减曲线:注意:只使用平方衰减时,在亮区和暗区之间可能会有比较明显的跳跃。上图中的每一种曲线都只使用了一个衰减因子,我们也可以混合使用它们以获得更加复杂的效果。
使用
Direct3D中的衰减公式来计算光线的衰减时,一般情况下得到的值在光源处均为1.0,在 光线的最大范围处为0.0。(通过计算得到的结果不需要归一化到0.0到1.0之间,超出这个范围的衰减值仍然是正确的,但是结果可能不会太好。)衰减值与光线的红、绿、蓝三种成分相乘,对光线的亮度进行缩放。计算了光线的衰减之后,Direct3D还要考虑聚光灯的效果,光线从表面反射的角度,以及顶点所用的材质的反射属性。8.2 反射模型
调整光线的衰减之后,Direct3D还要计算光线的反射问题。
系统会考虑两种反射类型,漫反射和镜面反射,并使用不同的公式来计算反射后的光线。计算出反射量之后,Direct3D将这些新得到的值应用到被照亮的定点的材质漫反射和镜面反射属性中。最终得到的颜色值再用于光栅处理,以产生Gouraud明暗处理效果和镜面高光。
下面我们分两部分来进行讨论:
Direct3D用下面的公式计算漫反射因子:
Rd = -D·N
公式中,
Rd是反射因子,D是光线照射到顶点的方向向量,N是顶点法向量。向量D和N都是归一化向量。光线的方向向量通过给它乘以-1使其翻转,这样就在方向向量和顶点法向量之间建立了恰当的联系。这个公式得到的值的范围从-1.0到1.0,然后被限制在0.0到1.0之间,再被用来对顶点反射的光线的亮度进行缩放。使用了漫反射公式之后,光线亮度就经过了一定的缩放。经过缩放的光线被应用于顶点材质的漫反射属性上,同时环境反射也要予以考虑,这样才能得到顶点的漫射成分。下面的公式中综合考虑了环境反射和漫反射因素,并得到顶点的漫射成分:
Dv=IaMa+Me+ARdCd
公式中,
Dv是计算得到的顶点的漫射成分,Ia是场景中的环境光等级,Ma是材质的环境光反射属性,变量Me是材质的辐射属性。变量A是顶点的衰减光线,Rd是反射因子。变量Cd可以是两种可能的颜色中的一个,它的选择依赖于系统的状态和顶点的格式。如果D3DLIGHTSTATE_COLORVERTEX灯光状态有效,则系统在Cd中使用漫射顶点颜色。.否则,系统使用漫射材质的颜色。如果使用漫射顶点颜色,则输出的alpha就等于顶点的漫射alpha值。否则,输出的alpha就等于漫射材质的alpha成分,并且范围为[0, 255]。使用了这个公式之后,
Dv就是被照亮的顶点的漫射颜色成分。详细内容见“镜面反射模型”和“聚光灯发散模型”。
8.2.2 镜面反射模型模拟镜面反射系统不仅需要知道光线传播的方向,还需要知道观察者眼睛的朝向。Direct3D使用观察和世界矩阵的逆来得到这些信息。系统使用简化的Phong镜面反射模型近似镜面反射强度,它用到一个“中间向量(halfway vector)”,这个中间向量位于指向光源的向量和指向眼睛的向量这两个向量之间。
根据简化的Phong模型,Direct3D用指向眼睛的向量减去指向光源的向量,这样就得到了中间向量。得到中间向量之后,系统用下面的公式来计算镜面反射:
Rs=(N·H)p
公式中,
Rs是镜面反射,N是顶点法向量,H表示中间向量,p是顶点使用的材质的镜面反射能量(specular reflection power)(它由材质的D3DMATERIAL的dvPower成员声明)。N和H向量都是归一化了的。和漫反射公式一样,镜面反射得到的值的范围从
-1.0到1.0,经过限制之后范围在0.0到1.0之间,然后就可以用来对顶点的反射光线进行缩放了。剩下的光线应用到顶点材质的镜面反射属性上,得到这个顶点的镜面成分,这也同漫反射相同,如下式所示: Sv=A RsCs公式中,
Sv是正在计算的镜面颜色,A是顶点的衰减光线,变量Rs是预先计算的镜面反射。变量Cs可以是两种可能的颜色中的一种,用哪一种要看系统的状态和顶点的格式。如果D3DLIGHTSTATE_COLORVERTEX灯光状态有效(并且顶点表现出镜面颜色),系统在Cs中使用镜面顶点颜色。否则,使用漫射材质颜色。
聚光灯发出的圆锥形光线由两部分组成:一个明亮的内圆锥和一个外圆锥。内圆锥中的光线是最亮的,并且不会影响到外圆锥,在两部分区域之间,光线亮度是逐渐衰减的。我们将这种衰减称为发散(
falloff)。一个顶点能够接受到多少光线决定于顶点在内圆锥或外圆锥中的位置。
Direct3D计算聚光灯方向向量(L)与从顶点到灯光的向量(D)的点乘。得到的值等于两向量间夹角的余弦值,然后用它作为顶点位置的标志来和光线圆锥的圆锥角进行比较,以决定顶点是在哪个圆锥中。下图中描绘出了这两个向量间的关系:
接下来,系统将这个值和聚光灯的内、外圆锥角的余弦值进行比较。在灯光的D3DLIGHT2结构中,dvTheta和dvPhi成员用来表示内、外圆锥的圆锥角。由于光线的衰减是从光照的中心线开始的,也就是随着离中心的距离的增加,光线逐渐衰减,因此在计算圆锥角的余弦值之前,Direct3D先将圆锥角减半。
如果向量L和D点乘的值小于外圆锥角的余弦值,那么顶点就在外圆锥以外,没有光线能照到它。如果点乘的结果大于内圆锥角的余弦值,那么顶点就在内圆锥中,并且得到的光线亮度最大(这时,仍然要考虑光线随距离的衰减)。如果顶点位于这两个区域之间的某个地方,那么Direct3D 就要使用下面的公式来计算光线的发散了:
lf=((cosa-cosφ)/(cosθ-cosφ))p
公式中,
If是顶点处的光线亮度(发散之后的),α是向量L和D之间的夹角,φ是外圆锥角的一半,θ是内圆锥角的一半,p是聚光灯的发散属性(D3DLIGHT2结构的dvFalloff成员)。这个公式得到一个介于0.0到1.0之间的值,用它来对顶点的光线亮度进行缩放从而产生发散效果。顶点光线随距离的衰减因子也要予以考虑。p值与D3DLIGHT2结构的dvFalloff成员相对应,并控制着发散曲线的形状。右图展示了不同的dvFalloff值对发散曲线的影响:
不同的
dvFalloff值产生的效果对于实际的光线来说只有细微的差别,并且不等于1.0的dvFalloff值可能会对性能有所影响,因此,大部分程序都将它设置为1.0。