4. 颜色
本文译自《Introduction to 3D Game Programming with DirectX 9.0》第四章“Color”,敬请斧正。
本章讨论场景中对象的颜色渲染。通过本章的学习,可以达到如下目标:
l 在Direct3D中如何描述颜色
l 理解颜色是如何被渲染的
4.1. 颜色的表现
在Direct3D中颜色使用RGB表示,也就是说,需要指定红、绿、蓝三原色的值,这三个分量的混合颜色就是最终的颜色。
我们使用两种结构表示RGB数据。第一种是D3DCOLOR类型,其实就是DWORD类型。该类型被分成4个8-bit的部分,分别用于存储颜色的分量。
每种颜色分量都是使用1个字节表示,所以每种颜色分量的亮度范围为0~~255。关于上图中的Alpha分量,目前可以不用理会,在讲述alpha混合时再详细讨论。
正确的填写D3DCOLOR结构中的各个分量需要进行位操作。为此,D3D提供了一个宏D3DCOLOR_ARGB,每个分量需要在0~~255之内。
D3DCOLOR brightRed = D3DCOLOR_ARGB(255, 255, 0, 0);
D3DCOLOR someColor = D3DCOLOR_ARGB(255, 144, 87, 201);
另外,还有一个宏D3DCOLOR_XRGB,它与上面的宏相似,但是不需要提供alpha参数值,而是将其设定为0xFF。
#define D3DCOLOR_XRGB(r,g,b) D3DCOLOR_ARGB(0xff,r,g,b)
在Direct3D中表示颜色的另一种结构是D3DCOLORVALUE。该结构使用浮点数表示颜色的各个分量的值,范围为0.0~~1.0。
typedef struct _D3DCOLORVALUE {
float r; // the red component, range 0.0-1.0
float g; // the green component, range 0.0-1.0
float b; // the blue component, range 0.0-1.0
float a; // the alpha component, range 0.0-1.0
} D3DCOLORVALUE;
有时,还使用D3DXCOLOR结构,它包含与D3DCOLORVALUE相同的数据成员,还有很多有用的构造函数和重载的操作符,这使很多颜色操作十分方便。因这两种数据结构具有相同的数据成员,故很容易在二者之间转换。D3DXCOLOR的定义如下:
typedef struct D3DXCOLOR
{
#ifdef __cplusplus
public:
D3DXCOLOR() {}
D3DXCOLOR( DWORD argb );
D3DXCOLOR( CONST FLOAT * );
D3DXCOLOR( CONST D3DXFLOAT16 * );
D3DXCOLOR( CONST D3DCOLORVALUE& );
D3DXCOLOR( FLOAT r, FLOAT g, FLOAT b, FLOAT a );
// casting
operator DWORD () const;
operator FLOAT* ();
operator CONST FLOAT* () const;
operator D3DCOLORVALUE* ();
operator CONST D3DCOLORVALUE* () const;
operator D3DCOLORVALUE& ();
operator CONST D3DCOLORVALUE& () const;
// assignment operators
D3DXCOLOR& operator += ( CONST D3DXCOLOR& );
D3DXCOLOR& operator -= ( CONST D3DXCOLOR& );
D3DXCOLOR& operator *= ( FLOAT );
D3DXCOLOR& operator /= ( FLOAT );
// unary operators
D3DXCOLOR operator + () const;
D3DXCOLOR operator - () const;
// binary operators
D3DXCOLOR operator + ( CONST D3DXCOLOR& ) const;
D3DXCOLOR operator - ( CONST D3DXCOLOR& ) const;
D3DXCOLOR operator * ( FLOAT ) const;
D3DXCOLOR operator / ( FLOAT ) const;
friend D3DXCOLOR operator * (FLOAT, CONST D3DXCOLOR& );
BOOL operator == ( CONST D3DXCOLOR& ) const;
BOOL operator != ( CONST D3DXCOLOR& ) const;
#endif //__cplusplus
FLOAT r, g, b, a;
} D3DXCOLOR, *LPD3DXCOLOR;
显然,结构D3DXCOLOR和D3DCOLORVALUE包含四个浮点数成员,所以可以将颜色看作一个4元向量(r,g,b,a)。颜色向量可以像普通的向量一样进行加、减、乘运算。另外,点积和叉积对颜色向量是没有含义的,而分量相乘却是有意义的。结构D3DXCOLOR定义的颜色与颜色相乘就是颜色分量的相乘,使用符号X表示:
(c1, c2, c3, c4) X (k1, k2, k3, k4) = (c1k1, c2k2, c3k3, c4k4)
4.2. 顶点颜色
几何体的颜色由组成它的顶点的颜色决定。因此,可以我们必须向顶点数据结构中添加颜色成员。在这里,没有采用D3DCOLORVALUE结构描述颜色,因为Direct3D希望使用32-bit的整型数表示颜色。另外,当使用顶点着色器(Vertex Shader)时,将会使用四元向量描述顶点颜色,这是一个128-bit的颜色。到讨论顶点着色器时会详细说明。
struct ColorVertex
{
float _x, _y, _z;
D3DCOLOR _color;
static const DWORD FVF;
}
const DWORD ColorVertex::FVF = D3DFVF_XYZ | D3DFVF_DIFFUSE;
4.3. 着色模式
着色发生在光栅化和通过顶点颜色计算像素颜色时。目前常用的有两种着色模式:平坦模式和高洛德模式。
在平坦模式下,像素的颜色一律取组成几何体的第一个顶点的颜色。所以,由下面三个顶点构成的三角形是红色的,因为第一个顶点的颜色是红色,令外两个顶点的颜色会被忽略。
ColorVertex t[3];
t[0]._color = D3DCOLOR_XRGB(255, 0, 0);
t[1]._color = D3DCOLOR_XRGB(0, 255, 0);
t[2]._color = D3DCOLOR_XRGB(0, 0, 255);
平坦模式往往是几何体看起来菱角分明,颜色间缺少平滑的过渡。另外一种较自然的着色模式就是高洛德模式,也叫平滑模式,在这种模式下,顶点的颜色以线形插值方式渲染表面。
在Direct3D中可以使用如下方法设置着色模式:
// set flat shading
Device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_FLAT);
// set Gouraud shading
Device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
4.4. 应用举例:彩色三角形
这个例子用以说明怎样使用平坦模式和高洛德模式渲染彩色三角形。首先,声明如下全局变量:
D3DXMATRIX World;
IDirect3DVertexBuffer9* Triangle = 0;
这里,World是三角形的世界坐标系转换矩阵,Triangle是存储三角形顶点数据的顶点缓冲区指针。这里只存储一个三角形的顶点信息,我们将使用World矩阵在坐标系中不同的位置多次渲染该三角形。
函数setup创建顶点缓冲区,并且填充三角形的顶点数据,最后禁用光照。例子中使用上面所声明的顶点结构ColorVertex,代码如下:
bool Setup()
{
// create vertex buffer
Device->CreateVertexBuffer(
3 * sizeof(ColorVertex),
D3DUSAGE_WRITEONLY,
ColorVertex::FVF,
D3DPOOL_MANAGED,
&Triangle,
0);
// fill the buffers with the triangle data
ColorVertex* v;
Triangle->Lock(0, 0, (void**)&v, 0);
v[0] = ColorVertex(-1.0f, 0.0f, 2.0f, D3DCOLOR_XRGB(255, 0, 0));
v[1] = ColorVertex( 0.0f, 1.0f, 2.0f, D3DCOLOR_XRGB( 0, 255, 0));
v[2] = ColorVertex( 1.0f, 0.0f, 2.0f, D3DCOLOR_XRGB( 0, 0, 255));
Triangle->Unlock();
// set projection matrix
D3DXMATRIX proj;
D3DXMatrixPerspectiveFovLH(
&proj,
D3DX_PI * 0.5f, // 90 - degree
(float)Width / (float)Height,
1.0f,
1000.0f);
Device->SetTransform(D3DTS_PROJECTION, &proj);
// set the render states
Device->SetRenderState(D3DRS_LIGHTING, false);
return true;
}
函数Display使用不同的着色模式在不同的位置渲染该三角形两次,其位置由World矩阵控制:
bool Display(float timeDelta)
{
if( Device )
{
Device->Clear(0, 0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 0xffffffff, 1.0f, 0);
Device->BeginScene();
Device->SetFVF(ColorVertex::FVF);
Device->SetStreamSource(0, Triangle, 0, sizeof(ColorVertex));
// draw the triangle to the left with flat shading
D3DXMatrixTranslation(&World, -1.25f, 0.0f, 0.0f);
Device->SetTransform(D3DTS_WORLD, &World);
Device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_FLAT);
Device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);
// draw the triangle to the right with gouraud shading
D3DXMatrixTranslation(&World, 1.25f, 0.0f, 0.0f);
Device->SetTransform(D3DTS_WORLD, &World);
Device->SetRenderState(D3DRS_SHADEMODE, D3DSHADE_GOURAUD);
Device->DrawPrimitive(D3DPT_TRIANGLELIST, 0, 1);
Device->EndScene();
Device->Present(0, 0, 0, 0);
}
return true;
}
4.5. 总结
l 颜色使用红、绿、蓝三原色的强度描述。在Direct3D中可以使用D3DCOLOR、D3DCOLORVALUE、D3DXCOLOR结构描述颜色。
l 有时将颜色看成四元向量(r,g,b,a)。颜色向量可以像普通向量一样进行加、减、放大操作。另外,颜色向量间的点积和叉积是没有含义的,但是分量间相乘却很有用。
l 可以为顶点指定颜色,然后Direct3D根据当前的着色模式决定如何将顶点的颜色转换到像素。
l 平坦着色模式取第一个顶点的颜色作为几何体像素的颜色;高洛德模式则通过线形插值法计算像素的颜色。