越 的个人资料三无之徒照片日志列表 工具 帮助

日志


10月7日

Dream Engine 2 开发近况

这是参加 CGDC 之后写的,断断续续写了几个月,是我历史上跨时最长的日志了,最近很忙,十一长假才得空闲放上来,希望以后不要这样了。这段时间我们公司遇到了前所未有的困难,希望曾经在这里工作过的人和了解我们公司内部情况的人嘴下留情了,无论怎样,绩思思对待游戏、对待研发的态度是认真的,对待技术的态度是极其负责的,在此谢过。

 

1.                又是 3 个多月没有更新了,这段时间发生了许多事情,但DE2的开发依然有条不紊的进行着,其中渲染系统支持了 Lighting Pre-Pass(也称 Deferred Lighting),这是 ShaderX7》中提出的渲染技术。其主要思想从 Deferred Shading DS)演化而来,但同时改善了 DS存在的问题。为了更好的说明LPP,首先要了解 DS,简单来说 DS 是一种光照的后期计算,在每帧开始时先生成光照所需要的几何体的数据缓冲--屏幕空间区域的大小--我们称之为 Geometry-Buffer,也就是通常所说的 G-Buffer,接下来计算光照时,在 PS 阶段通过纹理采样的方式采样之前生成的 G-Buffer,用这个数据和光源参数计算每个屏幕象素的光照值,由于被光照的物体的光照数据已经生成在 G-Buffer 中,所以不需要重复绘制物体,只需将光源作为模型(点光源、聚光灯等)或者屏幕大小的四边形(方向光、天光等)绘制即可,在多光源的情况下,整个光照过程只渲染一次物体,极大的减少了绘制上的重复。由于G-Buffer 的数据比较多,例如 DiffuseEmissiveSpecularSp PowerNormalDepth 等,需要多张RT来保存,这就意味着需要占用很多的显存来保存这些数据,即使通过一些合并数据的压缩技巧,对显存的占用量还是不小。为了解决这个问题,《Shader X》系列的编写者 Wolfgang Engel 提出了Light Pre-Pass Renderer技术,实际上就是 Pre-Pass Lighting,主要的思想是:在G-Buffer的生成阶段,只输出光照计算的必要参数,比如 Sp PowerNormalDepth等,在光照阶段通过前一步生成的 G-Buffer和光源参数生成屏幕空间Lighting RT,最后再绘制一遍物体,在绘制物体时获取物体表面的材质的 DiffuseEmissiveSpecular Color,以及前一步生成的Lighting RT,计算最终的颜色。由于在 G-Buffer 阶段会生成 Depth Buffer,所以在渲染物体时可以利用图形硬件的 Early-z Culling 机制剔除大量的无效像素,提高一定的PS阶段的性能。相对于 DS,这个方案的优点是减少了显存的占用,缺点是多渲染一遍物体。在传统的 FS DS 之间,这是个比较折中的方案。Crytek CryEngine3中加入了这个技术 presentation,用于在 Xbox360 这样显存资源比较稀缺的平台上实现多动态光照的效果。PS3 平台上的《抵抗2》是首款使用该技术的次世代游戏,这是它的技术文档Insomniac Prelighting - Game Developers Conference 2009。在实现的过程中有几个细节需要注意:

    1. G-Buffer 的格式,DreamEngine 2为了考虑到静态光照和动态光照的完美结合,我在 G-Buffer 阶段输出了4RT,分别是:DepthAmbient Color(Alpha 标识 Emissive)Normal & SPRGB-NormalAlpha-SP)、Light Mapping。所有RT都是32位格式,其中 Ambient Color 存储材质中的 Ambient Emissive Color,通过 Alpha 标识是否自发光。Normal & SP 存储静态物体的法线和材质中的高光系数,由于只有Alpha 通道存储SP,所以SP 的范围是从0~255,不过通过和美术的交流,发现这个范围一般也够用了。
    2. 性能,主要集中在光照阶段,剔除无用的像素。通过Stencil Buffer 来除不需要光照的像素,其实本质上是为了渲染的正确性,但由于需要先设置Stencil Buffer,这样在接下来的光照阶段可以利用图形硬件的 Early-Stencil Culling 技术剔除无效的像素,提高 PS 性能。对于方向光、天光则无法利用这个优势,只能全屏渲染。
    3. 光体的设置,Point Light 我采用球体计算光照,Spot Light 用四棱台代表光照范围。对于 View Frustum和光体的相交关系,一开始我使用判断球体和四棱台边界设置光体三角形的裁剪模式,但发现无论怎样设置,在 View Frustum的近截面和光体相交都会产生不正确的光照结果,毕竟光体使用多边形来描述的,几何上的精确无法在这里发挥作用,而且还导致代码中复杂的判断机制,最后索性全部使用无背面剔除渲染光体,在主流平台上经过测试发现性能并没有明显变化,代码也简化了许多。
    4. Shadow,对于阴影来说这个技术没有丝毫的改进,目前我依然采用每光源的阴影的后期处理,DirectionSkyLight采用 Deferred ShadowPoint\Spot 采用模板缓冲+渲染光体渲染阴影。实际上 DE中方向光\天光阴影生成一直都是最原始的 SM 做法,只是动态计算了 Shadow Caster 的包围盒作为 Light Space View Project Matrix 的依据。但场景规模较大时,Shadow 效果就差了,考察了当前比较流行的做法,PSMTSMLiPSMCSM 等,发现 PSMTSMLiPSM Camera 变化时阴影抖动比较严重,CSM 比较好的解决了抖动问题,CryEngine2 以及一些商业游戏使用了这个技术,但是却需要更多地 VRAM,考虑到我们目前项目的实际情况,视角相对固定,极少看到大范围的场景,所以打算在原始 SM上作些改进,目前主要是选取潜在投影体的问题,之前的做法是只要场景中的物件产生阴影,我们就忽略 Camera Fustum Culling,但这样的问题是产生的 Shader Caster BV 较大,直接导致 Shadow 的精度降低,所以这需要使用稍大些的 Frustum 来做 Culling,这样可以大大缓解这个问题,对我们目前的项目来说也够用了。
    5. Depth Buffer,由于引擎的多个渲染阶段都是基于 Image Space的,这就需要通过 Depth Buffer 还原世界空间位置,对于 LPP DS,更需要获得像素的世界空间位置来进行光照计算。之前引擎中的 Depth 格式采用的是齐次化后的世界空间 z,但这是需要进行一次 ViewProjInv矩阵变换才能还原出世界空间位置,对于后期处理来说开销较大。所以这部分的性能问题使我一直耿耿于怀,这次趁着 LPP的实现机会彻底改变了这种现状,现在我采用存储Normalized View Space z,也就是 View Space z/ Frustum ZFar,这是一个视空间 z 的线性比值,如果已知Camera Pos和视空间的Pos,简单的向量加法即可求得世界空间位置,视空间Pos可以通过 Frustum 的远截面的四个顶点(世界空间位置)到Camera Pos 的向量求得,以下简称FarPlaneCornerDir,在渲染屏幕四边形时,将FarPlaneCornerDir存储在矩形的四个顶点中,在 VS 中传给PS,在 PS中: World z = tex2d(DepthSampler, uv).r * FarPlaneCornerDir (插值后的)+ CameraPos, 这样就只需要一次乘法和向量加法就完成变换,大大提高还原时的性能。
