深度探索DxFramework 3-1

请尊重原作者的工作,转载时请务必注明转载自:www.xionggf.com

第3章 步入二维游戏的世界 1

3.1 二维动画的原理

在介绍计算机动画的原理之前,先谈谈大家都非常熟悉的电影的播放原理。播放电影的时候需要一大盘的电影胶片。胶片上面就是一帧一帧连续的静止图像。当播放的时候,把胶片按播放顺序,依次通过播放机的镜头,这样胶片上的图像将会逐帧投影到银幕上。因为人眼存在着视觉暂留的生理现象。当眼睛正在观察的物体突然消失的时候,被观察的物体还能在人眼中存留约1/24秒左右。因此,只要一系列本来就是连续的静止的图像,当以合适的速度逐个通过人眼的时候。便会产生连续的“会动”的视觉效果。

根据人的视觉存留时间可以知道,当电影胶片以每秒24帧的速度播放时,人们看到的就是和现实生活中速度相同的画面。当电影胶片以每秒大于24帧的速度播放时,画面中的各种动作将会变快,这就是俗称的“快镜头”。而如果电影胶片以每秒低于24帧的速度播放时,画面中的各种动作将会变慢,这就是俗称的“慢镜头”。

计算机动画的原理和播放电影的原理是类似的。举一个简单的二维动画例子:假如要做一个人物走动的二维动画,首先要研究人走路时身体各部位,如躯干、四肢移动摆动的位置。然后按相应时间相应的位置,把这些连续动作绘制成一副副的图。在计算机中就可以存储在一些图片文件中。这些“一副副的图”其实就是类似于电影胶片。把图从磁盘文件中载入到内存,然后按顺序将其逐帧在屏幕中绘制出来,就类似于播放电影的过程。当一组动作播放完成后,再从头从动作的第一帧播放,周而复始,就形成了连贯的动画。

三维动画和二维动画播放原理是一致的,也是一帧一帧地在屏幕中绘制。与二维动画类似,三维动画也可以分实时和非实时。我们玩的三维计算机游戏的游戏画面其实就可以认为是一种实时三维动画,这些画面都是在每一帧显示前由计算机实时地计算生成。非实时三维动画则用相关软件预先逐帧计算好并且存储在文件中,播放的时候从文件中逐帧读取数据显示即可。在本章中先讨论二维动画。

3.2 基础数学工具

