??xml version="1.0" encoding="utf-8" standalone="yes"?> 回到主题Q记录一下镜面反矩늚推导?/p> 在用Irrlicht和RTT做镜面效果的时候,用到?jin)反矩c(din)?是需要把摄相机镜像,渲染一个RTQ脓(chung)到镜面模型上。这个其实还U结?jin)许久,因?f)之前做水面渲染的时候,水面是^的,很好计算摄相机在水面以下的位|?但是换成镜面Q就不一样了(jin)Q因为镜面可能是L面?于是需要一个通用的反矩c(din)?/p> 反射矩阵的计是Zq面的,因ؓ(f)QQ何反,都需要一个反面?/p> 所以,我们先给出^面表C?Plane(nx,ny,nz,d); 其中(nx,ny,nz)已经单位化?/p> 然后Q我们假讄间中有Q意一点P(x,y,z,1) 设这个点P以Plane为反面的镜像点为P1(x1,y1,z1,w)?/p> Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q-Q?/p> Ҏ(gu)定理Q我们知道, 若两个点以某一点ؓ(f)镜像Q则两个点的坐标之和除以2Q就刚好是中炏V? q个理论我们用到q里的话Q?那这个中点就刚好是^面上的一个点?q面上的q个点就?P(x,y,z,1) - (nx,ny,nz,0)*D . 其中D是点P到^面的距离 而D=Plane dot P = (x*nx+y*ny+z*nz+d); ׃面的描述Q我们马上想刎ͼ那么要求点P1的话Q就是这?nbsp; (P+P1)/2 = P(x,y,z,1) - (nx,ny,nz,0)*D => P1 = P(x,y,z,1) - 2(nx,ny,nz,0)*D =>P1 = P(x,y,z,1) - 2(nx,ny,nz,0)*(x*nx+y*ny+z*nz+d) 换成矩阵形式则ؓ(f) ?-2*nx*nx -2*nx*ny -2*nx*nz 0 | | -2*ny*nx 1 - 2*ny*ny -2*ny*nz 0 | P1 = {x,y,z,1} x | -2*nz*nx -2*nz*ny 1-2*nz*nz 0 | | -2*d*nx -2*d*ny -2*d*nz 1 | 大功告成 btw:q是行主矩阵表示?/p> 目前在正研发中的目 《帝国来?jin)?/p> http://v.ku6.com/show/1zMrlsOx0nwpQw3JMoNAjw...html l爹妈d员(sh)后的W二个?《大话修仙?Q天地劫WEB) 处女?《爹妈d员?哎,W一ơ,l了(jin)它? http://www.87kd.com/game/dmzdy/ 公司让我们学?fn)实战经验,临时参与了(jin)两个月的《五虎上?/p> W一个FALSH技术DEMO KINGDOM RUSH http://www.shnenglu.com/Leaf/archive/2013/02/22/198012.html HALO 3D引擎DEMO演示 http://www.shnenglu.com/Leaf/archive/2013/02/22/198011.html 2部的《天地劫?/p> http://tdj.sjgame.cn/main.html 在游戏开发的道\上,我想我依然会(x)l箋(hu)努力、奋斗?生生不息Q不M弃。。。?/p>
最q在用irrlicht做一?D试衣间的项目,Z(jin)l项目增ȝ花样Q于是想实现一面镜子?/p>
我记得D3D龙书上有一个用模板缓冲区实现的例子。网上也有OPENGL实现的例子?但这一ơ,我想用irrlicht的RTT实现一面镜子效果?/p>
其实原理和水面反原理是一L(fng)Q?只是没有加扰动而已
W一步:(x)渲染反射贴图
反射贴图的渲染,其实是摄相机通过镜面镜像卛_Qirrlicht中我找了(jin)半天Q没有发现镜像矩늚法Q倒是在网上搜C(jin)一个?很是不错?/p>
同时Q也阅?jin)一下先前公司引擎项目的代码Q发现其实就是那个公式?有兴的朋友可以参看q里
http://www.cnblogs.com/glshader/archive/2010/11/02/1866971.html
通过q个镜面反射矩阵Q我们可以将摄相机镜像, 相当于是从镜子里向外看,渲染Z个世界?在渲染的时候,要记得设|裁剪面?在我的测试中我没有设|?/p>
W二步:(x)重新渲染世界
重新渲染世界的时候,镜子需要一个特D的U理来进行反脓(chung)图。(镜像摄相机空间的投媄(jing)U理映射Q?q个贴图方式Q就是指忽略镜子的纹理坐标,而通过
镜像摄相机来计算出投影坐标,然后贴在镜子上。在我的试中,是用SHADER来实现的?为镜子做?jin)一个特D的U理?/p>
下面Q我贴一下SHADERQ很单,如果实在不清楚的Q可以参考一些投q理相关的资料?/p>
点着色器代码 HLSL
float4x4 WorldViewProj;
float4x4 MirrorWorldViewProj;
struct VS_OUTPUT
{
float4 position :POSITION;
float3 uv: TEXCOORD0;
};
struct VS_INPUT
{
float4 position : POSITION;
float4 color : COLOR0;
float2 texCoord0 : TEXCOORD0;
};
VS_OUTPUT main(VS_INPUT input)
{
VS_OUTPUT output;
float4 pos = mul(input.position, WorldViewProj);
output.position = pos;
//计算反射U理的坐?
pos = mul(input.position,MirrorWorldViewProj);
output.uv.x = 0.5 * (pos.w + pos.x);
output.uv.y = 0.5 * (pos.w - pos.y);
output.uv.z = pos.w;
return output;
}
像素着色器代码 HLSL
sampler2D colorMap;
struct PS_OUTPUT
{
float4 color : COLOR0;
};
struct PS_INPUT
{
float4 position : POSITION;
float3 uv: TEXCOORD0;
};
PS_OUTPUT main( PS_INPUT input )
{
PS_OUTPUT output;
float2 uv = saturate(input.uv.xy / input.uv.z);
output.color = tex2D(colorMap,uv);
return output;
}
RTT相关的操作,irrlicht的RenderToTexture已经很明白了(jin)Q再此不在敷q?/p>
上图Q收?/p>
本来说是做镜子效果的Q结果手工计的镜面反射矩阵应用在irrlicht相机上的时候,始终无法出现效果Q只能去|上搜烦(ch)
在irrlicht official wiki上发C(jin)q个扩展的WaterNodeQ下载下来,改了(jin)点BUGQ整合进?jin)Terrain Demo里, 是上图的效果?/p>
在我的机器上,HLSL版本是没有问题的QGL版本貌似RTT有点问题?/p>
http://supertuxkart.sourceforge.net/
׃墙的原因Q需要各位搭梯子?/p>
上周末,在弄换装的时候,发现irrlicht引擎本n是不支持g蒙皮的,多少令h有些失望??j)里׃直寻思着怎么扩展一下,它弄出来?/p>
值得说明的是STK对irrlicht引擎的用法是很简单的Q基本上可以说是裸用Qƈ未在irrlicht接口上做修改?而是对外q行?jin)一些必要的扩展?/p>
当然QSTK也对外开放了(jin)一个irrlicht.dllQ说是修改了(jin)其中的BUG?但直接用irrlicht是可以的?/p>
废话不多_(d)来说说如何不修改irrlicht一行代码,通过外部扩展来实现硬仉骼动d
首先Q能够我们不修改irrlicht代码的原因,是因为ISkinnedMesh提供?jin)一个setHardwareSkinning接口Q默认ؓ(f)false.
虽然q个接口的说明是"(This feature is not implemented in irrlicht yet)”Q但q不代表Q设|与不设|无差别?
查看代码可以发现Q当你设|了(jin)q个为true以后Qirrlicht完全不你的动M(jin)?意思就是,要是你非要让我干我不q不?jin)的事,那就只有?zhn)另请高明了(jin)?/p>
irrlichtqCPU计算都不?x)参与?q正好让我们有机可乘Q完全用GPU接管?/p>
而要让一个顶点参与骨D,那骨骼烦(ch)引则是少不了(jin)的。所以,我们需要想办法让顶Ҏ(gu)据能够将骨骼索引代入SHADER中?/p>
在STK中用?jin)一Uy妙的Ҏ(gu)Q?是使用?jin)顶点的颜色数据Q?虽然q样一来,点颜色q不了(jin)?jin)?但在模型渲染Ӟ点颜色很少被用到的?也就是说Q顶炚w色在STK的动L型中Q被用作?jin)骨骼?ch)引?/p>
初始化骨骼烦(ch)引的Ҏ(gu)很简单,用下面的代码遍历卛_?/p>
设:(x)我们有一个骨骼动L型是 ISkinnedMesh* pSkinnedMesh = …
那么Q初始化代码如下
for(u32 i = 0;i < pSkinnedMesh ->getMeshBuffers().size();++i)
{
for(u32 g = 0;g < pSkinnedMesh ->getMeshBuffers()[i]->getVertexCount();++g)
{
pSkinnedMesh ->getMeshBuffers()[i]->getVertex(g)->Color = video::SColor(0,0,0,0);
}
}
//初始化完毕以后,是需要真正的索引赋g(jin)Q通过以下代码可以完成
const core::array<scene::ISkinnedMesh::SJoint*>& joints = pSkinnedMesh ->getAllJoints();
for(u32 i = 0;i < joints.size();++i)
{
const core::array<scene::ISkinnedMesh::SWeight>& weights = joints[i]->Weights;
for(u32 j = 0;j < weights.size();++j)
{
int buffId = weights[j].buffer_id;
int vertexId = pSkinedMesh->getAllJoints()[i]->Weights[j].vertex_id;
video::SColor* vColor = &pSkinedMesh->getMeshBuffers()[buffId]->getVertex(vertexId)->Color;
if(vColor->getRed() == 0)
vColor->setRed(i + 1);
else if(vColor->getGreen() == 0)
vColor->setGreen(i + 1);
else if(vColor->getBlue() == 0)
vColor->setBlue(i + 1);
else if(vColor->getAlpha() == 0)
vColor->setAlpha(i + 1);
}
}
//l过以上两个步骤Q顶Ҏ(gu)据改造完成?值得注意的是Q?在这里, 索引 0 是被认ؓ(f)是无效的
然后Q我们来创徏一个SHADER作ؓ(f)渲染?/p>
假设 我们这个pSkinnedMeshl定?jin)到了(jin)一个IAnimatedSceneNode* node 上?/p>
那,我们个结点创Z个材?在创建材质前Q我们需要准备一个SHADER回调?SHADER回调?yu)像下面一样就可以?jin)?/p>
class HWSkinCallBack:public video::IShaderConstantSetCallBack
{
scene::IAnimatedMeshSceneNode* m_pNode;
public:
HWSkinCallBack(scene::IAnimatedMeshSceneNode* node):m_pNode(node)
{
}
virtual void OnSetConstants(video::IMaterialRendererServices* services,
s32 userData)
{
scene::ISkinnedMesh* mesh = (scene::ISkinnedMesh*)m_pNode->getMesh();
f32 joints_data[55 * 16];
int copyIncrement = 0;
const core::array<scene::ISkinnedMesh::SJoint*> joints = mesh->getAllJoints();
for(u32 i = 0;i < joints.size();++i)
{
core::matrix4 joint_vertex_pull(core::matrix4::EM4CONST_NOTHING);
joint_vertex_pull.setbyproduct(joints[i]->GlobalAnimatedMatrix, joints[i]->GlobalInversedMatrix);
f32* pointer = joints_data + copyIncrement;
for(int i = 0;i < 16;++i)
*pointer++ = joint_vertex_pull[i];
copyIncrement += 16;
}
services->setVertexShaderConstant("JointTransform", joints_data, mesh->getAllJoints().size() * 16);
}
};
好了(jin)Q现在我们来创徏一个材?/p>
s32 hwskm = gpu->addHighLevelShaderMaterialFromFiles(
"../../skinning.vert","main",video::EVST_VS_2_0,
"","main",video::EPST_PS_2_0,&hwc,video::EMT_SOLID);
//用新创徏出来的材质赋值给q个l点
node->setMaterialType((video::E_MATERIAL_TYPE)hwskm );
//到此Q设|完毕?/p>
//最后,是skinning.vert本n的内容了(jin)?贴出来即可,没有太多技巧,是一个普通的蒙皮?/p>
// skinning.vert
#define MAX_JOINT_NUM 36
#define MAX_LIGHT_NUM 8
uniform mat4 JointTransform[MAX_JOINT_NUM];
void main()
{
int index;
vec4 ecPos;
vec3 normal;
vec3 light_dir;
float n_dot_l;
float dist;
mat4 ModelTransform = gl_ModelViewProjectionMatrix;
index = int(gl_Color.r * 255.99);
mat4 vertTran = JointTransform[index - 1];
index = int(gl_Color.g * 255.99);
if(index > 0)
vertTran += JointTransform[index - 1];
index = int(gl_Color.b * 255.99);
if(index > 0)
vertTran += JointTransform[index - 1];
index = int(gl_Color.a * 255.99);
if(index > 0)
vertTran += JointTransform[index - 1];
ecPos = gl_ModelViewMatrix * vertTran * gl_Vertex;
normal = normalize(gl_NormalMatrix * mat3(vertTran) * gl_Normal);
gl_FrontColor = vec4(0,0,0,0);
for(int i = 0;i < MAX_LIGHT_NUM;i++)
{
light_dir = vec3(gl_LightSource[i].position-ecPos);
n_dot_l = max(dot(normal, normalize(light_dir)), 0.0);
dist = length(light_dir);
n_dot_l *= 1.0 / (gl_LightSource[0].constantAttenuation + gl_LightSource[0].linearAttenuation * dist);
gl_FrontColor += gl_LightSource[i].diffuse * n_dot_l;
}
gl_FrontColor = clamp(gl_FrontColor,0.3,1.0);
ModelTransform *= vertTran;
gl_Position = ModelTransform * gl_Vertex;
gl_TexCoord[0] = gl_MultiTexCoord0;
gl_TexCoord[1] = gl_MultiTexCoord1;
/*
// Reflections.
vec3 r = reflect( ecPos.xyz , normal );
float m = 2.0 * sqrt( r.x*r.x + r.y*r.y + (r.z+1.0)*(r.z+1.0) );
gl_TexCoord[1].s = r.x/m + 0.5;
gl_TexCoord[1].t = r.y/m + 0.5;
*/
}
//注:(x)q是GLSL 2.0Q?在用IRR做测试的时候,要选GL驱动方式?/p>
q是上个囑Q不上图感觉没有真像?虽然囄不出来什么动?/p>
Z(jin)说明它真的在动,不得不上W二张?
在此Q十分感谢Super Tux Kart. 提供?jin)一个学?fn)和扩展irrlicht的榜?
那就从今天这个用irrlicht做天龙八部的模型换装说v吧?/p>
也不知道是ؓ(f)什么,最q又捣鼓起了(jin)OGRE和irrlicht. q且QL用irrlicht实现一些OGRE中的东西?/p>
当然Q这不是商业目Q也没有商业目的Q纯属蛋D已?/p>
一切行动的由来Q都来自于vczh那天晚上的D动?/p>
记得有一天晚上在里聊天Q大伙就U赞各位?qing)苣是多么的厉害?/p>
最后vc发了(jin)一个自q桌面截图_(d)(x)让你们看看菊(qing)苣是如何l成的(q不是原话,和话的字眼有出入Q在此不惌M责QQ如果真有想看的Q去ȝ的聊天记录)(j)
那天晚上Q我想了(jin)很久。想惌p从{做页总后,是如何虚渡光阴的?/p>
l于忍不住了(jin)Q翻开?jin)自qUd盘Q看看自己曾l做q的东ѝ?0%是徏好工E就没理?jin)?/p>
q才明白Q我花在思考上的时间远q大于了(jin)行动?于是Q我军_改变自己Q找回那个真的我?/p>
3D游戏是我的真爱, 真爱到就画面差一点,只要?DQ我也会(x)很喜Ƣ?/p>
于是Q我觉得自己q是应该接着先前的\C厅R?什么服务器Q什?AS3. 都是云Q?不喜Ƣ就是不喜欢?/p>
U下又开始研Iirrlicht?jin)?/p>
猛地一发现Q自己是多么的搞W, ?9q到11q_(d)一直在做引擎开发, 也翻qirrlicht和ogre无数遍?却从来就没有写完q一个完整的DEMO?/p>
q功能测试用例都没有写过。突然觉得之前的一些设计似乎有些脱M(jin)实际。没有真正用过Q又怎知如何是好Q如何是坏呢Q?/p>
q一ơ是真的玩irrlicht?jin)?中间也纠l过是不是OGRE更适合?但在目前q个旉有限的空间下Q我更愿意玩irrlicht.yQ轻ѝ?当然Q意味着更多东西要自己实现?不过对于一个代码控来说Q也反而更自得其乐?正好可以在短路的时候,d考一下其它引擎,用来扩充irrlicht.
我要做的不是把irrlicht整得牛BQ而是惌己弄弄,加上Udq_的崛P我觉得irrlicht更加适合吧?据说gameloft也有使用Q仅是据_(d)(j)?/p>
可能很多兄弟?x)说我这讲的东西Q其实就是一坨屎?jin)?不过Q我觉得再坏的评论,也表CZU关注?批评好过于无视啊~~~~
----------------------------------------------------------下面说说我遇上的U结------------------------------------------------
U结1:换装需要场景节炚w?/strong>
在irrlicht中,q没有提供普通引擎中的submesh或者bodypartq种东西Q用于直接支持换装?在irrlicht中,如果惌换装Q最直接的方法就是依赖于场景l点
比如,在我的示例中Q可以更换头发,帽子Q衣服,护腕Q靴子,面容?那就需?个场景节点,1个作为根节点Q用于控制整个角色的世界坐标Q^U,~放Q旋转等属性。另?个场景节点则分别l有各个部g的模?/p>
贴一下我的角色类的代码,行数不多
class CCharactor
{
IrrlichtDevice* m_pDevice;
IAnimatedMeshSceneNode* m_pBodyParts[eCBPT_Num];
ISceneNode* m_pRoot;
public:
CCharactor(IrrlichtDevice* pDevice)
:m_pDevice(pDevice)
{
memset(m_pBodyParts,0,sizeof(m_pBodyParts));
m_pRoot = pDevice->getSceneManager()->addEmptySceneNode(NULL,12345);
}
void changeBodyPart(ECharactorBodyPartType ePartType,stringw& meshPath,stringw& metrialPath)
{
ISceneManager* smgr = m_pDevice->getSceneManager();
IAnimatedMeshSceneNode* pBpNode = m_pBodyParts[ePartType];
IAnimatedMesh* pMesh = smgr->getMesh(meshPath.c_str());
if(pMesh==NULL)
return;
if(pBpNode==NULL)
{
pBpNode = smgr->addAnimatedMeshSceneNode(pMesh,m_pRoot);
m_pBodyParts[ePartType] = pBpNode;
}
else
{
pBpNode->setMesh(pMesh);
}
ITexture* pTexture = m_pDevice->getVideoDriver()->getTexture(metrialPath.c_str());
if(pTexture)
pBpNode->setMaterialTexture(0,pTexture);
}
};
//然后Q我用了(jin)一个结构体来构建部件信?/p>
struct SBodyPartInfo
{
stringw Desc;
ECharactorBodyPartType Type;
stringw MeshPath;
stringw MeterialPath;
};
U结2Q共享骨?/strong>
首先Qirrlicht 1.8中对OGRE模型的格式支持在代码中,最高只看到?.40版本的解析,更高的就?x)被无视?天龙八部的模型有几个?.30的,而用于换装和主角的,都是1.40的?可能是解析不全的原因Q导?.40的骨骼动L法正常播放?q个问题整了(jin)几个时Q没有解冻I明天l箋(hu)
其次Q多个模型共享骨骼只能通过场景节点的useAnimationFrom来完成,q且传入的是一个Mesh参数。这点让| 天龙八部的角色动作是分开?jin)的Q不同的d动作是一个skeleton文g?惌实现׃nQ有炚w?ch)?/p>
U结3Q模型文件格?/strong>
irrlicht不像OGRE那样有一个强大且成熟的模型文件格?虽然提供?irr格式Q但仅是用于irrEdit的场景信息输出。先看一张图
q张图是irrlicht samples中的MeshViewer的提C框内容?上面列出?jin)可以支持的模型文gcd?大家可以看看Q又有多模型格式是可以直接拿来攑ֈ目上用的呢Q?mdl和ms3d可以考虑Qdae的话Q我在开源游? A.D. 中见C用过?其它的话Q就完全不熟(zhn)了(jin)?OGRE?.mesh支持也不完全?N真要自己整一个?
我能惛_的,是选一个插件完整和模型和动L式都比较好的作ؓ(f)与美术工具交互的格式?自己再写一个工P转换成自q格式?/p>
U结4Q硬件蒙?/strong>
我以为像NIKO那样的技术狂Q怎么?x)放掉这一个特性?很高兴地在场景节点上发现?jin)硬件蒙皮的函数接口。但一看注释,把我咽着?jin)?/p>
//! (This feature is not implemented in irrlicht yet)
virtual bool setHardwareSkinning(bool on);
其它地方Q还没有LQ就先不发表aZ(jin)?l箋(hu)着q个很傻B,很天真的捣鼓之\?
上个图,U念一下我的irrlicht产物?/p>
布衣
换了(jin)w盔?/p>
换了(jin)帽子和靴?/p>
PS:头发没有U理Q所以是白的?/p>
用NVPerfHUD挂程序需要被挂的E序自n支持Q但是网上有一个老兄写的一个NVPerfHUD AnyE序可以帮你完成q个功能。对大多数D9的都有效。除非本w做?jin)破解?/p>
l大家这个链接,自己去解决个人问题?/p>
http://www.thecodeway.com/blog/?p=433
完美时空的引擎很不错Q依然坚挺地用着D8。其实挺x(chng)来看看的Q可惜市(jng)面上L的工具都不支持D8Q只有D9以及(qing)以后的版本?/p>
WOW在CTM的版本中升?jin)它的渲染引擎,光从水面和火焰就看得出来。 但WOW一向是以负载和畅度ؓ(f)主,q且WOW本n卖的不是画面。所以若不能帮助提升游戏性的渲染损耗基本上被无视了(jin)。 其实吧,个h觉得Q做游戏无非׃U了(jin)。 一U是q求画面Q这一Ҏ(gu)子很有心(j)得。二是追求可玩性,q一Ҏ(gu)想WOW应该遥遥领先?jin)吧。 另外一U追求就昑־很单U了(jin)Q-Q钱。 q一点国内许多游戏做得很好,在此׃列名字了(jin)Q因为比较多?/p>
׃上星期在公司试新写好的材质和资源管理时Q发现ALPHA试的草丛效果很不理惻I于是草的ALPHA混合开启了(jin)Q一旉Q感觉世界被颠倒了(jin)。因是按画的,排序也只能是按簇排序Q当开启了(jin)ALPHA混合后,草丛与草丛之间没有太多问题,只是草丛本n出现?jin)像素渲染错误。 不管是Z写和Z试如何选择开启和关闭。都?x)有问题?/p>
H然发现草这个东西很特别?/p>
草丛的徏模和GPU GEMS 1上的建模方式一P 三个矩Ş交叉的方式?/p>
Q、它需要用ALPHA混合来实现效?/p>
用ALPHA混合的效果是很不错的。因为可以边缘柔和。毕竟ALPHA试?x)因为图片ALPHA值采样引L(fng)误差D严重的钜ѝ其ơ就是对术要求太高?sh)(jin),需要严格控制ALPHA与非ALPHA交界处?/font>
Q、但它的行ؓ(f)属性却是一个非透明物体。
q是因ؓ(f)它必L深度关系Q必需写Z。 因ؓ(f)只有写了(jin)ZQ才能够正确CҎ(gu){透明物合,q且也只有写?jin)ZQ同一草中的多个面片才能够正常混合?/font>
在忽略其它情况下Q我试过?jin)Z试和Z写的四种l合Q也未能辑ֈ草丛本n和单草都同时达到正的效果?/p>
q是多看看别人怎么做的吧,所以上来就比较兛_(j)WOW的植被渲染了(jin)。 当看C面这L(fng)U理的时候,我笑?jin)。没惛_BLZ的美术资源节U到?jin)这L(fng)E度Q真是细致入微啊Q这不是拍马屁,试问有多美术资源是q样做的Q。 可以看出Q若不采用这L(fng)半张Q而用整张Q那么就?28X128的大?/p>
左边为原图,双为ALPHA通道查看?/p>
而对于树(wi)的渲染,则很普通了(jin)。设|好U理Q设|好数据和渲染状态,提交l制卛_?/p>
Z(jin)看到ALPHA混合的效果,我强忍着把WOW在我7300的显卡上开完了(jin)效果。以Z(x)咋的Q结果啥也没发生?/p>
WOW的植被的没有用到ALPHA混合。我失望?jin)?/p>
可能许多得,把多张纹理整合在一张上更好吧。 当然?jin),q是自然的。 下面是WOW中经怸起出现的植被的纹理打包图?/p>
大家可以看到Q右边的囑ƈ没有被裁剪,其原因不a而谕Q因为想以非对称营造感觉,q且g物体U理本n比较?yu)。)(j)?/p>
发现q草用的是两片,q且大小q(sh)一栗 不知是否真的有此必要?/p>
我最后要说明的是Q这文章q完了(jin)。没有结论,也没有感a。因为那样太俗套Q?/p>
工把物体脓(chung)囑֒物体颜色Q高光等l称为材质。D3D和OPENGLq样的图形接口则把物体表面脓(chung)囑֍独叫做纹理,而把漫反,高光{叫做材质?/p>
而在游戏引擎或图形引擎中提到的材质,则与此不同?引擎中提到的材质不仅上面的的内容?引擎中所谓的材质Q是指物体在渲染时一pd的状态控制?如,ALPHA混合开关以?qing)ALPHA混合因子、纹理过虑方式,U理通道状态、纹理矩c(din)裁剪模式等Q在D3D中,是SetRenderState,SetTextureStageState,SetSamplerStateState{所控制的。在OPENGL中,则大多数由glEnable所控制?/p>
我们所提到的材质系l,则是以此为基展开的?上面提到的这些因子,l成?jin)我们的材质?也是我们在渲染一个物体的时候,提交到设备的状态控制倹{?一个物体的一ơ渲染,我们UC一个PASS。于是我们顺其自然地这个渲染时的材质控制的最单位命名ؓ(f)Pass,则:(x)
struct TextureState
{
void* Texture;
int ColorOp;
int ColorAgr1;
int ColorAgr2;
int AlphaOp;
int AlphaAgr1;
int AlphaAgr2;
.....//更多内容
};
class CPass
{
CColor mAmbient;
CColor mDiffuse;
....更多内容
bool mAlphaEnable;
int mScrBlend;
int mDstBlend;
int mCullMode;
TextureState[4] mTextureStates;
...更多内容
};
当我们渲染一个物体的时候,只需要将q个c里面的状态应用到讑֤Q即可完成对物体的绘制?/p>
材质pȝ的基本内容就是这些,q也是最Ҏ(gu)做到的事情?/p>
但是Q我们都知道Q像D3D或OPENGLq样的图形接口每讄一ơGPU状态的时候,都会(x)有一定的开销Q通过查看相关文档可以看到某些函数的具体开销|(j)。而ؓ(f)?jin)保证我们的渲染畅Q我们不得不减少q样的开销?/p>
很自然地Q我们会(x)惛_Q尽量减切换。而如何减切换呢。我们可以记录下自己的硬件状态,在设|下一个的时候,先判断当前硬件状态是否相同,如果相同。则不用再设|?虽然某些囑Ş接口在其层底做了(jin)cM的功能。但我们外部判断一下,也未不可。从D3D上来Ԍ外部判断比让其内部判断效率更佟뀂需要注意的是,׃我们在自qE序里做?jin)相同判断。因此,当有另一个程序也修改讑֤状态的时候,׃(x)产生意想不到的效果。所以,我们应该适当的查询一下设备状态,q更新自q状态记录表。至于这个查询间隔,pҎ(gu)自己的实际情冉|试?jin)?/p>
通过记录状态的Ҏ(gu)来提升的效率是很不明昄Q因此,我们需要对材质q行排序Q至于怎么排序Q这法上的问题Q在此先不作q多解释?MQ我们将怼的材质排在一赗由于材质很怼Q绘完一个再l(sh)一个的时候,减少?jin)切换,从而大大提升了(jin)效率?/p>
既然已经是涉?qing)到讑֤了(jin),我们d考虑一下设备问题?如果讑֤不支持我们当前给定的材质状态,怎么办? q回FALSE不渲染。还是让E序DOWN掉? 对于一些重要的性能Q设备不支持让程序DOWN掉是最好的做法Q但是,对于像纹理合通道不的情况,让其DOWN掉就昑־划不来了(jin)。毕竟我们设计的游戏E序是想让更多的玩家能玩不是Q?q样׃(x)涉及(qing)到PASS的拆分问题?/p>
对于PASS的拆分,OGRE已经做得很好?jin)。根据用L(fng)讑֤性能Q如果不满Q就一直拆分,拆到用户满为止Q最后让一个物体渲染多ơ,来实现多个纹理通道混合的效果?而多PASS则是在渲染的时候不得不考虑的地方,毕竟有些物殊效果非得用多PASS不可?/p>
对于多PASS的设计,我们可以参考OGRE的材质方法或是D3DX的效果框架。我们把完成一个最l效果的Ҏ(gu)UC一个渲染技?Technique Q一个技术可由多个Pass来完成?/p>
class Technique
{
...更多内容
vector<CPass*> mPasses;
};
q样满了(jin)我们的需求?nbsp; 对于同一U效果,我们可以提供多种Technique供程序选择?而这个选择的条件则可以是根据硬件性能Q或是玩家手动选择的配|来实现?/p>
最后结构如下:(x)
class CMaterial
{
public:
...更多内容
vector<CTechnique*> mRenderTechs
};
׃八糟地说?jin)一通,希望没晕MhQ!Q?/p>
在我们的游戏开发中Q通常?x)遇C个模块之间的通信?回调估计是最常用的方式了(jin)?回调的设计思想很简单,是两个对象怺注册Q然后在需要的时候调用对方的函数?/p>
如下Q?/p>
q样Q当A执行自己的某些动作的时候,p用B的函敎ͼq样B׃(x)q行自己的更新或是一些处理?/p>
但是Q由于两个对象的直接回调Q导致了(jin)许多不方便之处。特别是当A和B的功能需要扩展的时候。例如:(x)现在A在执行过E中Q需要调用B中其它的功能函数。这时候就不得不修改A和B的接口。然后大安重新~译Q连接,执行?/p>
于是Q我们就?x)想会(x)不会(x)有一U更好的Ҏ(gu)来解册一问题?大家可以x(chng)QW(xu)INDOWS中的通信机制Q通过解析消息cd来进行处理。是的,消息回调的好处就是方便扩展?当然我们q里要讲的不是像WINDOWS中那L(fng)消息通信机制Q对于我们来_(d)那种做法q繁琐?/p>
假设现在是想让A通知B一些事情。那么,我们可以把B的void DoB();函数作一点点修改Q?/p>
同理Q当B要通知A的时候,也这样做p?jin)?/p>
但是Q这样也很麻?ch),关键在于Q如果现在写cA的hq不知道cB的h?x)怎么写,或者说Q类B不知道什么时候要写。另外,如果我们强制cB要实现这L(fng)接口Q会(x)有点不现实?
此时Q我们决定用一个中间对象来q接他们?/p>
q就是我们传说中的监听器?jin)?在OGRE或是一些广泛采用面向对象思想的源E序中,随处可见q样的模式?/p>
q是假设是A需要通知B一些事情。那么,可以在A中注册这个对象,然后调用它的Ҏ(gu)可以了(jin)?/p>
而我们在实现B的时候,除了(jin)要实现B自己的东西以外,q需要将ICallBackzq实?void Do(int Msg)函数Q?/p>
q样Q双方便很自然地通了(jin)信。而写cA的hҎ(gu)不需要理?x)类B的h?x)怎么写,也不用去类B?x)是什么样的类名。只要告诉写cB的hQ你需要实现这个Callback接口Qƈ且对应的MsgID是干什么用的就O(jin)K?jin)?/p>
也许初初的一看,q是吃力不讨好的工作。毕竟一个写A的hQ会(x)L那么多事情?而一个写B的hQ还要去实现一个CallbackcR但是,从可扩展性,和降低耦合上来Ԍ的确?x)v不少的作用?
而上面的void Do(int MsgID);函数Q可以做得更强大一炏V?/p>
写成void Do(void* pData); 而这个pData怎么使用Q就要看A和B通信的具体内容了(jin)?
我正力地试着把自己想要说的讲清楚Q谢谢!
q日很多朋友咨询Overlay中文昄问题Q回{的多了(jin)想烦(ch)性再写个文档了(jin)Q放在网上共享,于是有?jin)本?/span>
?span>Ogre1.2.5版本中,通过?span>Ogre官方论坛的开发者讨论实C(jin)Overlay的中文显C,当初的实现非常的怪异Q具体的实现可以参见Ogre官方论坛?/span>
随着Ogre的更斎ͼ现在Ogre已经发布?span>1.4.7Q?span>1.4pd版本有一个重要的改进Q就是加入了(jin)UTFStringQ这?span>Ogre中文昄予以很大的帮助。ؓ(f)?jin)便于演C,我直接?span>Ogre自带?span>OverlayQ也是大家熟?zhn)?span>DebugOverlayQ测试工E我选择Demo_ParticleFXQ选择其他的也没有关系。现在编译它Q运行后得到下图Q?/span>
囄最左下角显C的是英文DebugOverlayQ接下来我们的Q务就是把它编E中文的Q?span>^_^?/span>
Overlay中文化操作步骤如?/span>
1. 打开OgreSDK\media\packs\ OgreCore.zip?/span>
2. 打开C:\WINDOWS\FontsQ把simhei.ttfd?span>OgreCore.zipQ(什么,没有simhei.ttfq个文gQ那p其他的中?span>ttf字体吧)(j)?/span>
3. 打开OgreCore.zip中的Ogre.fontdefQ里面有BlueHighwayq个字体定义块,在他的下面添加我们的SimHeiQ?span>code_points里面的一大堆数字看不明白没关p,随后文章?x)解释?/span>
SimHei
{
type truetype
source simhei.ttf
size 16
resolution 96
code_points 33-166 24403-24403 21069-21069 24103-24103 36895-36895 29575-29575 24179-24179 22343-22343 26368-26368 39640-39640 20302-20302 19977-19977 35282-35282 24418-24418 25968-25968 37327-37327 25209-25209 27425-27425
}
4. 打开OgreCore.zip中的OgreDebugPanel.overlayQ把BlueHighway全部替换?span>SimHeiQ我们要使用中文字体?jin),嘿嘿?/span>
5. 修改完成后,保所做的修改已经保存?span>OgreCore.zip?/span>
6. q入Ogre解决Ҏ(gu)Q打开文gExampleFrameListener.hQ把54-59行的代码替换如下Q?/span>
static String currFps = "Current FPS: ";
static String avgFps = "Average FPS: ";
static String bestFps = "Best FPS: ";
static String worstFps = "Worst FPS: ";
static String tris = "Triangle Count: ";
static String batches = "Batch Count: ";
static DisplayString currFps = L"当前帧速率: ";
static DisplayString avgFps = L"q_帧速率: ";
static DisplayString bestFps = L"最高速率: ";
static DisplayString worstFps = L"最低速率: ";
static DisplayString tris = L"三角形数?span>: ";
static DisplayString batches = L"Ҏ(gu): ";
7. 最后重新编译工E,下面是我q行的截图,是不是已l显CZ文了(jin)Q?span>^_^?/span>
现在再来看看SimHei中的code_points是如何生成的Q这个可以参考我上次写的q篇文章http://www.cnblogs.com/gogoplayer/archive/2008/05/09/1189795.htmlQ至此,实现Overlay中文昄?/span>
转蝲h明出处:(x)
作者:(x)gogoplayer
E-mail : gogoplayer@163.com
QQ : 78939328
http://www.gogoplayer.com.cn谢伟?nbsp; feiyurainy@163.com
转蝲h明出?/p>
很早以前想写一些关?span>OGRE的文章了(jin)Q一直没Z(x)?/span>
理解一个渲染引擎,我觉得最重要的是先抓住了(jin)它的L构,它的ȝQ渲染流E,不然的话Q一个引擎几万行Q甚臛_十万行的代码Q光是打开solutionp吓你一跳了(jin)Q?/span>OGRE也有十几万行的代码量Q我一开始看它的时候也是无从下手,感觉代码太多?jin),不知道从哪开始看好,q个class看看Q那?/span>class看看Q由于对整个引擎没有一个清晰的认识Q看q了(jin)也印象不深,所以,最后,q是军_先找出它的主U,?jin)解它的渲染程Q这h能有机地把各个部分联pv来?/span>
q篇短文也是?/span>OGRE的主要渲染流E的一个介l,可能对一?/span>class不会(x)太多地去介绍具体的实现细节。我所用的代码都是取自?/span>OGRE的最新的CVS版本?/span>
读者最好对OGRE有一定的?jin)解Q至得看懂它的exampleQ不然可能一些东西理解v来比较困难。对D3DQ?/span>OPENGL有一定了(jin)解更好?/span>
如果你看q?/span>D3D SDK中带的例子,你一定知道一个比较简单的3DE序要运行v来,臛_都会(x)涉及(qing)以下的几部分Q?/span>
首先是数据的来源Q包括顶Ҏ(gu)据,U理数据{,q些数据可以从文件中dQ也可以在程序运行时生成?/span>
接下来,我们?x)徏立顶点缓冲区把顶点保存v来,建立texture对象来表C?/span>textureQ对点l成的物体设|它在世界坐标系下的坐标Q设|摄像机的位|,视点Q设|?/span>viewport的位|和大小Q然后就可以在渲染@环中开始调用渲染操作了(jin)Q经q了(jin)front buffer?/span>back buffer的交换,我们p在屏q上看到3D囑Ş?jin),伪代码如下?x)
setupVertexBuffer
setWorldTransform
setCamera
setProjectionTransform
setViewport
beginFrame
setTexture
drawObject
endFrame
以下是渲染一个物体的主要步骤Q在我看来,q就?/span>3DE序的主U,同样道理Q无Z多复杂的渲染引擎Q都得实Cq的q些步骤Q其他的一些效果如阴媄(jing)Q光照等Q都是附着在这条主U上的,所以,如果你能在你所研究的渲染引擎上也清晰地看到q条ȝQ可能对你深入地研究它会(x)大有帮助Q下面,我们׃h扑ֈOGRE中的q条ȝ?/span>
OGRE的渲染@环都是v源于Root::renderOneFrameQ这个函数在OGRE自带?/span>example中是不会(x)昑ּ调用的,因ؓ(f)example都调用了(jin)Root::startRenderingQ由startRendering来调?/span>renderOneFrameQ如果你?/span>OGRE来写真正的游戏,或者编辑器Q你可能需要在的消息主循环中调?/span>renderOneFrame?jin),思义Q这个函数就是对整个OGREq行一帧的更新Q包括动画,渲染状态的改变Q渲?/span>api的调用等Q在q个函数中,?x)包括?jin)我们上述伪代码的几乎全部内容Q所以是本文的重Ҏ(gu)在?/span>
q入renderOneFrameQ可以看到头两?/span>fire函数Q这U函数在OGRE中经常出玎ͼ一般都?/span>fire…start?/span>fire…end一起出现的Q在q些函数中,可能?x)处理一些用戯定义的操作,?/span>_fireFrameStarted׃(x)Ҏ(gu)以的frameListenerq行处理Q这?/span>fire函数可以暂时不用理会(x)Q(h)l看_updateAllRenderTargetsQ在q个函数中,?x)委zֽ前所用的rendererҎ(gu)有创建出来的render targetq行updateQ?/span>render target也就是渲染的目的圎ͼ一般会(x)有两U,一U是render textureQ一U是render bufferQ接着q入RenderSystem::_updateAllRenderTargetsQ可以看到在render system中,对创建出来的render target是用RenderTargetPriorityMap来保存的Q以便按照一定的序来对render targetq行updateQ因为在渲染物体?/span>render bufferӞ一般会(x)用到之前渲染好的render textureQ所?/span>render texture形式?/span>render target需要在render buffer之前q行更新?/span>
q入render target?/span>updateQ可以看刎ͼ它仍然把update操作l箋(hu)传递下去,调用所有挂在这?/span>render target上的viewport?/span>update?/span>
Viewport其实是定义?/span>render target上的一块要q行更新的区域,所以一?/span>render target是可以挂多个viewport的,以实现多人对战时分屏Q或者是Mȝ效果Q可以把OGRE中的viewport看成是保?/span>camera?/span>rendertargetq两者的l合Q把viewport中所定义?/span>camera所看到的场景内Ҏ(gu)染到viewport所定义?/span>render target的区域里?/span>
Viewportq有一个重要信息是ZOrderQ可以看?/span>RenderTarget中的ViewportList带有一个比较函敎ͼ所以在RenderTarget::update中,ZOrder小的,先被渲染,所以,如果两个viewport所定义的区域互盔R叠了(jin)Q而且ZOrder又不一P最l的效果是ZOrder的viewport的内容会(x)?/span>ZOrder大的viewport的内Ҏ(gu)覆盖?/span>
l箋(hu)q入Viewport::updateQ就像前面所_(d)它调用它所引用?/span>camera来渲染整个场景,而在Camera::_renderScene中,是调?/span>SceneManager::_renderScene(Camera* camera, Viewport* vp, bool includeOverlays)?/span>SceneManager::_renderScene里就是具体的渲染程?jin)。从函数名称q有参数也可以看出来Q这个函数的作用是利用所指定?/span>camera?/span>viewportQ来把场景中的内Ҏ(gu)染到viewport所指定?/span>render target的某块区域中。根?/span>cameraQ我们可以定?/span>view matrixQ?/span>projection matrixQ还可以q行视锥剔除Q只渲染看得见的物体。注意,我们q里只看标准?/span>SceneManager的方法,不看BspSceneManagerzcȝҎ(gu)Q而且Q我们会(x)抛开跟主U无关的内容Q如?/span>shadow?/span>setupQ骨骼动ȝ播放Q?/span>shader参数的传递等Q因为我们只注重渲染的主程?/span>
?/span>SceneManager::_renderScene中所应看的第一个重要函数是_updateSceneGraphQ?/span>OGRE对场景的l织是通过节点?wi)来l织的,一个节点,你可以看成是I间中的某些变换的组合,如位|,~放Q旋转等Q这些变换,?x)作用到挂接在这些节点上的具体的物体的信息,也就是说Q节点保存(sh)(jin)world transformQ对具体的物体,如一个hQ在I间中的定位Q都是通过操作节点来完成的。同时节点还?sh)存(sh)(jin)一个世界坐标的AABBQ这?/span>AABB能容Ux(chng)有它所挂接的物体的大小Q主要是用于视锥裁减的,如果当前摄像机看不见某个节点?/span>AABBQ那么说明摄像机看不见节Ҏ(gu)挂接的所有物体,所以在渲染时可以对q个节点视而不见?/span>
_updateSceneGraph的内部处理比较繁琐,我们只需知道Q经q了(jin)_updateSceneGraphQ场景节Ҏ(gu)(wi)中的每个节点都经q了(jin)更新Q包括位|,~放Q和方位Q还有节点的包围盒?/span>
l箋(hu)回到SceneManager::_renderSceneQ接下来要看的是setViewportQ它?x)调用具体?/span>renderer?/span>setviewport的操作,讄viewport中所挂接?/span>render target为当前所要渲染的目标Q?/span>viewport中的区域为当前所要渲染的目标中的区域?/span>
接下来要到OGRE渲染程中的一个重要的概念Q?/span>Render Queue。这个东西实在内Ҏ(gu)较多Q还是以后有Z(x)单独提出来说吧,你可以简单把它想成是一个容器,里面的元素就?/span>renderableQ每?/span>renderable可以看成是每ơ调?/span>drawprimitive函数所渲染的物体,可以是一个模型,也可以是模型的一部分。在RenderQueue中,它会(x)按材质来分组q些renderableQ还?sh)(x)?/span>renderableq行排序?/span>
在每一ơ调?/span>SceneManager::_renderSceneӞ都会(x)调用SceneManager::prepareRenderQueue来清?/span>RenderQueueQ然后再调用SceneManager::__findVisibleObjects来把当前摄像机所能看见的物体都加入到RenderQueue中?/span>
SceneManager::__findVisibleObjects是一个递归的处理过E,它从场景的根节点开始,先检查摄像机是否能看见这个节点的包围盒(包围盒在_updateSceneGraph时已l计好?jin)?j)Q如果看不见Q那么这个节点,q有它的子节炚w不用了(jin)。如果能看见Q再(g)挂在这个节点上的所?/span>MovableObjectQ如果当前所(g)的MovableObject是可见的Q就?x)调用它?/span>_updateRenderQueueҎ(gu)Q一般在q个Ҏ(gu)里就可以把和q个MovableObject相关?/span>renderable送入RenderQueue?jin)?/span>
q里要说?/span>MovableObjectQ?/span>MovableObject主要是用于表C场景中L的物体,?/span>EntityQ顾名思义Q能Ud的物体,不过它的“能移?#8221;q个能力是要通过SceneNode来实现的Q所?/span>MovableObject来能昄出来Q首先得先挂接在某个场景节点上,通过场景节点来定位。你可以控制MovableObject的一些属性,如某?/span>MovableObject是否要显C,是否要隐藏,都可以通过MovableObject::setVisibleҎ(gu)来实现?/span>
(g)完该节点上?/span>MovableObject之后Q就l箋(hu)调用所有子节点?/span>_findVisibleObjectsҎ(gu)Q一直递归下去。这Pp把场景中所有要渲染?/span>renderable所加入?/span>RenderQueue中了(jin)?/span>
x(chng)Q我们就拥有?jin)要渲染的物体的信息了(jin),接下来就是对q些物体q行渲染?jin),你?x)发现?/span>D3D?/span>OpenGL的代码很cM的调用:(x)
mDestRenderSystem->clearFrameBuffer
mDestRenderSystem->_beginFrame
mDestRenderSystem->_setProjectionMatrix
mDestRenderSystem->_setViewMatrix
_renderVisibleObjects();
mDestRenderSystem->_endFrame();
q些api的作用和D3D中的cM调用的作用都差不多,q里再说一?/span>_renderVisibleObjects()Q在q个函数中,?x)?/span>RenderQueue中的每个renderableq行渲染Q用的是visitor模式来遍历操作每?/span>renderableQ最l在SceneManager::renderSingleObject中取出每?/span>renderable所保存的顶点,索引Q世界矩늭信息Q来q行渲染。这其中q包括了(jin)查找该renderable最q的光源{操作,比较复杂?/span>
到这里,SceneManager::_renderScene的流E基本走完了(jin)Q也是_(d)OGRE一帧中的渲染流E差不多也结束了(jin)Q你应该也发玎ͼq个程跟你?/span>D3D写一个简单程序的程基本是一L(fng)Q在q个程的基上,再去看具体的实现Q如怎么栯|纹理,怎么栯用你熟?zhn)?/span>D3D?/span>OpenGL?/span>API来渲染物体,应该?x)简单得多?/span>
?/span>OGRE的渲染流E的大概介绍到这里也l束?jin),很多l节都没涉及(qing)Q以后有Z(x)再写吧?/span>
《游戏中的资源管理――资源高速缓存?br>转蝲h明出处:(x)http://groups.google.com/group/jianguhan
1.什么是资源高速缓?
资源高速缓存的原理与其它内存高速缓存的工作原理是相似的。在游戏的状态{换过E中Q有些数据是刚才使用q的Q那么直接从资源高速缓存(sh)载入卛_。例如,RPG游戏中主角从大地图进入一个房_(d)探烦(ch)一番后主角退出房_(d)此时只要直接从缓存(sh)载入大地图数据即可,节省?jin)从盘载入数据的时_(d)要知道从盘载入数据是非?shy;慢的。当?dng)如果你的游戏所使用的数据文件很,那么你可以在游戏q行q程中把q些数据完全储存在内存(sh)Q而不使用资源高速缓存?
2.一个简单的资源高速缓存管理器
下面我将向你展示一个比较简单的资源高速缓存管理器Q源代码来自我上一个游戏,如果你需要知道更多关于资源高速缓存方面的知识Q请参?lt;<Game Coding Complete>>的第八章?
首先Q需要一个机制来唯一标识一个资源,我们用下面这个结构来做资源句柄:(x)
struct ResHandle
{
ResHandle(std::string &resName, void *buffer, int size)
{
m_resName = resName;
m_size = size;
m_buffer = buffer;
}
~ResHandle()
{
if (m_buffer != 0) delete[] m_buffer;
}
std::string m_resName; //资源?
void *m_buffer; //资源句柄所标识的资?
DWORD m_size; //资源所占内存大?
};
好了(jin)Q现在我们可以从资源名来扑ևq个资源?jin),接下来实现这个资源高速缓存管理器Q?
class CacheManager
{
public:
CacheManager();
~CacheManager();
//载入资源QresName源名Q若载入成功size被设资源的大?
//注意Q管理中的资源不能在理器外用delete昄的删除它
void* Load(std::string resName, DWORD *size = 0);
//讄~存大小Q单位MB
void SetCacheSize(int sizeMB) { m_cacheSize = sizeMB * 1024 * 1024; }
//得到~存大小Q单位MB
int GetCacheSize() { return m_cacheSize / 1024 /1024; }
private:
void Free(); //释放lru链表中最后一个资?
void *Update(ResHandle *res); //更新l(f)ru链表
ResHandle *Find(std::string &resName); //扑և该资源名的资源句?
private:
DWORD m_cacheSize; //~存大小
DWORD m_allocated; //已用的~存大小
//lru链表Q记录最q被使用q的资源
std::list<ResHandle*> m_lru;
//资源标识映射
std::map<std::string, ResHandle*> m_resources;
};
CacheManager:: CacheManager ()
{
m_cacheSize = 0;
m_allocated = 0;
}
CacheManager::~ CacheManager ()
{
while (!m_lru.empty()) Free(); //释放所有管理中的资?
}
void * CacheManager::Load(std::string resName, DWORD *size)
{
ResHandle *handle = Find(resName); //查找该资源是否在~存?sh)?
if (handle != 0) //如果扑ֈ该资源句柄,则返回该资源q更新l(f)ru链表
{
if (size != 0) *size = handle->m_size;
return Update(handle);
}
else
{
//先检资源大?
DWORD _size = 资源大小;
//是否有够空?
while (_size > (m_cacheSize - m_allocated))
{
if (m_lru.empty()) break;
Free();
}
m_allocated += _size;
buffer = new char[_size];
//在这里用M你能惛_的办法蝲入资源文件到buffer
…
…
//记录当前资源
ResHandle *handle = new ResHandle(resName, buffer, _size);
m_lru.push_front(handle);
m_resources[resName] = handle;
if (size != 0) *size = _size;
return buffer;
}
return 0;
}
void CacheManager::Free()
{
std::list<ResHandle*>::iterator gonner = m_lru.end();
gonner--;
ResHandle *handle = *gonner;
m_lru.pop_back();
m_resources.erase(handle->m_resName);
m_allocated -= handle->m_size;
delete handle;
}
void * CacheManager::Update(ResHandle *res)
{
m_lru.remove(res);
m_lru.push_front(res);
m_size = res->m_size;
return res->m_buffer;
}
ResHandle * CacheManager::Find(std::string &resName)
{
std::map<std::string, ResHandle*>::iterator it = m_resources.find(resName);
if (it == m_resources.end()) return 0;
return (*it).second;
}
x(chng)Q你已经可以在游戏中~存?sh)M你想~存的资源了(jin)^_^
3. 资源理q阶
x(chng)你已l可以在游戏中缓存(sh)Q何你想缓存的资源?jin),但是你的dq没完成Q当你请求的资源存在于缓存(sh)外时Q那个闪耀的硬盘灯可能是玩家最感兴的东西?jin)?
因此你必L据不同的游戏cd使用不同的蝲入方式:(x)
一ơ蝲入所有东西:(x)适用于Q何以界面或关卡切换的游戏
只在关键点蝲入资源:(x)很多击游戏都用这L(fng)设计Q如“半条?#8221;
持箋(hu)载入Q适用于开攑֞地图的游戏,?#8220;侠盗猎R?#8221;
如果有可能的话,你还可以使用~存预测机制Q当CPU有额外时间的时候可以把未来可能用到的资源蝲入到资源高速缓存?
最后,管在游戏的资源理中资源打包不是必ȝQ但仍然大家把资源文件按cd分别打包到单一的文件中Q这ؓ(f)你节省磁盘空_(d)q加快游戏的载入速度?
?gu)http://blog.csdn.net/duzhi5368/archive/2008/04/22/2314232.aspx
使用设计模式来提高程序库的重复利用性是大型E序目开发必ȝ。但是在“四h?#8221;的设计模式概qC提到?/span>23U标准设计模式,不但难以CQ而且有些设计模式更多的适用于应用程序开发,Ҏ(gu)戏项目引擎设计ƈ没有很多的利用h(hun)倹{根据经验,_挑(xi)l选后Q笃志在q里记录一些自认ؓ(f)有利用h(hun)值的设计模式Q以便之后自p计时使用?/span>
观察者的设计意图和作用是Q?/span> 它将对象与对象之间创ZU依赖关p,当其中一个对象发生变化时Q它?x)将q个变化通知l与其创建关pȝ对象中,实现自动化的通知更新?/span>
游戏中观察者的适用环境?/span>Q?/span>
1Q?/span>UI控g理cR当我们?/span>GUI控g都用观察者模式后Q那么用L(fng)M界面相关操作和改变都会(x)通知其关联对?/span>-----我们?/span>UI事g机?/span>
2Q动ȝ理器。很多时候我们在播放一个动L的时候,对其Frame有很大兴,此时我们讄一?/span>FrameLister对象对其q行监视Q获得我们关?j)的事gq行处理是必ȝ?/span>
观察者伪代码Q?/span>
//-------------------------------------------------------------------------------------------------------
// 被观察对象目标类
Class Subject
{
// Ҏ(gu)目标l定一个观察?/span> Attach( Observer );
// 解除一个观察者的l定 DeleteAttach( Observer );
// 本目标发生改变(sh)(jin)Q通知所有的观察者,但没有传递改动了(jin)什?/span>
Notity()
{
For ( …遍历整个ObserverList …)
{ pObserver ->Update(); }
}
// 对观察者暴露的接口Q让观察者可获得本类有什么变?/span>GetState();
}
//-------------------------------------------------------------------------------------------------------
// 观察?/span>/监听者类
Class Observer
{
// 暴露l对象目标类的函敎ͼ当监听的对象发生?jin)变动,则它会(x)调用本函数通知观察?/span>
Void Update ()
{
pSubject ->GetState(); // 获取监听对象发生?jin)什么变?/span>
TODOQ?/span>DisposeFun(); // Ҏ(gu)状态不同,l予不同的处?/span>
}
}
//-------------------------------------------------------------------------------------------------------
非程序语a描述Q?/span>
A?/span>B的好朋友Q对B的行为非常关?j)?/span>B要出门,此时Al了(jin)B一个警报器Q告?/span>B_(d)(x)“如果你有事,立刻按这个警报器告诉我?#8221;。结?/span>B在外面遇上了(jin)ȝ(ch)Q按下警报器Q?/span>Update()Q,Bq?/span>AZ(jin)事,于是p查一?/span>B到底遇到?jin)什么麻?/span>(GetState())Q当知道B原来是因人打?jin),于是立刻q行处理DisposeFun()Q派?jin)一手下帮B打架?/span>
当然兛_(j)A的h可以不止一个,CQ?/span>D可能也对A很关?j),于?/span>Aq里保存?sh)个所有关?j)它的h的链表,当遇到麻?ch)的时候,轮流l每个h一份通知?/span>
单g模式的设计意囑֒作用?/span>Q?/span> 保证一个类仅有一个实例,q且Q仅提供一个访问它的全局讉K炏V?/span>
游戏中适用于单件模式的?/span>Q?/span>
1Q所有的Manger。在大部分的行引擎中都存在着它的影子Q例?/span>SoundManager, ParticeManager{?/span>
2Q大部分的工厂基cR这一点在大部分引擎中q是见不到的Q实际上Q我们的父类工厂采用唯一实例的话Q我们子c进行扩展时也会(x)有很大方ѝ?/span>
单g模式伪代?/span>Q?/span>
//-------------------------------------------------------------------------------------------------------
Class Singleton
{
Static MySingleton; // 单g对象Q全局唯一的?/span>
Static Instance(){ return MySingleton;} // 对外暴露接口
}
//-------------------------------------------------------------------------------------------------------
q代器设计意囑֒作用?/span>Q?/span> 提供一个方法,对一个组合聚合对象内各个元素q行讉KQ同时又不暴露该对象cȝ内部表示?/span>
游戏中适用于P代器模式的有Q?/span> 因ؓ(f)STL的流行,q个设计已经qؓ(f)人知?jin),我们对Q何Ş式的资源通一理Ӟ不免?x)将其聚合v来,或?/span>ListQ或?/span>VectorQ我们都需要一个对其进行访问的工具QP代器无疑是一个利器?/span>
q代器伪代码Q?/span>
//-------------------------------------------------------------------------------------------------------
// q代器基c?/span>
Class Iterator
{
Virtual First();
Virtual Next();
Virtual End();
Virtual CurrentItem(); // q回当前Item信息
}
//-------------------------------------------------------------------------------------------------------
// 聚合体的基类
Class ItemAggregate
{
Virtual CreateIterator(); // 创徏讉K自n的一个P代器
}
//-------------------------------------------------------------------------------------------------------
// 实例化的目聚合?/span>
Class InstanceItemAggregate : public ItemAggregate
{
CreateIterator(){ return new InstanceIterator(this); }
}
//-------------------------------------------------------------------------------------------------------
讉K者设计意囑֒作用?/span>Q?/span> 当我们希望对一个结构对象添加一个功能时Q我们能够在不媄(jing)响结构的前提下,定义一个新的对其元素的操作。(实际上,我们只是把对该元素的操作分割l每个元素自w类中实C(jin)而已Q?/span>
游戏中适用于访问者模式的?/span>Q?/span> M一个比较静(rn)态的复杂l构cM都适合采用一份访问者。这里的“比较?rn)态的复杂l构c?#8221;意思是Q该l构cM元素J多且种cd杂,且对应的操作较多Q但cd进行变化,我们p够将Q对q个l构cd素的操作独立出来Q避免污染这些元素对象?/span>
1Q例如场景管理器中管理的场景节点Q是非常J多的,而且U类不一Q例如有Ogre中的Root, Irrchit中就把摄象机Q灯光,MeshQ公告版Q声音都做ؓ(f)一U场景节点,每个节点cd是不同的Q虽然大安有共通的Paint(),Hide(){方法,但方法的实现形式是不同的Q当我们外界调用旉要统一接口Q那么我们很可能需要需要这L(fng)代码
Hide( Object )
{ if (Object == Mesh) HideMesh(); if (Object == Light) HideLight(); … }
此时若我们需要增加一?/span>Object新的cd对象Q我们就不得不对该函数进行修正。而我们可以这样做Q让Mesh,Light他们都(h)承于Object,他们都实C个函?/span>Hide(),那么变?/span>
Mesh::Hide( Visitor ) { Visitor.Hide (Mesh); }
Light::Hide(Visitor ){ Visitor.Hide (Light); }
我们在调用时只需?/span>Object.Hide(Visitor){ return Visitor.Hide(Object); }
q样做的好处Q我们免M(jin)寚w要函数的修正Q?/span>Object.Hide(Visitor){}函数我们可以怹不变Q但?strong>坏处也是很明昄Q因为将Ҏ(gu)从对象集合结构中抽离出来Q就意味着我们每增加一个元素,它必ȝ(h)承于一个抽象的被访问者类Q实现其全部函数Q这个工作量很大?/span>
所以,讉K者是仅适合于一个装载不同对象的大容器,但同时又要求q个容器的元素节点不应当有大的变动时才?/span>。另外,废话一句,讉K者破坏了(jin)OO思想的?/span>
讉K者伪代码Q?/span>
//-------------------------------------------------------------------------------------------------------
// 讉K者基c?/span>
Class Visitor
{
Virtual VisitElement( A ){ … }; // 讉K的每个对象都要写q样一个方?/span>
Virtual VisitElement( B ){ … };
}
// 讉K者实?/span>A
Class VisitorA
{
VisitElement( A ){ … }; // 实际的处理函?/span>
VisitElement( B ){ … }; // 实际的处理函?/span>
}
// 讉K者实?/span>B
Class VisitorB
{
VisitElement( A ){ … }; // 实际的处理函?/span>
VisitElement( B ){ … }; // 实际的处理函?/span>
}
// 被访问者基c?/span>
Class Element
{
Virtual Accept( Visitor ); // 接受讉K?/span>
}
// 被访问者实?/span>A
Class ElementA
{
Accecpt( Visitor v ){ v-> VisitElement(this); }; // 调用注册到访问者中的处理函?/span>
}
// 被访问者实?/span>B
Class ElementB
{
Accecpt( Visitor v ){ v-> VisitElement(this); }; // 调用注册到访问者中的处理函?/span>
}
//-------------------------------------------------------------------------------------------------------
外观模式的设计意囑֒作用?/span>Q?/span> 用h触的表层和内部子集的实现分离开发。实际上Q这个模式是个纸老虎Q之后我们看伪代码立d?x)发玎ͼq个模式实在用的太频J了(jin)?/span>
游戏中需要用外观模式的地方?/span>Q?/span> q个非常多了(jin)QD几个比较重要的?/span>
1Q实现^台无x(chng)。跨q_跨库的函数调用?/span>
2Q同一个接口去d不同的资源?/span>
3Q硬件自动识别处理系l?/span>
外观模式伪代?/span>
//-------------------------------------------------------------------------------------------------------
// 用户使用的接口类
Class Interface
{
// 暴露出来的函数接口函敎ͼ有且仅有一个,但内部实现是调用?jin)两个?/span>
Void InterfaceFun()
{
// Ҏ(gu)某种条gQ底层自ȝ选择使用A?/span>B的方法。用hd?j)底层实?/span>
If ( XXX )
{
ActualA->Fun();
}
Else
{
ActualB->Fun();
}
};
}
// 实际的实玎ͼ不暴露给用户知道
Class ActualA
{
Void Fun();
}
// 实际的实玎ͼ不暴露给用户知道
Class ActualB
{
Void Fun();
}
怎么PU老虎吧,看v来很高深摸测的命名而已?/font>
//-------------------------------------------------------------------------------------------------------
抽象工厂的设计意囑֒作用?/span>Q?/span> 装Z个接口,q个接口负责创徏一pd互相兌的对象,但用户在使用接口时不需要指定对象所在的具体的类。从中文命名也很Ҏ(gu)明白它是q行扚w生的一个生产工厂的作用?/span>
游戏中用抽象工厂的地方?/span>Q?/span> 基本上Q何有扚w的同cdŞ式的子g地方׃(x)有工厂的存在。(补充一句:(x)下面代码中的ConcreteFactory1实例工厂是工厂Q而抽象工厂仅仅是工厂的一个抽象层而已?/span>Q?/span>
1Q例如,在音频方面,一个音频的抽象工厂zZ同的工厂Q有音乐工厂Q音效工厂。音效工厂中又有一个创?/span>3Dx(chng)节点的方法,一个创建普通音效节点的Ҏ(gu)。最l用户只需?/span>SoundFactory->Create3DNode( pFileName );可以创Z个节点了(jin)?/span>
2Q场景对象?/span>
3Q渲染对象?/span>
4Q等{?#8230;…
工厂与单Ӟ理?/span>Manager关系一定是非常紧密的?/span>
抽象工厂伪代?/span>Q?/span>
//-------------------------------------------------------------------------------------------------------
class AbstractProductA {}; // 抽象的?/span>A基类
class AbstractProductB {}; //抽象的?/span>B基类
// 抽象工厂基类
class AbstractFactory
{
public:
virtual AbstractProductA* CreateProductA() = 0 ;// 创徏ProductA
virtual AbstractProductB* CreateProductB() = 0 ;// 创徏ProductB
} ;
class ProductA1 : public AbstractProductA {}; // 产品A的实?/span>1
class ProductA2 : public AbstractProductA {}; // 产品A的实?/span>2
class ProductB1 : public AbstractProductB {}; // 产品B的实?/span>1
class ProductB2 : public AbstractProductB {}; // 产品B的实?/span>2
// 实例工厂1
class ConcreteFactory1 : public AbstractFactory
{
virtual AbstractProductA* CreateProductA() { return new ProductA1() ; }
virtual AbstractProductB* CreateProductB() { return new ProductB1() ; }
static ConcreteFactory1* Instance() { } // 实例工厂量使用单g模式
} ;
// 实例工厂2
class ConcreteFactory2 : public AbstractFactory
{
virtual AbstractProductA* CreateProductA() { return new ProductA2() ; }
virtual AbstractProductB* CreateProductB() { return new ProductB2() ; }
static ConcreteFactory2* Instance() {} // 实例工厂量使用单g模式
} ;
}
//-------------------------------------------------------------------------------------------------------
客户端代?/span>Q?/span>
Void main()
{
AbstractFactory *pFactory1 = ConcreteFactory1::Instance() ;
AbstractProductA *pProductA1 = pFactory1->CreateProductA() ;
AbstractProductB *pProductB1 = pFactory1->CreateProductB() ;
AbstractFactory *pFactory2 = ConcreteFactory2::Instance() ;
AbstractProductA *pProductA2 = pFactory2->CreateProductA() ;
AbstractProductB *pProductB2 = pFactory2->CreateProductB() ;
}
//-------------------------------------------------------------------------------------------------------