4月16日

Dream Engine 2 的一些事(二)

最近在忙着优化游戏,除了用到的第一代引擎本身的优化外,美术也在资源制作上开始优化,减面、合并材质、模型、减关键帧、合理使用粒子模块...这些规则即使在我看来也不容易把握好,可苦了这些很感性的美术了。同时策划也尽可能做些优化工作,比如增加场景布局结构的合理性等,游戏客户端和服务器端也在优化,总之全民皆兵。可以预见到今后 CL 游戏场景将会更复杂,所以我也在积极推进美术工具的使用,以提高他们的生产效率,比如 Light Map Editor,CL 是基于静态光照来表现游戏场景的,之前都是美术在 3dsMax 中手工去调节整个场景的顶点色,费时费力,上周美术试用引擎提供的 LME 之后,提出一些反馈意见,我们也在改进之中。
说到静态光照,不得不说 DE2 的光照方案,在开始阶段我并没有确定开发 DE2 的静态光照技术,直到上个月的 GDC09,我了解到,UE3 这次的改进主要在静态光照系统:增加了称之为 LightMass 的系统,可以实现静态的全局光照,这样可以节省大量设置场景灯光的时间,使用这套系统可以可以告别以往一个 Level 要上百个光源的历史了。CryTeck 也推出了 CE3,实际上是 CE2 的低配置版,但游戏开发本身的功能也增强了。无论怎样,从这两款引擎的变化来说,引擎开发商们更务实了,渲染技术的发展也不像以前那样激进了,甚至还有些倒退,也许是考虑到金融危机的影响,PC的硬件发展将会放缓,从这方面来说是也许个正确的选择。根据这些信息,DE2 的静态光照系统也基本定型了:支持全局和局部静态光照,支持 Normaled LightMap 和普通的 Light Map,支持 Vertex LightMap 和 Texture LightMap。实际上 DE3D 早已经实现了局部静态光照,流程也已经完善,所以在此基础上开发 DE2 的静态光照系统是相对容易的。首先我实现的是 Normaled Light map,在翻阅了 HL2 渲染技术文档之后动手试了一下,基本都实现了,值得注意的是 bump basis 是在 tangant space 定义的,生成 lightmap 时要在 tangant space 生成光照信息,另外还要注意和引擎定义坐标系、Matrix 的行列优先级、旋转手相性要一致。Diffuse 光照好了,接下来是 Specular,HL2 中的静态 Specular 是通过 Envorment Map 来实现的,这种方法比较麻烦,而且需要预生成额外的纹理,我仔细研究了一下 UE3 的静态 Specular 的方式,通过简单的 Normal 和 bump basis 的 Dot 计算出 Specular 的系数,即可得到效果不错的静态 Specular,其实本质上也不算是静态,因为计算是实时的。接下来是全局光照,我希望用 Radiosity 来实现,参考了一篇 GPU 实现 Radiosity 静态光照的文章,完成了基本框架,已经交给同事去细化。
最近 Aion 挺火,我玩了一下,对其中可以生成 Alpha 物体的静态阴影很感兴趣,遂想如何实现,首先想到用 GPU 生成 LightMap 阴影,因为可以通过 texkill(clip) 过滤掉指定 alpha 值的像素。也曾想过用现在的 CPU 方式,但问题是第一是借用物理引擎的射线检测实现的阴影,当场景复杂光源较多时生成时间很长,第二就是很难生成 Alpha 物体的阴影(比如树叶),即使做到,实现上比较复杂,而且不灵活(比如无法获取美术在 Material Editor 中设定的自定义 Alpha Ref Value)。前段时间在 MSN 上和一位朋友 http://ixnehc.spaces.live.com/ 聊起这个话题,他与我分享了他的实现方案,给我很大启发:这是一种类似于 Deferred Shader 的方式,将需要光照计算的 LightMap Sampler 记录在浮点纹理上,通过 DS 方式计算光照,得到最后的 LightMap 数据,Shaow 采用和 Shadow Map 类似的方式生成,但存在精度问题,但仔细想会发现,对于方向光源,可以采用类似于分段 ShadowMap 的方式提高精度,对于点光源或者聚光灯,一般范围不会非常大,在大多数情况下也能满足要求。周一和同事们讨论了这个技术方案,认为可行,并制定了详细的技术实现细节,目前正在开发中。这样 DE2 将实现以 GPU 为主导、CPU 辅助的静态光照生成技术,将进一步提高静态光照的生成效率。另外,我们还讨论了 UE3 LightMass 系统的一些静态光照的技术细节,例如 Emmisive Lighting 等,这些也将在 DE2 中实现。
资源管理,整个三月份主要是在做这部分功能,由于之前的设计方案考虑到资源查找的优化,引入了资源ID 的概念,但这个优化增加许多复杂性,从结构到实现,从底层到高层,同时还牵扯出多人编辑资源的 ID 同步问题,以及 AutoPatch 时的 ID 同步问题,为了解决这些问题,我们还需要开发辅助的模块和工具来完成整个功能,甚至差点搞出个 C/S 结构的分布式编辑器模式。但即使这样,我们还是把整体设计方案确定了下来,包括所有的技术细节。某天晚上加班,引擎的同事问我:我们的资源管理是不是太复杂了?这让我陷入了沉思:我的初衷是什么?为什么现在会这样复杂?这样带来的性能提升和开发复杂度以及后期维护的成本是否平衡?经过一个周末的思考之后,痛定思痛,我决定去掉这个包袱,虽然性能没有提升,但是系统变得简单明了(Kiss 原则 )。周一我和同事们又仔细分析,最后决定丢弃之前的设计,轻装上阵。只是没想到这个决定得到了大家的一致认可,不约而同的都松了一口气。
逻辑编辑器,前几天我们讨论 DE2 的对象管理,讨论到最后就讨论到逻辑编辑器上来,由于之前对于网游逻辑的 Deploy 的问题一直悬而未决,使得我一直都在怀疑 Logic Editor 的实用性,因为这将改变既有的游戏开发模式,这种改变需要一段时间来适应。会议开完我的思绪仍然停不下来,抓住公司的 Server Engine 的主程序继续讨论,从开发模式的改变到实现的技术细节,我们讨论的比较深入,甚至可以肯定第一个用这种方式开发的游戏一定比传统的方式要慢,但从长远来看,是改进了游戏开发模式,提高生产率。所以,基本上确定了技术方案以及新的游戏逻辑开发模式,尽管还是有几个不确定因素。
上周把 DE2 的开发计划提交给了 CEO,这是一个粗略的计划,除了 DE2 的开发,还包含对 DE3D 的维护以及 Support 游戏项目的工作。DE2 的开发是我主动提出来的,作为公司的 CTO,我始终认为技术是游戏研发公司的核心竞争力,没有这个基础,在开发上很难走的更远。网游也在不断发展,玩家的需求会越来越多,游戏的功能也越复杂,对技术的要求也越高,虽然一款游戏的成功是多方面决定的,但是至少在技术层面,如果可以做好足够的基础保证,甚至走在业界的前端,将是保证公司持续竞争力的重要因素。
虽然,这条路难走,障碍很多,但我依然会继续前行。
2月24日

