经过一段时间的编码,终于完成了一个效果还算可以的Demo
发一些图片出来,如果对此感兴趣我们可以一起多多交流。(所有图片均缩小显示,可右键另存为看原图)
其中主要用了基于物理的渲染,物理摄像机,以及屏幕空间sss(其中皮肤半透部分因需求没有做,不是完整的sss,以后有机会完善之)
来张整体效果图

右边是两盏SpotLight对应的ShadowMap
下面是一些参数调整后的效果对比图



先看一下效果图。

最终效果

显示顶点的TBN(T、B、N分别用R、G、B颜色轴显示)

所使用的法线贴图
具体办法如下:
}
下面两张图是比较有用的 一张是uv图,一张是法线图。

uv图可用于直观的检查导出的顶点TBN是否正确。检查方法为,看顶点的T轴和B轴是否分别和UV图中U和V的箭头方向一致。如果一致则正确。
法线图可以检查UV镜像和UV旋转的问题。检查方法为,打上灯光看光照效果是否正确,即有无明显接缝。
下面是一些模型的测试截图

有UV镜像的一个面片 最终效果

模型顶点、面以及uv分布情况

顶点TBN(注意T轴和B轴分别与U和V的方向一至)
所使用的法线贴图

3DMax中的茶壶 最终效果

茶壶顶点TBN

