首 页 | 新 闻 | 技术中心 | 第二书店 | 《程序员》 | 《开发高手》 | 社 区 | 黄 页 | 人 才
移 动专 题SUNIBM微 软微 创精 华Donews人 邮
我的技术中心 
我的分类 我的文档
全部文章 发表文章
专栏管理 使用说明



 RSS 订阅 
最新文档列表
Windows/.NET
.NET  (rss)    
Visual C++  (rss)    
Delphi  (rss)    
Visual Basic  (rss)    
ASP  (rss)    
JavaScript  (rss)    
Java/Linux
Java  (rss)    
Perl  (rss)    
综合
其他开发语言  (rss)    
文件格式  (rss)    
企业开发
游戏开发  (rss)    
网站制作技术  (rss)    
数据库
数据库开发  (rss)    
软件工程
其他  (rss)    

积极原创作者 
iiprogram (68)
qdzx2008 (50)
goodboy1881 (14)
wangchinaking (58)
fancyhf (1)
harrymeng (41)
yjz0065 (113)
coofucoo (105)
Drate (69)
lphpc (30)
CSDN - 文档中心 - 其他 阅读:4194   评论: 2    参与评论
标题   Looking 2002-12-2     选择自 stormful 的 Blog
关键字   Looking 2002-12-2
出处  

