(译者语录: 原文里面使用CreateOffscreenPlainSurface生成一个离屏的页面, 然后用loadSurface载入我们需要的2D图片, 然后再将其贴到离屏页面上去的. 但是这种方法有一个缺点, 就是不支持关键色的选取, 还有就是不支持alpha通道. 所以我不打算在这里介绍原书里面的方法去渲染2D图片, 而是改用D3DXSprite来完成我们的工作.)
使用DirectX9来渲染2D图片并不是一件困难的事情. 本章中我们将会学习一下如果载入一副图片并在不同的位置, 用不同的透明度, 以不同的大小在屏幕上面显示这副图片.
本章包含的主题有:
我们为什么需要在Direct3D里面渲染2D图片?
生成我们的2D精灵类
利用我们的2D精灵类在屏幕上面显示一些图片
DirectX可以用离屏页面处理2D图片的渲染工作, 但是正如前面所说这种方法限制多多, 幸运的是DirectX还提供了2D精灵来完成这一项任务. 我们可以生成一个2D精灵的对象实例, 载入图片到精灵的纹理中, 并在需要的时候利用精灵设备在后台缓存器里面渲染我们的图片. 当渲染的工作完成之后, 缓存区将会被显示在屏幕上面, 我们的图片也会在我们希望的位置显示出来. 在渲染2D图片的时候你需要注意两件事情,一是当渲染多幅图片的时候, 如果有两幅图片的位置有所重叠, 那么后渲染的图片可能会覆盖遮住前一副被渲染的图片, 所以选择图片渲染的的顺序是非常重要的. 第二件事情就是超出缓存矩形区域部分的图片是不会显示在屏幕上面的. 这将意味着如果你的精灵图片如果和屏幕的边框重合了的花, 你图片某些部分将不会被显示出来, 除非你调整了目标矩形区域的大小已使它们不再重合.
让我们先从第三章的代码里面来看看Sprite类. Sprite类的头文件和你在前一章里面看到的那些个头文件是一样的. 它包含了类的定义, 函数和私有变量的声明定义. 所以我将不会再次把头文件的内容打印出来, 除非我们需要运用一些新的概念. 我建议大家先看看头文件的内容, 如果你想要继续下面的学习, 将类的头文件拷贝并复制到头文件夹里面去并不是一件难为情的事情, 毕竟里面有的只是对变量和函数的定义和声明而已. 我同样将跳过构造函数和析构函数, 因为对于我们所编写的所有的类, 他们的任务和功能都是完全想同的. 那就是在使用对象前清空我们的对象, 并在使用完以后之后将这些内存释放.
首先我们看到的函数便是负责载入图片的函数loadSprite. loadSprite有两个参量, 我们的设备对象和想要载入的图片文件地址. 首先我们在loadSprite里面调用D3DXCreateSprite来生成一个2D精灵. D3DXCreateSprite需要两个参数, 第一个是我们的D3D设备对象实例, 2D精灵便是由这个设备生成的, 第二个是指向我们的2D精灵的指针, 函数调用成功以后生成的2D精灵便是存放在这个地址里面的. 接下来的是一个D3DXIMAGE_INFO类型临时变量imageInfo, 用来在载入图片的时候记录图片的一些基本信息, 如高和宽. 下面最最重要的一个环节便是调用函数D3DXCreateTextureFromFileEx从我们指定的地址载入图片. D3DXCreateTextureFromFileEx有14个参数, 第十一个参数是颜色关键值, 此值所指定的颜色在我们载入图片时将会被透明色所取代, 第十二个参数是我们用来存放图片信息变量的地址, 最后一个参数是一个指向D3D纹理的指针, 这个指针所指向的D3D纹理就是我们调用的函数所生成的用来存放被载入的图片. 我们用hr记录函数的返回值, 如果载入图片失败, 在loadSprite返回false, 否则的话记录图片的高度和宽度, 并将显示图片的位置和大小初始化.
bool Sprite::loadSprite(LPDIRECT3DDEVICE9 device, std::string filename)
{
D3DXCreateSprite(device, &sprite); //调用函数生成一个2D精灵
D3DXIMAGE_INFO imageInfo; //记录载入图片的信息
HRESULT hr = D3DXCreateTextureFromFileEx(device, //D3D设备对象
filename.c_str(), // 载入图片的地址
D3DX_DEFAULT, // 图片的宽,
D3DX_DEFAULT, // 图片的高
D3DX_DEFAULT, // 图片的miplevels
0,
D3DFMT_UNKNOWN,
D3DPOOL_DEFAULT,
D3DX_FILTER_NONE,
D3DX_FILTER_NONE,
0xFF000000, // 关键颜色值(黑色), 将载入的图片里面的关键颜色值设置为透明色
&imageInfo, //记录载入图片的信息
NULL, //记录载入调色板的信息
&texture); // 用来储存载入图片的纹理对象实例
if( FAILED(hr))
{
return false;
}
height = (float)imageInfo.Height;
width = (float)imageInfo.Width;
position.x = 0.0f;
position.y = 0.0f;
position.z = 0.0f;
scale.x = 1.0f;
scale.y = 1.0f;
scale.z = 0.0f;
return true;
}
下面我们将会看到的函数便是渲染函数render. render函数负责将我们的图片拷贝到缓存区, 所以需要一个D3D设备对象作为自己的参数, 当然因为我们还想利用alpha通道来显示我们的图片, 所以render的第二个参数就是整数alpha, alpha在0到100之间, 表示的不透明的百分比. 在render函数中, 我们首先是将alpha值改变为0-255之间, 因为我们显示的颜色格式中alpha占8个比特. 接下来就是设置屏幕上面用来显示图片的矩形区域的大小和位置, 这里我们调用D3DXMatrixScaling函数来生成放缩矩阵, 调用D3DXMatrixTranslation来生成平移矩阵, 然后的要用D3DXMatrixMultiply来得到总的转换矩阵, 然后设置转换, 接着就是渲染我们的图片了. 在渲染之前通过调用Begin函数来准备一个设备以用来渲染图片, 渲染之后通过调用End函数来通知精灵设备我们的图片渲染完成了. 渲染时调用的是Draw函数, 这个函数共有5个参数, 最后一个参数表示我们在渲染图片对图片颜色所作的修正, 一般使用白色(255, 255, 255)表示不对颜色纠正, 而最后一项alpha则表示对透明度所作的修正.
void Sprite::render(LPDIRECT3DDEVICE9 pDevice, int alpha)
{
// 设置渲染图片是的alpha值
alpha = (int)(255*((float)alpha/100));
D3DXMATRIX scaleMatrix;
D3DXMATRIX transMatrix;
//放缩图片
D3DXMatrixScaling(&scaleMatrix, scale.x, scale.y, scale.z);
// 平移图片到我们的指定位置
D3DXMatrixTranslation(&transMatrix, position.x, position.y, position.z);
// 生成图片大小位置转换的矩阵
D3DXMatrixMultiply(&transMatrix, &scaleMatrix, &transMatrix);
// 设置图片大小位置的转换
sprite->SetTransform(&transMatrix);
//准备一个设备以用来渲染图片
sprite->Begin(D3DXSPRITE_ALPHABLEND);
// 渲染图片
sprite->Draw(texture, // 一个精灵纹理的实例
NULL, // 我们将要渲染的图片部分所在的矩形区域, NULL表示我们将要渲染整副图片
NULL, //指定精灵的位置, NULL表示点(0,0,0)为精灵的中心
NULL, //指定精灵的位置, NULL表示精灵的位置是点(0,0,0)
D3DCOLOR_RGBA(255,255,255,alpha)); //图片的颜色和alpha值被此值修正
sprite->End(); ////通知精灵设备我们的图片渲染完成了,
}
屏幕上面用来显示图片的矩形区域控制了我们的图片将会被拷贝到缓存区的什么部分. setPosition函数设置这个显示矩形区域左上角的坐标位置.
void Sprite::setPosition(int x, int y)
{
position.x = (float)x;
position.y = (float)y;
position.z = 0.0f;
}
setSize函数设置了D3DX的尺度矩阵的数值, 在渲染2D图片之前, 我们可以调用这个函数来设置屏幕上面用来显示图片的矩形区域的大小. percent是一个整数, 代表了显示的矩形相对原图片矩形比例的百分数.
void Sprite::setSize(int percent)
{
scale.x = (float)percent / 100.0f;
scale.y = (float)percent / 100.0f;
scale.z = 0;
}
接下来的两个函数是getWidth和getHeight, 有些时候我们可能需要得到2D精灵的实际尺寸, 所以可以通过调用这两个函数来获得.
int Sprite::getWidth()
{
return (int)width;
}
int Sprite::getHeight()
{
return (int)height;
}
以上介绍的便是我们的Sprite类了, 现在我们把它添加进GameMain类中, 并测试一下其对2D图片的渲染功能.
首先我们在GameMain头文件的开头增加一个类的声明, 并在类定义中增加一个私有变量来储存我们的2D精灵.
#pragma once
class dxMgr;
class dxText;
class Sprite;
....
private:
dxMgr* dxManager;
dxText* textManager;
Sprite* testSprite;
};
然后我们在初始化函数init中生成一个sprite的实例, 并载入图片"testImage.jpg"
bool GameMain::init(HWND wndHandle)
{
.....
//生成一个实例来测试我们新的2D精灵类
testSprite = new Sprite();
testSprite->loadSprite(dxManager->getD3DDevice(),"testImage.jpg");
if (!testSprite)
{
return false;
}
return true;
}
既然一个2D精灵类来渲染图片已经被生成了, 接下来需要做的便是在更新函数update里面渲染我们的图片. 我已经增加了2个调用来显示我们载入的图片, 一个以100%的比例, 70%的不透明度, 另外一个用了250%的比例, 100%的不透明度. 第一个由于设置了关键色作为透明色并以70%的不透明度来显示图片, 所以图片后面的文字"Hello World"还是部分可见的.
void GameMain::update(void)
{
//调用更新函数
// 开始渲染
dxManager->beginRender();
// 在这里调用游戏渲染函数
textManager->drawText("Hello World", 30, 30, 300, 300);
testSprite->setSize(100);
testSprite->setPosition(30,10);
testSprite->render(dxManager->getD3DDevice(), 70);
testSprite->setSize(250);
testSprite->setPosition(200,300);
testSprite->render(dxManager->getD3DDevice(), 100);
// 渲染结束
dxManager->endRender();
}
这章的内容我们已经全部介绍完了, 编译并运行你的项目, 你会在屏幕上面看到两只可爱的小蚊子. 一个在左上角, 另一个在屏幕中间接近底部的蚊子要稍微大上那么一点点.
第三章小结
本章中我们生成了一个新的类来载入并在屏幕上面渲染2D图片. 需要注意的是图片不能超过缓存区域的边界. 你可以通过调整源图片区域和目标图片区域来解决这个问题. 在继续下面的内容之前, 你可以试着修改关键色的颜色值, 或是将其设置为loadSprite的一个参数值, 这将会是一件很有意义的工作的. 你可能需要一些尝试才能够正确的设置你的颜色值, 颜色值0代表黑色, 最大的值255代表白色. 同样通过setPosition和setSize来改变显示的区域大小和位置, 或是修改render函数里面sprite->draw函数的第二个参数来达到仅在屏幕上面显示图片的部分区域.
(我写呀写呀写, mm没有上线, 我能做的只是不停的写, 来给自己找些事情做做而已仄. 呵呵, 快写完的时候mm也上线了, 真开心, 虽然也许不会聊上很多, 但是至少我知道她在那里, 在电脑前面坐着, 也在用功的写着东西, 也在辛苦的为老板工作, 至少在她闷的时候可以找我聊聊天, 至少我能够看到她在线的头像, 至少.... , 其实这些已经足够足够了, 我做人不贪心的, 知道这些我已经很欣慰了^_^. 这一章的代码是我根据原作者所提供的进行修改而得到的, 虽然简单但是却实现了大部分重要的功能, 你可以试着修改完善其中的一些部分, 或是根据你的需要来修改. 译者对这一部分进行了详细的研究和修改, 并在此基础上面制作了两个2D的小游戏, 唯一的缺点便是由此制作的2D游戏都必须安装Direct9.0才可以运行, 否则运行时系统可能说缺少d3d9.dll或是d3dx9_27.dll文件, 并且好像是驱动的版本越高越好, 游戏的FPS就越小越慢, 实在是不知道何原因, 难道对3D的支持提高了, 相应的2D支持就减少了???).
ps:有谁知道这是哪本书的请告知书名,谢谢.