Dream Engine 2 的一些事(一)

从今年初 DE2 开始开发到现在已二月有余,进度还算顺利,这次除了次世代的 3D 引擎之外,我们还开始了对象引擎和逻辑引擎的开发,同时对引擎的资源管理的设计也做了进一步的改进。实际上所谓的对象引擎就是基于 RTTI 的可扩展的对象系统,游戏可以根据引擎的 RTTI 规则来扩展自己的逻辑对象,无需重新编译引擎即可在引擎编辑器和游戏中使用游戏对象,同时这套机制也作为引擎核心对象的管理和反射机制。逻辑引擎实际上是一个可扩展的游戏逻辑框架结构,它基于对象引擎,在其内部存在一个逻辑驱动核心,这个核心主要的工作就是不停的更新当前的逻辑序列--更新每个逻辑序列的状态,每个逻辑序列由一系列的逻辑片段构成,逻辑片断通过某种机制关联在一起,形成完整的游戏逻辑流程,在每个逻辑片段内部都可以实现特定的逻辑,通过结合对象引擎的 RTTI 机制可以对游戏对象进行控制,这样就可以实现大部分的游戏对象间的交互。与此同时,通过提供可视化的逻辑编辑工具代替传统的手工编写游戏脚本逻辑,可以提高游戏开发效率,缩短迭代周期。实际上这种机制并不是什么革命性的技术,从本质上来说和传统的 脚本 + CPP = 游戏逻辑 的方式没有什么区别,只不过是提供了一个可扩展的框架结构,将所有用 DE2 开发的游戏的逻辑纳入其中来驱动。但是,目前还有些问题没有完全解决,比如:对于 Online Game 的 Server 和 Client 逻辑如何编辑和部署? 众所周知,网络游戏的逻辑分散在 S/C 两端,而且还不可避免的存在时序和验证问题,如果是单机游戏这种方式可以工作得很好,但是网络游戏的情况就比较复杂。而且这里还有两个细节的问题,一个是策划如何通过逻辑编辑器决定哪部分逻辑是运行在客户端,哪部分逻辑是服务器端?第二就是性能问题,RTTI 也好,逻辑引擎也好,天下没有免费的午餐,要做到可扩展,就要付出性能的代价,高度的抽象和游戏对象的运行时动态查找以及编辑势必带来性能开销,所以,在性能和扩展性上找到平衡点也是摆在我面前的一个难题。