接着介绍是vector2.h文件中的模板类和函数。VECTOR2定义了一个二维矢量的模板类。

  1#pragma once
  2#include "globals.h"
  3#include "error.h"
  4 
  5//最小误差,意思是两浮点数之差为小于0.0001的时候,
  6//就认为这两浮点数是相等的。
  7const XFLOAT VECTOR2_ZERO_TOLERANCE = 0.0001;
  8 
  9/* MSVC6 cannot use the overloaded
 10operators due to a compiler bug/feature */
 11#if _MSC_VER < 1300
 12#ifndef NO_TEMPLATE_SELECTION
 13#define NO_TEMPLATE_SELECTION
 14#endif
 15#endif
 16 
 17template< class T >
 18class VECTOR2
 19{
 20public:
 21   VECTOR2() {}
 22   VECTOR2(const T& x, const T& y) : x(x), y(y) {}
 23  
 24   //复制Direct3D的D3DXVECTOR2类型数据的内容
 25   VECTOR2(const D3DXVECTOR2& v) : x((T)v.x), y((T)v.y) {}
 26     
 27   //把VECTOR2类型的数据转化成D3DXVECTOR2类型
 28   inline D3DXVECTOR2 ToD3DXVECTOR2() const
 29   {
 30       return D3DXVECTOR2((float)x,(float)y);
 31   }
 32 
 33   //重载负号操作符,VECTOR2的取相反数操作。
 34   inline VECTOR2 operator- () const
 35   {
 36       return VECTOR2(-x, -y);
 37   }
 38 
 39   //重载加号操作符,VECTOR2的加法操作。
 40   inline VECTOR2 operator+ (const VECTOR2& v) const
 41   {
 42       return VECTOR2(this->x + v.x, this->y + v.y);
 43   }
 44 
 45   //重载加号操作符,VECTOR2的加法操作
 46   inline VECTOR2 operator- (const VECTOR2& v) const
 47   {
 48       return VECTOR2(this->x - v.x, this->y - v.y);
 49   }
 50  
 51   //重载乘号操作符,VECTOR2的垂直乘法操作。即,
 52   //两矢量的各个对应分量独自相乘,得出的值作为一个新矢量的分量。
 53   inline VECTOR2 operator* (const VECTOR2& v) const
 54   {
 55       return VECTOR2(this->x * v.x, this->y * v.y);
 56   }
 57  
 58   //重载除号操作符,VECTOR2的垂直除法操作。即,两矢量的各个
 59   //对应分量独自相除,得出的值作为一个新矢量的分量。
 60   inline VECTOR2 operator/ (const VECTOR2& v) const
 61   {
 62       return VECTOR2(this->x / v.x, this->y / v.y);
 63   }
 64 
 65#ifndef NO_TEMPLATE_SELECTION
 66   template < class U >
 67   inline VECTOR2 operator+ (const U& a) const
 68   {
 69       return VECTOR2(this->x + a, this->y + a);
 70   }
 71 
 72   template < class U >
 73   inline VECTOR2 operator- (const U& a) const
 74   {
 75       return VECTOR2(this->x - a, this->y - a);
 76   }
 77 
 78   template < class U >
 79   inline VECTOR2 operator* (const U& a) const
 80   {
 81       return VECTOR2(this->x * a, this->y * a);
 82   }
 83 
 84   template < class U >
 85   inline VECTOR2 operator/ (const U& a) const
 86   {
 87       return VECTOR2(this->x / a, this->y / a);
 88   }
 89 
 90#endif
 91   //重载自加操作
 92   inline void operator+= (const VECTOR2& v)
 93   {
 94       this->x += v.x;
 95       this->y += v.y;
 96   }
 97 
 98   //重载自减操作
 99   inline void operator-= (const VECTOR2& v)
100   {
101       this->x -= v.x;
102       this->y -= v.y;
103   }
104 
105   //重载垂直自乘操作
106   inline void operator*= (const VECTOR2& v)
107   {
108       this->x *= v.x;
109       this->y *= v.y;
110   }
111 
112   //重载垂直自除操作
113   inline void operator/= (const VECTOR2& v)
114   {
115       this->x /= v.x;
116       this->y /= v.y;
117   }
118 
119   //如果两矢量的各个相对应的分量都相等,则都相等
120   inline bool operator== (const VECTOR2& v) const
121   {
122       return ((this->x == v.x) && (this->y == v.y));
123   }
124 
125   inline bool operator!= (const VECTOR2& v) const
126   {
127       return ((this->x != v.x) || (this->y != v.y));
128   }
129 
130#ifndef NO_TEMPLATE_SELECTION
131  
132   template < class U >
133   inline void operator+= (const U& a)
134   {
135       this->x += a;
136       this->y += a;
137   }
138 
139   template < class U >
140   inline void operator-= (const U& a)
141   {
142       this->x -= a;
143       this->y -= a;
144   }
145 
146   template < class U >
147   inline void operator*= (const U& a) {
148       this->x *= a;
149       this->y *= a;
150   }
151 
152   template < class U >
153   inline void operator/= (const U& a)
154   {
155       this->x /= a;
156       this->y /= a;
157   }
158 
159   template < class U >
160   inline bool operator== (const U& a) const
161   {
162       return ((this->x == a) && (this->y == a));
163   }
164 
165   template < class U >
166   inline bool operator!= (const U& a) const
167   {
168       return ((this->x != a) || (this->y != a));
169   }
170#endif
171 
172   //矢量自加一个标量,即矢量的各个分量加上一个相同的标量
173   template < class U >
174   inline VECTOR2 ScalarAddition(const U& a) const
175   {
176       return VECTOR2(this->x + a, this->y + a);
177   }
178 
179   //矢量自减一个标量,即矢量的各个分量减去一个相同的标量
180   template < class U >
181   inline VECTOR2 ScalarSubtraction(const U& a) const
182   {
183       return VECTOR2(this->x - a, this->y - a);
184   }
185 
186   //矢量自乘一个标量,即矢量的各个分量乘上一个相同的标量
187   template < class U >
188   inline VECTOR2 ScalarMultiplication(const U& a) const
189   {
190       return VECTOR2(this->x * a, this->y * a);
191   }
192 
193   //矢量自除一个标量,即矢量的各个分量除上一个相同的标量
194   template < class U >
195   inline VECTOR2 ScalarDivision(const U& a) const
196   {
197       return VECTOR2(this->x / a, this->y / a);
198   }
199 
200   //判断两矢量是否相等
201   template < class U >
202   inline bool IsEqualTo(const U& a) const
203   {
204       return ((this->x == a) && (this->y == a));
205   }
206 
207   //判断两矢量是否不等
208   template < class U >
209   inline bool IsNotEqualTo(const U& a) const
210   {
211       return ((this->x != a) || (this->y != a));
212   }
213 
214   //判断两矢量的距离,令两矢量为V1(x1,y1),
215   //V2(x2,y2)其计算公式为
216   inline XFLOAT DistanceTo(const VECTOR2& v) const
217   {
218       return sqrt(pow(this->x - v.x,2) +
219                   pow(this->y - v.y,2));
220   }
221 
222   //求两矢量的夹角。
223   inline XFLOAT AngleTo(const VECTOR2& v) const
224   {
225       if ((v.x - this->x) == 0)
226           throw Error( _T("AngleTo going to
227                       divide by zero!"),
228                       __FILE__, __LINE__ );
229 
230       XFLOAT angle = atan((v.y - this->y)/(v.x - this->x));
231       if ((v.x - this->x) < 0)
232       {
233           angle += D3DX_PI;
234       }
235       return angle;
236   }
237 
238   //求本矢量的模
239   inline XFLOAT Magnitude() const
240   {
241       return sqrt(pow(x,2) + pow(y,2));
242   }
243 
244   //单位化(规格化)本矢量
245   inline void Normalize()
246   {
247       XFLOAT m = Magnitude();
248      
249       if (m <= VECTOR2_ZERO_TOLERANCE)
250       {
251           m = 1;
252       }
253 
254       x /= m;
255       y /= m;
256 
257       if (abs(x) < VECTOR2_ZERO_TOLERANCE) x = 0;
258       if (abs(y) < VECTOR2_ZERO_TOLERANCE) y = 0;
259   }
260 
261   //求两矢量的点积
262   //令两矢量为V1(x1,y1) V2(x2,y2)
263   //则两矢量的点积应为x1*x2+y1*y2;而此处的计算方式变成:
264   //sqrt( X1*X2*X1*X2+y1*y2*y1*y2 ),
265   //显然此处的矢量点积计算是错误的。
266   inline XFLOAT DotProduct(const VECTOR2& v)
267   {
268       return ((*this) * v).Magnitude(); //错误的计算方式
269 
270   }
271  
272   //求两矢量的叉积
273   inline XFLOAT CrossProduct(const VECTOR2& v)
274   {
275       return (this->x * v.y) - (this->y * v.x);
276   }
277 
278   T x;    //矢量的x分量
279   T y;    //矢量的y分量
280};
281 
282typedef VECTOR2< int > IVECTOR2;   //整数类型的二维矢量
283typedef VECTOR2< float > FVECTOR2; //单精度类型的二维矢量
284typedef VECTOR2< double > DVECTOR2;    //双精度类型的二维矢量
285 
286//把某矢量的每一个分量化格式成字符串
287template< class T >
288tstring toTString(const VECTOR2< T >& v)
289{
290   return tstring( _T("(") + toTString(v.x) + _T(",") +
291                   toTString(v.y) + _T(")"));
292}

3.3 基本的颜色理论

3.3.1 颜色的基本要素

3.3.2 颜色模型

3.4 DxFramework的纹理类

3.4.1 纹理的基本概念

在计算机图形学发展的早期,计算机生成的3D图像往往像是一块能反射强光的塑料。和自然界的各种物体相比,早期的3D图像缺乏各种纹路——如磨损、裂痕、指纹和污渍等,而这些纹路会增加三维物体的真实感。正因为如此,计算机图形学发展至今,纹理(texture)已经在得到普遍的应用,成为增强计算机生成的三维图像的真实感的重要工具。

在日常生活中,人们经常使用名词“纹理”来表示某个物体的光滑度或粗糙度。在计算机图形学中,纹理指的是一张表示物体表面细节的位图。在这里请注意这个位图不单单指Windows平台上以bmp为后缀名的图像文件格式。而“纹理贴图”(texture mapping)作为一个动宾式短语理解时,可以理解为使用图像,函数,或者是其他的数据源来改变物体的外观。

在Direct3D中,纹理可以贴在3D图元(primitive)的表面。例如,应用程序可以创建一个长方体,通过给这个长方体的六个表面都贴上一张木纹外观的纹理,从而使得这个长方体看起来像一个表面刨平打磨光滑了的木方。可以把草皮、泥土和岩石等纹理贴在构成山的图元的表面,这样就能得到看起来很真实的山坡。同样地,应用程序也可以通过使用纹理创建具有不同外观效果的三维图形。如:路边的路标,悬崖断层的岩层,或是地面上镶嵌的大理石地板,等等。

除了应用在3D图元上,纹理还可以应用在2D精灵上。DirectX8.1提供了ID3DXSprite接口,通过给此接口指定一个将要“贴”给2D精灵的纹理,ID3DXSprite接口将能自动地利用此纹理来绘制2D精灵。

3.4.2. 跨度和宽度

一般地,我们脑海中的纹理大都是二维的呈矩形状,有着高度和宽度(当然也有三维的纹理,在此就不作详叙)。纹理划分成一个个texel。texel的个数就是纹理的高度值乘以宽度值。这也就是所谓的纹理逻辑视图。如图:

但是在计算机存储空间中,纹理中每一个texel实际上是按照线性顺序一条线排列下去的,而不是像在逻辑视图中分为多行。如图:

所谓的跨度就是指在纹理逻辑视图中,每两行,垂直相邻的两个texel之间的内存偏移量。如图中的第1个texel和第6个texel的偏移量。必须注意的是,纹理的跨度并不一定是和纹理的宽度相等的。因为并不是所有的显示卡都能按照“跨度等于宽度”的方式来安排纹理所处的内存的。比如,在真实的内存空间中摆放完逻辑视图中的第一行texel之后,如果不是马上接着就摆放第二行texel,而是中间隔了若干个字节才摆放逻辑视图中的第二行texel的话,很显然,此时的跨度值就大于宽度值。

DirectX提供了检测显卡支持最大纹理宽度的功能。使用IDirect3DDevice8::GetDeviceCaps(D3DCAPS8* pCaps)函数和D3DCAPS8结构体能检测到当前的显卡的能力,如下:

 1//m_pd3dDevice是一个IDirect3DDevice8类型的指针
 2D3DCAPS8 d3dCaps;
 3m_pd3dDevice->GetDeviceCaps( &d3dCaps );
 4 
 5C_Texture类是对IDirect3DTexture8接口的进一步封装。此类实现了对纹理的内存管理。此类在程序中主要由2D精灵类C_Sprite和3D图元类C_Primitive所使用
 6 
 7map<tstring, TextureInfo>	C_Texture::infoMap; //纹理信息映射表
 8C_Texture::C_Texture(const tstring& saveFileName, int width, int height)
 9{
10    multipleLevel = false;
11    this->filename = saveFileName;
12    this->width = width;
13    this->height = height;
14    texture = NULL;
15
16    //纹理是否已经载入
17    if (infoMap.size()) 
18    {	    //要确保在已创建的纹理中,没有和要创建的纹理的名字同名
19        //如果要创建的纹理的名字,在纹理信息映射表中已经存在的话
20        if(infoMap.find(filename) != infoMap.end())
21        {
22            TextureInfo temp = infoMap[filename];
23
24            // 检查要创建的纹理的高度,宽度,multipleLevel
25            // 是否和已存在的同名纹理相等如果不相等的话,则抛出异常。
26            if ((multipleLevel != temp.multipleLevel) || (width != temp.imageInfo.Width) ||   (height != temp.imageInfo.Height)) 
27            {
28                    hrow Error(_T("C_Texture(): tried to load same texture with different width or height or multipleLevel"));
29            }
30
31            //如果要创建的纹理的属性和已存在的同名纹理的都相等,那么表
32            //示该纹理已经从磁盘文件中载入并创建对本纹理的引用计数加1
33            texture = infoMap[filename].texture;
34            ++(infoMap[filename].count); //引用计数加1
35            return;
36        }
37    }
38
39    //如果还没有纹理从磁盘文件中载入并创建,或者是已创建的纹理中,
40    //和要创建的纹理的名字同名,则调用Direct3D库函数
41    //D3DXCreateTexture,创建一张空白的纹理
42    TextureInfo info;
43    HRESULT hr = D3DXCreateTexture(C_View::p_D3DDevice, width, height, 0, D3DX_DEFAULT, D3DFMT_A8R8G8B8, D3DPOOL_MANAGED, 
44    &(info.texture));
45    if (FAILED(hr))  
46            throw Error(_T("CreateNewTexture(): D3DXCreateTexture "),__FILE__,__LINE__,hr);
47    info.count = 1;
48    info.multipleLevel = multipleLevel;
49    info.colorKey = NULL;
50    info.imageInfo.Width = width;
51    info.imageInfo.Height = height;
52
53    //保存好本纹理的IDirect3DTexture8接口
54    texture = info.texture;
55    //保存好本纹理的属性信息到纹理信息映射表中去
56    infoMap[filename] = info;
57    p_Pixels = NULL;
58}

分析完了上面的构造函数,重点分析库函数D3DXCreateTexture。第四个参数是mip level的设定,当其值为0或者是D3DX_DEFAULT的时候,完全的mipmap chain将会被创建。第五个参数,指定了纹理的Usage。第六个参数指定了纹理的像素格式,如下图,第七个参数设定为D3DPOOL_MANAGED,表示纹理将交给Direct3D进行管理。

  1C_Texture::C_Texture(const tstring& filename, bool multipleLevel, D3DCOLOR colorKey)
  2{
  3    this->multipleLevel = multipleLevel;
  4    this->filename = filename;
  5    texture = NULL;
  6
  7    //要确保在已创建的纹理中,没有和要创建的纹理的名字同名。如果要创建的纹理的名字,在纹理信息映射表中已经存在的话
  8    if (infoMap.size())
  9    {
 10        if (infoMap.find(filename) != infoMap.end())
 11        {
 12            if ((multipleLevel != infoMap[filename].multipleLevel) || (colorKey != infoMap[filename].colorKey)){
 13                throw Error(_T("C_Texture(): tried to load same texture with different multipleLevel or colorKey"));
 14            }
 15
 16            // 如果要创建的纹理的属性和已存在的同名纹理的都相等,那么表示该纹理已经从磁盘文件中载入并创建对本纹理的引用计数加1
 17            texture = infoMap[filename].texture;
 18            ++(infoMap[filename].count);
 19            return;
 20        }
 21    }
 22
 23    HRESULT	hr;
 24    TextureInfo	info;
 25    tstring	pathname(IMAGE_DIR + filename);
 26
 27    // 如果没有载入的话,则从磁盘文件中载入纹理图像数据根据是否使用多级渐进纹理,使用不同的创建标志
 28    if (multipleLevel)
 29    {
 30        hr =D3DXCreateTextureFromFileEx(C_View::p_D3DDevice, pathname.c_str(), 0, 0, D3DX_DEFAULT, 0, D3DFMT_A8R8G8B8, 
 31            D3DPOOL_MANAGED, D3DX_DEFAULT, D3DX_DEFAULT, colorKey, &(info.imageInfo),NULL, &(info.texture));
 32    } 
 33    else
 34    {
 35        // 对于2D项目而言,纹理的高宽值就不限定非得要2的整数次方大小。
 36        hr =D3DXCreateTextureFromFileEx(C_View::p_D3DDevice, pathname.c_str(), 0, 0, 1, 0, D3DFMT_A8R8G8B8, 
 37                D3DPOOL_MANAGED, D3DX_FILTER_NONE, D3DX_DEFAULT, colorKey, &(info.imageInfo), NULL, &(info.texture));
 38    }
 39
 40    if (FAILED(hr)) 
 41        throw Error(_T("CreateNewTexture(): D3DXCreateTextureFromFileEx ") + pathname,__FILE__,__LINE__,hr);
 42
 43    info.count = 1;
 44    this->width = info.imageInfo.Width;
 45    this->height = info.imageInfo.Height;
 46    info.colorKey = colorKey;
 47    info.multipleLevel = multipleLevel;
 48    texture = info.texture;
 49    infoMap[filename] = info;
 50    p_Pixels = NULL;
 51}
 52 
 53//本纹理的引用计数减1
 54C_Texture::~C_Texture() 
 55{
 56    --(infoMap[filename].count);
 57}
 58
 59//把所有的纹理都清空释放掉
 60void C_Texture::PurgeTextures(bool shutdown) 
 61{
 62    map<tstring, TextureInfo>::iterator iter = 
 63    infoMap.begin();
 64
 65    while (iter != infoMap.end()) 
 66    {
 67        if ((*iter).second.count < 0)
 68            throw Error(_T("texture count less than 0!"));
 69
 70        //如果有纹理的引用计数小于0了,或者说强制性地关闭本纹理的使用了
 71        if ((*iter).second.count == 0 || shutdown) 
 72        {
 73#ifdef _DEBUG
 74            //如果有纹理的引用计数大于0,但是又强制性地关闭
 75            //本纹理的使用的话,就要产生一个异常
 76            if ((*iter).second.count > 0) 
 77            {
 78                Error e = Error(_T("texture count for ") + (*iter).first + _T(" on shutdown purge greater than 0!"));
 79                e.Prompt();
 80            }
 81#endif
 82            //把所有的纹理都清空释放
 83            tstring filename = (*iter).first;
 84            IDirect3DTexture8* texture =  (*iter).second.texture; 
 85            iter++;
 86            SAFE_RELEASE(texture);
 87            infoMap.erase(infoMap.find(filename));
 88        }
 89        else{iter++;}
 90    }
 91}
 92
 93int C_Texture::GetWidth() 
 94{
 95    return infoMap[filename].imageInfo.Width;
 96}
 97
 98int C_Texture::GetHeight()
 99{
100    return infoMap[filename].imageInfo.Height;
101}
102
103tstring C_Texture::GetFilename()
104{
105    return filename;
106}
107
108IDirect3DTexture8* C_Texture::GetTexture() 
109{
110    return texture;
111}
112 
113//开始在纹理上进行绘制,即对纹理的像素数据进行操作
114void C_Texture::BeginDrawing()
115{
116    if (multipleLevel)
117    {     //首先要确保多级渐进纹理是不能被绘制的
118            Error( _T("C_Texture::BeginDrawing : Cannot begin drawing for multiple resolution level texture"));
119    }
120
121    ZeroMemory(&lockedRect, sizeof(lockedRect));
122
123    //把整张纹理都锁定,不能锁定时就抛出异常
124    HRESULT hr = texture->LockRect(0, &lockedRect, NULL, 0);
125
126    // Check if we can lock 原来的代码少了“throw语句”,bug    
127    if (FAILED(hr)) 
128        throw Error(_T("C_Texture::BeginDrawing: Cannot lock texture"));
129
130    //获取纹理的跨度(pitch),并且获取指向纹理的首地址指针
131    pitch = lockedRect.Pitch; 
132    p_Pixels = (unsigned char *)lockedRect.pBits; 
133}
134
135//结束对纹理的绘制,解除锁定
136void C_Texture::EndDrawing()
137{
138    if (multipleLevel)
139    {
140        Error( _T("C_Texture::EndDrawing : Cannot end drawing for multiple resolutionlevel created texture"));
141    }
142
143    texture->UnlockRect(0); 
144    pitch = 0;
145    p_Pixels = NULL;
146}
147
148 
149//把当前的纹理数据存到磁盘文件中。保存成bmp文件格式
150void C_Texture::SaveToFile() 
151{
152    if (multipleLevel) 
153    {
154        Error( _T("C_Texture::SaveToFile : Cannot save the texture for multiple resolution level texture"));
155    }
156
157    if (p_Pixels != NULL) 
158    {
159        Error(_T("C_Texture::SaveToFile : Cannot save while drawing, use EndDrawing before you call this function"));
160    }
161
162    tstring nameTemp = IMAGE_DIR + filename;
163    HRESULT hr = D3DXSaveTextureToFile(nameTemp.c_str(),D3DXIFF_BMP, texture, NULL);
164    if (hr != D3D_OK) 
165        Error(  _T("C_Texture::SaveToFile : Cannot save texture")); 
166}
167
168
169//从指定的颜色值中获取到该颜色的Alpha,Red,Green,Blue分量
170void C_Texture::ObtainARGBFromColor(DWORD color, unsigned char *p_A, unsigned char *p_R, unsigned char *p_G, unsigned char *p_B)
171{
172    //从高位到低位,分别是A R G B
173    //第31位到第24位是alpha分量,第23位到第16位是Red分量
174    //第15位到第8位是Green分量,第7位到第0位是Blue分量
175
176    //取最初的color值的第7位到第0位
177    *p_B = (unsigned char) (color & 255);
178    color = color >> 8; //右移8位
179
180    //取最初的color值的第15位到第8位
181    *p_G = (unsigned char) (color & 255);
182    color = color >> 8; //再右移8位
183
184    //取最初的color值的第23位到第16位
185    *p_R = (unsigned char) (color & 255);
186    color = color >> 8;//再右移8位
187
188    //取最初的color值的第31位到第24位
189    *p_A = (unsigned char) (color & 255);
190}
191
192 
193//在给定的纹理像素位置中把颜色值设为color
194void C_Texture::PutPixel(int x, int y, DWORD color)
195{
196   if (p_Pixels == NULL)
197   {
198       Error( _T("C_Texture::PutPixel :
199              Cannot draw pixel before calling
200              BeginDrawing"));
201   }
202
203
204   if ((x < 0) || (y < 0) || (x >= width) || (y >= height))
205       return;
206   *((DWORD *)&p_Pixels[y * pitch + (x << 2)]) = color;
207}
208
209//获取给定的纹理像素的颜色值
210DWORD C_Texture::GetPixel(int x, int y) {
211   if (p_Pixels == NULL) {
212       Error(_T("C_Texture::GetPixel : Cannot get pixel
213             before calling BeginDrawing"));
214   }
215   if ((x < 0) || (y < 0) ||
216       (x >= width) || (y >= height))
217       return 0;
218   return *((DWORD *)&p_Pixels[y * pitch + (x << 2)]);
219}
220 
221//把左上角坐标为(x1,y1),右上角坐标为(x2,y2)/矩形范围的所有像素填充成color颜色
222void C_Texture::Bar(int x1, int y1, int x2,
223                   int y2, DWORD color)
224{
225   if (p_Pixels == NULL)
226   {
227       Error(_T("C_Texture::Bar : Cannot draw bar
228                 before calling BeginDrawing"));
229   }
230 
231   // Perform simple clip
232   if (x1 < 0) x1 = 0;
233   if (y1 < 0) y1 = 0;
234   if (x2 >= width) x2 = width - 1;
235   if (y2 >= height) y2 = height - 1;
236 
237   if ((x1 >= width) || (y1 >= height)
238      || (x2 < 0) || (y2 < 0) )
239      return;
240   int i, j;
241 
242   for (i = y1; i <= y2; i++)
243   {
244       for (j = x1; j <= x2; j++)
245       {
246       *((DWORD *)&p_Pixels[i * pitch + (j << 2)]) = color;
247       }
248   }
249}
250 
251//把左上角坐标为(x1,y1),右上角坐标为(x2,y2)的矩形边上的像素填充成color颜色
252void C_Texture::Rectangle(int x1, int y1, int x2,
253                           int y2, DWORD color)
254{
255   int i;
256 
257   for (i = x1; i <= x2; i++)
258   {
259       PutPixel(i, y1, color);
260       PutPixel(i, y2, color);
261   }
262 
263   for (i = y1; i <= y2; i++)
264   {
265       PutPixel(x1, i, color);
266       PutPixel(x2, i, color);
267   }
268}

在纹理上画线,或者说在计算机屏幕上画线,就要谈几何线段光栅化的问题。我们知道,从数学角度上而言,要绘制的各种几何线段在实数空间中都是连续的,而在计算机中,能组成这些几何线段的像素,或者说texel,都是一个一个单独离散的。也就是说,无法直接把在实数连续空间中的几何线段的每一个点直接对应上整数离散空间上的像素点或者是texel上。因此,在像素空间中上或者是texel空间中,连续的几何线段只能由若干个线段近似地模拟表示。把在实数连续空间中的点转换到整数离散空间中的过程,便是几何线段光栅化。

直线生成算法有很多,在PC平台上应用最为广泛的是DDA(数字微分分析法digital differential analyzer)算法和Bresenham算法。Bresenham算法的关键在于要弄清楚在有限的屏幕分辨率的屏幕,或者在“纹理逻辑视图”上绘制一条线段的时候,线段上某一个点,当绘制到屏幕或纹理上时,所绘制的像素或者是texel可能在真实点位置之上,也可能在真实点位置之下。也可能正好对应。所绘制的像素或者时texel与真实线段之间的偏差称为这条线段在这点上的“误差”(error)。当绘制线段的过程,从一个像素点或者是texel点进行到下一个像素点或者是texel上的时候,这个误差将用来作一个更加精确的近似——将一个给定的像素或texel在水平或者垂直方向,或者两个方向上同时,移动一个单位,背离前面绘制的一个点。

现在用一个例子来说明,给定两个点的坐标,这两个点将作为将要绘制的线段的端点,坐标空间则采用和计算机屏幕空间或纹理逻辑视图空间相同的坐标系,即原点在左上角,X增长的方向是水平向右,Y增长的方向是垂直向下。如图:

在图中,线段的两个端点正好落在显示的像素上,但这条线段的其他任何部分都没有正好落在像素点上,这也就是说,其他所有的像素都只能作为线段的一个近似。Bresenham算法采用的近似方法就是每次在新绘制一个像素点的时候,将它沿着线段的主方向移动一个像素的宽度,而当线段在沿次要方向移动的距离超过两个相邻的像素的一半的时候,沿着次方向则需要移动一个像素的宽度。在图中,x方向是主要方向,这意味着有6个点要进行绘制。每个X坐标绘制一点,依次为0,1,2,3,4,5,而算法的关键之处在于,给定了X坐标值的前提下,如何确定所这些像素点对应的Y坐标值。

从图中可以看出,合适的Y坐标值应为:即就是说,选定点的标准是:在给定了X坐标值的前提下,此点的Y坐标值是最靠近实际线段的点的Y坐标值。Bresenham算法也同样地基于同样的标准来进行判断。它实现这样的判断,方法是:对每个X值保持关于这条线不断变化的误差,即是从真实线到当前y坐标有多远。如图所示,如果这个变化的误差表明当前的Y坐标偏离真实线段到了某个程度,意思是说相邻的Y坐标更加接近真实线,则当前的Y坐标改变为相邻的Y坐标。

基于上面的算法,可以再举一个例子演示Bresenham算法如何一步一步地绘制如下图的线段,线段的起始点为(0,0),绘制最初的像素。显然该处的误差为0。

因为水平X轴为主要方向,下一个像素点的X坐标为1,那么这个像素的Y坐标为0,(意思是指:这个像素点的Y坐标保持和上一个像素点Y坐标一致),还是为1(意思是指:这个点的Y坐标比上一个点的Y坐标沿着Y轴方向递增1),取决于真实线在这一点上,其Y坐标值离哪个像素点的Y坐标更近。在这一点处的误差为B-A。这个误差小于1/2,(也即是说小于到两个像素点沿Y轴方向的中点)。因此,Y坐标在X=1处不改变,结果在(1,0)处绘制第二个像素。

第三个象素的X坐标为2,与当前的Y坐标(值为0)的误差为C-A,结果,此差值比1/2大。因此,下一个象素点Y坐标更靠近线段的实际位置,第三个象素将在(2,1)处绘制,并且从误差中减去1来弥补新Y坐标对旧Y坐标的调整。当前象素的误差实际上是C-D。

第四个像素的X坐标为3,该点处的误差应为E-D,因为误差小于1/2。因此当前Y坐标不改变,第4个像素将在(3,1)处绘制。

第五个象素的X坐标为4,该点处的误差应该为F-D,因为误差大于1/2,因此当前Y坐标前移,第5个象素点将在(4,2)处绘制,并从当前的误差值中减去1,当前的误差点为G-F。最后第六个点就是端点。这个点的X坐标为5,该点的误差为G-G,即为0,表明这个点正好在真实线上,正如设定的那样子第六个点就是终点。因此当前Y坐标保持不变。终点在(5,2)处绘制。线段的绘制结束。

  1//使用Bresenham算法来绘制直线
  2void C_Texture::Line(int x1, int y1, int x2,  int y2, DWORD color)
  3{
  4    //使用Bresenham algorithm算法
  5    int currentX = x1;
  6    int currentY = y1;
  7    int deltaX;
  8    int deltaY;
  9    int signDX;
 10    int signDY;
 11    int temp;
 12    int num2DYAfter;
 13    int num2DYm2DXAfter;
 14    bool swapXY = false;
 15
 16    //从底到顶,或者是从顶到低,或者是两端点在同一垂直线上,即y2=y1  
 17    if (y2 < y1) {
 18        signDY = -1; 
 19    } else if (y1 < y2) {
 20        signDY = 1; 
 21    } else {
 22        signDY = 0;
 23    }
 24
 25    //从左到右,或者是从右到左,或者是两端点在同一水平线上,即X2=x1
 26    if (x2 < x1) {
 27        signDX = -1; 
 28    } else if (x1 < x2) {
 29        signDX = 1; 
 30    } else {
 31        signDX = 0;
 32    }
 33
 34    PutPixel(x1, y1, color);
 35
 36    //计算两端点的水平距离和垂直距离
 37    deltaX = abs(x2 - x1);
 38    deltaY = abs(y2 - y1);
 39
 40    // Temporary for calculation
 41    temp = 2 * (deltaY - deltaX);
 42
 43    //希望两端点的水平距离大于垂直距离
 44    if (deltaY > deltaX) {
 45        swap(deltaX, deltaY);
 46        swapXY = true;
 47    }
 48
 49    // deltaX > deltaY, after swap
 50    num2DYAfter = 2 * deltaY;
 51    num2DYm2DXAfter = 2 * (deltaY - deltaX);
 52    
 53    // Loop along the longer axis to draw pixels
 54    // 沿着比较长的轴逐个绘制像素
 55    for (int i = 1; i <= deltaX; i++) 
 56    {
 57        if (temp < 0)
 58        {
 59            if (swapXY) {
 60                currentY += signDY;
 61            } else {
 62                currentX += signDX;
 63            }
 64
 65            PutPixel(currentX, currentY, color);
 66            temp += num2DYAfter;
 67        }
 68        else
 69        {
 70            currentX += signDX;
 71            currentY += signDY;
 72            PutPixel(currentX, currentY, color);
 73            temp += num2DYm2DXAfter;
 74        }
 75    }
 76}
 77
 78//画圆算法
 79void C_Texture::Circle(int x, int y, 
 80                        int radius, DWORD color)
 81{
 82    // Use Midpoint Circle algorithm
 83    int currentDX = 0;
 84    int currentDY = radius;
 85    int tempCal = 1 - radius;
 86
 87    // Plot 4 starting points
 88    PutPixel(x, y + radius, color);
 89    PutPixel(x, y - radius, color);
 90    PutPixel(x + radius, y, color);
 91    PutPixel(x - radius, y, color);
 92
 93    while (currentDX < currentDY) {
 94        currentDX++;
 95        if (tempCal < 0) {
 96            tempCal += 2 * currentDX + 1;
 97        } else {
 98            currentDY--;
 99            tempCal += 2 * (currentDX - currentDY) + 1;
100        }
101
102        // Plot 8 points
103        PutPixel(x + currentDX, y + currentDY, color);
104        PutPixel(x + currentDX, y - currentDY, color);
105        PutPixel(x - currentDX, y + currentDY, color);
106        PutPixel(x - currentDX, y - currentDY, color);
107        PutPixel(x + currentDY, y + currentDX, color);
108        PutPixel(x + currentDY, y - currentDX, color);
109        PutPixel(x - currentDY, y + currentDX, color);
110        PutPixel(x - currentDY, y - currentDX, color);
111    }
112}
113
114//画椭圆
115void C_Texture::Ellipse(int x, int y, int radiusX, 
116                        int radiusY, DWORD color)
117{
118    int radiusXP2 = radiusX * radiusX;
119    int radiusYP2 = radiusY * radiusY;
120
121    int radiusXP2m2 = radiusXP2 * 2;
122    int radiusYP2m2 = radiusYP2 * 2;
123
124    int currentDX = 0;
125    int currentDY = radiusY;
126    int tempX = 0;
127    int tempY = radiusXP2m2 * radiusY;
128    
129    // Plot top and bottom most pixel
130    PutPixel(x, y - radiusY, color);
131    PutPixel(x, y + radiusY, color);
132
133    int temp;
134
135    // Draw the region where magnitude of the slope of the
136    //tangent line to the ellipse is less than 1
137    temp = (int)round(radiusYP2 - 
138                    (radiusXP2 * (radiusY - 0.25)));
139    while (tempX < tempY) 
140    {
141        currentDX++;
142        tempX += radiusYP2m2;
143        if (temp < 0) {
144            temp += radiusYP2 + tempX;
145        } else {
146            currentDY--;
147            tempY -= radiusXP2m2;
148            temp += radiusYP2 + tempX - tempY;
149        }
150        PutPixel(x + currentDX, y + currentDY, color);
151        PutPixel(x + currentDX, y - currentDY, color);
152        PutPixel(x - currentDX, y + currentDY, color);
153        PutPixel(x - currentDX, y - currentDY, color);
154    }
155
156    // Draw the rest
157    temp = (int)round(radiusYP2 * (currentDX + 0.5) * 
158                (currentDX + 0.5) + radiusXP2 * 
159                (currentDY - 1) * (currentDY - 1) 
160                - radiusYP2 * radiusXP2);
161
162    while (currentDY > 0)
163    {
164        currentDY--;
165        tempY -= radiusXP2m2;
166        if (temp > 0) {
167            temp += radiusXP2 - tempY;
168        } else {
169            currentDX++;
170            tempX += radiusYP2m2;
171            temp += radiusXP2 - tempY + tempX;
172        }
173        PutPixel(x + currentDX, y + currentDY, color);
174        PutPixel(x + currentDX, y - currentDY, color);
175        PutPixel(x - currentDX, y + currentDY, color);
176        PutPixel(x - currentDX, y - currentDY, color);
177    }
178}
179
180void C_Texture::FloodFill(int x, int y, DWORD color) 
181{
182    // Check if the point is in the screen or not
183
184    int tempX, tempY;
185    queue<int> xQueue;
186    queue<int> yQueue;
187    DWORD areaColor = GetPixel(x, y);
188    xQueue.push(x);
189    yQueue.push(y);
190
191    while (!xQueue.empty()) {
192        tempX = xQueue.front();
193        tempY = yQueue.front();
194        
195        xQueue.pop();
196        yQueue.pop();
197
198        if ((tempX >= 0) && (tempY >= 0) && 
199            (tempX < width) && (tempY < height) && 
200            (GetPixel(tempX, tempY) == areaColor)) 
201        {
202            PutPixel(tempX, tempY, color);
203
204            xQueue.push(tempX - 1);
205            yQueue.push(tempY);
206
207            xQueue.push(tempX + 1);
208            yQueue.push(tempY);
209
210            xQueue.push(tempX);
211            yQueue.push(tempY - 1);
212
213            xQueue.push(tempX);
214            yQueue.push(tempY + 1);
215        }
216    }
217}