ddpsctfmon.exe是什么进程程?DDP Service是什么服务?

帖子很冷清,卤煮很失落!求安慰
手机签到经验翻倍!快来扫一扫!
为什么用Photoshop Raw Camera 打开RAW比用DDP偏黄很多?!
1239浏览 / 7回复
最近用Photoshop Raw Camera 打开450D的RAW
感觉比用佳能自带的DDP明显偏黄很多?
虽然Photoshop Raw Camera 有强大的调节功能能把颜色调节至DDP的色彩,
但总感觉不方便啊,而且调节的色彩不是原汁原味的了。
不知道大家有没有这个问题?
上个截图给大家看看(直接抓屏):
这个是PSCR的效果,明显偏黄偏冷很多。
是不是什么设置没有设置好造成的呢???
对&楼主&传说中的摄影天才&说:=========================先弄清白平衡 3800K的含义 再玩RAW&& RAW不是那么好玩的
对&第1楼&tomatoshiloko&说:=========================可是白平衡我选择“原照设置”以后颜色还是没有DDP里面那种鲜润啊,明显偏色了。。。不知道是不是跟PS里面的“色彩设置”有关?
对&第2楼&传说中的摄影天才&说:=========================要相信自己的眼睛
你直接弄个原照设置 还不如JPG出片了……
对&第3楼&tomatoshiloko&说:=========================我曾经设置成 raw+jpg 出片,两个效果raw比jpg 好很多!!!
对&第1楼&tomatoshiloko&说:=========================请问在哪里看到了色温是3800K,看老半天了
对&第1楼&tomatoshiloko&说:=========================看到了,近视眼,不好使,呵呵
PHOTOSHOP处理的图片是比DDP灰,所以,还是原厂的处理软件好
可能感兴趣的板块:
用户名/注册邮箱/注册手机号
其他第三方号登录电子缩略语 第156页==www.ic37.com
&&&技术文章分类:
&&&电子综合工具:
&&您所在的位置:
Distributed Double C Loop Computer Network
分布式双循环计算机网络
Disk to Disk Mirroring
磁盘反射到磁盘
Data Display Monitor
数据显示监视器
Data Distribution Manager
数据分配管理器
Distributed Data Management
分布式数据管理
Device Descriptor Module
设备描述符模块
Data Demand Module
数据需求模块
Disk Direct Memory Access
磁盘直接存储器访问
Digital Data Network
数字数据网
Defense Data Network
国防部数据网(美国)
Distributed Data Network
分布式数据网
Defense Data Network Network Information Center
国防部数据网信息中心(美国)
Data Definition Name
数据定义名称
Dynamic Domain Naming System
动态域名命名系统
Dynamic Domain Naming Service
动态域名命名服务
Dynamic Directory Name Server
动态目录名称服务器
Digital Data Output Conversion Equipment
数字数据输出转换设备
Digital Data Output Conversion Element
数字数据输出转换单元
Digital Data Over Voice
语音类数字数据
Disk Defragmenter Optimization Wizard
磁盘整理优化向导
Distribute Data Passageway
分配数据通路
Data Distribution Path
数据分配路径
OS/2的设备驱动器简介文件格式〖后缀〗
Distributive Data Path
分配词数据路径
Digital Data Processor
数字数据处理器(美国计算机控制公司研制)
Device Diagnostic Program
设备诊断程序
Datagram Delivery Protocol
数据包发送协议
Differential Dynamic Programming
微分动态编程
Distributed Data Processing(-or)
分布式数据处理(器)
Design Data Package
设计数据包
Development Data Package
开发数据包
Digital Data Processing Equipment
数字数据处理设备
Demand Deposit Program Library
请求存放程序库
Distributed Data Processing Management
分布式数据处理管理
Discrimination Data Processing System
数据鉴别处理系统
Data Directed Programming System
数据直接编程系统
Digital Data Processing System
数字数据处理系统
Distributive Document Processing System
分发文件处理系统
Distributed Data Processing System
分布式数据处理系统
Direct Data Processing System
直接数据处理系统
Dial C on C Demand Routing
按需拨号路由选择,拨号联网路由
Disk Dump Restore
磁盘转储恢复〖VM〗
Digital Data Receiver
数字数据接收器
Data Dictionary or Repository
数据词典或数据储藏室
Double Data Rate
双速率,加倍数据速率〖显卡〗
Design Development Record
设计进展记录
Data Direction Register
数据方向寄存器
Digital Data Recorder
数字数据记录器
Dance Dance Revolution
热舞革命,跳舞毯
Detailed Design Review
细部设计评审
&&&&&&&&页次:&&&&&[156]&&&&&&第156页,本页显示记录,共30817条记录分617页显示
客服咨询热线:0
&&&电路图大全当前位置: >>
DirectX技术文章
游戏的结构简介 上一章的结尾,我曾经承诺将在本章教你写一个小的游戏引擎。但在结束上一章,然后我在考虑怎样更好 的完成这个系列教程时,我感到这并不是一个好主意。让我告诉你我的真实想法。 有几次, 我曾经间接或直接的提到你不能象在 DOS 模式下一样编写你的 Windows 程序, 你必须组织好每一 件事。 因为全部的主循环在每一帧至少要被执行一次, 所以你千万不能漏掉对 Windows 发给你的每一个请求的 跟踪。 现在, 你可能对我以前给你的 Demo 代码了解了一些, 但你可能还没有把它扩充为一个大个儿的游戏程序, 或者你还没有开始根据本教程系列编写自己的游戏, 太好了!?现在,在我教你具体怎样编程前,我得出了 一个结论,就是――现在,我们最后拿出一点儿时间来学习一下游戏的结构,看看一个游戏最终是怎样由各个 部分组成的。 本章不同于以往,你将很少看到程序代码,所以,哈哈,如果你对前几章关于 DirectDraw 的部分还不是 很明白, 或者还有一些其它的事不是很清楚,不要紧,因为我们要先暂停一下前面的部分,开始一个新的话 题。你只需要了解一些 Windows 编程的知识,所以看过或了 解第一、二章的内容就可以了。 还有一件事情我要说一下: 对于本章以及后面的一些章节, 我都将以 Terran (地球人) 这个小游戏为蓝 本 讲解,所以你最好下载这个 Demo 来更好的理解我所要讲的。目前的版本一,可能不能满足所有的显示卡,版 本二出来后可能会好些(已经完成)。废话不说 了,让我们开始吧 概览 在开始编程前, 你应该对你的游戏逻辑和具体操作方式有一个详细的方案。 这将保证你在实施代码编辑时, 不会出大的过错。否则,随着代码的编写,你本来清晰的思路会被意料外的问题和更新的创意搞得乱七八糟, 最后使你自己都迷失在自己的代码中了。相信我,我是有惨痛教训的。 你应该从 WinMain()函数着手,它是程序开始的地方。这一点你可能认为理所应当,但是很多人从编 写退出程序,或者从编写冲突检测函数,或者一些其 它的细节处入手,而不是从 WinMain()处开始,这是 一种倒序的编程方法。对于初学者,理想的顺序还是从头到尾的正序方式。你首先应该从主函数开始, 然后 在主函数里调用每一个子函数,以及处理一些其它的事情。这样做,至少有两个理由: 第一, 每一件事情都可以立竿见影。如果你从细节函数开始编写,你很难检测和实时演示这些函数的功 能,因为你的主环境还没有构造好(编写好)。另一方面,如果你按 照从头到尾的正序编写程序,你可以随 时随地检测你编写的结果。先有主干,再一步一步的添枝加叶,完善程序,你的游戏就会从开始走向完成。 第二, 有步骤的完善你的游戏。如果你先从细节函数着手,很有可能在稍后你有了新的想法或又想添加 一点儿功能,于是,你又写了另一个细节函数,最终,乱糟糟的一堆 函数,你都分不清应该先调用哪个,后 调用哪个。所以,最好的办法是,从简单到复杂一步一步的添加,条理清晰。我想你已经理解我的意思了,就 不多说了。 我在 Terran 中所做的是先把主程序分成五个部分, 然后每一个部分按照从头到尾的方式设计。 其中四个 是实际的游戏内容:卷轴引擎,脚本引擎,战斗引擎 和菜单系统。第五部分就是利用 Windows 和 DirectX 使 它们按照我的想法运转起来,这包括初始化、退出和从全屏模式变为 Windows 模式。游戏 的四个部分的每一 个部分都由多种函数完成,我将稍后详细介绍它们,所以你可以清楚的看到每一部分是怎样有机的组合的。希 望真正的给你启迪,并且我将尽量说 得清楚详细,使你更好的理解。 实例:Terran 从 Initialization(初始化)开始,也就是 WinMain()中首先要调用的部分。当然,现在表中的描述 很简单,实际上有好多函数会在初始化阶段被调用。我没有详细的列出所有的函数,原因是那样做 后,你将 根本无法看明白。 ^_^ 你当然已经很熟悉这些 DirectX 和 Windows 的函数――它们建立窗口, 还要用到 DirectX 的接口。还有很多接口我们还没有学习到,但是这对于 本章并不重要,我们将随用随学。这个 Load Game Data (读取游戏数据)部分将从外部文件读取所有的有用信息,包括:游戏中可利用的分类数据,脚本引擎要操作 的数据,图形系统数据,等等。 初始化部分的最后一件事情,调用一个游戏真正开始的脚本。Terran 中,几乎每一部分都是由脚本引擎 操控的。我将稍后详细介绍它。我将会在本系列教程安排一章来专心介绍怎样为你的游戏创建一个基础的,十 分有用的脚本引擎。 初始化完成后,程序进入主循环(Main Loop)。主循环将在你玩儿完游戏或你退出游戏后停止。在表中, 你可以看到每一次循环都经历 4 个基本步骤。 第一步,启动输入设备驱动程序。在 Terran 中,有两种输入设备(也就是操纵游戏的工具),键盘和游 戏棒。它们都具有同样的两种功能,一个是检测当前 的移动状态,一个是检测是否有即时的按键按下。第一 种是持续的按键,例如人物在地图上的移动,第二种检测是否有突发的事件,例如进入了菜单选项。 第二步,主循环装载音乐平台。基本上,载入音乐平台所做的就是在游戏进程没有结束前,播放音乐,然 后根据不同的场景,选择相应的音乐。不用担心具体的细节――我们将会对音乐部分有一个专题讲解。 第三步是最重要的一步:主循环在此被分成了五个部分,地图部分、菜单系统、战斗系统、脚本和结束 部分。字面上你可以大概的了解每一部分的功能。在图标中 你可以看出,只有结束部分(Shutdown)是单独 存在的,没有其它的子系统,一旦它被调用,ok,一切都将结束。其它的四个部分还需要费一点儿口舌。 这四个部分都有自己的子部分,在图上你一眼就能看出来。每一部分【例如:WorldMapMain()】都先完 成该部分的通用功能, 然后就进入分支子部分,完成具体的功能。有些时候,这些子功能通过 if 或 switch 就可以实现,但有些时候必须把子功能做成相应的函数来调用 地图部分(World Map):地图部分很简单。首先,它被相应的脚本调用;然后,基于子功能,或者相应 当前的用户输入,或者忽略;最后,就是屏幕上的人物更新,地图被显示,再根据用户的输入,地图不断的得 到更新。就这些! 菜单系统(Menu System):这部分有一点儿复杂,因为它要掌控每一件事情,包括加载游戏、创建角色, 设置硬件等等。主函数根据菜单的不同功能,把菜单设置成不同的颜 色,然后子菜单根据需要再有自己的子 菜单。菜单通常都通过指针数组来实现。游戏中的子系统通常都是一个整数标识,从而方便数组的索引。就像 下面这样: (*lpfnMenuSubfunctions[substate.current])(); 如果你对上面这一行感到困惑,那么你有 必要复习一下 C 语言的指针函数部分,此时,结合实际的情况, 你可能对指针的理解更深刻一些。每一个子菜单都操控着一个细节。当玩家选择不同的菜单条,就要 有程序 实现其相对应的功能,并保证在当前的帧得到显示。当然,如果有必要显示的话。做到这些,主菜单函数需要 运行当前的脚本,申请(调用)相应的动作,从 而显示相应的画面。如显示当前的金块儿数量,或者是游戏 角色的状态等等。 战斗系统(Battle System):战斗系统和菜单系统紧密的联合在一起。战斗系统是一个半自动系统 (semi-active),就像《最终幻想》系列(如果你还不知道《最 终幻想》这个游戏,我、我、我倒),任何 时候都是弹出菜单系统,来半控制角色进行打斗(因为具体的打斗动作,是由脚本控制的),而战斗系统也同 时控制菜单 系统,因此,战斗系统和菜单系统相辅相成。战斗的逻辑如下: 首先,判断所有的游戏角色是否通敌人的距离足够近。如果是敌人主动靠近游 戏角色,则敌人的 AI(人 工智能)接管,并为敌人做出一个选择;如果是游戏角色主动靠近敌人,并且当前的菜单系统不是战斗模式, 那么,战斗系统菜单启动, 然后由玩家进一步操控游戏角色。如果此时菜单系统被另一个游戏角色操控,那 么把当前要调用的菜单放入队列,等待调用。一旦前一个菜单调用完成,计算机(也 就是你的程序)要检查 该队列,如果不为空,就启动相应的菜单。 以上的任何一种情况,即敌人走近游戏角色或游戏角色走近敌人,一旦成 立,则进入下一个环节――调 用动作队列。每一次战斗逻辑(可能来自战斗系统调用,也可能来自菜单系统的调用)需要判断两件事:如果 有战斗动作正在进行中, 则由脚本控制完成;如果没有战斗动作发生,则检测战斗动作队列,如果队列中有 下一个动作,就交给脚本控制完成。 与普通敌人的战斗 (Regular Fight)和与 Boss(关头人物)的战斗(Boss Fight)不同之处在于,与 Boss 战斗时,游戏角色不能逃跑――你必须血战到底(很残酷哦!),用一个简单的 if 结构就完成了这个设 定。第三个子菜单 是胜利(Victory),它有一点儿不同,它报告战斗的成果,不用有什么逻辑判断,通常都 是由脚本控制这一步。 脚本 (Scripts Only):这是游戏主要部分的最后一个部分了。它很简单:就是运行被调用的脚本,仅 此而已!它通常都不被玩家所觉察,除了在游戏初始化的时候,有的游戏 打出一些初始化序列的字幕。不过, 无论是在游戏过程中正常出现无显示画面的时候,或者是计算机处理后台作业的时候,它始终是工作的。 (无 名英雄) 游戏结束 (Shutdown) 顾名思义, : 就是结束游戏: 它使程序退出主循环, 释放所有在游戏中创建的 DirectX 的对象,释放所有的游戏占用的内存,然后结束游戏。 主循环的最后一步是显示当前的帧, 就是简单的把后缓冲区的内容拷贝到屏幕。 如果是在 Windows 模式下, 还得使用双缓冲区;如果游戏是在全屏模式下,还得需要页面切换。 22:24 | 添加评论 | 固定链接 | 引用通告 (0) | 记录它 | 游戏的结构 DirectDraw 的位图化图形DirectDraw 的位图化图形简介 终 于,你已经掌握了制作一个完整游戏的基础知识了,只不过你现在还只能使用 GDI。今天,我们就学 习使用 DirectX 来执行每一件你以前用 GDI 完成的工 作,以及一些关于 DirectX 其它的东东。具体内容是: 装载(调用)位图,使用位块传输,填充表面,使用剪裁板、颜色键等拷贝位图。 你可以在不了解前一章内容的基础上学习本章,但象素格式是很重要的,我将经常直接或间接的提到它, 所以你至少应该看看上一章关于象素格式的部分!另外,我假设你已经本系列的第一、二、三、四章,并且拥 有一个 DirectX SDK 游戏开发平台。准备好了吗?发动引擎吧,女士们、先生们! 装载位图 不管你信不信,你的确已经知道了把位图装载到 DirectDraw 表面的大部分知识。怎么会这样呢?Well, 在 Windows GDI 下装载位图同在 DirectDraw 下极其相似,只是有一点点不同。轻轻的回忆一下,我们曾经使 用 LoadImage()函数得到位图的句柄,然后 把位图选入到内存设备上下文中,最后利用 BitBlt()函数把 图形从内存设备上下文中拷贝到显示设备上下文中,设备上下文可以用 GetDC()函数得 到。如果这个承担 显示任务的就是 DirectDraw 表面(现在我们就是要用它),我们就可以针对性的得到 DirectDraw 表面的设备 上下文!感谢上 帝,IDirectDrawSurface7 接口提供了一个极其简单的函数来得到这个设备上下文: HRESULT GetDC(HDC FAR *lphDC); 该函数的返回 类型同所有 DirectDraw 函数的返回类型相同。 如果函数调用成功, 参数就是一个 HDC 类型 的设备上下文的指针,很简单吧!本章就是从把一个位图装载 到 DirectDraw 表面讲起的。千万要记住使用完 了表面设备上下文后,你一定要释放它哦!你可能已经想到了,用表面接口函数 ReleaseDC()完 成: HRESULT ReleaseDC(HDC hDC); 你不用回头去看关于 GDI 部分的位图调用,我将把适合于 DirectDraw 的位图调用展现给你。唯一不同的 是:不是直接把设备上下文作为一个参数,而是用一个 DirectDraw 表面指针取代了它,然后函数从表面得到 设备上下文,用它来拷贝图形,最终释放设备上下文。(可能这里我说的有些混乱,但你看一下下面 的程序 代码就都明白了): int LoadBitmapResource(LPDIRECTDRAWSURFACE7 lpdds, int xDest, int yDest, int nResID) { HDC hSrcDC; // source DC - memory device context HDC hDestDC; // destination DC - surface device context HBITMAP // handle to the bitmap resource BITMAP // structure for bitmap info int nHeight, nW // bitmap dimensions // first load the bitmap resource if ((hbitmap = (HBITMAP)LoadImage(hinstance, MAKEINTRESOURCE(nResID), IMAGE_BITMAP, 0, 0, LR_CREATEDIBSECTION)) == NULL) return(FALSE); // create a DC for the bitmap to use if ((hSrcDC = CreateCompatibleDC(NULL)) == NULL) return(FALSE); // select the bitmap into the DC if (SelectObject(hSrcDC, hbitmap) == NULL) { DeleteDC(hSrcDC); return(FALSE); } // get image dimensions if (GetObject(hbitmap, sizeof(BITMAP), &bmp) == 0) { DeleteDC(hSrcDC); return(FALSE); } nWidth = bmp.bmW nHeight = bmp.bmH // retrieve surface DC if (FAILED(lpdds-&GetDC(&hDestDC))) { DeleteDC(hSrcDC); return(FALSE); } // copy image from one DC to the other if (BitBlt(hDestDC, xDest, yDest, nWidth, nHeight, hSrcDC, 0, 0, SRCCOPY) == NULL) { lpdds-&ReleaseDC(hDestDC); DeleteDC(hSrcDC); return(FALSE); } // kill the device contexts lpdds-&ReleaseDC(hDestDC); DeleteDC(hSrcDC); // return success return(TRUE); } 上面这段代码被设计成从资源调用位图,但你可以很容易就把它修改成从外部文件调用位图,或者更理 想的是,首先你从资源调用位图,如果失败,再试图从外部 文件调用位图。从外部调用,需要记住的是调用 LoadImage()函数时加上 LR_LOADFROMFILE 标志。最美妙的事情是,函数 BitBlt ()自动完成象素格式的 转换。举例说, 当我们把 24-bit 的位图放入内存设备上下文,再把它传送(拷贝)到 16-bit 色彩深度的表面, 所有的颜色将得 到正确的显示,不用顾忌象素格式是 555 还是 565,很方便吧,哦? 如果你要控制位图传递的实际过程,而不是使用 BitBlt()这 样简单的函数,你有两个选择。第一个, 你可以修改这个函数,需要利用 BITMAP 结构的 bmBits 成员,它是一个组成图象的位的 LPVOID 指针变量。 第 二种方法,如果你真的想控制图象的调用过程,你可以自己编写函数,思路是使用标准的 I/O 函数来打开图象 文件,然后读取它。要这样做,你需要了解位图文 件的结构。我们将不涉及这种函数的编写,因为目前的对 我们来说已经足够了,但我还是要为你将来的大展鸿图做一点点铺垫。 位图格式 令人高兴的是,要自己写一个调用位图的函数,有一个 Win32 结构的位图头文件可以利用。读取这个头 文件的信息,用 fread()这样简单的函数就可以 了。所有的位图文件都有这样一个头文件,它包含了位图的 全部信息。BITMAPFILEHEADER 就是这个头文件结构的名字,下面是它的原形: typedef struct tagBITMAPFILEHEADER { // bmfh WORD bfT // file type - must be &BM& for bitmap DWORD bfS // size in bytes of the bitmap file WORD bfReserved1; // must be zero WORD bfReserved2; // must be zero DWORD bfOffB // offset in bytes from the BITMAPFILEHEADER // structure to the bitmap bits } BITMAPFILEHEADER; 我就不详细介绍这些成员了,因为注释里已经说得很清楚了,只要使用 fread()读取它们就可以了。注 意要检测 bfType 成员是否等于字符“BM”, 若是,说明你正在处理一个有效的位图。在此之后,有另一个头 文件需要读取,它包含位图的尺寸、压缩类型等图象信息。以下是它的结构: typedef struct tagBITMAPINFOHEADER{ // bmih DWORD biS // number of bytes required by the structure LONG biW // width of the image in pixels LONG biH // height of the image in pixels WORD biP // number of planes for target device - must be 1 WORD biBitC // bits per pixel - 1, 4, 8, 16, 24, or 32 DWORD biC // type of compression - BI_RGB for uncompressed DWORD biSizeI // size in bytes of the image LONG biXPelsPerM // horizontal resolution in pixels per meter LONG biYPelsPerM // vertical resolution in pixels per meter DWORD biClrU // number of colors used DWORD biClrI // number of colors that are important } BITMAPINFOHEADER; 只有几个成员需要解说一下。第一个,注意压缩格式。大多数的位图你都需要做解压缩的操作。最普通 的位图压缩格式是 run-length 编码(RLE), 但只能应用于 4-bit 或 8-bit 图象,在此情况时,成员 biCompression 将分别是 BI_RLE4 和 BI_RLE8, 我们就不讨论这种压缩格 式了, 但它真的很简单, 很容易理解, 你如果要了解它是不会有任何麻烦的。 第二个,对于高色彩的位图,biClrUsed 和 biClrImportant 这两个成员通常设置为 0,所以不用太在意 它们。对于 BI_RGB 这种未压缩格式的位图,成员 biSizeImage 也将被设 置为 0。最后,针对我们的目的,其 它的结构成员都不是很重要的, 我们只需要注意位图的长、 宽和色彩的深度 (biWidth、 biHeight、biBitCount) 。 读取完了这些头文件的信息后,如果位图是 8-bit 或者以下色彩深度的(也就是调色板模式),调色板的 信息会紧跟在这些信息之后。也许出乎你的意料,调色板的信息不是存储在 PALETTEENTRY 结构中,而是在 RGBQUAD 结构中。RGBQUAD 结构如下: typedef struct tagRGBQUAD { // rgbq BYTE rgbB BYTE rgbG BYTE rgbR BYTE rgbR } RGBQUAD; 不要问我为什么红、绿、蓝以倒序方式排列,事实就是这样!读取 RGBQUAD 中的数据,把数据传递给 DirectDraw 调色板的数组。记得要把每个 PALETTEENTRY 的 peFlag 设置成 PC_NOCOLLAPSE。 之后呢(调色板信息不一定存在,因为高彩模式下就没有),你将发现图象位(image bits),你可能 会想到建立一个指针,在内存中分配足够的空间来控制这些图象位数据,然后读取它们。对极了,我正要这样 干。假设把存储在 BITMAPINFOHEADER 结构中的信息头文件称作 info,你的图象位指针称作 fptr,实施的代 码如下: UCHAR* buffer = (UCHAR*)malloc(info.biSizeImage); fread(buffer, sizeof(UCHAR), info.biSizeImage, fptr); 要记住,在一些情况下,biSizeImage 的值可能为 0,所以有必要在上面的代码运行前检测它一下。如果 它被设置为 0,你将不得不计算图象由多少个象素构成,每个象素需要多少个字节。 写你自己的位图调用函数,并非什么难事儿。但你觉得不需要,就用我们开始介绍的方法好了。这个话题 告一段落,下面让我们看看 DirectDraw 的精华:使用位块传输。 使用位块传输 位 块传输是显示卡操控位图数据的一部分, 你同样可以用它来进行颜色填充。 就像我们过一会儿看到的, 随着硬件的性能提高,会有很多经典的技巧。DirectX 有权使用硬件的加速功能,但要记住,如果 DirectX 使用的加速功能不被机器硬件支持,将自动启用硬件仿真层(HEL),但这也并非万无一失,因为有些 功能靠 硬件仿真层是无法实现的(否则谁还买 3D 加速卡^_^),所以你需要检测你的函数是否调用成功。 GDI 位块传输可以在 DirectDraw 编程中使用, 而且有时也的确是这样做的。 然而 DirectDraw 具有其自身 的位块传输函数,它们通常更加适合于编程环境,而且比 GDI 的相应的函数执行得更快。DirectDraw 位块传 输函数名为 Blt()和 BltFast(),都是有 IDirectDrawSurface7 接 口提供的。两者不同处是 BltFast() 不处理剪切、放缩等其它 Blt()做的有趣的事情。如果在硬件仿真层上,BltFast()要比 Blt()快 10% 左右,但如果有硬件加速卡支持(硬件加速卡主要就是为位块传输服务的),二者的速度就差不多了,而且现 在大多数的机器都有硬件加速卡,所以我总是使 用 Blt()。让我们仔细看看这个神奇的东东: HRESULT Blt( LPRECT lpDestRect, LPDIRECTDRAWSURFACE7 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwFlags, LPDDBLTFX lpDDBltFx ); 由于 Blt()所拥有的最后一个参数,使其能做很多特殊的事儿。该参数配有一个标志常量列表,我将会 向你介绍其中最有用的几个。另外,注意在把位图从一个表面向另一个表面传递时,你应该调用目的表面的 Blt(),不是源表面的。好了吗?以下是函数的参数说明: ※ LPRECT lpDestRect:参数 lpDestRect 为指向结构 RECT 的指针,它给出了位块传输操作的目标表面 的左上角和右下角的坐标。如果源表面和目标 (目的)表面的大小不一致,Blt()将把源表面的图象自动按 照比例适应目标表面的大小。如果此参数为 NULL,则使用整个目标表面。 ※ LPDIRECTDRAWSURFACE7 lpDDSrcSurface:参数 lpDDSrcSurface 为指向 DirectDraw 表面的指针,该 DirectDraw 表面为位块传输之源表面。如果你只是要用颜色填充目的表面,你可以把它设置为 NULL。 ※ LPRECT lpSrcRect: 参数 lpSrcRect 为指向结构 RECT 的指针, 它给出了位块传输 (有的书上也叫作“位 转换”)操作的源表面的左上角和右下角的坐标。如果此参数为 NULL,则使用整个源表面。 ※ DWORD dwFlags:对于这个参数有一个巨大的标志常量列表,可以用“|”组合使用标志常量。其中一 些是为 Direct3D 服务的, 所以我将把我们常用的列出 来:? DDBLT_ASYNA: 位块传输异步的以先入先出 (FIFO) 的顺序接收。如果没有空间可用于 FIFO 硬件,则该调用失败。 ? DDBLT_COLORFILL:使用 DDBLTFX 结构的数据成员 dwFillColor 作为 RGB 颜色填充目标表面的矩形。 ? DDBLT_DDFX:DDBLTFX 结构的 dwDDFX 成员指定了位块传输的使用效果。 ? DDBLT_DDROPS:DDBLTFX 结构的 dwDDROP 成员指定了光栅操作(ROPS),该操作不是 Win32 API 的一 部分。 ? DDBLT_KEYDEST:颜色键与目标表面相关联。 ? DDBLT_KEYDESTOVERRIDE:DDBLTFX 结构的 dckDestColorkey 成员是目标表面的颜色键。 ? DDBLT_KEYSRC:颜色键与源表面相关联。 ? DDBLT_KEYSRCOVERRIDE:DDBLTEX 结构的 dckSrcColorkey 成员是源表面的颜色键。 ? DDBLT_ROP:DDBLTFX 结构的 dwROP 成员是位块传输的 ROP(光栅操作代码),这些 ROP 与 Win32 API 中定义的那些相同。 ? DDBLT_ROTATIONANGLE: DDBLTFX 结构的 dwRotationAngle 成员是表面的旋转角度, 其单位为 1/100 度。 ? DDBLT_WAIT:在位块传输器忙的情况下,推迟 DDERR_WASSTILLDRAWING 返回值(位块传输函数调用失 败返回的值之一),而当位块传输开始或发生另一个错误时立即返回。 我几乎总是使用 DDBLT_WAIT 标志。颜色键标志也是很重要的,我们过一会儿再说它。现在,还有最后一 个 Blt()参数需要说一下: ※ LPDDBLTFX lpDDBltFX:这是一个指向 DDBLTFX 结构的指针,它可以包含各种特殊要求的信息。如果没 有什么特殊要求,你就设置为 NULL 好了。让我们仔细看看这个结构。我警告你,它是很魁梧的:^_^ typedef DWORD DWORD DWORD DWORD DWORD DWORD DWORD DWORD DWORD DWORD struct _DDBLTFX{ dwS dwDDFX; dwROP; dwDDROP; dwRotationA dwZBufferOpC dwZBufferL dwZBufferH dwZBufferBaseD dwZDestConstBitDunion { DWORD dwZDestC LPDIRECTDRAWSURFACE lpDDSZBufferD }; DWORD dwZSrcConstBitD union { DWORD dwZSrcC LPDIRECTDRAWSURFACE lpDDSZBufferS }; DWORD DWORD DWORD DWORD dwAlphaEdgeBlendBitD dwAlphaEdgeB dwR dwAlphaDestConstBitDunion { DWORD dwAlphaDestC LPDIRECTDRAWSURFACE lpDDSAlphaD }; DWORD dwAlphaSrcConstBitD union { DWORD dwAlphaSrcC LPDIRECTDRAWSURFACE lpDDSAlphaS }; union { DWORD dwFillC DWORD dwFillD DWORD dwFillP LPDIRECTDRAWSURFACE lpDDSP }; DDCOLORKEY ddckDestC DDCOLORKEY ddckSrcC } DDBLTFX, FAR* LPDDBLTFX; 如果我整个详细的介绍这个结构,恐怕我们都会受不了的,并且也没有这个必要。所以我只告诉你一些重 点的部分。谢天谢地,该结构的大部分都是为 z 缓冲区(z-buffers)和 α 消息服务的,我们不用理会它。嘻 嘻,我的工作量变得很小了: ※ DWORD dwSize:象所有的 DirectX 的结构一样,当你初始化这个结构时,该成员放置结构的大小。 ※ DWORD dwDDFX:这些是位块传送所能接受的一些特殊操作。列表并不长,别担心喔! ? DDBLTFX_ARITHSTRETCHY:位块传输时,在 Y 轴算术拉伸位图。 ? DDBLTFX_MIRRORLEFTRIGHT:y 轴上的镜像变换。表面从左到右完成镜像效果。 ? DDBLTFX_MIRRORUPDOWN:x 轴上的镜像变换。表面从上到下完成镜像效果。 ? DDBLTFX_NOTEARING:把动画图像块转移到前段缓存时可以使用这个参数,这样位块传输操作的时间会 与屏幕刷新率相一致,并使画面撕裂的可能性减小到最小。 ? DDBLTFX_ROTATE180:位块传输时,把表面顺时针旋转 180 度。 ? DDBLTFX_ROTATE270:位块传输时,把表面顺时针旋转 270 度。 ? DDBLTFX_ROTATE90:位块传输时,把表面顺时针旋转 90 度。 需要详细解释的可能只有 DDBLTFX_NOTEARING。游戏离不开动画,动画制作者主要关心的通常是动画的 速度和性能,速度太快会导致图象质量的 恶化。光栅扫描显示系统(我们基本上用的都是这种显示器)利用 电子束扫描每一条水平线上的屏幕象素点。象素行从屏幕的左上角开始更新,到屏幕的右下角结 束。各象素 行都被称为扫描线。电子束在每一行扫描线的末端被关掉,而电子枪重新瞄准下一行的起始点,这个过程成为 水平回扫。当过程执行到屏幕扫描线的最后 一行时,电子束再次被关掉,电子枪重新瞄准屏幕的左上角。电 子枪从屏幕的右下角重新瞄准到左上角的过程所需的时间被称为垂直回归或者屏幕空白周期。如果在 视频控 制器显示视频数据的同时,视频数据被 CPU 做了更改,这时就会产生问题。在 PC 机中,屏幕的刷新率通常在 60Hz 到 100Hz 之间,而现在的 CPU 则可以在每秒处理成百上千的指令,这样就很可能导致位于视频内存区的 图象在视频系统完成显示之前发生更改。 图象断裂的结果被称为图象撕裂。 就我的经验而 言, 使用了 DDBLTFX 结构的 DDBLTFX_NOTEARING 后,图象撕裂就不是什么问题了。 ※ DWORD dwROP: 使用这个标志来指定 Win32 模式的光栅操作代码。 GDI 函数 BitBlt 和 StretchBlt() 同 () 中的相对应参数的功能一样。可以 通过 IDirectDraw7::GetCaps()函数得到可能的光栅操作列表,可以 通过“|”组合标志常量,确定源矩形表面和目标矩形表面是怎样结合 的。 ※ DWORD dwRotationAngle:这是用来旋转位图角度的,可以旋转任意角度。这是非常棒的,但不幸的是, 它只能在 HAL 层(硬件抽象层)上工作,这就意 味着用户的显示卡要支持加速旋转,否则??,但不能保证 每个用户都有这种高档的显示卡,所以你需要考虑周全。如果你真的需要旋转处理,你只好自己写这样的 函 数了,这可是一个大话题,需要另写一部指南了,所以我们就越过它。但请注意,如果是 90 度倍数的角度, 你可以使用 DDBLTFX_ROTATE90 等。 这将使你回避显示卡不干活的风险。 ※ DWORD dwFillColor:如果你要使用位块传输来填充颜色,你必须把颜色放入到这个参数中。 ※ DDCOLORKEY ddckDestColorKey,ddckSrcColorKey:你要使用颜色键时必须要指定这些成员。这两个 家伙是很重要的。但暂时我们还不讨论它们,因为我们过一会儿才讲颜色键。 以上这些就是 DDBLTFX 结构中比较有用的成员了,这就意味着你现在拥有足够的知识进行位块传输了! 如果你现在感觉有些混乱,不要紧,你实践过一段时 间就会好了。让我们看看几个例子。假设你已经有了一 个叫作 lpddsBack 的后缓冲区,你想把它里面的内容传输到主表面,很简单的,你看: lpddsPrimary-&Blt(NULL, lpddsBack, NULL, DDBLT_WAIT, NULL); 再轻轻回忆一下,第一个参数和第三个参数分别是位块传输的目标矩形和源矩形。由于我把它们都设置 为 NULL,就说明是对全部的表面进行拷贝。现在,让我 们在看看,假设你有一个在离屏表面里的名字叫作 lpddsTileset 的 16×16 大小的图形,你想把它传输到后缓冲区,变成 32×32 大小的,你需要 这样做: RECT dest, SetRect(&src, 0, 0, 16, 16); // the coordinates of the tile SetRect(&dest, 0, 0, 32, 32); // where you want it to end up on the back buffer lpddsBack-&Blt(&dest, lpddsTileset, &src, DDBLT_WAIT, NULL); 这个例子同上一个例子不同处在与这个例子设置了位块传输的坐标。 由于这两个矩形区的大小不同, Blt ()依照比例适当的变更了图形的大小。最后,我们还 得举例说明一下 DDBLTFX 结构,就做一个颜色填充的 例子吧。假设你在 16-bit 色彩模式下,是 565 象素格式,你要把你的后缓冲区填充为蓝色。下面 就是你应该 做的: DDBLTFX INIT_DXSTRUCT(fx); // zero out the structure and set dwSize fx.dwFillColor = RGB_16BIT565(0, 0, 31); // set fill color to blue lpddsBack-&Blt(NULL, NULL, NULL, DDBLT_WAIT | DDBLT_COLORFILL, &fx); 注意参数的设置, 前三个都是 NULL, 你自己想想原因吧! 好了, 让我们看看另一个位块传输函数 BltFast() 吧。它只是 Blt()的简化版本,所以我们不需要太多的时间解释它。下面是它的原形: HRESULT BltFast( DWORD dwX, DWORD dwY, LPDIRECTDRAWSURFACE7 lpDDSrcSurface, LPRECT lpSrcRect, DWORD dwTrans ); 你可以看得出来,它同 Blt()极其相似。它也是 IDirectDrawSurface7 接口的成员函数,被目标表面调 用。来看看它的参数: ※ DWORD dwX,dwY:这是 Blt()和 BltFast()之间不同的地方。是目标表面上进行位块传输的 x 和 y 坐标。 如果源矩形大于目标矩形,则调用失败,因为 BltFaxt()不能干按比例变换的事儿及其它能够通过 Blt()完 成的工作。 ※ LPDIRECTDRAWSURFACE7 lpDDSrcSurface:这是源表面,同 Blt()的一样。 ※ LPRECT lpSrcRect:这个也同 Blt()的一样。是在源表面中定义了矩形左上角和右下角的 RECT 结构。 ※ DWORD dwTrans:定义了位块传输类型,它的标志列表很简单,只有四个标志: ? DDBLTFAST_DESTCOLORKEY:使用目标颜色键的透明位块传输。 ? DDBLTFAST_NOCOLORKEY:没有透明的普通复制位块传输。 ? DDBLTFAST_SRCCOLORKEY:使用源颜色键的透明位块传输。 ? DDBLTFAST_WAIT:如果位块传输忙的话,不产生 DDERR_WASSTILLDRAWING 消息。一旦位块传输能够开 始或者发生另一个错误时返回。 就这些!BltFast()支持颜色键。下面让我们看一个简单的示例。把整个后缓冲区拷贝到主表面: lpddsPrimary-&BltFast(0, 0, lpddsBack, NULL, DDBLTFAST_WAIT); 到现在为止, 你已经是一个位块传输的专家了, 还有几件事情对于 DirectX 程序很重要: 颜色键和剪裁板。 你可能知道在某些情况下关于颜色键有上百万种标志,那么到底怎样使用它呢?让我们一起看看吧! 颜色键 颜色键使一 个位图被拷贝到另一个位图上时,不使所有的象素都显现。例如:当你把一个精灵(游戏中 会动的对象一般都称作精灵)拷贝到地图上(背景上)时,这个精灵位图 一般不会是一个精灵形状的位图, 它通常都是一个矩形位图,位图里包含你所需要的精灵(除非你的精灵就是一个矩形机器人): 游戏中,地图是先于精灵显示的,那么精灵走到树后时,还应有相应被遮挡的部分,这个先不讨论,下一 节再说。现在,对我们更重要的是,如果不应用颜色键,这个精灵将永远带着这个黑色底框,这是绝对不能容 忍的。 为了解决这个问题,我们使用源颜色键。这个源颜色键告诉你精灵矩形的哪些颜色将不被拷贝(当然我 们是让黑色不被拷了)。一个颜色键由两个值组成:一个低 位颜色值,一个高位颜色值。当一个颜色键被申 请使用时, 在两个值之间的颜色, 包括这两个值的颜色都将不会被拷贝。 DirectX 中有一个结构用来处理 它, 在 叫作 DDCOLORKEY,看看吧: typedef struct _DDCOLORKEY{ DWORD dwColorSpaceLowV DWORD dwColorSpaceHighV } DDCOLORKEY, FAR* LPDDCOLORKEY; 很简单的结构,我就不解释了。我将展示给你使用了颜色键之后的效果。我使用颜色键的高位和低位两个 值仅仅把黑色包括在它们之间。因此,黑色是唯一不会被拷贝的颜色。 好多了,是不是?这就是我们想得到的结果!现在,在我告诉你怎样建立和使用颜色键之前,我还有说 一说目标颜色键,尽管我们的确我们不常用到它(我们常用 的是源颜色键)。鉴于源颜色键定义了哪些颜色 键不能被拷贝,目标颜色键定义了哪些颜色不能被写入(覆盖)。听起来很怪异,是不是?我也有同感。举个 实例你 就明白了。当你要把 A 位图拷贝到 B 位图的下面,意思就是把 A 位图作为背景,例如由于某种理由, 需要把一个文本框拷贝到空的后缓冲区,然后再把背景画面拷贝 到这个后缓冲区,但你又不能覆盖先前的文 本框。因此,在后缓冲区里除了文本框的那些黑色的部分才能被写入象素。 我也不清楚你什么时候需要处理这种情况,但是你的确可能用到(一旦你用到了,可千万要告诉我哦,我 一直没有遇到这种情况呢^_^)。现在,你已经知道什么是颜色键了,让我们看看怎样使用它们吧! 置颜色键 在 DirectDraw 中有两种方法使用颜色键。第一种,你可以链接一个颜色键(或者两个,如果你同时使用 源和目标颜色键)到表面,然后在位块传输时定 义 DDBLT_KEYSRC,DDBLT_KEYDEST,DDBLTFAST_SRCCOLORKEY 或 DDBLTFAST_DESTCOLORKEY 标志,具体使用哪个标志,取决于你使用哪个位块传输函数和使用哪种颜色键。 第二种,你可以创建一个颜色键,然后通过 DDBLTFX 结构传送给位块传输操 作。当你不断地需要使用颜色键 时,我向你推荐第一种方法;反之,当你偶然要使用一次颜色键,就用第二种方法吧! 你可以把颜色键链接到 已经建立好了的表面,也可以在建立表面的同时建立颜色键。两种方法我都将详 细告诉你。假设你工作在 16-bit 显示模式下,是 565 象素格式,你要在后缓 冲区使用一个仅包含黑色的源颜 色键。如果你的后缓冲区已经建立好了,你就可以简单建立一个 DDCOLORKEY 结构,然后把它传递给 IDirectDrawSurface7::SetColorKey()函数,如下所示: HRESULT SetColorKey( DWORD dwFlags, LPDDCOLORKEY lpDDColorKey ); 记住要用 FAILED()宏检测这个函数的返回值,保证一切都在计划之中。函数的参数很简单: ※ DWORD dwFlags:决定所使用颜色键类型的标志。以下三个是你将用到的: ? DDCKEY_COLORSPACE:该结构包含一个颜色范围,如果结构包含的是单独的颜色键则不作设置。 ? DDCKEY_DESTBLT:该结构指定颜色键或者颜色范围作为用于位块传输操作的目标颜色键。 ? DDCKEY_SRCBLT:该结构指定颜色键或者颜色范围作为用于覆盖操作的颜色键。 ※ LPDDCOLORKEY lpDDColorKey:这是指向 DDCOLORKEY 结构的指针。 就这么多。 你可以根据你所需要使用的颜色键适当地定义位块传输的标志。 注意, 一个颜色键链接到表面, 并不意味着你每一次必须使用它。如果你只定义了 DDBLT-WAIT 或 DDBLTFAST_WAIT 标志,颜色键将被忽略。下 面是设置颜色键的方法: DDCOLORKEY ckey.dwColorSpaceLowValue = RGB_16BIT565(0, 0, 0); // or we could just say '0' ckey.dwColorSpaceHighValue = RGB_16BIT565(0, 0, 0); if (FAILED(lpddsBack-&SetColorKey(DDCKEY_SRCBLT, &ckey))) { // error-handling code here } 如果你要为已经建立的颜色键链接一个表面,有几件事情你需要做。首先,当你定义 DDSURFACEDESC2 结构的有效成员时,你需要使 dwFlags 成员包含 DDSD_CKSRCBLT 或者 DDSD_CKDESTBLT 标志,具体使用哪个标 志,取决于你要使用哪种颜色键。回头再看看 DDSURFACEDESC2 结构,它包含两种 DDCOLORKEY 结构。一种称 为 ddcdCKSrcBlt,另一种称为 ddcdCKDestBlt,填 写适当的结构来创建表面。你就需要干这么多!以下是关 于 640×480 大小的离屏表面的实例代码: // set up surface description structure INIT_DXSTRUCT(ddsd); ddsd.dwFlags = DDSD_CAPS | DDSD_WIDTH | DDSD_HEIGHT | DDSD_CKSRCBLT; ddsd.dwWidth = 640; // width of the surface ddsd.dwHeight = 480; // and its height ddsd.ddckCKSrcBlt.dwColorSpaceLowValue = RGB_16BIT(0,0,0); // color key low value ddsd.ddckCKSrcBlt.dwColorSpaceHighValue = RGB_16BIT(0,0,0); // color key high value ddsd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN; // type of surface // now create the surface if (FAILED(lpdd7-&CreateSurface(&ddsd, &lpddsBack, NULL))) { // error-handling code here } 关于颜色键的部分到此结束。现在我们可以进行本章最后一项了――剪切。 IDirectDrawClipper 接口 假设你有一个图形,而你却只想把它的一部分显示在屏幕上。你应该怎样做呢?如果你曾经在 DOS 下编 写过游戏,你可能对剪切望而生畏。Well,在 DirectX 下,这只是小菜一碟!首先的首先,这的确是很容易 做到的,因为 DirectX 用矩形来做位块传输,改变矩形的坐标要比指定内存中的哪一部分 图形被拷贝(就像 在 DOS 下所做的)要容易的多。其次,DirectDraw 还为此提供了一个接口――IDirectDrawClipper。 DirectDraw 的剪切性能完全可以满足你的要求,你不但可以剪切矩形区域,你还可以剪切任意多边形区域! 真的是很棒!如果你在屏幕上同时要显示一个主窗口,在屏幕的一边 显示一个状态栏,在屏幕的底部显示一 个文字提示栏,并且用黑色分隔开这些区域,你可以用 DirectDraw 的剪切功能完成它,非常容易的! 做这样的操作你需要分几步走。首先你得得到一个指向 IDirectDrawClipper 接口的指针。没什么难的, 只需要调用 IDirectDraw7::CreateClipper(),如下: HRESULT CreateClipper( DWORD dwFlags, LPDIRECTDRAWCLIPPER FAR *lplpDDClipper, IUnknown FAR *pUnkOuter ); 在你调用这个函数前, 你应该首先声明一个 LPDIRECTDRAWCLIPPER 类型的指针, 这样你才能把它的地址传 递给这个函数。记着要检测函数的调用是否成功哦!以下是函数参数的解释: ※ DWORD dwFlags:简直就是幸福――这个参数还没有使用过,设置为 0。 ※ LPDIRECTDRAWCLIPPER FAR *lplpDDClipper:把你的 LPDIRECTDRAWCLIPPER 指针的地址传递给它。 ※ IUnknown FAR *pUnkOuter:你知道怎么做,设置为 NULL。^_^ 一旦你有了自己的接口指针,下一件事就是创建剪切列表(clip list)。多个剪切的矩形组成了剪切列 表。需要使用到 RGNDATA 结构,它包含了足够的信息来定义一个任意的区域,看看它的原形吧: typedef struct _RGNDATA { RGNDATAHEADER char Buffer[1]; } RGNDATA; 我需要详细解说一下它的参数。 ※ RGNDATAHEADER rdh:它是 RGNDATA 结构中嵌套的一个结构。它包含了第二个参数――Buffer 的所有 信息。它定义了需要剪切区域里的矩形的数目,整个区域的形状等信息。我们过一会儿再具体讨论它。 ※ char Buffer[1]:这并不意味着是只有一个值得数组;它将是在内存中任意大小的区域来控制着实际 的剪切区域数据。同样的,对于 RGNDATA 结构,我们 要声明一个指向该结构的指针,然后使用 malloc()函数 为 RGNDATAHEADER 设置足够的内存空间,也就是为剪切列表设置足够的空间。有一件事 我要提醒你:剪切列 表里的矩形按从上到下,然后从左到右排列,不能交迭。 我已经意识到你有些胡涂了,不要紧,继续学习,一切会好起来的。下面是 RGNDATAHEADER 结构的原形, 它比较好理解: typedef struct _RGNDATAHEADER { DWORD dwS DWORD iT DWORD nC DWORD nRgnS RECT rcB } RGNDATAHEADER; ※ DWORD dwSize:结构的大小。简单的使用 sizeof(RGNDATAHEADER)好了。 ※ DWORD iType:它描述了每个区域的外形。它是另有玄机的,以后我们再把它扩展开来讨论。现在,你 只要把它设置为 RDH_RECTANGLES 就好了,这也正是我们需要的。 ※ DWORD nCount:这是组成该区域的矩形的数量。换句话说,就是你的剪切列表理的矩形数。 ※ DWORD nRgnSize:为缓冲区的大小设置它,将得到自身的区域数据。由于我们使用 n 个矩形组成了剪 切区域,所以它的大小应该是 sizeof(RECT) *nCount。 ※ DWORD rcBound:这是一个矩形类型,包含了剪切列表里的所有矩形。通常你把它设置成表面上需要剪 切部分的尺寸。 现在,我们可以建立一个剪切列表了。首先我们声明一个 LPRGNDATA 的指针,分配给剪切列表足够的内 存空间;然后根据上面我们所学的设置每个成员。 让我们看看最简单的实例,你可能经常要用到它哦!它只 有一个剪切区域,并且,就是整个屏幕,是 640×480 显示模式的。以下就是代码: // first set up the pointer -- we allocate enough memory for the RGNDATAHEADER // along with one RECT. If we were using multiple clipping area, we would have // to allocate more memory. LPRGNDATA lpClipList = (LPRGNDATA)malloc(sizeof(RGNDATAHEADER) + sizeof(RECT)); // this is the RECT we want to clip t the whole display area RECT rcClipRect = {0, 0, 640, 480}; // now fill out all the structure fields memcpy(lpClipList-&Buffer, &rcClipRect, sizeof(RECT)); // copy the actual clip region lpClipList-&rdh.dwSize = sizeof(RGNDATAHEADER); // size of header structure lpClipList-&rdh.iType = RDH_RECTANGLES; // type of clip region lpClipList-&rdh.nCount = 1; // number of clip regions lpClipList-&rdh.nRgnSize = sizeof(RECT); // size of lpClipList-&Buffer lpClipList-&rdh.rcBound = rcClipR // the bounding RECT 一旦有了剪切列表,你需要把它作为你的剪裁板。你将调用 IDirectDrawClipper 接口的函数 SetClipList()。就是下面这个东东: HRESULT SetClipList( LPRGNDATA lpClipList, DWORD dwFlags ); 你所要做的就是把 RGNDATA 的指针传递给它。参数 dwFlags 没有用,设置为 0。现在,剪切列表设置好了, 还需要一步,就是把剪切列表链接到你所要控制的表面上,这需要调用 SetClipper()函数,它将表面指针 作为其唯一的参数: HRESULT SetClipper(LPDIRECTDRAWCLIPPER lpDDClipper); 你知道应该怎样做:就是把你已经设置好的接口的指针传递给它。任何时候,你要位块传输一个有剪裁 板相关联的表面,剪裁板将做所有的工作。所以如果你要在 屏幕上显示一个贴片的一部分,例如传输一个矩 形坐标为{-10,-10,6,6},或者类似的矩形贴图,都不会有麻烦的。很不错吧,嗯? 关于剪切的最后一件事情是必须要用 free()函数释放你用 malloc()设置的内存空间。 还有, 就是由于某 种原因在调用 SetClipList() 或 SetClipper()失败后, 在返回错误代码前或你要根据失败的结果进行操作前, 要释放内存空间。在你完成用 LPRGNDATA 的指针设置剪切列表 后,这个指针就没有存在的意义了,所以它占 用的内存空间将被立即释放。 总结 到此,关于 DirectDraw 的部分就讨论完了!你真的从这六章里学到了很多的知识,如果你坚持到现在, 那么祝贺你,你真的已经走了很长一段路了。对于这一章我们 所学习的,我将把它们组合在一起,给你一个 Demo 程序,它将从资源调用位图,使用位块传输位图,颜色填充,放缩位图比例,使用剪切功能。 仍然有些东东我们应该讨论,例如页面的切换(flipping),双缓冲区的应用等,无论如何,我们还将继 续,所以不必担心我们会遗漏重要的内容。 现在, 最初的原始积累已经结束, 我们以后的焦点会从创建 Windows 程序转移到创建一个贴图基础的 RPG 游戏。以后的章节将包括用 DirectInput 建立一个好的输入机制,写一个基础的引擎脚本,播放背景音乐和 配音,等等。下一章,我们将学习为贴图游戏制作一个简单的卷轴游戏引 擎,很容易的,没有你想象的那么 难哦! 22:21 | 添加评论 | 固定链接 | 引用通告 (0) | 记录它 | DirectDraw 的位 图化图形 DirectDraw 的调色板和象素DirectDraw 的调色板和象素DirectDraw 的调色板和象素(1)简介 今 天我们将分别使用调色板和 RGB 模式来熟悉 DirectDraw 的基本图形。 它们有什么不同呢?如果你曾经 在 DOS 下编程,你可能使用过调色板映射模式。 调色板是个颜色查询表,为了绘制象素,你将一个单独的字 节写入视频内存,通过这个字节你可以索引到一个拥有各种颜色的链表,这个颜色的链表,或查询表就叫 作 调色板。而 RGB 模式是不同的,因为它不需要颜色查询表。在 RGB 模式下绘制一个象素,你可以直接把红色、 绿色和蓝色的值写入视频内存。任何色彩深度高 于 8 位的调色板都可以用 RGB 模式取代。 编写本文时,我假设你已经读过了前面几章,知道了怎样设置 DirectDraw 和创建表面。 我们将使用 DirectX7,它包含了最新的 DirectDraw 接口。实际上,DirectX 7 中的 DirectDraw 接口可能是最后的升级版 本了!不用担心,未来的版本一定会兼容它的,但是未来可能是一个 DirectDraw 和 Direct3D 综合的产品, 管它那,我们学的不会没有用的。 在开始前我还有最后一件事要提醒你:在我的后续文章中关于调色板的部分可能再也用不到 了,所以, 如果你对于调色板模式不是很感兴趣,你可以跳过文章的前一部分,从象素格式开始看起。调色板的开发和使 用是 PC 中使用的原始视频系统的内存限制 带来的直接后果。 现在由于充足的显存使调色板模式几乎废弃不用 了。值得保留调色板模式的一个原因是,执行调色板操作可以实现一些有趣的动画效果。不罗嗦 了,让我们 开始吧! 创建 DirectDraw 的调色板 当你在色彩深度为 8 位或低于 8 位的模式下显示图形时,你必须创建调色板,也就是颜色查询表。更明确 的讲,对于 DirectX,调色板就是 PALETTEENTRY 结构。要建立一个调色板,我们要做如下三步: 1、 创建颜色查询链表。 2、 得到指向 IDirectDrawPalette 接口的指针。 3、 把调色板链接到 DirectDraw 表面。 我假设我们使用的是 8 位色彩深度。如果你要用 16 位或更高位的色彩深度编写游戏,你就不用继续看以 下这段疯狂的 Windows 素材了。总之,8 位色彩深度,我们可以有一个 256 个条目的调色板。所以,创建颜色 查询链表,有 256 个条目在其中: typedef struct tagPALETTEENTRY { // pe BYTE peR BYTE peG BYTE peB BYTE peF } PALETTEENTRY; 头三个参数很明显,分别是红色、绿色和蓝色的强度。每一个取值范围 0-255,BYTE 是无符号数据类型。 最后一个参数是控制标志,应该设置为 PC_NOCOLLAPSE。原因我就不说了。 现在,我们需要把 256 个条目有秩序的排列好,也就是为了一下能找到,我们为链表设置一个数组,象这 样: PALETTEENTRY palette[256]; Ok,我们有了数组了,你可以装载颜色了。当我工作在调色板模式下时,通常把颜色存储在一个外部文件 里,然后用一些如下的东东装载颜色: FILE* file_ if ((file_ptr = fopen(&palette.dat&, &rb&)) != NULL) { fread(palette, sizeof(PALETTEENTRY), 256, file_ptr); fclose(file_ptr); } All right,第一步完成了。现在我们需要得到调色板的接口。交给 IDirectDraw7::CreatePalette()函 数就好了: HRESULT CreatePalette( DWORD dwFlags, LPPALETTEENTRY lpColorTable, LPDIRECTDRAWPALETTE FAR *lplpDDPalette, IUnknown FAR *pUnkOuter ); 返回类型是 HRESULT, 你知道它的, 所以可以用 FAILED()和 SUCCEEDED()这两个宏检测函数是否调用成功。 参数的说明如下: ※ DWORD dwFlags:描述调色板对象的标志常量。当然,你可以用“|”组合它们: ? DDPCAPS_1BIT:1 位色彩,对应 2 色调色板。 ? DDPCAPS_2BIT:2 位色彩,对应 4 色调色板。 ? DDPCAPS_4BIT:4 位色彩,对应 16 色调色板。 ? DDPCAPS_8BIT:8 为色彩,对应 256 色调色板。 ? DDPCAPS_8BITENTRIES:指出引用一个 8 位色彩索引。就是说,每个颜色条目是它本身的到目的表 面 8 位调色板的索引。这叫作被变址的调色 板。它必须同 DDPCAPS_1BIT、DDPCAPS_2BIT,或者 DDPCAPS_4BIT 合用。乱套吧!^_^ ? DDPCAPS_ALPHA:每一个 PALETTEENTRY 的 peFlags 成员都应该被认为是阿尔发值。用这些标志创建 的调色板可以被粘贴在 Dierct3D 纹理表面,因为 DirectDraw 本身并不支持阿尔发混合。 ? DDPCAPS_ALLOW256:允许 8 位调色板的全部 256 个条目被使用。通常,0 指向黑色,255 指向白色。 ? DDPCAPS_INITIALIZE:指出应该用 PALETTEENTRY 的数组初始化调色板。 ? DDPCAPS_PRIMARYSURFACE:调色板将链接到主表面,好快速更改显示颜色。 ? DDPCAPS_VSYNC:一般画圆时用到它。 大多数情况,你将使用 DDPCAPS_8BIT | DDPCAPS_INITIALIZE,如果你刚好想建立一个空的调色板,稍 后再设置它,你可以去掉后者,就是 DDPCAPS_INITIALIZE。当 然,你还可以使用 DDPCAPS_ALLOW256,如果你 真的想改变这两个常用的条目。 ※ LPPALETTEENTRY lpColorTable:这个指针指向我们创建的查询表,把数组的名称传递给它就好了。 ※ LPDIRECTDRAWPALETTE FAR *lplpDDPalette:这是指向 IDirectDrawPalette 接口指针的地址。如果函 数调用成功,它将被初始化。 ※ IUnkown FAR *pUnkOuter:同以前一样,这总是为 COM 高级应用准备的。设置为 NULL 好了。 不是太糟糕吧!现在我们可以建立我们的调色板对象了。最后一步是把调色板链接到一个表面,这只需要 一个函数就好了――IDirectDrawSurface7::Setpalette()。它的原形如下: HRESULT SetPalette(LPDIRECTDRAWPALETTE lpDDPalette); 很简单,是不是?你只要把上一步得到的接口指针传递给它就可以了。那好,让我们把学到的综合到一 起,下面我给你一个程序框架,我假设我们已经利用调色板 的数组建立了一个索引链表,就像我们上一步做 的。 该框架是建立 DirectDraw 调色板来控制颜色, 并且把它链接到主表面 (当然, 主表面是我们事先做好 的) : LPDIRECTDRAWPALETTE // create the palette object if (FAILED(lpdd7-&CreatePalette(DDPCAPS_8BIT | DDPCAPS_INITIALIZE, palette, &lpddpal, NULL))) { // error-handling code here } // attach to primary surface if (FAILED(lpddsPrimary-&SetPalette(lpddpal))) { // error-handling code here } 就是这么简单。一旦你的调色板建立完成,绘制象素部分同 RGB 模式就没有什么不同了。从此时开始,我 将同时介绍 RGB 模式和调色板模式,在我们真正的显示图象前,我需要告诉你什么是 RGB 象素格式。 象素格式 象我前面说过的,当你把一个调色板模式的象素写入内存时,你同时分配了一个字节,每个字节表示一 个到色彩查询表的索引。在 RGB 模式下,你只需要把颜色 描述值写入内存,但每个颜色需要的字节数都要多 于一个字节。字节的多少同色彩的深度相关。对于 16-bit 色彩,你要为每个象素准备两个字节(16 位), 以 此类推,你可以猜到 32-bit 色彩是怎么回事了,这些都是很容易理解的。32-bit 色彩对于一个象素来说,每 一位的字符如下: AAAA AAAA RRRR RRRR GGGG GGGG BBBB BBBB “A”是代表“alpha” (阿尔发) 表示一个透明的值, , 这是为 Direct3D 准备的。 我以前说过, DirectDraw 不支持 α 混合,所以当你为 DirectDraw 创建 32-bit 色彩时,把高位都设置为 0 好了。下一个 8 位表示红色 强度的值,再下一个 8 位表示绿色,最后 8 位表示蓝色。 一个 32-bit 色彩的象素需要 32 位,所以我们一般用 UINT 类型来定义相对应的变量类型,这是一个无符号实 数类型。通常我用一个宏来把 RGB 数据转换成正确的象素格式。让我给你看看它的样子,希望这能更好的帮助 你理解象素格式: #define RGB_32BIT(r, g, b) ((r && 16) | (g && 8) | (b)) 就象你看到的,这个宏通过位移在相应的位置写入了相应的红、绿、蓝的强度值,并且完全符合正确的 象素格式。是不是开始感觉有点儿入门了?要建立一个 32 -bit 的象素,你就可以调用这个宏。红、绿、蓝每 一个颜色的强度值都是 8 位,它们的取值范围都是从 0――255。例如建立一个白色的象素,你可以这样: UINT white_pixel = RGB_32BIT(255, 255, 255); 24-bit 色彩基本相同,道理实际上是一样的,只是 24-bit 没有关于 α 的描述,也就是少了 α 那 8 位。 象素格式如下: RRRR RRRR GGGG GGGG BBBB BBBB 所以红色、绿 色、蓝色仍然都分别是 8 位,这就意味着 24-bit 色彩和 32-bit 色彩实际上是有相同颜色 深度的,只是 32-bit 多了个 α 混合。现在,你一定会想, 24-bit 比 32-bit 要好,真的是这样吗?否,因 为使用 24-bit 有一些麻烦,事实上没有 24-bit 的数据类型,在你建立象素时,你不得不分三 步写入红、绿、 蓝的强度值,而不是象 32-bit 一次就完成。尽管 32-bit 色彩需要更多的内存,但在大多数的机器上,它要更 快一些。实际上,很多显示 卡不支持 24-bit 色彩模式,因为每一个象素占用 3 个字节是很不方便的。 现在,轮到 16-bit 色彩了,它有一点儿小麻烦,因为对 于 16-bit 色彩,不是每一种显示卡都使用相同 的象素格式!有两种格式。其中一种,也是比较流行的,红色占据 5 位,绿色占据 6 位,蓝色占据剩下的 5 位。 另一种格式是分别都占据 5 位,剩下的一位,也就是高位不使用,一些老的显示卡都使用这种格式。所 以这两种格式看起来是这样的: 565 format: RRRR RGGG GGGB BBBB 555 format: 0RRR RRGG GGGB BBBB 当你工作在 16-bit 色彩深度下,你首先需要检测显示卡是支持 565 格式还是 555 格式,然后使用适当的 方式。这是很讨厌的,但你坚持用 16-bit 色彩,这是没有办法避免的。由于存在两种格式,你就需要两种宏: #define RGB_16BIT565(r, g, b) ((r && 11) | (g && 5) | (b)) #define RGB_16BIT555(r, g, b) ((r && 10) | (g && 5) | (b)) 对于 565 格式, 红色和蓝色的取值范围是 0――31, 绿色是 0――63; 对于 555 格式, 取值范围都是 0――31, 所以当要创建一个白色象素时,就会有所不同: USHORT white_pixel_565 = RGB_16BIT565(31, 63, 31); USHORT white_pixel_555 = RGB_15BIT555(31, 31, 31); 这个 USHORT 是无符号短实数类型,对应的变量只有 16 位。存在两种格式把事情搞得有些复杂,但在实 际的游戏编程过程中,你将会感觉到这并没有你想象 的那么讨厌。顺便说一下,有些时候 555 格式被称为 15-bit 色彩深度,所以在以后如果我这样谈到了它,你一定要心领神会哦! 现在或许是告诉你在 16-bit 色彩深度模式下,怎样检测显示卡到底支持哪种格式的时机了,是 555 还是 565 呢?最简单的办法就是调用 IDirectDrawSurface7 接口下的 GetPixelFormat()函数,它的原形如下: HRESULT GetPixelFormat(LPDDPIXELFORMAT lpDDPixelFormat); 参数是指向 DDPIXELFORMAT 结构的指针。你只要声明它,初始化它,然后传递它的地址就一切 OK 了。这 个结构的本身是巨大的,所以我就不列举它 了,但我得告诉你它的三个成员,都是 DWORD 类型的,它们是 dwRBitMask、dwGBitMask、和 dwBBitMask。你可以从 dwRBitMask、dwGBitMask 和 dwBBitMask 中获得掩码值 (新东东,先不用太明白)。你也可以用它们检测显示卡支持的格式。如果显 示卡支持 565,dwGBitMask 将 为 0x07E0。如果是 555 格式,dwGbitMask 为 0x03E0。 现在,我们已经学 习了所有我们可能用到的象素格式,可以进入在 DirectX 下显示图象的实际阶段了。 你已经等待了很久了,不是吗?在把象素放到表面上前,我们需要锁定表 面,至少是锁定表面的一部分。锁 定表面返回一个指向表面在内存里位置的指针,然后,我们就可以为所欲为了。 锁定表面 没什么令人意外的东东,我们将使用的函数是 IDirectDrawSurface7::Lock()。让我们仔细看看它: HRESULT Lock( LPRECT lpDestRect, LPDDSURFACEDESC lpDDSurfaceDesc, DWORD dwFlags, HANDLE hEvent ); 一定要检测函数的调用是否成功,否则可能会有大麻烦的:如果锁定失败,而返回的指针指向了一个不正 确的内存区域,你若操控该区域,很有可能导致系统的混乱。函数的参数有以下这些组成: ※ LPRECT lpDestRect:是一个指向 RECT 结构的指针,它描述了将要被直接访问的表面上的矩形区。该 参数被设置为 NULL,以锁定整个表面。 ※ LPDDSURFACEDESC2 lpDDSurfaceDesc:是 DDSURFACEDESC2 类型的结构变量的地址,它由直接访问表面 内存所必需的全部信息进行填充。在该结构中返回的信息表面的基地址、间距和象素格式。 ※ DWORD dwFlags:好像没有几个 DirectX 函数没有这个东东的。下面列出几个最有用的标志常量: ? DDLOCK_READONLY:被锁定的表面为只读。(当然就不能写入了) ? DDLOCK_SURFACEMEMORYPTR: 表面返回一个指向锁定矩形左上角坐标的有效指针; 如果没有指定矩形, 那么返回表面左上角的坐标。此项为默认且无需显式的输入。 ? DDLOCK_WAIT:如果其它线程或进程正在进行位转换操作,不能锁定表面内存,则一直等到该表面可 以锁定为止或返回错误信息。 ? DDLOCK_WRITEONLY:被锁定表面为可写。(当然就不能读取了) 由于我们使用锁定去操控象素,你将总会用到 DDLOCK_SURFACEMEMORYPTR。即使我们目前还没有学习位块 操作,但使用 DDLOCK_WAIT 总是一个好主意。 ※ HANDLE hEvent:没用的东东,设置为 NULL 好了。 一旦我们锁定了表面,我们需要查看一下 DDSURFACEDESC2 结构来获取一些表面信息。我们以前介绍过这 个结构,但在这里,针对现在的课题,我们只需要它的两个成员。由于它们都很重要,我就再重复一遍: ※ LONG lPitch:这个 lPitch 成员表示每个显示行的字节数,也就是行间距。例如,对于 640×480×16 模式,每一行有 640 个象素,每一个象素需要 两个字节存放颜色信息,所以行间距应该为 1280 个字节,对不 对?Well,对于一些显示卡,它的长度大于 1280,每行上多于的内存不存放任何的图象数 据,但你必须让它 存在,因为这种显示卡在某种显示模式下不能创建线性内存模式。的确,这种显示卡的比例很小,但你需要考 虑到它。 ※ LPVOID lpSurface:这是指向内存中表面的指针。不管你使用何种显示模式,DirectDraw 都创建一个 线性地址模式,使你能够操控表面上的象素。 这个 lpSurface 指针是很容易理解的,而行间距是一个需要记 住的重要值,因为你将必须使用它去计算特殊象素的偏移量。 我们过一会儿在细说,因为有一件事我们现在必须知道,当对锁定的表面操作完成后,你需要释放这个锁定表 面,这个函数 IDirectDrawSurface7::Unlock()的原形为: HRESULT Unlock(LPRECT lpRect); 参数同你传递给 Lock()函数的要保持一致。都准备好了,让我们画一些象素吧! 绘制象素 首先是确定从 Lock()函数得到的指针类型。逻辑上,我们希望指针的大小同象素的大小要保持一致。 所以我们为 8-bit 色彩深度分配了 UCHAR*类 型,USHORT*是 16-bit 的,UINT*是 32-bit 的。但是 24-bit 怎 么办呢?因为没有与之相对应的数据类型,我们还是使用 UCHAR* 类型,但具体操作有一些不同。 我们也应该把 lPitch 成员转换成与指针相同的单位。记得吗,当我们第一次从 DDSURFACEDESC2 结构得 到 lPitch 时,它是以字节为单位。对于 16-bit 模式,我们应该把它除以 2 以适应 USHORT,对于 32- bit 我 们应该把它除以 4 以适应 UINT。 在我们进行第二步前先看看实例代码。假设我们在 32-bit 模式锁定主表面来绘制象素。以下是代码: // declare and initialize structure DDSURFACEDESC2 INIT_DXSTRUCT(ddsd); // lock the surface lpddsPrimary-&Lock(NULL, &ddsd, DDLOCK_WAIT | DDLOCK_SURFACEMEMORYPTR, NULL); // now convert the pointer and the pitch UINT* buffer = (UINT*)ddsd.lpS UINT nPitch = ddsd.lPitch && 2; 现在让我先一步告诉你象素绘制函数,然后我再解释: inline void PlotPixel32(int x, int y, UINT color, UINT *buffer, int nPitch) { buffer[y*nPitch + x] = } All right,让我分别解说。首先,你可能已经注意到了我把它声明为一个 inline 函数,目的是消除传 递所有参数时的辅助操作,例如每次我们想要做些简单 的事情(如绘制一个象素)。在函数里,仅用了一行 就定位了我们要绘制的点和设置了该点的颜色。注意,颜色仅仅是一个值,不是由红、绿、蓝分别组成的,所 以 我们需要使用宏 RGB_32BIT()来设置这个颜色值。 公式用来定位要绘制象素的具体位置――y*nPitch + x 。nPitch 表示行间距,被 y 乘后就得到了正确的 行数,再加上 x,就得到了正确的位置。这就是你需要知道的,很简单吧!让我再告诉你在 8-bit 和 16-bit 模式下绘制象素的函数,它们都十分相象: inline void PlotPixel8(int x, int y, UCHAR color, UCHAR* buffer, int byPitch) { buffer[y*byPitch + x] = } inline void PlotPixel16(int x, int y, USHORT color, USHORT* buffer, int nPitch) { buffer[y*nPitch + x] = } 几个函数间唯一不同的就是参数数据类型的不同。 应该还记得对于 8-bit 色彩深度, 间距是以字节表示, 对于 16-bit,间距是以 USHORT 类型表 示。现在,只剩下一个模式没有说了,就是 24-bit 模式。由于没有相 应的数据类型,我们需要分别传递红、绿、蓝三个值,函数看起来应该如下: inline void PlotPixel24(int x, int y, UCHAR r, UCHAR g, UCHAR b, UCHAR* buffer, int byPitch) { int index = y*byPitch + x*3; buffer[index] = buffer[index+1] = buffer[index+2] = } 如你所看到的,它将工作慢一些,因为它多了一次乘法运算,并且有三次内存写操作。你可以用其它方 法替换 x*3 加快一些速度,如(x+x+x)或者 (x&&1)+x,但是不会有太大效果的。当然,她还没有到应该放弃的 地步。现在你就明白了为什么说 24-bit 色彩深度有点儿讨厌了吧! 关注速度 你应该采取一些行动使程序尽可能会的运行。 首先,锁定一个表面并不是最快的,所以你要试图锁定表面上你要操作的最小矩形区域。对于很多操作, 包括很简单的绘制象素演示程序,你都应该锁定最小的矩形区域。 第二,让我们就 640×480×16 模式来说,间距总是 1280 个字节,你应该试图考虑有没有更好的办法表述 它。当然,1280 个字节你是不能改变的,但我们可以使公式最优化,用位移来替代乘法是一贯的加速方法。 我们先前的公式是这样的: buffer[y*nPitch + x] = 如果我们知道 nPitch 将会是 640(由于 nPitch 是 USHORT 类型,不是字节),我们就可以加速它(我们 本来就知道它是 640)。640 不是一个理想的位移 数字,但 512 是 2 的 9 次幂,128 是 2 的 7 次幂,你猜到了 吧,512+128=640。^_^ 很棒吧?我们就可以用下面这个更快的公式取代先前的公式: buffer[(y&&9) + (y&&7) + x] = 多数的解决办法都是分解成 2 的几次幂,有的需要动一点儿脑筋,如 800×600(512+256+32=800), 小菜一碟哦!位移是我们应用的最快的运算符。 最后,如果你要使用两个函数―― 一个做乘法运算,一个做位移运算,要将比较判断放到循环的外部, 不能象下面这样: for (x=0; x&1000; x++) { if (nPitch == 640) PlotPixelFast16(); else PlotPixel16(); } 判断部分使你的优势殆尽,你应该这样做: if (nPitch == 640) { for (x=0; x&1000; x++) PlotPixelFast16( parameters ); } else { for (x=0; x&1000; x++) PlotPixel16( parameters ); } 有意义吧?无论何时用大的循环, 都应该尽量把判断放到循环的外部, 没有必要进行上千次同样的比较判 断。同理,如果你要绘制象素,形成有规律的图案,如水平线或垂直线,甚至是斜线,你都没有必要每一次都 重复确定象素的位置。看看下面的例子,画一条任意颜色的直线: for (x=0; x&640; x++) PlotPixel16(x, 50, color, buffer, pitch); 函数每次都重复计算正确的行,你可以一次就把行指定好。下面是快一点儿的做法: // get the address of the line USHORT* temp = &buffer[50*pitch]; // plot the pixels for (x=0; x&640; x++) { *temp = temp++; } 你可能认为节省这么一点点时间意义不大,但当你进行千万次的循环时,意义就很大了。游戏程序员总是 想办法提高游戏的速度的。 看看以前的文章,我们已经进行了好长时间的铺垫了。现在,我们知道了怎样绘制象素了,让我们看看能用现 在学到的做些什么。 淡出操作 在游戏中最常用到的屏幕操作就是淡出成黑色,或者从黑色淡入。两种方式是同样的机理:你简单画出 你的图象,然后申请一些屏幕转换来改变图象的亮度。对于 淡出,你减少亮度从 100%――0%;对于淡入, 你增加亮度从 0%――100%。 如果你工作在调色板模式, 这很容易做到, 你只要改变你的调色板的颜色就可 以 了。如果你工作在 RGB 模式下,你得考虑一些其它方法。 现在,我将说一说屏幕淡入、淡出相对好一些的方法。你可以使用 Direct3D,它支持 α 混合,先设定每 一帧的纹理,然后设置透明层;或者,更容易的方法,你可以使用 DirectDraw 的 color/gamma 控 制。但是, 如果你仅仅希望屏幕的一部分进行淡入或淡出的操作, 或者淡入或淡出一种非黑色的颜色, 而且你又不是一个 Direct3D 的高手――我本人就不 是!――那么具体做法的手册就在你眼前。现在,你所需要做的最基本的就 是读取每一个你需要控制的象素,然后把它分解成红色、绿色和蓝色,然后你把三个值分 别乘以要淡出或淡 入的级别,再合成 RGB 值,把新的颜色值写回缓冲区。听起来很复杂?别害怕,没有想象的那么坏。看看下面 这段演示代码,它演示了屏幕左上 角 200×200 区域的淡出效果,是 16-bit 色彩深度和 565 格式: void ApplyFade16_565(float pct, USHORT* buffer, int pitch) { int x, UCHAR r, g, USHORT for (y=0; y&200; y++) { for (x=0; x&200; x++) { // first, get the pixel color = buffer[y*pitch + x]; // now extract red, green, and blue r = (color & 0xF800) && 11; g = (color & 0x0730) && 5; b = (color & 0x001F); // apply the fade r = (UCHAR)((float)r * pct); g = (UCHAR)((float)g * pct); b = (UCHAR)((float)b * pct); // write the new color back to the buffer buffer[y*pitch + x] = RGB_16BIT565(r, g, b); } } } 现在,这个函数有很多不稳妥的地方。首先,计算象素的位置公式不但包含在循环中,而且还出现了两 次!你可以在整个程序中只计算它一次,但现在的代码计算 了它 80000 次!下面是你应该做的:在函数的开 始部分,你应该声明一个 USHORT*的变量,让它等于 buffer(如 USHORT* temp = buffer;)。在内部循环里, 增加一个指针使其能得到下一个象素;在外部循环,增加一行(temp+=jump;),使其能转入下一行。下面 是修改后 的代码: void ApplyFade16_565(float pct, USHORT* buffer, int pitch) { int x, UCHAR r, g, USHORT USHORT* temp = int jump = pitch - 200; for (y=0; y&200; y++) { for (x=0; x&200; x++, temp++) // move pointer to next pixel each time { // first, get the pixel color = * // now extract red, green, and blue r = (color & 0xF800) && 11; g = (color & 0x0730) && 5; b = (color & 0x001F); // apply the fade r = (UCHAR)((float)r * pct); g = (UCHAR)((float)g * pct); b = (UCHAR)((float)b * pct); // write the new color back to the buffer *temp = RGB_16BIT565(r, g, b); } // move pointer to beginning of next line temp+= } } 这就好一些了吧!jump 值是 USHORT 类型,是表示从 200 个象素宽的末尾(200 个象素没有占满一行)到 下一行开始的值。尽管如此,对于浮点运算和提取/还原颜色计算并没有提高速度。应该有办法的,看看这个: USHORT clut[65536][20]; 如果你要求一个 DOS 程序员 把这么大的数组放入他的程序中,他可能痛苦的会哭出声来,甚至当场昏死 过去,起码也要加速自然死亡。但在 Windows 中,如果你需要这样做,不会遇到什 么麻烦的。因为你拥有整 个系统的可利用内存。如果把整个的内循环替换成下面这一行,是不是很美妙的一件事呢? *temp = clut[*temp][index]; 这样做,又快了一 些!^_^ 你可以传递一个 0――100 间的整数来替代浮点数传递给函数。如果为 100, 就不需要淡出的操作了,所以就返回“什么事儿也不用做”;如果为 0,就用 ZeroMemory()函数处理所有的 工作好了。另外,把传递的数除以 5,作为数组的第二个下标。 如果你对于我知道查询表的尺寸感 到好奇,我就告诉你好了,65536 是 2 的 16 次幂,所以在 16-bit 模 式下,就有 65536 种颜色。既然我们的颜色值是无符号的值,它们的范围从 0 ――65535,那么我们就用 20 作为淡出的增量值好了,反正考虑到相关的内存,我觉得挺合适的。 对于 24-bit 和 32-bit 模式,你显然不能直接使用颜色查询表,因为数组太巨大了,所以你只有使用三个小一 点的数组: UCHAR red[256]; UCHAR green[256]; UCHAR blue[256]; 然后,每当你读取一个象素,就把它分解出的颜色值放入相应的数组,使其形成自己的查询表,经过变 化,再组合到一起,得到 RGB 色彩值。有很多办法可以实 现程序的优化,最好的办法是根据你的目的不断地 测试哪一种是最适合你的程序的,然后总结经验,记住它。我下面将简单的介绍一下你可能用得着的其它的转 换。 透明操作 把一个透明的图象覆盖在非透明的图象上,你就不能使用颜色查询表了,因为它总共需要有 65536 个查 询表,一台普通的电脑就需要 8.6GB 的内存来处理 这个庞然大物。^_^ 所以你不得不计算每一个象素。我将 给你一个基本的思路。假设你要用图象 A 覆盖图象 B,图象 A 的透明百分比为 pct,这是一个 0――1 之间的浮 点数,当为 0 时是完全不可见的,当为 1 时是完全可见的。那么,让我们把图象 A 的象素称作 pixelA,相对 应,图象 B 的象素称作 pixelB。你将应用下面这个公式: color = (pixelA * pct) + (pixelB * (1-pct)); 基本上,这是一个两个象素颜色的平均值。所以,你实际上看到每个象素有 6 个浮点乘法运算。你可以用 一些小型的查询表降低你的工作量。你真的应该试一试! 你或许想做的另一件事情是建立一个部分透明的纯色窗口。 那种效果用一个颜色查询表完全可以达到。 因 为对于“地球人”,我只需要为屏幕上可能出现的颜色提供蓝色。实际上,我就是用查询表完成的。我将告诉 你我实际的意思: void Init_CLUT(void) { int x, y, UCHAR r, g, // calculate textbox transparency CLUT for (x=0; x&65536; x++) { // transform RGB data if (color_depth == 15) { r = (UCHAR)((x & 0x7C00) && 10); g = (UCHAR)((x & 0x03E0) && 5); b = (UCHAR)(x & 0x001F); } else // color_depth must be 16 { r = (UCHAR)((x & 0xF800) && 11); g = (UCHAR)((x & 0x07E0) && 6); // shifting 6 bits instead of 5 to put green b = (UCHAR)(x & 0x001F); // on a 0-31 scale instead of 0-63 } // find brightness as a weighted average y = (int)r + (int)g + (int)b; bright = (int)((float)r * ((float)r/(float)y) + (float)g * ((float)g/(float)y) + (float)b * ((float)b/(float)y) + .5f); // write CLUT entry as 1 + one half of brightness clut[x] = (USHORT)(1 + (bright&&1)); } } 这段代码来源于“地球人”,用查询表创建了一个文本框。为了安全起见,随处都使用了类型修饰。这 段代码还能再快一些,但我没有很认真的优化,因为我只在 游戏的最开始的部分调用了它一次。首先,红、 绿、蓝的亮度值被提取出来,由于是 16-bit 模式,注意我们用了一个 color_depth 变量检测了显示 卡是 555 还是 565 格式。然后,用下面公式计算了象素的亮度: y = r + g + brightness = r*(r/y) + g*(g/y) + b*(b/y); 这是一个理想的平均值。我不能确定是否颜色亮度值这样得到就正确,但它看起来符合逻辑,并且实际 效果很好。在公式的最后我加了一个.5,因为当你把浮点 数变为整数时,小数部分被去掉,加上.5 使其凑整。 最后,我把亮度除以 2 再加上 1,这样不会使文本框太亮,加 1 使文本框不会全黑。由于 16-bit 模式的 低位 是蓝色,我可以只把颜色设置为蓝色, 就不用宏了。理解了吗?最后,结束之前,我给你演示怎样创建文本框: int Text_Box(USHORT *ptr, int pitch, LPRECT box) { int x, y, RECT // leave room for the border SetRect(&ibox, box-&left+3, box-&top+3, box-&right-3, box-&bottom-3); // update surface pointer and jump distance ptr += (ibox.top * pitch + ibox.left); jump = pitch - (ibox.right - ibox.left); // use CLUT to apply transparency for (y=ibox. y&ibox. y++) { for (x=ibox. x&ibox. x++, ptr++) *ptr = clut[*ptr]; ptr += } return(TRUE); } 这就是一个查询表,看起来更象淡出操作的代码了,就是查询表的控制值与前面的不一样了。这里用一个 计算代替了 20。 顺便说一下,对于查询表的一个声明,象下面这个: USHORT clut[65536]; 总结 本文是为以象素为基础的图形服务的。下一章,我们将学习位图的知识。不管你信不信,使用位图要比象 素简单多了,以后你就知道了。下一篇文章将是学习 DirectX 基础知识的最后一章,在此之后,我们将编写一 个 RPG 游戏。细节到时候你就知道了。 22:17 | 添加评论 | 固定链接 | 引用通告 (0) | 记录它 | DirectDraw 的调 色板和象素 DirectX 入门 DirectX 入门☆ 简介 今天我们要接触到令人 敬畏的 DirectX。它比 Windows GDI 要快好几倍, 可用于不同的语言和多种平台,支持从绘制象素到高级 3D 图象,从播放简单声 音到数字音乐,从键盘控制到反震手柄??它给你游戏编程所需 的一切(有点 夸张)。当然了,它是巨大的,需要好几本书才能含盖它的全部。先不要去担心 我在这里所教给你之外的数不清的知识,毕竟我把你推到了起跑线上。 阅读本章, 你需要前几章的知识和 C 语言的知识,由于我们还要谈到组件对 象模型(COM),它是面向对象系统的基础,你最好还要有一点儿 C++的知识。 没有也不太要紧,我在讲到这处时会照顾你的。反正你记住,使用 DirectX 并不 需要多少 C++的知识。开始吧! ☆ 什么是 DirectX? DirectX 是游戏制作者的 API(Application Development Interface)。 它是一组允许你直接控制计算机硬件设备的软件。如果你的硬件支持 DirectX, 并且你用硬件加速你的程序,这就意味着一个字―― 快。不用担心你的硬件知 识, 你不会真正的接触到它们。 我们是通过硬件抽象层 (HAL) 和硬件仿真层 (HEL) 来保证设备无关性和让你的程序正常运行。 DirectX 由很多组件构成,每一个都有特定的用途。组件 DirectDraw 是最 为重要的一个,因为所有的图形都要用到它,它是 2D 图形的引擎,3D 图形也同 样离不开它。DirectDraw 是我们今天就要说的。其它的组件是: ▲ DirectSound:提供硬件和软件的声音混合与回放。 ▲ DirectMusic:处理基于消息的音乐数据。它支持乐器数字接口(MIDI) 并为创建交互式音乐提供创作工具。 ▲ DirectPlay:使得通过调制解调器链接或通过网络来与应用程序相连成 为可能。 ▲ Direct3D:是一个三维图形包,它提供一个高级的保留模式(Retained Mode)接口,这使得你能够实现一个完整的三维图形系统。它还包含一个低级的 即时模式(Immediate Mode)接口,使得应用程序获得对渲染管线的完全控制。 ▲ DirectInput:为包括游戏杆、鼠标、键盘和游戏控制器在内的输入设备 提供支持。它还为反馈游戏设备提供支持。 ▲ DirectSetup:为 DirectX 提供了一个简单的安装过程。它简化了更新显 示和音频驱动程序的过程,并且确保没有硬件或软件冲突的存在。 ▲ AutoPlay:让你能够制作一张一旦插入驱动器就能自动安装的光盘。 AutoPlay 并非 DirectX 所独有,因为它是 Microsoft Win32 API 的一部分。 组件对象模型(COM)是 DirectX 的基础,有一些技巧建立 COM 对象――别 问我怎么做――但你知道一点点还是有好处的。我只是简单说一下,如果你 有 兴趣,具体的细节就自己查资料吧!可能下一节你有些困惑,但不要紧,我所说 的你不用太明白,毕竟我们的目的是使用 COM 对象,这可比创建容易多了。 ☆ 组件对象模型(COM) COM 接口是 DirectX 技术的基础,没有 COM 就没有 DirectX。(不用担心, 你只需要对 COM 技术有一个粗浅的了解就可以使用 DirectX――只要你在编写 DirectX 应用程序时遵循一定的步骤,甚至都可以在不了解 COM 的情况下使用 DirectX。 DirectX 的大多数 API 都是基于 COM 结构的。COM 为软件模块化和软件重用 提供了最坚实的基础,它的最重要的概念就是接口(interface),接口是软件 重用的最基本方法。更专业的说,接口是一系列操作的规范描述,即接口规范。 所有的 COM 接口都是从 Iunknown 接口继承而来的, IUnknown 接口是所有 COM 接口的根。IUnknown 接口具有 3 个方法: ? QueryInterface():此方法查询新接口,并在新接口存在时返回之。 ? AddRef():此方法在接口或其它应用程序连编到此 COM 对象上时将引用 计数值递加 1。 ? Release():此方法将 COM 对象的引用计数递减 1。当引用计数递减到 0 时,该 COM 对象自动释放。 所有 COM 对象都具有这三个方法。 虽然 DirectX 应用程序一般不需要考虑引 用计数的问题,但引用计数确实

我要回帖

更多关于 conime.exe是什么进程 的文章

 

随机推荐