五.灯光和材质

这一部分讨论Direct3D立即模式场景的照明问题:

 

1. 介绍光线和材质

  Direct3D在渲染的最后阶段对场景进行光栅,如果此时光线有效的话,它会将当前材质的颜色(和相关的纹理映射中的纹理像素texel)与每一个顶点的漫散射和镜面颜色进行混合,并通过混合后的颜色来决定每个像素的颜色,如果声明的话,还要包括场景中灯光对象和环境光等级所产生的光的颜色与亮度。当使用Direct3D光线和材质时,可以让Direct3D来处理光照的细节,但是高级编程人员可以自己来处理光线。

  如何对光线和材质进行操作会使场景有很大的不同。材质定义了一个表面如何反射光线。直射光(direct light)和环境光等级定义了被反射的光线。如果让Direct3D来处理光线,那么就必须使用材质来渲染场景。光线并不是场景所必需的,但是没有光线将使观察场景变得非常困难。最好的情况下也只能看到场景中物体的轮廓而已。

 

2. Direct3D光线模型vs.自然状态

  本质上,当光线从一个光源发出,它在到达观察者的眼睛之前,要经过成千上万的对象的反射。每一次反射,都有一部分光线被表面所吸收,一部分向任意的方向发散开去,剩余的部分射向另一个表面或者观察者的眼睛。这一过程一直持续下去,直到光线被削弱到零或者被观察者接收到。但是,谁也不知道光线到底被反射了多少次。

  很明显,要想完美的模仿光线的自然属性,所需的计算量是很巨大的。因此,出于速度方面的考虑,Direct3D的光线模型只是接近了自然世界中光线的工作方式。Direct3D以红、绿、蓝三种颜色成分的混合来表示光线的颜色。详细内容见“光线与材质的颜色值”。在Direct3D中,当光线从一个表面反射时,它的颜色与表面通过数学运算相互影响,最后产生出表现在屏幕上的颜色。有关的运算法则见“Direct3D光线的数学运算”。

  Direct3D立即模式光线模型产生两种类型的光线:环境光(ambient light)和直射光(direct light)。每一种光都有各自的属性,都与表面材质有不同的相互作用。环境光的方向和光源不确定:它在任何地方都维持一个较低的亮度等级。Direct3D中的环境光从本质上来说没有真正的方向和光源,只有颜色和亮度。实际上,环境光的等级完全不依赖于任何场景中的发光的对象。对与镜面反射,环境光不产生任何作用。

  直射光(direct light)使有场景中的对象产生的;它有颜色和亮度,并按照给定的方向传播。直射光与表面材质相互作用产生了镜面高光区,并且它的方向还是明暗处理算法的一个因素。当直射光被反射时,它对场景中的环境光等级不会产生任何影响。场景中产生直射光的对象都有不同的光照特性。详细内容见“灯光”。

  另外,一个多边形也有影响光线反射的属性。你可以为环境光设置一个单独的反射率特性,为材质的镜面和漫散射设置另外的反射率特性。详细内容见“材质”。

 

3. 光线和材质的颜色值

  Direct3D立即模式用四个成分来描述颜色(红、绿、蓝和alpha)。D3DCOLORVALUE结构中包含了每一个成分。每一个成员都是一个浮点数,范围从0.01.0。尽管光线和材质都使用这个结构来描述颜色,但是它们在结构中的值却有一点差别。

  灯光对象的颜色值表示的是它所发出的一个特殊的光线成分的量。光线不使用alpha成分,所以只需要考虑它的红、绿、蓝三种成分。你可以形象的将这三种成分当作三种颜色的透镜。每个透镜都可能使关闭的(D3DCOLORVALUE结构中相应的成员为0.0值),也可能是最亮的(为1.0值),或者是一个中间的值。来自每个透镜的颜色相互混合形成了最终的光线颜色。R: 1.0G: 1.0B: 1.0的混合产生出白光,R: 0.0G: 0.0B: 0.0混合则不发出任何光。你可以将颜色成分设为负值,这样实际上就将一个灯光从场景中移走了。你也可以将颜色成分设置为大于1.0的值,从而得到一个非常强烈的灯光。

  对于材质,颜色值则表示它将多少给定的光线从表面上反射出来。一个颜色成分为R: 1.0G: 1.0B: 1.0A: 1.0的材质会将所有的光线都反射出来。而一个R: 0.0G: 1.0B: 0.0A: 1.0的材质则将直接照射到表面上的绿色光完全反射。材质可以有多个反射率值,这样就可以产生出不同的效果;详细内容见“材质属性”。

  环境光的颜色值与灯光对象和材质的颜色值有所不同,详细内容见“直射光vs.环境光”。

 

