2008年11月19日
水面原来采用渲染质量较高的菲捏珥水面,这样渲染出来的效果确实不错,不过这需要渲染折射图与反射图,再加上最后一遍场景渲染,整个场景需要渲染三遍,虽然GPU GEM2里面提到了这类水面的优化,经过项目中使用发现开销还是相当的大,外加上原来没有考虑边界柔和,导致水面切边非常锋利,所以决定采用较低的alpha水面混合来代替。
接下来说说我采用的水面alpha混合方案,需求如下:
1. 水面只需要反射天空
2. 边界柔和,水深的地方反射强度大,也就是越看不清水底
3. 相机距离水面的距离越近,水面就需要越透明(试想相机看着人,而看不见水面下的情况)
根据以上需求,我决定采用在PS中做逐像素渲染。
1. 只反射天空虽然有时候看起来并不真实,但实现足够简单,效率高,这样就不需要反射图了,节省一个RT。简单的实现就是采用一张立方体贴图,然后根据相机与像素向量求个反射向量,再用texCUBE做个采样搞定。

2. 边界柔和看这张图,没有边界柔和的图很丑陋,像一把刀切过一样。这个可以通过做一张水深遮罩图来完成,这张图用基本的灰度图就可以了,平铺整个地形,可以这样生成:取像素任意一点,转换为世界坐标,求出与地形的交点,如果地形在上,则为全透明,填0,如果大于25.5则填1,中间做个线型混合 (byte)(abs(h) × 10)。这样得到的深度图效果如下:

如果把这张深度图做为alpha来输出,颜色为白色,我在场景中截了一张图,看,是不是有点样子了,岸边的锋利边缘被柔化了^_^。

3 相机距离水面的距离越近,水面就需要越透明,这个只须在PS中用distance求出相机与像素的距离,然后通过求出的距离混合alpha就可以了。
看看加上颜色信息后的最终效果,反射了天空,柔化了边缘,效率也提高了,正是我们想要的。
再来一张:
转载请注明出处:
作者:gogoplayer
E-mail : gogoplayer@163.com
QQ : 78939328
http://www.gogoplayer.com.cn
2008年11月13日
每盏灯都可以有镜面反射,最多支持三盏灯,再多就不能使用ps2_0,附上HLSL代码和执行文件,自己玩吧^_^
1
float4x4 World;
2
float4x4 View;
3
float4x4 Projection;
4
float4x4 WorldViewProjection;
5
float3 EyePosition;
6
7
#define MaxLights 3
8
9
float3 LightDirs[MaxLights];
10
float4 LightColors[MaxLights];
11
int LightCount;
12
13
float4 AmbientColor = float4(0.05,0.05,0.05,1);
14
float SpecularPower = 16;
15
16
texture Texture;
17
sampler TextureSampler = sampler_state
18

{
19
Texture = (Texture);
20
AddressU = Wrap;
21
AddressV = Wrap;
22
AddressW = Wrap;
23
MinFilter = Linear;
24
MagFilter = Linear;
25
MipFilter = Linear;
26
};
27
28
struct VertexShaderInput
29

{
30
float4 pos : POSITION0;
31
float2 texCoord : TEXCOORD0;
32
float3 normal : NORMAL;
33
float3 tangent : TANGENT;
34
};
35
36
struct VertexShaderOutput
37

{
38
float4 pos : POSITION0;
39
float2 texCoord : TEXCOORD0;
40
float3 normal : TEXCOORD1;
41
float3 view : TEXCOORD2;
42
};
43
44
VertexShaderOutput VertexShaderFunction(VertexShaderInput input)
45

{
46
VertexShaderOutput output;
47
48
WorldViewProjection = mul(mul(World, View), Projection);
49
output.pos = mul(input.pos, WorldViewProjection);
50
output.texCoord = input.texCoord;
51
output.normal = mul(input.normal, (float3x3)World);
52
output.view = EyePosition - mul(input.pos, World);
53
54
return output;
55
}
56
57
float4 PixelShaderFunction(VertexShaderOutput input) : COLOR0
58

{
59
float4 diffuseSum = 0;
60
float4 specularSum = 0;
61
for(int i = 0; i < LightCount; i++)
62
{
63
float3 L = normalize(-LightDirs[i]);
64
float3 N = normalize(input.normal);
65
float3 R = normalize(reflect(LightDirs[i], N));
66
float3 V = normalize(input.view);
67
68
diffuseSum += saturate(dot(N, L)) * LightColors[i];
69
specularSum += pow(saturate(dot(R, V)), SpecularPower);
70
}
71
72
float4 textureColor = tex2D(TextureSampler, input.texCoord);
73
74
float4 final = AmbientColor + textureColor * diffuseSum + specularSum;
75
76
return final;
77
}
78
79
technique Technique1
80

