Direct3D轮回:为D3D量身订做SpriteBatch类
2011-07-05 21:53 独孤残云 阅读(3131) 评论(10) 编辑 收藏 举报上一节中,我们大致讲述了Direct3D环境下,如何使用ID3DXSprite绘制2D对象。
ID3DXSprite虽然使用起来非常方便,但其自身存在着诸多的限制与弊端,一个最明显的体现,ID3DXSprite没有提供自身顶点级操作,因此无法应用特效;另外,其接口的设计也不是十分的友好~
这一节,我们使用D3D中最基本的图元绘制法,为其量身订做一个专属于D3D的CSpriteBatch类。
如下是CSpriteBatch的实现代码:
代码清单:SpriteBatch.h
来自:http://www.cnblogs.com/kenkao
-------------------------------------*/
#include "Texture2D.h"
#include "D3DEffect.h"
#include <list>
using namespace std;
#pragma once
// 精灵节点
struct SpriteNode
{
RECT _DesRect; // 目标区域
RECT _SurRect; // 纹理区域
float _layerDepth; // 深度(Z坐标)
D3DCOLOR _Color; // 色相
SpriteNode(){}
SpriteNode(RECT DesRect,RECT SurRect,float layerDepth,D3DCOLOR Color){
_DesRect=DesRect; _SurRect=SurRect; _layerDepth=layerDepth; _Color=Color;
}
};
class CSpriteBatch
{
public:
CSpriteBatch(IDirect3DDevice9* pDevice);
~CSpriteBatch(void);
public:
void Begin(CD3DEffect* pD3DEffect=NULL); // 开始绘制
void End(); // 结束绘制
void Release(){ // 释放CSpriteBatch
if(m_pSpriteNodeList->size()>0)
m_pSpriteNodeList->clear();
delete m_pSpriteNodeList;}
public: // 一系列的重载Draw函数 ^ ^
void Draw(
CTexture2D* pTexture, // 目标纹理
const RECT& DesRect, // 目标区域
const RECT& SurRect, // 纹理区域
const float& layerDepth = 0.0f, // 深度值(0、1之间)
D3DCOLOR Color = D3DXCOLOR_WHITE // 色相
);
void Draw(
CTexture2D* pTexture,
const POINT& Pos,
const RECT& SurRect,
const float& layerDepth = 0.0f,
D3DCOLOR Color = D3DXCOLOR_WHITE
);
void Draw(
CTexture2D* pTexture,
const POINT& Pos,
const float& layerDepth = 0.0f,
D3DCOLOR Color = D3DXCOLOR_WHITE
);
void Draw(
CTexture2D* pTexture,
const RECT& DesRect,
const float& layerDepth = 0.0f,
D3DCOLOR Color = D3DXCOLOR_WHITE
);
void Draw(
CTexture2D* pTexture,
const POINT& Pos,
const POINT& Size,
const RECT& SurRect,
const float& layerDepth = 0.0f,
D3DCOLOR Color = D3DXCOLOR_WHITE
);
void Draw(
CTexture2D* pTexture,
const POINT& Pos,
const POINT& Size,
const float& layerDepth = 0.0f,
D3DCOLOR Color = D3DXCOLOR_WHITE
);
public:
IDirect3DDevice9* GetDevice(){return m_pDevice;} // 获得设备指针
CD3DEffect* GetEffect(){return m_pD3DEffect;} // 获得特效指针
CTexture2D* GetTexture2D(){return m_pCurrentTexture;} // 获得活动纹理
private:
void PostFrame( // 单帧投递
RECT DesRect,
RECT SurRect,
float layerDepth,
D3DCOLOR Color
);
void Flush(); // 遍历所有节点并一次性完成绘制
private:
IDirect3DDevice9* m_pDevice; // 设备指针
CTexture2D* m_pCurrentTexture; // 活动纹理指针
CD3DEffect* m_pD3DEffect; // 特效指针
UINT m_NumPasses; // 特效路径数目
D3DXMATRIX m_OriViewMatrix; // 原始摄影矩阵
D3DXMATRIX m_OriProjMatrix; // 原始投影矩阵
D3DXMATRIX m_ViewMatrix; // 摄影矩阵
D3DXMATRIX m_ProjMatrix; // 投影矩阵
private:
list<SpriteNode>* m_pSpriteNodeList; // 精灵节点列表
};
代码清单:SpriteBatch.cpp
来自:http://www.cnblogs.com/kenkao
-------------------------------------*/
#include "StdAfx.h"
#include "SpriteBatch.h"
// 精灵顶点缓冲结构定义
struct VertexSprite{
VertexSprite(){}
VertexSprite(float x, float y, float z, float nx, float ny, float nz, D3DCOLOR color, float u, float v){
_x = x; _y = y; _z = z;
_nx = nx; _ny = ny; _nz = nz;
_color = color;
_u = u; _v = v;
}
float _x, _y, _z;
float _nx, _ny, _nz;
D3DCOLOR _color;
float _u, _v;
static const DWORD FVF;
};
const DWORD VertexSprite::FVF = (D3DFVF_XYZ | D3DFVF_NORMAL |D3DFVF_DIFFUSE | D3DFVF_TEX1);
CSpriteBatch::CSpriteBatch(IDirect3DDevice9* pDevice) : m_pDevice(NULL),
m_pD3DEffect(NULL),
m_pCurrentTexture(NULL),
m_NumPasses(0),
m_OriViewMatrix(D3DXMATRIX_IDENTITY),
m_OriProjMatrix(D3DXMATRIX_IDENTITY),
m_pSpriteNodeList(NULL)
{
// 传入3D设备
this->m_pDevice=pDevice;
// 获得单位摄影矩阵
m_ViewMatrix = D3DXMATRIX_IDENTITY;
// 获得正交投影矩阵
D3DXMatrixOrthoOffCenterLH(&m_ProjMatrix, 0.0f, 640.0f, 480.0f, 0.0f, 0.0f, 1.0f);
// 初始化精灵节点列表
m_pSpriteNodeList = new list<SpriteNode>;
}
CSpriteBatch::~CSpriteBatch(void)
{
}
void CSpriteBatch::Begin(CD3DEffect* pD3DEffect)
{
// 获得原始状态
m_pDevice->GetTransform(D3DTS_VIEW, &m_OriViewMatrix);
m_pDevice->GetTransform(D3DTS_PROJECTION, &m_OriProjMatrix);
// 设置单位摄影矩阵及正交投影矩阵
m_pDevice->SetTransform(D3DTS_VIEW, &m_ViewMatrix);
m_pDevice->SetTransform(D3DTS_PROJECTION, &m_ProjMatrix);
// 如果存在特效则应用之
if(pD3DEffect)
{
this->m_pD3DEffect=pD3DEffect;
m_pD3DEffect->BeginEffect(m_NumPasses);
}
}
void CSpriteBatch::End()
{
// 结束绘制之前Flush一次全部节点
Flush();
// 如果存在特效则结束之
if(m_pD3DEffect)
m_pD3DEffect->EndEffect();
// 还原原始摄影矩阵及投影矩阵
m_pDevice->SetTransform(D3DTS_VIEW, &m_OriViewMatrix);
m_pDevice->SetTransform(D3DTS_PROJECTION, &m_OriProjMatrix);
}
void CSpriteBatch::PostFrame(RECT DesRect,RECT SurRect,float layerDepth,D3DCOLOR Color)
{
// 列表新增一个节点
m_pSpriteNodeList->push_back(SpriteNode(DesRect,SurRect,layerDepth,Color));
}
void CSpriteBatch::Flush()
{
// 错误排查
int NumNodes = m_pSpriteNodeList->size();
if(NumNodes <= 0 || !m_pCurrentTexture)
return;
// 动态申请顶点缓冲区与索引缓冲区
VertexSprite* vb = new VertexSprite[NumNodes * 4];
UINT16* ib = new UINT16 [NumNodes * 6];
memset(vb, 0, sizeof(VertexSprite) * NumNodes * 4);
memset(ib, 0, sizeof(UINT16) * NumNodes * 6);
// 遍历全部节点,合并顶点缓冲及索引缓冲
int i = 0;
for(list<SpriteNode>::iterator ptr = m_pSpriteNodeList->begin();ptr != m_pSpriteNodeList->end();++ptr)
{
// 将纹理区域折合成uv坐标
float Txcrd_LU_u = ptr->_SurRect.left/m_pCurrentTexture->GetWidth();
float Txcrd_LU_v = ptr->_SurRect.top/m_pCurrentTexture->GetHeight();
float Txcrd_RU_u = ptr->_SurRect.right/m_pCurrentTexture->GetWidth();
float Txcrd_RU_v = ptr->_SurRect.top/m_pCurrentTexture->GetHeight();
float Txcrd_RD_u = ptr->_SurRect.right/m_pCurrentTexture->GetWidth();
float Txcrd_RD_v = ptr->_SurRect.bottom/m_pCurrentTexture->GetHeight();
float Txcrd_LD_u = ptr->_SurRect.left/m_pCurrentTexture->GetWidth();
float Txcrd_LD_v = ptr->_SurRect.bottom/m_pCurrentTexture->GetHeight();
// 填充顶点缓冲区数据
vb[i * 4 ] = VertexSprite(ptr->_DesRect.left, ptr->_DesRect.top, ptr->_layerDepth, 0.0f, 0.0f, 0.0f, ptr->_Color, Txcrd_LU_u, Txcrd_LU_v);
vb[i * 4 + 1] = VertexSprite(ptr->_DesRect.right,ptr->_DesRect.top, ptr->_layerDepth, 0.0f, 0.0f, 0.0f, ptr->_Color, Txcrd_RU_u, Txcrd_RU_v);
vb[i * 4 + 2] = VertexSprite(ptr->_DesRect.right,ptr->_DesRect.bottom,ptr->_layerDepth, 0.0f, 0.0f, 0.0f, ptr->_Color, Txcrd_RD_u, Txcrd_RD_v);
vb[i * 4 + 3] = VertexSprite(ptr->_DesRect.left, ptr->_DesRect.bottom,ptr->_layerDepth, 0.0f, 0.0f, 0.0f, ptr->_Color, Txcrd_LD_u, Txcrd_LD_v);
// 填充索引缓冲区数据
ib[i * 6 ] = i * 4;
ib[i * 6 + 1] = i * 4 + 1;
ib[i * 6 + 2] = i * 4 + 2;
ib[i * 6 + 3] = i * 4;
ib[i * 6 + 4] = i * 4 + 2;
ib[i * 6 + 5] = i * 4 + 3;
i++;
}
// 应用活动纹理
m_pDevice->SetTexture(0,m_pCurrentTexture->GetTexture());
// 如果存在特效则应用全部路径
if(m_pD3DEffect)
{
for(UINT i=0;i<m_NumPasses;i++)
{
// 开启路径
m_pD3DEffect->GetEffect()->BeginPass(i);
// 一次性绘制全部顶点
m_pDevice->SetFVF(VertexSprite::FVF);
m_pDevice->DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST, 0, NumNodes * 4, NumNodes * 2, &ib[0],
D3DFMT_INDEX16, &vb[0], sizeof(VertexSprite));
// 路径结束
m_pD3DEffect->GetEffect()->EndPass();
}
}
// 如果不存在特效则采用普通绘制
else
{
// 一次性绘制全部顶点
m_pDevice->SetFVF(VertexSprite::FVF);
m_pDevice->DrawIndexedPrimitiveUP(D3DPT_TRIANGLELIST, 0, NumNodes * 4, NumNodes * 2, &ib[0],
D3DFMT_INDEX16, &vb[0], sizeof(VertexSprite));
}
// 注销顶点缓冲区与索引缓冲区
delete[] vb;
delete[] ib;
// 清空节点列表数据
m_pSpriteNodeList->clear();
}
void CSpriteBatch::Draw(CTexture2D* pTexture,const RECT& DesRect,const RECT& SurRect,const float& layerDepth, D3DCOLOR Color)
{
// 如果当前没有活动纹理则应用之
if(!m_pCurrentTexture)
m_pCurrentTexture = pTexture;
// 如果当前纹理状态发生变化,则flush现有全部节点
if(m_pCurrentTexture != pTexture)
{
Flush();
// 重新应用新的活动纹理
m_pCurrentTexture = pTexture;
}
// 投递新节点数据
PostFrame(DesRect,SurRect,layerDepth,Color);
}
// 一系列的重载Draw函数
void CSpriteBatch::Draw(CTexture2D* pTexture,const POINT& Pos,const RECT& SurRect,const float& layerDepth, D3DCOLOR Color)
{
RECT rect;
rect.left = Pos.x;
rect.top = Pos.y;
rect.right = Pos.x + pTexture->GetWidth();
rect.bottom = Pos.y + pTexture->GetHeight();
Draw(pTexture,rect,SurRect,layerDepth,Color);
}
void CSpriteBatch::Draw(CTexture2D* pTexture,const POINT& Pos,const float& layerDepth, D3DCOLOR Color)
{
Draw(pTexture,Pos,pTexture->GetRect(),layerDepth,Color);
}
void CSpriteBatch::Draw(CTexture2D* pTexture,const RECT& DesRect,const float& layerDepth, D3DCOLOR Color)
{
Draw(pTexture,DesRect,pTexture->GetRect(),layerDepth,Color);
}
void CSpriteBatch::Draw(CTexture2D* pTexture,const POINT& Pos,const POINT& Size,const RECT& SurRect,const float& layerDepth, D3DCOLOR Color)
{
RECT rect;
rect.left = Pos.x;
rect.top = Pos.y;
rect.right = Pos.x + Size.x;
rect.bottom = Pos.y + Size.y;
Draw(pTexture,rect,SurRect,layerDepth,Color);
}
void CSpriteBatch::Draw(CTexture2D* pTexture,const POINT& Pos,const POINT& Size,const float& layerDepth, D3DCOLOR Color)
{
Draw(pTexture,Pos,Size,pTexture->GetRect(),layerDepth,Color);
}
实现思路上主要参照了Irrlicht引擎的开源代码和shallway 兄的一篇《[SheRO]用D3D绘制2D图像》(http://shallway.net/blog/?p=515)中提到的思路,大家可以点击链接参考一下~
基本的原理是这样的:
如果客户始终采取同一种纹理进行绘制,则我们在不打断设备渲染状态的前提下合并所有精灵节点的顶点缓冲和索引缓冲,然后一次性绘制出来;
如果客户中途切换纹理,则于此前一时刻执行步骤一,如此反复~
需要注意的地方有两点:
1>绘制2D图像相当于直接在屏幕上画图,因此摄影矩阵选取单位矩阵,投影矩阵则由正交投影得来,而并非常规下的透视投影;
2>关于动态生成顶点缓冲区方面,我们使用D3D下的DrawIndexedPrimitiveUP函数。这个函数虽然效率不如DrawIndexedPrimitive函数一次性lock顶点及索引缓冲区再一次性绘制,但却优于此方法反复lock反复绘制。这一问题shallway兄在其博文中使用其他方法解决了,本文的做法主要参照了Irrlicht引擎的实现。
另外,这个类在接口设计上部分借鉴了Xna4.0下SpriteBatch的实现。其Begin函数允许传入一个CD3DEffect对象指针,并在绘制过程中自动应用特效到2D图元——这一点单靠ID3DXSprite是做不到的~
以下是主体代码部分:
代码清单:D3DGame.cpp
来自:http://www.cnblogs.com/kenkao
-------------------------------------*/
#include "StdAfx.h"
#include "D3DGame.h"
#include "D3DCamera.h"
#include "D3DEffect.h"
#include "CoordCross.h"
#include "SimpleXMesh.h"
#include "Texture2D.h"
#include "D3DSprite.h"
#include "SpriteBatch.h"
#include <stdio.h>
//---通用全局变量
HINSTANCE g_hInst;
HWND g_hWnd;
D3DXMATRIX g_matProjection;
//---D3D全局变量
IDirect3D9 *g_pD3D = NULL;
IDirect3DDevice9 *g_pD3DDevice = NULL;
CMouseInput *g_pMouseInput = NULL;
CKeyboardInput *g_pKeyboardInput = NULL;
CD3DCamera *g_pD3DCamera = NULL;
CCoordCross *g_pCoordCross = NULL;
CSimpleXMesh *g_pSimpleXMesh = NULL;
CD3DEffect *g_pD3DEffect = NULL;
CD3DSprite *g_pD3DSprite = NULL;
CTexture2D *g_pTexture2D = NULL;
CSpriteBatch *g_SpriteBatch = NULL;
CTexture2D *g_pTexture2D2 = NULL;
CD3DEffect *g_pD3DEffect2 = NULL;
//---HLSL全局变量句柄
D3DXHANDLE g_CurrentTechHandle = NULL;
D3DXHANDLE g_matWorldViewProj = NULL;
D3DXHANDLE g_matWorld = NULL;
D3DXHANDLE g_vecEye = NULL;
D3DXHANDLE g_vecLightDir = NULL;
D3DXHANDLE g_vDiffuseColor = NULL;
D3DXHANDLE g_vSpecularColor = NULL;
D3DXHANDLE g_vAmbient = NULL;
D3DXHANDLE g_CurrentTechHandle2 = NULL;
D3DXHANDLE g_Scale = NULL;
// HLSL特效参数设置
void GetParameters();
void SetParameters();
void Initialize(HINSTANCE hInst, HWND hWnd)
{
g_hInst = hInst;
g_hWnd = hWnd;
InitD3D(&g_pD3D, &g_pD3DDevice, g_matProjection, hWnd);
g_pMouseInput = new CMouseInput;
g_pMouseInput->Initialize(hInst,hWnd);
g_pKeyboardInput = new CKeyboardInput;
g_pKeyboardInput->Initialize(hInst,hWnd);
g_pD3DCamera = new CD3DCamera;
}
void LoadContent()
{
g_pCoordCross = new CCoordCross;
g_pD3DCamera->SetCameraPos(D3DXVECTOR3(0.5f,0.5f,-5.0f));
g_pSimpleXMesh = new CSimpleXMesh;
g_pSimpleXMesh->LoadXMesh("teapot.X");
g_pD3DEffect = new CD3DEffect;
g_pD3DEffect2 = new CD3DEffect;
g_pD3DEffect->LoadEffect("Light.fx");
if(!g_pD3DEffect2->LoadEffect("Thunder.fx"))
g_pD3DEffect2->GetEffectError();
GetParameters();
g_pD3DSprite = new CD3DSprite(g_pD3DDevice);
g_SpriteBatch = new CSpriteBatch(g_pD3DDevice);
g_pTexture2D = new CTexture2D;
g_pTexture2D->LoadTexture("img.jpg");
g_pTexture2D2 = new CTexture2D;
g_pTexture2D2->LoadTexture("img2.jpg");
}
void Update()
{
g_pMouseInput->GetState();
g_pKeyboardInput->GetState();
g_pD3DCamera->Update();
}
void Draw()
{
// 参数设定
SetParameters();
g_pD3DDevice->SetTransform(D3DTS_VIEW,&g_pD3DCamera->GetViewMatrix());
POINT pos;
pos.x=0;
pos.y=0;
POINT pos2;
pos2.x = 440;
pos2.y = 260;
g_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_RGBA(100,149,237,255), 1.0f, 0);
if(SUCCEEDED(g_pD3DDevice->BeginScene()))
{
g_pCoordCross->Draw();
// 开始绘制并应用特效
g_SpriteBatch->Begin(g_pD3DEffect2);
// CSpriteBatch绘制
g_SpriteBatch->Draw(g_pTexture2D2,pos);
g_SpriteBatch->Draw(g_pTexture2D,pos2);
// 结束绘制并终止特效
g_SpriteBatch->End();
UINT numPasses;
// 开启特效
g_pD3DEffect->BeginEffect(numPasses);
for(UINT i=0;i<numPasses;i++)
{
// 开启路径
g_pD3DEffect->GetEffect()->BeginPass(i);
for(DWORD j=0;j<g_pSimpleXMesh->GetMaterialNum();j++)
{
g_pSimpleXMesh->DrawXMeshSubset(j);
}
// 路径结束
g_pD3DEffect->GetEffect()->EndPass();
}
// 特效结束
g_pD3DEffect->EndEffect();
g_pD3DDevice->EndScene();
}
g_pD3DDevice->Present(NULL, NULL, NULL, NULL);
}
void UnloadContent()
{
ReleaseCOM(g_pTexture2D2);
ReleaseCOM(g_pTexture2D);
ReleaseCOM(g_SpriteBatch);
ReleaseCOM(g_pD3DSprite);
ReleaseCOM(g_pD3DEffect2);
ReleaseCOM(g_pD3DEffect);
ReleaseCOM(g_pSimpleXMesh);
ReleaseCOM(g_pCoordCross);
}
void Dispose()
{
ReleaseCOM(g_pD3DCamera);
ReleaseCOM(g_pKeyboardInput);
ReleaseCOM(g_pMouseInput);
ReleaseCOM(g_pD3DDevice);
ReleaseCOM(g_pD3D);
}
void GetParameters()
{
// 获得HLSL中各个全局变量句柄
g_CurrentTechHandle = g_pD3DEffect -> GetEffect() -> GetTechniqueByName("SpecularLight");
g_matWorldViewProj = g_pD3DEffect -> GetEffect() -> GetParameterByName(0, "matWorldViewProj");
g_matWorld = g_pD3DEffect -> GetEffect() -> GetParameterByName(0, "matWorld");
g_vecEye = g_pD3DEffect -> GetEffect() -> GetParameterByName(0, "vecEye");
g_vecLightDir = g_pD3DEffect -> GetEffect() -> GetParameterByName(0, "vecLightDir");
g_vDiffuseColor = g_pD3DEffect -> GetEffect() -> GetParameterByName(0, "vDiffuseColor");
g_vSpecularColor = g_pD3DEffect -> GetEffect() -> GetParameterByName(0, "vSpecularColor");
g_vAmbient = g_pD3DEffect -> GetEffect() -> GetParameterByName(0, "vAmbient");
g_CurrentTechHandle2 = g_pD3DEffect2 -> GetEffect() -> GetTechniqueByName("Technique1");
g_Scale = g_pD3DEffect2 -> GetEffect() -> GetParameterByName(0, "Scale");
}
void SetParameters()
{
// 设定当前技术
g_pD3DEffect -> GetEffect() -> SetTechnique(g_CurrentTechHandle);
// 设定HLSL中的各个参数
D3DXMATRIX worldMatrix;
D3DXMatrixTranslation(&worldMatrix,0.0f,0.0f,0.0f);
g_pD3DEffect -> GetEffect() -> SetMatrix(g_matWorldViewProj,&(worldMatrix*g_pD3DCamera->GetViewMatrix()*g_matProjection));
g_pD3DEffect -> GetEffect() -> SetMatrix(g_matWorld,&worldMatrix);
D3DXVECTOR3 cameraPos = g_pD3DCamera->GetCameraPos();
D3DXVECTOR4 vecEye = D3DXVECTOR4(cameraPos.x,cameraPos.y,cameraPos.z,0.0f);
g_pD3DEffect -> GetEffect() -> SetVector(g_vecEye,&vecEye);
D3DXVECTOR4 vLightDirection = D3DXVECTOR4(0.0f, 0.0f, -1.0f, 1.0f);
g_pD3DEffect -> GetEffect() -> SetVector(g_vecLightDir,&vLightDirection);
D3DXVECTOR4 vColorDiffuse = D3DXVECTOR4(0.8f, 0.0f, 0.0f, 1.0f);
D3DXVECTOR4 vColorSpecular = D3DXVECTOR4(1.0f, 1.0f, 1.0f, 1.0f);
D3DXVECTOR4 vColorAmbient = D3DXVECTOR4(0.1f, 0.1f, 0.1f, 1.0f);
g_pD3DEffect -> GetEffect() -> SetVector(g_vDiffuseColor,&vColorDiffuse);
g_pD3DEffect -> GetEffect() -> SetVector(g_vSpecularColor,&vColorSpecular);
g_pD3DEffect -> GetEffect() -> SetVector(g_vAmbient,&vColorAmbient);
g_pD3DEffect2 -> GetEffect() -> SetTechnique(g_CurrentTechHandle2);
g_pD3DEffect2 -> GetEffect() -> SetFloat(g_Scale,0.8f);
}
最后是特效运用前后的效果对比:
右图在绘制过程中应用了Thunder.fx代码中的高亮效果(g_pD3DEffect2),代码来源于深蓝团长在其教程中为大家反复推荐的Silverlight专属渲染工具——Shazzam,感兴趣的朋友可以拜读一下团长的教程或者登录Shazzam的官网~
Xna下的SpiritBatch以及本文中实现的CSpiritBatch可以兼容Shazzam提供的所有特效,直接拿来用即可^ ^