3D引擎方面,DE2 重新设计的渲染架构在开发的过程中感觉到比 DE1 要好许多,因为它完全不需要关心渲染核心以外的数据结构,同时 SG 也实现了在只知道很少量的渲染信息的情i况下向渲染核心发送渲染请求的功能,这样 SG 和 Render Pipeline 就被隔离开来,实际上在系统中存在一个中间层,我们称之为 Render Primitive Manager, 它负责在 SG 和 Render Pipeline 之间沟通,也就是说这个 manager 同时知道两者,但它不需要太多细节就可完成渲染数据的构建工作。为了更好的适应今后的多核心平台,多线程渲染架构也是我一开始就考虑的功能。由于在开发之前做了充足的准备,以及在去年实现的多线程资源管理的基础之上,再加上新的 Render Pipeline 和 SG 的完全分离,我大概用了 2 天左右的时间就完成了开发及测试工作。为了做到和逻辑线程完全并行,渲染线程完全是 lock-free 模式,对渲染线程数据结构的操作完全是异步方式,同时提供方便的回调机制满足特定的渲染以及渲染结果查询需求。

材质系统是变化最大的系统之一,第一代引擎的材质系统完全被废弃,为了更好的扩展性,我们重新设计了 Shader 系统,这样做的前提是整个引擎的着色机制完全基于可编程管线,只保留 D3D 10 还保留的固定管线设置,在固定管线的数据结构实现上参考了 D3D10 的架构,这样将来在迁移到 D3D10 或者后续版本会更加容易。同时我们还设计了一套 Shader 编译机制,实现了在不同着色环境下的动态 Shader 生成,而且做到了和顶点生成结构无关,在这方面我们参考了 UE3 的材质系统和材质模板。目前已经开发完成了可视化的材质编辑器,基本上做到了 UE3 能实现的材质效果我们都可以实现。

