topameng's profileQuake3 启示录PhotosBlogListsMore Tools Help

Blog


    April 21

    is_integral 宏展开


    struct integral_c_tag
    {
        static const int value = 0;
    };

    template< bool C_ > struct bool_
    {
        static const bool value = C_;
        typedef integral_c_tag tag;
        typedef bool_ type;
        typedef bool value_type;
        operator bool() const
        {
            return this->value;
        }
    };
    typedef bool_<true> true_;
    typedef bool_<false> false_;
    template< typename T, T N > struct integral_c;

    template <class T, T val>
    struct integral_constant: public integral_c<T, val>
    {
        typedef integral_constant<T,val> type;
    };
    template<> struct integral_constant<bool,false>: false_
    {
       typedef integral_constant<bool,false> type;
    };
    template<> struct integral_constant<bool,true>: true_
    {
        typedef integral_constant<bool,true> type;
    };

    template< typename T > struct is_integral : integral_constant<bool,false>   
    {
    };
    template<> struct is_integral<unsigned char> : integral_constant<bool,true>
    {
        /*BOOST_TT_AUX_BOOL_TRAIT_VALUE_DECL(true) \
        BOOST_MPL_AUX_LAMBDA_SUPPORT_SPEC(1,is_integral,(unsigned char))
        */
    };
    BOOST_TT_AUX_BOOL_TRAIT_SPEC1(is_integral,unsigned char const,true)
    BOOST_TT_AUX_BOOL_TRAIT_SPEC1(is_integral,unsigned char volatile,true)
    BOOST_TT_AUX_BOOL_TRAIT_SPEC1(is_integral,unsigned char const volatile,true)
    March 18

    _beginthread还是CreateThread

    http://www.diybl.com/ 
    _beginthread还是CreateThread
    发表于 2006-01-28 01:56 AM 作者: polyrandom
    程序员对于Windows程序中应该用_beginthread还是CreateThread来创建线程,一直有所争论。本文将从对CRT源代码出发探讨这个问题。

    I. 起因

    今天一个朋友问我程序中究竟应该使用_beginthread还是CreateThread,并且告诉我如果使用不当可能会有内存泄漏。其实我过去对这个问题也是一知半解,为了对朋友负责,专门翻阅了一下VC的运行库(CRT)源代码,终于找到了答案。

    II. CRT

    CRT(C/C++ Runtime Library)是支持C/C++运行的一系列函数和代码的总称。虽然没有一个很精确的定义,但是可以知道,你的main就是它负责调用的,你平时调用的诸如strlen、strtok、time、atoi之类的函数也是它提供的。我们以Microsoft Visual.NET 2003中所附带的CRT为例。假设你的.NET 2003安装在C:\Program Files\Microsoft Visual Studio .NET 2003中,那么CRT的源代码就在C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\crt\src中。既然有了这些实现的源代码,我们就可以找到一切解释了。

    III. _beginthread/_endthread

    这个函数究竟做了什么呢?它的代码在thread.c中。阅读代码,可以看到它最终也是通过CreateThread来创建线程的,主要区别在于,它先分配了一个_tiddata,并且调用了_initptd来初始化这个分配了的指针。而这个指针最后会被传递到CRT的线程包装函数 _threadstart中,在那里会把这个指针作为一个TLS(Thread Local Storage)保存起来。然后_threadstart会调用我们传入的线程函数,并且在那个函数退出后调用_endthread。这里也可以看到, _threadstart用一个__try/__except块把我们的函数包了起来,并且在发生异常的时候,调用exit退出。(_threadstart和endthread的代码都在thread.c中)
    这个_tiddata是一个什么样的结构呢?它在mtdll.h中定义,它的成员被很多CRT函数所用到,譬如int _terrno,这是这个线程中的错误标志;char* _token,strtok以来这个变量记录跨函数调用的信息,...。
    那么_endthread又做了些什么呢?除了调用浮点的清除代码以外,它还调用了_freeptd来释放和这个线程相关的tiddata。也就是说,在 _beginthread里面分配的这块内存,以及在线程运行过程中其它CRT函数中分配并且记录在这个内存结构中的内存,在这里被释放了。
    通过上面的代码,我们可以看到,如果我使用_beginthread函数创建了线程,它会为我创建好CRT函数需要的一切,并且最后无需我操心,就可以把清除工作做得很好,可能唯一需要注意的就是,如果需要提前终止线程,最好是调用_endthread或者是返回,而不要调用ExitThread,因为这可能造成内存释放不完全。同时我们也可以看出,如果我们用CreateThread函数创建了线程,并且不对C运行库进行调用(包括任何间接调用),就不必担心什么问题了。

    IV. CreateThread和CRT

    或许有人会说,我用CreateThread创建线程以后,我也调用了C运行库函数,并且也使用ExitThread退出了,可是我的程序运行得好好的,既没有因为CRT没有初始化而崩溃,也没有因为忘记调用 _endthread而发生内存泄漏,这是为什么呢,让我们继续我们的CRT之旅。
    假设我用CreateThread创建了一个线程,我调用 strtok函数来进行字符串处理,这个函数肯定是需要某些额外的运行时支持的。strtok的源代码在strtok.c中。从代码可见,在多线程情况下,strtok的第一句有效代码就是_ptiddata ptd = _getptd(),它通过这个来获得当前的ptd。可是我们并没有通过_beginthread来创建ptd,那么一定是_getptd捣鬼了。打开 tidtable.c,可以看到_getptd的实现,果然,它先尝试获得当前的ptd,如果不能,就重新创建一个,因此,后续的CRT调用就安全了。可是这块ptd最终又是谁释放的呢?打开dllcrt0.c,可以看到一个DllMain函数。在VC中,CRT既可以作为一个动态链接库和主程序链接,也可以作为一个静态库和主程序链接,这个在Project Setting->Code Generations里面可以选。当CRT作为DLL链接到主程序时,DllMain就是CRT DLL的入口。Windows的DllMain可以由四种原因调用:Process Attach/Process Detach/Thread Attach/Thread Detach,最后一个,也就是当线程函数退出后但是线程还没有销毁前,会在这个线程的上下文中用Thread Detach调用DllMain,这里,CRT做了一个_freeptd(NULL),也就是说,如果有ptd,就free掉。所以说,恰巧没有发生内存泄漏是因为你用的是动态链接的CRT。
    于是我们得出了一个更精确的结论,如果我没有使用那些会使用_getptd的CRT函数,使用CreateThread就是安全的。

    V. 使用ptd的函数

    那么,究竟那些函数使用了_getptd呢?很多!在CRT目录下搜索_getptd,你会发觉很多意想不到的函数都用到了它,除了strtok、 rand这类需要保持状态的,还有所有的字符串相关函数,因为它们要用到ptd中的locale信息;所有的mbcs函数,因为它们要用到ptd中的 mbcs信息,...。

    VI. 测试代码

    下面是一段测试代码(leaker中用到了atoi,它需要ptd):

    代码: #include <windows.h>
    #include <process.h>
    #include <iostream>
    #include <CRTDBG.H>

    volatile bool threadStarted = false;

    void leaker()
    {
    std::cout << atoi( "0" ) << std::endl;
    }

    DWORD __stdcall CreateThreadFunc( LPVOID )
    {
    leaker();
    threadStarted = false;
    return 0;
    }

    DWORD __stdcall CreateThreadFuncWithEndThread( LPVOID )
    {
    leaker();
    threadStarted = false;
    _endthread();
    return 0;
    }

    void __cdecl beginThreadFunc( LPVOID )
    {
    leaker();
    threadStarted = false;
    }

    int main()
    {
    for(;;)
    {
    while( threadStarted )
    Sleep( 5 );
    threadStarted = true;
    // _beginthread( beginThreadFunc, 0, 0 );//1
    CreateThread( NULL, 0, CreateThreadFunc, 0, 0, 0 );//2
    // CreateThread( NULL, 0, CreateThreadFuncWithEndThread, 0, 0, 0 );//3
    }
    return 0;
    }
    如果你用VC的多线程+静态链接CRT(/MT或者/MTD)选项去编译这个程序,并且尝试打开1、2、3之中的一行,你会发觉只有2打开的情况下,程序才会发生内存泄漏(可以在任务管理器里面明显的观察到)。3之所以不会出现内存泄漏是因为主动调用了_endthread。

    VII. 总结

    如果你使用了DLL方式链接的CRT库,或者你只是一次性创建少量的线程,那么你或许可以采取鸵鸟策略,忽视这个问题。上面一节代码中第3种方法基于对 CRT库的了解,但是并不保证这是一个好的方法,因为每一个版本的VC的CRT可能都会有些改变。看来,除非你的头脑清晰到可以记住这一切,或者你可以不厌其烦的每调用一个C函数都查一下CRT代码,否则总是使用 _beginthread(或者它的兄弟_beginthreadex)是一个不错的选择。

    [后记]
    网友condor指出 本文的一个错误:在dllcrt0.c 中,DllMain的Thread Detach所释放的ptd,其实是dllcrt0.c的DllMain中的Thread Attach所创建的。也就是说,当你用CRT DLL的时候,DllMain对线程做了一切初始化/清除工作。我查看源代码,thread.c中的_threadstart函数,在设置TLS之前做了检查,这其实就是为了避免重复设置导致的内存泄漏。
    文章出处:http://www.diybl.com/course/3_program/c++/cppsl/2008831/139196.html

    这也是很多人用在了dll工程中,很难发现出现问题的原因吧! 大部分工程都是设置的 /md
    _BeginThread 使用了 TLS 线程本地存储。 createthread 无,只是sdk
    还有afxbeginthread 更好,这个MFC函数能够检查资源是否是当前线程分配的。如一个窗口句柄在一个线程创建,另一个线程使用就会出问题。用afxbeginthread 可以防止这个问题

    March 11

    tokenizer 使用例子

    #include<iostream>
    #include <boost/tokenizer.hpp>
    #include<string>

    int main()
    {
        using namespace std;
        using namespace boost;
        string s = "12252001";
        int offsets[] = {1,2,3,4};
        offset_separator f(offsets, offsets+4);
        typedef token_iterator_generator<offset_separator>::type Iter;
        Iter beg = make_token_iterator<string>(s.begin(),s.end(),f);
        Iter end = make_token_iterator<string>(s.end(),s.end(),f);
        // 上面这行语句也可以这样写:
        // Iter end;  
        for(;beg!=end;++beg)
        {
            cout << *beg << "\n";  
        }

    string str = "just test;;Hello|world||-foo--bar;yow;baz|";
    typedef tokenizer<char_separator<char> > token;
    char_separator<char> sep(" -;|");
    token tokens(str, sep);   
    for (token::iterator tok_iter = tokens.begin();
        tok_iter != tokens.end(); ++tok_iter)
    {
        cout << "<" << *tok_iter << "> ";
    }
    cout << "\n";
    return EXIT_SUCCESS;


    }

    结果:

    1
    22
    520
    01
    <just> <test> <Hello> <world> <foo> <bar> <yow> <baz>


    tokenizer 正如其意,是用来拆分字符串的boost库。第一个例子是按照offset_separator 指定的数组元素来拆分字符串。
    第二个则是按照char_separator指定的字符作为拆分字符。来拆分字符串。
    注token结构不大,没有默认的构造函数。直接在栈上构造,构造是指定好参数。

    March 06

    如何编写异常安全的C++代码

    http://tech.163.com 2006-04-17 09:19:59 来源: 网易学院(广州)  网友评论0 条 论坛

    关于C++中异常的争论何其多也,但往往是一些不合事实的误解。异常曾经是一个难以用好的语言特性,幸运的是,随着C++社区经验的积累,今天我们已经有足够的知识轻松编写异常安全的代码了,而且编写异常安全的代码一般也不会对性能造成影响。
      使用异常还是返回错误码?这是个争论不休的话题。大家一定听说过这样的说法:只有在真正异常的时候,才使用异常。那什么是“真正异常的时候”?在回答这个问题以前,让我们先看一看程序设计中的不变式原理。
      对象就是属性聚合加方法,如何判定一个对象的属性聚合是不是处于逻辑上正确的状态呢?这可以通过一系列的断言,最后下一个结论说:这个对象的属性聚合逻辑上是正确的或者是有问题的。这些断言就是衡量对象属性聚合对错的不变式。
      我们通常在函数调用中,实施不变式的检查。不变式分为三类:前条件,后条件和不变式。前条件是指在函数调用之前,必须满足的逻辑条件,后条件是函数调用后必须满足的逻辑条件,不变式则是整个函数执行中都必须满足的条件。在我们的讨论中,不变式既是前条件又是后条件。前条件是必须满足的,如果不满足,那就是程序逻辑错误,后条件则不一定。现在,我们可以用不变式来严格定义异常状况了:满足前条件,但是无法满足后条件,即为异常状况。当且仅当发生异常状况时,才抛出异常。
      关于何时抛出异常的回答中,并不排斥返回值报告错误,而且这两者是正交的。然而,从我们经验上来说,完全可以在这两者中加以选择,这又是为什么呢?事实上,当我们做出这种选择时,必然意味着接口语意的改变,在不改变接口的情况下,其实是无法选择的(试试看,用返回值处理构造函数中的错误)。通过不变式区别出正常和异常状况,还可以更好地提炼接口。
      对于异常安全的评定,可分为三个级别:基本保证、强保证和不会失败。
      基本保证:确保出现异常时程序(对象)处于未知但有效的状态。所谓有效,即对象的不变式检查全部通过。
      强保证:确保操作的事务性,要么成功,程序处于目标状态,要么不发生改变。
      不会失败:对于大多数函数来说,这是很难保证的。对于C++程序,至少析构函数、释放函数和swap函数要确保不会失败,这是编写异常安全代码的基础。
      首先从异常情况下资源管理的问题开始.很多人可能都这么干过:
    Type* obj = new Type;
    try{ do_something...}
    catch(...){ delete obj; throw;}

      不要这么做!这么做只会使你的代码看上去混乱,而且会降低效率,这也是一直以来异常名声不大好的原因之一. 请借助于RAII技术来完成这样的工作:
    auto_ptrobj_ptr(new Type);
    do_something...

      这样的代码简洁、安全而且无损于效率。当你不关心或是无法处理异常时,请不要试图捕获它。并非使用try...catch才能编写异常安全的代码,大部分异常安全的代码都不需要try...catch。我承认,现实世界并非总是如上述的例子那样简单,但是这个例子确实可以代表很多异常安全代码的做法。在这个例子中,boost::scoped_ptr是auto_ptr一个更适合的替代品。
      现在来考虑这样一个构造函数:
    Type() : m_a(new TypeA), m_b(new TypeB){}
      假设成员变量m_a和m_b是原始的指针类型,并且和Type内的申明顺序一致。这样的代码是不安全的,它存在资源泄漏问题,构造函数的失败回滚机制无法应对这样的问题。如果new TypeB抛出异常,new TypeA返回的资源是得不到释放机会的.曾经,很多人用这样的方法避免异常:
    Type() : m_a(NULL), m_b(NULL){
     auto_ptrtmp_a(new TypeA);
     auto_ptrtmp_b(new TypeB);
     m_a = tmp_a.release();
     m_b = tmp_b.release();
    }

      当然,这样的方法确实是能够实现异常安全的代码的,而且其中实现思想将是非常重要的,在如何实现强保证的异常安全代码中会采用这种思想.然而这种做法不够彻底,至少析构函数还是要手动完成的。我们仍然可以借助RAII技术,把这件事做得更为彻底:shared_ptrm_a; shared_ptrm_b;这样,我们就可以轻而易举地写出异常安全的代码:
    Type() : m_a(new TypeA), m_b(new TypeB){}
      如果你觉得shared_ptr的性能不能满足要求,可以编写一个接口类似scoped_ptr的智能指针类,在析构函数中释放资源即可。如果类设计成不可复制的,也可以直接用scoped_ptr。强烈建议不要把auto_ptr作为数据成员使用,scoped_ptr虽然名字不大好,但是至少很安全而且不会导致混乱。
      RAII技术并不仅仅用于上述例子中,所有必须成对出现的操作都可以通过这一技术完成而不必try...catch.下面的代码也是常见的:
    a_lock.lock();
    try{ ...} catch(...) {a_lock.unlock();throw;}
    a_lock.unlock();

      可以这样解决,先提供一个成对操作的辅助类:
    struct scoped_lock{
     explicit scoped_lock(Lock& lock) : m_l(lock){m_l.lock();}
     ~scoped_lock(){m_l.unlock();}
     private:
      Lock& m_l;
    };

      然后,代码只需这样写:
    scoped_lock guard(a_lock);
    do_something...

      清晰而优雅!继续考察这个例子,假设我们并不需要成对操作, 显然,修改scoped_lock构造函数即可解决问题。然而,往往方法名称和参数也不是那么固定的,怎么办?可以借助这样一个辅助类:
    template
    struct pair_guard{
     pair_guard(FEnd fe, FBegin fb) : m_fe(fe) {if (fb) fb();}
     ~pair_guard(){m_fe();}
     private:
      FEnd m_fe;
      ...//禁止复制
    };
    typedef pair_guard, function>simple_pair_guard;

      好了,借助boost库,我们可以这样来编写代码了:
    simple_pair_guard guard(bind(&Lock::unlock, a_lock), bind(&Lock::lock, a_lock) );
    do_something...

      我承认,这样的代码不如前面的简洁和容易理解,但是它更灵活,无论函数名称是什么,都可以拿来结对。我们可以加强对bind的运用,结合占位符和reference_wrapper,就可以处理函数参数、动态绑定变量。所有我们在catch内外的相同工作,交给pair_guard去完成即可。
    考察前面的几个例子,也许你已经发现了,所谓异常安全的代码,竟然就是如何避免try...catch的代码,这和直觉似乎是违背的。有些时候,事情就是如此违背直觉。异常是无处不在的,当你不需要关心异常或者无法处理异常的时候,就应该避免捕获异常。除非你打算捕获所有异常,否则,请务必把未处理的异常再次抛出。try...catch的方式固然能够写出异常安全的代码,但是那样的代码无论是清晰性和效率都是难以忍受的,而这正是很多人抨击C++异常的理由。在C++的世界,就应该按照C++的法则来行事。
      如果按照上述的原则行事,能够实现基本保证了吗?诚恳地说,基础设施有了,但技巧上还不够,让我们继续分析不够的部分。

    C++异常处理机制核心观点

    0.如果使用普通的处理方式:ASSERT,return等已经足够简洁明了,请不要使用异常处理机制.
    1.比C的setjump,longjump优秀.
    2.可以处理任意类型的异常. 你可以人为地抛出任何类型的对象作为异常.
        throw 100;
        throw \"hello\";

    3.需要一定的开销,频繁执行的关键代码段避免使用 C++异常处理机制.
    4.其强大的能力表现在:
    A.把可能出现异常的代码和异常处理代码隔离开,结构更清晰.
    B.把内层错误的处理直接转移到适当的外层来处理,化简了处理流程.传统的手段是通过一层层返回错误码把错误处理转移到上层,上层再转移到上上层,当层数过多时将需要非常多的判断, 以采取适当的策略.
    C.局部出现异常时,在执行处理代码之前,会执行堆栈回退,即为所有局部对象调用析构函数,保证局部对象行为良好.
    D.可以在出现异常时保证不产生内存泄漏.通过适当的try,catch布局,可以保证delete pobj;一定被执行.
    E.在出现异常时,能够获取异常的信息,指出异常原因.并可以给用户优雅的提示.
    F.可以在处理块中尝试错误恢复.保证程序几乎不会崩溃. 通过适当处理,即使出现除0异常,内存访问违例,也能让程序不崩溃,继续运行,这种能力在某些情况下及其重要.
    以上ABCDEF可以使你的程序更稳固,健壮,不过有时让程序崩溃似乎更容易找到原因,程序老是不崩溃,如果处理结果有问题,有时很难查找. 5.并不是只适合于处理’灾难性的’事件.普通的错误处理也可以用异常机制来处理,不过如果将此滥用的话,可能造成程序结构混乱,因为异常处理机制本质上是程序处理流程的转移,不恰当的,过度的转移显然将造成混乱.许多人认为应该只在’灾难性的’事件上使用异常处理,以避免异常处理机制本身带来的开销,你可以认为这句话通常是对的.
    6.先让程序更脆弱,再让程序更坚强.首先,它使程序非常脆弱,稍有差错,马上执行流程跳转掉,去寻找相应的处理代码,以求适当的解决方式.很像一个人身上带着许多药品,防护工具出行,稍有头晕,马上拿出清凉油;遇到蚊子立刻拿出电蚊拍灭之.
    WINDOWS:

    7.将结构化异常处理结合/转换到C++异常对象,可以更好地处理WINDOWS程序出现的异常.
    8.尽一切可能使用try,catch,而不是win32本身的结构化异常处理或者MFC中的TRY,CATCH宏.

    用得恰到好处,方显C++异常之美妙!catch(…)
    {
    }
       这三个点并不是说要省略什么.相反,你需要在程序中实际输入这三个点.这是一个很好的默认CATCH块,应该把它放在其他所有CATCH块之后.

        1.7异常规范:
        Double safe_divide(int top,int bottom) throw(DivideByZero);
        假如在函数中抛出一个异常,但异常规范中并未列出这个异常(也没有在函数内部捕捉),会发生什么事情?在这种情况下,程序会终止.尤其要注意的是,假如一个异常在函数中招聘但既没有在异常规范中列出,也没有在函数内部捕捉,那么它不会被任何catch块捕捉,而是直接导致程序终止.记住,如果完全没有异常规范列表,就连空白的都没有,那么效果等同于在规范列表中列出所有异常.在这种情况下,抛出一个异常不会终止程序.
    注意,异常规范是为那些准备跑到函数外部的异常而准备的.如果它们不跑到函数外部,就不归入异常规范.如果它们要跑到函数外部,就应该归入异常规范,无论它们起源于何处.如果在函数定义内部的一个try块中抛出一个异常,而且在函数定义内部的一个catch块中捕捉这个异常这个异常的类型就不需要在异常规范中列出.如果函数定义包括对另一个函数的调用,而另一个函数可能招聘一个它自已不会被捕捉的异常就应该在异常规范中列出异常的类型.
        要表示一个函数不应抛出任何不在函数内部捕捉的异常,需要使用一个空白异常规范.
        Void some_function() throw();
        几种方式可以总结如下:
        Void some_funtion() throw(DivideByZero,OtherExecption);
        //DivideByZero或OtherException类型的异常会被正常处理.
        //至于其他任何异常,如果抛出后未在函数主体中捕捉,就会终止程序.
        Void some_function()throw();
        //空异常列表:一旦抛出任何未在函数主体中捕捉的异常就会终止程序.
        Void some_function();
        //正常处理所有类型的所有异常.

        1.8陷阱:派生类中的异常规范
        在派生类中重定义或覆盖一个函数定义时,它应具有与基类中一亲友的异常规范,或至少应该在新的异常规范中给出基类异常规范的一个子集.换言之,重定义或覆盖一个函数定义时,不可在异常规范中添加新异常.但是,如果愿意,可删减基类中原有的异常.之所以有这个要求,是因为在能够使用基类对象的任何地方,都能使用一个派生类对象.因此,重定义或覆盖的函数必须兼容于为基类对象编写的任何代码.
        下面的内容是我从<<C++面向对象程序设计(第五版)>>找到值得学习的内容assert语句我们曾用以下语句测试一个名为in_stream的文件是否成功打开:
    If(in_stream.fail())
    {
        Cout<<”input file opening failed.\\n”;
        Exit(1);
    }
        可用assert(断言)语句编写同样的测试,如下所示:assert(!in_stream.fail());
    注意,这种情况下,我们要插入一个求反操作符(!),才能获得同样的效果,因为我们要断言的是文件打开操作”没有失败”.
        Assert语句由标识符assert,一个逻辑表达式(包含在一对圆括号内)各一个分号构成.可以使用任何逻辑表达式.如果逻辑表达式false,程序就终止运行,并给出一个错误消息.如果逻辑表达式示值为true,就什么情况都不会发生,程序继续执行assert语句之后的下一条语句.因此,assert 语句是在程序中进行错误检查的一种精简方式.
    Assert语句在cassert库中定义,所以使用assert语句的任何程序都必须包含以下
    include预编译指令:
        #include<cassert>
        Assert是一个宏(类似于函数的一种结构),所以有必要在一个库中定义它.
    使用assert语句的一个好处是可以将其关闭.你可在自己的程序中用assert语句来高度程序,再将其关闭使用户看不到他们无法理解的错误消息.关闭assert语句,还能减少程序执行这些语句的开销.要关闭程序中的所有assert语句,请在include预编译指令之前添加#define NDEBUG,如下所示:
        #define NDEBUG
        #include<cassert>
        因此,如果在进行了全面高度的程序中插入#define NDEBUG,就会关闭程序中的所有assert语句.如果以后改动了程序,可删除程序中的#define NDEBUG重新打开assert语句第6点补充为:
        6.先让程序更脆弱,再让程序更坚强.首先,它使程序非常脆弱,稍有差错,马上执行流程跳转掉,去寻找相应的处理代码,以求适当的解决方式,如果找不到任何解决办法(异常没有被捕获并处理),立刻(结束程序运行)。
        很像一个人身上带着许多药品,防护工具出行,稍有头晕,马上拿出清凉油;遇到蚊子立刻拿出电蚊拍灭之,遇到哪怕是蚊虫叮咬,如果找不到电蚊拍,他马上自杀!(这个家伙真是脆弱:) 以致于我们必须为他准备所有的防护工具一旦我们为他准备了全部适当的防护工具,他就变成一个非常坚强的人了!)。
        当然,代价是这个家伙变得比较笨重,行动迟缓。

    近似的公理:
    1 异常是同步的。即只能发生在函数边界。预定义类型的算术操作、预定义类型的不会导致异常
    2 对象的销毁是异常安全的。即析构函数、operator delete 和operator delete[] 是异常安全的。
    3 swap函数不会导致异常

    使用dbghelp获取调用堆栈--release下的调试方法学

    Author : Kevin Lynx

    当软件作为release模式被发布给用户时,当程序崩溃时我们很难去查找原因。常见的手法是输出LOG文件,根据LOG文件分析
    程序崩溃时的运行情况。我们可以通过SEH来捕获程序错误,然后输出一些有用的信息作为我们分析错误的资料。一般我们需要
    输出的信息包括:系统信息、CPU寄存器信息、堆栈信息、调用堆栈等。而调用堆栈则是最有用的部分,它可以直接帮我们定位
    到程序崩溃时所处的位置(在何处崩溃)。(codeproject上关于这个专题的常见开场白 = =#)

    要获取call stack(所谓的调用堆栈),就需要查看(unwind)stack的内容。We could conceivably attempt to unwind the
    stack ourselves using inline assembly. But stack frames can be organized in different ways, depending on compiler
    optimizations and calling conventions, so it could become complicated to do it that way.(摘自vld文档)要获取栈的
    内容,我们可以自己使用内联汇编获取,但是考虑到兼容性,内联汇编并不是一个好的解决方案。我们可以使用微软的dbghelp
    中的StackWalk64来获取栈的内容。

    StackWalk64声明如下:
    BOOL StackWalk64(
      DWORD MachineType,
      HANDLE hProcess,
      HANDLE hThread,
      LPSTACKFRAME64 StackFrame,
      PVOID ContextRecord,
      PREAD_PROCESS_MEMORY_ROUTINE64 ReadMemoryRoutine,
      PFUNCTION_TABLE_ACCESS_ROUTINE64 FunctionTableAccessRoutine,
      PGET_MODULE_BASE_ROUTINE64 GetModuleBaseRoutine,
      PTRANSLATE_ADDRESS_ROUTINE64 TranslateAddress
    );

    具体每个参数的含义可以参见MSDN。这里说下ContextRecord参数,该参数指定了CPU各个寄存器的内容。StackFrame指定了stack frame的内容。stack frame是什么,我也不知道。(= =) StackWalk64函数需要用户指定当前frame的地址,以及当前程序的指令地址。这两个信息都被填充进ContextRecord,然后传进StackWalk64函数。

    那么如何获取当前的stack frame地址和当前程序指令地址呢?如前所说,你可以使用内联汇编。(对于程序指令地址,因为要获取EIP寄存器的内容,而该寄存器不能被软件访问)也可以使用GetThreadContext一次性获取当前线程当前运行情况下的CPU各个寄存器内容。补充下,当前frame地址被放在EBP寄存器里,当前程序指令地址放在EIP寄存器里。但是,如同MSDN对GetThreadContext函数的说明一样,该函数可能获取到错误的寄存器内容(You cannot get a valid context for a running thread)。

    另一种获取Context(包含EBP and EIP)的方法就是使用SEH(结构化异常处理),在__except中使用GetExceptionInformation获取。 GetExceptionInformation 传回一个LPEXCEPTION_POINTERS指针,该指针指向一个EXCEPTION_POINTERS结构,该结构里包含一个Context的指针,即达到目标,可以使用StackWalk函数。

    补充一下,你可以直接使用StackWalk函数,StackWalk被define为StackWalk64(windows平台相关)。 unwind栈后,可以进一步获取一个stack frame的内容,例如函数名。这里涉及到SymFromAddr函数,该函数可以根据一个地址返回符号名(函数名)。还有一个有意思的函数:SymGetLineFromAddr,可以获取函数对应的源代码的文件名和行号。

    当然,这一切都依赖于VC产生的程序数据库文件(pdb),以及提供以上API函数的dbghelp.dll。

    参考一段简单的代码:

    #include <windows.h>
    #include <stdio.h>
    #include <dbghelp.h>

    #pragma comment( lib, "dbghelp.lib" )

    void dump_callstack( CONTEXT *context )
    {
        STACKFRAME sf;
        memset( &sf, 0, sizeof( STACKFRAME ) );

        sf.AddrPC.Offset = context->Eip;
        sf.AddrPC.Mode = AddrModeFlat;
        sf.AddrStack.Offset = context->Esp;
        sf.AddrStack.Mode = AddrModeFlat;
        sf.AddrFrame.Offset = context->Ebp;
        sf.AddrFrame.Mode = AddrModeFlat;

        DWORD machineType = IMAGE_FILE_MACHINE_I386;

        HANDLE hProcess = GetCurrentProcess();
        HANDLE hThread = GetCurrentThread();

        for( ; ; )
        {
            if( !StackWalk(machineType, hProcess, hThread, &sf, context, 0, SymFunctionTableAccess, SymGetModuleBase, 0 ) )
            {
                break;
            }

            if( sf.AddrFrame.Offset == 0 )
            {
                break;
            }
            BYTE symbolBuffer[ sizeof( SYMBOL_INFO ) + 1024 ];
            PSYMBOL_INFO pSymbol = ( PSYMBOL_INFO ) symbolBuffer;

            pSymbol->SizeOfStruct = sizeof( symbolBuffer );
            pSymbol->MaxNameLen = 1024;

            DWORD64 symDisplacement = 0;
            if( SymFromAddr( hProcess, sf.AddrPC.Offset, 0, pSymbol ) )
            {
                printf( "Function : %s\n", pSymbol->Name );
            }
            else
            {
                printf( "SymFromAdd failed!\n" );
            }

            IMAGEHLP_LINE lineInfo = { sizeof(IMAGEHLP_LINE) };
            DWORD dwLineDisplacement;

            if( SymGetLineFromAddr( hProcess, sf.AddrPC.Offset, &dwLineDisplacement, &lineInfo ) )
            {
                printf( "[Source File : %s]\n", lineInfo.FileName );
                printf( "[Source Line : %u]\n", lineInfo.LineNumber );
            }
            else
            {
                printf( "SymGetLineFromAddr failed!\n" );
            }
        }
    }

    DWORD excep_filter( LPEXCEPTION_POINTERS lpEP )
    {
        /**//// init dbghelp.dll
        if( SymInitialize( GetCurrentProcess(), NULL, TRUE ) )
        {
            printf( "Init dbghelp ok.\n" );
        }

        dump_callstack( lpEP->ContextRecord );

        if( SymCleanup( GetCurrentProcess() ) )
        {
            printf( "Cleanup dbghelp ok.\n" );
        }

        return EXCEPTION_EXECUTE_HANDLER;
    }

    void func1( int i )
    {
        int *p = 0;
        *p = i;
    }

    void func2( int i )
    {
        func1( i - 1 );
    }

    void func3( int i )
    {
        func2( i - 1 );
    }

    void test( int i )
    {
        func3( i - 1 );
    }

    int main()
    {
        __try
        {
            test( 10 );
        }
        __except( excep_filter( GetExceptionInformation() ) )
        {
            printf( "Some exception occures.\n" );
        }

        return 0;
    }

    以上代码在release模式下需要关掉优化,否则调用堆栈显示不正确(某些函数被去掉了?),同时需要pdb文件。当用户使用时可以只抛出挂掉时候的地址,然后开发者通过crashfinder这样的软件,启动拥有pdb的程序

    March 04

    smart_ptr 使用例子

    使用智能指针不会因为忘记delete指针而造成内存泄露。还有当第三方的lib中某些函数返回指针,这样的返回的指针被client使用的时候,lib就会失去对返回的指针的控制,这样delete的指针的任务一般就会交给调用方client,但是如果client忘记调用delete或是调用的时机不正确,都有可能导致问题,在这种情况下最好使用智能指针。
    使用智能指针的典型情况:
    使用智能指针可以保证异常安全
    保证程序在有异常抛出时仍然无内存泄露。
    智能指针更好的解决了资源的所有权共享               

    shared_ptr<boost/shared_ptr.hpp>:使用shared_ptr进行对象的生存期自动管理,使得分享资源所有权变得有效且安全.      scoped_ptr<boost/scoped_ptr.hpp>: 用于确保能够正确地删除动态分配的对象。scoped_ptr 有着与std::auto_ptr类似的特性,而最大的区别在于它不能转让所有权而auto_ptr可以。事实上,scoped_ptr永远不能被复制或被赋值!scoped_ptr 拥有它所指向的资源的所有权,并永远不会放弃这个所有权。 
    weak_ptr<boost/weak_ptr.hpp>:weak_ptr 是 shared_ptr 的观察员。它不会干扰shared_ptr所共享的所有权。当一个被weak_ptr所观察的 shared_ptr 要释放它的资源时,它会把相关的 weak_ptr的指针设为空。使用此辅助指针一般是防止悬空指针。intrusive_ptr<boost/intrusive_ptr.hpp>:shared_ptr的插入是版本,一般不使用,因为需要对使用此指针的类型中增加ref计数功能。但是可以保证不增加指针的大小。
    scoped_array<boost/scoped_array.hpp>: scoped_array 为数组做了scoped_ptr为单个对象的指针所做的事情:它负责释放内存。
    shared_array<boost/shared_array.hpp>: shared_array 用于共享数组所有权的智能指针。一般指向std::vector的shared_ptr提供了比shared_array更多的灵活性,所以一般使用std::vector<shared_ptr>。


    环境boost1_38_0
    #include <iostream>
    #include <string>
    #include <vector>
    #include <boost/smart_ptr.hpp>
    #include <boost/any.hpp>
    #include <boost/format.hpp>

    void PrintIfString(const boost::any& Any)
    {
        const boost::shared_ptr<std::string> *s =
            boost::any_cast<boost::shared_ptr<std::string> >(&Any);
        if (s && *s)
        {
            std::cout << **s << std::endl;        
        }

        const boost::weak_ptr<std::string>* s1 =
            boost::any_cast< boost::weak_ptr<std::string> >(&Any);
        if(s1)
        {       
            const boost::shared_ptr<std::string> s = s1->lock();
            if(s)
            {
                std::cout<<"weak: "<< *s <<std::endl;
            }
        }

    }

    int main(int argc, char* argv[])
    {
        std::vector<boost::any> Stuff;
        boost::shared_ptr<std::string> SharedString1(new std::string(
            "Share me.By the way,Boost.any is another useful Boost library"));
        boost::shared_ptr<std::string> SharedString2(SharedString1);
        boost::shared_ptr<int> SharedInt1(new int(42));
        boost::shared_ptr<int> SharedInt2 (SharedInt1);
        boost::weak_ptr<std::string> ShareString3(SharedString2);
        //SharedString1.reset(); 重置智能指针为NULL
        //SharedString2.reset();
        Stuff.push_back(SharedString1);
        Stuff.push_back(SharedString2);
        Stuff.push_back(SharedInt1);
        Stuff.push_back(SharedInt2);   
        Stuff.push_back(ShareString3);
        for_each(Stuff.begin(), Stuff.end(), PrintIfString);
        Stuff.clear();

        return 0;
    }

    scoped_prt 源码中,支持 if(smartptr) 判断的实现
    typedef T * this_type::*unspecified_bool_type;
    operator unspecified_bool_type() const
    {
        return px == 0? 0: &this_type::px;
    }

    令人发晕的类型unspecified_bool_type,这种类型实际上是"指向类的某内部成员变量的指针",不要认为它是指针类型(c必知必会上有说明)。(可以参考<<深入c++对象模型>>来查看这样的偏移值是多少)。unspecified_bool_type可以被当作bool类型使用,它要么为0,要么为1(&this_type::px总是1,错误这个是类内部成员变量的偏移值,cout输出为1是因为转换为bool类型了,可以用printf(”%p”,xx)来打印值。注意vc做了特殊处理,第一个元素偏移值打印为0,不像深入c++对象模型说的是1. 但实际上是转换判断还是为 true 的。

    注意这样使用:    boost::shared_ptr<int> SharedInt1(new int(42));  而不是先new 然后赋值智能指针。在函数参数上new可以保证异常安全。除了weak_ptr都可以按这种方法来使用。

    在C++标准4.12节中, 对指针与布尔值之间的关系有大致如下的描述:
    任何一个指针或成员对象指针都可以转换成布尔值,空指针转换成false, 其它值转换成true.
    这样我们就可以利用成员指针来进行转换。通常成员指针不可能(至少不那种容易)被隐式转换(转换的前提毕竟是类型要匹配,或可向上转换)。

    February 07

    Gamebryo限制4层材质

    由于地形贴图尺寸很大,所以无法使用全局贴图。而是把多层Tiling 的纹理使用alpha通道互相融合起来。诸如WOW[1],天堂2 等大型室外地形渲染多采用此技术。此技术在每一层Tiling 贴图依然用到了一张全局alpha 贴图。以及一张全地形唯一的静态光影贴图。在multitexture 中根据显卡的multitexture 处理单元数量,进行multi-pass 和multitexture 的混合渲染。
    比如,混合绿草,土路,野花的一块室外地形,需要至少3 层地表贴图。分别是一块256x256 的草贴图,128x128 的土路贴图,和128x128 的野花贴图。每层纹理使用Tiling 模式进行绘制,在绘制的同时,每层纹理带有一张ALPHA 灰度图(第一层不用)。用来表明本层纹理在融合中所占的比重。把3 张地表贴图,2 张ALPHA 贴图,以及一张LIGHTMAP贴图混合起来,就有如下地表效果。可以看出土路和草地之间的均匀过渡。

    如果进行优化,4 层ALPHA 可以混合称为一张贴图。Lightmap 一张。那么也至少需要2 张全局贴图进行地表绘制。

    --------------------------------------------------------------------------------------------------------------------------------------------摘自 ID TECH 5

    所以限制4层纹理一般是因为对于一个纹元RGBA,RGB 能代表3 个alpha。而alpha 通道代表阴影。再加上第一层的基础纹理不需要alpha值
    alpha值放入到一张纹理图中,不会因为LOD,而降低混合效果这也是一些引擎限制4层材质的原因。wow 也是4层材质的

    December 23

    WildMagic D3d Memory leak

    通过设置 dxcpl.exe 发现 wildMagic4.8 有内存泄漏,经过跟踪最终发现,作者设置默认字体不能使用UnladFont 函数卸载,d3d 设备也没有释放,顺便加上。修改如下
    Dx9Renderer::~Dx9Renderer ()
    {
        // release all fonts
        for (int i = 1; i < (int)m_kFontArray.size(); i++)
        {
            UnloadFont(i);
        }
        m_kFontArray[0]->Release();    //添加

        // clean up cursor
        if (!m_bCursorVisible)
        {
            ShowCursor(true);
        }
        m_pqDevice->Release();       //添加
        m_pqMain->Release();          //添加
    }

    对于学习来说这是款不错的引擎。4.0以上版本完全是shader驱动的,不在使用固定管线。
    用的是cg shader. 需要用nvdia cgc 编辑器编译cg shader脚本
    October 10

    杂事两三件

    使用左(右)手拇指指向坐标轴正向,握住坐标轴,4指环绕方向为正方向

    不管是左手右手,行向量还是列向量。矩阵存储基本都是[row][col]形式

    这时该如何建立矩阵呢?
    答案就是区分你用的是行向量还是列向量.

    也许有人会问为什么不区分是左手坐标系还是右手坐标系呢?
    因为矩阵M可以变换到矩阵M1, 那么他们一定是同在左手坐标系或右手坐标系,变换只能在可以互相转换的坐标系之间进行。 这也是为什么gamebryo摄像机朝向X.但最终dx 还是z值判断深度。这也是矩阵变换的结果

    如果你用的是行向量:由于行向量只能左乘矩阵(注意乘与乘以的区别)

    gamebryo 默认模型朝向-Y轴。摄像机看向X轴方向。上方向为Y. 当然变换后的矩阵在z向上世界坐标里面是看向Y轴。

    gamebryo 矩阵和dx矩阵相乘方向相同 Rx * Ry * Rz 先执行Rx,然后 Ry,最后Rz。

    在全屏调试下程序crash原因非常麻烦。设置断点也会导致全屏情况不能切换回桌面。这个有个简单办法就是在断点前加入一个MessageBox操作。通过弹出对话框,从全屏状态恢复到窗口状态。后面就是你的断点了。cool~

    对于NiNode代表的场景图。要节省效率可以做2个场景图。一个放入静态对象。这些对象不需要Update更新。另一个放入动画对象。这个场景图需要一直更新。
    同理对于实体-场景架构也可以用这个办法,但不包括大范围运动物体,如玩家和大范围寻路npc之类。这样会拉大其所在结点的包围体大小

    注意GameBryo 使用的场景图是一种包围体层次结构BHV,这种结构退化的表现就是八叉树。但注意不同于八叉树。可以参考3D游戏编程大师技巧上面的说明。这本书上说包围体层次结构建立可以使用分而治之或者集群技术算法。但GameBryo 并没有使用算法来确定树结构,所以通过程序Node attach 结点方法建立起来的包围体层次结构效率会非常差。而在3dmax里面是通过美术人员手动 group 一下相关网格来组成一个节点层次结构。如果遇到经验不足的,直接所有mesh挂在根结点下。效率也会非常差。不过还好3dmax很多很多操作会自动产生节点,所以通过group相关的(主要是空间相关、效果相关,可参考mout)节点在一起可以提高效能。

    softparticles 写深度到另一个rendertarget
    MeshCreation 使用了floodgate. 波浪由 floodgate 产生
    StandardMaterial 展示了标准的shader

    material 只是引用了 shader 。
    有很多使用光源方法。投射光源等等。

    scene graph bounding Spheres是用摄影机进行裁切。地表系统用了quadtree。

    NiMesh 比 NiGeomentry 有什么不同

    因为floodgate 等的使用,要提供一个更有弹性的系统。 NiMesh 取代之。更多东西可以在Mesh上分享。还有如GPU Instancing等功能。以前有三角形 ,线条等。现在统一为NiMesh

    gamebryo2.5 支持1024*1024地形大小。由于gb对应max是1:1的,这样相当于1平方公里. lightspeed 版本好像没有限制了。这个也可以不是1平方公里。就像魔兽世界一个地形网格代表的是3.333的样子。也不是1.就是说max人物建筑之类的可以缩小到1/3放入地形场景。这样地形就大了。如果要求不太高还可以缩小。
    地形支持4层材质(不是纹理。材质可以是很多纹理的混合。bump map. light map. 等等一个材质信息要丰富很多。如魔兽4个纹理的地形,可能就相当于一个材质如MOUT中的地形材质)。

    RenderStep 相当于一个effect. 如:hdr,dof 等等,游戏场景也是一个effect.相当于 dx 的 d3deffect. 负责 rendertargetgroup

    renderclick 是一个单独的渲染pass.可以设置摄像机和 culler.  一个renderstep的多个renderclick,操作的是相同的rendertargetgroup
    renderview 为 renderclick 提供一些列需要渲染的几何体数据

    对于 .fx 之外的 shader 一般需要一个 .nsf 描述文件,来描述shader文件中的外部变量。在程序中动态更新某个shader内变量如下:

    NiShaderFactory::UpdateGlobalShaderConstant("LightDiffuse",sizeof(afLightDiffuse), &afLightDiffuse);
    如果没有用nsf描述外部变量,可以通过NiShaderFactory::RegisterGlobalShaderConstant()函数来注册 shader 脚本中的外部变量

    注意 NiAplhaAccumulate 对物体是从后向前排序的,这样适合绘制alpha物体,但对于无alpha物体应该从前到后排序,这样减少像素值重复覆盖次数。就像bsp树的做法。 orion 例子里面的 Accumulate 就是这种做法

    NiSortAdjustNode 可以让引擎按节点下面网格顺序来draw alpha 物体而不进行排序操作,但还不清楚在3dmax中这些子网格是如何附加到这个虚拟体上的

    July 23

    光照

    对于灯光上面的环境光、漫反射、镜面光分别乘以当前材质的环境、漫反射、镜面分量,然后再叠加到顶点颜色上。
    对于direct3d 图形与动画程序设计上面的 环境、漫反射、镜面 shader 是相对于方向光的 shader.
    如果是点光源还要有衰减,而聚光灯还要有内外夹角。
    当然对于light 可以使用 dx 的固定管线灯光,这样可以不用写shader(但受8个灯光的限制)。下面是一个摘录的opengl 的 shader 光照模型。跟directx 在镜面光上可能有些不同


    这里提供一种使用GLSL shader实现更多数量的局部光照。
    在GLSL里,首先建立光照参数数据结构:

    struct myLightParams
    {
        bool enabled;
        vec4 position;
        vec4 ambient;
        vec4 diffuse;
        vec4 specular;
        vec3 spotDirection;
        float spotCutoff;
        float spotExponent;
        float constantAttenuation;
        float linearAttenuation;
        float quadraticAttenuation;
    };

    然后,需要app传入的参数:

    const int maxLightCount = 32;
    uniform myLightParams light[maxLightCount];
    uniform bool bLocalViewer;
    uniform bool bSeperateSpecualr;

    主函数:

    void main()
    {
        gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
        vec4 pos = gl_ModelViewMatrix * gl_Vertex;
        vec3 epos = vec3(pos)/pos.w;

        vec3 normal = normalize(gl_NormalMatrix * gl_Normal);

        vec3 eye;
        if (bLocalViewer)
           eye = -normalize(epos);
       else
            eye = vec3(0, 0, 1.0);

        vec4 amb = vec4(0);
        vec4 diff = vec4(0);
        vec4 spec = vec4(0);

    for (int i=0; i<maxLightCount; i++)
    {
         if (light[i].enabled == false)
           continue;

         if (light[i].position.w == 0)
        {
                DirectionalLight(i, eye, epos, normal, amb, diff, spec);
        }
        else if (light[i].spotCutoff == 180.0)
        {
                PointLight(i, eye, epos, normal, amb, diff, spec);
        }
        else
        {
                SpotLight(i, eye, epos, normal, amb, diff, spec);
        }
    }

        vec4 color = gl_FrontLightModelProduct.sceneColor +
                     amb * gl_FrontMaterial.ambient +
                     diff * gl_FrontMaterial.diffuse;

    if (bSeperateSpecualr)
    {
            gl_FrontSecondaryColor = spec * gl_FrontMaterial.specular;
    }
    else
    {
            gl_FrontSecondaryColor = vec4(0, 0, 0, 1.0);
            color += spec * gl_FrontMaterial.specular;
    }

        gl_FrontColor = color;
    }

    //对于方向光源的计算:

    void DirectionalLight(int i, vec3 eye, vec3 epos, vec3 normal, inout vec4 amb, inout vec4 diff, inout vec4 spec)
    {
         float dotVP = max(0, dot(normal, normalize(vec3(light[i].position))));
         float dotHV = max(0, dot(normal, normalize(eye+normalize(vec3(light[i].position)))));

        amb += light[i].ambient;
        diff += light[i].diffuse * dotVP;
        spec += light[i].specular * pow(dotHV, gl_FrontMaterial.shininess);
    }

    //对于点光源:

    void PointLight(int i, vec3 eye, vec3 epos, vec3 normal, inout vec4 amb, inout vec4 diff, inout vec4 spec)
    {
        vec3 VP = vec3(light[i].position) - epos;
        float d = length(VP);
        VP = normalize(VP);

        float att = 1.0/(light[i].constantAttenuation + light[i].linearAttenuation*d + light[i].quadraticAttenuation*d*d);
        vec3 h = normalize(VP+eye);

        float dotVP = max(0, dot(normal, VP));
        float dotHV = max(0, dot(normal, h));

        amb += light[i].ambient * att;
        diff += light[i].diffuse * dotVP * att;
        spec += light[i].specular * pow(dotHV, gl_FrontMaterial.shininess) * att;
    }

    //对于聚光灯:

    void SpotLight(int i, vec3 eye, vec3 epos, vec3 normal, inout vec4 amb, inout vec4 diff, inout vec4 spec)
    {
        vec3 VP = vec3(light[i].position) - epos;
        float d = length(VP);
        VP = normalize(VP);

        float att = 1.0/(light[i].constantAttenuation + light[i].linearAttenuation*d + light[i].quadraticAttenuation*d*d);

        float dotSpot = dot(-VP, normalize(light[i].spotDirection));
        float cosCutoff = cos(light[i].spotCutoff*3.1415926/180.0);

        float spotAtt = 0;
        if (dotSpot < cosCutoff)
            spotAtt = 0;
        else
            spotAtt = pow(dotSpot, light[i].spotExponent);

        att *= spotAtt;

        vec3 h = normalize(VP+eye);

        float dotVP = max(0, dot(normal, VP));
        float dotHV = max(0, dot(normal, h));

        amb += light[i].ambient * att;
        diff += light[i].diffuse * dotVP * att;
        spec += light[i].specular * pow(dotHV, gl_FrontMaterial.shininess) * att;
    }

    这样,对于场景之中的任意对象,它所能够接受计算的光源就可以突破8个的限制了。
    上述光照计算是遵循OpenGL spec的,因此与固定管线的效果是一致的。

    July 19

    Diffuse Lighting (Direct3D 9)

    After adjusting the light intensity for any attenuation effects, the lighting engine computes how much of the remaining light reflects from a vertex, given the angle of the vertex normal and the direction of the incident light. The lighting engine skips to this step for directional lights because they do not attenuate over distance. The system considers two reflection types, diffuse and specular, and uses a different formula to determine how much light is reflected for each. After calculating the amounts of light reflected, Direct3D applies these new values to the diffuse and specular reflectance properties of the current material. The resulting color values are the diffuse and specular components that the rasterizer uses to produce Gouraud shading and specular highlighting.

    Diffuse lighting is described by the following equation.

    Diffuse Lighting = sum[Cd*Ld*(N.Ldir)*Atten*Spot]
    
    Parameter Default value Type Description
    sum N/A N/A Summation of each light's diffuse component.
    Cd (0,0,0,0) D3DCOLORVALUE Diffuse color.
    Ld (0,0,0,0) D3DCOLORVALUE Light diffuse color.
    N N/A D3DVECTOR Vertex normal
    Ldir N/A D3DVECTOR Direction vector from object vertex to the light.
    Atten N/A FLOAT Light attenuation. See Attenuation and Spotlight Factor (Direct3D 9).
    Spot N/A FLOAT Spotlight factor. See Attenuation and Spotlight Factor (Direct3D 9).

    The value for Cd is either:

    • vertex color1, if DIFFUSEMATERIALSOURCE = D3DMCS_COLOR1, and the first vertex color is supplied in the vertex declaration.
    • vertex color2, if DIFFUSEMATERIALSOURCE = D3DMCS_COLOR2, and the second vertex color is supplied in the vertex declaration.
    • material diffuse color

    Note     If either DIFFUSEMATERIALSOURCE option is used, and the vertex color is not provided, the material diffuse color is used.

    To calculate the attenuation (Atten) or the spotlight characteristics (Spot), see Attenuation and Spotlight Factor (Direct3D 9).

    Diffuse components are clamped to be from 0 to 255, after all lights are processed and interpolated separately. The resulting diffuse lighting value is a combination of the ambient, diffuse and emissive light values.

    Example

    In this example, the object is colored using the light diffuse color and a material diffuse color. The code is shown below.

    D3DMATERIAL9 mtrl;
    ZeroMemory( &mtrl, sizeof(mtrl) );
    
    D3DLIGHT9 light;
    ZeroMemory( &light, sizeof(light) );
    light.Type = D3DLIGHT_DIRECTIONAL;
    
    D3DXVECTOR3 vecDir;
    vecDir = D3DXVECTOR3(0.5f, 0.0f, -0.5f);
    D3DXVec3Normalize( (D3DXVECTOR3*)&light.Direction, &vecDir );
    
    // set directional light diffuse color
    light.Diffuse.r = 1.0f;
    light.Diffuse.g = 1.0f;
    light.Diffuse.b = 1.0f;
    light.Diffuse.a = 1.0f;
    m_pd3dDevice->SetLight( 0, &light );
    m_pd3dDevice->LightEnable( 0, TRUE );
    
    // if a material is used, SetRenderState must be used
    // vertex color = light diffuse color * material diffuse color
    mtrl.Diffuse.r = 0.75f;
    mtrl.Diffuse.g = 0.0f;
    mtrl.Diffuse.b = 0.0f;
    mtrl.Diffuse.a = 0.0f;
    m_pd3dDevice->SetMaterial( &mtrl );
    m_pd3dDevice->SetRenderState(D3DRS_DIFFUSEMATERIALSOURCE, D3DMCS_MATERIAL);
    

    According to the equation, the resulting color for the object vertices is a combination of the material color and the light color.

    These two images show the material color, which is gray, and the light color, which is bright red.

    untitled red

    The resulting scene is shown below. The only object in the scene is a sphere. The diffuse lighting calculation takes the material and light diffuse color and modifies it by the angle between the light direction and the vertex normal using the dot product. As a result, the backside of the sphere gets darker as the surface of the sphere curves away from the light.

    lightd

    Combining the diffuse lighting with the ambient lighting from the previous example shades the entire surface of the object. The ambient light shades the entire surface and the diffuse light helps reveal the object's 3D shape.

    lightad

    Diffuse lighting is more intensive to calculate than ambient lighting. Because it depends on the vertex normals and light direction, you can see the objects geometry in 3D space, which produces a more realistic lighting than ambient lighting. You can use specular highlights to achieve a more realistic look.

    Ambient Lighting (Direct3D 9) 环境光

    Ambient lighting provides constant lighting for a scene. It lights all object vertices the same because it is not dependent on any other lighting factors such as vertex normals, light direction, light position, range, or attenuation. It is the fastest type of lighting but it produces the least realistic results. Direct3D contains a single global ambient light property that you can use without creating any light. Alternatively, you can set any light object to provide ambient lighting. The ambient lighting for a scene is described by the following equation.

    环境光为场景提供了一种恒定不变的光照。环境光对所有物体的顶点的照明效果相同,因为它与其余光照因子,如顶点法向、光的方向、光的位置、范围或衰减等无关。环境光是最快的一种类型,但它提供的真实感最少。Direct3D 包含了一个全局的环境光属性,应用程序可以直接使用而无需创建任何光源。另外,应用程序也可以设定光源提供环境光照。场景中环境光的计算由以下公式描述。

    Ambient Lighting = Ca*[Ga + sum(Atti*Spoti*Lai)] 
    

    Where:

    参数 默认值 类型 描述
    Ca (0,0,0,0) D3DCOLORVALUE Material ambient color  材质环境光颜色
    Ga (0,0,0,0) D3DCOLORVALUE Global ambient color  全局的环境光颜色
    Atteni (0,0,0,0) D3DCOLORVALUE Light attenuation of the ith light. See Attenuation and Spotlight Factor (Direct3D 9).
    第i个光源的衰减因子
    Spoti (0,0,0,0) D3DVECTOR Spotlight factor of the ith light. See Attenuation and Spotlight Factor (Direct3D 9).
    第i个光源的聚光灯因子
    sum N/A N/A Sum of the ambient light  环境光总和
    Lai (0,0,0,0) D3DVECTOR Light ambient color of the ith light  第i个光源环境光颜色

    The value for Ca is either:

    • vertex color1, if AMBIENTMATERIALSOURCE = D3DMCS_COLOR1, and the first vertex color is supplied in the vertex declaration.
    • vertex color2, if AMBIENTMATERIALSOURCE = D3DMCS_COLOR2, and the second vertex color is supplied in vertex declaration.
    • material ambient color.

    Note    If either AMBIENTMATERIALSOURCE option is used, and the vertex color is not provided, then the material ambient color is used.
               m_pDevice9->SetRenderState(D3DRS_AMBIENTMATERIALSOURCE , D3DMCS_MATERIAL);

    To use the material ambient color, use SetMaterial as shown in the example code below.

    Ga is the global ambient color. It is set using SetRenderState(D3DRS_AMBIENT). There is one global ambient color in a Direct3D scene. This parameter is not associated with a Direct3D light object.

    Lai is the ambient color of the ith light in the scene. Each Direct3D light has a set of properties, one of which is the ambient color. The term, sum(Lai) is a sum of all the ambient colors in the scene.

    Ca的值可以是:

    顶点颜色1,如果AMBIENTMATERIALSOURCE = D3DMCS_COLOR1,并且顶点声明中给出了第一个顶点的颜色。
    顶点颜色2,如果AMBIENTMATERIALSOURCE = D3DMCS_COLOR2,并且顶点声明中给出了第二个顶点的颜色。
    材质的环境反射色。

    注意 如果使用了任何一种AMBIENTMATERIALSOURCE,但是没有提供顶点颜色,那么系统会使用材质的环境反射色。

    要使用材质的环境反射色,按以下示例代码使用SetMaterial方法。

    Ga为全局的环境反射色,通过SetRenderState(D3DRENDERSTATE_AMBIENT)设置。Direct3D场景中只有一个全局环境反射色,它与其余Direct3D光源无关。

    Lai为场景中第i个光源的环境反射色。每个Direct3D光源都有一组属性,其中一个就是环境反射色。符号sum(Lai)表示场景中所有环境反射色的总和。

    Example

    In this example, the object is colored using the scene ambient light and a material ambient color.

    #define GRAY_COLOR	0x00bfbfbf
    
    // create material
    D3DMATERIAL9 mtrl;
    ZeroMemory(&mtrl, sizeof(mtrl));
    mtrl.Ambient.r = 0.75f;
    mtrl.Ambient.g = 0.0f;
    mtrl.Ambient.b = 0.0f;
    mtrl.Ambient.a = 0.0f;
    m_pd3dDevice->SetMaterial(&mtrl);
    m_pd3dDevice->SetRenderState(D3DRS_AMBIENT, GRAY_COLOR);
    

    According to the equation, the resulting color for the object vertices is a combination of the material color and the light color.

    These two images show the material color, which is gray, and the light color, which is bright red.

    untitled red

    The resulting scene is shown below. The only object in the scene is a sphere. Ambient light lights all object vertices with the same color. It is not dependent on the vertex normal or the light direction. As a result, the sphere looks like a 2D circle because there is no difference in shading around the surface of the object.
    渲染得到的场景如下所示。场景中唯一的物体是一个球体。环境光用相同的颜色对物体的所有顶点进行光照计算,它不依赖于顶点法向和光的方向。因此,球体看起来像是二维的圆,因为物体的表面没有明暗变化。

    light

    To give objects a more realistic look, apply diffuse or specular lighting in addition to ambient lighting.

    July 11

    Gamebryo/Artist's Guides/Gamebryo 3ds max Plug in/Geometry and Performance

    Introduction to Geometry, Performance, and 3ds Max
    A basic geometric question facing all real-time 3D artists is "How should I construct my scene so that I can maintain great real-time rendering speed?"
    There are numerous factors that affect performance, including how many triangles to use, the number of textures applied to a single surface, and the target platform. The sections that follow discuss these issues in more detail.

    对所有实时3D美工人员面对的基本的几何学问题是"为了维持良好实时渲染速度,我应该怎么构建我的场景?"
    这里有很多因素可能影响性能,包括使用了多少三角形,应用到一个单独平面的纹理贴图的数量,以及目标平台。接下来的章节我们将详细讨论这些问题。

    Terminology
    术语

    The Triangle/Mesh Ratio
    Triangle / Mesh 比值

    Transform-Rate, Fill-Rate
    转换率,填充率

    Clipping & Culling
    剪切和剔除

    Triangle/Mesh Ratios vs. Clipping and Culling
    Triangle/Mesh 比值与剪切和剔除比较

    Grouping
    群组

    Skinning & Morphing
    蒙皮 & 变形

    Cloning and Instancing
    克隆和实例化

    Multi/Sub-Object and Triangle/Mesh Ratios
    Multi/Sub-Object 物体 和 Triangle/Mesh 比值

    Multiple UVs, Smoothing Groups and Vertices
    多重 UVs,光滑群组和定点

    Precache Custom Attributes
    预先缓存定制属性

    Terminology
    术语

    The following section will introduce key terms and phrases that you should understand before discussing geometry.
    接下来的章节将在讨论几何体之前介绍一下你应该理解的一些关键术语和惯用语

    Stripification
    条带化

    This term describes an operation in which a list of independent triangles is transformed into a list of triangles that are linked together in a chain. Although this operation can increase performance, it is mutually exclusive with the Vertex Cache optimization which only operates on triange lists. Vertex cache optimization generally gives better performance results.

    这个术语描述了一个操作,在这个操作中一系列独立的三角被转化成一个三角形条带。尽管这个操作可以提高性能,但它和三角形列表上的顶点缓存优化是互斥的,而顶点缓存优化通常能带来更高的性能。(而且条带化要条带足够长才能优化,而很多三角形不能形成很好的条带)

    Mesh (or NiMesh)
    网格

    This term describes the Gamebryo representation of geometry. A mesh contains all of the per-vertex information about a piece of geometry, including vertex positions, normals, vertex colors, and UV sets. A mesh is usually composed of independent triangle primitives, but if the mesh has been stripified it will be composed of a set of triangle strips.
    这个术语描述了Gamebryo如何表示几何体。一个网格包括一块几何体的所有顶点数据信息,包括顶点位置,发现,颜色,和UV集合。一个网格通常由独立的三角形图元组成,但如果网格被条带化,那么它将由一套三角形条带组成。

    The Triangle/Mesh Ratio

    The triangle to mesh ratio is the most important geometric metric for game performance. The issue with triangles and meshes is that when rendering an mesh, Gamebryo must do a fixed amount of work on the CPU (property-state setup, texture swapping, etc.) each time it passes down the set of triangles, no matter how big. You should, thus, try to pack as many triangles as possible into each mesh.
    三角形对网格的比值对游戏的性能是一个非常重要几何度量。三角形和网格的问题是:当渲染一个网格时,无论每次处理的三角形集合多大,Gamebryo在CPU上必须做固定负荷的工作(属性设置,纹理交换等等) 。一次你应尽可能向每个网格打包更多的的三角形。

    In general, a game should never have fewer than 20 triangles per mesh. You don't have to increase the number of triangles just to improve the triangle/mesh ratio, but if doing so will improve vertex lighting or some geometric detail, it won't hurt the performance. Another way to tackle improving the ratio is to collapse similar meshes with the same materials that are close together in a scene. This collapsed mesh will be converted to a single mesh instead of several separate meshes, thus improving the overall ratio.
    总的来说,一个游戏每个网格不能少于20个三角形。你不需要为了提高三角形/网格比率而特意增加三角形个数,虽然这么做可以提高顶点光照和几何体细节,但不要让他损害性能。另一个途径是折叠在场景中相邻并使用的相同材质的网格。这样折叠的网格将转换成一个单独的网格而不是几个分散的网格,这样将改善原来所有单独网格的比值,让比率获得全面的提高。

    The importance of a large triangle/mesh ratio (i.e. a lot of triangles per mesh) is increased on hardware transform and lighting cards (high-end graphics cards). Hardware transform and lighting cards perform vertex transformation, lighting, and rasterization on the graphics card. Earlier cards could only perform the rasterization while the CPU was forced to do the vertex transformation and lighting.
    增大三角形/网格比率的价值(例如,一个网格拥有大量三角形)随着硬件变换和光照显卡(高端图形显卡??很多年前喽)出现而变得重要。硬件变换和光照(T&L)显卡在显卡端执行顶点变换,光照和光栅化。而早前的显卡只能执行光栅化而cpu被迫执行顶点变换和光照的计算。

    Hardware transform and lighting cards both free the CPU of this task and perform it faster than the CPU ever could. This division of labor decreases the time required to render an individual polygon but leaves the fixed amount of work that Gamebryo must do for each mesh (discussed earlier) unchanged. In a low triangle/mesh situation the CPU will become the bottleneck (doing the rendering setup) and the full rendering capabilities of the graphics card will not be used. In contrast, a high triangle/mesh ratio will allow the graphics card to draw as many polygons as possible and leave the CPU free to perform other operations.
    硬件T&L显卡减少了CPU的这项任务而且比CPU处理的更加迅速。这种分工减少了渲染一个单独多边形的时间,但是留给Gamebryo的每个网格的工作量没有改变。在三角形/网格第比值的较低情况下CPU将变成瓶颈(执行渲染设置操作),而显卡的全部渲染能力没有被用到。相反,三角形/网格高比值将允许显卡尽可能多的绘制三角形从而留给CPU空闲去执行其他工作
    尽可能把相同材质多边形放入同一个网格,而不是无尽的增加网格的三角形面数

    Performance Metrics
    性能标准

    Performance analysis done at nVidia and ATI revealed that on a 1 GHz CPU, you can render 25,000 objects per second before you spend all of your time on the CPU, a circumstance you wish to avoid at almost all costs.
    根据nVida 和 ATI 透漏的性能分析数据,在1GHz CPU 上,当你花费CPU全部时间,你可以每秒钟渲染25000个对象,而这种全部花费的情况是你希望避免的

    What do these statistics mean to an artist? For performance of 60 fps on a 1 GHz CPU, try to keep the number of visible objects in any given scene below 417 objects. In other words, make every object count!
    这个统计对于美工意味着什么呢?为了在1Hz CPU 上保持60fps的性能,应该在场景中控制可见对象少于417个。注意每个物体都要计算

    As the CPU speed of the target machine increases, the number of visible objects that may be rendered per frame will correspondingly increase.
    The following sections will discuss how art content issues could increase or decrease the Triangle/Mesh ratio.
    随着cpu的速度越来越快,每一帧渲染的物体数量也会相对增加。下一章将讨论美工内容如何提高或者减少三角形/网格比值

    Transform-Rate, Fill-Rate
    变换速率,填充速率

    Transform-Rate

    The transform-rate is the number of vertices a graphics card can process in a given time period. When the number of vertices to be transformed (moved, rotated, and lit) per time period exceeds the graphics card's capabilities, an application is said to be "transform limited."

    The following table shows the maximum T&L rate for various PC graphics hardware:
    变换率是指图形显卡在给定的时间周期内处理顶点的个数,如一个周期内变换(平移,旋转,照明)的顶点个数超过显卡的能力,可以说应用程序到达了"变换上限"。下列表格列出了不同的PC显卡最大的T&L的速率:

    Manufacturer & Card

     

    T&L - million vertex/sec

    nVidia GeForce 3

    40

    nVidia GeForce 4 Ti

    136

    NVidia GeForce FX

    200

    nVidia GeForce 6800

    600

    ATI Radeon 8500

    75

    ATI Radeon 9700

    300

    ATI Radeon X800

    780

    These values are, of course, theoretical peaks and do not represent real-world game situations. However, these numbers can be useful in examining performance. If you wish to achieve 60 fps on a GeForce 3, the absolute top amount of vertices you can transform in a frame is 666,666. Let's say that every object you render requires two passes. The theoretical top you could transform is now halved to 333,333 vertices. Mind you, these vertices all belong to one object and are untextured and flat shaded, drawn as optimally as possible with absolutely nothing else happening in the application. No interesting game could ever hope to achieve this situation.
    当然这些理论峰值并且不能代表真实游戏世界情况。但这些数字可以用来检查性能。如果你希望在GeForce 3上达到60fps.能够变换的一帧相对最大顶点数量是666,666。每个对象你需要渲染两次。能够变换顶点的理论最大值将减半为333,333. 注意:这些顶点都属于一个还未纹理化和平坦着色的对象,尽可能优化而且应用程序无任何情况发生。任何游戏都不可能发生这种情况。

    Fill-Rate
    填充率

    Not only is a graphics card limited in the number of vertices it can transform, it is also limited in the number of pixels it can write to the backbuffer per second. The backbuffer is the portion of a graphics card's memory that is used as a scratch pad while the final image is being assembled. When this process of writing to the backbuffer exceeds the graphics card's capability, an application is described as being "fill-rate limited."
    The following chart shows the maximum fill rate for various PC graphics hardware:

    显卡不仅仅在变换顶点的数量方面受到限制, 而且每秒填充到后缓冲区的像素个数也受到了限制。后备缓冲区是显卡显存的一部分,做为正在组装的最终图像便签。当写入后备缓冲区操作超出显卡能力时,应用程序称为"填充率上限"
    下图显示了不同PC显卡最大的填充率

    Manufacturer & Card

     

    Pixel fillrate - million pixel/sec

    nVidia GeForce 3

    800

    nVidia GeForce 4 Ti

    1200

    nVidia GeForce FX

    2000

    nVidia GeForce 6800

    6400

    ATI Radeon 8500

    1100

    ATI Radeon 9700

    2600

    ATI Radeon X800

    8300

    These values are, of course, theoretical peaks and do not represent real-world game situations. However, these numbers can be useful in examining performance. Let's see what we can do with a GeForce 3 at a display resolution of 1024 by 768. We'll assume for the moment that transformation and lighting comes for free (which it never does). 1024 by 768 resolution is 786,432 pixels. We'd like to run at 60 fps, so that involves rendering that 1024 by 768 image 60 times for a grand total of 47.19 million pixels. Assuming each pixel is drawn more than once, the maximum number of pixel writes we can do on each pixel is roughly 17. This, of course, assumes that all operations that write a pixel cost the same. Multitexturing and the complexity of pixel shaders quickly lower this number. Overuse of complex pixel shaders can quickly make an application fill rate limited.
    当然这些理论峰值并且不能代表真实游戏世界情况.但这些数字可以用来检查性能. 让我们看看我们在显示器分辨率1024*768的GeForce 3上能做什么。我们假定此时变换和光照操作免费(这不可能发生)。1024×768 分辨率表示786,432 个像素。我们希望以60fps运行。所以渲染1024×768分辨率图像60次将达到47.19 百万像素。假定每个像素被渲染不止一次,假定所有写入一个像素操作花费相同时间,我们在每个像素上写入的最大次数大约是17次 (800/47)。多纹理和复杂的像素shaders会降低这个数量。过度使用复杂的像素着色器会快速的达到应用程序填充率上限。

    Clipping & Culling
    裁剪和剔除

    The clipping/culling behavior of Gamebryo is another issue that must be kept in mind when creating scene geometry.
    当创建场景几何体时Gamebryo的裁切/剔除行为是另一个需要记在头脑中的问题

    Clipping is the process of dividing polygons into only the fragments that will appear on screen.
    裁切是将多边形分开成可以在屏幕上显示的片断的过程。

    Culling is an attempt to avoid clipping by rejecting whole NiMesh objects if no part of them appears on the screen. In general, culling is preferable to clipping because clipping is vastly more expensive than culling.
    如果物体的任何部分都不出现在屏幕上通过拒绝整个NiMesh对象显示来避免裁切就是剔除。总的来说,剔除比裁切更可取,因为裁切比剔除操作更加昂贵。

    You can structure your scenes to improve culling by limiting the volume of space an NiMesh occupies. A small NiMesh is more likely to be completely off screen while a very large one (e.g. a huge floor) is likely to be, at least, partially on screen all the time.
    通过限制NiMesh占据的空间体积,你可以在构造场景是提高剔除效率。一个小的NiMesh更可能完全离开屏幕而一个非常大的(例如巨型地板)很有可能,至少一部分一直在场景中。

    Triangle/Mesh Ratios vs. Clipping & Culling
    三角形/网格比值 VS 裁切和剔除

    These two issues, the triangle/mesh ratio and the culling/clipping behavior, place somewhat contradictory demands on you. To improve the triangle/mesh ratio you must have the most triangles in a mesh possible (mesh collapsing, increasing the number of triangles in the mesh, etc.). Simultaneously, the meshes must be kept compact to allow for efficient culling. There is no simple solution to this problem and you must constantly balance the two constraints. In general, triangles should be grouped into meshes so that culling will still be effective, but the meshes should contain the most triangles possible.

    For example, when modeling the four walls of a room, if the walls are relatively complex, each wall should be in its own mesh. Having all the walls in a single mesh would improve the triangle/mesh ratio but would force clipping on all the geometry. By dividing the walls into four meshes, two of them will usually be culled leaving the other two to be clipped. However, if the walls were very simple (i.e. 2 triangles each) then it might make sense to clump all the wall triangles together to avoid having several 2 triangle meshes.

    In modern hardware, side plane clipping is avoided as much as possible through various tricks. Near plane clipping remains a problem, however.

    对于这两个问题,三角形/网格比值和裁切/剔除形为,对于你来说处于某种对立的位置。为了改善三角形/网格比值,你必须尽可能让更多的三角形在一个网格中(折叠网格,增加网格中三角形的数量等等)。同时地,网格必须保持紧凑的以允许有效的剔除。对这个问题没有一个简单的解决办法,你必须自己在两个限制间找平衡。总的来说,三角形应该被组合到网格中,这样剔除仍然高效,但网格应该包含尽可能多的三角形。

    例如,当为一个屋子的4面墙壁建模,如果墙壁相对较复杂,每个墙壁应该有它自己的网格。把所有的墙壁放入一个单独的网格中可以改善三角形/网格比值,但是将强迫裁切所有的几何体。通过将墙壁分成4个网格,它们中的两个将总是执行剔除操作,而留下另外两个执行裁切。然而,如果墙壁是非常简单的 (比如每个2个三角形)那么可以考虑将整个墙壁整合起来以避免出现几个拥有两个三角形的网格。

    在现代的硬件中,侧平面(side plane)裁切可以通过多种多样的窍门尽可能避免,然而,近平面(near plane)裁切仍然是一个问题。

    Gamebryo/Artist's Guides/Gamebryo 3ds max Plug in/Geometry and Performance(II)

    Grouping and 3ds Max
    群组和3ds Max

    As with Multi/Sub-Object materials, grouping should be used with care. Everything in a scene in Max has a corresponding node in its scene graph. In Gamebryo, that node is called an NiNode. An NiMesh represents a group of triangles while an NiNode contains a list of children and a list of transforms. Whenever a collection of objects in Max is grouped, the Gamebryo 3ds max plug-in has to add another NiNode to group them together in Gamebryo.

    Indiscriminate use of grouping can have a significant effect on the triangle/mesh ratio. In general, a high-triangle single object has much better frame-rate performance then many objects grouped together.

    同使用Multi/Sub-Object材质一样,群组应该小心使用。在Max场景中的每个东西都有一个对应的节点在它的场景图中。在Gamebryo中,这个节点称为NiNode。一个NiMesh描述了一组三角形而一个 NiNode 包含了一个子结点列表以及一个变换列表。无论何时物体的集合在Max中被群组化,Gamebryo 3ds max插件必须加入另一个NiNode 将它们在Gamebryo中组合起来。

    不加选择的应用群组会对triangle/mesh ratio比率产生重大的影响。总的来说,一个复杂三角形单一对象比许多对象群组在一起,在帧速率上可以获得更好的性能。

    Skinning & Morphing with 3ds Max

    Skinning for Artists

    There are a few important bits of hardware knowledge that a character animator and modeler should be aware of before jumping into skinning a character. The hardware skinning pipeline is broken into two important numbers, the maximum number of bones the hardware can handle and the maximum number of bones that can influence a vertex. Each number has an important role to play in determining how your skinned character will perform in a game.

    The maximum number of bones the hardware can handle by default for most platforms is four. What exactly does this mean? Four is a really small number for a character. Behind the scenes, Gamebryo will break the skinned mesh apart into pieces that obey this limit. These pieces are referred to as skin partitions. For each partition we generate, we have to render the partition's geometry in a separate rendering call. For instance, a skinned character with 28 bones could be broken into 23 partitions in order to obey the maximum bone limit. This means that we are rendering the model in 23 separate pieces. Furthermore, each partition can only be composed of the vertices that use only those bones. If you're not careful in your weight assignments, you could end up with a partition with only one triangle!

    The maximum number of bones influencing a vertex by default on most platforms is also four. This means that a vertex that is influenced by five or more bones will only use the four most influential bones in its skinning. This may result in cracks in sections where many bones come together like the back of the neck and the crotch. Careful modeling and weight assignment will help to minimize this problem. This does not mean that you should always use four bones per vertex. In fact, it is best to use as few weights as are visually acceptable per vertex. This will help substantially when partitioning the mesh because more vertices will be able to fit into a partition.

    What should you take away from this discussion? First off, how you skin your character will directly transfer into performance for that character. Listed below are some useful hints on how to analyze your skinning performance and tips for getting good performance in general.

    • Do not use N-Links when skinning with Physique. Stick with No Blending or 2-Links.

    • Use the SkinAnalyzer plug-in to see how exactly your skin was broken apart into partitions.

    • Use the Skin Weight Threshold export option to drop weights that are trivial. This will greatly improve performance. Be careful to make sure that odd skinning artifacts do not result when this value is raised.

    • Use a pixel/vertex shader for skinning. This can really boost your performance if you can accept the fact that the shader will not work on cards that are not compliant with the version of pixel and vertex shader used. Often you can reduce the partition count to one or two using shaders.

    Physique vs. Skin modifier

    The Gamebryo Max Plug-in supports the two major modifiers for skinning. We have found the recent  versions of Skin to be more reliable and robust then Physique, even when applied to bipeds, however both are supported.

    Gamebryo doesn't support floating bones for physique. If your model requires this capability, we suggest modeling with Skin instead of physique.  In the Physique Level-Of-Detail panel, we only support Rigid Skin Update. When using Deformable, the exporter treats it like Rigid.

    Linking your skinned object

    Do not attach a skin to a bone in the hierarchy below that to which the skin is bound. This causes the mesh to translate twice, once for the skin binding and another for the child translation. You can create a node above the Bip01or base node to which the skin is attached, or use a character node to organize a bone structure and skin together.

    The mesh that has the Skin or Physique modifier uses the bones to determine its bounding volume. The hierarchy is updated by a depth-first traversal of the scene. If the skin occurs before the bones in a depth first traversal of the scene, the skin's bounding volume will lag one frame. Move the skin so that it occurs after the bone hierarchy in a depth first traversal to avoid this problem.

    Scaling your skinned object

    Do not scale a mesh after binding. If you scale a mesh you must unbind it first and rebind it after it has been properly scaled and translated. Scale it first and reset its transforms before you bind it to the bone system for best results.

    Morph and Skin work together

    Even though we support combining skin & morph targets we do not suggest it. If you want to make a character have facial animation morph targets, detach the head from the rest of the body and have it attached to the head bone as a direct link. The head mesh can use a morph target and translate via its relation with the head bone while the body uses a skin or physique modifier. When both modifiers are combined on a mesh it is much slower as all the vertices are being transformed twice, once in software and once in hardware. Please take this into consideration when making characters that have facial expression. See the section on Morphing Faces on Skinned Characters for more information.

    Morph Targets

    The Morph Modifier and Morph compound object are both supported in Gamebryo. We have found the Morph Modifier more reliable then the compound object in Max. Morph is inherently slower than skinning due to the fact that morphing is still done in software. However, there are many things that are easier to do with morph targets than with bone movement. Suggested uses for Morph targets are things like animated flags, facial expression and non-uniform scales.

    Instancing with Skinning and Morphing

    Objects that are skinned or morphed are exported such that clones (instances) are independently controlled and animated. However, when the Mesh Instancing tool plug-in is used, CPU skinned and morphed objects that are instances or exact copies in the Max scene will be exported as hardware instances. As a result, there is only one animated object and every other instance appears exactly the same. Move CPU skinned and morphed instances to another Max scene or use export selected to prevent this behavior when using the Mesh Instancing plug-in.

    Reading Skin Analyizer Plug-in Output

    The following section is a sample output from the skin analyzer plug-in.

    image1

    The model was broken into 23 partitions out of 28 bones. This model will likely perform moderately well. The key problems come around partition 19 in the list. Here we drop below 40 triangles per partition. At this point we are paying a fair amount of overhead for 6 partitions that don't have that many triangles in them. DirectX especially pays for this overhead. Often, this is unavoidable in modeling as some sections are the nexus of many bones and will be prone to small partitions (the neck and groin in particular). In general, the thing to be wary of is when the number of partitions exceeds the number of bones. This is horrible for performance because it also means that many of the partitions are incredibly small.

    Listed below the main chunk of text is a breakdown of each partition. This lists the bones involved in that partition and what the average weights were for that bone. This text can often be useful in pinpointing problem vertices since the bones in that partition would influence them

    Cloning and Instancing with 3ds Max

    3ds Max中的克隆和实例

    Instanced objects in Max are shared in Gamebryo as well. This technique can be extremely useful on memory-starved consoles. Note: If you adjust the pivot or non-uniform scale of an instance, it will become unique. Uniform scale is supported, but non-uniform scale cannot work with instances in Gamebryo because non-uniform scale is baked into the geometry. It should also be noted that instanced objects in Gamebryo must share the same material, unlike in Max. Instanced objects that need different materials should be made unique.

    Max中实例对象可以在Gamebryo很好的共享。这项技术对于内存受限的主机非常的实用。注意:如果你调整轴心点或者不成比例缩放一个实例,它将变得唯一。均匀缩放是被支持的,然而,不能对Gamebryo中实例进行不均匀缩放,因为不均匀缩放会烘培几何体(使几何体发生不规则变形,max有烘培一个操作)。还要注意Gamebryo中的实例对象必须共享相同的材质,这点不同于Max。 需要不同材质的实例对象应该制作成唯一的,不共享的。

    Non-Uniform & Uniform Scale

    不均匀和均匀缩放

    Gamebryo does not support animating non-uniform scales (i.e. scales with different values in the x, y, and z axes). Animating non-uniform scale is not supported and will be treated like a uniform scale when animating. All static non-uniform scales are baked into the geometry data itself.

    If you need to have something non-uniformly scaled or squashed, use a morph target to achieve the action. Morph targets can be created by squashing an object in 3ds max and using Tools / Snapshot to grab a desired morph target.

    Gamebryo不支持动画不均匀的缩放(比如在x,y和z轴上有不同的缩放比例) 。动画的不均匀缩放是不支持的并且在动画中做为等比例缩放的方式来处理。所有静态的不均匀缩放会烘培到几何数据本身。

    如果你需要使某些东西非等比例的缩放或者挤压,应用变形动画修改器对象(morph target)来完成这个任务。变形对象(morph target)可以在3ds max中通过挤压来制作,并应用Tools/Snapshot来获取一个想要的变形体对象(morph target)。

    Multi/Sub-Object and Triangle/Mesh Ratios

    Multi/Sub-对象 和 Triangle/Mesh 比率

    A convenient way of texturing an object in Max is to use the Multi/Sub-Object material. However, Gamebryo only supports one material per mesh because most hardware can only handle one material per set of triangles. When the Gamebryo 3ds max Plug-in encounters a Multi/Sub-Object material it must split the Max Mesh into multiple NiMesh objects (one for each material).

    在Max中贴图一个对象便利的方法是应用Multi/Sub-Object 材质。然而,Gamebryo只支持每个网格一个材质,因为大部分硬件只能为一个三角形集合处理一个材质。当 Gamebryo 3ds max 插件遇到一个Multi/Sub-Object材质,它必须将Max网格分开成多个NiMesh 对象 (每个网格对应一个材质)。

    Over-use of Multi/Sub-Objects is the most common offender in terms of performance in most data sets support receives.
    技术支持收到的大部分性能相关的问题都是和过度使用Multi/Sub-Objects 相关的

    If used indiscriminately, Multi/Sub-Object materials can lay waste to the triangle/mesh ratio and you end up with a single object made of many little pieces. Multi/Sub-Object materials do not need to be completely avoided but they should be used cautiously and with a consideration of the triangle/mesh ratio constraints.

    如果不加选择的使用Multi/Sub-Object材质可能造成triangle/mesh 比率的浪费以及导致一个单独物体被分成很多片。也不是绝对要禁用Multi/Sub-Object材质,但是应该小心的应用并且考虑到triangle/mesh比值的限制。

    When doing characters and discreet objects, the concept of a single texture 'Skin' for the subject is recommended.
    当处理角色和离散物体时,推荐使用"皮肤",它代表一个单独纹理的概念。

    An item made of 1 or 2 objects draws faster than one made of 12 or 13.  This is not to say, "Never use Multi-Subs" as they are very convenient. However, as an example we have received a support question on performance with a single character that had a multi-sub material on it. He was created in such a way that when exported, this single character turned into 42 different objects! This laid waste to performance since each of those 42 objects became individual meshes each with non-trivial overhead.

    一个由一两项组成的对象比由12或13个项组成的画得快。这不是说即使便利也不使用Multi-Subs。对于拥有multi-sub材质的单一角色性能问题,我们收到过一个技术支持的例子。它在导出时以这样的方式进行,这个单一的角色被转换成了42个不同的对象! 这造成了性能浪费,因为这42个对象中每一个都是单独的网格以至于造成了不平凡的开销。

    如果使用了它,Gamebryo exporter就会把一个Mesh分成多个导出。而每一个Mesh都需要一个Draw call来完成渲染。这就影响效率了。
    举个例子,如果你要做一片草地,每棵小草就是一个Model的话,那么就有太多的geometry nodes。从而需要太多的Draw call来渲染。如果把一小片草地组成一个geometry node,Draw call就减少了。因为每一个Draw call都要相应地改变Render state,这就消耗了一定的时间。
    结论应该是,不是不能用(因为有的时候用起来较为方便),而是慎用。用的时侯尽量考虑到对效率的影响。

    对于directx draw call 调用次数也是有限制的。即使函数什么都不做。当然切换渲染状态相对较小。但如果不需要的渲染状态也会影响。如没有用到alpha blend ,但打开了alpha blend 渲染状态。

    更多性能问题参考文档
    http://topameng.spaces.live.com/blog/cns!F962D4854A8233D!352.entry

    Multiple UVs, Smoothing Groups and Vertices

    多重 UVs,平滑群组和顶点

    Another geometric concern is Max's Mesh representation itself. Max can have more than one normal and UV (within a single UV channel) per vertex while real-time engines cannot. To resolve this incompatibility our 3ds max Plug-in will add additional vertices so that the NiMesh created has only one normal and UV per vertex. The NiMesh will look the same as the Max Mesh but will have more vertices. This vertex bloat will push the transform-rate but it is seldom a big problem. Using a lot of smoothing groups or complicated UV Mappings will make the problem worse but some vertex bloat is unavoidable.

    Having things smooth is faster than having things flat shaded. Vertexbloat.max is also a good example of this phenomenon. A 120-triangle sphere with 1 smoothing group has 62 vertices and a flat or un-shaded version has 358 vertices.

    另一个几何上的关注点是Max网格描述本身。Max每个顶点可以拥有超过一个的法线和UV (带一个单独的UV通道) 而实时的引擎却不可以。为解决这种不兼容性的3ds max 插件加入了额外的顶点,所以创建的NiMesh每个顶点只有唯一的法线及UV。NiMesh 看上去将和Max中网格一样但它拥有更多的顶点。这种顶点膨胀将给变换速率造成负担,但是它不是一个大问题。应用很多平滑群组或者复杂的UV Mappings将会加重这个问题,但是一些顶点的膨胀是无法避免的。

    使物体原滑比使他们平坦着色更快些。 Vertexbloat.max也是这个现象的很好的例子。一个120三角面的球带一个平滑的群组拥有62个顶点而一个平坦或者无着色的版本有358个顶点。


    image002

    The test case Vertexbloat.Max demonstrates this effect.

    Box01 has the usual Max UV mapping and a texture map that visualizes the UVs. In Max the vertex at the tip of the pink corner has the three UVs (1,0), (0,0) and (0,1).

    The Gamebryo 3ds max Plug-in will generate three vertices to replace this one Max vertex —one for each UV.  While 3ds max represents Box01 as 12 triangles and 8 vertices, the Gamebryo 3ds max Plug-in will convert this model into 12 triangles and 24 vertices.

    测试例子中 Vertexbloat.Max 演示了这种效果。

    Box01拥有通常的贴图以及一个可视化UV的材质贴图。在Max中在粉色角落顶点上有3个UV(1,0), (0,0) 以及 (0,1)。

    Gamebryo 3ds max 插件产生了3个顶点来代替这个Max中的顶点,每个UV对应一个。虽然3ds max将 Box01 描述成12个三角和8个顶点,但Gamebryo 3ds max 插件将这个模型转化成12个三角和24个顶点。

     

    Mesh Profile Custom Attribute
    网格档案Profile定制属性

    Every piece of geometry has a Mesh Packing Profile associated with it. This is used by the Packer tool plug-in to create the platform-specific geometry streams used by the graphics card. Depending on the situation, it can be necessary for a specific mesh to override the scene default packing profile and use its own profile. To override the scene default packing profile one would click on the "Add/Remove Mesh Profile attribute" button to add a Mesh Profile Attribute to the selected mesh.
    几何体的全部面片拥有一个 Mesh Packing Profile和它相关联。打包插件使用它来创建平台细节几何体流。基于这个情况,使用自定义的Profile覆盖特定网格上的scene default packing profile 是必须的。为了覆盖场景默认的包装profile可以点击 "Add/Remove Mesh Profile attribute"按钮来添加一个网格profile属性到选择的网格上。

    MeshProfileSelection

    For additional information on mesh profiles see Introduction to Mesh Profiles.
    更多关于网格profiles的信息请参考Introduction to Mesh Profiles

    Precache Custom Attributes
    缓存定制属性

    PrecacheCustAttribs The precache attributes serve a similar pupose to the Mesh Profile Custom Attribute. The mesh profile system provies all of the functionality of the precache attributes and more. The precache attribtues still exist in case a user wants precache control without having to author a custom profile as well as for backwards compatibility.

    These custom attributes tell the Gamebryo renderers how to treat an object once they have created the platform-specific versions of it. Creating the platform-specific version is called "pre-caching" the object. In some cases it may be useful for applications to keep information lying around after it has been "pre-cached".

    As an important example, triangle-triangle collision detection will not work if the triangles are thrown away once the renderer has pre-cached its data.

    Typically, artists will not need to add these attributes, since any application that pre-caches geometry, can set the "Keep" flags to hold on to data that is needed, as well. The UI exists so that advanced users can have complete control over how specific art assets are used by the renderers.

    These flags can be added through the user-interface via the Gamebryo toolbar. Click on the "Add/Remove Precache flags" icon.
    缓存属性和Mesh Profile Custom Attribute章节目标相似。网格Profile系统提供了所有的预先缓存属性的功能,甚至更多。预先缓存属性存在目的是,用户想要缓存管理而不想创建一个定制的profile,同时也是为了向后兼容。

    这些定制属性告诉Gamebryo渲染器,在创建平台细节的版本时如何对待对象。创建具体平台版本称为"pre-caching"对象。在某些情况下,应用程序在"pre-cached"之后,保持闲置信息是十分有用的。
    一个重要的例子,如果渲染器在预存数据后扔掉了三角形数据,那么三角形对三角形的碰撞检测就不能工作。
    一般而言,不需要美工来添加这些属性,因为预先缓存几何体的应用程序,可以设置"keep"标志来保持它需要的数据。这个UI保留着,以便让高级用户能够完全控制特定的美术资源如何被渲染器使用。

    这些标志这些标志可以通过用户界面的 Gamebryo toolbar 加入。点击 "Add/Remove Precache flags" 图标。

    Geometry Data Consistency
    几何数据一致性

    This radio button allows you to set how the renderer treats the data at runtime. "Default" sets the consistency to whatever Gamebryo best determines the data to be. "Static" means that once the data is in the renderer it will never change. This is a good setting for set pieces like buildings. "Mutable" means that the object may change from time to time. This would be useful for an application effect like changing a car's geometry to reflect hits it has taken on the road while driving. "Volatile" means that the object changes every frame. This setting is best set for when you are changing vertex colors on the fly or manually animating the UV coordinates.
    这个单选按钮允许用户设定渲染器在实时状态下如何处理数据。"Default" 由Gamebryo 来决定数据的一致性。"static" 指一旦数据进入了渲染器将不再改变。这个选项是用于设定类似建筑物的部件的好选择。"Mutable"意味着对象可能偶尔发生变化。比如需要表现出一辆汽车在驾驶过程中因碰撞发生形状改变效果,这时这个选项对应用程序就十分有用了。"Volatile"意味着这个对象每一帧都要发生改变。当你正在改变顶点颜色或者手工动画绘制UV坐标时,这个选项就是最好的选择。

    Data To Preserve
    保留的数据

    These check boxes allow the user to set which data is kept around after the "pre-cache" has occurred.

    这些复选框允许用户设定哪些数据在 "pre-cache"后需要保留下来。

    June 06

    terrain

    对于2.3地形是通过max或者maya导出来的网格。可能只有地形。而花草树木等等地面上的东西要在场景里面加入,然后拼接在一起。2.5 说有场景编辑器开始自带地形编辑器。不知道是否好用

    gamebryo release 与 ship 区别

    The Release builds include optimizaed code, but with the NiMemory system, NiMetrics, and release mode logging enabled. The Shipping builds do not have these systems enabled.
    ship 和 release 工程设置基本相同,但没有NiMemory、NiMetrics、release mode logging.更像是非常稳定之后的版本
    release 还会产生记录内存泄漏的xml。这个文件会越来越大。
    ship 对类内变量初始化与release有些不同,印象中一个bug:一个变量在release未初始化然后乘0正常为0,但在ship中结果不为0.faint
    June 04

    Gamebryo2.2 8*00系列显卡调试错误

    Debug fails with 8600GT

    Hi, I am getting assertions in my debug builds when using Geforce 8600GT. Everything was working fine when using 6600GT...
    I can only debug using the release version now
    I'm using Gamebryo 2.2.2.0 (24th Jan 2007)
    Graphics driver: 6.14.11.102 (I tried 158.22 too)
    DX SDK: Aug2006
    The following was from Debug version of Tutorial01.
    The assert message:
    Assertion failed!
    Program: ...
    File: m:\gamebryo2.2rc_win32_src_build...\nidx9s...esc.cpp
    Line: 88
    Expression: eType == D3DMULTISAMPLE_NONMASKABLE || uiResult == 1
    The call stack:
           msvcr80d.dll!_wassert(const wchar_t * expr=0x007b14e0, const wchar_t * filename=0x007b1560, unsigned int lineno=88)  Line 384      C
    >      Tutorial01.exe!NiDX9DeviceDesc:DisplayFormatInfo:DisplayFormatInfo()  + 0x1b3 bytes      
           Tutorial01.exe!NiDX9DeviceDesc::NiDX9DeviceDesc()  + 0x22d bytes      
           Tutorial01.exe!NiDX9AdapterDesc::NiDX9AdapterDesc()  + 0x272 bytes      
           Tutorial01.exe!NiDX9SystemDesc::FillAdapterInformation()  + 0xe1 bytes      
           Tutorial01.exe!NiDX9SystemDesc::NiDX9SystemDesc()  + 0x4b bytes      
           Tutorial01.exe!NiDX9Renderer::GetSystemDesc()  + 0xcf bytes      
           Tutorial01.exe!NiDX9Renderer::Initialize()  + 0xda bytes      
           Tutorial01.exe!NiDX9Renderer::Create()  + 0x113 bytes      
           Tutorial01.exe!Renderers::CreateRenderer()  Line 35 + 0x33 bytes      C++
           Tutorial01.exe!NiApplication::Initialize()  + 0x59 bytes      
           Tutorial01.exe!_WinMain@16()  + 0x2e0 bytes      
           Tutorial01.exe!__tmainCRTStartup()  Line 578 + 0x35 bytes      C
           Tutorial01.exe!WinMainCRTStartup()  Line 403      C
           kernel32.dll!7c816fd7()      
           [Frames below may be incorrect and/or missing, no symbols loaded for kernel32.dll]      


    The GeForce 8*00 series exposes multisample capabilities in a different way than other graphics cards, which causes older versions of Gamebryo to hit this assertion. The bug was fixed in Gamebryo 2.3.

    So are we saying that any applications built on Gamebryo 2.2 can't be debugged if using an 8000 series card? I have been getting the same error and finally found this post. We probably won't be getting Gamebryo 2.3 for another 1 or 2 months so I'd be grateful if anyone knows of a workaround for this. I'd really like to be able to debug my software, but still run the new graphics card. Any possible workarounds?

    If you have a copy of Gamebryo 2.3 lying around, you could move the NiDX9SystemDesc.h, .inl, and .cpp files from 2.3 into your copy of 2.2. You'll need to change any instances of "NIASSERT" to "assert", and there may be other minor changes necessary as well, but fixing this bug was the only significant change to occur in those files between those two branches.

    May 30

    GameBryo 笔记

    很久以前就申请了Google 笔记本,还一直没有使用。现在先用它来记录 GameBryo 一些知识点。而且笔记本是可以多人一起修改的,谁想修改给我留言哈。地址:
    http://www.google.com/notebook/public/12209111529083700989/BDRCSIwoQ3dOhhNQi?hl=__MSG_locale__

    May 29

    GameBryo2.2 SceneDesigner 不能启动

    装了gb 2.2 之后又安装了 gb2.3 而启动2.3 的场景编辑器之后 2.2 就不能启动了。因为这两个程序用的配置文件放入了相同的目录,并且有相同的名字,这样2.2 加载时发现版本不对,就会启动失败。
    删除文件
    C:\Documents and Settings\你的用户名\Local Settings\Application Data\Emergent Game Technologies\Gamebryo Scene Designer\tool.settings
    C:\Documents and Settings\All users\Local Settings\Application Data\Emergent Game Technologies\Gamebryo Scene Designer\tool.settings

    开始还以为是.net 框架打补丁影响了。一通狂卸载、最后vc2005、office 2007 都卸掉了,熬到了3点。看来得补习c#了。如果能熟练应用它,调试一下就可以更早发现这个问题

    May 21

    检测调试器是否存在

    BOOL IsDebuggerPresent()
    {
        _asm
        {
            mov        eax, dword ptr fs:[0x18]
            mov        eax,[eax+0x30]
            movzx    eax,byte ptr[eax+0x2]
        }
    }