转载时空之旅博客文章
http://hi.baidu.com/wxvp/blog/category/%D3%CE%CF%B7%D6%C6%D7%F7
windows游戏编程大师笔记1
dx的hal层:硬件抽象层,hal是一种设备驱动程序,dx直接调用它与硬件进行通讯,条件是当硬件支持
你提交的请求 时.hal才被调用,速度比hel快
dx的hel层:硬件仿真层,当硬件不支持你提交的请求 时.hel才被调用,比如说旋转位图,要是硬件不
支持就只有靠软件算法来.
使用dx时 hal和hel对程序员来说是透明的.
windows游戏编程大师笔记2:创建DirectDraw对象
创建DirectDraw对象
函数原型 HRESULT WINAPI DirectDrawCreate ( GUID FAR *lpGUID , LPDIRECTDRAW FAR *
lplpDD,
IUnknown
FAR * pUnkOuter);
其中 LpGUID 这是你使用的视频驱动的GUID 很多情况下用NULL代表系统默认配置
lplpDD 是一个指向指针的指针。函数调用成功后 它指向一个IDirectDraw接口
pUnkOuter 高级功能,一般设置为NULL。
eg: DirectDrawCreate (NULL,$lpdd,NULL); 如果执行成功,返回IDirectDraw1.0接口,注意不是
7.0的。
一般在程序中这样
if (FAILED(DirectDrawCreate(NULL,&lpdd,NULL)))
{
//error
}
DirectDrawCreate返回 DD_OK表示完全成功.(书P195页)
升级到DiectDraw7的方法
////////////////////////////////////////////////////////////////
LPDIRECTDRAW lpdd = NULL;
LPDIRECTDRAW lpdd7=NULL;
if (FAILED(DirectDrawCreate(NULL,&lpdd,NULL)))
{
//error
}
if(FAILED(lpdd->QueryInterface(IID_IDirectDraw7,(LPVOID *)&lpdd7)))
{
//error
}
//再释放旧接口
lpdd->Release();
lpdd = NULL;
///////////////////////////////////////////////////////////////////////////////////////
/////////////////////
方法2 直接创建一个dx7的对象
HRESULT WINAPI DirectDrawCreateEx (GUID FAR *lpGUID, LPVOID *lplpDD, REFIID
iid,IUnknown FAR *pUnkOuter )
eg:
if (FAILED(DirectDrawCreateEx(NULL, (void **)&lpdd, IID_IDirectDraw7, NULL)))
return(0);
windows游戏编程大师笔记3:windows协作模式
全屏模式 FULL-SCREEN MODE
窗口模式 WINDOWED MODE
用这个方法 IDirectDraw7 :: SetCooperativeLevel()
函数原型 HRESULT SetCooperativeLevel (
HWND hWnd //一般是主
窗口句柄
DWORD dwFlags//控制标
志,见p199
);
eg:全屏协作模式
lpdd7->SetCooperativelevel (hwnd,DDSCL_FULLSCREEN | DDSCL_ALLOWMODEX |
DDSCL_EXCLUSIVE | DDSCL_ALLOWREBOOT);
一般窗口协作模式设置
lpdd7->SetCooperativeLevel (hwnd,DDSCL_NORMAL);
windows游戏编程大师笔记4:设置屏幕显示模式
IDirectDraw7 :: SeDisplayMode()
原型 HRESULT SetDisplayMode (DWORD dwWidth,//宽的像素
DWORD dwHeight,//长的像素
DWORD dwBPP,//颜色位数,8,16,24
DWORD dwRefreshRate,//刷新频率,默
认是系统的频率就设为0
DWORD dwFlags//标志位
);
eg: 建立16位色的800 x 600 模式
lpdd->SetDisplayMode (800,600,16,0,0);
windows游戏编程大师笔记5:调色板
创建一个256色的调色板并在DirectDraw中使用
1 创建一个或多个调色板数据结构:大小为256且类型为 PALETTENTRY的数组
type unsigned char BYTE
typedef struct tagPALETTEENTRY
{
BYTE peRed;
BYTE peGreen;
BYTE peBlue;
BYTE peFlags;
} PALETTEENTRY;
PALETTEENTRY palette[256];
填充每一个元素
2 从DirectDraw 对象创建一个DirectDraw调色板接口 IDirectDrawPalette对象。一般直接映射到
硬件VGA调色板寄存器。p207
创建 IDirectDrawPalette 接口 IDirectDraw7 :: CreatePalette()
HRESULT CreatePalette( DWORD dwFlags,
LPPAETTEENTRY lpColorTable,//之前定义的那个数组地址
LPDIRECTDRAWPALETTE FAR *lplpDDpalette,
IUnknown FAR *pUnkOuter
);
eg:
LPDIRECTDRAWPALETTE lpddpal =NULL;
if (FAILED(lpdd->CreatePalette ( DDPCAPS_8BIT|DDPCAPS_ALLOW256|DDPCAPS_INITIALIZE,
palette,//之前定义的调色板数组地址
&lpddpal,//IDirectDrawPalette接口地址
NULL)))
3 关联到一个绘图表面.
主显示表面直接对应被光栅化的实际显存,任何时候都是显示在屏幕上,但只能有一个主显示表面
。
从显示页面可以有多个。
创建主显示表面步骤
1>填充一个DDSURFACEDESC2数据结构。
2>用IDirectDraw7 :: CreateSurface()来创建
eg:
///////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////
LPDIRECTDRAWSURFACE7 lpddsprimary = NULL ;//定义一个指向主表面的一个指针
DDSURFACEDESC2 ddsd;//一个DirectDraw 表面的结构
//填充ddsd的一些域
ddsd.dwSize = sizeof(ddsd);//p211
ddsd.dwFlags = DDSD_CAPS;//p211
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;//p214
//创建表面
if ( FAILED (lpdd->CreateSurface ( &ddsd,&lpddsprimary,NULL)))
{}
///////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////
创建完主表面后可以关联调色板了
if (FAILED (lpddsprimary->SetPalette(lpddpal))) //lpddsprimary是前面定义的主表面,
lpddpal是前面定义的dx调色板接口
{}
windows游戏编程大师笔记6:锁屏与绘点函数
绘制像素在之前的几步做完之后就可以进行了。
DirectDraw下的视频模式和显示表面都是线性的,即使显卡本身不是线性的,DirectDraw也会把它
转换成线性的。
绘制像素之前还要先锁屏
HRESULT Lock(LPRECT lpDestRect ,//要锁定的屏幕矩形,NULL为全部主表面。
LPDDSURFACEDESC2 lpDDSurfaceDesc,//表面结构的地址
DWORD dwFlags,//控制标志p219
HANDLE hEvent//事件,一般设为NULL
);
eg:
DDSURFACEDESC2 ddsd;
memset (&ddsd,0,sizeof(ddsd));//初始化ddsd
if (FAILED(lpddsprimary->Lock(NULL,
&ddsd,
DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT,
NULL);
下面是16位的两种RGB的宏
#define _RGB16BIT555(r,g,b) ((b & 31) + ((g & 31)<<5) + ((r & 31) <<10) )
#define _RGB16BIT565(r,g,b) ( (b & 31) + ((g & 63)<<5) + ((r & 31) <<11) )
下面是画点函数
8位的
inline void plot8(int x,
inty,
UCHAR color,
UCHAR *buffer,//UCHAR是8位的
int mempitch
)
{
buffer[x+ y * mempitch] = color;//现在是直接写显存或内存了
}
16位的
inline void plot16(int x,
int y,
UCHAR red,
UCHAR green,
UCHAR blue,
USHORT *buffer,//USHORT是16位的
int mempitch
)
{
buffer[x+y*(mempitch>>1)] = __RGB16BIT565 (red,green,blue);
//因为mempitch是设置的分辨率的倍数
//如果是640*480*8 那么mempitch就是640
//如果是640*480*16 那么mempitch就是 1280
//所以上面要右移一位来除以2
}
///////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////
之后是解锁操作
HRESULT Unlock (LPRECT lpRect)
eg:
if (FAILED (lpddsprimary->Unlock(NULL)))
{}
windows游戏编程大师笔记7:清除资源
在前面所有完成之后,退出程序时要清除之前申请的资源。
在清除时要按照之前申请顺序的反向顺序来清除。
eg:
//清除调色板
if (lpddpal->Release())
{
lpddpal->Release();
lpddpal =NULL;
}
//清除主表面
if (lpddsprimary)
{
lpddsprimary->Release();
lpddsprimary = NULL;
}
//清除DirectDraw对象
if (lpdd)
{
lpdd->Release():
lpdd = NULL;
}
windows游戏编程大师笔记8:高彩模式和获取像素格式
16位色高彩模式:5r.5g.5b 和5r.6g.5b这两种,第一种只有15位,剩一位做透明度,第二种绿色为6
位,因为人对绿色要敏感些。
建立2个 宏来表示
#define _RGB16BIT555(r,g,b) ( (b&31) + ( (g&31)<<5) + ( (r&31)<<10) )
为什么要&31呢?因为31的16位2进制是0000000000011111,就是相当于掩码那种意思。向左移动5位
就相当于g在中间了,后面的&63也是这种
#define _RGB16BIT565(r,g,b) ( (b&31) + ( (g&63)<<5) + ( (r&31)<<11) )
如何知道显卡是以上面那种格式工作的呢,这要通过IDIRECTDRAWSURFACE7 :: GetPixelFormat()函
数来获取。
HRESULT GetPixelFormat (LPDDPIXELFORMAT lpDDPixelFormat);//p230页.
下面是16位的画点函数,在使用它前后要锁定表面和解锁表面。
inline void Plot_Pixel_16(int x,int y,int red,int green, int blue, USHORT
*video_buffer, int lpitch)
{
USHORT pixel = __RGB16BIT565(red,green,blue);
video_buffer[x+y*(lpitch >> 1)] = pixel;
//因为lpitch是设置的分辨率的倍数
//如果是640*480*8 那么lpitch就是640
//如果是640*480*16 那么lpitch就是 1280
//所以上面要右移一位来除以2
}
32位的画点
#define _RGB32BIT(a,r,g,b) ((b) + ( (g) << 8) + ((r) << 16 ) + ((a) << 24))
inline void Plot_Pixel_32(int x,int y,int alpha,int red,int green,int blue,UINT
*video_buffer,int lpitch32)
{
UINT pixel = _RGB(alpha,red,green,blue);
video_buffer [x + y*lpitch32] = pixel;
//lpitch32和上面的lpitch一样 ,不同的是位移2位 即:除以4
}
windows游戏编程大师笔记9:双缓冲技术
在dx中实现双缓冲除了要设立主显示页面外,还要创建一个同主显示页面完全一样的离屏表面。
如果不设缓冲表面,也可以用同主显示页面同样大小的内存代替
如:640*480*8的内存
UCHAR *double_buffer = (UCHAR *) malloc (640*480);
或者
UCHAR *double_buffer = new UCHAR[640*480];
让后备缓冲显示出来,可以把后备缓冲拷贝到主显示页面代码如下
if (mempitch == 640)
{
memcpy ( (void *) primary_buffer, (void*) double_buffer , 640*480);
//如果内存步长正好是640 就整块拷贝
}
else
{ //如果内存步长不是640 就一行一行的拷贝
for (int y =0; y<480 ;y++)
{
memcpu((void *) primary_buffer, (void *) double_buffer,640);
primary_buffer = primary_buffer + mempitch;//主缓冲也移动相应的步长,相当于主缓冲换行
double_buffer = double_buffer + 640;//在拷贝了640字节后,double_buffer也相应的移动
640,double_buffer换行
}
}//见p242图,由于mempitch比640要长,故每行都有额外的内存。
下面是用dx来创建一个后备表面
1先将 DDSD_BACKBUFFERCOUNT加到dwFlags标志字段,向DirectDraw表明DDSURFACESC2结构的
dwBackBufferCount字段有效,其中包括后备缓冲数目。
2再将控制标志 DDSCAPS_COMPLEX和DDSCAPS_FLIP加到DDSURFACEDESC2结构的特性描述字段
ddsCaps.dwCaps上。
3最后像通常一样建立表面,用 IDIRECTDRAWSURFACE7 :: GetAttachedSurface()得到后备缓冲。
例子:创建一个带有后备缓冲的主表面。
DDSURFACEDESC2 ddsd; //directdraw 表面结构说明
LPDIRECTDRAWSURFACE7 lpddsprimary = NULL;//主表面指针
LPDIRECTDRAWSURFACE7 lpddsback = NULL;//后备缓冲指针
DDRAW_INIT_STRUCT(ddsd);//初始化ddsd
ddsd.dwFlags = DDSD_CAPS | DDSD_BACKBUFFERCOUNT; //绿色的表示ddsd的dwBackBufferCount字
段有效
ddsd.dwBackBufferCount = 1;//表示一个后备缓冲页面
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE | DDSCAPS_COMPLEX | DDSCAPS_FLIP;//步骤第
二条
if (FAILED(lpdd->CreateSurface(&ddsd, &lpddsprimary, NULL);
return(0);//创建主表面
ddsd.ddsCaps.dwCaps = DDSCAPS_BACKBUFFER;
if (FAILED(lpddsprimary->GetAttachedSurface(&ddsd.ddsCaps, &lpddsback)))
return(0);//主表面得到后备缓冲
如果想操控后备缓冲信息
// 初始化ddsd
DDRAW_INIT_STRUCT(ddsd);
lpddsback->Lock(NULL, &ddsd, DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT, NULL);//锁定后备缓
冲
//这里可以对它进行读写操作
lpddsback->Unlock(NULL);//解锁
页面切换:前面有了主表面和后备缓冲表面,进行页面切换后就能实现动画了。
1清除后备缓冲。(初始化)
2将场景渲染到后备缓冲。
3用后备缓冲切换掉主显示表面。
4锁定帧速率。
5重复第一步。
用下面的函数来对关联表面和主表面进行切换。
HRESULT Flip (LPDIRECTDRAWSURFACE7 lpDDSurfaceTargetOverride, DWORD dwFlags);
其中lpDDSurfaceTargetOverride是个高级参数,用来覆盖切换链,实现切换到另一个表面,而不是
切换到同主表面相关的后备缓冲,这里值为NULL。后面的dwFlags是控制标志见p248
默认是1。这些要在DDCAPS结构中设置了DDCAPS2_FLIPINTERVAL后才起作用。
切换之前主表面和后备缓冲表面都要解锁。
代码例子p249
windows游戏编程大师笔记10:blitter(向目标内存复制矩形位图)
blitter(在灵格斯翻译软件中译成:块传送器)其实就是从源内存地址向目标内存地址复制矩形位
图。位图一般是线性存储在内存中。
这里借书上一个函数例子加以说明,当然这个比dx提供的要慢很多,只能说明道理。
void Blit8x8(int x,int y, UCHAR *video_buffer, UCHAR *bitmap)
{ //上面x,y是目标地址中的位置。*video_buffer是目标表面的地址,*bitmap是源表面的地址.
//目标表面是640*480*8位的。源表面是8*8的256色位图
video_buffer = (x + (y << 9) + (y << 7) );
//上面这句相当于现在目标表面中先找到x,y处
//相当于 y*640 = y * (512+128)=y * 2^9 + y * 2^7 = (y << 9) + (y<<7)
UCHAR pixel ;//用于读写的像素
//下面这个内循环用于拷贝一行的数据,外循环用于每一行的递增。
for (int index_y =0 ; index_y < 8; index_y++)
{
for (int index_x=0; index_x < 8; index_x++)
{
if (pixel = bitmap[index_x])
video_buffer[index_x]=pixel;
}
//下面这两句是每拷贝完一行后源矩形和目标矩形分别递增一行
bitmap+=8;
video_buffer+=640;
}
下面介绍dx的blitter函数
IDIRECTDRAWSURFACE7 :: Blt() //这个可以裁剪
IDIRECTDRAWSURFACE7 :: BltFast() //不能裁,但要快些。
HRESULT Blt(LPRECT lpDestRect, //确定目标表面的区域,为空时表示整个表面
LPDIRECTDRAWSURFACE7 lpDDSrcSurface, //源表面的地址
LPRECT lpSrcRect, //源表面上的矩形区域,为空表示整个表面
DWORD dwFlags, //控制标志,p257页
LPDDBLTFX lpDDBltFx //一个包含blitter所要信息的结构体,为dwflags所用
);
填充例子,p259页,还有p263
色彩键
http://hi.baidu.com/wxvp/blog/item/e64455ca93edf24ff21fe78d.html
创建离屏表面
http://hi.baidu.com/wxvp/blog/item/758a7adcbc664ea8cc11664c.html
位图
http://hi.baidu.com/wxvp/blog/item/5ac948807851e5de9023d94f.html
裁剪
http://hi.baidu.com/wxvp/blog/item/e9f8faf046e64bca7831aaf7.html
上面这些连接都是为blit动画准备的。
windows游戏编程大师笔记11:裁剪基础
不得不说《windows游戏编程大师》这本书从知识结构上来说安排的有些跳跃性,这也是我做这个笔
记的原因。前面的blit其实还没有讲完。这里开始讲裁剪:就是在blit进行的时候 拷贝你自己想要
的那一部分,而剪掉不要的。(书上定义为:剪掉落在视口<Viewport>之外的像素)
最简单的裁剪过滤器
int x1,y1,x2,y2;//视口的 左上角坐标和右下角坐标
void plot_pixel_clip8( int x ,int y , //这个坐标就是需要判断是否裁剪掉的那个像素
UCHAR color,//
UCHAR *video_buffer //目标表面地址
)
{
if (x >= x1 && x <= x2 && y >= y1 && y <= y2)
video_buffer[x+y*640] = color;//如果在视口内就画这个像素点在目标表面.
}
下面贴一个裁剪函数,书上的。
void Blit_Clipped(int x, int y, // 源图左上角坐标
int width, int height, // 源图长宽
UCHAR *bitmap, // 源图地址
UCHAR *video_buffer, // 目标视口地址
int mempitch) // 目标视口的内存步长
{
// this function blits and clips the image sent in bitmap to the
// destination surface pointed to by video_buffer
// the function assumes a 640x480x8 mode
//源图左上角坐标属于下列条件就立即退出函数,因为源图在视口之外了。
if ((x >= SCREEN_WIDTH) || (y>= SCREEN_HEIGHT) ||
((x + width) <= 0) || ((y + height) <= 0))
return;
// clip source rectangle
// 如果通过了上面的if语句 就给x1 y1 x2 y2 赋值
int x1 = x;
int y1 = y;
int x2 = x1 + width - 1;
int y2 = y1 + height -1;
// 如果源图左上角x在视口的左边,那么视口内的被裁剪的源图片的起始x就为0
if (x1 < 0)
x1 = 0;
if (y1 < 0)
y1 = 0;
// now lower left hand corner
if (x2 >= SCREEN_WIDTH)
x2 = SCREEN_WIDTH-1;
if (y2 >= SCREEN_HEIGHT)
y2 = SCREEN_HEIGHT-1;
// now we know to draw only the portions of the bitmap from (x1,y1) to (x2,y2)
// compute offsets into bitmap on x,y axes, we need this to compute starting point
// to rasterize from
//计算源图的裁剪点的坐标
int x_off = x1 - x;
int y_off = y1 - y;
// compute number of columns and rows to blit
//这是视口内填充时的右下角坐标
int dx = x2 - x1 + 1;
int dy = y2 - y1 + 1;
// compute starting address in video_buffer
//找到视口内填充的起点
video_buffer += (x1 + y1*mempitch);
// compute starting address in bitmap to scan data from
//找到源图被裁剪的起点
bitmap += (x_off + y_off*width);
// at this point bitmap is pointing to the first pixel in the bitmap that needs to
// be blitted, and video_buffer is pointing to the memory location on the destination
// buffer to put it, so now enter rasterizer loop
UCHAR pixel; // used to read/write pixels
//下面的for语句是 一行一行的复制源图被剪切的那部分
for (int index_y = 0; index_y < dy; index_y++)
{
// inner loop, where the action takes place
for (int index_x = 0; index_x < dx; index_x++)
{
// read pixel from source bitmap, test for transparency and plot
if ((pixel = bitmap[index_x]))
video_buffer[index_x] = pixel;
} // end for index_x
// advance pointers
video_buffer+=mempitch; // bytes per scanline
bitmap +=width; // bytes per bitmap row
} // end for index_y
} // end Blit_Clipped
现在开始讲用dx的裁剪器
IDirectDrawClipper
1创建DirectDraw裁剪器对象。
2创建裁剪序列。
3用IDIRECTDRAWCLIPPER :: SetClipList()将裁剪序列发送给裁剪器。
4用IDIRECTDRAWSURFACE7 :: SetClipper()将裁剪器同窗口和表面关联。
1创建裁剪器对象
HRESULT CreateClipper ( DWORD dwFlags, //控制标志
LPDIRECTDRAWCLIPPER FAR *lplpDDClipper,//裁剪器地址
IUnknown FAR *pUnkOuter // COM stuff
)
例子
LPDIRECTDRAWCLIPPER lpddclipper = NULL;
if ( FAILED(lpdd->CreateClipper(0,&lpddclipper,NULL));
成功的话lpddclipper就会指向一个有效的IDirectDrawClipper接口。
2裁剪器序列 就是进行blit的有效矩形序列。要填充一个RGNDATA数据结构,p272
3发送序列给裁剪器
HRESULT SetClipList( LPRGNDATA lpClipList,
DWORD dwFlags
);
例子
if ( FAILED (lpddclipper->SetclipList(&rgndata,0)));
return(0);
4关联表面
HRESULT SetClipper (LPDIRECTDRAWCLIPPER lpDDClipper);
例子
if (FAILED(lpddsurface->SetClipper(&lpddcliper)));
//其中lpddsurface 多是离屏表面.
上面4步代码见p273
windows游戏编程大师笔记12:位图的运用(bmp文件)
或许现在用bmp格式的游戏少了,但我是初学者嘛,所以还是要学。
位图结构与读写 借个链接:http://www.vckbase.com/document/viewdoc/?id=674 写的很清楚
书上自定义了一个 *BITMAP_FILE_PTR结构 用来存储位图
typedef struct BITMAP_FILE_TAG
{
BITMAPFILEHEADER bitmapfileheader; // this contains the bitmapfile header
BITMAPINFOHEADER bitmapinfoheader; // this is all the info including the palette
PALETTEENTRY palette[256]; // we will store the palette here
UCHAR *buffer; // this is a pointer to the data
} BITMAP_FILE, *BITMAP_FILE_PTR;
Load_Bitmap_File( BITMAP_FILE_PTR bitmap,char *filename) 这个函数是用来读取位图数据的
(见p279)
int Load_Bitmap_File(BITMAP_FILE_PTR bitmap, char *filename)
{
// this function opens a bitmap file and loads the data into bitmap
int file_handle, // the file handle
index; // looping index
UCHAR *temp_buffer = NULL; // used to convert 24 bit images to 16 bit
OFSTRUCT file_data; // the file data information
// open the file if it exists
if ((file_handle = OpenFile(filename,&file_data,OF_READ))==-1)
return(0);
// now load the bitmap file header
_lread(file_handle, &bitmap->bitmapfileheader,sizeof(BITMAPFILEHEADER));
// test if this is a bitmap file
if (bitmap->bitmapfileheader.bfType!=BITMAP_ID)
{
// close the file
_lclose(file_handle);
// return error
return(0);
} // end if
// now we know this is a bitmap, so read in all the sections
// first the bitmap infoheader
// now load the bitmap file header
_lread(file_handle, &bitmap->bitmapinfoheader,sizeof(BITMAPINFOHEADER));
// now load the color palette if there is one
if (bitmap->bitmapinfoheader.biBitCount == 8)
{
_lread(file_handle, &bitmap->palette,MAX_COLORS_PALETTE*sizeof(PALETTEENTRY));
// now set all the flags in the palette correctly and fix the reversed
// BGR RGBQUAD data format
for (index=0; index < MAX_COLORS_PALETTE; index++)
{
// reverse the red and green fields
int temp_color = bitmap->palette[index].peRed;
bitmap->palette[index].peRed = bitmap->palette[index].peBlue;
bitmap->palette[index].peBlue = temp_color;
// always set the flags word to this
bitmap->palette[index].peFlags = PC_NOCOLLAPSE;
} // end for index
} // end if
// finally the image data itself
_lseek(file_handle,-(int)(bitmap->bitmapinfoheader.biSizeImage),SEEK_END);
// now read in the image, if the image is 8 or 16 bit then simply read it
// but if its 24 bit then read it into a temporary area and then convert
// it to a 16 bit image
if (bitmap->bitmapinfoheader.biBitCount==8 || bitmap->bitmapinfoheader.biBitCount==16
||
bitmap->bitmapinfoheader.biBitCount==24)
{
// delete the last image if there was one
if (bitmap->buffer)
free(bitmap->buffer);
// allocate the memory for the image
if (!(bitmap->buffer = (UCHAR *)malloc(bitmap->bitmapinfoheader.biSizeImage)))
{
// close the file
_lclose(file_handle);
// return error
return(0);
} // end if
// now read it in
_lread(file_handle,bitmap->buffer,bitmap->bitmapinfoheader.biSizeImage);
} // end if
else
{
// serious problem
return(0);
} // end else
#if 0
// write the file info out
printf("/nfilename:%s /nsize=%d /nwidth=%d /nheight=%d /nbitsperpixel=%d /ncolors=%d
/nimpcolors=%d",
filename,
bitmap->bitmapinfoheader.biSizeImage,
bitmap->bitmapinfoheader.biWidth,
bitmap->bitmapinfoheader.biHeight,
bitmap->bitmapinfoheader.biBitCount,
bitmap->bitmapinfoheader.biClrUsed,
bitmap->bitmapinfoheader.biClrImportant);
#endif
// close the file
_lclose(file_handle);
// flip the bitmap
Flip_Bitmap(bitmap->buffer,
bitmap->bitmapinfoheader.biWidth*(bitmap->bitmapinfoheader.biBitCount/8),
bitmap->bitmapinfoheader.biHeight);
// return success
return(1);
} // end Load_Bitmap_File
///////////////////////////////////////////////////////////
Unload_Bitmap_File( BITMAP_FILE_PTR bitmap )用来释放位图
int Unload_Bitmap_File(BITMAP_FILE_PTR bitmap)
{
// this function releases all memory associated with "bitmap"
if (bitmap->buffer)
{
// release memory
free(bitmap->buffer);
// reset pointer
bitmap->buffer = NULL;
} // end if
// return success
return(1);
} // end Unload_Bitmap_File
windows游戏编程大师笔记13:创建离屏表面
创建离屏表面的一个目的就是用来存储位图,以供dx使用。
创建离屏表面和创建其表基本一样,除了下面几点。
1 必须将DDSURFACEDESC2.dwFlags 设置为 (DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT )
2 必须在DDSURFACEDESC2.dwWidth 和 DDSURFACEDESC2.dwHeight 中设置所请求的表面尺寸。
3 必须将 DDSURFACEDESC2.ddsCaps.dwCaps 设置为 DDSCAPS_OFFSCREENPLAIN | memory_flags ,
其中memory_flags决定在那儿创建表面。
LPDIRECTDRAWSURFACE7 DDraw_Create_Surface(int width, int height, int mem_flags, int
color_key = 0)
{
// this function creates an offscreen plain surface
DDSURFACEDESC2 ddsd; // working description
LPDIRECTDRAWSURFACE7 lpdds; // temporary surface
// set to access caps, width, and height
memset(&ddsd,0,sizeof(ddsd));
ddsd.dwSize = sizeof(ddsd);
ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT;
// set dimensions of the new bitmap surface
ddsd.dwWidth = width;
ddsd.dwHeight = height;
// set surface to offscreen plain
ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN | mem_flags;
// create the surface
if (FAILED(lpdd->CreateSurface(&ddsd,&lpdds,NULL)))
return(NULL);
// test if user wants a color key
if (color_key >= 0)
{
// set color key to color 0
DDCOLORKEY color_key; // used to set color key
color_key.dwColorSpaceLowValue = 0;
color_key.dwColorSpaceHighValue = 0;
// now set the color key for source blitting
lpdds->SetColorKey(DDCKEY_SRCBLT, &color_key);
} // end if
// return surface
return(lpdds);
} // end DDraw_Create_Surface
windows游戏编程大师笔记14:色彩键(blt时使用)
色彩键分为 源色彩键 和 目标色彩键
源色彩键在 源图像上设置 之后 blit时 不拷贝设置颜色的像素。
目标色彩键 在目标表面设置 之后blit时 只要是设置了的颜色 就不能被拷贝上去。
色彩键的数据结构
typedef struct _DDCOLORKEY {
DWORD dwColorSpaceLowValue;
DWORD dwColorSpaceHighValue;
} DDCOLORKEY, FAR* LPDDCOLORKEY;
该结构 8位色的设置方法
eg:将256中的10至20作为透明色
DDCOLORKEY key;
key.dwColorSpaceLowValue = 10;
key.dwColorSpaceHighValue = 20;
16位色的设置方法
eg:将纯蓝色设为透明色
DDCOLORKEY key;
key.dwColorSpaceLowValue = _RGB16BIT565(0,0,32);
key.dwColorSpaceHighValue = _RGB16BIT565(0,0,32);
使用色彩键的方法
1 在建表面时设置 或者之后用 SetColorKey()设置
2 像上面一样填充色彩键结构
3 在ddsd 结构的dwFlags字段中 加一个 DDSD_CKSRCBLT 标志
这里写出用函数设置的例子
DDCOLORKEY color_key;
color_key.dwColorSpaceLowValue = low_value;
color_key.dwColorSpaceHighValue = high_value;
lpdds->SetColorKey( DDCKEY_SRCBLT | DDCKEY_COLORSPACE, &color_key);
windows游戏编程大师笔记15:位图缩放,旋转
用dx缩放很简单。只要改变目标矩形的尺寸就可以在blt时进行缩放。
eg:
dest_rect.left = x;
dest_rect.top = x;
dest_rect.right = x+m-1;//在这里改变相应的尺寸
dest_rect.bottom = y+n-1;//
source_rect.left = 0 ;
source_rect.top = 0;
source_rect.right = 64 -1;
source_rect.bottom = 64-1;
if ( FAILED( lpddsback->Blt( &dest_rect, lpdds_image,&source_rect, (DDBLT_WAIT |
DDBLT_KEYSRC),NULL)))
return 0;
dx旋转
eg:
DDBLTFX ddbltfx;
DDRAW_INIT_STRUCT (ddbltfx);
ddbltfx.dwRotationAngle = angle;
if ( FAILED( lpddsback->Blt( &dest_rect, lpdds_image,&source_rect, (DDBLT_WAIT |
DDBLT_KEYSRC | DDBLT_ROTATIONANGLE),&ddbltfx)))
return 0;
windows游戏编程大师笔记16:directdraw一些注意
GDI 和dx 联用
很简单,只需在dx中找到一个兼容dc 就可以像标准调用GetDc()那样使用。
eg:
LPDIRECTDRAWSURFACE7 lpdds;
HDC xdc;
if( FAILED( lpdds->GetDC( &xdc))) //获得dc
{/* error*/}
//***************************
//这两个if语句之间做想做的事
//***************************
if (FAILED(lpdds->ReleaseDC(xdc))) //释放
{/* error*/}
如果表面被锁定 Getdc就不起作用,因为GetDC()也要求锁定表面,任何时候,GDI和DX只有一个能
写表面,而不是同时。
一个打印 GDI 文本的函数。
int Draw_Text_GDI(char *text, int x,int y,
COLORREF color, LPDIRECTDRAWSURFACE7 lpdds)
{
// this function draws the sent text on the sent surface
// using color index as the color in the palette
HDC xdc; // the working dc
// get the dc from surface
if (FAILED(lpdds->GetDC(&xdc)))
return(0);
// set the colors for the text up
SetTextColor(xdc,color);
// set background mode to transparent so black isn't copied
SetBkMode(xdc, TRANSPARENT);
// draw the text a
TextOut(xdc,x,y,text,strlen(text));
// release the dc
lpdds->ReleaseDC(xdc);
// return success
return(1);
} // end Draw_Text_GDI
使用方法如下
Draw_Text_GDI ("aaaaaaaaaaa",102,204,RGB(255,0,128),lpddsprimary);
DirectDraw的一些有用函数
查看HAL和HEL结构
HRESULT GetCaps (LPDDCAPS lpDDDriverCaps,
LPDDCAPS lpDDHELCaps);
eg:
DDCAPS hel_caps,hal_caps;
DDRAW_INIT_STRUCT (hel_caps);
DDRAW_INIT_STRUCT (hal_caps);
if (FAILED(lpdd->GetCaps(&hal_caps,&hel_caps)));
return (0);
之后就可以在 hal_caps和hel_casp里面找相关信息。
至于它们的结构可以看这里 doc.51windows.net/Directx9_SDK/
在窗口模式使用dx要注意的
1 协作模式 http://hi.baidu.com/wxvp/blog/item/4574fb3173daac10eac4af35.html
2 用CreateWindow函数时 使用WS_OVERLAPPED | WS_OVERLAPPED-WINDOW标记
3 用GetWindowRect()来获取整个窗口(包括控件和边框)的坐标.在收到每次WM_MOVE消息的时候要调
用一次该函数以便确定新的坐标.
4 取像素格式
eg:
int pixe_format =0;
DDPIXELFORMAT ddpixelformat;
DDRAW_INIT_STRUCT(ddpixelformat);
lpddsprimary->GetPixelFormat(&ddpixelformat);
pixel_format = ddpixelformat.dwRGBBitCount;
5 查找实际客户区大小(除开边框和控件栏) p318
6 dx在窗口下的裁剪
windows游戏编程大师笔记17:光栅化 及 画线算法
算法什么是光栅化: 如果 我们在纸上画出 a点到b点的矢量 我们几乎可以把他分成很小很小的
一段(接近无穷小),但是这个矢量线段要是显示在 显示器上 时 由于显示器受分辨率的影响,矢量线
段就不能完全实际的由无限小的点组成 ,这样在显示器上画出来的矢量线段 和 纸上的 线段就不是
完全一样的. 而 把像素以最接近实际直线的位置 在显示器上从 点 a 到 点 b 来填充 (即画线)
这个过程就叫 光栅化
画线算法
hi.baidu.com/wxvp/blog/item/a9a1ef227a4a82ac4623e8a5.html
关于斜率m: = dy /dx 引用书上的一句话: 斜率为5.0 表示 如果x坐标增加1.0个单位,那么 y坐标
就相应增加 5.0个单位.
windows游戏编程大师笔记18:裁剪直线
这和之前讲的 像素在视口中的裁剪不一样 是直线的裁剪
我觉得图像都是由线段构成的,哪怕是曲线在一定程度上讲也是由一些小的直线组成的.所以这个直
线裁剪也是有针对性的.
对于直线在视口中裁剪 就有4种情况 a 完全在视口之外,这就不用处理
b 完全在视口之内,也不用裁剪,直接画出来
c 直线一个端点在裁剪区外,要进行判断裁剪.
d 直线两个端点在裁剪区外,中间一部分在裁剪区内,也要判断裁剪
视口是一个矩形,判断直线裁剪就是判断直线与视口的交点后作出的取舍.而判断直线相交的公式,初
中都学过
1截距式 y = m*x + b
2点斜式 (y-y0) = m*(x-x0)
3两点式 (y-y0) = (x-x0) * (y1-y0)/(x1-x0)
4一般式 a*x +b*y = c
5参数式 p = p0 + v*t
cohen-sutherland 算法 一种直线裁剪算法
下面是它书上的一个函数代码
int Clip_Line(int &x1,int &y1,int &x2, int &y2)
{
// this function clips the sent line using the globally defined clipping
// region
// internal clipping codes
#define CLIP_CODE_C 0x0000 //视口中间区域
#define CLIP_CODE_N 0x0008 //视口北(上面)
#define CLIP_CODE_S 0x0004 //视口南(下面)
#define CLIP_CODE_E 0x0002 //视口东
#define CLIP_CODE_W 0x0001 //视口西
#define CLIP_CODE_NE 0x000a //视口东北
#define CLIP_CODE_SE 0x0006 //视口东南
#define CLIP_CODE_NW 0x0009 //视口西北
#define CLIP_CODE_SW 0x0005 //视口西南
int xc1=x1,
yc1=y1,
xc2=x2,
yc2=y2;
int p1_code=0, //2个状态变量,保存线段2个端点在那个区域
p2_code=0;
// determine codes for p1 and p2
//如果y1小于视口最小的y轴坐标,就把"视口北"这个状态或运算后赋给p1_code,
if (y1 < min_clip_y)
p1_code|=CLIP_CODE_N;
else
if (y1 > max_clip_y)
p1_code|=CLIP_CODE_S;
if (x1 < min_clip_x)
p1_code|=CLIP_CODE_W;
else
if (x1 > max_clip_x)
p1_code|=CLIP_CODE_E;
//经过上面这些if语句判断后,p1_code的状态被 "或"运算多次后,最后的值可能是这8个状态中的一
个,下面的 p2_code 同样道理
if (y2 < min_clip_y)
p2_code|=CLIP_CODE_N;
else
if (y2 > max_clip_y)
p2_code|=CLIP_CODE_S;
if (x2 < min_clip_x)
p2_code|=CLIP_CODE_W;
else
if (x2 > max_clip_x)
p2_code|=CLIP_CODE_E;
// try and trivially reject
if ((p1_code & p2_code)) //如果线段在视口外 就直接返回
return(0);
// test for totally visible, if so leave points untouched
if (p1_code==0 && p2_code==0) //如果线段在视口内 就直接返回 不用裁剪
return(1);
// determine end clip point for p1
switch(p1_code)
{
case CLIP_CODE_C: break;
case CLIP_CODE_N:
{
yc1 = min_clip_y;
xc1 = x1 + 0.5+(min_clip_y-y1)*(x2-x1)/(y2-y1); //用两点式变换出来的,其中那个0.5是为了
防止误差加上的
} break;
case CLIP_CODE_S:
{
yc1 = max_clip_y;
xc1 = x1 + 0.5+(max_clip_y-y1)*(x2-x1)/(y2-y1);
} break;
case CLIP_CODE_W:
{
xc1 = min_clip_x;
yc1 = y1 + 0.5+(min_clip_x-x1)*(y2-y1)/(x2-x1);
} break;
case CLIP_CODE_E:
{
xc1 = max_clip_x;
yc1 = y1 + 0.5+(max_clip_x-x1)*(y2-y1)/(x2-x1);
} break;
// these cases are more complex, must compute 2 intersections
case CLIP_CODE_NE:
{
// north hline intersection
yc1 = min_clip_y;
xc1 = x1 + 0.5+(min_clip_y-y1)*(x2-x1)/(y2-y1);
// test if intersection is valid, of so then done, else compute next
if (xc1 < min_clip_x || xc1 > max_clip_x)
{
// east vline intersection
xc1 = max_clip_x;
yc1 = y1 + 0.5+(max_clip_x-x1)*(y2-y1)/(x2-x1);
} // end if
} break;
case CLIP_CODE_SE:
{
// south hline intersection
yc1 = max_clip_y;
xc1 = x1 + 0.5+(max_clip_y-y1)*(x2-x1)/(y2-y1);
// test if intersection is valid, of so then done, else compute next
if (xc1 < min_clip_x || xc1 > max_clip_x)
{
// east vline intersection
xc1 = max_clip_x;
yc1 = y1 + 0.5+(max_clip_x-x1)*(y2-y1)/(x2-x1);
} // end if
} break;
case CLIP_CODE_NW:
{
// north hline intersection
yc1 = min_clip_y;
xc1 = x1 + 0.5+(min_clip_y-y1)*(x2-x1)/(y2-y1);
// test if intersection is valid, of so then done, else compute next
if (xc1 < min_clip_x || xc1 > max_clip_x)
{
xc1 = min_clip_x;
yc1 = y1 + 0.5+(min_clip_x-x1)*(y2-y1)/(x2-x1);
} // end if
} break;
case CLIP_CODE_SW:
{
// south hline intersection
yc1 = max_clip_y;
xc1 = x1 + 0.5+(max_clip_y-y1)*(x2-x1)/(y2-y1);
// test if intersection is valid, of so then done, else compute next
if (xc1 < min_clip_x || xc1 > max_clip_x)
{
xc1 = min_clip_x;
yc1 = y1 + 0.5+(min_clip_x-x1)*(y2-y1)/(x2-x1);
} // end if
} break;
default:break;
} // end switch
// determine clip point for p2
switch(p2_code)
{
case CLIP_CODE_C: break;
case CLIP_CODE_N:
{
yc2 = min_clip_y;
xc2 = x2 + (min_clip_y-y2)*(x1-x2)/(y1-y2);
} break;
case CLIP_CODE_S:
{
yc2 = max_clip_y;
xc2 = x2 + (max_clip_y-y2)*(x1-x2)/(y1-y2);
} break;
case CLIP_CODE_W:
{
xc2 = min_clip_x;
yc2 = y2 + (min_clip_x-x2)*(y1-y2)/(x1-x2);
} break;
case CLIP_CODE_E:
{
xc2 = max_clip_x;
yc2 = y2 + (max_clip_x-x2)*(y1-y2)/(x1-x2);
} break;
// these cases are more complex, must compute 2 intersections
case CLIP_CODE_NE:
{
// north hline intersection
yc2 = min_clip_y;
xc2 = x2 + 0.5+(min_clip_y-y2)*(x1-x2)/(y1-y2);
// test if intersection is valid, of so then done, else compute next
if (xc2 < min_clip_x || xc2 > max_clip_x)
{
// east vline intersection
xc2 = max_clip_x;
yc2 = y2 + 0.5+(max_clip_x-x2)*(y1-y2)/(x1-x2);
} // end if
} break;
case CLIP_CODE_SE:
{
// south hline intersection
yc2 = max_clip_y;
xc2 = x2 + 0.5+(max_clip_y-y2)*(x1-x2)/(y1-y2);
// test if intersection is valid, of so then done, else compute next
if (xc2 < min_clip_x || xc2 > max_clip_x)
{
// east vline intersection
xc2 = max_clip_x;
yc2 = y2 + 0.5+(max_clip_x-x2)*(y1-y2)/(x1-x2);
} // end if
} break;
case CLIP_CODE_NW:
{
// north hline intersection
yc2 = min_clip_y;
xc2 = x2 + 0.5+(min_clip_y-y2)*(x1-x2)/(y1-y2);
// test if intersection is valid, of so then done, else compute next
if (xc2 < min_clip_x || xc2 > max_clip_x)
{
xc2 = min_clip_x;
yc2 = y2 + 0.5+(min_clip_x-x2)*(y1-y2)/(x1-x2);
} // end if
} break;
case CLIP_CODE_SW:
{
// south hline intersection
yc2 = max_clip_y;
xc2 = x2 + 0.5+(max_clip_y-y2)*(x1-x2)/(y1-y2);
// test if intersection is valid, of so then done, else compute next
if (xc2 < min_clip_x || xc2 > max_clip_x)
{
xc2 = min_clip_x;
yc2 = y2 + 0.5+(min_clip_x-x2)*(y1-y2)/(x1-x2);
} // end if
} break;
default:break;
} // end switch
// do bounds check
if ((xc1 < min_clip_x) || (xc1 > max_clip_x) ||
(yc1 < min_clip_y) || (yc1 > max_clip_y) ||
(xc2 < min_clip_x) || (xc2 > max_clip_x) ||
(yc2 < min_clip_y) || (yc2 > max_clip_y) )
{
return(0);
} // end if
// store vars back
x1 = xc1; //最后返回被裁剪好的端点坐标
y1 = yc1;
x2 = xc2;
y2 = yc2;
return(1);
} // end Clip_Line
windows游戏编程大师笔记19:多边形结构
前面有画直线,现在就用画直线函数来画多边形.
这是一个书上定义的多边形数据结构
typedef struct POLYGON2D_TYP
{
int state; //多边形状态
int num_verts; //顶点数目
int x0,y0; //多边形中心位置
int xv,yv; //初始化速度
DWORD color; //
VERTEX2DI *vlist;//顶点列表地址
} POLYGON2D, *POLYGON2D_PTY
其中VERTEX2DI 结构如下
typedef struct VERTEX2DI_TYP
{
int x,y; //顶点位置
} VERTEX2DI ,*VERTEX2DI_PTR
至于绘制多边形函数很简单,只要安顶点顺序把各个顶点用画线函数连接起来就可以了
下面是书上的函数 其中 UCHAR *vbuffer, int lpitch是画线函数需要的显示表面地址和内存步长
int Draw_Polygon2D(POLYGON2D_PTR poly, UCHAR *vbuffer, int lpitch)
{
// this function draws a POLYGON2D based on
// test if the polygon is visible
if (poly->state)
{
// loop thru and draw a line from vertices 1 to n
for (int index=0; index < poly->num_verts-1; index++)
{
// draw line from ith to ith+1 vertex
Draw_Clip_Line(poly->vlist[index].x+poly->x0,
poly->vlist[index].y+poly->y0,
poly->vlist[index+1].x+poly->x0,
poly->vlist[index+1].y+poly->y0,
poly->color,
vbuffer, lpitch);
} // end for
// now close up polygon
// draw line from last vertex to 0th
Draw_Clip_Line(poly->vlist[0].x+poly->x0,
poly->vlist[0].y+poly->y0,
poly->vlist[index].x+poly->x0,
poly->vlist[index].y+poly->y0,
poly->color,
vbuffer, lpitch);
// return success
return(1);
} // end if
else
return(0);
} // end Draw_Polygon2D
windows游戏编程大师笔记20:多边形的平移,旋转,缩放
平移,旋转,缩放,这是肯定要遇到的问题
先是平移
对于点 (x,y) 有 xt = x + dx
yt = y + dy
对于多边形:若多边形有中心坐标而且其它点有对应中心的相对坐标,那么就只做中心的平移. 如果
没有中心坐标就要每个点都要进行平移.
再是旋转
这个要复杂一些,书上有用三角公式的推导过程p354,因为不能画图,我就直接写结果
xr = x*cos(a) + y*sin(a)
yr = x*sin(a) + y*cos(a)
其中xr,yr是旋转后的坐标,a是旋转的角度
用三角函数的时候要注意 参数是 弧度 而不是 角度
角度 弧度 弧度(数字表示)
360 2π 6.28
180 π 3.14159....
90 π/2 1.57
57.295 π/π 1.0
1 π/180 0.0175
//下面这段代码是把0到359度的正弦与余弦值放到2个数组里方便以后调用
float cos_look[360];
float sin_look[360];
for (int ang = 0; ang < 360; ang++)
{
// convert ang to radians
float theta = (float)ang*PI/(float)180;
// insert next entry into table
cos_look[ang] = cos(theta);
sin_look[ang] = sin(theta);
}
////////////////////////////////////////////////
//下面是旋转函数
int Rotate_Polygon2D(POLYGON2D_PTR poly, int theta)
{
// this function rotates the local coordinates of the polygon
// test for valid pointer
if (!poly)
return(0);
// loop and rotate each point, very crude, no lookup!!!
for (int curr_vert = 0; curr_vert < poly->num_verts; curr_vert++)
{
// perform rotation这里用的就是上面推导出来的公式
float xr = (float)poly->vlist[curr_vert].x*cos_look[theta] -
(float)poly->vlist[curr_vert].y*sin_look[theta];
float yr = (float)poly->vlist[curr_vert].x*sin_look[theta] +
(float)poly->vlist[curr_vert].y*cos_look[theta];
// store result back
poly->vlist[curr_vert].x = xr;
poly->vlist[curr_vert].y = yr;
} // end for curr_vert
// return success
return(1);
}
//最后是缩放
xs = s*x
ys = s*y
s是缩放的倍数.
windows游戏编程大师笔记21:关于矩阵,以及用矩阵来平移,旋转,缩放2009年09月23日 星期三
11:55没看到这一节前,我从来没想到矩阵及其相关运算在游戏编程中这么重要.关于线性代数我只是
自学过一些,而且已经忘了很多了,看来要重新开始学了.
矩阵的维数
矩阵一般用大写字母表示
如 A = |7 8| 是一个2x2的矩阵
` |3 5|
B = |12 0| 是一个3x2的矩阵
` |0 4|
` |3 7|
这种 m X n 的模式叫做 矩阵的维数 其中m是行数,n是列数
关于方程组的矩阵
例如
3*x + 2*y = 1
4*x - 9*y = 9 这是一个2元一次方程组
系数矩阵 变量矩阵 常量
矩阵
A = |3 2| X = |x|
B = |1|
` |4 -9| |y|
|9|
单位矩阵
主对角线为1其它都为0的方阵 就是单位矩阵
如
K = |1 0|
` |0 1|
P = |1 0 0|
` |0 1 0|
` |0 0 1|
这些都是
但这种是不是呢?
W = |0 0 1|
` |0 1 0|
` |1 0 0| 这个书上没讲 等以后查资料吧
零矩阵
矩阵里都为0的矩阵就是零矩阵
如
Z = | 0 0|
Z2=| 0 0 0|
` | 0 0 0|
` | 0 0 0|
矩阵加法
一般的矩阵加法 是要维数相同的两个矩阵来加减.
例子 A = |0 3| B = |-1 2|
` |0 5| | 0 4|
A + B = |(0-1) (3+2)| = |-1 5|
` |(0+0) (5+4)| |0 9|
矩阵乘法
一般来说两个矩阵相乘要满足这种条件:A矩阵为 m x n B矩阵为 n x r,即A矩阵的列数要等于B矩
阵的行数
例如 3x2 和2x3 再比如 4x5和 5x7
例子
A = |1 2| B = |1 3 5|
` |3 4| |6 0 4|
C = A x B = |(1*1 + 2*6) (1*3 + 2*0) (1*5 + 2*4)| = |13 3 13|
` |(3*1 + 4*6) (3*3 + 4*0) (3*5 + 4*4)| |27 9 31|
矩阵相乘的函数
转的一个函数
void MatrixMutiply(int a[][],int b[][])
{ int a[row_a][col_a];
int b[row_b][col_b];
int c[row_c][col_c];
int i,j,k;
int row_a,col_a,row_b,col_b,row_c,col_c;
for(i=0;i<row_c;i++)
{
for(j=0;j<col_c;j++)
{c[i][j]=0;
for(k=0;k<col_a;k++) //记住i,j,k的取值!!
c[i][j]+=a[i][k]*b[k][j];
printf("%3d ",c[i][j]);
}
printf("/n");
}
}
前面讲了平移,旋转,缩放,现在利用矩阵也可以做这样的变换
将指定的点同变换矩阵相乘就行了. p' =p * M
其中p'是变换后的点 p是之前的点,M是变换矩阵
p的表示方法 [x y 1.0] 最后的那个1.0 是是为了计算方便加上的,
平移的变换矩形
Mt = |1 0 0|
' |0 1 0|
' |dx dy 1|
缩放的变换矩形
Ms = |sx 0 0|
' |0 sy 0|
' |0 0 0|
旋转的变换矩形
Mr = | cosA sinA 0|
' | -sinA cosA 0|
' | 0 0 1|
windows游戏编程大师笔记22:填充三角形2009年10月05日 星期一 10:39这种填充三角形的方法首先
要确定三角形在屏幕上的形状种类,然后用memset函数来填充.
种类分为
1平顶:即上面的2个顶点有相同的y坐标
2平底:即下面的2个顶点有相同的y坐标
3任意:这种可以分解成上面2种.
先说说平底三角的填充算法
这个算法主要思路就是从上面顶点开始从上到下填充x轴的平行线,那怎么找平行线呢??
找平行线的方法:首先来看 dx/dy 这个意思就是y轴每一个单位的变换量引起x轴相应的变换,例如
dx/dy=2 的意思就是y轴每增加1个单位 那么x轴就相应增加2个单位.而dx/dy正好是斜率的倒数.
1算出2个斜率的倒数 dxy_left 和 dxy_right
2以顶点(x0,y0)为起点, xs=xe=x0 ,y=y0 ,xs(左边的x坐标)和xe(右边的x坐标)
3y轴增加1个单位 那么2个边上的x坐标xs和xe就相应增加 dxy_left 和 dxy_right
即 xs += dxy_left; xe += dxy_right;
4,从上面得到了 xs和xe之后 就从 (xs,y) 到 (xe ,y)画线,这里的y是y轴上增加1个单位后的
量
5,重复3到4的步骤直到平底的两个顶点画完.
书上的算法
float dxy_left = (x2-x0)/(y2-y0); //算出2个斜边的斜率的倒数
float dxy_right = (x1-x0)/(y1-y0);
float xs= x0;
floaat xe= x0;
for (int y= y0; y<y1; y++)
{
Draw_line( (int)xs, (int)xe, y, c); //画线
xs += dxy_left; //y轴每增加1 xs和xe就增加相应的dxy_left,dxy_right
xe += dxy_right;
}
平顶三角的原理基本和这个一样.
windows游戏编程大师笔记23:填充凸多边形2010年01月15日 星期五 23:55注意这里是凸多边形
填充(或者叫光栅化)凸多边形,书上介绍了2种方法
第一种方法是把凸多边形分解成许多三角形,然后用之前讲过的填充三角形的方法递归完成.这种方
法很好理解,算法也不难,就不多说了
第二种是直接对一个多边形光栅化.(这个函数需要传入一个各顶点坐标按顺时针排序的多边形),
这里给个"天之痕"博客的网址要是看不动下面的注解,可以看他的
http://blog.csdn.net/lingang_/archive/2008/09/06/2892829.aspx 这个函数400多行,感觉有点
长
其中POLYGON2D_PTR 是个多边形结构,我之前应该写过.
*vbuffer是显示表面地址指针
mempitch是内存步长
void Draw_Filled_Polygon2D(POLYGON2D_PTR poly, UCHAR *vbuffer, int mempitch)
{
// this function draws a general n sided polygon
int ydiff1, ydiff2, // difference between starting x and ending x
xdiff1, xdiff2, // difference between starting y and ending y
start, // starting offset of line between edges
length, // distance from edge 1 to edge 2
errorterm1, errorterm2, // error terms for edges 1 & 2
offset1, offset2, // offset of current pixel in edges 1 & 2
count1, count2, // increment count for edges 1 & 2 因为是从上往下画,所以当每画
完一条边
//count1 或者count2 都会变成0 然后重新计算它们的值,而新的值就负责下 一条边
xunit1, xunit2; // unit to advance x offset for edges 1 & 2
// initialize count of number of edges drawn:边数
int edgecount = poly->num_verts-1;//为什么减1
// determine which vertex is at top of polygon: 确定哪个顶点在最上面
int firstvert=0; // start by assuming vertex 0 is at top 开始的顶点
int min_y=poly->vlist[0].y; // find y coordinate of vertex 0
for (int index=1; index < poly->num_verts; index++)
{
// Search thru vertices
if ((poly->vlist[index].y) < min_y)
{
// is another vertex higher?
firstvert=index; //把firstvert赋值成y值最小的那个
min_y=poly->vlist[index].y;
} // end if
} // end for index
// finding starting and ending vertices of first two edges:找到最上面顶点相关联的两条边
//即最上面顶点相关的顺时针和逆时针相连的两条边
int startvert1=firstvert; // get starting vertex of edge 1
int startvert2=firstvert; // get starting vertex of edge 2
int xstart1=poly->vlist[startvert1].x+poly->x0;
int ystart1=poly->vlist[startvert1].y+poly->y0;
int xstart2=poly->vlist[startvert2].x+poly->x0;
int ystart2=poly->vlist[startvert2].y+poly->y0;
int endvert1=startvert1-1; // get ending vertex of edge 1
if (endvert1 < 0)
endvert1=poly->num_verts-1; // check for wrap
int xend1=poly->vlist[endvert1].x+poly->x0; // get x & y coordinates
int yend1=poly->vlist[endvert1].y+poly->y0; // of ending vertices
int endvert2=startvert2+1; // get ending vertex of edge 2
if (endvert2==(poly->num_verts))
endvert2=0; // Check for wrap
int xend2=poly->vlist[endvert2].x+poly->x0; // get x & y coordinates
int yend2=poly->vlist[endvert2].y+poly->y0; // of ending vertices
// draw the polygon:
//开始画多边形了
while (edgecount>0)
{
// continue drawing until all edges drawn
offset1=mempitch*ystart1+xstart1; // offset of edge 1 左边缘偏移量
offset2=mempitch*ystart2+xstart2; // offset of edge 2 右边缘偏移量 其实第一次循环开
始时 offset1和2都是一样的,就是最上面的顶点.
// initialize error terms 初始化误差额,这种错误差用的很巧妙,让我想起以前一个画线算法里
面也有类似的
// for edges 1 & 2
errorterm1=0;
errorterm2=0;
// get absolute value of
if ((ydiff1=yend1-ystart1) < 0)
ydiff1=-ydiff1;
// x & y lengths of edges
if ((ydiff2=yend2-ystart2) < 0)
ydiff2=-ydiff2;
if ((xdiff1=xend1-xstart1) < 0)
{
// get value of length
xunit1=-1; // calculate X increment 计算x的增值 xunit1就是x的前进单
位
xdiff1=-xdiff1;
} // end if
else
{
xunit1=1;
} // end else
if ((xdiff2=xend2-xstart2) < 0)
{
// Get value of length
xunit2=-1; // calculate X increment 同上一个
xdiff2=-xdiff2;
} // end else
else
{
xunit2=1;
} // end else
// choose which of four routines to use
if (xdiff1 > ydiff1)
{ //dx1>dy1 就是斜边1的斜率绝对值小于1的情况
// if x length of edge 1 is greater than y length
if (xdiff2 > ydiff2)
{ //dx2 > dy2 就是斜边2斜率绝对值小于1的情况 后面还有几种斜率判断 都和这一个意思
// if X length of edge 2 is greater than y length
// increment edge 1 on X and edge 2 on X:
count1=xdiff1; // count for x increment on edge 1
count2=xdiff2; // count for x increment on edge 2
while (count1 && count2)
{
// continue drawing until one edge is done
// calculate edge 1:
//这个while是画左边缘的一个点
while ((errorterm1 < xdiff1) && (count1 > 0))
{
// finished w/edge 1?
if (count1--)
{
// count down on edge 1
offset1+=xunit1; // increment pixel offset
xstart1+=xunit1;
} // end if
errorterm1+=ydiff1; // increment error term
if (errorterm1 < xdiff1)
{ // if not more than XDIFF
vbuffer[offset1]=(UCHAR)poly->color; // ...plot a pixel
} // end if
} // end while
errorterm1-=xdiff1; // if time to increment X, restore error term
// calculate edge 2:
//这个while是画右边缘的一个点
while ((errorterm2 < xdiff2) && (count2 > 0))
{
// finished w/edge 2?
if (count2--)
{
// count down on edge 2
offset2+=xunit2; // increment pixel offset
xstart2+=xunit2;
} // end if
errorterm2+=ydiff2; // increment error term
if (errorterm2 < xdiff2)
{ // if not more than XDIFF
vbuffer[offset2]=(UCHAR)poly->color; // ...plot a pixel
} // end if
} // end while
errorterm2-=xdiff2; // if time to increment X, restore error term
// draw line from edge 1 to edge 2:
//计算上面画的左右边缘两个点之间的长度
length=offset2-offset1; // determine length of horizontal line
if (length < 0)
{ // if negative...
length=-length; // make it positive
start=offset2; // and set START to edge 2
} // end if
else
start=offset1; // else set START to edge 1
//这个for语句是连接左右两个点,画线 后面的while和for基本和这类似
for (int index=start; index < start+length+1; index++)
{ // From edge to edge...
vbuffer[index]=(UCHAR)poly->color; // ...draw the line
} // end for index
offset1+=mempitch; // advance edge 1 offset to next line
ystart1++;
offset2+=mempitch; // advance edge 2 offset to next line
ystart2++;
} // end if
} // end if
else
{
// increment edge 1 on X and edge 2 on Y:
count1=xdiff1; // count for X increment on edge 1
count2=ydiff2; // count for Y increment on edge 2
while (count1 && count2)
{ // continue drawing until one edge is done
// calculate edge 1:
while ((errorterm1 < xdiff1) && (count1 > 0))
{ // finished w/edge 1?
if (count1--)
{
// count down on edge 1
offset1+=xunit1; // increment pixel offset
xstart1+=xunit1;
} // end if
errorterm1+=ydiff1; // increment error term
if (errorterm1 < xdiff1)
{ // If not more than XDIFF
vbuffer[offset1]=(UCHAR)poly->color; // ...plot a pixel
} // end if
} // end while
errorterm1-=xdiff1; // If time to increment X, restore error term
// calculate edge 2:
errorterm2+=xdiff2; // increment error term
if (errorterm2 >= ydiff2)
{ // if time to increment Y...
errorterm2-=ydiff2; // ...restore error term
offset2+=xunit2; // ...and advance offset to next pixel
xstart2+=xunit2;
} // end if
count2--;
// draw line from edge 1 to edge 2:
length=offset2-offset1; // determine length of horizontal line
if (length < 0)
{ // if negative...
length=-length; // ...make it positive
start=offset2; // and set START to edge 2
} // end if
else
start=offset1; // else set START to edge 1
for (int index=start; index < start+length+1; index++) // from edge to edge
{
vbuffer[index]=(UCHAR)poly->color; // ...draw the line
} // end for index
offset1+=mempitch; // advance edge 1 offset to next line
ystart1++;
offset2+=mempitch; // advance edge 2 offset to next line
ystart2++;
} // end while
} // end if
} // end if
else
{
if (xdiff2 > ydiff2)
{
// increment edge 1 on Y and edge 2 on X:
count1=ydiff1; // count for Y increment on edge 1
count2=xdiff2; // count for X increment on edge 2
while(count1 && count2)
{ // continue drawing until one edge is done
// calculate edge 1:
errorterm1+=xdiff1; // Increment error term
if (errorterm1 >= ydiff1)
{ // if time to increment Y...
errorterm1-=ydiff1; // ...restore error term
offset1+=xunit1; // ...and advance offset to next pixel
xstart1+=xunit1;
} // end if
count1--;
// Calculate edge 2:
while ((errorterm2 < xdiff2) && (count2 > 0))
{ // finished w/edge 1?
if (count2--)
{ // count down on edge 2
offset2+=xunit2; // increment pixel offset
xstart2+=xunit2;
} // end if
errorterm2+=ydiff2; // increment error term
if (errorterm2 < xdiff2)
{ // if not more than XDIFF
vbuffer[offset2]=(UCHAR)poly->color; // ...plot a pixel
} // end if
} // end while
errorterm2-=xdiff2; // if time to increment X, restore error term
// draw line from edge 1 to edge 2:
length=offset2-offset1; // determine length of horizontal line
if (length < 0)
{ // if negative...
length=-length; // ...make it positive
start=offset2; // and set START to edge 2
} // end if
else
start=offset1; // else set START to edge 1
for (int index=start; index < start+length+1; index++) // from edge to edge...
{
vbuffer[index]=(UCHAR)poly->color; // ...draw the line
} // end for index
offset1+=mempitch; // advance edge 1 offset to next line
ystart1++;
offset2+=mempitch; // advance edge 2 offset to next line
ystart2++;
} // end if
} // end if
else
{
// increment edge 1 on Y and edge 2 on Y:
count1=ydiff1; // count for Y increment on edge 1
count2=ydiff2; // count for Y increment on edge 2
while(count1 && count2)
{
// continue drawing until one edge is done
// calculate edge 1:
errorterm1+=xdiff1; // increment error term
if (errorterm1 >= ydiff1)
{ // if time to increment Y
errorterm1-=ydiff1; // ...restore error term
offset1+=xunit1; // ...and advance offset to next pixel
xstart1+=xunit1;
} // end if
count1--;
// calculate edge 2:
errorterm2+=xdiff2; // increment error term
if (errorterm2 >= ydiff2)
{ // if time to increment Y
errorterm2-=ydiff2; // ...restore error term
offset2+=xunit2; // ...and advance offset to next pixel
xstart2+=xunit2;
} // end if
--count2;
// draw line from edge 1 to edge 2:
length=offset2-offset1; // determine length of horizontal line
if (length < 0)
{
// if negative...
length=-length; // ...make it positive
start=offset2; // and set START to edge 2
} // end if
else
start=offset1; // else set START to edge 1
for (int index=start; index < start+length+1; index++)
{ // from edge to edge
vbuffer[index]=(UCHAR)poly->color; // ...draw the linee
} // end for index
offset1+=mempitch; // advance edge 1 offset to next line
ystart1++;
offset2+=mempitch; // advance edge 2 offset to next line
ystart2++;
} // end while
} // end else
} // end if
// another edge (at least) is complete. Start next edge, if any.
if (!count1)
{ // if edge 1 is complete...判断边是否画完,如果画完就把结束点变为
下一条边的起点.
--edgecount; // decrement the edge count
startvert1=endvert1; // make ending vertex into start vertex
--endvert1; // and get new ending vertex
if (endvert1 < 0)
endvert1=poly->num_verts-1; // check for wrap
xend1=poly->vlist[endvert1].x+poly->x0; // get x & y of new end vertex
yend1=poly->vlist[endvert1].y+poly->y0;
} // end if
if (!count2)
{ // if edge 2 is complete...和上面的if语句差不多值不过上面那个是逆
时针,这个是顺的
--edgecount; // decrement the edge count
startvert2=endvert2; // make ending vertex into start vertex
endvert2++; // and get new ending vertex
if (endvert2==(poly->num_verts))
endvert2=0; // check for wrap
xend2=poly->vlist[endvert2].x+poly->x0; // get x & y of new end vertex
yend2=poly->vlist[endvert2].y+poly->y0;
} // end if
} // end while
} // end Draw_Filled_Polygon2D
windows游戏编程大师笔记24:多边形碰撞2010年01月26日 星期二 17:48这里讲的多边形碰撞检测是
基于之前的多边型结构的.
1边界圆: 以多边形中心为圆心,算出其平均半径.这样就把多边形看成是一个圆.检测两个多边形是
否碰撞只要检测两个圆心之间的距离是否等于两个多边形圆的半径之和就行了.
两点间距离 = (dx^2 + dy^2)^1/2 对于这个式子,为了运算更快要去掉开方,所以书上说要用它
的 麦克劳林级数来获得原函数的近似值.误差为3.5%
函数如下
#define MIN(a,b) ((a<b) ? a:b)
#define MAX(a,b) ((a>b) ? a:b)
int Fast_Distance_2D(int x, int y)
{
x = abs(x);
y = abs(y);
int mn = MIN(x,y);
return (x + y - (mn>>1) - (mn>>2) + (mn>>4);
}
windows游戏编程大师笔记25:用DirectInput来控制输入2010年07月08日 星期四 00:08之前我们键
盘控制输入是用的KEYDOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0),那为什
么现在又要用directinput8呢?因为directinput支持的设备除了键盘鼠标还有游戏杆,手柄等等.而
且在多键同时输入的情况会有优势.好像它还是不进windows消息队列的.
一般游戏的输入在擦除上一帧画面之后
基本上分成9个部分来完成
1创建IDirectInput8接口
p454
例子:
#include"DINPUT.H"
LPDIRECTINPUT8 lpdi =NULL;
if (FAILED(DirectInput8Create ( main_instace, //程序实例句柄
DIRECTINPUT_VERSION,// 版本号,填这个宏定义就可以了
IID_IDirectInput8, //接口版本,一般就是这个
(void**)&lpdi, //接口指针地址
NULL) //一般为NULL
//结束后对应要释放资源
if (lpdi) lpdi->Release();
2查询GUID
查询设备GUID是为了创建设备对象时要调用.
对于键盘和鼠标默认的是 GUID_SysKeyboard ,GUID_SysMouse
其它设备使用IDirectInput8 :: EnumDevices (
DWORD dwDevtype, // 要查询的设备类型
LPDIENUMCALLBACK lpcallback,//回调函数
LPVOID pvRef, //用户设置的数据指针
DWORD dwFlags) // 枚举标志
3创建设备的com对象
用CreateDevice()
例子
IDIRECTINPUTDEVICE8 lpdikey =NULL;
if (FAILED(lpdi->CreateDevice(GUID_SysKeyboard,&lpdikey,NULL)));
其中lpdi是第一步中创建好的.
4设置该设备的协作等级
if (FAILED(lpdike->SetCooperativeLevel(main_window_handle,DISCL_BACKGROUND |
DISCL_NONEXCLUSIVE)))
5设置设备的数据格式
if (FAILED(lpdikey->SetDataFormat(&c_dfDIKeyboard)))
其中c_dfDIKeyboard代表通用键盘格式,还有c_dfDIMouse是鼠标,c_dfDIJoystick是游戏杆
6设置设备的特殊属性
对于键盘来说 这步好像没有
7获得设备
if (FAILED(lpdikey->Acquire()))
8轮询设备
对于键盘也是可选的
9从设备获取数据
有了上面之后可以将键盘各个键位的一个瞬间状态存在一个数组里面
typedef _DIKEYSTATE UCHAR[256]
例子
HRESULT result;
while (result = lpdikey->GetDeviceState(sizeof(_DIKEYSTATE),(LPVOID)keystate) ==
DIERR_INPUTLOST)
{
if (FAILED(lpdikey->Acquire()))
{ break;}
}
之所以这里用while 循环反复去读 是因为有可能丢失设备,虽然这几率很小,比如你突然拔掉键盘.
用完之后就是归还,释放等等
if (lpdikey)
lpdikey->Unacquire();//只归还不释放
if (lpdikey)
lpdikey->Release();//释放
if (lpdi)
lpdi->Release();
///////////////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////
鼠标的输入
基本上键盘的一样,除了数据格式,还有就是用的是相对坐标(相对于上一次坐标的位置),不是绝对坐
标.