2009年11月15日
这几天折腾物理引擎,下了Havok和PhysX。
前天装了Havok,把他的例子全跑了一遍,感觉不错。昨天装PhysX却遇到了问题,安装完后运行例子直接归天,打开PhysX例子代码调试后发现NxPhysicsSDK实例创建不成功。
调试语句gPhysicsSDK = NxCreatePhysicsSDK(NX_PHYSICS_SDK_VERSION, gAllocator);
发现gPhysicsSDK总是为0,修改一下代码,通过NxSDKCreateError查看错误代码,总是为NXCE_PHYSX_NOT_FOUND。
NxSDKCreateError e;
// Create the physics SDK
gPhysicsSDK = NxCreatePhysicsSDK(NX_PHYSICS_SDK_VERSION, 0, 0, NxPhysicsSDKDesc(), &e);
官网搜索了一下,总结出如下几条:
1. 如果显卡驱动不够新,则需要安装新的显卡驱动,我下了一个191.07_desktop_winxp_32bit_international_whql.exe
2. 然后要安装最新发布的SystemSoftware,我装的是PhysX_9.09.30_SystemSoftware.exe
3. 然后再安装SDK,我的版本是PhysX_SDK_2.8.3.msi
好久没写Blog,哎,还是有点偷懒了,今天就把原来做过的一个分析整理出来吧,主要关于Ogre场景树的更新过程。
Node的更新是分两个阶段的:
1:当前帧设置节点局部SRT(缩放、旋转、平移)
2:下一帧计算节点的世界SRT
也就是说Ogre不赞成你依赖他的场景树,试试运行这个例子:
SceneNode *root = mSceneMgr->getRootSceneNode();
SceneNode *p2 = root->createChildSceneNode(Vector3(1,2,3));
SceneNode *p3 = p2->createChildSceneNode(Vector3(2,3,4));
SceneNode *p4 = p3->createChildSceneNode(Vector3(4,5,6));
Vector3 v;
v = p4->_getDerivedPosition();
std::cout << v << std::endl;
p2->setPosition(0,1,0);
v = p4->_getDerivedPosition();
std::cout << v << std::endl;
理想结果应该是(6,9,10),运行结果是(7,9,13),这是因为Ogre是在下一帧计算节点的世界SRT的,如果要在本帧获取世界SRT
最保险就是手动调用根节点的_update().
深入分析:
1: 调用任意改变节点局部SRT的方法会调用Node::needUpdate
2: needUpdate会调用requestUpdate,requestUpdate是一个递归函数,而且一定是子节点调用,例如这样:
3: mParent->requestUpdate(this, forceParentUpdate);
4: requestUpdate会从当前节点沿着父节点向上递归把自身加入到父节点的更新节点集中
5: 场景管理器会在每帧开始前调用getRootSceneNode()->_update(true, false)
6: _update会深度递归场景树更新每个节点的世界SRT
这里还有一个优化,就是Ogre只更新requestUpdate收集的子节点,^_^
转载请注明出处:
作者:gogoplayer
E-mail : gogoplayer@163.com
QQ : 78939328
http://gogoplayer.cnblogs.com/
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日
摘要: 换装基本上是每个网游都必须有的一个功能,每种网游的做法都各有不同,有些是换掉整个模型,有些则是通过可以换掉模型的一个部分完成。前者属于整体换,相对简单些;后者则是通过部分替换实现,目前用的比较多,本文主要描述后者的。 在开始描述换装前,首先要具备骨骼动画的知识,如果对骨骼动画的原理不熟悉,换装是比较难以理解的。换装的核心其实并不在换上,而是要理解为什么能换,而这些都和骨骼动画密不可分。骨骼动画是通...
阅读全文
2008年5月9日
摘要: 近日很多朋友咨询Overlay中文显示问题,回答的多了想索性再写个文档算了,放在网上共享,于是就有了本篇。 在Ogre1.2.5版本中,通过与Ogre官方论坛的开发者讨论实现了Overlay的中文显示,当初的实现非常的怪异,具体的实现可以参见Ogre官方论坛。随着Ogre的更新,现在Ogre已经发布了1.4.7,1.4系列版本有一个重要的改进,就是加入了UTFString,这为Ogre中文显示予以...
阅读全文
摘要: 今天在做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 24...
阅读全文
2008年4月24日
摘要: QuickGUI是Ogre引擎下的一种用户界面,如果你使用的Ogre,而Ogre自带的Overlay无法满足你的需求时,你可以考虑QuickGUI,相对于CEGUI,他小巧,并且完全基于Ogre设计,这点比CEGUI要好,但是没有CEGUI支持那么多的控件,不过对于一般的用户,QuickGUI提供的控件还是够用的,下面是QuickGUI目前支持的控件『V0.97版本』。ButtonCheckBox...
阅读全文