4. 直射光vs.环境光

  直射光和环境光是相互独立的,它们有不同的光照效果,并且处理方法也完全不相同。

  直射光就是:直射(direct)。它有方向和颜色,总是由场景中的对象发出,并且它是明暗处理要考虑的一个因素。不同类型的灯光对象以不同的方式发出直射光,也就产生出不同的效果。可以调用IDirect3D3::CreateLight方法来创建灯光,使用IDirect3DLight::SetLight方法设置属性,调用IDirect3DViewport3::AddLight方法将它们加入场景。

  环境光存在于场景的任何地方。你可以将它看作是一个用来填充整个场景的通用的光线等级,而不要考虑队形和它们的方向。环境光没有位置和方向,只有颜色和亮度。另外,在进行明暗处理时,不用考虑环境光的影响。如果使用DrawPrimitive结构进行渲染,可以通过调用IDirect3DDevice3::SetLightState方法来创建一个环境光等级,要将dwLightStateType参数声明为D3DLIGHTSTATE_AMBIENT,将dwLightState参数声明为所需的RGBA颜色。如果使用执行缓冲,必须在缓冲器中包含D3DOP_STATELIGHT操作码、D3DLIGHTSTATE_AMBIENT标志和相应的D3DSTATE结构中的颜色值。

  环境光的颜色值在DrawPrimitive和执行缓冲方法中有相同的解释。它采用RGBA形式,每种成分都是从0255的整数。(这与立即模式中的大多数颜色值不同。详细内容见“光线和材质的颜色值”。)可以使用RGBA_MAKE宏来产生RGBA整数值。颜色中的成分用来控制颜色的透明度。在ramp仿真中,环境光没有颜色,所以alpha成分被用来表示亮度。使用硬件加速或是RGB仿真时,alpha成分将被忽略掉。

 

5. 起动与禁止光线引擎

  Direct3D通常对包含有法向量的顶点执行光线运算。但是,当我们使用DrawPrimitive渲染时,可以使用D3DDP_DONOTLIGHT标志来禁止包含有法向量的顶点进行光线运算。

  如果程序使用执行缓冲,在调用IDirect3DVertexBuffer::ProcessVertices方法时可以通过包含或省略D3DVOP_LIGHT标志来起动或禁止光线运算。

  如果渲染设备没有分配材质,Direct3D光线引擎也会被禁止。

 

6. 灯光

  灯光用来照亮场景中的物体。下面我们来介绍如何使用灯光:

 

6.1 介绍灯光对象

  Direct3D使用四种类型的灯光:点光源(point light)、聚光灯(spotlight)、方向光(directional light)和平行点光(parallel-point light)。它们可以任意选择。每种光的光照属性和运算量都不相同。下面我们分别来讨论它们:

  不要将灯光对象与环境光等级相互混淆。详细内容见“直射光vs.环境光”,“灯光属性”和“使用灯光”。

6.1.1 点光源

  点光源在场景中有颜色和位置,但是没有单独的方向。它向各个方向发出的光线都是等量的,如下图所示:

pic60.gif (2626 bytes)

  一个灯泡就是一个点光源。点光源受衰减(attenuation)和范围(range)的影响,并且它是一个顶点一个顶点照亮多面体的。在进行光线处理时,Direct3D使用灯光的位置和被照亮的顶点的坐标得到一个代表光线方向的向量,以及光线走过的路程。通过这两个值(还有顶点的法向量),来计算光线照亮表面的效果。

 