{
81
pass Pass1
82
{
83
VertexShader = compile vs_2_0 VertexShaderFunction();
84
PixelShader = compile ps_2_0 PixelShaderFunction();
85
}
86
}
87
转载请注明出处:
作者:gogoplayer
E-mail : gogoplayer@163.com
QQ : 78939328
http://www.gogoplayer.com.cn
2008年11月6日
新版Ogre的帧监听器(FrameListener)新加了一个方法,frameRenderingQueued,查看例子后发现,原先的frameStarted基本都被这个方法所代替了,决定打开源代码看看Ogre的意图。
我们从renderOneFrame开始分析,这个方法只有三句话。
1
bool Root::renderOneFrame(void)
2
{
3
if(!_fireFrameStarted())
4
return false;
5
6
if (!_updateAllRenderTargets())
7
return false;
8
9
return _fireFrameEnded();
10
}
11
1. 触发所有FrameListener的frameStarted
2. _updateAllRenderTargets
3. 触发所有FrameListener的frameEnded
这个过程很清晰,不再多说,并没有发现frameRenderingQueued调用。接着进入_updateAllRenderTargets,这个方法也做三件事。
bool Root::_updateAllRenderTargets(void)

{
// update all targets but don't swap buffers
mActiveRenderer->_updateAllRenderTargets(false);
// give client app opportunity to use queued GPU time
bool ret = _fireFrameRenderingQueued();
// block for final swap
mActiveRenderer->_swapAllRenderTargetBuffers(mActiveRenderer->getWaitForVerticalBlank());
return ret;
}

1. 更新所有渲染目标(不翻转)
2. 触发所有FrameListener的frameRenderingQueued(发现目标了^_^)
3. 翻转所有渲染目标
由此可见,frameRenderingQueued的调用时机是在frameStarted的后面,frameEnded的前面,而且在frameStarted和frameRenderingQueued还有一个更新所有渲染目标(不翻转),经过这样分析,调用关系就清楚了,简单归纳为下列五步:
1. 触发所有FrameListener的frameStarted
2. 更新所有渲染目标(不翻转)
3. 触发所有FrameListener的frameRenderingQueued
4. 翻转所有渲染目标
5. 触发所有FrameListener的frameEnded
通过分析后已经可以看明白调用过程了,接下来进一步说说,为什么要加入frameRenderingQueued呢,一个方法的加入总归是有原因的,大家都很忙,Ogre团队不会浪费时间加一个没用的东西。首先看原来用的frameStarted,可以看到frameStarted的调用时机是最靠前的,接下来是更新所有渲染目标(不翻转),然后是frameRenderingQueued。可以看到,frameStarted和frameRenderingQueued的主要区别就在于更新所有渲染目标前调用还是后调用。如果把逻辑放在frameStarted中,那么更新所有渲染目标这个操作必然在其之后;反之把逻辑放在frameRenderingQueued中,则逻辑执行在更新所有渲染目标之后,那为什么要放在后面,而不是放在前面呢?原因在于渲染是由GPU来完成的,更新所有渲染目标这个操作返回时,GPU需要一定的时间来完成当前的渲染,这样当执行frameRenderingQueued时,相当于逻辑和GPU在并行计算,CPU也没有闲着,这样就提高了效率,效率很重要,不是么,^_^。
这里还需要指出的一点是,由于frameRenderingQueued执行时,GPU已经在渲染上一帧内容了,因此写在frameRenderingQueued里的逻辑将在下一帧才能够在渲染中体现出来,一般来说,这个问题是无关紧要的。
转载请注明出处:
作者:gogoplayer
E-mail : gogoplayer@163.com
QQ : 78939328
http://www.gogoplayer.com.cn
2008年10月30日
XNA2.0系统API居然出错!折腾了我N久。现象是鼠标射线不准,莫名其妙的不准,有时旋转一下相机就乱了,在官网论坛上找了一个替代版本,问题解决。这个问题XNA1.0并不存在,到2.0就有了,用反编译查看,果然是XNA1.0使用DX实现,XNA2.0是重写的方法。这个问题XNA论坛都提出来了,ViewPort.Unproject也算是一个比较重要的方法,居然到XNA3.0还存在,真不知道开发人员是怎么想的,为这个破问题折腾来折腾去,先前以为是相机问题,重写了好多遍,看来即使是官方API也不要过于迷信,这回主要就栽在这点。
经验总结:代码使人写的,不是神写的,人写的就会出错,就这么简单。最后附上可用的代替版本,看有多少可怜的孩子还在受到原API的毒害…

