您的位置:首页 > 理论基础 > 数据结构算法

使用D3D8实现2D图形显示技术

2008-05-22 22:38 501 查看

使用D3D8实现2D图形显示技术(一)

作者:Lacutis

前言
DirectDraw已经死了,所以为什么还要用DirectDraw?从DirectX8.0开始,微软提供了3D技术可以直接用于2D图形程序设计。你可以注意到DirectDraw7的有关文档从DirectX的文档中取消了。

当然2D图形程序设计还是可以实现,使用现有的3D图像技术,更多的2D特效能在轻易实现,这个教程不会给你任何特效,只是给你最基本的2D图形显示概要。

章节概要
这个教程是不完全的,因为我用的是IDirect3D8做范例,最后才发现某些东西并不使是我想象的那么好,因为有些和DirectDraw类似的东西并不是我想象的那样,Direct3D9界面中很多东西正是我想要的,但是下次再说这个。

微软的帮助文件
微软的帮助文件,象DirectDraw7,DirectX8.0,DirectX8.1,还有DirectX9.0,可以在这里找到http://msdn.microsoft.com/archive/,英文的,没办法。

所需头文件
在写码之前,你必须包括些头文件。两个头文件是最常用的,所以我在需要这两个头文件的源码中加入这个。

#include <d3d8.h>
#include <d3dx8.h> 

IDirect3D8界面
如果要使用Direct3D8,你就必须先建立IDirectD8界面对象。不同于DirectDraw,和其他早期的D3D界面那样。用起来很麻烦,现在,只需要简单的一句就能建立IDirect3D8界面对象。