6.1.2 聚光源

  聚光源有颜色、位置以及发出光线的方向。它发出的光线由一个明亮的内圆锥和一个大一些的较暗的外圆锥组成,光线在这两个圆锥之间亮度逐渐减小,如下图所示,途中还标出了相关的D3DLIGHT2结构的成员

pic61.gif (18227 bytes)

  聚光灯发出的光线会发生分散现象(falloff),同时也会有衰减,并有一定的范围。这些因素,包括光线到达每个顶点的路程,在计算场景中对象的光照效果时都会予以考虑。对这些因素的考虑,使得聚光灯的运算量在立即模式的所有灯光中是最大的。

 

6.1.3 方向光

  方向光只有颜色和方向,没有位置。它发出平行光线,也就是说,由它发出的光线都按照同一个方向传播。你可以将一个方向光源想象成一个位于无穷远处的光源,就像太阳。方向光不会衰减,也没有范围限制,因此,在计算顶点颜色时只要考虑光源的方向和颜色。这样一来,它对系统的开销就会减少,一次它也是对结算量要求最少的。

 

6.1.4 平行点光

  平行点光只有颜色和位置。它所创建的效果同点光源比较相似,并且在一定的精度要求下,它的系统开销要比点光源小。这样一来,当非常精确的镜面高光效果不是很关键时,使用平行点光源是一个很好的选择。Direct3D用平行点光照亮一个多面体的方式与点光源很相似,并且可以使用一种简化操作来提高性能。这种简化操作就是为照亮多面体中所有顶点的光源建立一个单一的方向,而不再需要象在使用点光源时那样对每个顶点都要重新计算一个方向。(这个单一的方向可以是从光源位置到多面体原点的方向。)因为所有的顶点都拥有同样的方向,因此可以说光线是平行的,这也就是这种灯光名称的由来。同方向光一样,平行点光不会衰减,也没有范围的限制。

 

6.2 灯光的属性

  灯光的属性就是指一个灯光对象的类型和颜色。对于不同的灯光类型,一个灯光可以有衰减和范围属性,或者是聚光灯的效果。但是,不是所有的类型都使用所有的属性。Direct3D立即模式使用D3DLIGHT2结构来携带所有类型的灯光的属性。下面我们分以下几个部分来讨论有关灯光的属性:

 

6.2.1 灯光类型

  灯光的类型属性定义了我们使用何种类型的Direct3D灯光对象。我们使用D3DLIGHT2结构的dltType成员中的D3DLIGHTTYPE枚举类型的一个值来定义灯光类型。共有四种类型的立即模式灯光类型:点光源,聚光灯,方向光和平行点光。

6.2.2 灯光颜色

  D3DLIGHT2结构的dcvColor成员中的颜色属性是一个RGBA颜色。最常用到的颜色是白色 (R:1.0 G:1.0 B:1.0)。你可以创建自己需要的颜色。

  一般情况下,我们将灯光的颜色值设置在0.01.0之间(包括0.01.0),但它不是必须的。例如,你可以将所有的成分都设置成2.0,创建一个“比白光更亮的”灯光。当我们要突出某些内容时,这种设置非常有用。

    注:尽管Direct3D使用RGBA形式,但是alpha只是没有用的。详细内容见“灯光和材质的颜色之”。

 

6.2.3 灯光位置、范围和衰减

  位置、范围和衰减属性用来对灯光在世界空间中进行定位,还定义了灯光随距离变化的发散性质。和其它属性一样,这些属性也在D3DLIGHT2结构中进行定义。

  1. 位置
  2.   在D3DLIGHT2结构中,灯光的位置使用它的dvPosition成员的D3DVECTOR结构来描述。x-y-z-坐标都在世界空间中。方向光是唯一不使用位置属性的灯光类型。

  3. 范围
  4.   一个灯光的范围属性在世界空间中定义了一个距离,在这个距离之外,多面体将不再能接收到灯光发出的光线。dvRange成员包含了一个浮点值,这个值代表了一个灯光的最大范围。大多数的程序都将这个值设置为可能的最大值,也就是使用D3d.h文件中定义的D3DLIGHT_RANGE_MAX。方向光和平行点光不使用范围属性。

  5. 衰减
      衰减属性用来控制光线的亮度随距离的增加而逐渐减小。D3DLIGHT2中有三种描述衰减的成员:dvAttenuation0dvAttenuation1dvAttenuation2。这些成员包含了从0.01.0之间的浮点值,分别来控制灯光的衰减性质——保持常量、线性衰减、二次衰减。大多数程序都将dvAttenuation1成员设置为1.0,其它两个设置为0.0,这样光线的亮度就会随着距离线性的变化,在光源处亮度最大,在灯光的最大范围处为0。也可以通过这三种成员的组合来得到更加复杂的效果。或者将它们设置为超出一般的范围;也可以设置为负值,使光线的亮度随距离的增加而逐渐增长。有关Direct3D计算衰减效果的数学模型的内容见“灯光的过距离衰减”。和范围属性一样,方向光和平行点光不使用衰减属性。

 