8月31日

我对软件架构设计的认识(一)

最近一直致力于引擎的架构设计,到目前为止一个多月没有写一行代码。半个多月前我的计划是这样的:留出足够的设计时间,例如3个月,然后开始集中开发。在这种思想指导下,整个引擎的开发计划也是如此定制的。现在几乎每天都在更改前一天的设计,每天的工作都在构思-〉推翻-〉重构-〉思考-〉验证的过程中,不停的否定-〉肯定,自己跟自己打架,很希望能有人跟我一起讨论。虽然很寂寞,不过也自得其乐。一个多月的设计让我感觉有必要重新审视自己的思路,所以今天把自己对软件架构设计的认识写下来,也许以后回顾的话能得到一些参考罢。
什么是好的设计?怎么样才能做好一个设计?这是我这几年一直疑惑的问题。首先要明确一点,设计的目的是什么?我的回答是:设计是为了编码提供开发依据,为了在将来的的软件的维护过程中提供依据,为了让整个开发团队能充分理解软件的体系结构提供依据。其实设计还可以有很多目的,例如为了骗取项目投资人的信任,为了满足 Boss 的对你的满意程度,为了能在年终考核里加入更有分量的砝码等等。总之,设计的目的分为两大部分,1.真正满足整个软件生命周期的需要,2.满足除了软件需要以外的需求。很可惜,自从搞软件开发以来,我写的设计大部分属于后一种,反而是我自己的项目里的设计倒是第一种,但是很少。不过我想这是普遍的现象,造成这种情况的原因有几个:1.开发周期短,这势必会导致开发人员在设计上花的时间不够,时间不够意味着不可能出现好的设计,这是绝大多数的情况。2.Boss 认识不足:这是要命的,如果你的 Boss 认识不到设计的重要性,甚至不懂软件开发,劝你赶快跳槽吧。3.自身认识不够:很多程序员知道要做设计,但不知道如何下手,不过这种情况现在好了许多,毕竟这类的书籍到处都是,UML\敏捷\XP\AOP\OOP....等等众多的开发方法让人感到头晕目眩。但仍然有很多人不知道该怎样设计,做好设计,这个话题下面会展开论述。讲了这么一大堆,到底应该怎样才能做好设计呢?
讨论任何问题,都要有一个假定的前提,所以在这里我也要假定一下:假设你的 Boss 认识设计的重要性,假设你有一定的设计时间,假设你有设计的经验。当接收到一个项目,首先重要的一点是要分析,当然系统分析不在这个讨论的范畴,不过我认为在现实开发过程中设计其实是包含一部分系统分析的工作的。如果按照传统的瀑布型软件开发模式,软件的生命周期是这样的:分析-〉设计〉编码-〉测试-〉维护,随着软件系统的日益复杂,这种模式暴露了很多问题:1.这种顺序化的开发过程是在一个对整个软件系统有充分认识和了解的前提下的,而要对一个复杂的软件系统有充分的了解(包括实现细节),这个本身就是一个完整的实现过程,从这点来说,是矛盾的。2.在复杂软件结构下,如果有能做到这种程度项目开发团队,那一定是有着丰富的开发经验,并经历过无数次的失败的项目,而现实的情况往往是经验较少的项目团队来开发复杂软件系统,而经验丰富的开发团队也是经过失败才从新手达到老鸟的程度的。从这一点来说,也是矛盾的。
话说回分析,分析由谁来做?这是一个大问题,书本上讲分析由系统分析员来做。系统分析员具有什么能力?系统分析员应该是有着丰富经验的开发老手。但现实的情况是分析人员是通过国家系统分析员考试的刚毕业的大学生,是市场人员,是你的 Boss,甚至是项目的需求方人员,他们不具有丰富的开发经验,甚至都不懂软件开发,在这种情况下出来的所谓需求分析报告可想而知了,除了对要做的软件的外观有一个描述之外,对开发没有任何帮助。如果你的项目很不幸出于这种情况下(基本上国内都这样,所以我们都很不幸),而且恰好你是一个很负责任的开发者,那么你的设计中就要包含本应该在需求分析阶段完成的系统分析的工作了。但是,这样你就会非常被动,原因2点:1.项目规定的设计时间对你来说很可能会不够用,因为你要完成额外的分析工作。2.Boss 或者 项目需求方对你的压力,因为他不知道分析的工作没有完成,他们理所应当的认为你现在的工作只是设计。好吧,也许你说“太糟了!能不能完美一些?”,那么好,假设系统分析人员给你一份足够专业的需求分析文档,那么还依然存在问题:1.分析是否能覆盖到所有的需求?2.对于隐含的需求是否分析清楚(这恰恰是最重要的,现实的情况是往往漏掉的一个隐含的需求会导致整个结构的变化)?3.是否做好用户需求到开发需求之间的映射(这是体现功力的地方了)?所以,未来的路还很长。
 话再说回设计,正是因为以上的种种原因,我已经有很长时间习惯了没有需求分析的设计了,我的设计中往往包含了分析的工作,虽然累了些,反而觉得这未尝不是一件好事,至少保持了分析-〉设计的连续性,而且也可以做到分析和设计之间的互动:通过设计来反映分析的缺陷-〉重新分析-〉修改设计,其实这就是一个迭代的过程,同样,在开发中任何两个阶段中都可以这样来互动。实际上迭代式开发模式就是在瀑布是开发基础上加了个循环,可以评估每个阶段的输出结果,并修改上一个阶段的输出结果。其实最早迭代模式的出现,是为了解决在开发过程中需求不断变化的问题,传统的瀑布式开发是假定需求是不会变化的。软件的复杂性会导致不可能有某一个人掌握或者在短时间内的就能分析透彻的,更何况还不断的修改需求。而在这里,我倒是觉得经验少的团队很适合迭代开发,或者说很适合一个你不太熟悉的复杂系统。
4月25日

DreamEngine开发手札1

1.Material LyShader 对象的关系。LyMatetial LyGeometry 对应。每个 Geometry 拥有 shader 的实例(应该称之为 LyShaderRef).Geometry 按材质划分,Mesh 是否将所拥有的 Geometry 存储为一个 VB?

2.每祯在更新时使用裁减算法将所有在视锥内部的 LyNode 对象筛选出来,当绘制时将 附着在可见LyNode 上的所有 LyEntity 的所有 LySubEntity(可渲染对象)处理,首先按照是否半透明进行分类,分为两个可渲染对象列表,然后将非透明列表进行Shader 和从近到远排序,透明列表进行从远到近排序,最终将处理之后的两个渲染对象列表送入 LyRender::DrawScene 中,由于每个可渲染对象都拥有一个 Shader 句柄,所以在渲染时可以查询到指定的 Shader,然后开始渲染。