m_pD3D = Direct3DCreate8(D3D_SDK_VERSION);
if (m_pD3D == NULL)
{
::MessageBox(hWnd,
"ERROR: Failed to acquire IDirect3D8 interface instance.",
"ERROR", MB_OK);
return E_FAIL;


D3D_SDK_VERSION是什么,是一个常量,用来保证建立D3D界面时使用的DirectX头文件版本是正确版本。这个在微软的文档中都有定义。下一步是定义或是提取一个IDirect3DDevice8界面对象的指针,用这个指针来初始化显示,建立主表面和背景表面。

建立IDirect3DDevice8界面对象
在建立这个界面之前,你必须测试电脑硬件是否支持你要建立的显示模式。所谓显示模式有两种定义,一是视窗程序模式,你写的程序在一个视窗里运行。另一种就是常用的全屏模式。前一种格式,显示格式依赖于系统设定的显示模式,也就是说,你把桌面设定为1024X768X16,你可以设定你自己的应用程序视窗的长宽,但是不能改变你视窗程序的像素深度。如果你使用全屏模式,你可以设定长宽和象素深度,你还是需要测试硬件对你的设定是否支持。

做到这一点其实很容易,进入Direct3D8以后,这些比较麻烦的操作都变得简单了,首先你要做的,是调用GetDisplayMode这个函数,我是这么做的

D3DDISPLAYMODE display_mode;
HRESULT hRet = m_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT,
&display_mode);
if(FAILED(hRet))
{
::MessageBox(hWnd,
"ERROR: Failed to acquire current display mode.",
"ERROR", MB_OK);
return hRet;


调用这个函数返回D3DDISPLAYMODE数据结构,这里面的数据可以用来进行你的测试。看看这个结构中的数据:

typedef struct _D3DDISPLAYMODE {
UINT Width;
UINT Height;
UINT RefreshRate;
D3DFORMAT Format;
} D3DDISPLAYMODE; 
 

前面两个很容易,是你的桌面的长宽,第三个是屏幕刷新率,最后一个像素的深度,这是一个自定义的数据,enum type。在我的程序中,我只是简单地比较这个数据来测定现有的桌面像素深度,就这样:

// OK I want 32Bit color depth in the format of X8R8G8B8.
if (display_mode.Format != D3DFMT_X8R8G8B8)
{
// All right, now you tell the user to change their display mode,
::MessageBox(hWnd,
"ERROR: Display Resolution is not up to spec. Pixel resolution should be 32bit.",
"ERROR", MB_OK);
return hRet;


D3DFMT_X8R8G8B8的定义是,表面的象素深度是4字节,最左边的字节是未知象素深度,接下来是红色像素深度,绿色像素深度,和蓝色像素深度。其他数据还有D3DFMT_A8R8G8B8,D3DFMT_R8G8B8,D3DFMT_R5G6B5等等等。我这种做法其实是一种白痴懒汉的做法,真正的做法是再调用CheckDeviceFormat来确定硬件是否支持你的主表面和背景表面格式。还有EnumAdapterModes来给你硬件所支持的一系列的显示格式。

下一步是建立IDirect3DDevice8界面对象,你需要初始化一个数据结构,这个数据结构是用于定义显示模式,这个结构是D3DPRESENT_PARAMETERS,主要的目的2D图形显示,所以这个结构中很多数据你都不用定义。我的定义是:

ZeroMemory(&m_d3dpp, sizeof(m_d3dpp));
// Throw away previous frames, we don't need them
m_d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
m_d3dpp.hDeviceWindow = hWnd;
//We only need a single back buffer
m_d3dpp.BackBufferCount= 1; 
// even though you uses windowed mode, we can still
// set the backbuffer width and height.
m_d3dpp.BackBufferWidth = 640;
m_d3dpp.BackBufferHeight = 480;

m_d3dpp.Windowed = TRUE;
m_d3dpp.BackBufferFormat = display_mode.Format;
m_d3dpp.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;

hRet = m_pD3D->CreateDevice(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL, // we want full hardware support.
hWnd,
// we are not doing vertex processing.
D3DCREATE_MIXED_VERTEXPROCESSING,
&m_d3dpp,
&m_pD3DDevice);
if(FAILED(hRet))
{
::MessageBox(hWnd,
"ERROR: Failed to acquire IDitect3DDevice instance.",
"ERROR", MB_OK);
return hRet;
}
 

看看我都定义了些什么。首先是D3DPRESENT_PARAMETERS::SwapEffect,我定义为D3DSWAPEFFECT_DISCARD,也就是每次的表面翻页后,背景表面的数据都给丢弃。

然后我定义D3DPRESENT_PARAMETERS::hDeviceWindow为程序的视窗ID,也就是程序视窗的HWND数值。

D3DPRESENT_PARAMETERS::BackBufferCount定义背景表面的数量,我设定这个为1。

虽然我设计的这个例程是视窗模式,但是主表面的长宽还是可以定义的。所以我将主表面和背景表面的大小定义为640X480。

然后,我定义显示方式为视窗显示模式,然后设定背景显视模式为X8R8G8B8,看来我前面说的是错的,你在视窗模式下可以定义像素的深度。我只是没有进一步考证这个问题。

最后也是最重要的一步是定义
D3DPRESENT_PARAMETERS::Flags数据为D3DPRESENTFLAG_LOCKABLE_BACKBUFFER,这么做的原因是,如果步定义这个,你就无法直接将数据写上背景表面上。所有DirectDraw能进行的工序都做不了。

到这里了,最后一道工序是调用IDirect3D8的CreateDevice函数,第一个参数是主显卡的ID,这个数值永远是D3DADAPTER_DEFAULT,第二个参数是视窗的ID,也就是视窗的HWND数值。第三个参数设定3D顶点的处理方式,也就是用硬件处理还是软件处理,还是混合,因为我们的程序中可能回会用道这个,我把它设定为D3DCREATE_MIXED_VERTEXPROCESSING。第四个参数是我定义的D3DPRESENT_PARAMETERS结构地址,最后是返回IDirect3DDevice8的地址。只要所有的函数调用都没有问题,到此为止,所有的初始化步骤完成。

如何清理背景表面
这个很简单,你只要这么做:

m_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET,
D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0); 

Clear是专门用来清理表面,用于做显示前的前期准备。第一个参数是指定多少个长方形需要清理,第二个是一个指针指向一个长方体数组,第一个参数就是定义这个数组的大小。第三个参数定义清除的目标,我将这个设为D3DCLEAR_TARGET,还有两个不同的常量,D3DCLEAR_STENCIL和D3DCLEAR_ZBUFFER,都是和3D图形程序设计有关,我在这里只考虑2D有关的东西,所以这两个常量是没用的。第四个参数是填上表面的颜色,我定义为漆黑色,第五个是Z值,0.0是深度离盯着屏幕的观众最近,1.0是最远,这里任何数值都无所谓,所以我用1.
d93c
0。最后一个是Stencil数值,这个也无所谓,所以我给它0。

如何显示准备好的背景表面
这个也会很简单,只要这么做就可以了:

m_pD3DDevice->Present(NULL, NULL, NULL, NULL); 

这个函数需要四个参数,第一个是原表面的长方形,如果是整个视窗显示,NULL就够了。第二个是目标表面的长方形,如果是全视窗更新,调入值也是NULL。第三个,告诉IDirect3DDevice你想要显示的视窗HWND数值。这个,你在前面早就定义,就是你在D3DPRESENT_PARAMETERS::hDeviceWindow值,除非你想将画面贴到不相关的视窗上,你只要调入NULL就可以。最后一个根本没有用,所以调入NULL。

如何设定储藏表面,用来存放图片
这个也很简单,你如果需要表面来存储图片,必须使用IDirect3DSurface8,嘿嘿,现在可以看出来DirectDraw现在根本没有用了吧?现在给你我找到的问题,使用Direct3D8进行2D图形显示,并不是很好,特别是模仿DirectDraw有关的工序。这个先不说,先讲讲如何建立这个IDirect3DSurface8界面对象。

首先你要定义一个指针:

IDirect3DSurface8 * m_pD3DSurface; 

然后,你要建立这个表面,下面的代码可以证明建立一个表面是多么简单:

HRESULT hRet = pDevice->CreateImageSurface(img_size_x, img_size_y,
D3DFMT_X8R8G8B8, &m_pD3DSurface);
if (FAILED (hRet))
{
return hRet;


四个参数很容易理解吧?第一个是表面的长,第二个是表面的宽,第三个是表面的像素深度,最后一个是返回的表面指针。是不是很简单?

然后你要将图片载入表面,所需工序也很简单:

return D3DXLoadSurfaceFromFile(m_pD3DSurface,
NULL,
NULL,
fn,
NULL,
D3DX_DEFAULT,
col_key,
NULL); 

很简单吧,这个函数是Direct3D扩展库里的帮助函数,可以用来提取各种各样的图像文件格式,象BMP,JPG,GIF,PCX,等等。你不仅能从文件里载入图片,还能从内存中载入图片文件,甚至能从内存中载入图片,这些函数是:

D3DXLoadSurfaceFromFile()
D3DXLoadSurfaceFromFileInMemory()
D3DXLoadSurfaceFromMemory()
D3DXLoadSurfaceFromResource()
D3DXLoadSurfaceFromSurface() 

这些都在d3dx8.h里面,我用的是第一个函数,D3DXLoadSurfaceFromFile()。这个函数所需的参数很多,第一个是载入表面的指针,第二个是指向调色板的指针,这个主要是用于象素为八位的图形格式,不知道谁还用这个,所以,我个人觉得没用,我给了NULL,第三个参数定义表面上你想要存储图像的长方形,我只定义整个表面的,所以不需要定义长方形,给它NULL。第四个是图像文件的文件名。第五个是图形文件中的你想要的长方形,我想载入整个文件,所以给它NULL。第六个是载入文件时进行过滤的风格,我用的是最慢的一种,D3DX_FILTER_TRIANGLE | D3DX_FILTER_DITHER。还有其他风格你可以使用,这里就不多介绍,通过微软文档,你可以了解这些风格的使用方法。然后你定义透明色,这个颜色用来定义绘画时被忽略的颜色,这个颜色是D3DCOLOR。

我前面没有介绍D3DCOLOR,D3DCOLOR就是DWORD,四个字节。第一个字节是颜色的透明度,0x0(0)表示完全透明,0xFF(255)表示完全不透明;第二个字节是红色的深度,0x0是最浅,0xFF 是最深;第三个字节是绿色的深度,0x0是最浅,0xFF是最深;第三个字节是蓝色的深度,0x0是最浅,0xFF是最深。 
举例:0xFF000000才是表示漆黑色。0x00000000不是漆黑色。
 

最后一个参数是指向一个D3DXIMAGE_INFO,你可以定义这个参数,然后调入函数,也可以设定为NULL,这个参数是调入返回型,也就是说你给定了一个变量,调入后,如果函数运行成功,这个变量的数据会改变并返回。

将储藏表面上的画面贴到背景表面上,然后翻页
做到这一步其实也很简单,但是功能很有限,所以我不喜欢用DirectDraw风格的进行贴图。IDirect3DDevice8只给你提供了一个函数,叫CopyRect(),这个函数只能进行原图贴图,不能扩大缩小贴图,也不能使用透明色贴图,所以我很失望。

实现这一步很容易,第一是获得背景表面的指针,这是你要贴的目标表面,原表面是你的储藏表面。得到背景表面后,使用IDirect3DDevice8::CopyRect来将你要的图片长方形贴到背景表面上。我们来看看具体的操作:

HRESULT D3DDrawHelper::Draw(IDirect3DSurface8 * pSurface,
const RECT * p_src_rect,
const POINT * p_dest_pt)
{
if (pSurface == NULL
|| p_src_rect == NULL
|| p_dest_pt == NULL)
return E_FAIL;

IDirect3DSurface8 * pBackBuffer;
HRESULT hRet = m_pD3DDevice->GetBackBuffer(0,
D3DBACKBUFFER_TYPE_MONO, &pBackBuffer);
if (FAILED (hRet))
{
::MessageBox(NULL, "Failed 1", "ERROR", MB_OK);
return hRet;
}

hRet = m_pD3DDevice->CopyRects(pSurface,
p_src_rect,
1,
pBackBuffer,
p_dest_pt);
if (FAILED (hRet))
{
if (hRet == D3DERR_INVALIDCALL)
::MessageBox(NULL, "Failed 2", "ERROR", MB_OK);
return hRet;
}

return S_OK;


首先是获得背景表面,也就是使用IDirect3DDevice8::GetBackBuffer,这个函数需要3个参数,第一个是背景表面的下标,这里的原因是,你可以拥有多于一个的背景表面,所以需要一个下标来获取你所需要的背景表面。第二个是表面的样式,这个只有一和有效数值D3DBACKBUFFER_TYPE_MONO,因为D3DBACKBUFFER_TYPE_STEREO在IDirect3D8里是不支持的。

下一个函数是CopyRect,将一块表面上的图像长方形贴到另一块表面上。这个函数的长处是,你可以定义好多不同的长方形于一个数组,然后,定义你的长方形的数量,CopyRect会自动替你重复贴图。将一连串的长方形图像从一个表面贴到另一表面上。

但是,这个函数不支持透明色,不支持扩大缩小图像,所以使用这个函数能做到的功能很小,先谈谈它所需要的参数,第一个是存储表面的指针,从这个表面,图像长方形被贴到目标表面上。第二个是长方形数组,这个数组定义原图上需要的长方形。第三个是数组大小,我直接用和一个长方形,所以数组大小只是一。第四个是目标表面,第五个是目标表面的点数组,这个数组是个和前面说的长方形数组是平行的,数组的大小和前面的也是一样的,之所以使用点是因为长方形数组已经定义了长方形的大小。

好了,到这里,你就能进行翻页了,怎么翻页呢?前面说到的IDirect3DDevice8::Present()就是用来进行这样的操作。

下次讲讲如何超越这个局限达到更好的2D图形特效。

例子源码:
D3DDrawHelper 
头文件:

#ifndef D3D_DRAW_HELPER_H_
#define D3D_DRAW_HELPER_H_

#include <d3d8.h>
#include <d3dx8.h>

class D3DDrawHelper
{
public:
D3DDrawHelper();
virtual ~D3DDrawHelper();

HRESULT Init(HWND hWnd);
void Destroy();

HRESULT Blank();
HRESULT Display();

inline IDirect3DDevice8 * GetDevice()
{ return m_pD3DDevice; }

HRESULT Draw(IDirect3DSurface8 * pSurface, const RECT * p_src_rect,
const POINT * p_dest_pt);

protected:
IDirect3D8 * m_pD3D;
IDirect3DDevice8 * m_pD3DDevice;
D3DPRESENT_PARAMETERS m_d3dpp;
};

#endif

源码文件:

#include "StdAfx.h"
#include "d3d_draw_helper.h"

D3DDrawHelper::D3DDrawHelper()
: m_pD3DDevice(NULL)
{
ZeroMemory(&m_d3dpp, sizeof(m_d3dpp));
}

D3DDrawHelper::~D3DDrawHelper()
{
Destroy();
}

HRESULT D3DDrawHelper::Init(HWND hWnd)
{
// acquire IDirect3D8 interface instance.
m_pD3D = Direct3DCreate8(D3D_SDK_VERSION);
if (m_pD3D == NULL)
{
::MessageBox(hWnd,
"ERROR: Failed to acquire IDirect3D8 interface instance.",
"ERROR", MB_OK);
return E_FAIL;
}

// Now get the device.
// Get the current(desktop) display mode. This is really only needed if
// we're running in a window.
// And we are using window mode. We are not using full screen mode.
D3DDISPLAYMODE display_mode;
HRESULT hRet = m_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT,
&display_mode);
if(FAILED(hRet))
{
::MessageBox(hWnd,
"ERROR: Failed to acquire current display mode.",
"ERROR", MB_OK);
return hRet;
}

// OK I want 32Bit color depth in the format of X8R8G8B8.
if (display_mode.Format != D3DFMT_X8R8G8B8)
{
// All right, now you tell the user to change their display mode,
::MessageBox(hWnd,
"ERROR: Display Resolution is not up to spec. Pixel resolution should be 32bit.",
"ERROR", MB_OK);
return hRet;
}

ZeroMemory(&m_d3dpp, sizeof(m_d3dpp));
// Throw away previous frames, we don't need them
m_d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
m_d3dpp.hDeviceWindow = hWnd;
//We only need a single back buffer
m_d3dpp.BackBufferCount= 1;

// even though you uses windowed mode, we can still
// set the backbuffer width and height.
m_d3dpp.BackBufferWidth = 640;
m_d3dpp.BackBufferHeight = 480;

m_d3dpp.Windowed = TRUE;
m_d3dpp.BackBufferFormat = display_mode.Format;
m_d3dpp.Flags = D3DPRESENTFLAG_LOCKABLE_BACKBUFFER;

hRet = m_pD3D->CreateDevice(D3DADAPTER_DEFAULT,
D3DDEVTYPE_HAL, // we want full hardware support.
hWnd,
// we are not doing vertex processing.
D3DCREATE_MIXED_VERTEXPROCESSING,
&m_d3dpp,
&m_pD3DDevice);
if(FAILED(hRet))
{
::MessageBox(hWnd,
"ERROR: Failed to acquire IDitect3DDevice instance.",
"ERROR", MB_OK);
return hRet;
}

return S_OK;
}

void D3DDrawHelper::Destroy()
{
if (m_pD3DDevice)
{
m_pD3DDevice->Release();
m_pD3DDevice = NULL;
}
}

HRESULT D3DDrawHelper::Blank()
{
return m_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET,
D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
}

HRESULT D3DDrawHelper::Display()
{
return m_pD3DDevice->Present(NULL, NULL, NULL, NULL);
}

HRESULT D3DDrawHelper::Draw(IDirect3DSurface8 * pSurface,
const RECT * p_src_rect,
const POINT * p_dest_pt)
{
if (pSurface == NULL
|| p_src_rect == NULL
|| p_dest_pt == NULL)
return E_FAIL;

IDirect3DSurface8 * pBackBuffer;
HRESULT hRet = m_pD3DDevice->GetBackBuffer(0,
D3DBACKBUFFER_TYPE_MONO, &pBackBuffer);
if (FAILED (hRet))
{
::MessageBox(NULL, "Failed 1", "ERROR", MB_OK);
return hRet;
}

hRet = m_pD3DDevice->CopyRects(pSurface,
p_src_rect,
1,
pBackBuffer,
p_dest_pt);
if (FAILED (hRet))
{
if (hRet == D3DERR_INVALIDCALL)
::MessageBox(NULL, "Failed 2", "ERROR", MB_OK);
return hRet;
}

return S_OK;
}

 
by Lacutis. 
 

D3DSurfaceHelper 

头文件:
#ifndef D3D_SURFACE_HELPER_H_
#define D3D_SURFACE_HELPER_H_

#include <d3d8.h>
#include <d3dx8.h>

class D3DSurfaceHelper
{
public:
D3DSurfaceHelper();
virtual ~D3DSurfaceHelper();

void Destroy();
// initialize the colorkey to pitch black.
HRESULT CreateSurfaceFromFile(IDirect3DDevice8 * pDevice,
const char * fn,
int img_size_x, int img_size_y,
D3DCOLOR col_key = 0xFF000000);
inline IDirect3DSurface8 * GetSurface()
{ return m_pD3DSurface; }

protected:
IDirect3DSurface8 * m_pD3DSurface;
};

#endif

源码文件:

#include "StdAfx.h"
#include "d3d_surface_helper.h"

D3DSurfaceHelper::D3DSurfaceHelper()
: m_pD3DSurface(NULL)
{
}

D3DSurfaceHelper::~D3DSurfaceHelper()
{
}

void D3DSurfaceHelper::Destroy()
{
if (m_pD3DSurface)
{
m_pD3DSurface->Release();
m_pD3DSurface = NULL;
}
}

HRESULT D3DSurfaceHelper::CreateSurfaceFromFile(IDirect3DDevice8 * pDevice,
const char * fn,
int img_size_x, int img_size_y,
D3DCOLOR col_key)
{
if (!pDevice)
return E_FAIL;

HRESULT hRet = pDevice->CreateImageSurface(img_size_x, img_size_y,
D3DFMT_X8R8G8B8, &m_pD3DSurface);
if (FAILED (hRet))
{
return hRet;
}

/*
D3DXIMAGE_INFO src_info;
ZeroMemory((BYTE *)&src_info, sizeof(D3DXIMAGE_INFO);
src_info.Width = 800;
src_info.Height = 600;
src_info.Depth = 24; // how many bits per pixel.
src_info.MipLevel = 0; // not important.
src_info.ResourceType = D3DRTYPE_TEXTURE; // set a
*/

return D3DXLoadSurfaceFromFile(m_pD3DSurface,
NULL,
NULL,
fn,
NULL,
D3DX_DEFAULT,
col_key,
NULL);
}
 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