6.2.4 灯光的方向

  一个灯光的方向属性决定了光线在世界空间中的传播方向。只有方向光和聚光灯才使用方向,在D3DLIGHT2结构中,使用dvDirection成员的D3DVECTOR结构来描述灯光的方向。方向向量是指从原点开始的一段距离,它不考虑灯光在场景中的位置。因此,一个指向z-轴正方向的聚光灯的方向向量就是<0,0,1>,而不考虑它的位置。简单来说,我们可以使用一个方向向量为<0,-1,0>的方向光来模拟向下发光的太阳的效果。

注意:尽管不需要对方向向量进行归一化,但是它们必须有一定的量级。换句话说,不能使用<0,0,0>的方向向量。

 

6.2.5 聚光灯属性

  D3DLIGHT2结构中有三种成员只能由聚光灯来使用——dvFalloffdvThetadvPhi。它们用来控制一个聚光灯内、外圆锥的大小,以及在它们之间光线的变化。

  dvTheta值是聚光灯内圆锥角的弧度值,dvPhi值是外圆锥角的弧度值。dvFalloff值来控制从内圆锥的外缘到外圆锥的内缘之间的光线亮度的变化。多数程序都将dvFalloff设为1.0,这样两个圆锥之间的部分的光线的亮度就是一个常量,当然,我们也可以将它设置为其它的值。有关Direct3D计算两圆锥间光线衰减效果的数学模型,见“聚光灯衰减模型”。

  下图中展示了上述这些值之间的关系:

pic62.gif (22719 bytes)

6.3 使用灯光

下面我们来介绍如何使用Direct3D立即模式的灯光。

 

6.3.1 准备使用灯光

  下面的步骤是准备使用灯光所必需的。注意:下面的步骤假定程序已经初始化了Direct3D子系统和创建了一个视口。

  1. 创建灯光对象
  2. 创建一个灯光,使Direct3D分配内部数据结构。

  3. 设置灯光属性
  4. 通过设置灯光属性,可以选择使用的灯光类型,以及要求Direct3D如何计算灯光效果。

  5. 将灯光加入视口

  将灯光加入视口的灯光列表。

 

6.3.2 创建灯光

  下图展示了创建一个灯光的一般的方式。

pic63.gif (5001 bytes)

  调用IDirect3D3::CreateLight方法来创建一个灯光对象。第一个参数是一个变量的地址,如果调用成功的话,这个变量将包含一个正确的IDirect3DLight接口指针。第二个参数要在COM aggregation中使用——由于aggregation是不执行的,所以这个参数必须被设置为NULL。下面的例子展示了调用这个方法的代码:


/*
* For the purposes of this example, the g_lpD3D3 variable is the
* address of an IDirect3D3 interface exposed by a Direct3D
* object.
*/

LPDIRECT3DLIGHT g_lpD3DLight;
HRESULT hr;
hr = g_lpD3D3->CreateLight (&g_lpD3DLight, NULL);
if (SUCCEEDED(hr))
{
 // Set the light properties.
}
else
 return hr;

 