2002-12-2

    好像很久没有写Looking日记了,好像有一个月了吧。过去的一个月里发生的事情,我想我一生都不会忘记。我的一个朋友惹上的刑事官司,在他的父母再三恳求之下,我居然成了他的代理人。从那天开始,我的生活就进入了社会的令一个层面。我以前是一个不太接触社会的人,我周围的人基本上都是大学毕业,有1/4的人是硕士及以上学历,我一直认为这个社会是文明、礼貌的社会。但,当我来到公检法这个圈子里后,我发现我好像到了火星。当我第一次来到看守所为朋友办手续时,接待我的是个老公安。几句话后,他发现我没有一点经验后,劈头盖脸的就是一句:你是不是不接触社会,是不是念书念傻了。当时,我一脸窘迫,点点头说,确实是念傻了。后来,可能是觉得我确实傻的可爱,老公安开始为我指点了一些门道。就这样,我踏上了一段奇怪的旅程。后来,我每天往返于刑警队和检查院才知道,那天的遭遇,真是小菜一碟。不过,司法部门的工作人员的态度虽然差了一些,但还是比较公正的,检察院驳回了对我的朋友的指控。在这20多天里,我还是有很多收获的,至少我的法律意识提高了很多,我感觉以前的我简直就是个法盲。
   
    还是继续说我的Looking吧。自从进而了D3D的世界,我就处于发现和回忆状态。以前虽然写过一些OPENGL的代码,但那毕竟是两年前的事情了,重新建立我的3D编程概念是当前最重要的。总的感觉D3D在功能和概念上与OPENGL是非常类似的,在程序界面和调试功能上好像要比OPENGL优胜一些。由于我看D3D的文档比较吃力,于是我在网上寻找了很多资料,不过有价值的很少,特别是中文资料。有一个很有趣的现象,国内的技术网站的内容重复率真是惊人的高。我在Google上查找资料的时候,最害怕的就是某个生僻的单词被某篇中文资料收集,如果发生这种情况,就不用干别的了,先翻20页再说。绕了一大圈,最后还是苦读M$文档,说来也怪,这次看M$文档就亲切多了,可能是没有后路了的原因吧。
   
    到目前为止,我对3D世界里的空间管理的兴趣要比渲染的兴趣大的多。因为,我认为空间管理才是3D引擎的核心和关键任务。在3D引擎里,像ZBuffer这样普通、通用的技术都是多余的,越高级的功能速度越慢。因此,我没有太多地把注意力放在光照和材质这类事情上,我只是在,Camera的位置上放了一个同向的Spotlight,它的范围和camera space相当,对于我来说,只要能看清楚就行。我首选要解决的问题是camera位置的调整和world space的旋转,我必须能够看到3d space里的各个细节才能进行其它工作。在写Looking编辑器的时候,我的首选参照对象是3DMax,因此每增加什么功能我都会去先研究研究3DMax。在研究3DMax的world space旋转功能时,我发现了我以前在概念上的一个误区。以前在写Looking的时候,显示实际效果的view是D3D view(使用D3D技术渲染的view,呵呵纯粹的俚语),而3个投影view是普通的windows窗体。但3DMax显然不是这样处理的,他的4个view都是得D3D view。这样作有很多显而易见的好处,其中最重要的是在编程界面上所有的view得到了统一。这个技术的关键在于效果view使用的是普通的projection,而投影view使用的是orthogonal projection,也就是投影view的view平截头是方的。这样在效果view里的操作和投影view里的操作就几乎是一样的了,唯一的不同可能只是FVF的差异和投影view里不需要光照并且投影view只需要画出framework。这也意味着,我以前作的很多工作完蛋了。
   
    我一直认为程序员在编写程序的时候,最应该花费精力的是概念和结构,至少不是编写代码。这就像开车一样,如果你的注意力始终在挂档上,那你肯定是个新手。教我开车的师傅说过的一句话令我印象非常深刻:如果你在时速80脉的情况下,还能知道路边的女孩是否漂亮,你就是个成手的。由于我的工作关系,我经常要培训一些新兵。我经常会问他们,在编程的时候你在想什么?这不是在责问他们,而是我确实想知道他们在想什么。在我看来,编程时在想什么是评价一个程序员技术能力的最基本的指标之一。在编写一个项目的过程,有时就是寻找某个概念的过程。当项目完成后,这个概念也就丰满了,项目顺应这概念的曲线而存在。一个项目是什么样子的?这就是项目的样子。如果,一个项目完成了,还总结不出点东西,那这个项目肯定是失败的项目。
   
    解决了view的问题后,我需要作一些辅助性的基础工作。由于我是D3D的新手,我必须要编写大量的试验性代码。但是,D3D的FVF令我非常厌烦。每个函数都要增加新的struct,尽管我把这些struct定义在函数体内,并使用相同的名字,但还是太多了。这时我需要一个通用的D3D渲染函数,我不需要追求速度,只要通用就行。要实现它,我必须提出一个数据源的概念,我把它称为Solid。有了Solid,就需要一个Face的概念;有了Face,就需要一个Polygon的概念;有的Polygon,就需要一个Vertex Pool的概念。这时候,我想我已经越界了。这些概念都是3D Engine的概念,而不是编辑器的概念。如果任由其发展下去,以后会产生概念冲突。于是我建立了一个新的DLL工程,叫LJet。它将是Looking编辑器、Looking编译器和Looking解释器的通用API。写到这里,我开始越来越喜欢D3D了。D3D不禁提供了丰富的渲染功能,而且它还提供的丰富的3D数学支持。像Vector、Matrix、Plane、Quaternion支持函数,应有尽有。如果使用OPENGL这些恐怕都要自己写,虽然难度不大,但代码量却不少,而且还需要大量的调试。现在就简单多了,我可以直接进入主题。当然也不能原样照抄,否则离开了D3D,LJet就玩不转了。在LJet中我使用J作为系统前缀,这样在所有的D3D结构里我把D3D或D3DX字样替换成J后typedef一下,在相关的函数里,我把D3D或D3DX字样替换成j后#define一下,以后要摆脱D3D恐怕就得#if #else #endif了。
   
    现在,我要重新考虑component的问题了。既然定义了通用的data source格式,组件的接口需要进行一些调整。由于组件本身其实就是一个Solid,因此它只需导出Solid实例就可以了。好在我现在只有一个component,一个cube component。我认为,在研究3d engine时,一个cube就足够了。它足可以组成相当复杂的场景。但是,可能是因为好玩,我一口气又作了球体、圆柱、台锥、圆环、棱锥、管子和大茶壶等7个component。只要是3DMax的标准组件,我都完成了一个,就像下面那个图片所显示的,很有趣吧。当然这些组件还有不少瑕疵,不过以后在修改吧。

    越研究3DMax越觉得它是个好东西。该软件在交互上的创意真是神来之笔,我真想把这些都搬到我的系统里。这时候,Looking编辑器又来到了一个十字路口。Looking编辑器现在越来越具备了商业级3D图形编辑软件的特征。这时候,我很矛盾,是在编辑系统里继续发展下去,还是向3d engine转移注意力?最后,我选择了后者,毕竟这是我长久以来的一个愿望。但是压制继续发展Looking编辑器的强烈愿望,令我很遗憾,我想我以后会再去发展它的。
   
    再次开发Looking已经有一个半月了。在此期间,我一直在躲避某个东西,我深深的对它有敬畏感。其实它就是LJet。再此之前,我小范围的发展了一下LJet,没有敢轻举妄动。原因有两个,一方面Looking编辑器还没有足够的功能来支持LJet的调试;另一方面,我感觉我的状态还不够好,不足以完成它。在长年累月的编程工作中,状态的起伏是不可避免的。在状态不会的时候去编写核心代码,会产生大量的废码,甚至导致在思维上的崩溃从而放弃项目。但是,我们又不能不工作,因此如何调整状态在程序生涯中是非常重要的。如何在状态不好的时候完成机械性、辅助性工作,如何在状态好的时候完成核心工作?我给这种调整起了个名字,我套用一个围棋术语“腾挪”,或者叫“洗衣机”,呵呵下围棋的朋友一定知道什么意思。但遗憾的是国内公司的管理实在是成问题,至少我呆过的公司都这样。当我腾挪的时候,往往会同领导产生分歧。解释一般是徒劳的。领导一般认为程序设计是可以定量、定时的。这真是很可笑,也很可叹。我曾经给某个领导说了个比喻,我问他:这有一堆砖头,你让我把它砌成一面墙;你可以很清楚的定量、定时,我可以作,但我不能保证它不坍塌;你认为我的工作性质和砌墙类似么?我不知道他听懂了没有?往往,为了保质量的完成项目,我只有用强硬态度。我想我的领导都会认为我是一个能干活的人,但态度实在不好。每每,我超出预计的完成任务,最后拿到的却是缩了水的奖金,呵呵,其中的委屈有谁知道。
   
    为朋友办事,耽误了我大量的时间。那些天,我每每想起Looking都非常着急。事情总算尘埃落地了,一回到电脑前,我就产生了要开发LJet的强烈愿望。我给自己定的第一个任务是CSG(实体构造学)。为什么是csg?原因只有一个,我喜欢。第一次看到csg的boolean效果是在2年前,当时我被这种效果震住了,当时我想我一定要自己实现一个。在深入开发LJet之前,我必须要作一件事情,那就是给自己定个规矩,程序风格。LJet的代码量大约要有1万行,在我作过的系统里它不算大,但它的调试难度却肯定是最大的。举个简单的例子,如果一个球面和一个立方体相互切割,会产生800多个polygon,如果其中的几个polygon出现问题,该如何定位?我首先想到的问题是程序代码的清晰度问题。在培训新兵的时候,我经常会问他们:你编写的代码是给谁看的?我给他们的答案是:你编写的代码是给其他程序员看的;其他程序员看不懂的代码就是废码;如果一段时间后自己都看不懂的代码可真就是乱码了。呵呵,我想不会有人有兴趣看LJet的代码,我提高程序的清晰度只是为了降低调试强度。我给自己定的目标是:程序的清晰度要达到伪代码级。为了实现这个目标,我使用了大量的宏。很多C程序员可能都犯过下面这个错误:
    if ( a == b )
     ...
    写成
    if ( a = b )
     ...
    这东西就像癌症一样。在头脑不清楚的时候,非常难于发现。往往调试了几个小时后,才发现自己犯了这么个愚蠢的错误。如果在LJet里有一、两个这样的错误,我看我就别干了。
    #define JEQUAL( a, b )  ( ( a ) == ( b ) )
    上面的这个宏可以解决这种问题。这是个小事情,确实是小事情;这会增加键盘的敲击量,确实非常费手指头。但我不敢冒险,当所有的小事情累积在一起,就会变成大事情。当感觉到代码不可调试、不可控制时,能作的恐怕只有放弃了。
    我看过很多C程序员的代码,我发现他们对指针操作情有独钟。我不是说不应该使用指针,这也是不可能的。我只是说代码的书写方式上没完没了的->,恐怕也是灾难。在LJet里的指针操作是非常巨大的,可以肯定100%的函数入口参数需要指针,95%的返回值是指针。在这里存在着一个很重要问题,代码的语意问题。一个变量或函数的名字,就应该说明它的功能,特别是函数名。不恰当的函数名甚至会把意思完全弄反了。在我编写代码的时候,经常为了使名称语意清晰、一目了然而且完全具备单向性,花费大量精力。在LJet中所有的指针操作都是用宏替代的。现在LJet已经有3000多行代码了,但在C源文件中,没有一个->操作。
   
    在这里我举一个例子。在LJet中有一个JBSPNODE结构,它是一个二叉树节点。同时它描述了两个方向,右节点是front,左节点是back。在二叉树的维护上,我们可能更喜欢使用left和right这样的术语,而在空间分割上我们可能更喜欢front和back这样的描述。
    #define JBSPNODE_LEFT( p )   ( ( p )->left )
    #define JBSPNODE_RIGHT( p )   ( ( p )->right )
    #define JSBNODE_FRONT   JBSPNODE_RIGHT
    #define JSBNODE_BACK   JBSPNODE_LEFT
    这样代码的语意就非常清晰了。另外宏提供了一个一动而动全局的机会。例如LJET的polygon结构是JPOLYGON,它描述顶点的方式是使用一个顶点索引数组。
    #define JPOLYGON_VERTEX( p, i )  ( ( p )->indices[ i ] )
    在历遍polygon的所有edge时, 经常会是下面这样:
    for ( UINT i = 0; i < JPOLYGON_COUNT( pPoly ); i++ )
    {
     JPOLYGON_INDEX_TYPE idx1, idx2;
     idx1 = JPOLYGON_VERTEX( pPoly, i );
     if ( JEQUAL( i, JPOLYGON_COUNT( pPoly ) - 1 ) )
      idx2 = JPOLYGON_VERTEX( pPoly, 0 );
     else
      idx2 = JPOLYGON_VERTEX( pPoly, i + 1 );
    }
    但如果把JPOLYGON_VERTEX修改一下,代码就清晰多了:
    #define JPOLYGON_VERTEX( p, i )  ( ( p )->indices[ i ] % JPOLYGON_COUNT( ( p ) ) )
    for ( UINT i = 0; i < JPOLYGON_COUNT( pPoly ); i++ )
    {
     JPOLYGON_INDEX_TYPE idx1, idx2;
     idx1 = JPOLYGON_VERTEX( pPoly, i );
 idx2 = JPOLYGON_VERTEX( pPoly, i + 1 );
    }
    当然,为了提高效率可以声明一个新的顶点索引宏。
    #define JPOLYGON_VERTEX( p, i )  ( ( p )->indices[ i ] )
    #define JPOLYGON_VERTEX2( p, i )  ( ( p )->indices[ i ] % JPOLYGON_COUNT( ( p ) ) )
    有时候把一个函数放到什么地方也是非常伤脑筋的事情。例如一个plane分割polygon的函数,是放在plane.c里还是放在polygon.c里?这可不是个小事情,当系统有几百个函数的时候,我希望从名字上就能定位它在哪个文件里。我一般使用主动动词法。例如,刚才的那个函数名是jPlaneSplitPolygon,那它肯定在plane.c里。
   
    尽管在代码清晰度上煞费苦心,但LJet的调试强度还是令我意外。这也难怪,在调试BSP树的时候,整个文件都充满了递归、变相递归、连表操作、树操作,而且吞吐的数据量大的惊人。另外一个原因是,LJet在开发初期几乎不可能调试。调试基本上是在有数据的情况下进行的,但LJet是个扁平的系统构建,这些元素互相纠缠,缺一不可。在没有相当的代码的情况下,根本就运行不起来,更不用说看到结果了。LJet是在写了2000多行代码后,才开始调试的。在没有调试的情况下凭空构造系统,我称之为“盲写”(呵呵,又是俚语)。我认为,一个程序员特别是C程序员的“盲写”能力,是评价其技术水准的基本指标之一,当然,这不是在评价初级程序员。
   
    经过和LJet的一番角斗,基于bsp的csg系统终于运行起来了。但令我费解的是,在极度切割的情况下(例如,一个cub和一个sphere进行boolean操作),会出现很多斑点。尽管我进行了polygon优化和polygon合并。但情况依然没有改善。数据量实在太大了,尽管没有使用最优polygon合并算法,一次合并也会合并120多个polygon。为了这些斑点,我和LJet整整僵持了2天。最后,当我看到一篇关于T-junctions的文章后,才知道问题所在。真是谢天谢地,我还以为这次又完蛋了。下面的图片是csg的boolean sub的结果,由于csg还在收缩调整阶段,因此结果还没有达到100%的满意。
   
    我们家的宝宝两个月后就要出生了。昨天,我把耳朵贴在我爱人的肚子上对宝宝说:等你出来后,我会在电脑里给你建一个大毫斯(big house)。这时候他,砰,踢了我一脚,看来他还是个急性子,嫌我工作太慢。呵呵。


相关文章
对该文的评论
rainfield790111 ( 2004-03-13)
我现在在做opengl的三维实体布尔运算,有很多问题。不知道您有空帮我指点一下好么?
如果可以,rain_field@eyou.com联系我。

rainfield
 
thanks in advance.
tomz ( 2004-02-18)
不知你对开源的和3dmax开源的blender的看法如何?