Code
1 public static Vector3 UnprojectEx(Viewport viewport, Vector3 screenSpace,Matrix projection, Matrix view, Matrix world)
2 {
3 //First, convert raw screen coords to unprojectable ones
4 Vector3 position = new Vector3();
5 position.X = (((screenSpace.X - (float)viewport.X) / ((float)viewport.Width)) * 2f) - 1f;
6 position.Y = -((((screenSpace.Y - (float)viewport.Y) / ((float)viewport.Height)) * 2f) - 1f);
7 position.Z = (screenSpace.Z - viewport.MinDepth) / (viewport.MaxDepth - viewport.MinDepth);
8
9 //Unproject by transforming the 4d vector by the inverse of the projecttion matrix,
10 //followed by the inverse of the view matrix.
11 Vector4 us4 = new Vector4(position, 1f);
12 Vector4 up4 = Vector4.Transform(us4,
13 Matrix.Invert(Matrix.Multiply(Matrix.Multiply(world, view), projection)));
14 Vector3 up3 = new Vector3(up4.X, up4.Y, up4.Z);
15 return up3 / up4.W; //better to do this here to reduce precision loss..
16 }
转载请注明出处:
作者:gogoplayer
E-mail : gogoplayer@163.com
QQ : 78939328
http://www.gogoplayer.com.cn
2008年10月14日
换装基本上是每个网游都必须有的一个功能,每种网游的做法都各有不同,有些是换掉整个模型,有些则是通过可以换掉模型的一个部分完成。前者属于整体换,相对简单些;后者则是通过部分替换实现,目前用的比较多,本文主要描述后者的。
在开始描述换装前,首先要具备骨骼动画的知识,如果对骨骼动画的原理不熟悉,换装是比较难以理解的。换装的核心其实并不在换上,而是要理解为什么能换,而这些都和骨骼动画密不可分。骨骼动画是通过关键帧驱动骨骼运动,随之依次调整每块骨头的朝向和坐标,骨头再带动顶点运动(为了高效,现在很多都使用GPU加速,这样就不需要修改顶点缓冲区了),蒙皮信息就描述了了每个顶点受哪些骨头的影响,以及他们的权重,这样骨骼动画就运动起来了。其他的原理先略过,google上解释比我的好,^_^。
目前一般网游的换装都是采用替换掉模型某个部分来实现的,脸型、头发、衣服、手套、裤子、裙子、鞋子等一般都和人物肉身做在一起,可以在需要时显示或隐藏对应部分。这里一般有一个原则,如果对应部分需要支持形变,则必须和肉身做到一起,因为必需要有蒙皮信息才能实现形变。
在我这个设计中,人物分成六个部分,分别是头发,头,上身,手,腿,脚。骨骼只采用一套,男女共用,动作也分开来,每个动作都可以驱动骨架。每个部分都需要包含蒙皮信息,但不需要骨架,因为骨架可以共用,不需要保存多份,动作也一样,也只要一份就可以了,想要实现高效的换装,骨架和动作一定要支持共享。在Ogre中,骨骼和动作被合起来放到一个skeleton文件中,为了换装,自然要把这些东西分拆掉,难道天龙八部就是这个原因?(^_^,我猜的,可能是)
分成六个部分后,就可以在需要是把对应部分替换掉实现换装,刚才说了,换装的难点在于为什么能换。能换的原因是在接缝的地方,只要没有接缝,这个部分就好像被换掉了,那怎么保证没有接缝呢,那就要使接缝处的顶点受到骨头影响的权重保持一致,只要一致了,换装就可以实现了。
再说点题外话,一般手里拿的武器属于挂接物体,不属于换装范畴,挂接相对来说更简单一点,创建一个节点绑定到某块骨头就可以了,这类物体的共性是物体只有刚性变化。还有一些换装是通过换模型的贴图来完成。总之,换装如果不考虑效率和资源共享并不难,换装的原理是何骨骼动画相关的,当然这只适用于本文提到的换装方式,如果你有其他更巧妙的换装方法,请告诉我,一起分享啊,^_^。
转载请注明出处:
作者:gogoplayer
E-mail : gogoplayer@163.com
QQ : 78939328
http://www.gogoplayer.com.cn
2008年5月9日
近日很多朋友咨询Overlay中文显示问题,回答的多了想索性再写个文档算了,放在网上共享,于是就有了本篇。
在Ogre1.2.5版本中,通过与Ogre官方论坛的开发者讨论实现了Overlay的中文显示,当初的实现非常的怪异,具体的实现可以参见Ogre官方论坛。
随着Ogre的更新,现在Ogre已经发布了1.4.7,1.4系列版本有一个重要的改进,就是加入了UTFString,这为Ogre中文显示予以很大的帮助。为了便于演示,我直接使用Ogre自带的Overlay,也就是大家熟悉的DebugOverlay,测试工程我选择Demo_ParticleFX,选择其他的也没有关系。现在编译它,运行后得到下图:

图的最左下角显示的就是英文DebugOverlay,接下来我们的任务就是把它编程中文的,^_^。
Overlay中文化操作步骤如下
1. 打开OgreSDK\media\packs\ OgreCore.zip。
2. 打开C:\WINDOWS\Fonts,把simhei.ttf添加到OgreCore.zip,(什么,没有simhei.ttf这个文件,那就还其他的中文ttf字体吧)。
3. 打开OgreCore.zip中的Ogre.fontdef,里面有BlueHighway这个字体定义块,在他的下面添加我们的SimHei,code_points里面的一大堆数字看不明白没关系,随后文章会解释。
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.overlay,把BlueHighway全部替换成SimHei,我们要使用中文字体了,嘿嘿。
5. 修改完成后,确保所做的修改已经保存到OgreCore.zip。
6. 进入Ogre解决方案,打开文件ExampleFrameListener.h,把54-59行的代码替换如下:
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"平均帧速率: ";
static DisplayString bestFps = L"最高帧速率: ";
static DisplayString worstFps = L"最低帧速率: ";
static DisplayString tris = L"三角形数量: ";
static DisplayString batches = L"批次: ";
7. 最后重新编译工程,下面是我运行的截图,是不是已经显示中文了,^_^。

现在再来看看SimHei中的code_points是如何生成的,这个可以参考我上次写的这篇文章http://www.cnblogs.com/gogoplayer/archive/2008/05/09/1189795.html,至此,实现Overlay中文显示。
转载请注明出处:
作者:gogoplayer
E-mail : gogoplayer@163.com
QQ : 78939328
http://www.gogoplayer.com.cn
今天在做Ogre中文显示时,遇到了Ogre字体code_points生成问题,下面来看一下我使用的黑体定义。
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
}
字体code_points就是你要使用的Unicode字符编码,例如‘当’这个字的编码用16进制表示是5F53,转换成10进制就是24403,在Ogre字体定义中使用的是10进制,根据Ogre字体定义文档的描述,这个‘当’字需要在文件中描述成这样24403-24403,你可以到http://www.chi2ko.com/tool/CJK.htm查看每个字符对应的编码。
为了自动生成Ogre字体code_points,网上转了一圈没有找到合适的软件,那就自己动手吧,软件界面如下:

软件中分隔线以上是单字符转换,对应上述网站的编码查找功能;分隔线以下是字符串转换,可以用他来生成Ogre字体需要的code_points格式。
点击这里下载,注:软件需要.Net2.0 Framework。
转载请注明出处:
作者:gogoplayer
E-mail : gogoplayer@163.com
QQ : 78939328
http://www.gogoplayer.com.cn/
2008年4月24日
2008年4月21日
Shawn Hargreaves Blog的blog对我很有帮助,翻译高手的文章,诚惶诚恐,一定做到认真仔细,保持原汁原味,基于外语和水平的限制,难免会有纰漏,到时还望各位指正。
2008年3月28日
Lsge ---2D引擎介绍
Lsge全称为Light Shadow Game Engine,和Ogre有点像,因为这个2D引擎使用了很多Ogre的代码,因此得名。
引擎使用VS2005构建,支持Unicode,工程附带三个演示,可以帮助开发人员迅速了解,Lsge最大的特点是使用简单,内置了很多兼而易用的功能。
Lsge是我大一、大二时的作品,设计的初衷是为了能快速写出小游戏,使开发者把注意力放在创造上,这个引擎的渲染效率不高,但是没有关系,引擎目标是小游戏,简单的演示,不是大规模游戏,在我设计期间,网络给了我所需要的大多数资料,让我有机会接触到Ogre,zlib,audiere,ois等巨人,有了他们的帮助,才有这个引擎的发展,引擎有完整的注释,文档比较完善。
很早就想开放Lsge,回报网络,只有开放才能避免固步自封,我始终认为我所了解的只是沧海一粟。在Lsge完成的很长一段时间里,我差点忘了他的存在,前几天我想起了他,认为我改写点东西,整理出来,让他不只是在我的硬盘中,这对一些开发者可能有帮助,发表出来,将让他重获新生,最后,再次感谢Ogre,zlib,audiere,ois等巨人,感谢网络,没有你们就没有Lsge。
附上一张引擎演示图(^_^):

附加说明:
选项配置:Lsge自带所需要的库,需要注意的是Lsge所需要的DX库需要特殊配置。


项目属性配置:
需要把调试->工作目录设为../Bin/$(ConfigurationName)

VS2005版本较多,主要分为sp1和非sp1版本,配置比较麻烦,经常会出现应用程序没有初始化之类的错误,这些可以通过安装正确地库一一排除。
Lsge下载:
blog不支持大文件,还要分割成五份,麻烦啊。
Lsge第一部分
Lsge第二部分
Lsge第三部分
Lsge第四部分
Lsge第五部分