6.3.3 设置灯光属性

  创建了一个灯光对象之后,要设置D3DLIGHT2结构,然后调用IDirect3DLight::SetLight方法来设置灯光的属性。SetLight方法将一个已经准备好的D3DLIGHT2结构的地址作为它唯一的一个参数。也可以使用新的信息来调用SetLight方法,从而修正灯光的属性。

  下面的例子中,我们将一灯光设置为白色,并且不随距离进行衰减:


/*
* For the purposes of this example, the g_lpD3DLight variable
* is a valid pointer to an IDirect3D3 interface.
*/

D3DLIGHT2 g_light;
HRESULT hr;

// Initialize the structure.
ZeroMemory(&g_light, sizeof(D3DLIGHT2));
g_light.dwSize = sizeof(D3DLIGHT2); // MUST set the size!

// Set up for a white point light.
g_light.dltType = D3DLIGHT_POINT;
g_light.dcvColor.r = 1.0f;
g_light.dcvColor.g = 1.0f;
g_light.dcvColor.b = 1.0f;

// Position it high in the scene, and behind the viewer.
// (Remember, these coordinates are in world space, so
// the "viewer" could be anywhere in world space, too.
// For the purposes of this example, assume the viewer
// is at the origin of world space.)

g_light.dvPosition.x = 0.0f;
g_light.dvPosition.y = 1000.0f;
g_light.dvPosition.z = -100.0f;

// Don't attenuate.
g_light.dvAttenuation0 = 1.0f;
g_light.dvRange = D3DLIGHT_RANGE_MAX;

// Make the light active light.
g_light.dwFlags = D3DLIGHT_ACTIVE;

// Set the property info for this light.
// We have to cast the LPD3DLIGHT2 to be
// an LPD3DLIGHT in order to compile.
//
// (See the following note for details.)
hr = g_lpD3DLight->SetLight((LPD3DLIGHT)&g_light);
if (SUCCEEDED(hr))
{
 // Add the light to the viewport.
}
else
 return hr;

注意:IDirect3DLight::SetLight方法检查由lpLight成员声明的dwSize成员,已决定使用D3DLIGHT2结构还是D3DLIGHT结构。D3DLIGHT2结构使用了一些新的内容来提供更可靠的灯光性能。由于向后的兼容性,SetLight方法的参数列表没有改变。因此,必须将D3DLIGHT2结构的地址提供给LPD3DLIGHT数据类型,以避免编译错误。

(通过一些列的SetLight调用来修正灯光属性时,并不需要每次都要将灯光加入到视口中。)

 

6.3.4 将灯光加入视口

  调用视口的IDirect3DViewport3::AddLight方法将灯光加入视口中。AddLight方法通知视口将灯光加入带灯光对象列表中。当我们调用AddLight时,要声明IDirect3DLight接口的指针,这个指针在最初调用IDirect3D3::CreateLight来创建灯光时可以得到。


/*
* For this example, the g_lpD3DLight variable is a pointer to
* the IDirect3DLight interface of a light object whose
* illumination properties have been set, and the lpViewport3
* variable is a valid IDirect3DViewport3 interface.
*/

HRESULT hr;
hr = lpViewport3->AddLight (g_lpD3DLight);
if (FAILED (hr))
 return hr;

 

6.3.5 删除灯光

  当我们不再使用一个灯光时,我们可以调用视口的IDirect3DViewport3::DeleteLight方法将这个灯光删除。这个方法接受一个指向要删除的灯光的IDirect3DLight接口的指针。

  DeleteLight方法只将灯光对象从灯光列表中移走,并不施放这个灯光对象。如果想要将它施放,必须调用这个灯光对象的IUnknown::Release方法。

 

6.3.6 恢复灯光属性

  调用一个灯光对象的IDirect3DLight::GetLight方法可以得到这个灯光对象的属性。调用GetLight方法时,应该将D3DLIGHT2结构的地址当作LPD3DLIGHT数据类型来进行传递,而不应该使用D3DLIGHT结构的地址。在传递D3DLIGHT2结构的地址时,应该确保有足够的可用内存来接收属性信息,这些信息由内部数据成员直接拷贝到所提供的结构中。对于DirectX中的所有结构,都要确保在使用之前对结构的dwSize成员(以字节方式)进行初始化。

 

上一页 | 目录 | 下一页