茶壶盖顶部特写,有uv旋转
转自 http://clkrst.itpub.net/post/137/41162
linux下跑得一直很好的程序,到了windows下面就跑不起来了。内存异常,检查了一下,很快发现是因为在主程序中释放了一块在DLL中分配的内存,这种问题虽然早就知道了,但是一直没有仔细考虑过,所以今天就深入研究了一下。
在linux下,每个进程只有一个heap,在任何一个动态库模块so中通过new或者malloc来分配内存的时候都是从这个唯一的heap中分配的,那么自然你在其它随便什么地方都可以释放。这个模型是简单的。
但是在windows下面,问题变得复杂了。
1、windows允许一个进程中有多个heap,那么这样就需要指明一块内存要在哪个heap上分配,win32的HeapAlloc函数就是这样设计的,给出一个heap的句柄,给出一个size,然后返回一个指针。每个进程都至少有一个主heap,可以通过GetProcessHeap来获得,其它的堆,可以通过GetProcessHeaps取到。同样,内存释放的时候通过HeapFree来完成,还是需要指定一个堆。
2、这样的设计显然是比较灵活的,但是问题在于这样的话,每次分配内存的时候就必须要显式的指定一个heap,对于crt中的new/malloc,显然需要特殊处理。那么如何处理就取决于crt的实现了。vc的crt是创建了一个单独的heap,叫做__crtheap,它对于用户是看不见的,但是在new/malloc的实现中,都是用HeapAlloc在这个__crtheap上分配的,也就是说malloc(size)基本上可以认为等同于HeapAlloc(__crtheap, size)(当然实际上crt内部还要维护一些内存管理的数据结构,所以并不是每次malloc都必然会触发HeapAlloc),这样new/malloc就和windows的heap机制吻合了。(这里说的是vc的crt实现,我不知道其它crt实现是否如此)
3、如果一个进程需要动态库支持,系统在加载dll的时候,在dll的启动代码_DllMainCRTStartup中,会创建这个__crtheap,所以理论上有多少个dll,就有多少个__crtheap。最后主进程的mainCRTStartup 中还会创建一个为主进程服务的__crtheap。(由于顺序总是先加载dll,然后才启动main进程,所以你可以看到各个dll的__crtheap地址比较小,而主进程的__crtheap比较大,当然排在最前面的堆是每个进程的主heap。)
4、从上面的分析中可以看出,对于crt来说,由于每个dll都有自己的heap,所以每个dll通过new/malloc分配的内存都是在自己dll内部的那个heap上用HeapAlloc来分配的,而如果你想在其它模块中释放,那么在释放的时候HeapFree就会失败了,因为各个模块的__crtheap是不一样的。
这样,基本上事情就比较清楚了,在windows下一个进程存在着多个heap,除了一个主heap外,还有很多的__crtheap,用来处理通过c/c++的运行库进行的内存操作。所以使用new/malloc来分配的内存实际上都是局部的,可以在多个dll中共享,但是却必须是谁申请谁释放。这个是windows下的一个规则。以前知道这个规则,但是不知道为什么,现在算是比较明白了。(当然如果在dll内部使用HeapAlloc(GetProcessHeap(), size)来分配的内存是可以在dll以外释放的,因为这时内存分配在全局的主heap上,而不是分配在dll自己的__crtheap上)
http://blog.sina.com.cn/s/blog_4c20b377010007yr.html
本文的目的是: 简单说明如何把zlib加入到MFC程序中,提供内存压缩功能.
1. 如何获得zlib
zlib的主页是:http://www.zlib.net/
2. 用VC++6.0打开
把下载的源代码解压打开,VC6.0的工程已经建好了,在\projects\visualc6. 双击zlib.dsw, 可以在VC++6.0中看到里面有3个工程: zlib 是库文件(编译设置选中 win32 lib debug / release), 工程example 是如何使用 zlib.lib 的示例, 工程minigzip 是如何用 zlib 提供的函数读写.gz文件的示例(*.gz的文件一般Linux下比较常用).
3. 如何加入到我的工程
编译好 zlib.lib 后, 你就得到了调用一个静态库所需要的所有文件了(zlib.lib, zlib.h, zconf.h). 如何调用静态库不用我说了吧.
4. 用zlib能干什么
先来看看 zlib 都提供了那些函数, 都在zlib.h中,看到一堆宏不要晕,其实都是为了兼容各种编译器和一些类型定义.死死抓住那些主要的函数的原型声明就不会受到这些东西的影响了.
关键的函数有那么几个:
(1)int compress (Bytef *dest,
把源缓冲压缩成目的缓冲, 就那么简单, 一个函数搞定
(2) int compress2 (Bytef *dest,
功能和上一个函数一样,都一个参数可以指定压缩质量和压缩数度之间的关系(0-9)不敢肯定这个参数的话不用太在意它,明白一个道理就好了: 要想得到高的压缩比就要多花时间
(3) uLong compressBound (uLong sourceLen);
计算需要的缓冲区长度. 假设你在压缩之前就想知道你的产度为 sourcelen 的数据压缩后有多大, 可调用这个函数计算一下,这个函数并不能得到精确的结果,但是它可以保证实际输出长度肯定小于它计算出来的长度
(4) int uncompress (Bytef *dest,
解压缩(看名字就知道了:)
(5) deflateInit() + deflate() + deflateEnd()
3个函数结合使用完成压缩功能,具体用法看 example.c 的 test_deflate()函数. 其实 compress() 函数内部就是用这3个函数实现的(工程 zlib 的 compress.c 文件)
(6) inflateInit() + inflate() + inflateEnd()
和(5)类似,完成解压缩功能.
(7) gz开头的函数. 用来操作*.gz的文件,和文件stdio调用方式类似. 想知道怎么用的话看example.c 的 test_gzio() 函数,很easy.
(8) 其他诸如获得版本等函数就不说了.
总结: 其实只要有了compress() 和uncompress() 两个函数,在大多数应用中就足够了.
题外话: 我最初看到zlib的源代码时被好多宏吓倒了,呵呵,后来仔细看下去才发现原来接口那么简单. 至于那些英文说明也没想象中的那么难懂.只要有尝试的勇气,总能有些收获.
项目大事记
2007.3 项目基本上算是进入真正的开发阶段
2007.6 新的投资人老陈,老谭加入
2007.12.31 第一次面向玩家测试,程序频繁崩溃
2008.1-2009.3 研发由短平快迅速上市的方针改为做高品质的产品
2009.3-2009.6 和山东某公司合作运营,效果不理想
2009.3 拟被某上市公司收购,未成功
2009.4.18 第一次面向玩家的大规模测试,效果比较理想但人数上不去,内存占用太高
2009.7 被另一公司收购
2009.12 换游戏名称第一次面向玩家的大规模测试,客户端报错平凡,服务器宕机,人数上不去
2010.3 再次正式上线运营,效果不理想
我的情况
我于2008年5月加入公司,进入公司主要负责世界编辑器、粒子编辑器、物理系统的编写。我并没有直接参与到游戏客户端代码的编写。大多数工作都是在负责游戏相关工具的开发以及技术难点的公关工作。
我的责任:
一、没有过多的参与到游戏本身代码的编写和监管之中。
二、没有顶住来自市场或投资商的不切实际的需求变更。
程序问题
一、程序底层没有细致规划,各模块耦合太大。
由于游戏最初的定位非常简单,只是简单将美术资源替换和修改,以及非常简单的程序功能的改动(源有代码为2005年左右的基于DX8的代码),然后马上上线运营。不过经过市场检验发现游戏程序根本无法达到基本要求。于是开始大刀阔斧的改程序加功能。当然这些改动还是有一定的成效的,不过对于一款商业游戏软件来说还是有很大的距离。当我加入项目组时,整个游戏连世界编辑都没有,美术在设计场景时还要两个人一起,一个用人在游戏里面跑,一个用笔记录对应的坐标点。。。。。。无语。最初游戏中连最基本的场景管理等都没有。而且各个模块之间基本上没有界限,该分开的地方不分开(一个与脚本相关的CPP文件居然有24000多行。。。。。。),该抽象的地方不抽象。可以这样说大部分人都还停留在以面向过程的程序设计思想在写程序(虽然项目后期有了很大改观,可是参与者基本上都已经无能为力了)。由于各模块的偶合太大,也就造成了以后对任何功能的修改和添加都可能引起一连串的BUG。
二、第三方库的选择和使用都有很大问题。
每个第三方库都有自己的特点和使用技巧,但是在我们的项目中对第三方库的使用很有问题。比如CEGUI,到项目结束我们团队里面的人还没有一个人敢拍胸脯说“我能把CEGUI用好!”。还有lua脚本的使用也是如此。
三、需求不断变化已有功能不断返工。
这个不太好说,如果有一个项目统管者我想应该好很多吧。
四、程序员水平参差不齐,编程经验及其缺乏。
这个没有什么好说的。公司的待遇只有这么多,招聘的人员水平也只有这个样子(大部分是没有从业经验或才从学校毕业的)。唉只有叹息。。。。。。不过团队中的人能在这样的框架下写成这样效果我已经感到欣慰了。至少他们通过这一次锻炼提高了非常多。
五、具体功能的实现缺乏细致的设计规划以及充足的时间来具体实施。
具体功能的实现缺乏规划这个是一个比较普遍的情况。当然这有如下几个主要原因a、需求提的太匆忙,给程序留出的时间太少。这一点我的体会非常深,从我进入该项目组以来整个项目组的人都是在不断的赶新功能,缺乏一些大的规划和设计。这些功能基本上都是想到一个感觉不错就立马要加,而且都是说,没有这个就不能上线。等这个功能完成了又会要求做另外一个类似情况的功能,如此循环程序就不断重构原有代码和添加新代码。b、具体实现人员由于缺乏经验或太过于繁忙没有去设计和规划,只是一味的为完成任务而完成任务。这也是造成一旦需求有所变化或调整,那么代码改动会非常大。c、游戏功能的添加与取消太过随意。这个问题我的影响特别深,游戏原来有基于二维格子的自动寻路功能,没有精细碰撞,没有跳跃等,一段时间后提出要求说需要精细碰撞和跳跃(由于时间等原因去掉自动寻路),好既然决定那么程序就开始动吧,程序写碰撞算法,美术准备碰撞模型等。过来一段时间这些基本ok后,另一个运营商要求自动寻路必须加,好又再次把自动寻路(本来想直接加3D自动寻路的结果又一次被否决了,还是使用2D寻路,实际效果并不好勉强可用。。。。。)加上去。
六、项目前期及中期基本上没有必要的测试。
我们的项目可以说能走到今天简直是奇迹。在项目的前期和中期基本上没有专门的测试。我所了解到的测试基本都是程序员自己实现了功能后自己简单的测试(有的可能根本就没有测试过),最多在加上相关的策划人员配合测试。经过几次阶段性开发后,公司内部才开始组织专门的测试部门进行相对系统的测试。在短短的有限的一两个月中测试出的BUG个数就高达2800多个,当然这里面还不包括那些我们已知但由于底层等原因无法彻底解决的好几个重大Bug(比如客户端的CEGUI的使用,服务器端的数据库访问机制和效率问题等)。

