﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>C++博客-永远也不完美的程序-文章分类-图形编程</title><link>http://www.cppblog.com/liangairan/category/7795.html</link><description>不断学习，不断实践，不断的重构……</description><language>zh-cn</language><lastBuildDate>Fri, 04 Mar 2011 13:42:16 GMT</lastBuildDate><pubDate>Fri, 04 Mar 2011 13:42:16 GMT</pubDate><ttl>60</ttl><item><title>（转）多线程渲染(Multithreaded- rendering)3D引擎实例分析 : FlagshipEngine</title><link>http://www.cppblog.com/liangairan/articles/141003.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Wed, 02 Mar 2011 10:43:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/141003.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/141003.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/141003.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/141003.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/141003.html</trackback:ping><description><![CDATA[<p><strong><span style="font-size: large;">1. 开篇：关于FlagshipEngine<br>
</span>
</strong>
<br></p>
<p align="left"><span style="font-size: small;">首先要感谢旗舰工作室的倒掉，让我可以名正言顺的使用FlagshipEngine这个
名字，话说这个实验引擎，当初只是我的大学毕业设计，工作之后实在太忙，写写停停，进度缓慢，到今天也只能算V0.001，其特性主要有以下三点：<br>
</span>
</p>
<p align="left"><br>
<span style="font-size: small;"><strong>一、多线程<br>
</strong>
</span>
<span style="font-size: small;">&nbsp; &nbsp;</span>
</p>
<p><span style="font-size: small;">多核<span class="t_tag">CPU</span>
早已普及，但3D引擎却迟迟不能享受到其好处，还
仅仅停留在资源异步加载，音频独立线程等不疼不痒的应用，就在一年前吧，公司的牛人们为了优化骨骼动画和粒子计算煞费苦心，这两样计算，特别是在无法控制
同屏资源的网络游戏中，对CPU资源的占用非常可观，自然也拖累了游戏帧数，于是我便有了将逻辑计算与渲染分离的想法。<br>
&nbsp; &nbsp;&nbsp; &nbsp; </span></p>
<p align="left"><span style="font-size: small;">FlagshipEngine实现了一套没有线程同步的双线程结构，可以做到骨骼动画、
粒子计算、光源<span class="t_tag">移动</span>
等逻辑计算分离到一个单独的线程运行，完全不影响渲
染帧数。</span>
</p>
<p><br>
<br>
<span style="font-size: small;"><strong>二、shader渲染器<br>
&nbsp; &nbsp;&nbsp; &nbsp; <br>
</strong>
</span>
</p>
<p align="left"><span style="font-size: small;">DX10已经放弃了固定管线，那么我们也没理由再留恋
它，完全基于shader的渲染器实现起来更加清晰简洁，并且易于扩展，目前FlagshipEngine已经实现了DX9和DX10两个渲染器，可以方
便的添加特效。<br>
</span>
</p>
<p align="left"><br>
<span style="font-size: small;"><strong>三、统一剪裁<br>
&nbsp; &nbsp;&nbsp; &nbsp; </strong>
</span>
</p>
<p align="left"><span style="font-size: small;">场景组织和剪裁永远是3D引擎
的核心功能，视锥、四叉树、<span class="t_tag">BSP</span>
、Portal如何选择，如何统一是个难题，我的
做法是将所有的剪裁都抽象成剪裁面，并用压栈和出栈的方式，递归的对场景进行剪裁，另外我们还可以对大块实体绑定简单模型的遮挡体，使用边缘检测算法生成
遮挡剪裁面，实现遮挡剪裁。<br>
&nbsp; &nbsp;&nbsp; &nbsp;</span>
</p>
<p align="left"><span style="font-size: small;">这套机制还没有经过严格的测试，有待进一步的验证。</span>
</p>
<p><br>
<br>
<br>
<br>
<br>
<br>
<br>
<span style="font-size: small;"><strong><span style="font-size: large;">2. 3D引擎多线程：资源异步加载 <br>
</span>
</strong>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br>
资源异步加载恐怕是3D引擎中应用最为广泛的多线程技术了，特别是在无缝地图的网络游戏中，尤为重要，公司3D引擎的资源加载部分采用了硬盘-&gt;内
存-&gt;显存两级加载的模式，超时卸载也分两级，这样虽然实际效果不错，但代码非常繁琐，在FlagshipEngine中，我设法将其进行了一定程
度的简化。<br>
<br>
首先我们需要定义一个Resource基类，它大致上是这样的：<br>
<br>
class _DLL_Export Resource : public Base<br>
&nbsp; &nbsp; {<br>
&nbsp; &nbsp; public:<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;Resource();<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;virtual ~Resource();<br>
<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;// 是否过期<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;bool&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; IsOutOfDate();<br>
&nbsp; &nbsp;&nbsp; &nbsp; <br>
&nbsp; &nbsp; public:<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;// 是否就绪<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;virtual bool&nbsp; &nbsp; IsReady();<br>
<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;// 读取资源<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;virtual bool&nbsp; &nbsp; Load();<br>
<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;// 释放资源<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;virtual bool&nbsp; &nbsp; Release();<br>
<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;// 缓存资源<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;virtual bool&nbsp; &nbsp; <span class="t_tag">Cache</span>
();<br>
<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;// 释放缓存<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;virtual void&nbsp; &nbsp; UnCache();<br>
<br>
&nbsp; &nbsp; protected:<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;// 加载标记<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;bool&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;m_bLoad;<br>
<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;// 完成标记 <br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;bool&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;m_bReady;<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br>
&nbsp; &nbsp; private:<br>
<br>
&nbsp; &nbsp; };&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br>
<br>
在实际游戏中，加载资源的范围大于视野，当摄像机移动到单元格边缘(必须有一定的缓冲区)，就应将新的单元格中的对象加入到资源加载队列中，唤醒资源加载
线程调用Load接口进行加载，完成后将该资源的加载标记设为true。而通过可视剪裁所得到的最终可视实体，则需要调用Cache接口构建图像API所
需对象，当Load和Cache都完成后IsReady才会返回true，这时该资源才能开始被渲染。<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br>
卸载方面，在加载新的单元同时，卸载身后旧的单元，对单元内所有资源调用Release，Load/Release带有引用计数，仍被引用的资源不会被卸
载。当某一资源长时间没有被看见，则超时，调用UnCache释放VertexBuffer等资源。<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br>
为了实现超时卸载功能，我们需要一个ResourceManager类，每帧检查几个已Cache的资源，看起是否超时，另外也需对已加载的资源进行分类
管理，注册其资源别名（可以为其文件名），提供查找资源的接口。<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br>
另外为了方便使用，我们需要一个模板句柄类ResHandle&lt;T&gt;，设置该资源的别名，其内部调用ResourceManange的查找方
法，看此资源是否已存在，如不存在则new一个新的，GetImpliment则返回该资源对象，之后可以将该资源添加到实体中，而无需关心其是否已被加
载，代码如下：<br>
<br>
template &lt;class T&gt;<br>
&nbsp; &nbsp; class _DLL_Export ResHandle<br>
&nbsp; &nbsp; {<br>
&nbsp; &nbsp; public:<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;ResHandle() { m_pResource = NULL; }<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;virtual ~ResHandle() {}<br>
<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;// 设置资源路径<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;void&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;SetPath( wstring szPath )<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;{<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;Resource * pResource =
ResourceManager::GetSingleton()-&gt;GetResource( Key( szPath ) );<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;if ( pResource != NULL )<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;{<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; m_pResource = (T *) pResource;<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;}<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;else<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;{<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; m_pResource = new T;<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; m_pResource-&gt;SetPath( szPath );<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; ResourceManager::GetSingleton()-&gt;AddResource(
m_pResource );<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;}<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;}<br>
<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;// 模板实体类指针<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;T *&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; GetImpliment() { return (T *) m_pResource; }<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;T *&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; operator-&gt; () { return (T *) m_pResource; }<br>
<br>
&nbsp; &nbsp; protected:<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;// 模板实体类指针<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;Resource *&nbsp; &nbsp;&nbsp; &nbsp;m_pResource;<br>
<br>
&nbsp; &nbsp; private:</span>
<br>
<span style="font-size: small;"><br>
&nbsp; &nbsp; };</span>
<br>
<br>
<br>
<span style="font-size: small;"><br>
<br>
</span>
<br>
<span style="font-size: small;"><br>
<br>
<br>
<br>
<br>
<br>
<strong><span style="font-size: large;">3.&nbsp;&nbsp;3D引擎多线程：渲染与逻辑分离 </span>
</strong>
&nbsp;&nbsp;<br>
&nbsp; &nbsp;&nbsp; &nbsp;<br>
目前的3D引擎的渲染帧和逻辑帧都是在一个线程上运行的，在网络游戏中大量玩家聚集，繁重的骨骼动画计算和粒子计算极大的拖累了渲染帧数，有两种有效措
施：</span>
<br>
<br>
<span style="font-size: small;">1、控制同屏显示人数，但玩家体验不好 </span>
<br>
<br>
<span style="font-size: small;">2、帧数低于某值时减少动画Tick频率，但带来的问题是动画不连贯。<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br>
如果考虑使用多线程优化，最容易想到的就是采用平行分解模式，将骨骼动画计算和粒子计算写成两个for循环，然后用OpenMP将其多线程化，但事实上这
样并不会提高多少效率，这两者计算仍然要阻滞渲染帧，线程的创建也有一定的消耗。于是我想到了一种极端的解决方案，采用任务分解模式，将渲染和逻辑完全分
离到两个线程去，互不影响，当然这样线程同步会是大问题，毕竟线程的数量和BUG的数量是成正比的。<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br>
我们首先来分析下这两个线程分别需要做什么工作，需要那些数据。渲染线程需要获取实体的位置、材质等信息，并交给<span class="t_tag">GPU</span>
渲染，逻辑线程需要更新实体的位置、材质、骨骼动
画等数据，很显然一个写入一个读取，这为我们实现一个没有线程同步的多线程3D渲染系统提供了可能。<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br>
为了让读取和写入不需要Lock，我们需要为每一份数据设计一个带有冗余缓存的结构，读取线程读取的是上次写入完成的副本，而写入线程则向新的副本写入数
据，并在完成后置上最新标记，置标记的操作为原子操作即可。以Vector为例，这个结构大致是这样的：<br>
<br>
struct VectorData <br>
{<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;Vector4f&nbsp; &nbsp; m_pVector[DATACENTER_CACHE];<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;int&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;m_iIndex;<br>
<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;VectorData()<br>
&nbsp; &nbsp; {<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;memset( m_pVector, 0, DATACENTER_CACHE * sizeof(Vector4f) );<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;m_iIndex = 0;<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;}<br>
<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;void&nbsp; &nbsp; Write( Vector4f&amp; rVector )<br>
&nbsp; &nbsp; {<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;int iNewIndex = m_iIndex == DATACENTER_CACHE - 1 ? 0 :
m_iIndex + 1;<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;m_pVector[iNewIndex] = rVector;<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;m_iIndex = iNewIndex;<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;}<br>
<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;Vector4f&amp;&nbsp; &nbsp; Read()<br>
&nbsp;&nbsp;{<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;return m_pVector[m_iIndex];<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;}<br>
};<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br>
当然我们可以用模板来写这个结构，让其适用于int，float，matrix等多种数据类型，余下的工作就简单了，将所有有共享数据的类的成员变量都定
义为以上这种数据类型，例如我们可以定义：<br>
<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;SharedData&lt;Matrix4f&gt;&nbsp;&nbsp;m_matWorld;<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br>
在渲染线程中调用pDevice-&gt;SetWorldMatrix( m_matWorld.Read() );<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br>
在逻辑线程中调用m_matWorld.Write( matNewWorld );<br>
<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br>
需要注意的是，这种方案并非绝对健壮，当渲染线程极慢且逻辑线程极快的情况下，有可能写入了超过了DATACENTER_CACHE次，而读取却尚未完
成，那么数据就乱套了，当然真要出现了这种情况，游戏早已经是没法玩了，我测试的结果是渲染帧小于1帧，逻辑帧大于10000帧，尚未出现问题。<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br>
FlagshipEngine采用了这一设想，实际Demo测试结果是，计算25个角色的骨骼动画，从静止到开始奔跑，单线程的情况下，帧数下降了
20%～30%，而使用多线程的情况下，帧数完全没有变化！<br>
<br>
<br>
<br>
<br>
</span>
<br>
<br>
<span style="font-size: small;"><br>
<br>
<br>
<br>
<strong><span style="font-size: large;">4.&nbsp;&nbsp;3D引擎多线程：框架 </span>
</strong>
&nbsp;&nbsp;<br>
&nbsp; &nbsp;&nbsp; &nbsp;<br>
现在我们已经有了三个可独立工作的线程：资源加载线程、逻辑线程、渲染线程，下一步我们需要决定它们如何在实际的项目中相互配合，也就是所谓的应用程序框
架了，该框架需要解决以下两个问题<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br>
首先，资源读取线程可以简单设计为一个循环等待的线程结构，每隔一段时间检查加载队列中是否有内容，如果有则进行加载工作，如果没有则继续等待一段时间。
这种方式虽然简单清晰，但却存在问题，如果等待时间设得过长，则加载会产生延迟，如果设得过短，则该线程被唤醒的次数过于频繁，会耗费很多不必要的CPU
时间。<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br>
然后，主线程是逻辑线程还是渲染线程？因为逻辑线程需要处理键盘鼠标等输入设备的消息，所以我起初将逻辑线程设为主线程，而渲染线程另外创建，但实际发
现，帧数很不正常，估计与WM_PAINT消息有关，有待进一步验证。于是掉转过来，帧数正常了，但带来了一个新的问题，逻辑线程如何处理键盘鼠标消息？<br>
&nbsp; &nbsp;&nbsp; &nbsp;<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br>
对于第一个问题，有两种解决方案：<br>
&nbsp; &nbsp;&nbsp; &nbsp; <br>
第一，我们可以创建一个Event，资源读取线程使用WaitForSingleObject等待着个Event，当渲染线程向加载队列添加新的需加载的
资源后，将这个Event设为Signal，将资源读取线程唤醒，为了安全，我们仍需要在渲染线程向加载队列添加元素，以及资源加载线程从加载队列读取元
素时对操作过程加锁。<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br>
第二，使用在渲染线程调用PostThreadMessage，将资源加载的请求以消息的形式发送到资源价值线程，并在wParam中传递该资源对象的指
针，资源加载线程调用WaitMessage进行等待，收到消息后即被唤醒，这种解决方案完全不需要加锁。<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br>
对于第二个问题，我们同样可以用PostThreadMessage来解决，在主线程的WndProc中，将逻辑线程需要处理的消息发送出去，逻辑线程收
到后进行相关处理。<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br>
需要注意的是，我们必须搞清楚线程是在何时创建消息队列的，微软如是说：<br>
<br>
The thread to which the message is posted must have created a message
queue, or else the call to PostThreadMessage fails. Use one of the
following methods to handle this situation. <br>
<br>
Call PostThreadMessage. If it fails, call the Sleep function and call
PostThreadMessage again. Repeat until PostThreadMessage succeeds. <br>
Create an event object, then create the thread. Use the
WaitForSingleObject function to wait for the event to be set to the
signaled state before calling PostThreadMessage. In the thread to which
the message will be posted, call PeekMessage as shown here to force the
system to create the message queue. <br>
PeekMessage(&amp;msg, NULL, WM_USER, WM_USER, PM_NOREMOVE)<br>
Set the event, to indicate that the thread is ready to receive posted
messages. <br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br>
看来，我们只需要在线程初始化时调一句PeekMessage(&amp;msg, NULL, WM_USER, WM_USER,
PM_NOREMOVE)就可以了，然后在主线程中如此这般：<br>
<br>
switch ( uMsg )<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;{<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;case WM_PAINT:<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;{<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; hdc = BeginPaint(hWnd, &amp;ps);<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; EndPaint(hWnd, &amp;ps);<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;}<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;break;<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;case WM_DESTROY:<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;{<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; m_pLogic-&gt;StopThread();<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; WaitForSingleObject( m_pLogic-&gt;GetThreadHandle(),
INFINITE );<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; PostQuitMessage(0);<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;}<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;break;<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;default:<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;{<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; if ( IsLogicMsg( uMsg ) )<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; {<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;PostThreadMessage( m_pLogic-&gt;GetThreadID(), uMsg,
wParam, lParam );<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; }<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; else<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; {<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return DefWindowProc( hWnd, uMsg, wParam, lParam );<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; }<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;}<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;break;<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;}<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br>
<br>
在逻辑线程中这般如此：<br>
<br>
MSG msg;<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;while ( m_bRunning )<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;{<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;if ( PeekMessage( &amp;msg, NULL, 0, 0, PM_NOREMOVE ) )<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;{<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; if ( ! GetMessageW( &amp;msg, NULL, 0, 0 ) )<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; {<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return (int) msg.wParam;<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; }<br>
<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp; MessageProc( msg.message, msg.wParam, msg.lParam );<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;}<br>
<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;LogicTick();<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;}&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br>
<br>
完成！ <br>
<br>
<br>
<br>
<br>
</span>
<br>
<br>
<br>
<span style="font-size: small;"><br>
<br>
<br>
<span style="font-size: large;"><strong>5.&nbsp;&nbsp;3D引擎多线程：逻辑操作 </strong>
</span>
&nbsp; &nbsp; <br>
&nbsp; &nbsp; <br>
在实际游戏中，逻辑线程需要对渲染对象做许多操作，比如添加与删除，改变渲染对象的属性等等，而由于在先前的设计中，逻辑线程与渲染线程相互独立，如果只
是改变某一共享数据，没有问题，但如果操作影响到了场景结构，例如实体的添加与删除，则必须进行线程同步，这又违背了FlagshipEngine的设计
初衷——避免繁重的逻辑计算影响渲染速度。<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br>
解决办法其实在上一篇中已经提到了，仍然是利用天然的同步机制——Windows消息，添加实体时，逻辑线程只是new了一个Entity对象，设置这个
对象的初始共享数据，比如位置信息，同时向渲染线程发送一条WM_ADDENTITY的自定义消息，将Entity指针作为wParam传递。渲染线程接
受到消息后调用Entity的UpdateScene方法，更新Entity在场景树中的位置，并加载资源。<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br>
删除也是一样，逻辑线程向渲染线程发送WM_DELETEENTITY消息，并不再使用该Entity指针，渲染对象则处理改消息，将此Entity从场
景中删除并卸载资源。<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br>
这里有一个非常危险的情况，前面一篇提到，资源加载也是通过消息传递实现的，同样是传递的资源指针，如果逻辑线程添加了一个Entity，还没加载就删掉
了它，则资源加载线程会拿到一个过期指针，一切就结束了。。。<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br>
解决这一问题，最稳妥的方法是消息的wParam并不传递指针，而是传递该Entity或资源的唯一ID，这样的话即使ID过期，也可轻松忽略掉这条消
息，坏处是每次消息处理都的从全局的<span class="t_tag">map</span>
里检查是否存在此ID对应的Entity或资源，
这可是笔不小的开销。<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;<br>
第二种方案，我们仍然传递指针，只是在接受到WM_DELETEENTITY消息时，检查该Entity是否已经加载完成，如果没有完成，则重新将此消息
加入消息队列，下个渲染帧再次判断。<br>
&nbsp; &nbsp;&nbsp; &nbsp; <br>
FlagshipEngine的多线程设计大致就是如此了。</span>
</p>
<p><span style="font-size: small;"><strong><span style="font-size: large;">6.&nbsp;&nbsp;<span class="t_tag">DX11</span>
与多线程渲染 </span>
</strong>
<br>
<br>
前几天突然想起新的DXSDK应该早出了，去微软网站一看，好么。。。2008
Dec版早出了，这次火星了，下载完后居然发现包坏掉了，于是重下。。。还是坏掉！第三次也不行，折腾了一下午，最后放弃了，还好貌似只有Sample的
最后一点点没解压开，没什么大碍。<br>
&nbsp; &nbsp; <br>
本来只是以为自己只是火星了而已，装好一看，完。。。彻底冥王星了，DX11的Preview版出了！真是又激动又懊悔，粗略看了看，新特性真是太令人激
动了，主要有以下几点：<br>
<br>
一、SM5.0 从类C变成类C++了，有类有继承有虚函数，太夸张了。。。<br>
<br>
二、支持Shader<span class="t_tag">动态</span>
Link，DX9里面就有个
FragmentLinker，不太好用，DX10直接取消了，这次变本加厉的又回来了！<br>
<br>
三、渲染的多线程支持，我重点来谈谈这个<br>
&nbsp; &nbsp;<br>
<span style="font-size: 27pt;"><br>
</span>
<span style="font-family: Arial;"><span style="font-size: 12pt;">DX11提供了一个新的接口：</span>
</span>
<br>
<span style="font-family: Arial;"><span style="font-size: 12pt;">ID3D11DeviceContext，取
代了以前Device接口所有与渲染相关的功能，有两个类型：immediate和deferred，前者和现在的效果一样，收到渲染指令就立即执行，而
后者则会将命令缓存起来，由用户决定何时执行</span>
</span>
。<br>
&nbsp; &nbsp; <br>
在例子MultithreadedRendering11中，渲染了三面带反射的镜子和一个人物模型，Sample创建了四个Context，三个
deferred用于镜子反射表面的渲染，一个immediate用于最终场景，Sample创建了三个线程，渲染帧开始时，首先并行的执行三个镜子反射
的渲染，完成后，在主线程顺序执行三个Context，然后用immediate的Context渲染最终场景。<br>
&nbsp; &nbsp; <br>
由这个例子，我们来展望一下美好的未来：所有的渲染表面都可以并行执行，比如水面、镜子、甚至<span class="t_tag">Shadow</span>
Map，并行的进行场景剪裁，使得多核
CPU的使用更有效率。<br>
&nbsp; &nbsp; <br>
DX11预计在今年年底推出，于Windows7捆绑，到那时，四核CPU应该已经普及了吧。。。<br>
<br>
<br>
<br>
<br>
<br>
</span>
<br>
<br>
<span style="font-size: small;"><br>
<br>
<br>
<br>
<strong><span style="font-size: large;">7.&nbsp;&nbsp;多Pass渲染体系与多线程渲染的矛盾 </span>
</strong>
&nbsp; &nbsp; <br>
<br>
最近为了实现多光源和多阴影的渲染，把渲染系统改成了多Pass的，对每一个可见光源进行一次光照、ShadowMap和最终阴影的渲染，虽然这样等于是
把整个场景重复渲染了很多次，但为了实现灵活的实时光照系统，这似乎是唯一的办法了。<br>
&nbsp; &nbsp; <br>
但实践后发现，阴影和光照会随着骨骼动画的播放而闪烁，甚至镜头的移动也会造成闪烁，究其原因，还是逻辑线程和渲染线程的同步问题，由于对场景内的同一个
物体渲染了多次，而逻辑线程又在不停的更新摄像机和骨骼动画数据，导致了两Pass渲染取到的数据很可能不一致，造成了光照和阴影的闪烁。<br>
&nbsp; &nbsp; <br>
所以共享数据结构必须做一些修改，在多Pass渲染开始前进行一次备份，渲染中只取备份数据，这样就保证了多次渲染的数据一致性了<br>
<br>
&nbsp; &nbsp; <br>
也就是加这么两个简单的set和get方法，在渲染相关数据读取时调用get，逻辑相关时调用read<br>
<br>
template &lt;class T&gt;<br>
struct SharedData<br>
&nbsp; &nbsp;{<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;T&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;m_pData[DATACENTER_CACHE];<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;T&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;m_kCloneData;<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;int&nbsp; &nbsp;&nbsp; &nbsp;m_iIndex;<br>
<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;SharedData()<br>
&nbsp; &nbsp;&nbsp; &nbsp;{<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;ZeroMemory( m_pData, DATACENTER_CACHE * sizeof(T) );<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;m_iIndex = 0;<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;}<br>
<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;void&nbsp; &nbsp; Write( T&amp; rData )<br>
&nbsp; &nbsp;&nbsp; &nbsp;{<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;int iNewIndex = m_iIndex == DATACENTER_CACHE - 1 ? 0 :
m_iIndex + 1;<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;m_pData[iNewIndex] = rData;<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;m_iIndex = iNewIndex;<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;}<br>
<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;T&amp;&nbsp; &nbsp; Read()<br>
&nbsp; &nbsp;&nbsp; &nbsp;{<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return m_pVector[m_iIndex];<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;}<br>
<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;void&nbsp; &nbsp; Set()<br>
&nbsp; &nbsp;&nbsp; &nbsp;{<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;m_kCloneData = Read();<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;}<br>
<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;T&amp;&nbsp; &nbsp;&nbsp; &nbsp;Get()<br>
&nbsp; &nbsp;&nbsp; &nbsp;{<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;return m_kCloneData;<br>
&nbsp; &nbsp;&nbsp; &nbsp;&nbsp;&nbsp;}<br>
&nbsp; &nbsp; };<br>
<br>
<br>
<br>
<br>
<br>
</span>
<br>
<span style="font-size: small;"><br>
<br>
<br>
<br>
<strong><span style="font-size: large;">8.&nbsp;&nbsp;几种多线程3D引擎架构的比较 </span>
</strong>
&nbsp; &nbsp; <br>
&nbsp;&nbsp;<br>
首先我们得明确3D引擎使用多线程的目的所在：<br>
1、在CPU上进行的逻辑计算（比如骨骼动画粒子发射等）不影响渲染速度<br>
2、较差的GPU渲染速度的低下不影响逻辑速度<br>
<br>
&nbsp; &nbsp;&nbsp; &nbsp;<br>
第一个目标已经很明确了，我来解释下需要达到第二个目标的原因：许多动作游戏的逻辑判定是基于帧的，所以在渲染较慢的情况下，逻辑不能跳帧，而仍然需要严
格执行才能保证游戏逻辑的正确性，这就导致了游戏速度的放慢，而实际上个人认为渲染保持15帧以上就已经可以正常进行游戏了。<br>
&nbsp; &nbsp;&nbsp; &nbsp;<br>
在较差的GPU上跑《鬼泣4》《刺客信条》《波斯王子4》简直就像是慢镜头一样，完全没法玩。而实际上CPU跑满帧是没有问题的，如果能把逻辑帧和渲染帧
彻底分离，即使渲染帧达不到要求，但CPU仍能正确的执行游戏逻辑，就可以解决动作游戏对GPU要求过高的问题。<br>
<br>
&nbsp; &nbsp;&nbsp; &nbsp;<br>
我们先来看多线程Ogre的两种架构，第一种是middle-level multithread</span>
</p>
<p><span style="font-size: small;"><img  src="http://hi.csdn.net/attachment/201006/13/0_12764237400pjz.gif" alt="">
</span>
</p>
<p><span style="font-size: small;">如上图所示，每个需渲染的实体被复制成了两份，主线程和渲染线程交替更新和渲染同一个实体的两个备份，并在一帧结束时同
步，这种解决方案达到了第一个目标而并没有达到第二个目标，同时两份实体的维护也相对复杂，并且没法为更多核数的CPU进行扩展优化。<br>
&nbsp; &nbsp;&nbsp; &nbsp;<br>
第二种Ogre多线程的方法是 low-level multithread</span>
</p>
<p><span style="font-size: small;"><img  src="http://hi.csdn.net/attachment/201006/13/0_1276423805lToQ.gif" alt="">
</span>
</p>
<p><span style="font-size: small;">&nbsp; &nbsp;&nbsp;&nbsp; <br>
如图，将D3D对象复制两份，同样是在帧结束时同步并交换，和上面的优缺点类似。两种多线程Ogre的解决方案都是在引擎层完成的，对上层应用透明，对于
用户而言无需考虑多线程细节，这点是非常不错的。<br>
<br>
&nbsp; &nbsp;&nbsp; &nbsp;<br>
接下来我们来看SIGGRAPH2008上，id soft提出的多线程3D引擎的方案</span>
</p>
<p><span style="font-size: small;"><img  src="http://hi.csdn.net/attachment/201006/13/0_1276423865an9z.gif" alt="">
<br>
</span>
</p>
<p><span style="font-size: small;"><br>
</span>
</p>
<p><span style="font-size: small;">这里是已PS3的引擎结构为例的，与PC有较大的差别，其中SPU是Cell芯片的8个协处理器，拥有强大的并行能
力，id的解决方案在SPU上进行了诸如骨骼动画、形变动画、顶点和索引缓存的压缩、Progressive
Mesh的计算等诸多内容，同时与PPU上的物理计算RSX上的渲染工作交错进行，最大化的利用了PS3的硬件结构，最终的游戏产品《Rage》很快就会
面世了！</span>
</p>
<p><span style="font-size: small;">最后是我的解决方案</span>
</p>
<p><span style="font-size: small;"><img  src="http://hi.csdn.net/attachment/201006/13/0_1276423977VS8Y.gif" alt="">
<br>
</span>
</p>
<p><span style="font-size: small;"><br>
</span>
</p>
<p><span style="font-size: small;">特点是逻辑完全分离，无需同步，虽然成功的达到了文章开始提出的两个目标，但对于引擎的使用者必须考虑多线程的诸多问题，
各种计算需放在哪个线程，如何在两个线程间交互，都需要深入思考，所以要应用到实际的游戏制作，恐怕还有很长的一段路要走。<br>
&nbsp; &nbsp;&nbsp; &nbsp;<br>
结合目前的架构和上面看到的几种多线程架构，同时也为了迎接DX11的到来，我准备将我的方案进一步改进成如下所示</span>
</p>
<p><img  src="http://hi.csdn.net/attachment/201006/13/0_1276424023v0VH.gif" alt=""></p>
<p><span style="font-size: small;">场景剪裁与提交渲染交替进行，并在渲染帧末进行一次同步，而多个渲染表面的场景剪裁可再并行执行。<br>
&nbsp; &nbsp;&nbsp; &nbsp;<br>
图片多，文字少，需更详细<span class="t_tag">资料</span>
请自行google，本文就此结束！</span>
</p>
<span style="font-size: small;">from&nbsp; http://www.cppblog.com/flagship/category/9250.html</span><img src ="http://www.cppblog.com/liangairan/aggbug/141003.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2011-03-02 18:43 <a href="http://www.cppblog.com/liangairan/articles/141003.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title> Dx 10 与 Dx 9 的一些技术区别(转)</title><link>http://www.cppblog.com/liangairan/articles/139893.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Fri, 11 Feb 2011 06:47:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/139893.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/139893.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/139893.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/139893.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/139893.html</trackback:ping><description><![CDATA[<p><strong>本文件来自：http://blog.csdn.net/codeboycjy/archive/2009/11/29/4900467.aspx<br></strong></p>
<p><strong>引言：</strong> <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
DX10发布已经有一段时间了，网上可以查到很多关于Dx9与10的区别的文章。但是大多数都是从玩家角度考虑的。只是展示一下Dx9和Dx10分别渲染
出的图片，并且Dx9所渲染的图片经常会缩水很多，目的就是为了展示出Dx10的强大。给大多数人的理解就是，DX10能做出比Dx9好很多的画面。我并
不否认Dx10比Dx9优化了很多，但是随便展示出两张图片进行对比，其实意义也不是特别的大。因为我们不知道帧率的对比。而且虽然很多新的技术在Dx9
里面没有，但是还是有一部分可以用其他方法模拟出来的，只是效率上有所下降。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 本文简单介绍了DX10和DX9的一些技术上的区别。从程序员的角度看DX10比DX9优势的地方。适合对于Dx有一定了解的朋友。</p>
<p><strong>正文：</strong> <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 在Windows
98的年代里，GDI和DirectX是完全独立的两个接口。GDI（Graphical Device
Interface）是专门用于二维图形显示的接口，封装了一些基本的功能，效率相对DirectX来说要低一些。而DirectX是专门用于游戏开发领
域的，它允许用户通过这个接口直接与硬件交互。但是这两个接口之间的交互是非常受限制的，主要原因就是由于底层的驱动架构：</p>
<p><a  href="http://p.blog.csdn.net/images/p_blog_csdn_net/codeboycjy/624615/o_%E6%96%B0%E5%BB%BA%E4%BD%8D%E5%9B%BE%E5%9B%BE%E5%83%8F_2.jpg"><img  src="http://p.blog.csdn.net/images/p_blog_csdn_net/codeboycjy/624615/o_%E6%96%B0%E5%BB%BA%E4%BD%8D%E5%9B%BE%E5%9B%BE%E5%83%8F_thumb.jpg" style="border-width: 0px; display: inline;" title="新建位图图像" alt="新建位图图像" width="364" border="0" height="223"></a> </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 我们看到，在这个驱动模型里面，底层的硬件驱动都是独立的两部分。直到Windows Vista的发布，微软更新了底层的驱动模型</p>
<p><a  href="http://p.blog.csdn.net/images/p_blog_csdn_net/codeboycjy/624615/o_%E6%96%B0%E5%BB%BA%E4%BD%8D%E5%9B%BE%E5%9B%BE%E5%83%8F%20%282%29_2.jpg"><img  src="http://p.blog.csdn.net/images/p_blog_csdn_net/codeboycjy/624615/o_%E6%96%B0%E5%BB%BA%E4%BD%8D%E5%9B%BE%E5%9B%BE%E5%83%8F%20%282%29_thumb.jpg" style="border-width: 0px; display: inline;" title="新建位图图像 (2)" alt="新建位图图像 (2)" width="365" border="0" height="224"></a> </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 在这个新的驱动架构下，所有的图形接口都是基于DirectX
Runtime的。这就为GDI和DirectX交互提供了可能，这也是Vista能够提供更好的用户界面体验的一个重要原因。DirectX9为了向下
兼容，所以不得不做一些妥协的工作。例如当VRAM的占用超出了一定界限的时候，Dx9会发出error，而这并不是因为驱动无法提供更多的VRAM。事
实上，底层驱动完全可以提供几乎无限的VRAM，但是为了向下兼容其他比较旧的显卡，因为这些卡在这里面可能会出现问题，所以Dx9还会出现Error。
由于这种向下兼容的被迫妥协，不免使得Dx9在Vista下的表现不能完全利用底层的优势。对于Dx熟悉的朋友可能会注意到，在Dx9与Dx10之间，有
一版Dx
9Ex。这一个版本的Dx是不能在XP下运行的，因为它更多的利用了新的驱动模型的优势，需要新的驱动模型才可以支持。而XP下的驱动模型还是上面的模
型。Dx10是完全建立在新的驱动模型下面的全新的接口，它在Vista下可以完全发挥底层设计的优势。但是也同样需要WDDM的支持，这就是DX10不
能在XP下运行的最主要的原因了。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 简单从底层介绍了一下Dx9与Dx10的区别（希望了解更深入的朋友，可以查看DX SDK里面的Graphics APIs in Windows那篇文章）。那么下面我来介绍一下从编程接口角度看，DX10为我们带来了一些什么样的变化：</p>
<p><strong>完全的可编程管线：</strong> <br>&nbsp;&nbsp;&nbsp;&nbsp;
在DX10里面，是没有固定管线的。如果程序员想用这个接口渲染图形的话，就必须自己写Shader脚本来实现图元的现实。事实上，在大多数次时代的三维
游戏中，几乎很少有单纯的固定管线渲染的图元了。因为Phong模型的表现力毕竟还很有限，只通过diffuse,
specular等一些简单的属性描述出的东西很难让人信服。可能唯一大量需要固定管线的部分就是二维图形UI部分了。如果UI不是特别复杂，只是渲染二
维图片的话，固定管线的功能也就很方便了。不过实现一个模拟固定管线的Shader脚本也并不是什么麻烦事情，所以即使Dx10没有固定管线，也对程序员
来说，也不是什么损失了。</p>
<p><strong>完全的HLSL脚本编写： <br></strong>&nbsp;&nbsp;&nbsp;&nbsp; 对于早期的可编程管线有了解的朋友，可能会想起来，在Dx8的时候是可以用类汇编语言来编写Shader脚本的。在Dx9可以用两者任意一个来编写Shader了。但是在DX10里面，是不可以用汇编来写shader脚本的。</p>
<p><strong>Shader Model 4.0：</strong> <br>&nbsp;&nbsp;&nbsp;&nbsp; 在Dx10里的Shader是基于Shader
Model 4.0的。具体细节我不是很清楚，但是SM
4.0有更多的指令数。如果实现个多光源的效果，可能在SM2.0里面只能做到8个（当然不排除能做更多个），是因为指令数目是有限制的。那么在新的
SM4.0里面，肯定是可以实现更多的光源数目了。当然这只是一个例子而已，而且多光源技术也不是什么先进的东西，很多场景中都被延迟光照所取代了。</p>
<p><strong>没有CAPS：</strong> <br>&nbsp;&nbsp;&nbsp;&nbsp;
在Dx9里面，程序员经常会查询那些功能是被硬件所支持的，哪些是不能的。而在Dx10里面，CAPS的概念就被移除了。一块显卡或者支持DX10的所有
特性，或者干脆就不是块DX10显卡。那么意味着程序员可以使用DX10的一切功能而不需要在这之前查询当前硬件是否支持这项功能。</p>
<p><strong>Geometry Shader:</strong> <br>&nbsp;&nbsp;&nbsp;&nbsp;
GS是DX10新推出的一个概念。它是在VS和PS之间的一个GPU
Kernel类型，负责接收由VS处理后的顶点，然后可以生成新的顶点，重新做处理。举一个简单的例子，粒子系统，假设有1k个粒子。那么每帧实际需要从
CPU传输到GPU的数据是1K*4，因为每个粒子由四个顶点组成。而这些数据是要走PCIE总线的，这个总线的带宽的效率远远不及GPU On
Chip
Memory的。如果有了GS，我们完全可以只传输每个粒子的中心，然后GS由粒子中心信息生成新的顶点。那么这样以来，就可以省下四倍的传输。当然这只
是一个简单的例子而已，而且即使在DX9上渲染粒子系统，粒子的更新如果用GPU来处理的话，完全可以不传输每个粒子的信息。</p>
<p><strong>Shader脚本开始支持整型数据： <br></strong>&nbsp;&nbsp;&nbsp;&nbsp;
在DX9里面，实际上Shader中是没有整数的概念的。即使在VS或者PS里面声明一个int，其实硬件通过float的转换来处理的。在DX10里
面，是有对于整数的支持的。可以对整数进行位运算等操作，这些都是在硬件上实现的。输入的纹理的数据类型也可以是整型的。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 贴两张网上对比Dx9和Dx10的效果图吧，^_^。</p>
<p>&nbsp;<a  href="http://p.blog.csdn.net/images/p_blog_csdn_net/codeboycjy/624615/o_20070309000355113_2.jpg"><img  src="http://p.blog.csdn.net/images/p_blog_csdn_net/codeboycjy/624615/o_20070309000355113_thumb.jpg" style="border: 0px none ; display: inline;" title="20070309000355113" alt="20070309000355113" width="244" border="0" height="184"></a>&nbsp;&nbsp; <a  href="http://p.blog.csdn.net/images/p_blog_csdn_net/codeboycjy/624615/o_20070309000356678_2.jpg"><img  src="http://p.blog.csdn.net/images/p_blog_csdn_net/codeboycjy/624615/o_20070309000356678_thumb.jpg" style="border: 0px none ; display: inline;" title="20070309000356678" alt="20070309000356678" width="244" border="0" height="184"></a> </p>
<p>&nbsp;<a  href="http://p.blog.csdn.net/images/p_blog_csdn_net/codeboycjy/624615/o_20070309000357224_2.jpg"><img  src="http://p.blog.csdn.net/images/p_blog_csdn_net/codeboycjy/624615/o_20070309000357224_thumb.jpg" style="border: 0px none ; display: inline;" title="20070309000357224" alt="20070309000357224" width="244" border="0" height="184"></a>&nbsp;&nbsp; <a  href="http://p.blog.csdn.net/images/p_blog_csdn_net/codeboycjy/624615/o_20070309000357460_2.jpg"><img  src="http://p.blog.csdn.net/images/p_blog_csdn_net/codeboycjy/624615/o_20070309000357460_thumb.jpg" style="border: 0px none ; display: inline;" title="20070309000357460" alt="20070309000357460" width="244" border="0" height="184"></a></p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 左边的两张是Dx9的右边是Dx10的。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;
DX10有了这些变化后，可以方便程序员进行开发。但不是说DX10可以做到的东西，DX9就完全做不到，只不过是DX10的效率更高一些。我们看上面的
对比图，其实如果做一个fake的光照效果，左下角的图完全可以用DX9模拟出来（个人感觉只是右边加上了点后处理特效而已）。举另一个例子来说，用
DX10做阴影效果，Shadow
Volumn可以在GPU端利用GS来生成，然后用Stream-out功能把生成的资源再利用，从而做出这个效果。但是我们也同样在DX9上看到了
Shadow Volumn的Demo。其实效果是差不多的，主要区别在于前者利用了GPU去生成Shadow
Volumn，这个任务本身就是一个并行的过程，GPU处理要优于CPU处理。而且渲染是在GPU端进行的，如果利用CPU生成的数据，就必须把数据通过
PCIE传输到显卡上，这些也是很耗时的过程。当然，如果实在要用DX9在GPU端生成Shadow
Volumn，还可以通过CUDA，OpenCL等一些通用计算接口来帮助处理。但是这样会给程序很大限制，因为AMD和Nvidia有各自不同的解决方
案，如果你用了其中一家的，就很难在另一家的卡上Work（OpenCL除外）。</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 基本上就介绍这些内容吧，我了解的还很少，很多东西是查资料的。如果有什么错误的地方，欢迎和我交流。^_^</p>
<h1 class="title_txt">					</h1><img src ="http://www.cppblog.com/liangairan/aggbug/139893.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2011-02-11 14:47 <a href="http://www.cppblog.com/liangairan/articles/139893.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>shader复杂与深入：Normal Map（法线贴图）2</title><link>http://www.cppblog.com/liangairan/articles/135001.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Mon, 29 Nov 2010 09:58:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/135001.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/135001.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/135001.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/135001.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/135001.html</trackback:ping><description><![CDATA[<p><br>在前文中我尽可能地把我所理解Normal Map原理总结了一下，本续篇将从实践部分继续开始，各位看官尽情拍砖。——ZwqXin.com<br>上篇见：[shader复习与深入：Normal Map(法线贴图)Ⅰ]<br>1. 怎样获得顶点的TBN<br>其实我觉得这个是实践部分最麻烦的地方。OpenGL提供了诸如glNormal、normal-vbo之类的接口设置顶点的法线，然后在shader中以gl_Normal等方式取得顶点法线数据，但是没有提供切线和副法线的。当然两者只要其一就足够了（另一者可通过叉乘和左/右手定则获得）。因为要把TBN导入shader，干脆就设置attribute变量，记录每个顶点的切线。切线一般就是相邻顶点的差向量了（其实这有时候是非常繁重的工作）。<br>如果是通常的3DS模型的话，顶点法线是共顶点的面的面法线的加权，这样法线就不一定垂直于某个面，即与切线不垂直。但只要它们还是近似垂直的，上篇提及的Gram-Schmidt 算法应该可以处理。或者在shader中，把法线与切线叉乘出副法线，再用法线与副法线叉乘得新的切线，也能确保两两垂直。这样之前的TBN矩阵的转置矩阵就能直接作为其逆矩阵，完成向量从模型坐标系往切线空间坐标系的变换了。<br>问题不只这样。对于一些模型，共享顶点的三角面片面法线差角太大，这时候计算出的该顶点法线和切线就可能带来麻烦。在橙书（OpenGL Shading Language）中，谈及了切线必须是一致的（consistently），面片相邻的顶点切线不应该差距太大。但若相邻面片夹角太大，得到的该顶点法线就可能与&#8220;共享该顶点的面片&#8221;上的其他顶点的法线差异很大，从而切线也会相差很大，直接导致光向量等在这两顶点的切线空间差异很大，插值的各个针对像素的光向量方向差异很大，与像素法线点乘的cos也会差异得很明显（而现实中一般的凹凸面漫反射光线不会有太大方向差异）。解决方法是把该出了问题的顶点拆成两个（原地拷贝，3DS模型就不用了- -），一个面片用一个，其法线只受所属的面片的面法线决定（这样最后会形成突出的边缘，但夹角大的面片之间实际上就应该会是有这样的效果吧）。<br>另一个问题，我们向shader传入顶点法线切线，希望副法线由两者叉乘得出。但既然叉乘就有个方向问题（结果可以有两个方向，AXB与BXA是不一样的，我以前弄shadow volume就曾被它这种特性作弄过）。AXB改成BXA实际上会导致凹凸感反向，原来凹的变凸了，原来凸的变凹了（要仔细比对，不然会有首因效应）。一般就用N X T吧，因为基本上都是这个顺序的，结果也符合原Normal Map。<br>2. GLSL 1.2 Shader实现代码<br>没什么好说的，就是前面算法翻译成GLSL。<br>Vertex Shader：<br>&nbsp;<br>// vertex shader <br>uniform vec3 lightpos; //传入光源的模型坐标吧 <br>uniform vec4 eyepos; <br>&nbsp; <br>varying vec3 lightdir; <br>varying vec3 halfvec; <br>varying vec3 norm; <br>varying vec3 eyedir; <br>&nbsp; <br>attribute vec3 rm_Tangent; <br>&nbsp; <br>void main(void) <br>{ <br>&nbsp;&nbsp; vec4 pos = gl_ModelViewMatrix * gl_Vertex; <br>&nbsp;&nbsp; pos = pos / pos.w; <br>&nbsp;&nbsp;&nbsp; <br>//把光源和眼睛从模型空间转换到视图空间 <br>&nbsp;&nbsp; vec4 vlightPos = (gl_ModelViewMatrix * vec4(lightpos, 1.0)); <br>&nbsp;&nbsp; vec4 veyePos&nbsp;&nbsp; = (gl_ModelViewMatrix * eyepos); <br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp; lightdir = normalize(vlightPos.xyz - pos.xyz); <br>&nbsp;&nbsp; vec3 eyedir = normalize(veyePos.xyz - pos.xyz); <br>&nbsp;&nbsp;&nbsp; <br>&nbsp; //模型空间下的TBN <br>&nbsp;&nbsp; norm = normalize(gl_NormalMatrix * gl_Normal); <br>&nbsp; <br>&nbsp;&nbsp; vec3 vtangent&nbsp; = normalize(gl_NormalMatrix * rm_Tangent); <br>&nbsp; <br>&nbsp;&nbsp; vec3 vbinormal = cross(norm,vtangent); <br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp; //将光源向量和视线向量转换到TBN切线空间 <br>&nbsp;&nbsp; lightdir.x = dot(vtangent,&nbsp; lightdir); <br>&nbsp;&nbsp; lightdir.y = dot(vbinormal, lightdir);&nbsp; <br>&nbsp;&nbsp; lightdir.z = dot(norm&nbsp;&nbsp;&nbsp;&nbsp; , lightdir); <br>&nbsp;&nbsp; lightdir = normalize(lightdir); <br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp; eyedir.x = dot(vtangent,&nbsp; eyedir); <br>&nbsp;&nbsp; eyedir.y = dot(vbinormal, eyedir); <br>&nbsp;&nbsp; eyedir.z = dot(norm&nbsp;&nbsp;&nbsp;&nbsp; , eyedir); <br>&nbsp;&nbsp; eyedir = normalize(eyedir); <br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp; halfvec = normalize(lightdir + eyedir); <br>&nbsp; <br>&nbsp;&nbsp; gl_FrontColor = gl_Color; <br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp; gl_TexCoord[0] = gl_MultiTexCoord0; <br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp; gl_Position = ftransform(); <br>}<br>// vertex shaderuniform vec3 lightpos; //传入光源的模型坐标吧uniform vec4 eyepos;varying vec3 lightdir;varying vec3 halfvec;varying vec3 norm;varying vec3 eyedir;attribute vec3 rm_Tangent;void main(void){&nbsp;&nbsp; vec4 pos = gl_ModelViewMatrix * gl_Vertex;&nbsp;&nbsp; pos = pos / pos.w;&nbsp;&nbsp; //把光源和眼睛从模型空间转换到视图空间&nbsp;&nbsp; vec4 vlightPos = (gl_ModelViewMatrix * vec4(lightpos, 1.0));&nbsp;&nbsp; vec4 veyePos&nbsp;&nbsp; = (gl_ModelViewMatrix * eyepos);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lightdir = normalize(vlightPos.xyz - pos.xyz);&nbsp;&nbsp; vec3 eyedir = normalize(veyePos.xyz - pos.xyz);&nbsp;&nbsp;&nbsp;&nbsp; //模型空间下的TBN&nbsp;&nbsp; norm = normalize(gl_NormalMatrix * gl_Normal);&nbsp;&nbsp; vec3 vtangent&nbsp; = normalize(gl_NormalMatrix * rm_Tangent);&nbsp;&nbsp; vec3 vbinormal = cross(norm,vtangent);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //将光源向量和视线向量转换到TBN切线空间&nbsp;&nbsp; lightdir.x = dot(vtangent,&nbsp; lightdir);&nbsp;&nbsp; lightdir.y = dot(vbinormal, lightdir);&nbsp;&nbsp;&nbsp; lightdir.z = dot(norm&nbsp;&nbsp;&nbsp;&nbsp; , lightdir);&nbsp;&nbsp; lightdir = normalize(lightdir);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; eyedir.x = dot(vtangent,&nbsp; eyedir);&nbsp;&nbsp; eyedir.y = dot(vbinormal, eyedir);&nbsp;&nbsp; eyedir.z = dot(norm&nbsp;&nbsp;&nbsp;&nbsp; , eyedir);&nbsp;&nbsp; eyedir = normalize(eyedir);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; halfvec = normalize(lightdir + eyedir);&nbsp;&nbsp; gl_FrontColor = gl_Color;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gl_TexCoord[0] = gl_MultiTexCoord0;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gl_Position = ftransform();}<br>传入的lightPos，eyePos，gl_Vertex，gl_Normal，rm_Tangent是其模型坐标系下的坐标、向量，乘以ModelView矩阵（法线切线乘以ModelView矩阵的转置逆矩阵）到了视图空间（vlightPos，veyePos，pos，norm, vtangent）；在视图空间它们已经有了&#8220;世界&#8221;的概念了，因此可以平等地相互影响（在各自封闭的模型空间是享受不了的），可以作各种点乘叉乘加减乘除计算。<br>注意，lightPos，eyePos虽说是在其各自模型坐标系下定义的，但不对它们弄什么平移旋转缩放操作的话，其模型矩阵就是一单位阵，此时其&#8220;世界坐标 == 模型坐标&#8221;。所以这时我可以当它是在世界空间定义的坐标（实际上一般我们都会在世界空间定义这两个点）。（注意，前提是不对它们做模型变换。）<br>从以上量得到光源向量、视线向量后（它们在视图空间），N、T叉乘得B（注意它们现在都在视图空间），通过TBN矩阵逆矩阵把两向量变换到当前顶点的切线空间，交给光栅去插值。 <br>对以上有不理解的朋友，可能是没看上篇：[shader复习与深入：Normal Map(法线贴图)Ⅰ]<br>fragment shader：<br>&nbsp;<br>//fragment shader <br>uniform float shiness; <br>uniform vec4 ambient, diffuse, specular; <br>&nbsp; <br>uniform sampler2D bumptex; <br>uniform sampler2D basetex; <br>&nbsp; <br>float amb = 0.2; <br>float diff = 0.2; <br>float spec = 0.6; <br>&nbsp; <br>varying vec3 lightdir; <br>varying vec3 halfvec; <br>varying vec3 norm; <br>varying vec3 eyedir; <br>&nbsp; <br>void main(void) <br>{ <br>&nbsp;&nbsp; vec3 vlightdir = normalize(lightdir); <br>&nbsp;&nbsp; vec3 veyedir = normalize(eyedir); <br>&nbsp; <br>&nbsp;&nbsp; vec3 vnorm =&nbsp;&nbsp; normalize(norm); <br>&nbsp;&nbsp; vec3 vhalfvec =&nbsp; normalize(halfvec);&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp; vec4 baseCol = texture2D(basetex, gl_TexCoord[0].xy);&nbsp; <br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp; //Normal Map里的像素normal定义于该像素的切线空间 <br>&nbsp;&nbsp; vec3 tbnnorm = texture2D(bumptex, gl_TexCoord[0].xy).xyz; <br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp; tbnnorm = normalize((tbnnorm&nbsp; - vec3(0.5))* 2.0);&nbsp; <br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp; float diffusefract =&nbsp; max( dot(lightdir,tbnnorm) , 0.0);&nbsp; <br>&nbsp;&nbsp; float specularfract = max( dot(vhalfvec,tbnnorm) , 0.0); <br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp; if(specularfract &gt; 0.0){ <br>&nbsp;&nbsp; specularfract = pow(specularfract, shiness); <br>&nbsp;&nbsp; } <br>&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp; gl_FragColor = vec4(amb * ambient.xyz * baseCol.xyz <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + diff * diffuse.xyz * diffusefract * baseCol.xyz <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + spec * specular.xyz * specularfract ,1.0); <br>}<br>//fragment shaderuniform float shiness;uniform vec4 ambient, diffuse, specular;uniform sampler2D bumptex;uniform sampler2D basetex;float amb = 0.2;float diff = 0.2;float spec = 0.6;varying vec3 lightdir;varying vec3 halfvec;varying vec3 norm;varying vec3 eyedir;void main(void){&nbsp;&nbsp; vec3 vlightdir = normalize(lightdir);&nbsp;&nbsp; vec3 veyedir = normalize(eyedir);&nbsp;&nbsp; vec3 vnorm =&nbsp;&nbsp; normalize(norm);&nbsp;&nbsp; vec3 vhalfvec =&nbsp; normalize(halfvec);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; vec4 baseCol = texture2D(basetex, gl_TexCoord[0].xy);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //Normal Map里的像素normal定义于该像素的切线空间&nbsp;&nbsp; vec3 tbnnorm = texture2D(bumptex, gl_TexCoord[0].xy).xyz;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tbnnorm = normalize((tbnnorm&nbsp; - vec3(0.5))* 2.0);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; float diffusefract =&nbsp; max( dot(lightdir,tbnnorm) , 0.0);&nbsp;&nbsp;&nbsp; float specularfract = max( dot(vhalfvec,tbnnorm) , 0.0);&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(specularfract &gt; 0.0){&nbsp;&nbsp; specularfract = pow(specularfract, shiness);&nbsp;&nbsp; }&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; gl_FragColor = vec4(amb * ambient.xyz * baseCol.xyz&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + diff * diffuse.xyz * diffusefract * baseCol.xyz&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; + spec * specular.xyz * specularfract ,1.0);}<br>注意把normal map里的normal由(0,1)映射回(-1,1)。baseCol得到的是基底纹理的像素颜色。其余部分就是per pixel lighting的东西了。[Shader快速复习：Per Pixel Lighting(逐像素光照)]</p>
<p>(上为底纹理和法线纹理，下为它们与某破壁模型合作的效果，纹理from planetpixelemporium.com)<br>&nbsp;</p>
<p>(我想是游戏最常用的用途：砖墙。我想是最常用的NormalMap,from NEHE)</p>
<p><br>(自己把墙壁BaseMap放入Photoshop的normalMapFilter里弄的NormalMap，呃.....)</p>
<p>本文来源于ZwqXin <a href="http://www.zwqxin.com/">http://www.zwqxin.com/</a> , 转载请注明<br>原文地址：<a href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map-2.html">http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map-2.html</a> </p>
<p><br>&nbsp;</p>
<img src ="http://www.cppblog.com/liangairan/aggbug/135001.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2010-11-29 17:58 <a href="http://www.cppblog.com/liangairan/articles/135001.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>shader复杂与深入：Normal Map（法线贴图）1</title><link>http://www.cppblog.com/liangairan/articles/135000.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Mon, 29 Nov 2010 09:53:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/135000.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/135000.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/135000.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/135000.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/135000.html</trackback:ping><description><![CDATA[<p>转自：<a href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map.html">http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map.html</a><br>Normal Map法线贴图，想必每个学习计算机图形学的人都不陌生。今天在这里按我的理解总结一下，作为复习，也作为深入学习吧。——ZwqXin.com<br>自从看完那本《数学在计算机图形学上的应用》后，一直想好好地真正实践一次法线贴图/凹凸贴图呢（以前是根据橙书弄了一下罢了）。昨天偶尔看到篇涉及BumpMap的文，正好觉得是个机会，便在网上狂找相关资料——果然，越看越觉得自己还有很多理论的地方需要弄明白呢。<br>说起Normal Map（法线贴图），就会想起Bump Map（凹凸贴图）。Bump Mapping是Blin大师在1978年提出的图形学算法，目的是以低代价给予计算机几何体以更丰富的表面信息（高模盖低模）。30年来，这项技术不断延展，尤其是计算机图形学成熟以后，相继出现了不少算法变体，90年代末的Normal Map解放了必须自行计算纹理像素法线的痛苦，新世纪以来相继又出现了Parallax Mapping, Relief Mapping等技术。抛开那些无聊的概念区分，它们的本体还是Bump Map，目的也是一致的。<br>1. 传统的Bump Map<br>如果你对纯净的Bump Map有兴趣，A Practical and Robust Bump-mapping Technique for Today's GPU应该是值得一看的论文。说Today，其实是GDC 2000的事情了，但对于传统的Bump Map的理论是很丰富的，我是没精力看完它啦&#8230;&#8230;<br>那时候的Bump Map须要我们计算纹理图上每个像素的法线信息，简单的还可能做到，对复杂的纹理要搞清面光背光份量简直要命，于是就用Height Map，在一张高度图上记录每个像素对应的纹理位置的高度信息（这个比较容易办到，NEHE22也是这类）。看上去就是一张地形网格——这样的话，计算每个像素点的法线就不那么难了。XY方向相邻像素的高度相减就是两条正交的切向量，叉乘外加左/右手定则就获得法线。或者更精确点，用八邻域弄个边缘检测算子（sobel、拉普拉斯之类 ）[图像处理里的空间域滤波]，或者应用斜坡法([水效果Ⅲ - 抖动波] )来求切线、法线。</p>
<p>&nbsp;2. 制作NormalMap<br>但是这样还是挺麻烦的，既然都动用额外的贴图了，何不把这些与实现无关的预处理——作为结果的法线信息——都放进纹理里呢？这就是Normal Map的思想起源。但是，谁来做这样的一张法线图呢？敲定美工了。每个像素的RGB分别存储该像素对应法线的XYZ分量，只要把法线的分量由（-1，1）映射成（0，255）就可了。观察一张法线图，以蓝色为主，是因为朝向图面外的法线（0，0，1）都被编码成（0，0，127）了（读入OpenGL后即(0,0,0.5)），而图上越红的地方表明法线越向右，越绿的地方表明法线越向上，就可以理解了。总体来说，就是一张紫蓝色的图。怎么做这样的图呢？当然最好是有一个工具，输入原图和高度图后执行上述的算法得出新图了，事实上已经有很多这类工具了（譬如比较著名的photoshop的NV插件Normal Map Filter，甚至不用高度channel也可[效果- -]），以下几篇文章有详细介绍，有兴趣的可以看一看：<br>Tutorial On Normal Mapping （PHOTOSHOP [ENGLISH]）<br>怎样用PhotoShop创建Bump Map图像 （PHOTOSHOP [CHINESE]）<br>Nvidia Normal Map 插件参数之详解 (PHOTOSHOP [翻译])<br>GIMP normalmap plugin&nbsp;&nbsp; (GIMP&nbsp;&nbsp; [ENG]）<br>关于NormalMap制作的原理，更详细的可参考此文：Normalmap原理及去除接缝<br>&nbsp;3. 切线空间(Tangent Space)<br>其实这个概念前文已经提及了。每个像素根据高度图生成的三轴坐标系，就是被称为切线空间坐标系的东西，每个像素人手一个。可见Normal Map里面每个像素的法线就是定义在这个切线空间的。注意，这些法线是属于像素的，而不是顶点，我们平时用的法线是顶点法线，是定义在模型坐标系的[乱弹OpenGL中的矩阵变换(上)] ，定义于所属物件的唯一的局部坐标系原点之上。而这些像素法线定义于切线坐标系，其原点就在该像素上，切线副法线在法线的垂直平面上。</p>
<p><br>（表面依然是平的，但通过搅动法线，使进入我们眼睛的光线强度不一，模拟出凹凸面漫反射的特点。图from GDNet）<br>应用这些像素法线的目的无非是计算出该像素的OutPut颜色：col = baseColor * (amb + diffuse) + specular。这些都应该在像素着色器（fragment shader）里进行，因为我们要做的是针对每个像素的处理[Shader快速复习：Per Pixel Lighting(逐像素光照)] 。其中需要用到像素法线的是diffuse和specular（以前是用通过顶点法线线性插值而来的normal），法线分别与光线向量、半向量作点乘得到对应因子。这个因子是个夹角cos而已，所以只要满足像素法线与两个向量单位化并在同一坐标系下（而无论是哪个坐标系），夹角就是一定的。这样看来，两个选择：<br>1. 把像素法线都从各自的切线空间转到视图空间来，再点乘；<br>2.把光线向量、半向量从视图空间转到像素各自的切空间来，再点乘。<br>很多文章一口咬定就是第2种好，原因是第1种要变换N个量；第2种只变换2个量。仔细分析，其实两种选择变换的次数是一样的，都是2*N。说第2种好，是因为：<br>第1种必须在fragment shader里进行，对象是从Normal Map读出的像素法线和经过线性插值而来的两个向量，它们不是同一坐标系的，按描述应该是各像素法线乘以各自一个的变换矩阵，转到视图空间来，但确实没有其他的可提供构筑这个矩阵的信息了，若有可能应该就是另外的varying变量传入了；<br>第2种可以选择在vertex shader里进行，但是能不能就在这里变换到切线空间呢？假设可以，那么得到的针对顶点的数值在光栅化-线性插值后能否满足呢？<br>要回答这个问题，还得考虑像素的切线空间和顶点的切线空间之间的关系。是的，顶点法线也可以变换到切线空间，但这有什么用呢？一步一步来吧。先考虑切线空间在OpenGL世界里的次元位置：</p>
<p>(from paulsprojects)<br>为什么是紧挨模型坐标系呢？其实想想也能理解，在上面谈及切线坐标系的时候，并没有广阔的&#8220;世界&#8221;这个概念。只针对每个像素/顶点，无疑是比模型坐标系更狭隘的&#8220;世界观&#8221;，所以那个位置是适合的（箭头方向无所谓，坐标系之间是可以相互转换的）。其实对于某个具体的物体上的像素/顶点，你可以考虑那是把模型空间的原点平移到该像素/顶点上，各模型坐标系方向轴向量一起经过旋转，使Z轴与像素/顶点的法线重合，XY轴分别与像素/顶点的切线副法线重合——这只是一个仿射变换而已，如同模型/世界/视图空间之间的变换一样。<br>如果你记得图形学书上关于世界/视图空间的变换矩阵的构建的话，就更容易理解这样的形式了。从切线空间到模型空间的变换矩阵（TBN矩阵MTBN）为：</p>
<p>&nbsp;其中T，B，N是定义在模型空间的该像素/顶点的&#8220;切/副法/法向量&#8221;。稍微检验一下，考虑某个三角面上的某个顶点，其法线充当切线空间的Z轴，在切线空间中表示为（0，0，1），在OpenGL里解释为一个列向量（0，0，1）T，用上面的矩阵MTBN左乘该向量，得到（Nx，Ny，Nz）T，正是该向量在模型空间的表示。其他两轴同理。说明该矩阵把切线空间的坐标系统转换到模型空间了（一切变换都是在变换坐标系[乱弹OpenGL中的矩阵变换(上)] ）。当然这是特例说明，但确实这个矩阵包含仿射矩阵里的旋转元素了（它只包含旋转，不设置平移，是因为我们只需要它来变换向量，向量是可以任意平移的，若要弄完整的4X4矩阵，第4列平移列就是该顶点模型坐标）。具体推导也不难，随便Google一下"tangent space"就出来一堆了，而且都是基本一样的推导过程，推一个：Tangent Space。<br>其逆变换（矩阵MTBN-1）就可以把向量从模型空间变换到对应顶点的切线空间了。如果你确保T，B，N两两垂直，这个正交矩阵的逆矩阵就是其转置矩阵，这很理想。但万一你不确保这点（涉及到具体应用，很多问题的，后面会说），就保证它们大致满足三叉状，用所谓的Gram-Schmidt 算法矫正：<br>T&#8242; = T &#8722; (N &#183; T)N<br>B&#8242; = B &#8722; (N &#183; B)N &#8722; (T&#8242; &#183; B)T&#8242;<br>反正最后得到的是这样的形式——用它左乘光源向量和半向量，就得到对应于该顶点切线空间的光源向量和半向量了：<br>T&#8242;x<br>B&#8242;x<br>NxT&#8242;y<br>B&#8242;y<br>NyT&#8242;z<br>B&#8242;z<br>Nz </p>
<p>为什么是顶点？因为这是你唯一能取得其切线/副法线/法线的东西了。这也是之前说的选择1不行的原因，在那张Normal Map里面已经没有任何法线副法线的确实信息了（只知道它们在法线垂直平面上），即使能通过别的方法取得（起码要增加传入数据），那要在fragment shader里每像素人手又计算一个矩阵，这就又是一个&#8220;计算量&#8221;（不是次数）的问题。所以还是用选择2吧，也就是上面矩阵MTBN-1的讨论。<br>选择2的第一个问题现在很清楚了：是可以的。只要取得顶点的切线/副法线/法线数据就能建立矩阵并变换光源向量和半向量，但结果是针对顶点的，我们需要的是针对像素的。光栅化线性插值这两个向量，就是对应像素的值，但这对吗？直觉上不对，但结果显示这样做没有不妥（或者说不会与真实所须差太多）。一般文章都没有直接透视这个问题，其实考虑一个矩形平面就露馅了，它四个顶点的TBN一致，变换得的光源向量也该一致，插值后得光源向量也该一致，但NormalMap中的像素有各自不同的切线空间系统，光源向量不该一致的呃（虽则同向光源、不同法线足够形成凹凸效果）。所以我对选择2的第二个问题保持疑问，有道深者请为鄙人指点迷津！<br>反正即使计算两向量夹角的计算可能会有偏差，也不会太离谱，问题到此结束。至于有的文章提及对diffuse的计算，光源向量插值后不须再归一化的问题（我尝试过，整体会变暗一点），就不深入了。注意我们在vertex shader里变换到切线空间的是模型空间下的光源向量和视线向量（半向量是它们的和），而一般这两个向量定义在视图空间，所以之前还要做一个视图空间-&gt;模型空间的变换（用ModelView矩阵的逆矩阵）。这是很多文章囫囵掉的一点。但如果你能取得视图空间下的顶点TBN，也不需。因为切线/副法线/法线若是被变换到视图空间，则上面的TBN矩阵MTBN就是把东西从该顶点的切线空间变换到视图空间（道理是一样的），MTBN-1就能把视图空间下的这两个向量变换到该顶点的切线空间（参见下篇的代码）。<br>&nbsp;最后的问题：怎么去取得模型空间下的顶点的切线，副法线，法线？连同shader实现代码一起，我会在下篇谈及，请留意了哦。</p>
<p>本文来源于ZwqXin <a href="http://www.zwqxin.com/">http://www.zwqxin.com/</a> , 转载请注明<br>原文地址：<a href="http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map.html">http://www.zwqxin.com/archives/shaderglsl/review-normal-map-bump-map.html</a> </p>
<p><br>&nbsp;</p>
<img src ="http://www.cppblog.com/liangairan/aggbug/135000.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2010-11-29 17:53 <a href="http://www.cppblog.com/liangairan/articles/135000.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Cascaded shadow map（转）</title><link>http://www.cppblog.com/liangairan/articles/134477.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Wed, 24 Nov 2010 02:20:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/134477.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/134477.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/134477.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/134477.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/134477.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 转自：http://class.gd/content/shadow-map%E9%98%B4%E5%BD%B1%E8%B4%B4%E5%9B%BE%E6%8A%80%E6%9C%AF%E4%B9%8B%E6%8E%A2%E2%85%A2本文来源：http://www.zwqxin.com/archives/opengl&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&n...&nbsp;&nbsp;<a href='http://www.cppblog.com/liangairan/articles/134477.html'>阅读全文</a><img src ="http://www.cppblog.com/liangairan/aggbug/134477.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2010-11-24 10:20 <a href="http://www.cppblog.com/liangairan/articles/134477.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Light Pre Pass in XNA: Basic Implementation</title><link>http://www.cppblog.com/liangairan/articles/lightprepass.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Sun, 15 Aug 2010 02:01:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/lightprepass.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/123479.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/lightprepass.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/123479.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/123479.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 转自：http://mquandt.com/blog/2009/12/light-pre-pass-in-xna-basic-implementation/NOTE: This article is now obsolete. An up-to-date sample and article can be found at http://mquandt.com/blog/2010/03/lig...&nbsp;&nbsp;<a href='http://www.cppblog.com/liangairan/articles/lightprepass.html'>阅读全文</a><img src ="http://www.cppblog.com/liangairan/aggbug/123479.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2010-08-15 10:01 <a href="http://www.cppblog.com/liangairan/articles/lightprepass.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>一起学习Shadow mapping</title><link>http://www.cppblog.com/liangairan/articles/110244.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Sun, 21 Mar 2010 12:15:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/110244.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/110244.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/110244.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/110244.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/110244.html</trackback:ping><description><![CDATA[转自：<a href="http://www.cnblogs.com/cxrs/archive/2009/10/17/1585038.html">http://www.cnblogs.com/cxrs/archive/2009/10/17/1585038.html</a><br>1、什么是Shadow Maping?<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Shadow Mapping是由Lance&nbsp;Williams于1978年在一篇名为<span class=string>"Casting&nbsp;curved&nbsp;shadows&nbsp;on&nbsp;curved&nbsp;surfaces"</span><span>的文章中提出的，这篇文章是ShadowMap技术之根源。其实原理很简单，如果光源和目标点之间的连线没有任何物体阻挡的话，则目标点没有在阴影中;如果有物体遮挡，则目标点处在阴影中。而ShadowMap，就是一张记录了每个象素处用于比较遮挡关系信息的Texture.&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;产生这个ShadowTexture的方法很简单，以SpotLight为例，把3D Camera放到光源的位置，把DepthTest打开，渲染场景，在PixShader中把每个象素的深度信息或者光源和此象素的距离信息写到RenderTarget上,由于DepthTest是打开的，保证了最终写到RenderTarget上的均是物体上未处在阴影中的点的深度值，实质完全可以等效为最终的DepthBuffer。<br></span>&nbsp;&nbsp;&nbsp;&nbsp; 得到这个ShowMap之后，如何最终生成阴影呢？在PixShader对每个pixel进行处理时，算出当前象素与灯当的距离Dc，与存在ShdowMap中的引像素的值Dz进行比较，如果Dc &gt; Dz,则在阴影中，反之则被灯光照亮。<br>2、Shadowmap之HLSL的实现<br>&nbsp;&nbsp;&nbsp; 在Direct SDk中有ShadowMap的Sample，下面的Shader和Sample里面空全一样，只是加了一些注释便于理解。<br>&nbsp;&nbsp;&nbsp; (1)生成ShadowMap的VS和PS<br>
<p>//-----------------------------------------------------------------------------<br>// Vertex Shader: VertShadow<br>void VertShadow( float4 Pos : POSITION,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; float3 Normal : NORMAL,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; out float4 oPos : POSITION,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; out float2 Depth : TEXCOORD0 )<br>{<br>&nbsp;&nbsp;&nbsp; //从模型坐标系变换到观察坐标系<br>&nbsp;&nbsp;&nbsp; oPos = mul( Pos, g_mWorldView );<br>&nbsp;&nbsp;&nbsp;//进行投影变换<br>&nbsp;&nbsp;&nbsp;oPos = mul( oPos, g_mProj );<br>&nbsp;&nbsp; //把投影坐标系的ZW值赋给Depth，作为PixelShader中的输出，这里的Z还是齐次坐标，这里不直接输出Z/W，我的理解是让Z和W都在Rasterizer中进行线性插<br>&nbsp; //值，这样可以增加最终生成的ShadowMap的精度。<br>&nbsp;&nbsp;&nbsp; Depth.xy = oPos.zw;<br>}<br>//-----------------------------------------------------------------------------<br>// Pixel Shader: PixShadow<br>void PixShadow( float2 Depth : TEXCOORD0,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; out float4 Color : COLOR )<br>{<br>&nbsp;&nbsp;&nbsp; //&nbsp;把 z / w的值作为Color值输出，写到RenderTarget上，此时的RT formate是D3DFMT_R32F<br>&nbsp;&nbsp; //把Z/W目的是把齐次坐标Z变换到三维空间的非齐次坐标，范围则是[-1,1]<br>&nbsp;&nbsp;&nbsp; Color = Depth.x / Depth.y;<br>}</p>
(2)用ShadowMap生成Shadow<br>
<p>//-----------------------------------------------------------------------------<br>// Vertex Shader: VertScene<br>// Desc: Process vertex for scene<br>//-----------------------------------------------------------------------------<br>void VertScene( float4 iPos : POSITION,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; float3 iNormal : NORMAL,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; float2 iTex : TEXCOORD0,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; out float4 oPos : POSITION,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; out float2 Tex : TEXCOORD0,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; out float4 vPos : TEXCOORD1,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; out float3 vNormal : TEXCOORD2,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; out float4 vPosLight : TEXCOORD3 )<br>{<br>&nbsp;&nbsp;&nbsp; vPos = mul( iPos, g_mWorldView );<br>&nbsp;&nbsp;&nbsp; oPos = mul( vPos, g_mProj );<br>&nbsp;&nbsp;&nbsp; vNormal = mul( iNormal, (float3x3)g_mWorldView );<br>&nbsp;&nbsp;&nbsp; Tex = iTex;<br>&nbsp;&nbsp;&nbsp; //把当前顶点位置变换到以光源为Camera的投影空间，<br>&nbsp;&nbsp;&nbsp; vPosLight = mul( vPos, g_mViewToLightProj );<br>}</p>
<p>&nbsp;</p>
<p><br>//-----------------------------------------------------------------------------<br>// Pixel Shader: PixScene<br>// Desc: Process pixel (do per-pixel lighting) for enabled scene<br>//-----------------------------------------------------------------------------<br>float4 PixScene( float2 Tex : TEXCOORD0,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; float4 vPos : TEXCOORD1,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; float3 vNormal : TEXCOORD2,<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; float4 vPosLight : TEXCOORD3 ) : COLOR<br>{<br>&nbsp;&nbsp;&nbsp; float4 Diffuse;</p>
<p>&nbsp;&nbsp;&nbsp; //&nbsp;计算光源到当前象素方向向量并单位化<br>&nbsp;&nbsp;&nbsp; float3 vLight = normalize( float3( vPos - g_vLightPos ) );</p>
<p>&nbsp;&nbsp;&nbsp; //&nbsp;&nbsp;dot( vLight, g_vLightDir )为光源到当前象素方向向量和光的方向向量之间的夹角余旋值，由于是spotlight，因此必须要在spotlight可照射的范围内。因为角 <br>&nbsp;&nbsp;&nbsp; //度越小余旋值越大，因此这里是大于<br>&nbsp;&nbsp;&nbsp; if( dot( vLight, g_vLightDir ) &gt; g_fCosTheta )&nbsp;<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // Pixel is in lit area. Find out if it's<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // in shadow using 2x2 percentage closest filtering</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//从投影空间坐标转化为纹理空间坐标，也就是找到投影空间中的点和纹理空间中的点的对应关系<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //除以w,xy坐标便处在(-1,1)的范围内，乘0.5加0.5，则变换到了(0,1)的范围，因texture space的u,v坐标是(0,1)的<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; float2 ShadowTexC = 0.5 * vPosLight.xy / vPosLight.w + float2( 0.5, 0.5 );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//在投影坐标系中，Y轴是向上的，而在纹理空间中Y轴向下，因此要作以下处理<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ShadowTexC.y = 1.0f - ShadowTexC.y;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 在texel space中对应的象素坐标<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; float2 texelpos = SMAP_SIZE * ShadowTexC;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp;取得小数部分&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; float2 lerps = frac( texelpos );</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //这里使用的是2x2 percentage closest filtering,因此是采的邻近的四个点，判断它们是否在阴影中，<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; float sourcevals[4];<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sourcevals[0] = (tex2D( g_samShadow, ShadowTexC ) + SHADOW_EPSILON &lt; vPosLight.z / vPosLight.w)? 0.0f: 1.0f;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sourcevals[1] = (tex2D( g_samShadow, ShadowTexC + float2(1.0/SMAP_SIZE, 0) ) + SHADOW_EPSILON &lt; vPosLight.z / vPosLight.w)? 0.0f: 1.0f;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sourcevals[2] = (tex2D( g_samShadow, ShadowTexC + float2(0, 1.0/SMAP_SIZE) ) + SHADOW_EPSILON &lt; vPosLight.z / vPosLight.w)? 0.0f: 1.0f;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; sourcevals[3] = (tex2D( g_samShadow, ShadowTexC + float2(1.0/SMAP_SIZE, 1.0/SMAP_SIZE) ) + SHADOW_EPSILON &lt; vPosLight.z / vPosLight.w)? 0.0f: 1.0f;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp;用lerps&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; float LightAmount = lerp( lerp( sourcevals[0], sourcevals[1], lerps.x ),<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lerp( sourcevals[2], sourcevals[3], lerps.x ),<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; lerps.y );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //&nbsp;计算光照，如果完全在阴影中，则LightAmount为0，这里只计算了Diffuse color，没有高光<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Diffuse = ( saturate( dot( -vLight, normalize( vNormal ) ) ) * LightAmount * ( 1 - g_vLightAmbient ) + g_vLightAmbient )<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; * g_vMaterial;<br>&nbsp;&nbsp;&nbsp; } else<br>&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Diffuse = g_vLightAmbient * g_vMaterial;<br>&nbsp;&nbsp;&nbsp; }</p>
<p>&nbsp;&nbsp;&nbsp; return tex2D( g_samScene, Tex ) * Diffuse;<br>}</p>
<br>3、ShdowMap的优缺点<br>&nbsp;&nbsp;&nbsp; 优点：简单，不需要知道场景中Object的Geometry,不需要Stencil Buffer，每个灯光只需多渲染一个Pass。<br>&nbsp;&nbsp;&nbsp; 缺点：当ShadowMap分辨率不够高时，或灯光与物体隔得很近时，在边缘处会产生<span>Aliasing</span>，锯齿，因此，很多改进shadowMap的算法都围绕着如何消除锯齿作文章。<br>4、ShadowMap的改进<br>&nbsp;&nbsp;&nbsp; 关于ShadowMap的改进，又出了很多的paper和技术，比如：Percentage Shadow map,&nbsp; 使用bloom filter对ShadowMap进行模糊处理.以及siggraph 2002 中Marc Stamminger和 George Drettakis提出的Perspective Shadow map.以及Adaptive Shadow Map等等。
<img src ="http://www.cppblog.com/liangairan/aggbug/110244.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2010-03-21 20:15 <a href="http://www.cppblog.com/liangairan/articles/110244.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>修改ETM,用Ogre实现《天龙八部》地形与部分场景详解（转）</title><link>http://www.cppblog.com/liangairan/articles/107932.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Tue, 16 Feb 2010 14:15:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/107932.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/107932.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/107932.html#Feedback</comments><slash:comments>9</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/107932.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/107932.html</trackback:ping><description><![CDATA[<p>本文主要讲的是《天龙八部》游戏的地形和一部分场景的具体实现，使用C++, Ogre1.6，我摸索了段时间，可能方法用的并不是最好的，但好歹实现了。文章可能讲得有点罗嗦，很多简单的东西都讲了。我是修改了ETM（Editable Terrain Manager）实现的地形，其实单单实现天龙八部的地形场景等的载入根本不需要使用ETM，直接用Ogre的顶点-&gt;索引-&gt;纹理就可以搞定地形，但我要做的是可以实时编辑的，所以用了ETM，场景其由于很重要的粒子和model等部分我还没去看，所以等以后看了再详细写关于场景的部分，但这个Demo已经实现了基本的场景的载入。光，雾，环境，静态物等都能载入。 </p>
<p>修改过的ETM和这个场景的Demo代码可以通过文章底下的链接下载。 </p>
<p>Demo截图如下：（少林） </p>
<p><img height=629 alt="" src="http://images.cnblogs.com/cnblogs_com/syqking/sample_shaolin.JPG" width=805> </p>
<p>这个Demo比较简单，只能移动摄像机看看场景。 </p>
<p>我研究这些的动机是当前在学校做一个网游项目，想做得类似于《Second Life》，苦于没有游戏美工，最近有马上要二期验收了，为了让游戏看上去光鲜一点，无奈之下只好借《天龙八部》的资源来用了。看了不少大牛的博客，将得感觉都有点不是很详细，只是大概把文件格式讲了一下而已，具体怎么实现说得不多（可能是觉得实现太容易，懒得多说了吧...）最主要的是，似乎没看到有人发完整的代码。 </p>
<p>实际项目中用的程序代码我就不放出来了，场景部分差不多，只是多了个内建的编辑器，人物移动和网络通信部分等。 </p>
<p>编辑器的截图晒一下，功能还不全 :-) </p>
<p><img height=606 alt="" src="http://images.cnblogs.com/cnblogs_com/syqking/editor.JPG" width=802 border=0> </p>
<p>&nbsp;</p>
<p>言归正传，先简单地说一下载入一个天龙八部场景的大致过程： </p>
<ul>
    <li>读取.Scene文件
    <li>根据&lt;Texture&gt;读取.Terrain文件
    <li>读取地砖大小(&lt;tileSize&gt;) 地形大小(xsize, ysize)，缩放值(&lt;scale&gt;)，地图中心坐标(&lt;center&gt;)。
    <li>读取所有要用的地形贴图（&lt;textures&gt;中各项）。
    <li>读取.gridinfo 文件，此文件中存放着每个格子对应的纹理坐标。
    <li>根据3，4，5步的信息用修改过的ETM创建Terrain。
    <li>读取lightmap, 是png格式的预处理的场景阴影图。
    <li>读取场景中的各种模型等，并插入到场景Root中。 </li>
</ul>
<p><font face=宋体 color=#808080 size=2>(注：天龙八部的场景包含很多个文件，用&#8220;劒蚩&#8221;的资源提取工具提取出来，文件夹下的基本都是，但我暂时不考虑寻路，碰撞等，所以就地形来讲只研究.Terrain文件，.Gridinfo文件。 资源提取的问题可访问</font><a href="http://www.cnitblog.com/sword/category/5167.htmlScene" target=_blank><font face=宋体 color=#808080 size=2><u>http://www.cnitblog.com/sword/category/5167.htmlScene</u></font></a><font face=宋体 color=#808080 size=2> )</font> </p>
<p>&nbsp; </p>
<p>下面我分几个部分来具体讲如何实现天龙八部的场景Demo。 </p>
<h3>读取高度图</h3>
<p>做地形首先肯定是要读取高度图，《天龙八部》的高度图是保存在.Heightmap文件中，读取的方法是跳过前面8个字节，读地形的width和height，然后读取width*height个float型数据，上面说到.Terrain文件中有地形大小(xsize, ysize)，缩放值(&lt;scale&gt;)，地图中心坐标(&lt;center&gt;)，&lt;scale&gt;中有xyz 3个值(一般情况下是100,100,100)，分别是x,y,z轴的放大系数，用ETM创建地形的时候，直接用读取到的float型数据作为高度图数据，然后再用上面那些值作为参数，定义地形的大小，缩放值，和偏移。 </p>
<p>这是读取高度图的代码,heightMapData是float型的数组，存放原始的高度图信息。</p>
<pre class=code><span style="COLOR: blue">void </span><span style="COLOR: #020002">TileTerrainInfo</span>::<span style="COLOR: #020002">LoadHightMap</span>( <span style="COLOR: blue">const char</span>* <span style="COLOR: #020002">fileName</span>, <span style="COLOR: blue">const char</span>* <span style="COLOR: #020002">type </span>)
{
<span style="COLOR: #020002">FILE</span>* <span style="COLOR: #020002">pf </span>= <span style="COLOR: #020002">fopen</span>( <span style="COLOR: #020002">fileName</span>, <span style="COLOR: #a31515">"rb" </span>);
<span style="COLOR: #020002">fseek</span>( <span style="COLOR: #020002">pf</span>, 8, <span style="COLOR: #020002">SEEK_SET </span>);
<span style="COLOR: blue">int </span><span style="COLOR: #020002">height</span>, <span style="COLOR: #020002">width</span>;
<span style="COLOR: #020002">fread</span>( &amp;<span style="COLOR: #020002">width</span>, 4,1, <span style="COLOR: #020002">pf </span>);
<span style="COLOR: #020002">fread</span>( &amp;<span style="COLOR: #020002">height</span>, 4,1, <span style="COLOR: #020002">pf </span>);
<span style="COLOR: #020002">assert</span>( <span style="COLOR: #020002">height </span>= <span style="COLOR: blue">this</span>-&gt;<span style="COLOR: #020002">height</span>+1 );
<span style="COLOR: #020002">assert</span>( <span style="COLOR: #020002">width </span>== <span style="COLOR: blue">this</span>-&gt;<span style="COLOR: #020002">width</span>+1 );
<span style="COLOR: blue">if</span>( <span style="COLOR: #020002">heightMapData </span>)
<span style="COLOR: blue">delete </span>[]<span style="COLOR: #020002">heightMapData</span>;
<span style="COLOR: #020002">heightMapData </span>= <span style="COLOR: blue">new float</span>[<span style="COLOR: #020002">height</span>*<span style="COLOR: #020002">width</span>];
<span style="COLOR: blue">for</span>( <span style="COLOR: blue">int </span><span style="COLOR: #020002">i </span>= 0; <span style="COLOR: #020002">i </span>&lt; <span style="COLOR: #020002">height</span>; ++<span style="COLOR: #020002">i </span>)
{
<span style="COLOR: blue">for</span>( <span style="COLOR: blue">int </span><span style="COLOR: #020002">j  </span>= 0; <span style="COLOR: #020002">j </span>&lt; <span style="COLOR: #020002">width</span>; ++<span style="COLOR: #020002">j </span>)
{
<span style="COLOR: blue">float </span><span style="COLOR: #020002">data</span>;
<span style="COLOR: #020002">fread</span>( &amp;<span style="COLOR: #020002">data</span>, 4,1,<span style="COLOR: #020002">pf </span>);
<span style="COLOR: #020002">heightMapData</span>[<span style="COLOR: #020002">i</span>*<span style="COLOR: #020002">width</span>+<span style="COLOR: #020002">j</span>] = <span style="COLOR: #020002">data</span>;
}
}
<span style="COLOR: #020002">fclose</span>( <span style="COLOR: #020002">pf </span>);
}</pre>
<pre class=code>&nbsp;</pre>
<p><strong><u></u></strong></p>
<h3>材质文件的分析</h3>
<p>我想先讲一下地形的材质，因为用别人的资源，首先要知道怎么用这些资源，一般情况下材质信息可以明显地反映出如何使用纹理资源（不排除有可能用代码动态生成材质）。 </p>
<p>在每个.Terrain文件的最下面，有这些内容。 </p>
<p>&nbsp; &lt;materials&gt; <br>&nbsp;&nbsp;&nbsp; &lt;template material="Terrain/OneLayer" name="OneLayer"/&gt; <br>&nbsp;&nbsp;&nbsp; &lt;template material="Terrain/OneLayerLightmap" name="OneLayerLightmap"/&gt; <br>&nbsp;&nbsp;&nbsp; &lt;template material="Terrain/TwoLayer" name="TwoLayer"/&gt; <br>&nbsp;&nbsp;&nbsp; &lt;template material="Terrain/TwoLayerLightmap" name="TwoLayerLightmap"/&gt; <br>&nbsp;&nbsp;&nbsp; &lt;fog_replacement exp="Terrain/OneLayer_ps%fog_exp" exp2="Terrain/OneLayer_ps%fog_exp2" linear="Terrain/OneLayer_ps%fog_linear" none="Terrain/OneLayer_ps"/&gt; <br>&nbsp;&nbsp;&nbsp; &lt;fog_replacement exp="Terrain/TwoLayer_ps%fog_exp" exp2="Terrain/TwoLayer_ps%fog_exp2" linear="Terrain/TwoLayer_ps%fog_linear" none="Terrain/TwoLayer_ps"/&gt; <br>&nbsp;&nbsp;&nbsp; &lt;fog_replacement exp="Terrain/OneLayerLightmap_ps%fog_exp" exp2="Terrain/OneLayerLightmap_ps%fog_exp2" linear="Terrain/OneLayerLightmap_ps%fog_linear" none="Terrain/OneLayerLightmap_ps"/&gt; <br>&nbsp;&nbsp;&nbsp; &lt;fog_replacement exp="Terrain/TwoLayerLightmap_ps%fog_exp" exp2="Terrain/TwoLayerLightmap_ps%fog_exp2" linear="Terrain/TwoLayerLightmap_ps%fog_linear" none="Terrain/TwoLayerLightmap_ps"/&gt; <br>&nbsp; &lt;/materials&gt; </p>
<p>定义了一些材质模板。 </p>
<p>我没有深究其他的，只考虑TwoLayerLightmap这个材质。 </p>
<p>不记得是在哪个文件夹下，有一个文件FairyTerrain.material，其中就是地形的材质。 </p>
<p>我修改了一些内容，将&lt;lightmap&gt;设tex_coord = 0. &lt;layer0&gt;设tex_coord=1,&lt;layer1&gt;设tex_coord=2。这是因为我想让ETM原有的地形和天龙八部的地形共存，而原有地形纹理坐标刚好和&lt;lightmap&gt;纹理坐标相符合，所以设为同一层。 </p>
<p>这是我改过的材质 </p>
<p>material Terrain/TwoLayerLightmap <br>{&nbsp; </p>
<p>&nbsp;&nbsp; technique <br>&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pass <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; fragment_program_ref Terrain/TwoLayerLightmap_ps <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; texture_unit <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; texture_alias &lt;layer0&gt; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; texture &lt;layer0&gt; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tex_address_mode clamp <br>&nbsp;&nbsp;&nbsp;&nbsp; tex_coord_set 1 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; texture_unit <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; texture_alias &lt;layer1&gt; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; texture &lt;layer1&gt; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tex_address_mode clamp <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tex_coord_set 2 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; texture_unit <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; texture_alias &lt;lightmap&gt; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; texture &lt;lightmap&gt; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tex_address_mode clamp <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; tex_coord_set 0 </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>&nbsp;&nbsp;&nbsp; } </p>
<p>} </p>
<p>&lt;layer0&gt;，&lt;layer1&gt;，&lt;lightmap&gt;是一个pass中的3个texture_unit.也就是3层纹理。顾名思义&lt;layer0&gt;是第一层纹理，&lt;layer1&gt;是第二层纹理，&lt;lightmap&gt;是光照图纹理（阴影图），具体如何使用，如何使天龙八部的地形贴图资源对应到layer0,layer1，我下面会讲到。 </p>
<p>从FairyTerrain.cg中我们可以找到对应的shader。 <br>void TwoLayerLightmap_ps( <br>&nbsp;&nbsp;&nbsp; in float2 uv0 : TEXCOORD0, <br>&nbsp;&nbsp;&nbsp; in float2 uv1 : TEXCOORD1, <br>&nbsp;&nbsp;&nbsp; in float2 uvLightmap : TEXCOORD2, <br>&nbsp;&nbsp;&nbsp; in uniform sampler2D layer0, <br>&nbsp;&nbsp;&nbsp; in uniform sampler2D layer1, <br>&nbsp;&nbsp;&nbsp; in uniform sampler2D lightmap, <br>&nbsp;&nbsp;&nbsp; in float4 diffuse : COLOR0, <br>&nbsp;&nbsp;&nbsp; in float4 specular : COLOR1, <br>&nbsp;&nbsp;&nbsp; out float4 oColour : COLOR) <br>{ <br>&nbsp;&nbsp;&nbsp; float4 c0 = tex2D(layer0, uv0); <br>&nbsp;&nbsp;&nbsp; float4 c1 = tex2D(layer1, uv1); <br>&nbsp;&nbsp;&nbsp; float3 texturedColour = lerp(c0.rgb, c1.rgb, c1.a); <br>&nbsp;&nbsp;&nbsp; float4 lightmapColour = tex2D(lightmap, uvLightmap); <br>&nbsp;&nbsp;&nbsp; float4 baseColour = diffuse * lightmapColour; <br>&nbsp;&nbsp;&nbsp; float3 finalColour = baseColour.rgb * texturedColour + specular.rgb * (1-c0.a) * (1-c1.a) * lightmapColour.a; <br>&nbsp;&nbsp;&nbsp; float3 resultColour = Fogging(finalColour); <br>&nbsp;&nbsp;&nbsp; oColour = float4(finalColour, baseColour.a); <br>} </p>
<p>很容易看出其大致思路是&lt;layer1&gt;的alpha值控制&lt;layer0&gt;和&lt;layer1&gt;进行混合。 </p>
<p>可见，天龙八部的地形应该是部分像魔兽一样的格子式地形，部分权重图地形，也就是ETM原有的那种贴图模式，很多层纹理，然后又1-2层手动生成的纹理数据控制各层纹理的alpha值，达到混合的效果，只不过这里是只有一个alpha通道来控制纹理混合。</p>
<p>两层纹理的效果比单独一层纹理好的多，我用OneLayerLightmap材质试过，效果比较赫人... </p>
<h3>&nbsp;</h3>
<h3>地形纹理的实现</h3>
<p>&lt;lightmap&gt;纹理很明显，是一整张纹理贴到整个地形，没什么好说的。 </p>
<p>但&lt;layer0&gt; &lt;layer1&gt;这两层地形纹理应该怎么贴上去呢？ </p>
<p>对于材质中的这两层纹理，有两种可能。 </p>
<p>1.&lt;layer0&gt;,&lt;layer1&gt;本身只是材质模板中纹理的名字，没有实际意义，在实际的程序中会为每一块地形从材质模板继承一个模板，然后修改材质中纹理的名称。 </p>
<p>2.在程序中手动创建&lt;layer0&gt;,&lt;layer1&gt;。 </p>
<p>&nbsp; </p>
<p>先说两种不能实现的方法： </p>
<p>1. 在程序中手动创建&lt;layer0&gt;,&lt;layer1&gt;, 为极大极大的贴图（和真实的地形一样大），该贴图根据.Terrain和.Gridinfo中的信息来组成，和lightmap一样，整个贴到地形上。 </p>
<p>在小游戏，只有可能一个屏幕那么大的地形，也许可以用，而且效果可能不错，但在这种地形相对较大的游戏中是不可能的，首先，极大的浪费资源，一个地砖的纹理，可能被用到几十次几百次，那么在这个大纹理中，就会有几百个地砖纹理的拷贝，其次，不可能创建这么大的纹理（硬件不支持？反正我试过创建不出来..） </p>
<p>2. 像ETM一样，将所有要用到的纹理（假设有n张）一个一个作为texture_unit放在材质里面，然后用n/4张手动生成的纹理去控制这些纹理的alpha值。这个方法不是对于天龙八部的地形不是很现实，一般每个天龙八部的地形有大概十几个不同的纹理，如果用这个方法，每个pass一般支持8个texture_unit，十几个纹理，加n/4张控制纹理需要3-4个pass，效率似乎... 而且我们通过天龙的材质文件可以看出，游戏应该不是用的这个方法来实现的。 </p>
<p>3. 每一个格子都有自己独自的材质，修改每个格子材质中的&lt;layer0&gt;, &lt;layer1&gt;， 改为它需要的材质文件，如 "05武当\褐色土地底层.jpg&#8221; 相当于将每一个格子作为单独的mesh。这个是可以实现的，我试过，将ETM的TileSize设为1，然后生成每个Tile的时候修改其材质，成功了，地形也显示出来了，完全正确，但帧率..... 呵呵，debug模式下fps 大于0小于1... 到release也许可以到十几吧，我没试，显然是不能这样搞的...&nbsp; </p>
<p>我最后实现地形贴图用的是texture atlas,手动创建一张纹理，将所有要用到的地形纹理组合成一张大纹理，然后每一个顶点设基于这张大纹理的UV坐标，texture atlas比每个格子设材质更好的原因很显而易见，具体可以参考附件中所带的文章，《&#8220;Batch, Batch, Batch:&#8221;What Does It Really Mean?》中的第30页:Batch Breaker: Texture Change. </p>
<p>下图就是将wudang.Terrain中 </p>
<p>&lt;textures&gt; </p>
<p>&nbsp; &lt;textures&gt; </p>
<p>&nbsp;&nbsp;&nbsp; &lt;texture filename="03南海/岩石海礁01.jpg" type="image"/&gt; </p>
<p>&nbsp;&nbsp;&nbsp; &lt;texture filename="03南海/岩石海礁03.jpg" type="image"/&gt; </p>
<p>&nbsp;&nbsp;&nbsp; &lt;texture filename="05武当/褐色土地底层.jpg" type="image"/&gt; </p>
<p>&nbsp;&nbsp;&nbsp; &lt;texture filename="05武当/褐色土地上层.tga" type="image"/&gt; </p>
<p>&nbsp;&nbsp;&nbsp; &lt;texture filename="05武当/青砖地底层.tga" type="image"/&gt; </p>
<p>&nbsp;&nbsp;&nbsp; &lt;texture filename="13镜湖/镜湖桃花瓣.tga" type="image"/&gt; </p>
<p>&nbsp;&nbsp;&nbsp; ... ... </p>
<p>&nbsp; &lt;/textures&gt; </p>
<p>所定义的所有纹理组合成的一张大纹理。 </p>
<p><img height=416 alt="" src="http://images.cnblogs.com/cnblogs_com/syqking/atlas.JPG" width=733> </p>
<p>可以发现，天龙八部中的地形贴图大小是不同的，但最大是256x256(就我目前所知)，所以我干脆将每一格划为256x256，共可容纳有ROW_SIZExCOL_SIZE张小贴图，这样大贴图的大小应该是256*COL_SIZE x 256*ROW_SIZE。&nbsp; </p>
<p>我这台机器支持的最大纹理大小似乎为4096x4096，那么理论上因该可以最多容纳16*16张小贴图，绰绰有余了。这样虽然浪费一点空间，但可以很方便地通过ID索引贴图坐标。 </p>
<p>比如 &lt;pixmap bottom="0.2480469" left="0.00390625" right="0.4960938" textureId="2" top="0.001953125"/&gt; 通过这样一块pixmap的定义，我们可以根据textureId=2找到它所所在的位置。 </p>
<p>其所在行为textureId/COL_SIZE，所在列为textureId%COL_SIZE。如上面那张大纹理的COLE_SIZE = 8（一行有8张小贴图） </p>
<p>所以textureId=2的这张小贴图所在行row=0，坐在列col=2. </p>
<p>我们知道纹理坐标范围为0.0f-1.0f，所以textureId=2的小贴图左上角的UV坐标为U = (float)col/COL_SIZE = 0.25f , V = (float)row/ROW_SIZE = 0.0f. </p>
<p>再根据pixmap中的信息left ,right, top, bottom 可以计算出小贴图四个点的坐标。在创建顶点时将纹理坐标附上即可。 </p>
<p>具体的过程应该是 </p>
<p>1.手动创建名字为&lt;layer0&gt;的texture </p>
<p>代码如下： </p>
<p>TexturePtr layer0 = TextureManager::getSingletonPtr()-&gt;createManual( <br>&nbsp;&nbsp; "&lt;layer0&gt;", "General",TEX_TYPE_2D, <br>&nbsp;&nbsp; layerTextureWidth,layerTextureHeight, 1, 3, PF_BYTE_RGBA, TU_WRITE_ONLY ); </p>
<p>2. 将材质中的texture_unit &lt;layer1&gt;中的texture_name 由&lt;layer1&gt;改为&lt;layer0&gt;，因为我们两层用的是同一张纹理，没必要复制一遍，直接改名指向同一张纹理就行了。 </p>
<p>代码如下： </p>
<p>MaterialPtr material (MaterialManager::getSingleton().getByName("Terrain/TwoLayerLightmap")); <br>material-&gt;getTechnique(0)-&gt;getPass(0)-&gt;getTextureUnitState( 1 )-&gt;setTextureName( "&lt;layer0&gt;"); </p>
<p>3.读取.Texture文件，将要用到的纹理拼接为大纹理，如上面那张图。 </p>
<h3>&nbsp;</h3>
<h3>地形的顶点与索引</h3>
<p>&nbsp;</p>
<p>若地图为192x192，它就是应该有192*192个格子。一般情况下的做法下，它应该有193*193个顶点，织成一个网状，但由于我用的atlas， </p>
<p>可以知道，每一个非边缘的顶点将会有4个纹理坐标（左上，右上，左下，右下 ） </p>
<p>如下图 </p>
<p><img height=106 alt="" src="http://images.cnblogs.com/cnblogs_com/syqking/vertex.JPG" width=120> </p>
<p>中间的顶点要同时负责A块的右下，B块的左下，C块的右上，D块的左上。 </p>
<p>话说一个顶点确实是同时又多个纹理坐标的，只要设置不同的tex_coord。但天龙八部地形贴图一般有3层，&lt;layer0&gt;,&lt;layer1&gt;,&lt;lightmap&gt;，分别是两层地形，一层预处理的阴影。 </p>
<p>一层&lt;lightmap&gt;不用多说的，就是一张大纹理，每个顶点的坐标是u=col/terrainColSize, v=row/terrainRowSize. </p>
<p>另外两层就是我们需要考虑的，因为有两层，这样每个点不止同时负责4块，要同时负责两层共8块，这样这个pass的8个texture_unit都满了，必须再用一个pass来做&lt;lightmap&gt;那一层，效率不行。 </p>
<p>所以只好用另一方法，就是在非边缘的每一个位置，将4个顶点重合在一起，这4个顶点的纹理坐标不同，但位置相同，即每一个格子都有四个独立的顶点，相邻的两个格子有两个点重合。 </p>
<p>也就是说192x192的地图，需要有192*192*4个顶点。索引方式还是差不多，每一个格子需要6个索引，所以一共要192*192*6个索引。 </p>
<p>这样，ETM中生成顶点和索引的部分代码都需要改，生成顶点的代码在void Tile::createVertexData(size_t startx, size_t startz)中，生成索引的代码在void Tile::createIndexData()中。</p>
<p>天龙八部的.Terrain文件一般有这么一行&lt;scale x="100" y="100" z="100"/&gt;，说明地形在3个方向都是放大100倍，x,z本是一格大小为1x1为单位的，放大后即为100x100， </p>
<p>一个192x192的地形实际游戏中的大小应该为19200*19200，而天龙的坐标系是正中间坐标为.Terrain文件中的&lt;center&gt;的值，若不存在则中心为(0,0), 正方向为正，负方向为负，所以当&lt;center&gt;值为（0，0），192x192的地形实际坐标范围应该是 (-9600，-9600)到（9600，9600）。 </p>
<p>要注意的是不是将所有顶点作为一个mesh，而是应该根据.Terrain文件中的&lt;tileSize&gt;规定每一个TerrainTile的大小，每个TerrainTile中包含tileSize x tileSize 个地形网格，一个TerrainTile作为一个Entity插入到一个场景节点。 </p>
<p>顶点位置和索引考虑完了，该是要考虑每个顶点的纹理坐标的问题了。</p>
<p>要给每个顶点设UV必须用到.Gridinfo文件中的信息，该文件中定义了每一个格子对应的纹理信息。</p>
<p>具体的文件格式可参考，我在这就不赘述了。</p>
<p><a href="http://www.cppblog.com/mybios/archive/2009/07/26/91267.html" target=_blank><font color=#000000><u>http://www.cppblog.com/mybios/archive/2009/07/26/91267.html</u></font></a> </p>
<p>此处是正解，其他地方似乎多多少少都有错，特别是op=8的时候，要注意是与对角线两边的两个点（不是对角线上的点）从上面复制到下面。 </p>
<p>如图1应该是将左上角的顶点纹理坐标复制到右下角 </p>
<p>图2应该是将右上角的纹理坐标复制到左下角。&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </p>
<p><img height=89 alt="" src="http://images.cnblogs.com/cnblogs_com/syqking/mesh.JPG" width=95 border=0><img height=89 alt="" src="http://images.cnblogs.com/cnblogs_com/syqking/mesh_inverse.JPG" width=95 border=0> </p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 图1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 图2</p>
<p>但还有一处我有不同，op=4的时候，我觉得应该是顺时针转90度，测试下来似乎没问题。 </p>
<p>这是我的根据op操作UV坐标的代码，op=4的时候我貌似确实是在顺时针转吧&#8230;&#8230;</p>
<pre class=code><span style="COLOR: blue">void </span><span style="COLOR: #020002">changeGridInfoUV</span>( <span style="COLOR: #020002">AutoTexCoord</span>&amp; <span style="COLOR: #020002">leftTop</span>, <span style="COLOR: #020002">AutoTexCoord</span>&amp; <span style="COLOR: #020002">rightTop</span>, <span style="COLOR: #020002">AutoTexCoord</span>&amp; <span style="COLOR: #020002">leftBottom</span>, <span style="COLOR: #020002">AutoTexCoord</span>&amp; <span style="COLOR: #020002">rightBottom</span>, <span style="COLOR: #020002">uchar state</span>, <span style="COLOR: blue">bool </span><span style="COLOR: #020002">bIndex </span>)
{
<span style="COLOR: green">//0 不变
//1 图片水平翻转
//2 图片垂直翻转
//4 顺时针旋转度
//8 对角线上方顶点纹理坐标复制到对角线下方顶点。（与对角线垂直的两个顶点）
</span><span style="COLOR: #020002">uchar res1 </span>= <span style="COLOR: #020002">state</span>&amp;1;
<span style="COLOR: #020002">uchar res2 </span>= <span style="COLOR: #020002">state</span>&amp;2;
<span style="COLOR: #020002">uchar res3 </span>= <span style="COLOR: #020002">state</span>&amp;4;
<span style="COLOR: #020002">uchar res4 </span>= <span style="COLOR: #020002">state</span>&amp;8;
<span style="COLOR: blue">if</span>( <span style="COLOR: #020002">res1 </span>!= 0 )
{
<span style="COLOR: #020002">leftTop</span>.<span style="COLOR: #020002">Exchange</span>( <span style="COLOR: #020002">rightTop </span>);
<span style="COLOR: #020002">leftBottom</span>.<span style="COLOR: #020002">Exchange</span>( <span style="COLOR: #020002">rightBottom </span>);
}
<span style="COLOR: blue">if</span>( <span style="COLOR: #020002">res2 </span>!= 0 )
{
<span style="COLOR: #020002">leftTop</span>.<span style="COLOR: #020002">Exchange</span>( <span style="COLOR: #020002">leftBottom </span>);
<span style="COLOR: #020002">rightTop</span>.<span style="COLOR: #020002">Exchange</span>( <span style="COLOR: #020002">rightBottom </span>);
}
<span style="COLOR: blue">if</span>( <span style="COLOR: #020002">res3 </span>!= 0 )
{
<span style="COLOR: #020002">leftTop</span>.<span style="COLOR: #020002">Exchange</span>( <span style="COLOR: #020002">rightTop </span>);
<span style="COLOR: #020002">leftBottom</span>.<span style="COLOR: #020002">Exchange</span>( <span style="COLOR: #020002">rightTop </span>);
<span style="COLOR: #020002">rightBottom</span>.<span style="COLOR: #020002">Exchange</span>( <span style="COLOR: #020002">rightTop </span>);
}
<span style="COLOR: blue">if</span>( <span style="COLOR: #020002">res4 </span>!= 0 )
{
<span style="COLOR: green">// 非正常索引
</span><span style="COLOR: blue">if</span>( <span style="COLOR: #020002">bIndex </span>) {
(<span style="COLOR: #020002">leftBottom</span>.<span style="COLOR: #020002">setX</span>( <span style="COLOR: #020002">rightTop</span>.<span style="COLOR: #020002">getX</span>) );
<span style="COLOR: #020002">leftBottom</span>.<span style="COLOR: #020002">setY</span>( <span style="COLOR: #020002">rightTop</span>.<span style="COLOR: #020002">getY</span>() );
}
<span style="COLOR: green">// 正常索引
</span><span style="COLOR: blue">else </span>{
<span style="COLOR: #020002">rightBottom</span>.<span style="COLOR: #020002">setX</span>( <span style="COLOR: #020002">leftTop</span>.<span style="COLOR: #020002">getX</span>() );
<span style="COLOR: #020002">rightBottom</span>.<span style="COLOR: #020002">setY</span>( <span style="COLOR: #020002">leftTop</span>.<span style="COLOR: #020002">getY</span>() );
}
}
}</pre>
<h3>读取场景环境与模型</h3>
<p>&nbsp;</p>
<p>先阶段我读取了一部分场景，包括环境和一些模型，粒子等部分还没看，所以这个场景是不完整的，不过大概的轮廓都出来了。</p>
<pre class=code>读取场景其实就是用TinyXML读取.Scene中的各种XML项，然后根据读取的数据创建相应的场景节点，或设置相应的场景环境，如雾，skydome等。</pre>
<pre class=code>具体代码下载附件看吧，有点无聊，都是switch-case语句。</pre>
<pre class=code>但有一点一定要注意，在读取资源前一定要先调用一个函数</pre>
<pre class=code>setlocale( LC_CTYPE, "" );</pre>
<pre class=code>不然中文路径或文件名的.mesh文件是读不了的。</pre>
<pre class=code>地形和场景都搞定了，可以看看结果了！然而，悲剧出现了！</pre>
<pre class=code>远景的效果图，很明显，地形一格一格像有裂缝一样&#8230;&#8230;</pre>
<p><img height=601 alt="" src="http://images.cnblogs.com/cnblogs_com/syqking/far.JPG" width=802 border=0></p>
<p>&nbsp;</p>
<p>近处的效果图， 近了以后，就没地形的裂缝了&#8230;&#8230;</p>
<p><a href="http://www.cnblogs.com/syqking/admin/file:///C:/Documents%20and%20Settings/Administrator/Local%20Settings/Temp/WindowsLiveWriter-429641856/supfiles506AAB/near5.jpg" target=_blank></a><img height=599 alt="" src="http://images.cnblogs.com/cnblogs_com/syqking/near.JPG" width=799 border=0>&nbsp;</p>
<p><strong></strong>&nbsp;</p>
<h3>地形裂缝问题</h3>
<p>问了一下我的一个学长，自己也思考些时间。估计是由于是用的atlas texture, 然后一定距离后的mipmap和texture filtering，产生了裂缝的问题。 </p>
<p>通过附件中的《Improve Batching Using Texture Atlases 》在Applying Texture Filtering To Atlases节可以看讨论到texture filtering对texture atlas造成的影响，但是解决方案只是理论上的，并没有实现，一个是写shader,在不同的mipmap下调整纹理坐标，另一个是预留纹理，就是在已有纹理上加一圈和边缘相同的像素。文章中还提到：enabling anisotropic filtering minimizes these errors，我试了一下，设置了filtering anisotropic ， 并且将anisotropic_max 设为最大，结果却是略有好转，但帧率损失了50fps左右&#8230; 也许是我的显卡太水了？我最后还是没采用这个解决方案。</p>
<p><strong></strong></p>
<p>我最后的解决方案是 </p>
<p>1,手动创建纹理的时候，设置其mipmap的级别最多为3，这样就不会有更高级别的mipmap，导致更严重的失真。 </p>
<p>2,手动创建mipmap，而不是让其自动生成。比如原来是1024*1024大小的贴图，1级mipmap的大小应该为512*512，默认它是自动生成的，我设为手动生成，先把个小贴图缩小为50%，再把他们组成一张512x512的贴图作为原来的贴图的mipmap,一次类推手动生成3层mipmap.这样情况稍微有了点改善。 </p>
<p>3,小贴图合成大贴图之前，将他们统一resize到256*256的大小，在组合成大贴图，这样后感觉裂缝问题好转了不少。 </p>
<p>4,预留纹理坐标，虽然天龙八部开始就预留了纹理坐标，可以.Terrain文件中的纹理坐标很多都不是绝对的0.0f 0.25f 0.5f 等，都是一些 0.2480469 ，0.00390625，0.4960938等，我不知道它本是出于什么原因。但我用了这样的坐标还是不行，还是要把它设地更加靠内才能避免裂缝。 </p>
<p>所以读取纹理坐标的时候加上了一个fixFloat的过程，这个过程很不科学，但我试了一下似乎有点用处就用上了。</p>
<pre class=code><span style="COLOR: blue">static void </span><span style="COLOR: #020002">fixFloat</span>( <span style="COLOR: blue">float</span>&amp; <span style="COLOR: #020002">f </span>)
{
<span style="COLOR: blue">if</span>( <span style="COLOR: #020002">f </span>&lt; 0.01f )
<span style="COLOR: #020002">f </span>= 0.005f;
<span style="COLOR: blue">else if</span>( <span style="COLOR: #020002">f </span>&gt; 0.24f &amp;&amp; <span style="COLOR: #020002">f </span>&lt; 0.25f )
<span style="COLOR: #020002">f </span>= 0.245f;
<span style="COLOR: blue">else if</span>( <span style="COLOR: #020002">f </span>&gt; 0.25f &amp;&amp; <span style="COLOR: #020002">f </span>&lt; 0.26f )
<span style="COLOR: #020002">f </span>= 0.255f;
<span style="COLOR: blue">else if</span>( <span style="COLOR: #020002">f </span>&gt; 0.49f &amp;&amp; <span style="COLOR: #020002">f </span>&lt; 0.50f )
<span style="COLOR: #020002">f </span>= 0.495f;
<span style="COLOR: blue">else if</span>( <span style="COLOR: #020002">f </span>&gt; 0.5f &amp;&amp; <span style="COLOR: #020002">f </span>&lt; 0.51f )
<span style="COLOR: #020002">f </span>= 0.505f;
<span style="COLOR: blue">else if</span>( <span style="COLOR: #020002">f </span>&gt; 0.74f &amp;&amp; <span style="COLOR: #020002">f </span>&lt; 0.75f )
<span style="COLOR: #020002">f </span>= 0.745f;
<span style="COLOR: blue">else if</span>( <span style="COLOR: #020002">f </span>&gt; 0.75f &amp;&amp; <span style="COLOR: #020002">f </span>&lt; 0.76f )
<span style="COLOR: #020002">f </span>= 0.755f;
<span style="COLOR: blue">else if</span>( <span style="COLOR: #020002">f </span>&gt; 0.99f )
<span style="COLOR: #020002">f </span>= 0.995f;
}</pre>
<p>&nbsp; </p>
<p>这样也在一定程度上改善了裂缝的问题，但有的贴图看上去会不太吻合，但总比裂缝好。 </p>
<p>随后地形基本上看不出有裂缝了，但还是有一点痕迹。</p>
<p><a href="http://www.cnblogs.com/syqking/admin/file:///C:/Documents%20and%20Settings/Administrator/Local%20Settings/Temp/WindowsLiveWriter-429641856/supfiles506AAB/sample_terrain[6].jpg" target=_blank></a>&nbsp;</p>
<p><font size=3><img height=627 alt="" src="http://images.cnblogs.com/cnblogs_com/syqking/sample_terrain.JPG" width=805 border=0></font></p>
<p><font size=3>这个地形裂缝的问题困扰了我许久，最后的解决方案我也觉得不甚满意，不知道有哪位有好点的解决方案请告诉我。</font></p>
<p><br>&nbsp;</p>
<img src ="http://www.cppblog.com/liangairan/aggbug/107932.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2010-02-16 22:15 <a href="http://www.cppblog.com/liangairan/articles/107932.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>骨骼动画导论（转）</title><link>http://www.cppblog.com/liangairan/articles/105923.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Mon, 18 Jan 2010 02:57:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/105923.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/105923.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/105923.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/105923.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/105923.html</trackback:ping><description><![CDATA[转自<a href="http://blog.csai.cn/user1/261/archives/2008/28536.html">http://blog.csai.cn/user1/261/archives/2008/28536.html</a><br><br>先说明,这篇文章由我翻译自Evan Pipho的&lt;&lt;Focus On 3D Models&gt;&gt;一书的内关于骨骼动画第五章的内容,去掉了前面的说明和最后的Demo说明,包含了所有的理论内容,转载请注明出处,谢谢!<br><br>
<h3><span>理解骨骼动画</span></h3>
<p><span>骨骼动画是使用</span><span>&#8221;</span><span>骨头</span><span>&#8221;</span><span>来运动一个模型而不是通过手动编辑和移动每个顶点或面而实现的动画</span><span>.</span><span>每个顶点被附着到一根骨头</span><span>(</span><span>或有时是多根骨头</span><span>).</span><span>一根骨头或一个关节只是一组顶点的一个控制点</span><span>.</span><span>这些概念类似于我们身体里的关节</span><span>,</span><span>例如我们的膝关节或腕关节等</span><span>.</span><span>当骨头运动时</span><span>,</span><span>每个附着在它上的顶点也跟着运动</span><span>,</span><span>如图</span><span>5.2</span><span>显示的那样</span><span>.</span><span>甚至骨头自身的运动也会导致其它骨头的运动</span><span>.</span><span>这使得模型运动起来比较适当合理</span><span>,</span><span>因为在真实的生活中</span><span>,</span><span>身体的一部分运动会影响到身体的其它部分</span><span>.</span><span>所以</span><span>,</span><span>程序员需要能利用骨头来计算单个顶点的变换</span><span>.</span><span>虽然这会带来较多的工作量</span><span>,</span><span>可是结果证明这是值得的</span><span>.</span></p>
<p><span>&lt;!--[if !vml]--&gt;<img onmousewheel="return bbimg(this)" onclick=javascript:window.open(this.src); src="http://blog.csai.cn/UploadFiles/2008-6/630471595.jpg" onload=rsimg(this,500)>&lt;!--[endif]--&gt;</span></p>
<p><span>图</span><span>5.2 </span><span><span>顶点动画需要你移动每个顶点</span><span>,</span></span><span><span>而骨骼动画使你只需移动模型中的一根骨头</span><span>,</span></span><span><span>顶点将能随之而变</span><span>.</span></span></p>
<h3><span>骨骼动画的优点</span></h3>
<p><span>骨格动画比传统的顶点变换型动画有很多优点</span><span>,</span><span>正如你在前面章节里看到的那样</span><span>.</span></p>
<p><span>首先</span><span>,</span><span>也是对于玩家最直观的一点便是要增加真实感</span><span>.</span><span>基于骨骼运动的角色更趋于真实性</span><span>,</span><span>并常出现在比传统模型要求和周围事物有更强交互性的地方</span><span>.</span><span>要使模型更趋于运动真实性</span><span>,</span><span>则对基于骨骼的模型来说确是件简单的事</span><span>.</span><span>在传统的关键帧动画中</span><span>,</span><span>游戏会在两个位置中间进行线性插值</span><span>.</span><span>然而</span><span>,</span><span>在这种情况下</span><span>,</span><span>关节实际上并没有旋转</span><span>,</span><span>这对于生命体以旋转方式运动会有问题</span><span>.</span></p>
<p><span>这对用户而言</span><span>,</span><span>并不那么明显</span><span>,</span><span>但对程序员来说</span><span>,</span><span>那样的动画是否占用了较少的存储空间是非常重要的</span><span>.</span><span>用于代替为每帧动画都存储一组新的顶点</span><span>,</span><span>所有我们需要存储的只是骨头的旋转和平移信息</span><span>.</span><span>这可以节省巨大的存储空间</span><span>,</span><span>你仅仅只要加入<a name=OLE_LINK3></a><a name=OLE_LINK2></a><a name=OLE_LINK1><span><span><span>骨头</span>和<span>顶点</span></span></span></a></span><span><span><span><span>—</span></span></span></span><span><span><span><span>骨头附着</span></span></span></span><span><span><span><span>信息</span></span></span></span><span>.</span></p>
<p><span>这因骨骼动画需要而多加的一点存储空间可以用于保存更细致的模型</span><span>,</span><span>加入额外的支运动帧</span><span>,</span><span>或甚至可经适当改进后</span><span>,</span><span>用于游戏的其它地方</span><span>.</span><span>你能够添加更多细节至游戏的世界中</span><span>,</span><span>改进</span><span>A.I.</span><span>来提供一个更刺激的游戏</span><span>,</span><span>或者加入一些很酷的东东或复活节彩蛋</span><span>,</span><span>而这些都是原来你很想加入却因涉及空间问题而不能加入的</span><span>.</span></p>
<p><span>而另一个优点表现在那些创建游戏</span><span>3D</span><span>内容的美工上</span><span>.</span><span>一个好的骨骼动画系统将缩减美工做模型动画的时间</span><span>.</span><span>几乎每一个好的动画程序都已使用骨骼动画来确保模型运动的过程从美工到程序员</span><span>,</span><span>再到游戏</span><span>,</span><span>最后到玩家的平滑过渡</span><span>.</span><span>这加速了游戏内容的创建过程</span><span>,</span><span>也确保了当模型被导出成游戏所使用的格式时没有动画或特征的失真</span><span>.</span></p>
<p><span>一个更长远的优点是对程序员的另一个好处</span><span>(</span><span>这越来越好了</span><span>,</span><span>不是吗</span><span>?).</span><span>因为骨骼的自由性</span><span>,</span><span>这使得你可以随心所欲地实时定位它们</span><span>,</span><span>也可以实现在运行时创建动画</span><span>.</span><span>就相当于提供了一个更多样化的动画库</span><span>.</span><span>你甚至能够让游戏控制当身体碰到一个物体时的动作</span><span>,</span><span>或从一个斜坡上滑下来</span><span>.</span><span>这种技术是在玩的时候即时产生的</span><span>,<span>Unreal Tournament 2003&#8217;s Physics</span></span><span>系统</span><span>(http://www.epicgames.com)</span><span>里就有这样一个值得注意的例子</span><span>.</span><span>角色和模型在环境里面真实交互</span><span>,</span><span>包括从斜坡上滑下和靠在上面徐徐移动</span><span>.</span></p>
<p><span>骨骼动画有一个缺点就是相对于传统的关键帧动画</span><span>,</span><span>它不容易被理解和实现</span><span>.</span><span>本章将帮助你缓解任何你对骨骼动画的担心</span><span>.</span></p>
<h3><span>骨骼动画的原理</span></h3>
<p><span>首先来看看你的手臂</span><span>.</span><span>将你的手臂在你胸前展开并观察它</span><span>.</span><span>你的手臂有许多骨头</span><span>,</span><span>两根主要的大骨头</span><span>(two main ones),</span><span>还有手掌</span><span>(</span><span>译注</span><span>:</span><span>这里指腕关节到指尖的所有部分</span><span>,</span><span>下面说的手掌也一样</span><span>)</span><span>和手指上的一些骨头</span><span>.</span></p>
<p><span>移动你的手指</span><span>,</span><span>是不是它们移动</span><span>?</span><span>只移动你的手指</span><span>,</span><span>而你手臂上的其它部分并不会因此而移动</span><span>.</span><span>现在弯曲你的肘关节</span><span>,</span><span>不仅你的手臂移动了</span><span>,</span><span>而且你的手指和手掌也同样跟着移动了</span><span>.</span><span>如果不是这样的</span><span>,</span><span>那你的手臂和你的手掌及手指就变得断开了</span><span>,</span><span>且每部分都孤立得悬挂在空中</span><span>,</span><span>这可不太好</span><span>.</span></p>
<p><span>如何将这个手臂和骨骼动画关联在一起运动呢</span><span>?</span><span>你的手臂代表了</span><span>3D</span><span>模型的一部分</span><span>,</span><span>你的手指</span><span>,</span><span>手掌</span><span>,</span><span>以及手臂的上下各部分都是这个模型的一部分</span><span>.</span><span>不同的关节和骨头贯穿了你的手臂</span><span>,</span><span>如肩关节</span><span>,</span><span>肘关节</span><span>,</span><span>腕关节</span><span>,</span><span>还有指关节</span><span>.</span></p>
<p><span>这就是说</span><span>,</span><span>当你移动手臂</span><span>&#8221;</span><span>上游</span><span>&#8221;</span><span>的一根骨头时</span><span>,</span><span>任何在这根骨头的下游部分也都会跟着移动</span><span>.</span><span>这就是骨骼动画最基本的概念</span><span>.</span></p>
<p><span>这样就有一个美妙之处是</span><span>:</span><span>它可以允许你移动模型上的任何一根骨头</span><span>,</span><span>并渗透到下面的移动</span><span>,</span><span>应用到以这个移动为原点的下面的任何事物</span><span>. </span><span>例如</span><span>,</span><span>这允许你只需移动角色的肩膀</span><span>,</span><span>不需担心肘和手的移动位置</span><span>,</span><span>因为它们会自动移到正确位置</span><span>.</span><span>你也能够通过复位以确保它们会被自动更新</span><span>.</span><span>图</span><span>5.3</span><span>显示了一个关节和附着在这些关节上的顶点的例子</span><span>.</span></p>
<p><span>&lt;!--[if !vml]--&gt;<img onmousewheel="return bbimg(this)" onclick=javascript:window.open(this.src); src="http://blog.csai.cn/UploadFiles/2008-6/630466610.jpg" onload=rsimg(this,500)>&lt;!--[endif]--&gt;</span></p>
<p><span>图</span><span>5.3 </span><span><span>当执行骨骼动画时</span><span>,</span></span><span><span>你不必担心关节</span><span>,</span></span><span><span>或骨头之间的结合点</span><span>.</span></span><span><span>每个顶点实际上是附着</span><span>(</span></span><span><span>关联</span><span>)</span></span><span><span>到关节点的</span><span>,</span></span><span><span>而不是骨头</span><span>.</span></span></p>
<h3><span>根关节</span></h3>
<p><span>根关节是一个模型中的终端关节</span><span>.</span><span>任何其它关节都以自己的路径最终关联到根关节</span><span>.</span><span>任何对根关节的操作</span><span>,</span><span>如不论是平移或是旋转</span><span>,</span><span>都会影响到模型中的每个顶点</span><span>.</span><span>你可以认为根关节是控制所有其它关节的关节</span><span>.</span><span>简单修改根关节</span><span>,</span><span>你能使角色直立行走</span><span>,</span><span>或旋转他以让他倒过来在天花板上行走</span><span>—</span><span>所做的只是一个简单的调用</span><span>.</span></p>
<p><span>在每个模型里只有一个根关节</span><span>,</span><span>它没有父关节</span><span>.</span><span>根关节通常是许多骨头连接的地方</span><span>,</span><span>而不是需要一个小动画的地方</span><span>.</span><span>例如包括中部和下后部</span><span>,</span><span>但没有明确要求根关节一定要在模型中的某个准确位置</span><span>.</span><span>只要你愿意</span><span>,</span><span>每个模型都可以不同</span><span>.</span><span>图</span><span>5.4</span><span>显示了当根节点的位置和朝向被修改后会发生的变化</span><span>.</span></p>
<p><span>&lt;!--[if !vml]--&gt;<img onmousewheel="return bbimg(this)" onclick=javascript:window.open(this.src); src="http://blog.csai.cn/UploadFiles/2008-6/630330270.jpg" onload=rsimg(this,500)>&lt;!--[endif]--&gt;</span></p>
<p><span>图</span><span>5.4 </span><span><span>旋转或平移根关节将影响模型中其他所有的关节和项点</span><span>.<br><br></p>
<h3><span><span>先说明,这篇文章由我翻译自Evan Pipho的&lt;&lt;Focus On 3D Models&gt;&gt;一书的内关于骨骼动画第五章的内容,去掉了前面的说明和最后的Demo说明,包含了所有的理论内容,转载请注明出处,谢谢!</span><br></span></h3>
<h3><span>父关节和子关节</span></h3>
<p><span>一个关节可以有父关节和子关节</span><span>.</span><span>其父关节会影响到它做任何事情</span><span>.</span><span>父关节的旋转和平移会影响计算当前关节的新位置</span><span>.</span><span>再拿手臂的例子来说</span><span>,</span><span>肘关节是手掌的父关节</span><span>.</span><span>移动肘则影响手掌</span><span>.</span><span>在简单的骨骼动画里</span><span>,</span><span>每个关节只有一个父关节</span><span>,</span><span>如果有的话</span><span>.</span></p>
<p><span>但是</span><span>,</span><span>一个关节可以有许多子关节</span><span>.</span><span>子关节是相对于父关节来说的</span><span>.</span><span>任何你对父关节做的事都会渗透到子关节</span><span>.</span><span>换个角度来说就是</span><span>,</span><span>当前关节是所有在它下面的关节的父关节</span><span>.</span></p>
<p><span>&lt;!--[if !vml]--&gt;<img onmousewheel="return bbimg(this)" onclick=javascript:window.open(this.src); src="http://blog.csai.cn/UploadFiles/2008-6/630616024.jpg" onload=rsimg(this,500)>&lt;!--[endif]--&gt;</span></p>
<p><span>图</span><span>5.5 </span><span><span>父</span><span>-</span></span><span><span>子关节的关系</span></span></p>
<h3><span>骨骼动画的关键帧</span></h3>
<p><span>常规关键帧动画保存了多份顶点拷贝</span><span>,</span><span>骨骼动画系统也有关键帧</span><span>.</span><span>关键帧的再现是一个模型位置的瞬时状态</span><span>.</span></p>
<p><span>然而</span><span>,</span><span>不同于每个关键帧都包含其自身所有顶点的拷贝的方式</span><span>,</span><span>骨骼动画的关键帧或叫<span>骨骼帧</span></span><span>(boneframe)</span><span>包含了旋转和平移的变换信息</span><span>,</span><span>一般平移是一个</span><span>X,Y,Z</span><span>值的形式</span><span>,</span><span>和三个分别包含了按</span><span>X,Y,Z</span><span>轴旋转的值</span><span>.</span><span>正如常规顶点关键帧一样</span><span>,</span><span>这些骨骼帧必须被插值来提供平滑的动画效果</span><span>.</span></p>
<p><span>位置或平移的值可以在两个之间进行线性插值</span><span>,</span><span>就像你在传统关键帧动画里对顶点的操作一样</span><span>.</span><span>但旋转则有问题</span><span>,</span><span>简单的在两个值之间进行平移那样的插值会产生奇怪的效果</span><span>.</span><span>旋转将不是平滑的</span><span>,</span><span>它可能会加速和减速</span><span>,</span><span>这依赖于其自身的位置</span><span>.</span><span>如果两个旋转信息之间的差异比较大</span><span>,</span><span>模型可能像胶在一起的方块那样出现</span><span>&#8221;</span><span>渗出</span><span>(ooze)&#8221;.</span><span>这是因为使用线性插值</span><span>,</span><span>所有的东西都是沿着直线插值的</span><span>,</span><span>如果这用这种方式执行旋转就会产生奇怪的效果</span><span>,</span><span>因为旋转是沿着弧插值而不是直线</span><span>.</span><span>对弧形路径按两端点的直线方式而不是沿弧的路径方式就会产行</span><span>&#8221;</span><span>渗出</span><span>&#8221;</span><span>效果</span><span>.</span></p>
<p><span>解决这个问题的最好方法就是采用<span>四元数</span></span><span>.</span><span>这你在第二章就学会过了</span><span>.&#8221;</span><span>介绍四元数</span><span>&#8221;,</span><span>四元数最大的优点之一是他们可以容易地进行插值</span><span>.</span><span>不仅容易插值</span><span>,</span><span>而且能很容易地进行<span>球面</span>线性插值</span><span>.</span></p>
<p><span>球面线性插值</span><span>在球面上的两点之前进行插值</span><span>.</span><span>然而</span><span>,</span><span>相对于在两点之间以直线方式逼近</span><span>,</span><span>球面线性插值是沿着球表面的曲面的</span><span>.</span><span>你可以设想捡起一只球</span><span>,</span><span>比如一个篮球</span><span>,</span><span>并在上面标上两点</span><span>,</span><span>然后</span><span>,</span><span>用你的手指找出这两点间最短的路径</span><span>.</span><span>因为你的手指不能进入球的内部</span><span>,</span><span>于是这条在两点间的路径将是一条弧</span><span>.</span><span>这就是</span><span>SLERP</span><span>的处理方式</span><span>.</span><span>使用</span><span>SLERP</span><span>函数</span><span>,</span><span>旋转就可以沿着弧进行插值</span><span>,</span><span>创建出一个比较平滑的</span><span>,</span><span>顺眼的效果</span><span>.</span></p>
<h3><span>计算位置</span></h3>
<p><span>利用你在上面己经阅读到的信息</span><span>,</span><span>你可能想尝试实现骨骼动画了</span><span>.</span><span>然而</span><span>,</span><span>你还没有学习关于父关节是如何影响子关节的具体内容</span><span>.</span><span>简单地使用关键帧将使每个关节独立地移动</span><span>,</span><span>很可能产生一个奇怪的</span><span>,</span><span>扭曲的网格</span><span>.</span></p>
<p><span>这部分内容将告诉你关于如何更改它以使关节之间能正常运动</span><span>.</span><span>首先你要做的就是</span><span>创建一个变换矩阵</span><span>,</span><span>该矩阵用于每一个使用了不同旋转和平移关键帧数据</span><span>的</span><span>(</span><span>关键帧</span><span>)</span><span>点</span><span>.</span><span>这个变换矩阵可以通过先生成三个旋转矩阵</span><span>(</span><span>译注</span><span>:x,y,z</span><span>三个方向的旋转矩阵</span><span>)</span><span>和平移矩阵</span><span>,</span><span>这在第一章已经学过了</span><span>, </span><span>再将这三个矩阵相乘就会生成了最终的变换矩阵</span><span>.</span><span>你也可以使用</span><span>matrix</span><span>类的</span><span>SetRotation</span><span>和</span><span>SetTranslation</span><span>函数来避免你自己做矩阵乘法</span><span>.</span><span>这个生成的矩阵叫做<span>相对矩阵</span></span><span>(relative matrix).</span></p>
<p><span>下一步你需要计算出一个<span>绝对矩阵</span></span><span>(absolute matrix).</span><span>绝对矩阵是关节的相对矩阵乘上它的父关节的绝对矩阵得到的</span><span>.</span><span>绝对矩阵告诉你关节的绝对变换</span><span>(</span><span>译注</span><span>:</span><span>就是将关节的本地坐标变换为世界坐标</span><span>).</span><span>这包括了自身的相对变换</span><span>,</span><span>除此之外</span><span>,</span><span>层次结构中所有在它之前的关节的变换都已计算完成</span><span>.</span><span>这允许其他关节的移动作为移动关节链上游关节的结果</span><span>.</span><span>细想一下</span><span>,</span><span>例如</span><span>,</span><span>当你移动肩膀时肘又是怎么移动的</span><span>,</span><span>这引出了一个问题</span><span>:</span><span>你如何计算最初的绝对矩阵</span><span>?</span><span>记住</span><span>,</span><span>根关节是没有父关节的</span><span>,</span><span>因此</span><span>,</span><span>它的绝对矩阵就是它的相对矩阵</span><span>.</span></p>
<p><span>假如你以正确的顺序遍历了各关节</span><span>,</span><span>同时按此顺序计算出各个绝对矩阵</span><span>,</span><span>每个关节</span><span>(</span><span>的绝对矩阵</span><span>)</span><span>将会包含它父关节的变换</span><span>,</span><span>以及它父关节的父关节的变换等等</span><span>.</span><span>图</span><span>5.6</span><span>显示了当你变换一个关节前顾及所有先前</span><span>(</span><span>关节</span><span>)</span><span>的变换时发生的情况</span><span>.</span></p>
<p><span>&lt;!--[if !vml]--&gt;<img onmousewheel="return bbimg(this)" onclick=javascript:window.open(this.src); src="http://blog.csai.cn/UploadFiles/2008-6/630535123.jpg" onload=rsimg(this,500)>&lt;!--[endif]--&gt;</span></p>
<p><span>图</span><span>5.6 </span><span><span>遍历各关节</span><span>,</span></span><span><span>并顾及其所有前面的变换</span><span>.</span></span><span><span>注意哪怕只有一个关节要移动</span><span>,</span></span><span><span>在它下面的关节也得跟着移动</span><span>,</span></span><span><span>这很像移动你的臀部</span><span>,</span></span><span><span>则你的膝和踝也得跟着移动一样</span><span>.</span></span></p>
<p align=left><span>你是否只存储了子关节的索引</span><span>,</span><span>而没有储存关节的父关节索引</span><span>?</span><span>你立即要访问的索引集依赖于模型的格式</span><span>.</span><span>一些格式像</span><span>MS3D</span><span>同时给你每个关节的父关节索引</span><span>,</span><span>其它格式则可能给你子关节索引</span><span>.</span><span>使用子关节索引需要一个明显不同于使用父索引的方法</span><span>,</span><span>但一点都不会比后者难</span><span>.</span><span>你再次从根关节开始</span><span>,</span><span>在计算了根关节的变换矩阵之后</span><span>,</span><span>通过一个类似于</span><span>glPushMatrix</span><span>的命令将一个新的矩阵压入栈内</span><span>.</span><span>就创建了一个新的<span>世界变换矩阵</span></span><span>(world matrix)</span><span>的拷贝</span><span>,</span><span>这个世界变换矩阵是在被显示前执行了所有的几何变换的矩阵</span><span>.</span><span>现在用你的新矩阵乘以根关节的本地矩阵</span><span>.</span><span>结果就是世界变换矩阵</span><span>,</span><span>它在下一个骨头被画出之前将所有事物放置到正确位置</span><span>,</span><span>父关节的变换也被考虑在内了</span><span>.</span><span>举个例子</span><span>,</span><span>角色的臀部的旋转可能是某些动作的总合</span><span>.</span><span>因为膝关节和踝关节是臀关节的子关节</span><span>,</span><span>它们也要被旋转</span><span>.</span></p>
<p align=left><span>绘制函数像这样递归</span><span>,</span><span>它对自己的子关节继续调用绘制函数</span><span>,</span><span>每个子关节又对自己的子关节调用渲染函数</span><span>,</span><span>以此类推直到到达终端关节</span><span>(</span><span>没有子关节的关节</span><span>),</span><span>复位栈用一个</span><span>glPopMatrix</span><span>这样的命令</span><span>.</span><span>例如</span><span>,</span><span>当要绘制一个角色的一条腿时</span><span>,</span><span>新的矩阵被压入栈内用于计算膝关节</span><span>,</span><span>踝关节和脚关节</span><span>.</span><span>但是</span><span>,</span><span>如果要开始计算手臂时</span><span>,</span><span>你应该出格到处理腿之前的栈状态</span><span>,</span><span>否则</span><span>,</span><span>每当你移动腿时</span><span>,</span><span>手臂也将跟着移动了</span><span>.</span></p>
<p align=left><span>图</span><span>5.7</span><span>显示了一个递归渲染函数</span><span>.</span></p>
<p align=left><span>&lt;!--[if !vml]--&gt;<img onmousewheel="return bbimg(this)" onclick=javascript:window.open(this.src); src="http://blog.csai.cn/UploadFiles/2008-6/630574345.jpg" onload=rsimg(this,500)>&lt;!--[endif]--&gt;</span></p>
<p align=left><span>图</span><span>5.7 </span><span><span>渲染用子索引代替父索引存储的关节</span></span></p>
<h3><span>将网格<a name=OLE_LINK5></a><a name=OLE_LINK4><span>附着</span></a>到骨骼</span></h3>
<p><span>当你的关节己能平滑运动的时候</span><span>,</span><span>也是该将网格附着到骨骼的时候</span><span>.</span><span>网格</span><span>(mesh)</span><span>组成了模型的形状</span><span>,</span><span>它是由一组使模型具有立体感和细节的顶点和三角形组成</span><span>,</span><span>没有网格</span><span>,</span><span>基于骨骼运动的模型只是一个简单的骨架</span><span>.</span><span>每个网格的顶点存储了一个指向关节数组的索引</span><span>,</span><span>用于指示它被附着到某根骨头</span><span>.</span><span>现在</span><span>,</span><span>存储关节的方式决定了这些顶点的变换和渲染的方法</span><span>.</span></p>
<p><span>如果每个关节存储时带有其父关节的索引</span><span>,</span><span>同时你已经遍历和计算了最终的绝对矩阵</span><span>,</span><span>那么附着网格是很简单的</span><span>.</span><span>确保变换后的顶点保存到一个专门的地方</span><span>,</span><span>不要覆盖原始的顶点</span><span>.</span><span>这是因为在绝大多数格式里</span><span>,</span><span>骨骼帧不会被累积</span><span>,</span><span>每帧保存了从起点起的特定关节的旋转和平移的信息</span><span>.</span><span>如果你不回退到原始顶点</span><span>,</span><span>那么当每次计算新顶点位置时</span><span>,</span><span>模型将会飘乎不定得运动</span><span>.</span><span>图</span><span>5.8</span><span>中可以看出每个顶点单独附着到一个关节是什么意思</span><span>.</span></p>
<p><span>&lt;!--[if !vml]--&gt;<img onmousewheel="return bbimg(this)" onclick=javascript:window.open(this.src); src="http://blog.csai.cn/UploadFiles/2008-6/630592980.jpg" onload=rsimg(this,500)>&lt;!--[endif]--&gt;</span></p>
<p><span>图</span><span>5.8</span><span><span>顶点附着到关节</span></span></p>
<p>&nbsp;</p>
<p><span>现在你很可能在对自己说</span><span>,&#8221;</span><span>好的</span><span>,</span><span>我能运动一个模型的顶点</span><span>,</span><span>那三角形</span><span>,</span><span>法线</span><span>,</span><span>以及纹理坐标怎么处理呢</span><span>&#8221;? </span><span>这也正是骨骼模型真正开始闪光的地方</span><span>.</span><span>每个模型包含了一个纹理坐标的集合和一个三角形信息的集合</span><span>.</span><span>仅因为顶点的位置变更并不意味着三角形顶点索引和纹理坐标也会变化</span><span>.</span><span>这意思是说</span><span>,</span><span>当你第一次设置好它们之后就根本不需要再担心它们</span><span>.</span></p>
<p><span>法线则是另一回事了</span><span>,</span><span>因为多边形的朝向和顶点的位置更改了</span><span>,</span><span>因此法线也相应更改了</span><span>.</span><span>如果你正在使用面法线</span><span>,</span><span>那么你在每一帧将他们传给渲染器之前都需要重新计算</span><span>.</span><span>然而</span><span>,</span><span>如果你一开始就计算了顶点法线</span><span>,</span><span>那么你很幸运</span><span>,</span><span>顶点法线不必在变换后都完全重新计算</span><span>,</span><span>它们能利用和顶点相同的变换矩阵来进行变换</span><span>.</span><span>唯一不同的是</span><span>,</span><span>你不需要考虑平移</span><span>.</span><span>使用</span><span>matrix</span><span>类的</span><span>Transform3()</span><span>函数将旋转你的顶点法向量</span><span>,</span><span>同时仍能保留其原始大小</span><span>.</span></p>
<p align=left><span>如果关节保存了子关节的索引</span><span>,</span><span>并且你使用</span><span>glPushMatrix</span><span>将当前的变换矩阵压入栈内</span><span>,</span><span>那么渲染模型就会变得十分容易</span><span>.</span><span>对这种情况</span><span>,</span><span>没有必要在显示之前变换每个顶点</span><span>.</span><span>不管渲染任何东西都没有更改的必要</span><span>.</span><span>任何你要渲染的现在将被适当变换</span><span>,</span><span>甚至面法线</span><span>.</span><span>另一个需要考虑的问题是</span><span>,</span><span>顶点如何附着到多于一根的骨头</span><span>.</span><span>此时</span><span>,</span><span>每根骨头将被赋予一个权重</span><span>,</span><span>这个权重决定它影响关节点的比重</span><span>.</span><span>最终的变换是所有这些被附着的骨头的变换的加权平均</span><span>.</span></p>
</span></span>
<img src ="http://www.cppblog.com/liangairan/aggbug/105923.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2010-01-18 10:57 <a href="http://www.cppblog.com/liangairan/articles/105923.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Ogre骨骼动画融合（转）</title><link>http://www.cppblog.com/liangairan/articles/105922.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Mon, 18 Jan 2010 02:47:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/105922.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/105922.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/105922.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/105922.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/105922.html</trackback:ping><description><![CDATA[转自<a href="http://www.guibian.com/article.asp?id=50">http://www.guibian.com/article.asp?id=50</a><br><br>很多商业引擎都支持骨骼动画融合这个功能。<br>融合可以让你的动画自然过渡到下一个动画，你可以精确控制下一个动画播放时间等信息。<br><br>Ogre其实也支持动画的融合，只是Ogre并没有给出代码级的支持。<br>PS：Ogre很有意思，他天然支持很多东西，但是都没有实现出来，需要你来实现。<br><br>好的，下面来通过代码看看如果进行一个简单的骨骼动画融合。<br>
<div class="UBBPanel codePanel">
<div class=UBBTitle><img style="MARGIN: 0px 2px -3px 0px" alt=程序代码 src="http://www.guibian.com/images/code.gif"> 程序代码</div>
<div class=UBBContent><br><br>class AnimationBlender<br>{<br>public:<br>&nbsp;&nbsp;&nbsp;&nbsp;enum BlendingTransition&nbsp;&nbsp;//不同的混合方式<br>&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;BlendSwitch,&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 直接切换到目标动画<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;BlendWhileAnimating,&nbsp;&nbsp; // 交叉淡入淡出（源动画比例缩小，同时目标动画比例增大）<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;BlendThenAnimate&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 淡出源动画到目标动画第一帧，然后开始目标动画 <br>&nbsp;&nbsp;&nbsp;&nbsp;};<br><br>private:<br>&nbsp;&nbsp;&nbsp;&nbsp;Entity *mEntity;<br>&nbsp;&nbsp;&nbsp;&nbsp;AnimationState *mSource;<br>&nbsp;&nbsp;&nbsp;&nbsp;AnimationState *mTarget;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;BlendingTransition mTransition;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;bool loop; //是否循环<br><br>&nbsp;&nbsp;&nbsp;&nbsp;~AnimationBlender() {}<br><br>public: <br>&nbsp;&nbsp;&nbsp;&nbsp;Real mTimeleft, mDuration; //持续时间<br><br>&nbsp;&nbsp;&nbsp;&nbsp;bool complete;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;void blend( const String &amp;animation, BlendingTransition transition, Real duration, bool l );<br>&nbsp;&nbsp;&nbsp;&nbsp;void addTime( Real );<br>&nbsp;&nbsp;&nbsp;&nbsp;Real getProgress() { return mTimeleft/ mDuration; }<br>&nbsp;&nbsp;&nbsp;&nbsp;AnimationState *getSource() { return mSource; }<br>&nbsp;&nbsp;&nbsp;&nbsp;AnimationState *getTarget() { return mTarget; }<br>&nbsp;&nbsp;&nbsp;&nbsp;AnimationBlender( Entity *);<br>&nbsp;&nbsp;&nbsp;&nbsp;void init( const String &amp;animation );<br>};<br></div>
</div>
<br><br>实现的代码<br>
<div class="UBBPanel codePanel">
<div class=UBBTitle><img style="MARGIN: 0px 2px -3px 0px" alt=程序代码 src="http://www.guibian.com/images/code.gif"> 程序代码</div>
<div class=UBBContent><br>void AnimationBlender::init(const String &amp;animation)<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;//初始化所有动作的AnimationState<br>&nbsp;&nbsp;&nbsp;&nbsp;AnimationStateSet *set = mEntity-&gt;getAllAnimationStates();<br>&nbsp;&nbsp;&nbsp;&nbsp;AnimationStateIterator it = set-&gt;getAnimationStateIterator();<br>&nbsp;&nbsp;&nbsp;&nbsp;while(it.hasMoreElements())<br>&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AnimationState *anim = it.getNext();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;anim-&gt;setEnabled(false);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;anim-&gt;setWeight(0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;anim-&gt;setTimePosition(0);<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;//初始化mSource<br>&nbsp;&nbsp;&nbsp;&nbsp;mSource = mEntity-&gt;getAnimationState( animation );<br>&nbsp;&nbsp;&nbsp;&nbsp;mSource-&gt;setEnabled(true);<br>&nbsp;&nbsp;&nbsp;&nbsp;mSource-&gt;setWeight(1);<br>&nbsp;&nbsp;&nbsp;&nbsp;mTimeleft = 0;<br>&nbsp;&nbsp;&nbsp;&nbsp;mDuration = 1;<br>&nbsp;&nbsp;&nbsp;&nbsp;mTarget = 0;<br>&nbsp;&nbsp;&nbsp;&nbsp;complete=false;<br>} <br>void AnimationBlender::blend( const String &amp;animation, BlendingTransition transition, Real duration, bool l )<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;loop=l; //设置是否需要循环<br>&nbsp;&nbsp;&nbsp;&nbsp;if( transition == AnimationBlender::BlendSwitch )<br>&nbsp;&nbsp;&nbsp;&nbsp;{//如果混合方式为直接切换，改变mSource 即可<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if( mSource != 0 )<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mSource-&gt;setEnabled(false);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mSource = mEntity-&gt;getAnimationState( animation );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mSource-&gt;setEnabled(true);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mSource-&gt;setWeight(1);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mSource-&gt;setTimePosition(0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mTimeleft = 0;<br>&nbsp;&nbsp;&nbsp;&nbsp;} <br>&nbsp;&nbsp;&nbsp;&nbsp;else <br>&nbsp;&nbsp;&nbsp;&nbsp;{ <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//先取得新的动画状态<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;AnimationState *newTarget = mEntity-&gt;getAnimationState( animation );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if( mTimeleft &gt; 0 ) //前一次的混合尚未结束<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if( newTarget == mTarget )<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 新的目标就是正在混合中的目标，什么也不做<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else if( newTarget == mSource )<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 新的目标是源动画，直接go back<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mSource = mTarget;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mTarget = newTarget;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mTimeleft = mDuration - mTimeleft; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 现在newTarget是真的新的动画了<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if( mTimeleft &lt; mDuration * 0.5 ) //上一次的混合进度还未超过一半<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 简单替换Target就行了<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mTarget-&gt;setEnabled(false);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mTarget-&gt;setWeight(0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else //如果已经过半，旧的target成为新的source<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mSource-&gt;setEnabled(false);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mSource-&gt;setWeight(0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mSource = mTarget;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;} <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mTarget = newTarget;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mTarget-&gt;setEnabled(true);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mTarget-&gt;setWeight( 1.0 - mTimeleft / mDuration );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mTarget-&gt;setTimePosition(0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else //上次的混合已经结束，当前未处于混合状态中<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mTransition = transition;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mTimeleft = mDuration = duration;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mTarget = newTarget;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mTarget-&gt;setEnabled(true);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mTarget-&gt;setWeight(0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mTarget-&gt;setTimePosition(0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br>void AnimationBlender::addTime( Real time )<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;if( mSource != 0 ) //若无AnimationState则不进行操作<br>&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if( mTimeleft &gt; 0 ) //两个动画仍在混合过程中<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mTimeleft -= time; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if( mTimeleft &lt; 0 )<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 混合完毕,切换到目标动画<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mSource-&gt;setEnabled(false);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mSource-&gt;setWeight(0);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mSource = mTarget;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mSource-&gt;setEnabled(true);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mSource-&gt;setWeight(1);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mTarget = 0;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;// 仍然处于混合状态中，改变两个动画的权值<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mSource-&gt;setWeight(mTimeleft / mDuration);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mTarget-&gt;setWeight(1.0 - mTimeleft / mDuration);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//在这种混合方式下，需要为目标动画增加时间<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if(mTransition == AnimationBlender::BlendWhileAnimating)<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mTarget-&gt;addTime(time);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;if (mSource-&gt;getTimePosition() &gt;= mSource-&gt;getLength())<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;complete=true;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;else<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;complete=false;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mSource-&gt;addTime(time);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;mSource-&gt;setLoop(loop);<br>&nbsp;&nbsp;&nbsp;&nbsp;}<br>}<br>AnimationBlender::AnimationBlender( Entity *entity ) : mEntity(entity){}<br></div>
</div>
<br><br><br>哈哈，相信你已经看出来，这段代码有很多很多的不足。比如只有骨骼动画的融合，<br>而且还是写死的，必须0.5。<br><br>我现在已经扩展了这些功能，包括做到了骨骼动画与定点动画的融合，变形动画的融合等等。而且可以精确控制融合的时机。你有什么好想法也欢迎与我讨论
<img src ="http://www.cppblog.com/liangairan/aggbug/105922.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2010-01-18 10:47 <a href="http://www.cppblog.com/liangairan/articles/105922.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Skeletal Animation Blending（转）</title><link>http://www.cppblog.com/liangairan/articles/105794.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Sat, 16 Jan 2010 02:37:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/105794.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/105794.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/105794.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/105794.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/105794.html</trackback:ping><description><![CDATA[转自：<a href="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/Features.htm">http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/Features.htm</a><br><br>
<h1>2 Features</h1>
<p><br>This section describes the major features of DM2, in basic form.&nbsp; While the functionality of these features will be discussed, more detailed information about their implementation will be given later. </p>
<p align=center><a href="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/Features.htm#2.1"><img height=65 src="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/images/DM2Bonesw.jpg" width=100 border=0></a>&nbsp; <a href="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/Features.htm#2.2"><img height=65 src="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/images/DM2Morphw.jpg" width=100 border=0></a>&nbsp; <a href="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/Features.htm#2.3"><img height=65 src="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/images/DM2Subsetsw.jpg" width=100 border=0></a>&nbsp;<a href="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/Features.htm#2.4"><img height=65 src="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/images/DM2Material.jpg" width=100 border=0></a><br>&nbsp;<a href="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/Features.htm#2.5"><img height=65 src="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/images/DM2Blendw.jpg" width=100 border=0></a>&nbsp; <a href="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/Features.htm#2.6"><img height=65 src="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/images/DM2Zipw.jpg" width=100 border=0></a>&nbsp; <a href="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/Features.htm#2.7"><img height=65 src="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/images/DM2CEFw.jpg" width=100 border=0></a>&nbsp; <a href="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/Features.htm#2.8"><img height=65 src="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/images/DM2SDKw.jpg" width=100 border=0></a></p>
<hr noShade SIZE=1>
<h3 align=left><a name=2.1></a>2.1 Skeletal Animation </h3>
<p align=left>DirectMesh v2.0 will use skeletal animation as it's primary form of frame-based movement.&nbsp; Skeletal animation is based on bones, which are in turn connected to individual vertices.&nbsp; These vertices act as a "skin" on top of the bones.&nbsp; </p>
<p align=left>Unlike keyframe animation, where each and every vertex is animated, skeletal animation consists of transforming only the bones.&nbsp; The bones then act on the assigned vertices, providing the expected deformation.&nbsp; Since this is how the human body actually works, it is an ideal scheme for character animation.</p>
<p align=left>These vertex deformations will be processed in a vertex shader, on GPU hardware.&nbsp; If the required hardware is not available, the animation interfaces will fall back to a software implementation.&nbsp; Leveraging the GPU to perform these transforms will minimize the performance hit that is normally associated with skeletal animation.</p>
<p align=left>The software implementation will also be available for use even if hardware support is present.&nbsp; This could be useful in situations where the same pose must be rendered multiple times.&nbsp; In this case, it is a waste to skin the mesh for each time, considering that it can be done in software once and applied to all subsequent renders.</p>
<p align=left>Each vertex will have a maximum of 4 attached bones.&nbsp; This is to accommodate for the limitations of current graphics hardware, file size, and memory usage.&nbsp; A case of 5 or more bones acting on a single vertex is usually uncommon, anyways.</p>
<p align=left>This maximum of 4 attached bones will be able to be changed at runtime.&nbsp; This can be useful in situations where dynamic level-of-detail (LOD) is critical.&nbsp; For example, if a model is far away from the viewpoint, the number of active bones can be lowered to prevent wasted transforms.&nbsp; If the models moves closer to the viewpoint, the number of active bones can be raised again.</p>
<hr noShade SIZE=1>
<h3 align=left><a name=2.2></a>2.2 Morph Targets</h3>
<p align=left>A morph target is a keyframe of a mesh (or a portion of a mesh) in a specific pose.&nbsp; Each morph target is blended with the base mesh by a certain percentage, where 0% has no effect on the mesh and 100% has full effect on the mesh.&nbsp; This percentage can be animated, providing smooth transitions between targets.&nbsp; When a morph target exhibits an effect on the base mesh (that is, it's blend percentage is greater than 0), it is said to be "active".&nbsp; </p>
<p align=left>This morphing technique is very useful for situations where a high amount of detail is required.&nbsp; Since using bones is not practical in such situations,&nbsp; morph targets are used instead.&nbsp; A prime example is facial animation, since individual vertices must be tweaked to get the exact pose required.</p>
<p align=left>Multiple morph targets can be active during the same frame.&nbsp; For example, the face of a character may have a "winking" morph target and a "smiling" morph target.&nbsp; These two targets can be blended together with the base mesh to generate a model that is both "winking" and "smiling".</p>
<p align=left>Due to the complexity of morph targets, they must be processed in software.&nbsp; For this reason, there is no limit to the number of morph targets per mesh.&nbsp; However, more morph targets will result in increased memory usage and file size.&nbsp; Also, there is no limit to the number of active morph targets, although more active morph targets will result in a performance hit.</p>
<p align=left><a href="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/Features.htm#2.1">Skeletal animation</a> and morphing can both be used simultaneously.&nbsp; After all of the active morph targets are composted into a dynamic buffer, the processed mesh will be deformed by the skinning shader.&nbsp; This provides a very flexible solution, in which both quick, efficient animation and detailed animation can be combined.</p>
<p align=left>For a practical example of morphing, consider our friend Johnny:</p>
<p align=center><img height=350 src="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/images/johnny1.jpg" width=300 border=0></p>
<p>Johnny has the following morph targets defined:</p>
<p align=center><img height=350 src="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/images/johnny2.jpg" width=300 border=0><img height=350 src="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/images/johhny3.jpg" width=300 border=0><img height=350 src="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/images/johhny4.jpg" width=300 border=0></p>
<p align=left>We can now combine these morph targets in any way that we like.&nbsp; For example:</p>
<p align=center><img height=350 src="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/images/johnny5.jpg" width=300 border=0></p>
<hr noShade SIZE=1>
<h3 align=left><a name=2.3></a>2.3 Subsets</h3>
<p align=left>A subset is a group of faces that share the same <a href="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/Features.htm#2.4">material</a>.&nbsp; A DirectMesh model can be divided up into multiple subsets, allowing for different materials to be used throughout the mesh.&nbsp; This can be very useful when modeling, since most real-world objects and characters are actually composites of different parts.</p>
<p align=left>Depending on the number of active bones, there may be more transformation matrices than constant registers in the skinning shader.&nbsp; If this occurs, the mesh must be split up into multiple subsets, based on each face's active bones.&nbsp; This is done so that the shader will always have registers available for the matrices.</p>
<p align=left>Below is a table of the default maximum number of bones per subset, based on each vertex shader model.&nbsp; Note that 12 or 16 constant registers will always be left over, in case the shader needs to store custom data.&nbsp; Since each bone transformation matrix is 3x4 (3 rows by 4 columns; the perspective vector does not need to be store), every bone takes up 3 constants (each constant register holds a float4, and a matrix is 3 float4's).</p>
<div align=center>
<table id=table1 width=800 border=0>
    <tbody>
        <tr>
            <td align=middle bgColor=#990000><strong><font face=Verdana color=#ffffff size=2>Vertex Shader Model</font></strong></td>
            <td align=middle bgColor=#990000><strong><font face=Verdana color=#ffffff size=2>Minimum Constants Available</font></strong></td>
            <td align=middle bgColor=#990000><font face=Verdana color=#ffffff size=2><strong>Constants Left Over</strong></font></td>
            <td align=middle bgColor=#990000><strong><font face=Verdana color=#ffffff size=2>Bone Matrices Available</font></strong></td>
        </tr>
        <tr>
            <td align=middle bgColor=#cccccc><font face=Verdana size=2>vs_1_1</font></td>
            <td align=middle bgColor=#cccccc><font face=Verdana size=2>96</font></td>
            <td align=middle bgColor=#cccccc><font face=Verdana size=2>12</font></td>
            <td align=middle bgColor=#cccccc><font face=Verdana size=2><strong>28</strong></font></td>
        </tr>
        <tr>
            <td align=middle bgColor=#cccccc><font face=Verdana size=2>vs_2_0</font></td>
            <td align=middle bgColor=#cccccc><font face=Verdana size=2>256</font></td>
            <td align=middle bgColor=#cccccc><font face=Verdana size=2>16</font></td>
            <td align=middle bgColor=#cccccc><font face=Verdana size=2><strong>80</strong></font></td>
        </tr>
        <tr>
            <td align=middle bgColor=#cccccc><font face=Verdana size=2>vs_2_x</font></td>
            <td align=middle bgColor=#cccccc><font face=Verdana size=2>256</font></td>
            <td align=middle bgColor=#cccccc><font face=Verdana size=2>16</font></td>
            <td align=middle bgColor=#cccccc><font face=Verdana size=2><strong>80</strong></font></td>
        </tr>
        <tr>
            <td align=middle bgColor=#cccccc><font face=Verdana size=2>vs_3_0</font></td>
            <td align=middle bgColor=#cccccc><font face=Verdana size=2>256</font></td>
            <td align=middle bgColor=#cccccc><font face=Verdana size=2>16</font></td>
            <td align=middle bgColor=#cccccc><font face=Verdana size=2><strong>80</strong></font></td>
        </tr>
    </tbody>
</table>
</div>
<div align=center>
<p align=left>It is important to note that these values are only the recommended defaults.&nbsp; A new value may be specified at runtime (the maximum number of active bones per subset is 255), and the subsets will be reconfigured automatically.&nbsp; This is to accommodate for both shaders that need additional constant space and for substantially different hardware.
<p align=left>Since each subset has different device-related properties, each subset must be rendered independently.&nbsp; For this reason, it is best to keep the number of subsets as low as possible.<br>&nbsp;
<hr noShade SIZE=1>
<h3 align=left><a name=2.4></a>2.4 Material Manager</h3>
<p align=left>DM2 files can contain information defining a mesh's material.&nbsp; A material includes the selected shader, constant values, and textures.&nbsp; This will allow the artist to easily export the material properties along with the base mesh and its animations.</p>
<p align=left>At runtime, the DirectMesh interfaces can automatically load all vertex shader, pixel shader, and effect files.&nbsp; Textures can also be dynamically loaded.&nbsp; Before rendering, DirectMesh will setup the shader, its constants, and the textures.&nbsp; No outside intervention is necessary.&nbsp; However, since this tightly integrates the material system and DirectMesh, complete material management will be optional.&nbsp; Some developers may prefer to handle material-related events themselves.</p>
<hr noShade SIZE=1>
<h3 align=left><a name=2.5></a>2.5 Animation Blending</h3>
<p align=left>An Animation Stage represents an individual, active animation that can be blended with other stages to form the final pose of the mesh.&nbsp; In order to combine the Animation Stages, two types of animation blending are implemented within the pipeline:&nbsp; additive blending and transitional blending.</p>
<h4 align=left><a name=2.5.1></a>2.5.1 Additive Blending</h4>
<p align=left>Additive blending is used to combine multiple Animation Stages.&nbsp; Essentially, it overlays multiple Animation Stages on top of one another, enabling multiple animations to be playing at the same time.&nbsp;&nbsp; There is no limit to the number of additive blends, but each additional stage will produce a small performance hit.</p>
<p align=left>To ensure that the bones are correctly blended together, the artist can flag each bone as "active" or "inactive" for each Animation Stage.&nbsp; The "active" bones from the different Animation Stages will then be combined to form the final pose.&nbsp; This effectively masks bones that are not essential to each animation from the bones that are.&nbsp; For example, a "Walking" animation may displace the leg and arm bones.&nbsp; However, only the leg bones are flagged as active, since they are essential to the "Walking" animation.&nbsp; The arm bones are not essential (since you can walk and do different things with your arms at the same time), so they can be used in a different blending stage.</p>
<p align=left>All of the active animations are organized into a hierarchy.&nbsp; If a conflict between animations occur (they both have the same bone(s) active at the same time), then the animation higher in the hierarchy will override the lower animation.&nbsp; If the complete bone set is not flagged as active between the blended animations, then the highest animation will use the position of it's bones.&nbsp; Also, if more than one blended animation has at least one active <a href="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/Features.htm#2.2">morph target</a>, then the morphs in the higher animation will override the morphs in the lower animation(s).</p>
<p align=left>Take the following as an example:</p>
<div align=center>
<table id=table2 style="BORDER-COLLAPSE: collapse" cellPadding=0 width=500 border=0>
    <tbody>
        <tr>
            <td colSpan=2>&nbsp;</td>
        </tr>
        <tr>
            <td><img src="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/images/Blending_Walk.png" border=0></td>
            <td><img height=450 src="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/images/Blending_WalkActive.png" width=250 border=0></td>
        </tr>
        <tr>
            <td colSpan=2>&nbsp;</td>
        </tr>
        <tr>
            <td colSpan=2>
            <p align=center>The "Walk" pose.&nbsp; This is the first animation in the blending sequence.&nbsp; On the right, the active bones are highlighted in red.</p>
            </td>
        </tr>
        <tr>
            <td colSpan=2>&nbsp;</td>
        </tr>
        <tr>
            <td><img height=450 src="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/images/Blending_Fire.png" width=250 border=0></td>
            <td><img height=450 src="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/images/Blending_FireActive.png" width=250 border=0></td>
        </tr>
        <tr>
            <td colSpan=2>
            <p align=center>&nbsp;</p>
            </td>
        </tr>
        <tr>
            <td colSpan=2>
            <p align=center>The "Fire" pose.&nbsp; This is the second animation in the blending sequence.&nbsp; On the right, the active bones are highlighted in red.</p>
            </td>
        </tr>
        <tr>
            <td colSpan=2>&nbsp;</td>
        </tr>
        <tr>
            <td colSpan=2>
            <p align=center><img height=450 src="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/images/Blending_Final.png" width=250 border=0></p>
            </td>
        </tr>
        <tr>
            <td colSpan=2>&nbsp;</td>
        </tr>
        <tr>
            <td colSpan=2>
            <p align=center>The Final pose.&nbsp; Note that the active bones from the "Walk" pose are combined with the active bones from the "Fire" pose.&nbsp; The bones that aren't flagged as active between "Walk" and "Fire" are taken from "Fire".</p>
            </td>
        </tr>
    </tbody>
</table>
</div>
<h4 align=left><a name=2.5.2></a>2.5.2 Transitional Blending</h4>
<p align=left>Transitional blending is used to blend the end of one animation with the beginning of another.&nbsp; For example, a "Walking" animation can seamlessly transition to a "Running" animation.&nbsp; This is achieved by blending a decrementing percentage of the current animation with an incrementing percentage of the future animation.</p>
<p align=left>Whereas additive blending blends separate Animation Stages together, transitional blending is used to compose an individual Animation Stage.&nbsp; It never blends two stages together - just individual animations, creating it's own dynamic Animation Stage.&nbsp; In effect, stages created with the Transitional Blender are fed back into the Additive Blender, allowing them to be combined with other stages.&nbsp; </p>
<p align=left>This is useful if only one of the active stages needs to transition.&nbsp; Referring the examples from above, assume that the "Walking" animation is being blended with the "Firing" animation.&nbsp; The "Walking" animation can then be transitioned into the "Running" animation, while still being blended with the "Firing" animation.</p>
<p align=left>Also, additive blending combines Animation Stages, based on each stage's set of active bones.&nbsp; Transitional blending, on the other hand, simply combines all bones.&nbsp; The set of active bones in the new Animation Stage are determined by combining both sets of active bones from the animations being transitioned between.&nbsp; </p>
<hr noShade SIZE=1>
<h3 align=left><a name=2.6></a>2.6 File Compression</h3>
<p align=left>To maintain an acceptable disk footprint, DM2 files can be compressed.&nbsp; <a href="http://www.gzip.org/zlib/">zLib</a> will be used to deflate and inflate the files.&nbsp; File compression is optional, allowing for custom file processing (ie custom compression and/or encryption) or a shorter loading time.</p>
<hr noShade SIZE=1>
<h3 align=left><a name=2.7></a>2.7 Common Exporter Framework</h3>
<p align=left>The Common Exporter Framework, otherwise abbreviated as CEF, serves as an exporting library, independent of 3d modeling suites.&nbsp; This allows for new exporters to be created quickly, since the majority of the functionality is completely reusable.</p>
<p align=center><img height=600 src="http://www.circlesoft.org/DirectMesh2/DM2Spec/pages/images/CEF.jpg" width=800 border=0></p>
<p align=left>To create a new plugin, the programmer must provide the implementation of an abstracted Studio interface, which acts as a bridge between CEF and the modeling suite.&nbsp; It allows the framework to communicate back and forth with any arbitrary program.&nbsp; Dialogs (as well as other GUI-related events), file writing, and mesh processing will be handled by CEF.</p>
<p align=left>A 3D modeling program doesn't have to support all of the features of DM2 to use CEF.&nbsp; When queried, the interface to the modeler can fill in data that it has, and can leave empty the data that it doesn't support.&nbsp; For example, if "Studio X" doesn't support morph targets, it should just leave the morphing data blank.&nbsp; However, it can still fill in all of the other information that it does support.</p>
<hr noShade SIZE=1>
<h3 align=left><a name=2.8></a>2.8 Software Development Kit</h3>
<p align=left>The DirectMesh Software Development Kit comes complete with source code, documentation, utilities, exporters, samples, and tutorials.&nbsp; The licensing of the DMSDK is very simple:&nbsp; it is completely free to use in any private, public, or commercial endeavor.&nbsp; The source code can be modified without any restrictions whatsoever.</p>
</div>
<img src ="http://www.cppblog.com/liangairan/aggbug/105794.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2010-01-16 10:37 <a href="http://www.cppblog.com/liangairan/articles/105794.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>应用程序配置不正确，程序无法启动的问题小结（转）</title><link>http://www.cppblog.com/liangairan/articles/105368.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Sun, 10 Jan 2010 15:36:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/105368.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/105368.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/105368.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/105368.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/105368.html</trackback:ping><description><![CDATA[<p>转自：<a href="http://blog.csdn.net/waterathena/archive/2009/04/20/4094771.aspx">http://blog.csdn.net/waterathena/archive/2009/04/20/4094771.aspx</a><br></p>
<p>重装系统之后，开发环境进行重新配置了。因为之前那个环境用了大概有半年多了，所以只是基本的配置作了记录，其他细节的东西并没有以文字方式记录下来。这次运行起来有了一些未遇到过的问题。</p>
<p>问题1：重装系统之前就遇到过，解决了。主要原因缺少DLL</p>
<p>问题描述：项目组要提交0.9版到QT部门进行测试，打包发布文件之后，在测试部测试电脑上安装之后，发现双击.EXE不能运行。弹出信息框：由于应用程序配置不正确，应用程序未能启动。重新安装应用程序可能会纠正这个问题。</p>
<p>解决方法：当时网上一查发现需要拷贝VS2005安装包目录下的一些DLL文件。我的安装目录是：C:\Program Files\Microsoft Visual Studio 8\VC\redist\x86。我把这个目录下的所有相关文件都拷贝到项目生成文件目录下。</p>
<p>参考原文：<a href="http://www.cnblogs.com/wuhanhoutao/archive/2008/01/09/1031928.html">http://www.cnblogs.com/wuhanhoutao/archive/2008/01/09/1031928.html</a></p>
<p>问题2：在重新安装系统，重新安装开发环境之后，工作了几日后，发布新的版本给QT测试部门测试。发现又出现这样的问题。主要原因MANIFEST不正确</p>
<p>问题描述：安装完成之后，点击.exe不能运行。弹出信息框：由于应用程序配置不正确，应用程序未能启动。重新安装应用程序可能会纠正这个问题。</p>
<p>其中有一次弹出的信息框为英文操作系统下面的信息：The application has failed to start because its side-by side configuration is incorrect.</p>
<p>正是这个信息里面的&#8220;side-by-side configuration&#8221;让我去了解WINSXS。</p>
<p>参考文章：<a href="http://microsoft.cnfan.net/vista/3434.html">http://microsoft.cnfan.net/vista/3434.html</a> （vista WinSxS 混乱导致的应用程序不能启动 ）</p>
<p>参考文章：<a href="http://blog.csdn.net/arau_sh/archive/2008/12/17/3538252.aspx">http://blog.csdn.net/arau_sh/archive/2008/12/17/3538252.aspx</a> （提供四种解决方法）</p>
<p>参考文章：<a href="http://hi.baidu.com/freedomknightduzhi/blog/item/60a5012e512bba554fc226e9.html">http://hi.baidu.com/freedomknightduzhi/blog/item/60a5012e512bba554fc226e9.html</a></p>
<p>参考文章：<a href="http://www.cnblogs.com/holly/archive/2009/02/20/1395150.html">http://www.cnblogs.com/holly/archive/2009/02/20/1395150.html</a> （可以尝试）</p>
<p>参考文章：<a href="http://blog.csdn.net/newweapon/archive/2008/06/17/2556857.aspx">http://blog.csdn.net/newweapon/archive/2008/06/17/2556857.aspx</a></p>
<p>官方参考资料：<a href="http://msdn.microsoft.com/en-us/library/dd408052(VS.85).aspx">http://msdn.microsoft.com/en-us/library/dd408052(VS.85).aspx</a></p>
<p>参考文章：<a href="http://blogs.msdn.com/suzcook/archive/2004/05/14/132022.aspx">http://blogs.msdn.com/suzcook/archive/2004/05/14/132022.aspx</a> （APP.CONFIG example)</p>
<p>我的解决方法：</p>
<p>1.我试验了在测试目标机上安装VC++ 2005 Redistribute package，.exe可以正确运行。但是我觉得这个并不能代表真正原因，因为之前我也没有安装任何发布包，但是.exe可以正确运行。</p>
<p>2.参考文章&#8220;vista WinSxS 混乱导致的应用程序不能启动 &#8221;可以知道，.exe在运行的时候执行的进程去系统默认路径寻找需要的DLL文件，而没有使用拷贝到本目录下的DLL文件。所以如果系统没有安装发布套件，那么执行的时候就出现了&#8220;应用程序配置不正确&#8230;&#8230;&#8221;的错误。</p>
<p>该文章中提供的解决方案把MANIFEST文件不要内嵌到.EXE文件中，调整编译设置后我重新编译了程序，然后拷贝程序到目标测试机测试，发现还是不行。这个时候我去查看了编译生成在外部的MANIFEST文件（用文本文件可以打开MANIFEST）文件，发现了问题：<br>&nbsp;</p>
<p>仔细一看就发现同样的DLL写了两次，只是版本不同。而我拷贝到执行文件同目录下的版本为8.0.50727.762。<span style="COLOR: #4cf4f3">把最后那个dependency分支去掉后</span>，在把执行文档所在的整个目录拷贝到QT目标测试机就运行起来了。</p>
<p>题外话：</p>
<p>1.在上面的参考文章中提到使用静态链接MFC库的形式，就不会出现这样的问题。但是静态链接库不好，另外也会造成编译生成的.EXE庞大。</p>
<p>如果实在没有办法的情况下，也只能静态编译了。设置顺序为：Project-&gt;Properties(ALT+F7)-&gt;Property Page-&gt;General-&gt;Project Defaults-&gt;use of MFC设置：USE MFC IN A STATIC LIBRARY</p>
<p>然后BUILD。</p>
<p>2.在目标测试机上安装相应的发布套件。VC_REDIST_X86.EXE</p>
<p>需要把官方发布的安装套件和你的程序捆绑安装，很麻烦。需要判断操作系统（32or64位），需要判断操作系统当前使用语言，然后安装对应的版本。这是一定能解决不能运行的问题的办法。</p>
<p>Microsoft Visual C++ 2005 Redistributable Package (x64) 下载地址：</p>
<p><a href="http://www.microsoft.com/downloads/details.aspx?displaylang=zh-cn&amp;FamilyID=90548130-4468-4bbc-9673-d6acabd5d13b">http://www.microsoft.com/downloads/details.aspx?displaylang=zh-cn&amp;FamilyID=90548130-4468-4bbc-9673-d6acabd5d13b</a></p>
<p>Microsoft Visual C++ 2005 Redistributable Package (x86)</p>
<p><a href="http://www.microsoft.com/downloads/details.aspx?familyid=32BC1BEE-A3F9-4C13-9C99-220B62A191EE&amp;displaylang=zh-cn">http://www.microsoft.com/downloads/details.aspx?familyid=32BC1BEE-A3F9-4C13-9C99-220B62A191EE&amp;displaylang=zh-cn</a></p>
<p>3. 如何查看本机的.NET Framework版本</p>
<p>参考文章：<a href="http://blog.csdn.net/FantasiaX/archive/2008/05/17/2453907.aspx">http://blog.csdn.net/FantasiaX/archive/2008/05/17/2453907.aspx</a></p>
<p>我喜欢的方法：从注册表里查</p>
<p>注册表其实就是一个大型的&#8220;键-值对列表&#8221;（Key-Value Pair List）。注册表中的键是以树状层级结构记录的，所以图标看起来像是一个&#8220;文件夹&#8221;；一个键下，可以有&#8220;值&#8221;，而可以有再下一级的键。值是有数据类型的，比如字符串型、数字型；每个值都有自己的&#8220;Value Name&#8221;和&#8220;Value Data&#8221;。所以：</p>
<p>如果：HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v2.0.50727键下Value Name为Install的值的Value Data等于1，则说明.NET Framework 2.0.50727已经安装。同理，检查HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.0键和HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v3.5键下的Install值的Value Data就知道是否安装了这两个版本。我估计，如果没有安装过3.0和3.5的话，这两个键是不会存在的，如果安装后又卸载了，键可能会在，Install的Value Data可能会是0。</p>
<p><br>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/waterathena/archive/2009/04/20/4094771.aspx">http://blog.csdn.net/waterathena/archive/2009/04/20/4094771.aspx</a></p>
<img src ="http://www.cppblog.com/liangairan/aggbug/105368.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2010-01-10 23:36 <a href="http://www.cppblog.com/liangairan/articles/105368.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Skinned Mesh原理解析和一个最简单的实现示例（转）</title><link>http://www.cppblog.com/liangairan/articles/95322.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Fri, 04 Sep 2009 10:04:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/95322.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/95322.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/95322.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/95322.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/95322.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: &nbsp;Blog: http://blog.csdn.net/n5&nbsp;&nbsp; &amp; http://www.cnblogs.com/winfree&nbsp;(3D笔记only)Histroy:Version:1.01&nbsp;Date:2008-11-01 &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 修改了一些不精确的用语Ve...&nbsp;&nbsp;<a href='http://www.cppblog.com/liangairan/articles/95322.html'>阅读全文</a><img src ="http://www.cppblog.com/liangairan/aggbug/95322.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2009-09-04 18:04 <a href="http://www.cppblog.com/liangairan/articles/95322.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>光与材质（转）</title><link>http://www.cppblog.com/liangairan/articles/94103.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Sat, 22 Aug 2009 09:44:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/94103.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/94103.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/94103.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/94103.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/94103.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 转载自：http://www.gesoftfactory.com/developer/LnM.htm光用于照亮场景中的物体。当光照被启用时，Microsoft&#174; Direct3D&#174;根据下列组合计算每个顶点的颜色。    当前材质的颜色及相关纹理贴图中的纹理像素（texels）。    若已给出顶点的漫反射色和镜面反射色，则使用。    场景中光源产生的光的颜...&nbsp;&nbsp;<a href='http://www.cppblog.com/liangairan/articles/94103.html'>阅读全文</a><img src ="http://www.cppblog.com/liangairan/aggbug/94103.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2009-08-22 17:44 <a href="http://www.cppblog.com/liangairan/articles/94103.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Skeletal Animation (Skinning)（转）</title><link>http://www.cppblog.com/liangairan/articles/92919.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Tue, 11 Aug 2009 09:30:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/92919.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/92919.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/92919.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/92919.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/92919.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: Hva:Tegning og animering av et skjelett med skinn In todays games skeletal animation is the prefered way of animating characters. This report describes my attempt at making my own simple s...&nbsp;&nbsp;<a href='http://www.cppblog.com/liangairan/articles/92919.html'>阅读全文</a><img src ="http://www.cppblog.com/liangairan/aggbug/92919.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2009-08-11 17:30 <a href="http://www.cppblog.com/liangairan/articles/92919.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>HLSL初级教程（转）</title><link>http://www.cppblog.com/liangairan/articles/89671.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Thu, 09 Jul 2009 10:10:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/89671.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/89671.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/89671.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/89671.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/89671.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 目录前言1.HLSL入门&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.1什么是着色器&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.2什么是HLSL&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1.3怎么写HLSL着色器&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp...&nbsp;&nbsp;<a href='http://www.cppblog.com/liangairan/articles/89671.html'>阅读全文</a><img src ="http://www.cppblog.com/liangairan/aggbug/89671.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2009-07-09 18:10 <a href="http://www.cppblog.com/liangairan/articles/89671.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>D3D性能优化（转）</title><link>http://www.cppblog.com/liangairan/articles/88855.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Tue, 30 Jun 2009 00:50:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/88855.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/88855.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/88855.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/88855.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/88855.html</trackback:ping><description><![CDATA[<p>出处：<a href="http://blog.csdn.net/compiler/archive/2009/03/10/3975239.aspx">http://blog.csdn.net/compiler/archive/2009/03/10/3975239.aspx</a><br>常规技巧</p>
<p>1 只在必须的时候Clear。<br>&nbsp;&nbsp; IDirect3DDevice9::Clear函数通常需要花费较多的时间，因此要尽量少调用，而且只清空的确需要清空的缓存。</p>
<p>2 尽量减少状态切换。并且将需要进行的状态切换组合在一起设置。<br>&nbsp;&nbsp; 状态包括RenderState，SamplerState，TextureStageState等</p>
<p>3 纹理尺寸尽可能小</p>
<p>4 从前至后渲染场景中的对象<br>&nbsp;&nbsp; 从前至后渲染可以尽可能早地精选出不需要绘制的对象和象素</p>
<p>5 使用三角条带代替三角列表和三角扇。为了能更有效利用顶点高速缓存（cache），在排列条带时因考虑尽快重用顶点。</p>
<p>6&nbsp; 根所需要据消耗的系统资源来逐步减少特效。</p>
<p>7 经常性地检测程序的性能。<br>&nbsp;&nbsp; 这样可以更容易发现引起性能突变的部分</p>
<p>8 最小化顶点缓存的切换</p>
<p>9 尽可能使用静态顶点缓存</p>
<p>10 对静态对象，对每种FVF使用一个大的静态顶点缓存来保存多个对象的顶点数据，而不是每个对象使用一个顶点缓存。<br>&nbsp;&nbsp; 其目的也是减少顶点缓存的切换</p>
<p>11 如果程序需要随机访问AGP内存中的顶点缓存，顶点格式的大小最好是32bytes的倍数。否则，选择合适的最小的格式。<br>32bytes 也就是8个float数据或2个vector4。</p>
<p>12 使用顶点索引方式渲染，这样可以更有效利用顶点高速缓存。</p>
<p>13 如果深度缓存格式中包含有模版缓存，总是将两者一起Clear。</p>
<p>14 将计算结果和输出的shader指令合并：<br>// Rather than doing a multiply and add, and then output the data with<br>//&nbsp;&nbsp; two instructions:<br>mad r2, r1, v0, c0<br>mov oD0, r2</p>
<p>// Combine both in a single instruction, because this eliminates an <br>//&nbsp;&nbsp; additional register copy.<br>mad oD0, r1, v0, c0</p>
<p>建立一个场景对象的数据库，首先使用最低精度的模型，在保证性能的前提下逐步使用更高精度的模型。密切关注渲染的总的三角面数。</p>
<p>将使用相同渲染状态和贴图的图元集中在一起绘制，这样能尽量减少顶点缓存和状态的切换。并且将状态切换操作集中成一组设置。</p>
<p>尽量减少光源数量，使用环境光来提高亮度。方向光源比点光源和聚光灯更高效，因为光的方向是固定的。使用光照范围参数来剔除不受光照影响的物体。镜面高光几乎使光照计算量加倍，因此只在需要时使用，将D3DRS_SPECULARENABLE设为FALSE，将材质的specular power 设为0，将材质的specular color 设为0。</p>
<p>尽量减小纹理尺寸，这样可以增加纹理被缓存的可能性。尽量减少纹理的切换，将使用同一纹理的对象集中绘制。尽量使用正方形纹理。最快的纹理是256&#215;256，将4张128&#215;128的纹理拼接成256&#215;256使用。</p>
<p>连接World-View Matrix， 将ViewMatrix设为Identity减少矩阵乘法运算。</p>
<p>动态纹理。首先要检查D3DCAPS2_DYNAMICTEXTURES来判断硬件是否支持。<br>其二，动态纹理不能放在MANAGED pool中。动态纹理总是能锁定，甚至是在D3DPOOL_DEFAULT中。D3DLOCK_DISCARD是合法的。<br>DrawProceduralTexture(pTex)<br>{<br>&nbsp;&nbsp;&nbsp; // pTex should not be very small because overhead of<br>&nbsp;&nbsp;&nbsp; //&nbsp;&nbsp; calling driver every D3DLOCK_DISCARD will not<br>&nbsp;&nbsp;&nbsp; //&nbsp;&nbsp; justify the performance gain. Experimentation is encouraged.<br>&nbsp;&nbsp;&nbsp; pTex-&gt;Lock(D3DLOCK_DISCARD);<br>&nbsp;&nbsp;&nbsp; &lt;Overwrite *entire* texture&gt;<br>&nbsp;&nbsp;&nbsp; pTex-&gt;Unlock();<br>&nbsp;&nbsp;&nbsp; pDev-&gt;SetTexture();<br>&nbsp;&nbsp;&nbsp; pDev-&gt;DrawPrimitive();<br>}</p>
<p>当需要在每帧里锁定顶点或索引缓存是，应该使用动态缓存（D3DUSAGE_DYNAMIC）。对动态缓存使用D3DLOCK_DISCARD锁定能减少延迟。D3DLOCK_NOOVERWRITE锁定可以用于在缓存空闲处添加新的数据而不修改已经写入的数据。</p>
<p>使用Effect时，应该根据Effect，然后根据Technique来安排渲染顺序，也就是使用相同Effect和Technique的物体应该集中绘制。这样可以减少状态切换开销。</p>
<p>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/compiler/archive/2009/03/10/3975239.aspx">http://blog.csdn.net/compiler/archive/2009/03/10/3975239.aspx</a></p>
<img src ="http://www.cppblog.com/liangairan/aggbug/88855.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2009-06-30 08:50 <a href="http://www.cppblog.com/liangairan/articles/88855.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>渲染状态管理（转）</title><link>http://www.cppblog.com/liangairan/articles/87569.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Sat, 13 Jun 2009 03:44:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/87569.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/87569.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/87569.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/87569.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/87569.html</trackback:ping><description><![CDATA[<p>来源：<a href="http://blog.csdn.net/duduliao/archive/2008/08/25/2827683.aspx">http://blog.csdn.net/duduliao/archive/2008/08/25/2827683.aspx</a><br>渲染状态管理 出 处：Maple Studio <br>[ 2003-06-16 ] 作 者：maple </p>
<p><br>　　目 录<br>　　1 基本思想<br>　　2 实际问题<br>　　3 渲染脚本</p>
<p><br>--------------------------------------------------------------------------------</p>
<p>　　提高3D图形程序的性能是个很大的课题。图形程序的优化大致可以分成两大任务，一是要有好的场景管理程序，能快速剔除不可见多边形，并根据对象距相机远近选择合适的细节（LOD）；二是要有好的渲染程序，能快速渲染送入渲染管线的可见多边形。<br>　　我们知道，使用OpenGL或Direct3D渲染图形时，首先要设置渲染状态，渲染状态用于控制渲染器的渲染行为。应用程序可以通过改变渲染状态来控制OpenGL或Direct3D的渲染行为。比如设置Vertex/Fragment Program、绑定纹理、打开深度测试、设置雾效等。<br>　　改变渲染状态对于显卡而言是比较耗时的操作，而如果能合理管理渲染状态，避免多余的状态切换，将明显提升图形程序性能。这篇文章将讨论渲染状态的管理。</p>
<p>1、基本思想<br>　　我们考虑一个典型的游戏场景，包含人、动物、植物、建筑、交通工具、武器等。稍微分析一下就会发现，实际上场景里很多对象的渲染状态是一样的，比如所有的人和动物的渲染状态一般都一样，所有的植物渲染状态也一样，同样建筑、交通工具、武器也是如此。我们可以把具有相同的渲染状态的对象归为一组，然后分组渲染，对每组对象只需要在渲染前设置一次渲染状态，并且还可以保存当前的渲染状态，设置渲染状态时只需改变和当前状态不一样的状态。这样可以大大减少多余的状态切换。下面的代码段演示了这种方法：</p>
<p>　　// 渲染状态组链表，由场景管理程序填充<br>　　RenderStateGroupList groupList;<br>　　// 当前渲染状态<br>　　RenderState curState;</p>
<p>　　&#8230;&#8230;</p>
<p>　　// 遍历链表中的每个组<br>　　RenderStateGroup *group = groupList.GetFirst();<br>　　while ( group != NULL )<br>　　{<br>　　　　// 设置该组的渲染状态<br>　　　　RenderState *state = group-&gt;GetRenderState();<br>　　　　state-&gt;ApplyRenderState( curState );</p>
<p>　　　　// 该渲染状态组的对象链表<br>　　　　RenderableObjectList *objList = group-&gt;GetRenderableObjectList();<br>　　　　// 遍历对象链表的每个对象<br>　　　　RenderableObject *obj = objList-&gt;GetFirst();<br>　　　　while ( obj != NULL )<br>　　　　{<br>　　　　　　// 渲染对象<br>　　　　　　obj-&gt;Render();<br>　　　　　　obj = objList-&gt;GetNext();<br>　　　　}</p>
<p>　　　　group = groupList.GetNext();<br>　　}</p>
<p>　　其中RenderState类的ApplyRenderState方法形如：</p>
<p>　　void RenderState::ApplyRenderState( RenderState &amp;curState )<br>　　{<br>　　　　// 深度测试<br>　　　　if ( depthTest != curState.depthTest )<br>　　　　{<br>　　　　　　SetDepthTest( depthTest );<br>　　　　　　curState.depthTest = depthTest;<br>　　　　}</p>
<p>　　　　// Alpha测试<br>　　　　if ( alphaTest != curState.alphaTest )<br>　　　　{<br>　　　　　　SetAlphaTest( alphaTest );<br>　　　　　　curState.alphaTest = alphaTest;<br>　　　　}</p>
<p>　　　　// 其它渲染状态<br>　　　　&#8230;&#8230;<br>　　}</p>
<p>　　这些分组的渲染状态一般被称为Material或Shader。这里Material不同于OpenGL和Direct3D里面用于光照的材质， Shader也不同于OpenGL里面的Vertex/Fragment Program和Direct3D里面的Vertex/Pixel Shader。而是指封装了的显卡渲染图形需要的状态（也包括了OpenGL和Direct3D原来的Material和Shader）。<br>　　从字面上看，Material（材质）更侧重于对象表面外观属性的描述，而Shader（这个词实在不好用中文表示）则有用程序控制对象表面外观的含义。由于显卡可编程管线的引入，渲染状态中包含了Vertex/Fragment Program，这些小程序可以控制物体的渲染，所以我觉得将封装的渲染状态称为Shader更合适。这篇文章也将称之为Shader。<br>　　上面的代码段只是简单的演示了渲染状态管理的基本思路，实际上渲染状态的管理需要考虑很多问题。</p>
<p>2、实际问题</p>
<p>　　2.1 消耗时间问题<br>　　改变渲染状态时，不同的状态消耗的时间并不一样，甚至在不同条件下改变渲染状态消耗的时间也不一样。比如绑定纹理是一个很耗时的操作，而当纹理已经在显卡的纹理缓存中时，速度就会非常快。而且随着硬件和软件的发展，一些很耗时的渲染状态的消耗时间可能会有减少。因此并没有一个准确的消耗时间的数据。<br>　　虽然消耗时间无法量化，情况不同消耗的时间也不一样，但一般来说下面这些状态切换是比较消耗时间的：</p>
<p>Vertex/Fragment Program模式和固定管线模式的切换（FF，Fixed Function Pipeline）</p>
<p>Vertex/Fragment Program本身程序的切换。 <br>改变Vertex/Fragment Program常量。 <br>纹理切换。 <br>顶点和索引缓存（Vertex &amp; Index Buffers）切换。 <br>　　有时需要根据消耗时间的多少来做折衷，下面将会遇到这种情况。</p>
<p>　　2.2 渲染状态分类<br>　　实际场景中，往往会出现这样的情况，一类对象其它渲染状态都一样，只是纹理和顶点、索引数据不同。比如场景中的人，只是身材、长相、服装等不同，也就是说只有纹理、顶点、索引数据不同，而其它如Vertex/Fragment Program、深度测试等渲染状态都一样。相反，一般不会存在纹理和顶点、索引数据相同，而其他渲染状态不同的情况。我们可以把纹理、顶点、索引数据不归入到Shader中，这样场景中所有的人都可以用一个Shader来渲染，然后在这个Shader下对纹理进行分组排序，相同纹理的人放在一起渲染。</p>
<p>　　2.3 多道渲染（Multipass Rendering）<br>　　有些比较复杂的图形效果，在低档显卡上需要渲染多次，每次渲染一种效果，然后用GL_BLEND合成为最终效果。这种方法叫多道渲染Multipass Rendering，渲染一次就是一个pass。比如做逐像素凹凸光照，需要计算环境光、漫射光凹凸效果、高光凹凸效果，在NV20显卡上只需要1个 pass，而在NV10显卡上则需要3个pass。Shader应该支持多道渲染，即一个Shader应该分别包含每个pass的渲染状态。<br>　　不同的pass往往渲染状态和纹理都不同，而顶点、索引数据是一样的。这带来一个问题：是以对象为单位渲染，一次渲染一个对象的所有pass，然后渲染下一个对象；还是以pass为单位渲染，第一次渲染所有对象的第一个pass，第二次渲染所有对象的第二个pass。下面的程序段演示了这两种方式：</p>
<p>　　2.3.1 以对象为单位渲染</p>
<p>　　// 渲染状态组链表，由场景管理程序填充<br>　　ShaderGroupList groupList;</p>
<p>　　&#8230;&#8230;</p>
<p>　　// 遍历链表中的每个组<br>　　ShaderGroup *group = groupList.GetFirst();<br>　　while ( group != NULL )<br>　　{<br>　　　　Shader *shader = group-&gt;GetShader();<br>　　　　RenderableObjectList *objList = group-&gt;GetRenderableObjectList();</p>
<p>　　　　// 遍历相同Shader的每个对象<br>　　　　RenderableObject *obj = objList-&gt;GetFirst();<br>　　　　while ( obj != NULL )<br>　　　　{<br>　　　　　　// 获取shader的pass数<br>　　　　　　int iNumPasses = shader-&gt;GetPassNum();<br>　　　　　　for ( int i = 0; i &lt; iNumPasses; i++ )<br>　　　　　　{<br>　　　　　　　　// 设置shader第i个pass的渲染状态<br>　　　　　　　　shader-&gt;ApplyPass( i );<br>　　　　　　　　// 渲染对象<br>　　　　　　　　obj-&gt;Render();<br>　　　　　　}<br>　　　　　　obj = objList-&gt;GetNext();<br>　　　　}<br>　　　　group = groupList-&gt;GetNext();<br>　　}</p>
<p>　　2.3.2 以pass为单位渲染</p>
<p>　　// 渲染状态组链表，由场景管理程序填充<br>　　ShaderGroupList groupList;</p>
<p>　　&#8230;&#8230;</p>
<p>　　for ( int i = 0; i &lt; MAX_PASSES_NUM; i++ )<br>　　{<br>　　　　// 遍历链表中的每个组<br>　　　　ShaderGroup *group = groupList.GetFirst();<br>　　　　while ( group != NULL )<br>　　　　{<br>　　　　　　Shader *shader = group-&gt;GetShader();<br>　　　　　　int iNumPasses = shader-&gt;GetPassNum();</p>
<p>　　　　　　// 如果shader的pass数小于循环次数，跳过此shader<br>　　　　　　if( i &gt;= iNumPasses )<br>　　　　　　{<br>　　　　　　　　group = groupList-&gt;GetNext();<br>　　　　　　　　continue;<br>　　　　　　}</p>
<p>　　　　　　// 设置shader第i个pass的渲染状态<br>　　　　　　shader-&gt;ApplyPass( i );<br>　　　　　　RenderableObjectList *objList = group-&gt;GetRenderableObjectList();</p>
<p>　　　　　　// 遍历相同Shader的每个对象<br>　　　　　　RenderableObject *obj = objList-&gt;GetFirst();<br>　　　　　　while ( obj != NULL )<br>　　　　　　{<br>　　　　　　　　obj-&gt;Render();<br>　　　　　　　　obj = objList-&gt;GetNext();<br>　　　　　　}<br>　　　　　　group = groupList-&gt;GetNext();<br>　　　　}<br>　　}</p>
<p>　　这两种方式各有什么优缺点呢？以对象为单位渲染，渲染一个对象的第一个pass后，马上紧接着渲染这个对象的第二个pass，而每个pass的顶点和索引数据是相同的，因此第一个 pass将顶点和索引数据送入显卡后，显卡Cache中已经有了这个对象顶点和索引数据，后续pass不必重新将顶点和索引数据拷到显卡，因此速度会非常快。而问题是每个pass的渲染状态都不同，这使得实际上每次渲染都要设置新的渲染状态，会产生大量的多余渲染状态切换。<br>　　以pass为单位渲染则正好相反，以Shader分组，相同Shader的对象一起渲染，可以只在这组开始时设置一次渲染状态，相比以对象为单位，大大减少了渲染状态切换。可是每次渲染的对象不同，因此每次都要将对象的顶点和索引数据拷贝到显卡，会消耗不少时间。可见想减少渲染状态切换就要频繁拷贝顶点索引数据，而想减少拷贝顶点索引数据又不得不增加渲染状态切换。鱼与熊掌不可兼得。由于硬件条件和场景数据的情况比较复杂，具体哪种方法效率较高并没有定式，两种方法都有人使用，具体选用那种方法需要在实际环境测试后才能知道。</p>
<p>　　2.3 多光源问题<br>　　待续&#8230;&#8230;</p>
<p>　　2.4 阴影问题<br>　　待续&#8230;&#8230;</p>
<p>3、渲染脚本<br>　　现在很多图形程序都会自己定义一种脚本文件来描述Shader。<br>　　比如较早的OGRE（Object-oriented Graphics Rendering Engine，面向对象图形渲染引擎）的Material脚本，Quake3的Shader脚本，以及刚问世不久的Direct3D的Effect File，nVIDIA的CgFX脚本（文件格式与Direct3D Effect File兼容），ATI RenderMonkey使用的xml格式的脚本。OGRE Material和Quake3 Shader这两种脚本比较有历史了，不支持可编程渲染管线。而后面三种比较新的脚本都支持可编程渲染管线。</p>
<p>脚本 特性 范例 <br>OGRE Material 封装各种渲染状态，不支持可编程渲染管线 &gt;&gt;&gt;&gt; <br>Quake3 Shader 封装渲染状态，支持一些特效，不支持可编程渲染管线 &gt;&gt;&gt;&gt; <br>Direct3D Effect File 封装渲染状态，支持multipass，支持可编程渲染管线 &gt;&gt;&gt;&gt; <br>nVIDIA CgFX脚本 封装渲染状态，支持multipass，支持可编程渲染管线 &gt;&gt;&gt;&gt; <br>ATI RenderMonkey脚本 封装渲染状态，支持multipass，支持可编程渲染管线 &gt;&gt;&gt;&gt; </p>
<p>　　使用脚本来控制渲染有很多好处：</p>
<p>可以非常方便的修改一个物体的外观而不需重新编写或编译程序。 <br>可以用外围工具以所见即所得的方式来创建、修改脚本文件（类似ATI RenderMonkey的工作方式），便于美工、关卡设计人员设定对象外观，建立外围工具与图形引擎的联系。 <br>可以在渲染时将相同外观属性及渲染状态的对象（也就是Shader相同的对象）归为一组，然后分组渲染，对每组对象只需要在渲染前设置一次渲染状态，大大减少了多余的状态切换。 </p>
<p><br>本文来自CSDN博客，转载请标明出处：<a href="http://blog.csdn.net/duduliao/archive/2008/08/25/2827683.aspx">http://blog.csdn.net/duduliao/archive/2008/08/25/2827683.aspx</a></p>
<img src ="http://www.cppblog.com/liangairan/aggbug/87569.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2009-06-13 11:44 <a href="http://www.cppblog.com/liangairan/articles/87569.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>深入理解Direct3D9</title><link>http://www.cppblog.com/liangairan/articles/87422.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Thu, 11 Jun 2009 11:49:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/87422.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/87422.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/87422.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/87422.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/87422.html</trackback:ping><description><![CDATA[<p>深入理解D3D9对图形程序员来说意义重大，我把以前的一些学习笔记都汇总起来，希望对朋友们有些所帮助，因为是零散笔记，思路很杂，还请包涵。</p>
<p>其实只要你能完美理解D3DLOCK、D3DUSAGE、D3DPOOL、LOST DEVICE、QUERY、Present（）、BeginScene（）、EndScene（）等概念，就算是理解D3D9了， 不知道大家有没有同感。有如下几个问题，如果你能圆满回答就算过关：）。<br><span lang=EN-US>1、&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span lang=EN-US>D3DPOOL_DEFAULT</span>、<span lang=EN-US>D3DPOOL_MANAGED</span>、<span lang=EN-US>D3DPOOL_SYSTEMMEM</span>和<span lang=EN-US>D3DPOOL_SCRATCH</span><span>到底有何本质区别？<br><span lang=EN-US>2、&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span lang=EN-US>D3DUSAGE<span>的具体怎么使用？<br><span lang=EN-US>3、&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 什么是<span lang=EN-US>Adapter？什么是<span lang=EN-US>D3D Device</span>？<span lang=EN-US>HAL Device</span>和<span lang=EN-US>Ref Device</span>有何区别？<span lang=EN-US>Device</span>的类型又和<span lang=EN-US>Vertex Processing</span><span>类型有什么关系？<br><span lang=EN-US>4、&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span lang=EN-US>APP（<span lang=EN-US>CPU</span>）、<span lang=EN-US>RUNTIME</span>、<span lang=EN-US>DRIVER</span>、<span lang=EN-US>GPU</span>是如何协同工作的？<span lang=EN-US>D3D API</span><span>是同步函数还是异步函数？<br><span lang=EN-US>5、&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;Lost Device到底发生了什么？为什么在设备丢失后<span lang=EN-US>D3DPOOL_DEFAULT类型资源需要重新创建？</span></span></span></span></span></span></span></span></span></span></span></span></span></p>
<p>在<span lang=EN-US>D3D</span>中有三大对象，他们是<span lang=EN-US>D3D OBJECT</span>、<span lang=EN-US>D3D ADAPTER</span>和<span lang=EN-US>D3D DEVICE</span>。<span lang=EN-US>D3D OBJECT</span>很简单，就是一个使用<span lang=EN-US>D3D</span>功能的<span lang=EN-US>COM</span>对象，其提供了创建<span lang=EN-US>DEVICE</span>和枚举<span lang=EN-US>ADAPTER</span>的功能。<span lang=EN-US>ADAPTER</span>是对计算机图形硬件和软件性能的一个抽象，其包含了<span lang=EN-US>DEVICE</span>。<span lang=EN-US>DEVICE</span>则是<span lang=EN-US>D3D</span>的核心，它包装了整个图形流水管线，包括变换、光照和光栅化（着色），根据<span lang=EN-US>D3D</span>版本不同，流水线也有区别，比如最新的<span lang=EN-US>D3D10</span>就包含了新的<span lang=EN-US>GS</span>几何处理。图形管线的所有功能由<span lang=EN-US>DRIVER</span>提供，而<span lang=EN-US>DIRVER</span>分两类，一种是<span lang=EN-US>GPU</span>硬件<span lang=EN-US>DRIVER</span>，另一种是软件<span lang=EN-US>DRIVER</span>，这就是为什么在<span lang=EN-US>D3D</span>中主要有两类<span lang=EN-US>DEVICE</span>， <span lang=EN-US>REF和<span lang=EN-US>HAL</span>，使用<span lang=EN-US>REF DEVICE</span>时，图形管线的光栅化功能由软件<span lang=EN-US>DRIVER</span>在<span lang=EN-US>CPU</span>上模拟的，<span lang=EN-US>REF DEVICE</span>从名字就可以看出这个给硬件厂商做功能参考用的，所以按常理它应该是全软件实现，具备全部<span lang=EN-US>DX</span>标准功能。而使用<span lang=EN-US>HAL DEVICE</span>时，<span lang=EN-US>RUNTIME</span>则将使用<span lang=EN-US>HAL</span>硬件层控制<span lang=EN-US>GPU</span>来完成变换、光照和光栅化，而且只有<span lang=EN-US>HAL DEVICE</span>中同时实现了硬件顶点处理和软件顶点处理（<span lang=EN-US>REF DEVICE</span>一般不能使用硬件顶点处理，除非自己在驱动上做手脚，比如<span lang=EN-US>PERFHUD</span>）。另外还有个一个不常用的<span lang=EN-US>SOFTWARE DEVICE</span>，用户可以使用<span lang=EN-US>DDI</span>编写自己的软件图形驱动，然后注册进系统，之后便可在程序中使用。</span></p>
<p>检查系统软件硬件性能。<br>在程序的开始我们就要判断目标机的性能，其主要流程是：<br>确定要用的缓冲格式<br>GetAdapterCount()<br>GetAdapterDisplayMode</p>
<p><span><span lang=EN-US>GetAdapterIdentifier //</span><span>得到适配器描述<br><span lang=EN-US>CheckDeviceType //<span>判断指定适配器上的设备是否支持硬件加速<br><span lang=EN-US>GetDeviceCaps //指定设备的性能，主要判断是否支持硬件顶点处理<span lang=EN-US>(T&amp;L)<br><span lang=EN-US>GetAdapterModeCount //</span><span>得到适配器上指定缓冲格式所有可用的显示模式<br><span lang=EN-US>EnumAdapterModes //<span>枚举所有显示模式<br><span>CheckDeviceFormat<br>CheckDeviceMultiSampleType<br>详细使用请参考DX文档。<span lang=EN-US><br></span></span></span></span></span></span></span></span></span></span></span></p>
<p><span lang=EN-US><span lang=EN-US>WINDOWS图形系统的主要分为四层：图形应用程序、<span lang=EN-US>D3D RUNTIME、<span lang=EN-US>SOFTWARE DRIVER和<span lang=EN-US>GPU。此四层是按功能来分的，实际上他们之间界限并不如此明确，比如<span lang=EN-US>RUNTIME中其实也包含有<span lang=EN-US>USER MODE的<span lang=EN-US>SOFTWARE DRIVER，详细结构这里不再多说。而在<span lang=EN-US>RUNTIME里有一个很重要的结构，叫做<span lang=EN-US>command buffer，当应用程序调用一个<span lang=EN-US>D3D API时，<span lang=EN-US>RUNTIME</span>将调用转换成设备无关的命令，然后将命令缓冲到这个<span lang=EN-US>COMMAND BUFFER</span>中，这个<span lang=EN-US>BUFFER</span>的大小是根据任务负载动态改变的，当这个<span lang=EN-US>BUFFER</span>满员之后，<span lang=EN-US>RUNTIME</span>会让所有命令<span lang=EN-US>FLUSH</span>到<span lang=EN-US>KERNEL</span>模式下的驱动中，而驱动中也是有一个<span lang=EN-US>BUFFER</span>的，用来存储已被转换成的硬件相关的命令，<span lang=EN-US>D3D</span>一般只允许其缓冲最多<span lang=EN-US>3</span>个帧的图形指令，而且<span lang=EN-US>RUNTIME</span>和<span lang=EN-US>DRIVER</span>都会被<span lang=EN-US>BUFFER</span>中的命令做适当优化，比如我们在程序中连续设置同一个<span lang=EN-US>RENDER STATE</span>，我们就会在调试信息中看到如下信息&#8220;<span lang=EN-US>Ignoring redundant SetRenderState - X</span>&#8221;，这便是<span lang=EN-US>RUNTIME</span>自动丢弃无用的状态设置命令。在<span lang=EN-US>D3D9</span>中可以使用<span lang=EN-US>QUERY</span>机制来与<span lang=EN-US>GPU</span>进行异步工作，所谓<span lang=EN-US>QUERY</span>就是查询命令，用来查询<span lang=EN-US>RUNTIME</span>、<span lang=EN-US>DRIVER</span>或者<span lang=EN-US>GPU</span>的状态，<span lang=EN-US>D3D9</span>中的<span lang=EN-US>QUERY</span>对象有三种状态，<span lang=EN-US>SIGNALED</span>、<span lang=EN-US>BUILDING</span>和<span lang=EN-US>ISSUED</span>，当他们处于空闲状态后会将查询状态置于<span lang=EN-US>SIGNALED STATE</span>，查询分开始和结束，查询开始表示对象开始记录应用程序所需数据，当应用程序指定查询结束后，如果被查询的对象处于空闲状态，则被查询对象会将查询对象置于<span lang=EN-US>SIGNALED</span>状态。<span lang=EN-US>GetData</span>则是用来取得查询结果，如果返回的是<span lang=EN-US>D3D_OK</span>则结果可用，如果使用<span lang=EN-US>D3DGETDATA_FLUSH</span>标志，表示将<span lang=EN-US>COMMAND BUFFER</span>中的所有命令都发送到<span lang=EN-US>DRIVER</span>。现在我们知道<span lang=EN-US>D3D API绝大部分都是同步函数，应用程序调用后，<span lang=EN-US>RUNTIME只是简单的将其加入到<span lang=EN-US>COMMAND BUFFER</span>，可能有人会疑惑我们如何测定帧率？又如何分析<span lang=EN-US>GPU</span>时间呢？对于第一个问题我们要看当一帧完毕，也就是<span lang=EN-US>PRESENT()</span>函数调用是否被阻塞，答案是可能被阻塞也可能不被阻塞，要看<span lang=EN-US>RUNTIME</span>允许缓冲中存在的指令数量，如果超过额度，则<span lang=EN-US>PRESENT</span>函数会被阻塞下来，如何<span lang=EN-US>PRESENT</span>完全不被阻塞，当<span lang=EN-US>GPU</span>执行繁重的绘制任务时，<span lang=EN-US>CPU</span>工作进度会大大超过<span lang=EN-US>GPU</span>，导致游戏逻辑快于图形显示，这显然是不行的。测定<span lang=EN-US>GPU</span>工作时间是件很麻烦的事，首先我们要解决同步问题，要测量<span lang=EN-US>GPU</span>时间，首先我们必须让<span lang=EN-US>CPU</span>与<span lang=EN-US>GPU</span>异步工作，在<span lang=EN-US>D3D9</span>中可以使用<span lang=EN-US>QUERY</span>机制做到这点，让我们看看<span lang=EN-US>Accurately Profiling Driect3D API Calls</span>中的例子:<br>IDirect3DQuery9* pQueryEvent;</span></span></span></span></span></span></span></span></span></span></span></span></span></p>
<p><span lang=EN-US><span lang=EN-US>//1.</span><span>创建事件类型的查询事件<br><span>m_pD3DDevice-&gt;CreateQuery( D3DQUERYTYPE_EVENT, &amp;pQueryEvent);<br><span lang=EN-US>//2.</span>在<span lang=EN-US>COMMAND BUFFER</span>中加入一个查询结束的标记，此查询默认开始于<span lang=EN-US>CreateDevice<br>pQueryEvent-&gt;Issue(D3DISSUE_END);<br><span lang=EN-US>//3.</span>将<span lang=EN-US>COMMAND BUFFER</span>中的所有命令清空到<span lang=EN-US>DRIVER</span>中去，并循环查询事件对象转换到<span lang=EN-US>SIGNALED</span>状态，当<span lang=EN-US>GPU</span>完成<span lang=EN-US>CB</span><span>中所有命令后会将查询事件状态进行转换。<br><span>while(S_FALSE == pQueryEvent-&gt;GetData( NULL, 0, D3DGETDATA_FLUSH) )<br>&nbsp;&nbsp; &nbsp;&nbsp;;<br>LARGE_INTEGER start, stop;<br>QueryPerformanceCounter(&amp;start);&nbsp;<br>SetTexture();<br>DrawPrimitive();&nbsp;<br>pQueryEvent-&gt;Issue(D3DISSUE_END);<br>while(S_FALSE == pQueryEvent-&gt;GetData( NULL, 0, D3DGETDATA_FLUSH) )<br>&nbsp;&nbsp; &nbsp; &nbsp;&nbsp;;<br>QueryPerformanceCounter(&amp;stop);&nbsp;</span></span></span></span></span></span></p>
<p><span lang=EN-US><span lang=EN-US>1.第一个GetData调用使用了D3DGETDATA_FLUSH标志，表示要将COMMAND BUFFER中的绘制命令都清空到DRIVER中去，当GPU处理完所有命令后会将这个查询对象状态置SIGNALED。<br>2.将设备无关的SETTEXTURE命令加入到RUNTIME的COMMAND BUFFER中。<br>3.将设备无关的DrawPrimitive命令加入到RUNTIME的COMMAND BUFFER中。<br>4.将设备无关的ISSUE命令加入到RUNTIME的COMMAND BUFFER中。<br>5.GetData会将BUFFER中的所有命令清空到DRIVER中去，注意这是GETDATA不会等待GPU完成所有命令的执行才返回。这里会有一个从用户模式到核心模式的切换。<br>6.等待DRIVER将所有命令都转换为硬件相关指令，并填充到DRIVER BUFFER中后，调用从核心模式返回到用户模式。<br>7.GetData循环查询 查询对象 状态。当GPU完成所有DRIVER BUFFER中的指令后会改变查询对象的状态。</span></span></p>
<p>如下情况可能清空RUNTIME COMMAND BUFFER，并引起一个模式切换：<br>1.Lock method（某些条件下和某些LOCK标志）</p>
<p>2.创建设备、顶点缓冲、索引缓冲和纹理<br>3.完全释放设备、顶点缓冲、索引缓冲和纹理资源<br>4.调用ValidateDevice<br>5.调用Present<br>6.COMMAND BUFFER已满<br>7.用D3DGETDATA_FLUSH调用GetData函数</p>
<p><span lang=EN-US><span><span lang=EN-US><span><span>对于<span lang=EN-US>D3DQUERYTYPE_EVENT的解释我不能完全理解（<span><span lang=EN-US>Query for any and all asynchronous events that have been issued from API calls</span>）明白的朋友一定告诉我，只知道当<span lang=EN-US>GPU</span>处理完<span lang=EN-US>D3DQUERYTYPE_EVENT</span>类型查询在<span lang=EN-US>CB</span>中加入的<span lang=EN-US>D3DISSUE_END</span>标记后，会将查询对象状态置<span lang=EN-US>SIGNALED</span>状态，所以<span lang=EN-US>CPU</span>等待查询一定是异步的。为了效率所以尽量少在<span lang=EN-US>PRESENT</span>之前使用<span lang=EN-US>BEGINSCENE ENDSCENE</span>对，为什么会影响效率？原因只能猜测，可能<span lang=EN-US>EndScene</span>会引发<span lang=EN-US>Command buffer flush</span>这样会有一个执行的模式切换，也可能会引发D3D RUNTIME对MANAGED资源的一些操作。而且<span lang=EN-US>ENDSCENE</span>不是一个同步方法，它不会等待<span lang=EN-US>DRIVER</span>把所有命令执行完才返回。&nbsp;</span></span></span></span></span></span></span></p>
<p><span lang=EN-US><span lang=EN-US><span lang=EN-US>D3D RUTIME的内存类型，分为<span lang=EN-US>3种，<span lang=EN-US>VIDEO MEMORY（<span lang=EN-US>VM）、<span lang=EN-US>AGP MEMORY</span>（<span lang=EN-US>AM</span>）和<span lang=EN-US>SYSTEM MEMORY</span>（<span lang=EN-US>SM</span>），所有<span lang=EN-US>D3D</span>资源都创建在这<span lang=EN-US>3</span>种内存之中，在创建资源时，我们可以指定如下存储标志，<span lang=EN-US>D3DPOOL_DEFAULT</span>、<span lang=EN-US>D3DPOOL_MANAGED</span>、<span lang=EN-US>D3DPOOL_SYSTEMMEM</span>和<span lang=EN-US>D3DPOOL_SCRATCH</span>。<span lang=EN-US>VM</span>就是位于显卡上的显存，<span lang=EN-US>CPU</span>只能通过<span lang=EN-US>AGP</span>或<span lang=EN-US>PCI-E</span>总线访问到，读写速度都是非常慢的，<span lang=EN-US>CPU</span>连续写<span lang=EN-US>VM</span>稍微快于读，因为<span lang=EN-US>CPU</span>写<span lang=EN-US>VM</span>时会在<span lang=EN-US>CACHE</span>中分配<span lang=EN-US>32或</span><span lang=EN-US>64</span>个字节（取决于<span lang=EN-US>CACHE LINE</span>长度）的写缓冲，当缓冲满后会一次性写入<span lang=EN-US>VM</span>；<span lang=EN-US>SM</span>就是系统内存，<span lang=EN-US>CPU</span>读写都非常快，因为<span lang=EN-US>SM</span>是被<span lang=EN-US>CACHE</span>到<span lang=EN-US>2</span>级缓冲的，但<span lang=EN-US>GPU</span>却不能直接访问到系统缓冲，所以创建在<span lang=EN-US>SM</span>中的资源，<span lang=EN-US>GPU</span>是不能直接使用的；<span lang=EN-US>AM</span>是最麻烦的一个类型，<span lang=EN-US>AM</span>实际也存在于系统内存中，但这部分<span lang=EN-US>MEM</span>不会被<span lang=EN-US>CPU CACHE</span>，意味着<span lang=EN-US>CPU</span>读写<span lang=EN-US>AM</span>都会写来个<span lang=EN-US>CACHE MISSING</span>然后才通过内存总线访问<span lang=EN-US>AM</span>，所以<span lang=EN-US>CPU</span>读写<span lang=EN-US>AM</span>相比<span lang=EN-US>SM</span>会比较慢，但连续的写会稍微快于读，原因就是<span lang=EN-US>CPU</span>写<span lang=EN-US>AM</span>使用了&#8220;<span lang=EN-US>write combining</span>&#8221;，而且<span lang=EN-US>GPU</span>可以直接通过<span lang=EN-US>AGP</span>或<span lang=EN-US>PCI-E</span>总线访问<span lang=EN-US>AM</span>。&nbsp;</span></span></span></span></span></span></p>
<p><span lang=EN-US><span lang=EN-US><span>如果我们使用<span lang=EN-US>D3DPOOL_DEFAULT来创建资源，则表示让<span lang=EN-US>D3D RUNTIME根据我们指定的资源使用方法来自动使用存储类型，一般是<span lang=EN-US>VM或<span lang=EN-US>AM，系统不会在其他地方进行额外备份，当设备丢失后，这些资源内容也会被丢失掉。<span>但系统并不会在创建的时候使用D3DPOOL_SYSTEMMEM或D3DPOOL_MANAGED来替换它，注意他们是完全不同的POOL类型，创建到D3DPOOL_DEFAULT中的纹理是不能被CPU LOCK的，除非是动态纹理。但创建在D3DPOOL_DEFAULT中的VB IB RENDERTARGET BACK BUFFERS可以被LOCK。当你用D3DPOOL_DEFAULT创建资源时，如果显存已经使用完毕，则托管资源会被换出显存来释放足够的空间。&nbsp;<span lang=EN-US>D3DPOOL_SYSTEMMEM和<span lang=EN-US>D3DPOOL_SCRATCH</span>都是位于<span lang=EN-US>SM</span>中的，其差别是使用<span lang=EN-US>D3DPOOL_SYSTEMMEM</span>时，资源格式受限于<span lang=EN-US>Device</span>性能，因为资源很可能会被更新到<span lang=EN-US>AM</span>或<span lang=EN-US>VM</span>中去供图形系统使用，但<span lang=EN-US>SCRATCH</span>只受<span lang=EN-US>RUNTIME</span><span>限制，所以这种资源无法被图形系统使用。<span>&nbsp;<span>D3DRUNTIME会优化D3DUSAGE_DYNAMIC 资源，一般将其放置于AM中，但不敢完全保证。另外为什么静态纹理不能被LOCK，动态纹理却可以，都关系到D3D RUNTIME的设计，在后面D3DLOCK说明中会叙述。</span></span></span></span></span></span></span></span></span></span></span></span></p>
<p><span lang=EN-US>D3DPOOL_MANAGED</span>表示让<span lang=EN-US>D3D RUNTIME</span>来管理资源，被创建的资源会有<span lang=EN-US>2</span>份拷贝，一份在<span lang=EN-US>SM</span>中，一份在<span lang=EN-US>VM/AM</span>中，创建的时候被放置<span lang=EN-US>L</span>在<span lang=EN-US>SM</span>，在<span lang=EN-US>GPU</span>需要使用资源时<span lang=EN-US>D3D RUNTIME</span>自动将数据拷贝到<span lang=EN-US>VM</span>中去，当资源被<span lang=EN-US>GPU</span>修改后，<span lang=EN-US>RUNTIME</span>在必要时自动将其更新到<span lang=EN-US>SM</span>中来，而在<span lang=EN-US>SM</span>中修改后也会被<span lang=EN-US>UPDATE</span>到<span lang=EN-US>VM</span>去中。所以被<span lang=EN-US>CPU</span>或者<span lang=EN-US>GPU</span>频发修改的数据，一定不要使用托管类型，这样会产生非常昂贵的同步负担。当<span lang=EN-US>LOST DEVICE</span>发生后，<span lang=EN-US>RESET</span>时<span lang=EN-US>RUNTIME</span>会自动利用<span lang=EN-US>SM</span>中的<span lang=EN-US>COPY</span>来恢复<span lang=EN-US>VM</span>中的数据，因为备份在<span lang=EN-US>SM</span>中的数据并不是全部都会提交到<span lang=EN-US>VM</span>中，所以实际备份数据可以远多于<span lang=EN-US>VM</span>容量，随着资源的不断增多，备份数据很可能被交换到硬盘上，这是<span lang=EN-US>RESET</span>的过程可能变得异常缓慢，<span lang=EN-US>RUNTIME</span>给每个<span lang=EN-US>MANAGED</span>资源都保留了一个时间戳，当<span lang=EN-US>RUNTIME</span>需要把备份数据拷贝到<span lang=EN-US>VM</span>中时，<span lang=EN-US>RUNTIME</span>会在<span lang=EN-US>VM</span>中分配显存空间，如果分配失败，表示<span lang=EN-US>VM</span>已经没有可用空间，这样<span lang=EN-US>RUNTIME</span>会使用<span lang=EN-US>LRU</span>算法根据时间戳释放相关资源，<span lang=EN-US>SetPriority</span>通过时间戳来设置资源的优先级，最近常用的资源将拥有高的优先级，这样<span lang=EN-US>RUNTIME</span>通过优先级就能合理的释放资源，发生释放后马上又要使用这种情况的几率会比较小，应用程序还可以调用<span lang=EN-US>EvictManagedResources</span>强制清空<span lang=EN-US>VM</span>中的所有<span lang=EN-US>MANAGED</span>资源，这样如果下一帧有用到<span lang=EN-US>MANAGED</span>资源，<span lang=EN-US>RUNTIME</span>需要重新载入，这样对性能有很大影响，平时一般不要使用，但在关卡转换的时候，这个函数是非常有用的，可以消除<span lang=EN-US>VM</span>中的内存碎片。<span lang=EN-US>LRU</span>算法在某些情况下有性能缺陷，比如绘制一帧所需资源量无法被<span lang=EN-US>VM</span>装下的时候（<span lang=EN-US>MANAGED</span>），使用<span lang=EN-US>LRU</span>算法会带来严重的性能波动，如下例子：</p>
<p><span lang=EN-US>BeginScene();<br>Draw(Box0);<br>Draw(Box1);<br>Draw(Box2);<br>Draw(Box3);<br>Draw(Circle0);<br>Draw(Circle1);<br>EndScene();<br>Present();</span></p>
<p>假设<span lang=EN-US>VM</span>只能装下其中5个几何体的数据，那么根据<span lang=EN-US>LRU</span>算法，在绘制<span lang=EN-US>Box3</span>之前必须清空部分数据，那清空的必然是<span lang=EN-US>Circle0</span>&#8230;&#8230;，很显然清空<span lang=EN-US>Box2</span>是最合理的，所以这是<span lang=EN-US>RUNTIME</span>使用<span lang=EN-US>MRU</span>算法处理后续<span lang=EN-US>Draw Call</span>能很好的解决性能波动问题，但资源是否被使用是按<span lang=EN-US>FRAME</span>为单位来检测的，并不是每个<span lang=EN-US>DRAW CALL</span>都被记录，每个<span lang=EN-US>FRAME</span>的标志就是<span lang=EN-US>BEGINSCENE/ENDSCENE</span>对，所以在这种情况下合理使用<span lang=EN-US>BEGINSCENE/ENDSCENE</span>对可以很好的提高<span lang=EN-US>VM</span>不够情况下的性能。根据<span lang=EN-US>DX</span>文档的提示我们还可以使用<span lang=EN-US>QUERY</span>机制来获得更多关于<span lang=EN-US>RUNTIME MANAGED RESOURCE</span>信息，但好像只在<span lang=EN-US>RUNTIME DEBUG</span>模式下有用，理解<span lang=EN-US>RUNTIME</span>如何<span lang=EN-US>MANAGE RESOURCE</span>很重要，但编写程序的时候不要将这些细节暴露出来，因为这些东西都是经常会变的。最后还要提醒的是，不光<span lang=EN-US>RUNTEIME</span>会<span lang=EN-US>MANAGE RESOURCE</span>，<span lang=EN-US>DRIVER</span>也很可能也实现了这些功能，我们可以通过<span lang=EN-US>D3DCAPS2_CANMANAGERESOURCE</span>标志取得<span lang=EN-US>DRIVER</span>是否实现资源管理功能的信息，而且也可以在<span lang=EN-US>CreateDevice</span>的时候指定<span lang=EN-US>D3DCREATE_DISABLE_DRIVER_MANAGEMENT</span>来关闭<span lang=EN-US>DRIVER</span>资源管理功能。&nbsp;&nbsp;</p>
<p><span lang=EN-US>D3DLOCK</span>探索<span lang=EN-US>D3D RUNTIME</span>工作</p>
<p>如果<span lang=EN-US>LOCK DEFAULT</span>资源会发生什么情况呢？<span lang=EN-US>DEFAULT</span>资源可能在<span lang=EN-US>VM</span>或<span lang=EN-US>AM</span>中，如果在<span lang=EN-US>VM</span>中，必须在系统内容中开辟一个临时缓冲返回给数据，当应用程序将数据填充到临时缓冲后，<span lang=EN-US>UNLOCK</span>的时候，<span lang=EN-US>RUNTIME</span>会将临时缓冲的数据传回到<span lang=EN-US>VM</span>中去，如果资源<span lang=EN-US>D3DUSAGE</span>属性不是<span lang=EN-US>WRITEONLY</span>的，则系统还需要先从<span lang=EN-US>VM</span>里拷贝一份原始数据到临时缓冲区，这就是为什么不指定<span lang=EN-US>WRITEONLY</span>会降低程序性能的原因。<span lang=EN-US>CPU</span>写<span lang=EN-US>AM</span>也有需要注意的地方，因为<span lang=EN-US>CPU</span>写<span lang=EN-US>AM</span>一般是<span lang=EN-US>WRITE COMBINING</span>，也就是说将写缓冲到一个<span lang=EN-US>CACHE LINE</span>上，当<span lang=EN-US>CACHE LINE</span>满了之后才<span lang=EN-US>FLUSH</span>到<span lang=EN-US>AM</span>中去，第一个要注意的就是写数据必须是<span lang=EN-US>WEAK ORDER</span>的（图形数据一般都满足这个要求），据说<span lang=EN-US>D3DRUNTIME</span>和<span lang=EN-US>NV DIRVER</span>有点小<span lang=EN-US>BUG</span>，就是在<span lang=EN-US>CPU</span>没有<span lang=EN-US>FLUSH</span>到<span lang=EN-US>AM</span>时，<span lang=EN-US>GPU</span>就开始绘制相关资源产生的错误，这时请使用<span lang=EN-US>SFENCE</span>等指令<span lang=EN-US>FLUSH CACHE LINE</span>。第二请尽量一次写满一个<span lang=EN-US>CACHE LINE</span>，否则会有额外延迟，因为<span lang=EN-US>CPU</span>每次必须<span lang=EN-US>FLUSH</span>整个<span lang=EN-US>CACHE LINE</span>到目标，但如果我们只写了<span lang=EN-US>LINE</span>中部分字节，<span lang=EN-US>CPU</span>必须先从<span lang=EN-US>AM</span>中读取整个<span lang=EN-US>LINE</span>长数据<span lang=EN-US>COMBINE</span>后重新<span lang=EN-US>FLUSH</span>。第三尽可能顺序写，随机写会让<span lang=EN-US>WRITE COMBINING</span>反而变成累赘，如果是随机写资源，不要使用<span lang=EN-US>D3DUSAGE_DYNAMIC</span>创建，请使用<span lang=EN-US>D3DPOOL_MANAGED</span>，这样写会完全在<span lang=EN-US>SM</span>中完成。</p>
<p>普通纹理（<span lang=EN-US>D3DPOOL_DEFAULT</span>）是不能被锁定的，因为其位于<span lang=EN-US>VM</span>中，只能通过<span lang=EN-US>UPDATESURFACE</span>和<span lang=EN-US>UPDATETEXTURE</span>来访问，为什么<span lang=EN-US>D3D</span>不让我们锁定静态纹理，却让我们锁定静态<span lang=EN-US>VB IB</span>呢？我猜测可能有<span lang=EN-US>2</span>个方面的原因，第一就是纹理矩阵一般十分庞大，且纹理在<span lang=EN-US>GPU</span>内部已二维方式存储；第二是纹理在<span lang=EN-US>GPU</span>内部是以<span lang=EN-US>NATIVE FORMAT</span>方式存储的，并不是明文<span lang=EN-US>RGBA</span>格式。动态纹理因为表明这个纹理需要经常修改，所以<span lang=EN-US>D3D</span>会特别存储对待，高频率修改的动态纹理不适合用动态属性创建，在此分两种情况说明，一种是<span lang=EN-US>GPU</span>写入的<span lang=EN-US>RENDERTARGET</span>，一种是<span lang=EN-US>CPU</span>写入的<span lang=EN-US>TEXTURE VIDEO</span>，我们知道动态资源一般是放置在<span lang=EN-US>AM</span>中的，<span lang=EN-US>GPU</span>访问<span lang=EN-US>AM</span>需要经过<span lang=EN-US>AGP/PCI-E</span>总线，速度较<span lang=EN-US>VM</span>慢许多，而<span lang=EN-US>CPU</span>访问<span lang=EN-US>AM</span>又较<span lang=EN-US>SM</span>慢很多，如果资源为动态属性，意味着<span lang=EN-US>GPU</span>和<span lang=EN-US>CPU</span>访问资源会持续的延迟，所以此类资源最好以<span lang=EN-US>D3DPOOL_DEFAULT</span>和<span lang=EN-US>D3DPOOL_SYSTEMMEM</span>各创建一份，自己手动进行双向更新更好。千万别&nbsp;<span lang=EN-US>RENDERTARGET</span>以<span lang=EN-US>D3DPOOL_MANAGED&nbsp;</span>属性创建，这样效率极低，原因自己分析。而对于改动不太频繁的资源则推荐使用<span lang=EN-US>DEFAULT</span>创建，自己手动更新，因为一次更新的效率损失远比<span lang=EN-US>GPU</span>持续访问<span lang=EN-US>AM</span>带来的损失要小。&nbsp;</p>
<p>不合理的<span lang=EN-US>LOCK</span>会严重影响程序性能，因为一般<span lang=EN-US>LOCK</span>需要等待<span lang=EN-US>COMMAND BUFFER</span>前面的绘制指令全部执行完毕才能返回，否则很可能修改正在使用的资源，从<span lang=EN-US>LOCK</span>返回到修改完毕<span lang=EN-US>UNLOCK</span>这段时间<span lang=EN-US>GPU</span>全部处于空闲状态，没有合理使用<span lang=EN-US>GPU</span>和<span lang=EN-US>CPU</span>的并行性，<span lang=EN-US>DX8.0</span>引进了一个新的<span lang=EN-US>LOCK</span>标志<span>D3DLOCK_DISCARD，表示不会读取资源，只会全写资源，这样驱动和RUNTIME配合来了个瞒天过海，立即返回给应用程序另外块VM地址指针，而原指针在本次UNLOCK之后被丢弃不再使用，这样CPU LOCK无需等待GPU使用资源完毕，能继续操作图形资源（顶点缓冲和索引缓冲），这技术叫VB IB换名（renaming）。</span>&nbsp;</p>
<p>很多困惑来源于底层资料的不足，相信要是MS开放D3D源码，开放驱动接口规范，NV / ATI显示开放驱动和硬件架构信息，这些东西就很容易弄明白了。</p>
<p>顺便做个书的广告 《人工智能：一种现代方法》中文版 卓越网已经有货，AI巨作，不过阅读需要相当的基础，对思维非常有启迪，想买的朋友不要错过。后面我会将学习重点从图形转到AI上来，对AI有兴趣的朋友一起交流。</p>
<img src ="http://www.cppblog.com/liangairan/aggbug/87422.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2009-06-11 19:49 <a href="http://www.cppblog.com/liangairan/articles/87422.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>MAX SDK的INode的变换矩阵,以及Object的一些常识</title><link>http://www.cppblog.com/liangairan/articles/86207.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Sat, 30 May 2009 14:12:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/86207.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/86207.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/86207.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/86207.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/86207.html</trackback:ping><description><![CDATA[<p>转<a href="http://blog.csdn.net/Nhsoft/archive/2005/01/06/241629.aspx">http://blog.csdn.net/Nhsoft/archive/2005/01/06/241629.aspx</a><br></p>
<p>MAX&nbsp;SDK中,INode是很重要的一个部分。&nbsp;几乎所有的东西都需要通过INode来访问:Mesh&nbsp;Camera等.<br>下面我来简单的介绍一下和INode相关的各种不同的TM.(Transformation&nbsp;Matrix).仅仅代表我近日阅读MAX&nbsp;SDK的一些理解.希望高手指教.新手讨论. </p>
<p>&#160;</p>
<p>首先是介绍一下Node和Object(Mesh&nbsp;TriObject等).<br>MAX里的Scene是由INode构成的，Node里包含很多东西.这个Node可能是可以Deform(变形)的,或者是不可以Deform的。如常见的Editable&nbsp;Mesh和Cylinder一类的东西是可以Deform的Node.&nbsp;而Camera是不可以Deform的。<br>一个包含了几何物体的Node(就是可以转化为GeomObject的)中的几何体并不是Node本身.而是Node中的Object&nbsp;.这个Object&nbsp;可能是一个Derived&nbsp;Object(表示这个Object从别的Object那里派生出来,通常是从上一个修改器(Modifier)的结果那里派生的).&nbsp;同时还附加了一些Modefier&nbsp;&nbsp;每个Modifer的修改结果都是一个Deried&nbsp;Object&nbsp;.而类似Pyhsique的修改器都是作用在Derived&nbsp;Object上.因此,要找一个Node有没有Pyhsique的修改器,需要通过Derived&nbsp;Object来得到.<br><font color=#ff0000>INode-&gt;GetObjectRef()</font>可以得到这个物体的Object&nbsp;.&nbsp;<br><font color=#ff0000>Object-&gt;SuperClassID()&nbsp;==&nbsp;GEN_DERIVED_CLASS_ID</font>的话，就表示这个Object是一个Derived&nbsp;Object&nbsp;.&nbsp;<br><font color=#ff0000>DerivedObject-&gt;GetObjectRef()</font>得到这个这个Derived&nbsp;Object是从那个Object派生出来的..</p>
<p>用上面三个函数.可以遍历一个Node从创建到形成这个Node,所有经过的Derived&nbsp;Object.</p>
<p>如果要得到这个Node上最终的Object的状态(ObjectState.它包含了Node的Object).&nbsp;只要调用INode-&gt;EvalWorldState()就可以了。</p>
<p>下面.我来介绍Node里容易引起混淆的几个TM.<br><font color=#ff0000>INode-&gt;GetNodeTM().:</font>&nbsp;NodeTM()只包含了Node的TM.不是物体的TM.&nbsp;每个Node都有个基准点(Pivot&nbsp;Point).&nbsp;这个Pivot&nbsp;Point在世界坐标中的状态,就是这个Node的TM.&nbsp;而附加在这个Node上的Object相对这个Pivot&nbsp;Point可能会有其他的变换(平一,旋转等).&nbsp;所以.NodeTM不能用来变换Object.&nbsp;</p>
<p><font color=#ff0000>INode-&gt;GetParentTM().</font>&nbsp;父节点的TM.我们说过NodeTM是在世界坐标中的。所以,要得到节点相对父节点的TM需要&nbsp;GetNodeTM()＊Inverse(GetParentTM()).</p>
<p><font color=#ff0000>INode-&gt;GetObjectTM().</font>&nbsp;用这个矩阵,可以把Node中的Object变换到世界空间来.<br>其实<font color=#006400>GetObjectTM()&nbsp;=&nbsp;NodeTM()&nbsp;*&nbsp;(Object相对于Node的变换).</font></p>
<p><font color=#ff0000>INode-&gt;GetObjTMAfterWSM()</font>&nbsp;:&nbsp;WSM的意思是World&nbsp;Space&nbsp;Modifer.&nbsp;Modifer有两中,一种是Object&nbsp;Space的。一种是World&nbsp;Space的.如名字所暗示的:&nbsp;World&nbsp;Space&nbsp;Modifier要把Object先变换到世界空间里来.而不是物体空间中,所以,如果一个Object(Node上的Object)受到过World&nbsp;Space&nbsp;Modifier的影响。我们在EvalWorldState()的时候已经把顶点变换到世界坐标系中来了。GetObjectTM返回的将是把Object从INode的局部坐标系变换到世界坐标系的矩阵, 如果把ObjectTM乘上得到的顶点(经过WSM后，顶点已经在世界空间中了),就做了两次世界变换了,所以我们需要得到Node的一个矩阵,这个矩阵是表示能把经过WSM变换后的顶点正确的变换到世界空间中来. 这个矩阵就是<font color=#ff0000>GetObjTMAfterWSM().&nbsp;</font>&nbsp;如果一个INode上没有WSM. 那么GetObjTMAfterWSM()和GetObjectTM是相同的.同样,它和GetObjTMBeforeWSM也是相同的。 </p>
<p><font color=#ff0000>INode-&gt;GetObjTMBeforeWSM()</font>&nbsp;:&nbsp;这个函数的到WSM施加在INode前的ObjectTM.即这个INode不包括WSM时候,把它的Object的顶点变换到世界坐标系中的变换矩阵..</p>
<p><br>以上只是本人的拙见.参考了MAX&nbsp;SDK中的:<br>The&nbsp;Node&nbsp;and&nbsp;Object&nbsp;Offset&nbsp;Transformations<br>Geometry&nbsp;Pipeline&nbsp;System<br>的两个部分.</p>
<p>发表在这里,希望能让MAX&nbsp;SDK中的东西也能引起大家注意.&nbsp;多多讨论.<br>MAX&nbsp;SDK里有很多没有天理的东西.希望大家也能商量点天理出来</p>
<p>xheartblue&nbsp;2005&nbsp;-&nbsp;1-&nbsp;6&nbsp;凌晨<br></p>
<img src ="http://www.cppblog.com/liangairan/aggbug/86207.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2009-05-30 22:12 <a href="http://www.cppblog.com/liangairan/articles/86207.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>游戏程序中的骨骼插件（上）</title><link>http://www.cppblog.com/liangairan/articles/85828.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Tue, 26 May 2009 10:06:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/85828.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/85828.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/85828.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/85828.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/85828.html</trackback:ping><description><![CDATA[<div>
<h5><strong>文/潘李亮</strong><br><br><strong>引言</strong><br><br>在3D引擎中，骨骼动画系统是非常重要的一个组成部分，虽然在一个游戏的真正开发过程中，一个优秀的游戏引擎也许不需要用户去关心它的骨骼动画系统是如何实现的，但是还是有很多人希望了解这样的一个技术。<br><br>本文将会介绍骨骼动画系统里的一个基础部件：3Ds MAX 的骨骼动画导出插件。<br><br><strong>3Ds Max SDK和插件系统</strong><br><br>最新版本MAX9的MAXSDK包含在安装光盘里，在安装完MAX后直接安装SDK，并在工程里添加maxsdk的包含路径和库的路径就可以开始编译max插件了。MAX SDK还提供了3Ds Max Help for Visual Studio，这个帮助系统可以集成到Visual Studio .NET的帮助系统中，非常方便。建议在安装的时候一起装上。<br><br>MaxSDK主要目的就是用来开发MAX插件，虽然Max也提供了MaxScript，也可以用来做插件，但是对C++程序原来说，MaxSDK则更顺手一些。<br><br>Max插件根据用途分为好几种，每种对应不同的扩展名，在游戏开发中，我们通常比较关心三种类型的插件，他们分别是: 导入/导出插件，对应扩展名为dli/dle， utility 插件，对应扩展名为dlu，以及扩展名为dlm的modifier。导入导出插件基本上说是MAX与其它工具交互的接口。Utility插件则可以为 MAX增加很多操作功能面板。Modifier则是3DsMAX<br><br>3DsMax自带的插件放在X:\3DsMax\maxsdk\stdplugs目录下，而我们自己编写的插件通常会放到X:\3DsMax \maxsdk\ plugins目录下。只要把插件放到这两个目录下，Max在启动的时候就会自动加载你的插件。很多初学者可能会问dlm/dle这些插件是怎么生成的呢？其实这些都是一些标准的dll程序，只是扩展名不同而已。跟编译一个普通的Windows DLL没有区别。<br><br><img alt="" src="http://images.csdn.net/20080603/02-%E6%8F%92%E4%BB%B601.jpg"><br><br>初学MaxSDK最好的例子应该就是MAXSDK自带的sample。在maxsdk的安装目录下可以找到，一般是X:\3DsMax \maxsdk\samples 下。这个目录下已经对插件的种类进行了分类。一般在做骨骼动画导出插件的时候，我们不会选择导出插件而是选择utility插件，这样做的目的是 ultility插件在max启动的时候就处于激活状态，而导出插件则只会在用户选择export命令的那一刻，并且这些插件都可以访问到MAX的整个环境，因此，使用utility插件会让用户更加的方便，本文的例子就是采用utility插件。<br><br><strong>构造第一个3Ds Max 插件 </strong><br><br>本节我将讲述如何快速的建立一个utility插件的框架，因为关心的是导出插件本身的功能，而不是插件框架本身，因此我给大家提供一个个比较简洁的方法:使用3dsmaxPluginWizard. 这是MaxSDK提供的一个组件，位于X:\3dsmax\maxsdk\howto\3dsmaxPluginWizard下，仔细阅读一遍这个目录下的ReadMe.txt文件的Installing一小节，就可安装好3DsMaxPluginWizard. 这时候打开Visual Studio 2005.在新建工程中就可以看到3Ds Max Plugin Wizard一项，选择后，看到标签页一共有三页，在第一页Plugin-Type里，选择Utility项，在接下来的Plugin Detail里填入详细信息如图2所示。<br><br><img alt="" src="http://images.csdn.net/20080603/02-%E6%8F%92%E4%BB%B602.jpg"><br><br>最后在Project Detials 选项卡里填入maxsdk的路径，插件输出路径和3dsmax.exe所在的路径就可以生成一个utility工程了。<br><br>生成的工程仅仅是一个架子，它包含了两个类和一个IDD_PANEL的对话框。第一个类MyMaxSkinExporter是从 UtilityObj派生下来的，代表了插件本身。另外一个类从ClassDesc2派生，用来描述这个插件的一些信息。IDD_PANEL则是我们插件的主界面，我们可以简单的理解它就是我们插件的主窗口。<br><br>MyMaxSkinExporter有两个重要的函数: BeginEditParams(Interface *ip，IUtil *iu)和EndEditParams(Interface *ip，IUtil *iu)这两个函数。BeginEditParams可以简单的理解成插件的初始化函数，EndEditParams则在插件退出时候被调用。参数 Interface *ip 则代表整个Max对象，用它可以访问到MAX程序的所有功能。<br><br>编译这个工程，一个简单的utility插件就已经生成了，你可以在刚才Project Detials选项卡里填入的插件输出路径里找到生成的插件。<br><br><strong>3Ds MAX的场景组织和几何管道</strong><br><br>要编写一个导出骨骼动画的插件，必须先了解MAX是如何组织场景的，并了解MAX中一个mesh对象从建立到最终输出都经历那些阶段。下面首先介绍一下MAX的场景组织。<br><br>MAX的整个场景是一个树状结构，树的节点用INode来表示，整个树的根节点可以通过Interface::GetRootNode来获得，场景中的所有物体都是INode。INode中的NumberOfChildren函数和GetChildNode则用来访问INode的子节点。要遍历整个场景中对象，只需要通过Interface::GetRootNode和GetChildNode做一个递归&#209;&#172;环就可以了。如果仅仅是想获得在视口中选定的物体，则可以使用Interface::GetSelNodes函数。<br><br>INode仅仅是一个虚拟的节点，它本身仅仅包含一些标记和变换信息，并不表示实际的Object。实际的Object需要附着在INode上，并以INode的坐标系为Object的本地坐标系。Max中常见的Object有形状(各种参数曲线)，Camera，Mesh等。Object有自己的变换矩阵(TM)， 在很多情况下这个矩阵都是单位矩阵。<br><br><img alt="" src="http://images.csdn.net/20080603/02-%E6%8F%92%E4%BB%B603.jpg"><br><br>INode的变换矩阵可以通过INode::GetNodeTM来获得，而附着在INode上的Object的变换矩阵则通过 INode::GetObjectTM来获得，因为Object相对于Node的变换矩阵通常是单位矩阵，GetNodeTM和GetObjectTM获得的矩阵通常也是一样的，但是在必要的时候一定要加以区别。关于INode和Object的变换矩阵问题的详细讨论可以参考我blog上的一篇文章：http://blog.csdn.net/Nhsoft/archive/2005/01/06/241629.aspx<br><br>接下来我来看一下3DsMax一个几何物体的Pipeline。前面说过Object是附着在INode上的，在MAX里，Node有一个 Object Reference的指针，指向一个物体对象。熟悉MAX的操作方式的读者都知道，我们在MAX里建立一个对象后，可以在上面添加各种修改器 -Modifier。在Max的几何管道中，我们建立的对象通常称为Base Object。所有施加在这个物体上的修改器形成一个修改器堆栈-ModStack。BaseObject经过这个ModStack后形成一个新的 Object Reference。ModStack中的每个Modifier都是输入一个Object Reference，输出一个Object Reference， 并且在应用第一个Modifier的时候会自动在几何管道里插入一个Derived Object。最终INode的Object Reference将指向这个Derived Object。<br><br>Modifier在管道中的应用实例是ModApp对象，一个ModApp代表一个应用在Object上Modifier修改，ModApp包含一个ModContext的数据对象，Modifier用ModContext中的数据来对Object进行修改，以生成最终数据。<br><br>修改器按照应用的坐标系不同分成局部空间修改器和世界空间修改器(World Space Modifier)。局部空间修改器仅仅在Object的局部空间中修改Object，不会对坐标系造成影响。世界空间的修改器比如水波纹修改器则要求先将物体变换到世界空间后再进行修改，修改完成后的坐标也是世界空间的坐标。相对来说处理世界空间修改器会麻烦的多。（如果一个物体应用了世界空间修改器，则通过Mesh对象取得的坐标已经是世界坐标系中的了。不需要再乘以INode::GetObjectTM了）。<br><br><strong>导出骨骼动画数据</strong><br><br>了解了MAX的场景管理和几何管道以后，我们就可以很方便的建立一个如何取得MAX场景中定点数据的流程了。<br><br>骨骼动画系统，首先应该包括物体的蒙皮数据和顶点与骨骼的绑定信息。我们分两部分介绍皮肤数据的导出。第一步，我们要导出蒙皮数据，为了简单起见，在这里只导出蒙皮的位置，法向量与切向量纹理坐标等信息留给读者自己去研究。在3DsMax里。要建立骨骼动画模型，可以使用两种修改器Skin 和，Physique。其中Physique是属于CharacterStudio的，他的API相对比较复杂，本文只介绍使用Skin修改器制作的骨骼动画模型文件。<br><br>在界面上增加一些按钮<br><br>在了解了那么多理论后，我们可以开始做一些实质上的事情了，首先我们要给我们的插件增加一些按钮，通过这些按钮，使用可以下达保存/加载骨架，导出动作，导出皮肤等任务。<br><br>我们在第三节中生成的IDD_PANEL的对话框中加入几个按钮，分别用于保存骨架，加载骨架，导出动作，导出皮肤。并在对话框的WM_COMMAND消息中加入按钮响应代码。对话框的窗口过程为MyMaxSkinExporterDlgProc。<br><br>增加完的IDD_PANEL对话框看上去如图4。<br><br><img alt="" src="http://images.csdn.net/20080603/02-%E6%8F%92%E4%BB%B604.jpg"><br><br><strong>定义顶点数据类型</strong><br><br>骨骼动画的顶点数据应该包含顶点位置，纹理坐标，法向量，影响的骨骼编号和权重，一般影响到某个顶点的骨骼数目不会超过４个。同时，顶点位置也有两种记录方法：相对于世界空间的和相对骨骼空间的，这里我们采用相对于世界空间的记录方法，因为这种方案比较直观，只需要记录一个顶点位置就可以。麻烦的地方在于，因为骨骼的变换矩阵要求顶点是相对于该骨骼的局部空间的，因此顶点在参与骨骼蒙皮计算的时候，需要先乘上骨骼的初始位置的矩阵的逆，以变换到骨骼空间。<br><br>&nbsp;&nbsp; struct Vertex_t<br><br>&nbsp;&nbsp; {<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Point pos , normal , texCoord；<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int&nbsp;&nbsp;&nbsp; matID;<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; int&nbsp;&nbsp; nEffBone;<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; struct{<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int&nbsp;&nbsp;&nbsp; boneIdx;<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; float&nbsp; weight;<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }Bone[4];<br><br>&nbsp;&nbsp; };<br><br><strong>导出骨架</strong><br><br>骨骼动画系统中骨架为动画的载体，所有的蒙皮都附着在骨架之上。同时要保证属于一个角色的所有的蒙皮都使用同一个骨架来建立和导出，这是一套换装系统的基本需求。因此骨架的导出和保存通常是一次性的，后续导出皮肤的时候都应该以这个骨架为基准。这也要求我们在导出骨架的时候就需要导出所有的骨骼。<br><br>骨架上的骨骼其实也是一个INode，骨骼仅仅是一些变换矩阵的信息而已。目前没有特别好的办法鉴定哪些INode是骨骼，比较可行的办法是把所有Skin修改器使用到的INode都列为骨骼，同时美工还可以手动指定哪些Node为骨骼，并把这些标记用INode:: SetUserPropBool("IsBone"，bIsBone);记录到MAX文件中。<br><br>保存骨架的时候，需要保存骨骼的父子关系。并需要保存这个骨骼的第一帧数据。这要求如果美工在两个不同的MAX文件里制作不同的动作的时候，除了保证骨架相同以外，第一帧也需要完全相同．<br><br>骨架的保存和加载代码如下：<br><br>&nbsp;&nbsp; struct Bone_t{<br><br>&nbsp;&nbsp;&nbsp; Matrix NodeInitTM;<br><br>&nbsp;&nbsp;&nbsp; char&nbsp;&nbsp; Name[32]，ParantName[32];<br><br>&nbsp;&nbsp; };<br><br>&nbsp;&nbsp; class CSkeleton {<br><br>&nbsp;&nbsp; public:&nbsp;&nbsp;&nbsp; vector&lt;Bone_t&gt; m_Bones;<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; void loadSkeleton(const char* skeFileName){<br><br>&nbsp; &nbsp;&nbsp;&nbsp; ifstream in(skeFileName , ios::binary);<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; while(!in.eof()){<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Bone_t _bone;<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; in.read((char*)&amp;_bone , sizeof(Bone_t));<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; m_Bones.push_back(_bone);<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; in.close();<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br><br>&nbsp;&nbsp; int findBoneIndex(INode* pNode)&nbsp;&nbsp;&nbsp; {//<br><br>&nbsp;&nbsp; for(int i = 0&nbsp; ; i&nbsp; &lt; m_Bones.size() ; i ++)<br><br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if(string(m_Bones[i].Name) == pNode-&gt;GetName() )&nbsp;&nbsp;&nbsp; return i;<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; return -1;<br><br>&nbsp; }<br><br>&nbsp;&nbsp;&nbsp; void saveBone(ostream&amp; out , INode* pNode , bool bRoot){<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Bone_t _bone;<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; _bone.NodeInitTM = pNode-&gt;GetNodeTM(0)&nbsp; ;<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; strncpy(_node.Name， pNode-&gt;GetName() , 32);<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; if(bRoot) _bone.ParantName[0] = 0;<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; else{<br><br>&nbsp;&nbsp;&nbsp; INode* pPNode =pNode-&gt;GetParantNode();<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; strncpy(_bone.ParantName， pPNode-&gt;GetName() , 32);<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; }<br><br>&nbsp;&nbsp;&nbsp;&nbsp; out.write( (char*)&amp;_bone , sizeof(Bone_t) );<br><br>&nbsp;&nbsp;&nbsp;&nbsp; for(int i = 0 ; i &lt; pNode-&gt;NumberOfChildren() ; i ++)<br><br>&nbsp;&nbsp;&nbsp;&nbsp; saveBone(out，pNode-&gt;GetChildNode(i)， false)；<br><br>&nbsp; }<br><br>&nbsp;&nbsp;&nbsp;&nbsp; void saveSkeleton(const char* skeFileName , INode* pRootNode){<br><br>&nbsp; &nbsp;&nbsp;&nbsp; ofstream out(skeFileName , ios::binary);<br><br>&nbsp; &nbsp;&nbsp;&nbsp; saveBone(out , pRootNode , true);<br><br>&nbsp; &nbsp;&nbsp;&nbsp; out.close();<br><br>&nbsp;&nbsp; }<br><br>&nbsp;&nbsp; };<br><br>findBoneIndex函数的目的是在把从文件中加载的骨骼和MAX中的Node对应起来.因为是根据名字来进行查找比较，因此要求所有的 Node都必须要有唯一的名字．同时，骨骼之间的父子关系也是通过名字来标记的．每个Bone都记录了它的父节点的名字．Save Skeleton骨架的按钮响应代码如下:<br><br>&nbsp;&nbsp; void OnSaveSkeleton()<br><br>&nbsp;&nbsp; {<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CSkeleton* pSkeleton = GetGlobalSkeleton();<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Assert(ip-&gt;GetSelNodeCount() == 1); //导出骨架的时候只能选择一个节点<br><br><br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; const char* filename = GetSaveFileName() ;<br><br><br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(filename){<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pSkeleton-&gt; saveSkeleton(filename , ip-&gt;GetSelNode(0) );<br><br>}<br><br>&nbsp;&nbsp; }<br><br><strong>导出骨架动作</strong><br><br>骨架导出后，我们需要进一步导出这个骨架的动作。在导出动作的时候，需要加载一个事先已经导出的骨架。然后遍历这个骨架中所有的骨骼，找到这个骨骼对应的INode对象。然后确定动画的长度和帧数，为每一个骨头的保存一个变换矩阵。<br><br>void OnExportAnimation()<br><br>&nbsp;&nbsp; {<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; const char* fileName = GetSaveFileName();<br><br>ofstream out(fileName , ios::binary);<br><br>Interval ARange =&nbsp; ip-&gt;GetAnimRange(); //获得动画的长度<br><br><br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; TimeValue&nbsp;&nbsp; tAniTime =&nbsp; ARange.End() - ARange.Start();<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;TimeValue&nbsp;&nbsp; tTime = ARange.Start();<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;int nFrame = tAniTime/GetTicksPerFrame();<br><br>//计算动画帧数<br><br>out.write((char*)&amp;nFrame , sizeof(int));<br><br>//记录有多少frame;<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; for(int i = 0 , ; i &lt; nFrame ; i ++ ,tTime += GetTicksPerFrame()){<br><br>&nbsp;&nbsp; CSkeleton* pSkeleton = GetGlobalSkeleton();<br><br>&nbsp;&nbsp; for(int iBone = 0 ; iBone &lt; pSkeleton-&gt;m_Bones.size() ; iBone ++){<br><br>&nbsp;&nbsp; Bone_t&amp; bone = pSkeleton-&gt;m_Bones[iBone];<br><br>&nbsp;&nbsp; INode* pBoneNode = GetNodeByName(bone.Name);<br><br>//通过名字获得INode指针<br><br>&nbsp;&nbsp; Matrix mat = pBoneNode-&gt;GetNodeTM(tTime，NULL);<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; out((char*)&amp;mat , sizeof(Matrix));<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp; }<br><br>&nbsp;&nbsp; }<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; out.close();<br><br>&nbsp;&nbsp; }<br><br>这里演示里我们记录的是骨骼的绝对变换矩阵，而不是相对父骨骼的变换矩阵，这省去了我们从根骨骼开始计算骨架的麻烦，但是也多了很多限制，比如不能进行动作混合，不能做动作的插值等，使用相对父骨骼的局部矩阵的算法留给读者自己去实现，也可以参考Cal3D和我开源的XReal3D的导出插件。此外，因为我们在顶点数据中只保存了相对世界空间的位置，所以骨骼中的NodeInitTM将用来把相对世界空间的顶点位置变换到骨骼的局部空间中，皮肤混合的时候计算公式将如下：<br><br><img alt="" src="http://images.csdn.net/20080603/02-%E6%8F%92%E4%BB%B605.jpg"><br><br>其中M(t，i)为第i块骨头在t时刻的变换矩阵。<br><br>同样的，我们只是简单的导出每一帧的变换矩阵，而没有处理关键帧，使用关键帧加上相对父节点的局部变换矩阵的四元数插值，在保准动作的准确性前提下能大大的降低动作文件磁盘占用。</h5>
<p>&nbsp;</p>
<h5><strong>文/潘李亮</strong><br><br><strong>查找Skin修改器</strong><br><br>要找到一个Mesh上是不是有Skin修改器，根据MAX的几何管道的结构，需要遍历整个ModStack中的Derived Object。判断应用在这些Derived Object上的修改器的类型。MAX中所有的对象都有一个类似COM的GUID的唯一标记ClassID。Skin修改器的ClassID为 SKIN_CLASSID，在获得Derived Object的修改器后只需要检查修改器的ClassID是不是SKIN_CLASSID即可。示例代码如下：<br><br>ISkin * FindSkinModifier(INode *pINode){<br><br>&nbsp; Object * pObject = pINode-&gt;GetObjectRef();<br><br>&nbsp; if(pObject == 0) return 0;<br><br>&nbsp; // 循环检测所有的DerivedObject<br><br>&nbsp; while(pObject-&gt;SuperClassID() == GEN_DERIVOB_CLASS_ID)<br><br>&nbsp; {<br><br>&nbsp;&nbsp;&nbsp; IDerivedObject * pDerivedObject = <br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; static_cast&lt;IDerivedObject *&gt;(pObject);<br><br>&nbsp;&nbsp;&nbsp; for(int stackId = 0; <br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; stackId &lt; pDerivedObject-&gt;NumModifiers(); <br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; stackId++)<br><br>&nbsp;&nbsp;&nbsp; {<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Modifier * pModifier = <br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pDerivedObject-&gt;GetModifier(stackId);<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //检测ClassID是不是Skin修改器<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if(pModifier-&gt;ClassID() == SKIN_CLASSID) {<br><br>&nbsp;&nbsp; return (ISkin*) pModifier-&gt;GetInterface(I_SKIN);<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br><br>&nbsp;&nbsp;&nbsp; }<br><br>&nbsp;&nbsp;&nbsp; //下一//个Derived Object&nbsp;&nbsp;&nbsp; <br><br>&nbsp;&nbsp;&nbsp; pObject = pDerivedObject-&gt;GetObjRef();<br><br>&nbsp; }<br><br>&nbsp; return 0;<br><br>}<br><br><strong>获取Mesh对象</strong><br><br>根据第四节中描述的，要从一个INode中获得Mesh对象，首先应该从INode中获得Object对象，然后再转成Mesh对象。具体代码如下：<br><br>Mesh* GetMesh(INode* pNode , int iMaxTime){<br><br>&nbsp; NullView view; <br><br>&nbsp; //NullView是自定义的View类。详细参见完整的插件代码<br><br>&nbsp; BOOL&nbsp; bNeedDelete = false;<br><br>&nbsp; ObjectState os = pNode-&gt;EvalWorldState(iMaxTime);<br><br>&nbsp; Object* pObj =&nbsp; os.obj;<br><br>&nbsp; TriObject * triObject = (TriObject *)pObj-&gt;<br><br>&nbsp; ConvertToType(iMaxTime, triObjectClassID);<br><br>&nbsp; GeomObject* pGeoObj = (GeomObject*)pObj;<br><br>&nbsp; Mesh * pMesh = pGeoObj-&gt;GetRenderMesh( <br><br>&nbsp;&nbsp;&nbsp; iMaxTime , m_pNode , view , bNeedDelete );<br><br>&nbsp; return pMesh;<br><br>}<br><br><strong>获取皮肤数据与顶点的骨骼绑定信息</strong><br><br>在成功获取到一个节点的ISkin对象和Mesh以后，就可以使用这两个对象来提取物体的顶点数据和骨骼的绑定信息了。<br><br>Mesh中的数据保存在不同的数组中，常用的包含以下几种：顶点位置信息，颜色信息，法向量，UV坐标，MapChannel信息等。其中法向量的信息不是特别的准确，需要考虑平滑组，面法向量与顶点法向量的差异等。MapChannel用于仅仅有多层纹理贴图坐标的情况，在只有一层纹理坐标的情况下则不需要考虑，使用UV坐标就足够了。本文仅仅演示如何导出顶点位置，单层纹理和骨骼绑定信息，如何准确的计算法向量以及处理多层纹理坐标的内容不在本文的讨论范围。<br><br>Mesh中的numVerts变量标记了Mesh有几个顶点位置，numTexCoords标记了有几个纹理坐标。而通常这两个值是不一样的。<br><br>要提取骨骼绑定信息，首先需要从ISkin对象中查询到ISkinContextData接口： ISkinContextData* pSkinContext<br><br>= pSkin-&gt;GetContextInterface(pNode); 通过ISkinContextData的GetNumAssignedBones函数可以得到影响到这个顶点的骨骼的个数，进而通过 GetAssignedBone和GetBoneWeight接口可以到这个Bone的index已经这个Bone对这个顶点的权重。<br><br>void GetVertexBoneInfo(INode* pNode , ISkin* pSkin , Mesh* mesh ,&nbsp; int vertexIdx , int uvIdx , Vertex_t&amp; vOut) {<br><br>&nbsp; CSkeleton* pSkeleton = GetGlobalSkeleton();<br><br>&nbsp; ISkinContextData* pSkinCtx = <br><br>&nbsp;&nbsp;&nbsp; pSkin-&gt;GetContextInterface(pNode);<br><br>&nbsp; int nBones = <br><br>&nbsp;&nbsp;&nbsp; pSkinCtx-&gt;GetNumAssignedBones(vertexIdx);<br><br>&nbsp; vOut.pos = mesh-&gt;verts[vertexIdx] * <br><br>&nbsp;&nbsp;&nbsp; pNode-&gt;GetObjectTM(0,NULL);//第一帧的数据<br><br>&nbsp; vOut.texCoord = mesh-&gt;texCoords[uvIdx];<br><br>&nbsp; vOut.nEffBone = nBones;<br><br>&nbsp; for(int jBone = 0; jBone &lt; nBones; jBone ++) {<br><br>&nbsp;&nbsp;&nbsp; INode* pBone = pSkin-&gt;GetBone(<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pSkinCtx-&gt;GetAssignedBone(vertexIdx, Bone));<br><br>&nbsp;&nbsp;&nbsp; vOut.Bone[jBone].weight&nbsp; = <br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pSkinCtx-&gt;GetBoneWeight(vertexIdx,jBone);<br><br>&nbsp;&nbsp;&nbsp; vOut.Bone[jBone].boneIdx = <br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pSkeleton-&gt;findBoneIndex(pBone);<br><br>&nbsp; }<br><br>}<br><br>上述示例代码中并没有考虑到有个别顶点的骨骼数量超过4种情况，在正式的代码中应该对所有影响到这个顶点的骨骼权重进行排序，取前4个权重最大的，丢弃其余的骨骼。并要考虑有有些重复骨骼的问题。<br><br><strong>导出面的和材质信息</strong><br><br>在成功解决了顶点和骨骼的数据提取后,还需要获得面的信息,即顶点的拓扑关系。3D渲染器能处理的面一般都是三角形面，因此一般要求美工在制作模型的时候首先将模型转换成Editable Mesh。这样能确保在Mesh取到的面都是三角形。<br><br>MAX中的面有两种，用来表示形状的Face类和用来表示纹理的TVFace类。Face和TVFace是一一对应的。就是说要获得一个三角形的完整数据，必须同时获取Face类和TVFace类。<br><br>Face类中的信息比较重要的有三个顶点的位置索引，可以通过Face::v[i]来获得，得到这个索引后，在Mesh::verts数组里就可以获得位置的数据。同理TVFace也一样。<br><br>Face类里除了顶点信息外，还保存了和材质相关的信息：MaterialID。一个Mesh上通常只能应用一个Material，那为什么会有 MaterialID这个概念呢？因为在Max里除了标准材质以外，还有一种美工非常有用的材质叫多材质（Multi-Material），这种材质可以可以包含很多个标准材质，我们可以通过判断材质的ClassID来判断它是不是一个多材质，如果是，就遍历它的所有子材质（Sub-Mateiral）。 MaterialID就是对应的子材质的序号。在绘制的时候，MaterialID相同的三角形表示它们有相同的纹理和材质，在导出的时候应该按照 MaterialID进行排序。<br><br>Max中的Material使用Mtl类表示，可以通过INode::GetMtl()来获得。Mtl:: NumSubMtls加GetSubMtl则可访问到这个Material的所有Material。对于一个标准材质，我们可以获得这个材质的各种属性，包括纹理贴图，纹理贴图使用的贴图坐标通道（MapChannel）,以及环境光，高光等属性。处理材质的示例代码如下：<br><br>void SaveMaterial(const char* fileMat , INode* pNode){<br><br>&nbsp; ofstream out(fileMat,ios::binary);<br><br>&nbsp; Mtl* pMtl = pNode-&gt;GetMtl();<br><br>&nbsp; int nSubMat = pMtl-&gt;NumSubMtls();<br><br>&nbsp; if (nSubMat == 0) {<br><br>&nbsp;&nbsp;&nbsp; //处理和保存一个标准材质的代码，详见参考资料<br><br>&nbsp;&nbsp;&nbsp; saveStdMaterial(out , (StdMat*)pMtl); <br><br>&nbsp; } else {<br><br>&nbsp;&nbsp;&nbsp; for(int i = 0 ; i &lt; pMtl-&gt;NumSubMtls(); i++ )<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; saveStdMaterial(out , <br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (StdMat*)pMtl-&gt;GetSubMtl(i));&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <br><br>&nbsp; }<br><br>}<br><br>&nbsp;&nbsp; 以下为简单的提取三角形数据的代码。<br><br>void SaveMesh(const char* fileMesh , INode* pNode , Mesh* pMesh) {<br><br>&nbsp; ISkin* pSkin = GetSkinModifier(pNode);<br><br>&nbsp; ofstream out(fileMesh , ios::binary);<br><br>&nbsp; int nFace = pMesh-&gt;numFaces ;&nbsp; <br><br>&nbsp; out.write( (char*)&amp;nFace,sizeof(int) );<br><br>&nbsp; for(int i = 0 ;i &lt; pMesh-&gt;numFaces ; i ++) {<br><br>&nbsp;&nbsp;&nbsp; TVFace&amp; tvface = pMesh-&gt;tvFace[i];<br><br>&nbsp;&nbsp;&nbsp; Face&amp; face = pMesh-&gt;faces[i];<br><br>&nbsp;&nbsp;&nbsp; for(int j = 0 ; j &lt; 3 ; j++) {<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //一个三角形三个顶点<br><br>&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; Vertex_t vert ;<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; vert.matID = face.getMatID();<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; GetVertexBoneInfo(pNode, pSkin, pMesh,<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; face.v[j], tvface.getTVert(j), vert);<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //保存到文件<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; out.write( (char*)&amp;vert,sizeof(vert) ); <br><br>&nbsp;&nbsp;&nbsp; }<br><br>&nbsp; }<br><br>}<br><br>&nbsp;&nbsp; 上述代码仅仅是简单的保存了每一个面的信息，真正应用的时候，至少还应该把所有的面按照MateiralID来进行排序，并且 GetVertexBoneInfo函数在生成顶点数据的时候必然有很多顶点是完全相同的，应该把这些相同的顶点都去掉。面的信息使用Index Buffer来保存。鉴于处理这些代码过长，就不在文中一一举例。<br><br>除了顶点材质相关的信息之外，Face类中比较重要的是法向量相关的信息，如RVertex和平滑组数据(smooth group)等。读者可以参阅参考资料。<br><br><strong>进一步的工作</strong><br><br>现在我们已经基本可以获得一个简单的骨骼动画系统所需要的大部分数据了。但是这仅仅局限于一个演示性的骨骼动画系统，离一个完整鲁棒的系统还有很多事情要做。<br><br>上述例子中，我们仅仅导出了皮肤的顶点和第一层纹理坐标。读者还需要进一步处理多层贴图，顶点颜色，法向量等数据。在导出的骨架中，还应该能更方便的处理骨架的层次关系，以及能区分角色的上身和下身。因为通常一个骨骼动画系统是需要进行上下身的动作融合的。其次骨骼的变换矩阵应该是保存相对于父骨骼的局部变换矩阵，局部矩阵可以分解成四元数，平移和缩放，使用四元数能进行更平滑的插值和动作间的过渡，而且为了减少动作文件的大小，关键帧动画也是值得考虑的一个技术点。<br><br>在数据组织方面，我认为一个完善的骨骼动画系统应该能合理而自然的管理所有数据，XReal3D的数据组织采用的是流式存储，所有的动作、骨架、顶点位置、纹理坐标都拥有自己的流。这样的存储方式，在保持弹性的前提下大大的减少了一个骨骼动画角色的文件数量，方便管理和维护。<br><br>此外，现在的角色动画系统还应该包括表情动画和柔体系统。表情动画，一般称为在3DsMax的例子里有一个修改器就是表情动画，编译这个插件和使用它的头文件，我们可以访问Max里的表情动画。这个插件的位置在X:\3dsmax\maxsdk\samples\modifiers \morpher中。柔体系统也叫布料系统，可以用它来制作衣服飘动的效果。<br><br>最后一个需要说的IGame， IGame是3DsMaxSDK中附带一个用来为导出3D游戏相关数据的开发包。基本上，IGame是3DsMAXSDK的一个包装，使用起来更加方便而已。要深入的了解和掌握3DsMax插件的开发，还是需要对3DsMaxSDK有一定的熟悉程度。<br><br>总而言之，一个完整的骨骼动画系统插件是非常复杂的工程，鉴于文章篇幅，不可能介绍的面面俱到，希望本文对那些初次接触MAX插件开发的读者能有一定的帮助。 <br><br><strong>参考资料</strong><br><br>1．3DsMAX Help<br><br>2.&nbsp; Cal3D :&nbsp; https://gna.org/projects/cal3d/<br><br>3.&nbsp; XReal3D MAX Exporter : http://gforge.osdn.net.cn/projects/xreal3d/<br><br><strong>作者简介：</strong><br><br>潘李亮，2003年毕业于西北工业大学，爱好计算机图形学，曾在游戏公司负责3D引擎开发工作，目前在Corel公司从事多媒体软件开发。</h5>
</div>
<img src ="http://www.cppblog.com/liangairan/aggbug/85828.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2009-05-26 18:06 <a href="http://www.cppblog.com/liangairan/articles/85828.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>渲染状态管理（转载）</title><link>http://www.cppblog.com/liangairan/articles/78443.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Tue, 31 Mar 2009 02:25:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/78443.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/78443.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/78443.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/78443.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/78443.html</trackback:ping><description><![CDATA[<font size=2><strong>文档简介：<br></strong>　　提高3D图形程序的性能是个很大的课题。图形程序的优化大致可以分成两大任务，一是要有好的场景管理程序，能快速剔除不可见多边形，并根据对象距相机远近选择合适的细节（LOD）；二是要有好的渲染程序，能快速渲染送入渲染管线的可见多边形。 <br><br>　　我们知道，使用OpenGL或Direct3D渲染图形时，首先要设置渲染状态，渲染状态用于控制渲染器的渲染行为。应用程序可以通过改变渲染状态来控制OpenGL或Direct3D的渲染行为。比如设置Vertex/Fragment Program、绑定纹理、打开深度测试、设置雾效等。<br><br>　　改变渲染状态对于显卡而言是比较耗时的操作，而如果能合理管理渲染状态，避免多余的状态切换，将明显提升图形程序性能。这篇文章将讨论渲染状态的管理。<br><br><strong>文档目录：</strong><br>　　</font><a href="http://dev.gameres.com/Program/Visual/3D/renderstate.htm#基本思想" target=_self><u><font color=#800080 size=2>基本思想</font></u></a><br><font size=2>　　</font><a href="http://dev.gameres.com/Program/Visual/3D/renderstate.htm#渲染状态管理的问题" target=_self><u><font color=#800080 size=2>实际问题</font></u></a><br><font size=2>　　</font><a href="http://dev.gameres.com/Program/Visual/3D/renderstate.htm#渲染脚本" target=_self><u><font color=#800080 size=2>渲染脚本</font></u></a><br><br><font size=2>文档内容：<br><br></font><strong><a name=基本思想>基本思想</a></strong>
<p style="MARGIN-TOP: 6px; MARGIN-BOTTOM: 5px; WORD-SPACING: 1px"><font size=2>　　我们考虑一个典型的游戏场景，包含人、动物、植物、建筑、交通工具、武器等。稍微分析一下就会发现，实际上场景里很多对象的渲染状态是一样的，比如所有的人和动物的渲染状态一般都一样，所有的植物渲染状态也一样，同样建筑、交通工具、武器也是如此。我们可以把具有相同的渲染状态的对象归为一组，然后分组渲染，对每组对象只需要在渲染前设置一次渲染状态，并且还可以保存当前的渲染状态，设置渲染状态时只需改变和当前状态不一样的状态。这样可以大大减少多余的状态切换。下面的代码段演示了这种方法：</font><br>&nbsp;</p>
<div align=center>
<center>
<table cellSpacing=0 cellPadding=0 width=560 bgColor=#c0c0c0 border=0>
    <tbody>
        <tr>
            <td width="100%"><font color=#000000><font face=Fixedsys><font size=2>&nbsp;<br>&nbsp;// 渲染状态组链表，由场景管理程序填充<br>&nbsp;RenderStateGroupList groupList;<br>&nbsp;// 当前渲染状态<br>&nbsp;RenderState curState;<br><br>&nbsp;</font>&#8230;&#8230;<font face="Courier New" size=2><br><br>&nbsp;// </font></font><font size=2>遍历链表中的每个组<br></font><font face=Fixedsys>&nbsp;RenderStateGroup *group = groupList.GetFirst();<br>&nbsp;while ( group != NULL )<br>&nbsp;{&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp; // <font size=2>设置该组的渲染状态<br></font>&nbsp;&nbsp;&nbsp;&nbsp; RenderState *state = group-&gt;GetRenderState();<br>&nbsp;&nbsp;&nbsp;&nbsp; state-&gt;ApplyRenderState( curState );<br><br>&nbsp;&nbsp;&nbsp;&nbsp; // <font size=2>该渲染状态组的对象链表<br></font>&nbsp;&nbsp;&nbsp;&nbsp; RenderableObjectList *objList = group-&gt;GetRenderableObjectList();<br>&nbsp;&nbsp;&nbsp;&nbsp; // <font size=2>遍历对象链表的每个对象<br></font>&nbsp; &nbsp;&nbsp; RenderableObject *obj = objList-&gt;GetFirst();<br>&nbsp;&nbsp;&nbsp;&nbsp; while ( obj != NULL )<br>&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // <font size=2>渲染对象<br>&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp; obj-&gt;Render();<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; obj = objList-&gt;GetNext();<br>&nbsp;&nbsp;&nbsp;&nbsp; }<br><br>&nbsp;&nbsp;&nbsp;&nbsp; group = groupList.GetNext();&nbsp;<br>&nbsp;}<br>&nbsp;<br>&nbsp;</font>其中<font face=Fixedsys size=2>RenderState</font>类的<font face=Fixedsys size=2>ApplyRenderState</font>方法形如：<font size=2>&nbsp;<br>&nbsp;void RenderState::ApplyRenderState( RenderState &amp;curState )&nbsp;<br>&nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp; // 深度测试 </font></font><font size=2><br></font><font face=Fixedsys>&nbsp;&nbsp;&nbsp;&nbsp; if ( depthTest != curState.depthTest )<br>&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SetDepthTest( depthTest );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; curState.depthTest = depthTest;<br>&nbsp;&nbsp;&nbsp;&nbsp; }<br><br>&nbsp;&nbsp;&nbsp;&nbsp; // Alpha<font size=2>测试<br></font>&nbsp;&nbsp;&nbsp;&nbsp; if ( alphaTest != curState.alphaTest )<br>&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SetAlphaTest( alphaTest );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; curState.alphaTest = alphaTest;<br>&nbsp;&nbsp;&nbsp;&nbsp; }<br><br>&nbsp;&nbsp;&nbsp;&nbsp; // <font size=2>其它渲染状态<br>&nbsp;&nbsp;&nbsp;&nbsp; </font>&#8230;&#8230;<br><font size=2>&nbsp;}<br></font></font>&nbsp; <font face=Fixedsys>&nbsp; </font></font></td>
        </tr>
    </tbody>
</table>
</center></div>
<p style="MARGIN-TOP: 6px; MARGIN-BOTTOM: 5px; WORD-SPACING: 1px">&nbsp;&nbsp;&nbsp;&nbsp;</p>
<p style="MARGIN-TOP: 6px; MARGIN-BOTTOM: 5px; WORD-SPACING: 1px"><font size=2>　　这些分组的渲染状态一般被称为<font face=Arial>Material</font>或<font face=Arial>Shader</font>。这里<font face=Arial>Material</font>不同于<font face=Arial>OpenGL</font>和<font face=Arial>Direct3D</font>里面用于光照的材质，<font face=Arial>Shader</font>也不同于<font face=Arial>OpenGL</font>里面的<font face=Arial>Vertex/Fragment Program</font>和<font face=Arial>Direct3D</font>里面的<font face=Arial>Vertex/Pixel Shader</font>。而是指封装了的显卡渲染图形需要的状态（也包括了<font face=Arial>OpenGL</font>和<font face=Arial>Direct3D</font>原来的<font face=Arial>Material</font>和<font face=Arial>Shader</font>）。</font></p>
<p style="MARGIN-TOP: 6px; MARGIN-BOTTOM: 5px; WORD-SPACING: 1px"><font size=2>　　从字面上看，<font face=Arial>Material</font>（材质）更侧重于对象表面外观属性的描述，而<font face=Arial>Shader</font>（这个词实在不好用中文表示）则有用程序控制对象表面外观的含义。由于显卡可编程管线的引入，渲染状态中包含了<font face=Arial>Vertex/Fragment Program</font>，这些小程序可以控制物体的渲染，所以我觉得将封装的渲染状态称为<font face=Arial>Shader</font>更合适。这篇文章也将称之为<font face=Arial>Shader</font>。</font></p>
<p style="MARGIN-TOP: 6px; MARGIN-BOTTOM: 5px; WORD-SPACING: 1px"><font size=2>　　上面的代码段只是简单的演示了渲染状态管理的基本思路，实际上渲染状态的管理需要考虑很多问题。</font></p>
<p><font size=2><br>　</font></p>
<h3 style="MARGIN-TOP: 6px; MARGIN-BOTTOM: 5px; WORD-SPACING: 1px"><font size=3><a name=渲染状态管理的问题>渲染状态管理的问题</a></font></h3>
<p style="MARGIN-TOP: 6px; MARGIN-BOTTOM: 5px; WORD-SPACING: 1px">　</p>
<h4 style="MARGIN-TOP: 6px; MARGIN-BOTTOM: 5px; WORD-SPACING: 1px">　消耗时间问题</h4>
<p style="MARGIN-TOP: 6px; MARGIN-BOTTOM: 5px; WORD-SPACING: 1px"><font size=2>　　改变渲染状态时，不同的状态消耗的时间并不一样，甚至在不同条件下改变渲染状态消耗的时间也不一样。比如绑定纹理是一个很耗时的操作，而当纹理已经在显卡的纹理缓存中时，速度就会非常快。而且随着硬件和软件的发展，一些很耗时的渲染状态的消耗时间可能会有减少。因此并没有一个准确的消耗时间的数据。</font></p>
<p style="MARGIN-TOP: 6px; MARGIN-BOTTOM: 5px; WORD-SPACING: 1px"><font size=2>　　虽然消耗时间无法量化，情况不同消耗的时间也不一样，但一般来说下面这些状态切换是比较消耗时间的：</font></p>
<ul>
    <li>
    <p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 4px; WORD-SPACING: 1px"><font face=Arial size=2>Vertex/Fragment Program</font><font size=2>模式和固定管线模式的切换（<font face=Arial>FF</font>，<font face=Arial>Fixed Function Pipeline</font>） </font></p>
    <li>
    <p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 4px; WORD-SPACING: 1px"><font face=Arial size=2>Vertex/Fragment Program</font><font size=2>本身程序的切换 </font></p>
    <li>
    <p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 4px; WORD-SPACING: 1px"><font size=2>改变<font face=Arial>Vertex/Fragment Program</font>常量 </font></p>
    <li>
    <p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 4px; WORD-SPACING: 1px"><font size=2>纹理切换 </font></p>
    <li>
    <p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 4px; WORD-SPACING: 1px"><font size=2>顶点和索引缓存（<font face=Arial>Vertex &amp; Index Buffers</font>）切换 </font></p>
    </li>
</ul>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 4px; WORD-SPACING: 1px"><font size=2>　　有时需要根据消耗时间的多少来做折衷，下面将会遇到这种情况。 </font></p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 4px; WORD-SPACING: 1px">&nbsp; </p>
<h4 style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 4px; WORD-SPACING: 1px">　渲染状态分类</h4>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 4px; WORD-SPACING: 1px"><font size=2>　　实际场景中，往往会出现这样的情况，一类对象其它渲染状态都一样，只是纹理和顶点、索引数据不同。比如场景中的人，只是身材、长相、服装等不同，也就是说只有纹理、顶点、索引数据不同，而其它如<font face=Arial>Vertex/Fragment Program</font>、深度测试等渲染状态都一样。相反，一般不会存在纹理和顶点、索引数据相同，而其他渲染状态不同的情况。我们可以把纹理、顶点、索引数据不归入到<font face=Arial>Shader</font>中，这样场景中所有的人都可以用一个<font face=Arial>Shader</font>来渲染，然后在这个<font face=Arial>Shader</font>下对纹理进行分组排序，相同纹理的人放在一起渲染。</font></p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 4px; WORD-SPACING: 1px">&nbsp;</p>
<h4 style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 4px; WORD-SPACING: 1px">　多道渲染（<font face=Arial>Multipass Rendering</font>）</h4>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 4px; WORD-SPACING: 1px"><font size=2>　　有些比较复杂的图形效果，在低档显卡上需要渲染多次，每次渲染一种效果，然后用<font face=Arial>GL_BLEND</font>合成为最终效果。这种方法叫多道渲染<font face=Arial>Multipass Rendering</font>，渲染一次就是一个<font face=Arial>pass</font>。比如做逐像素凹凸光照，需要计算环境光、漫射光凹凸效果、高光凹凸效果，在<font face=Arial>NV20</font>显卡上只需要<font face=Arial>1</font>个<font face=Arial>pass</font>，而在<font face=Arial>NV10</font>显卡上则需要<font face=Arial>3</font>个<font face=Arial>pass</font>。<font face=Arial>Shader</font>应该支持多道渲染，即一个<font face=Arial>Shader</font>应该分别包含每个<font face=Arial>pass</font>的渲染状态。</font></p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 4px; WORD-SPACING: 1px"><font size=2>&nbsp;&nbsp;&nbsp; 不同的<font face=Arial>pass</font>往往渲染状态和纹理都不同，而顶点、索引数据是一样的。这带来一个问题：是以对象为单位渲染，一次渲染一个对象的所有<font face=Arial>pass</font>，然后渲染下一个对象；还是以<font face=Arial>pass</font>为单位渲染，第一次渲染所有对象的第一个<font face=Arial>pass</font>，第二次渲染所有对象的第二个<font face=Arial>pass</font>。下面的程序段演示了这两种方式：</font></p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 4px; WORD-SPACING: 1px">&nbsp;</p>
<div align=center>
<center>
<table height=300 cellSpacing=0 cellPadding=0 width=560 bgColor=#c0c0c0 border=0>
    <tbody>
        <tr>
            <td width="100%" bgColor=#000000 height=30><font face="Courier New" color=#ffffff>&nbsp;</font><font color=#ffffff>以对象为单位渲染</font></td>
        </tr>
        <tr>
            <td width="100%" height=274><font face=Fixedsys color=#000000 size=2>&nbsp;<br>&nbsp;// 渲染状态组链表，由场景管理程序填充<br>&nbsp;ShaderGroupList groupList;<br><br>&nbsp;&#8230;&#8230;<br><br>&nbsp;// 遍历链表中的每个组<br>&nbsp;ShaderGroup *group = groupList.GetFirst();<br>&nbsp;while ( group != NULL )<br>&nbsp;{&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp; Shader *shader = group-&gt;GetShader();<br>&nbsp;<br>&nbsp;&nbsp;&nbsp; &nbsp;RenderableObjectList *objList = group-&gt;GetRenderableObjectList();<br><br>&nbsp;&nbsp;&nbsp;&nbsp; // 遍历相同Shader的每个对象<br>&nbsp;&nbsp;&nbsp; &nbsp;RenderableObject *obj = objList-&gt;GetFirst();<br>&nbsp;&nbsp;&nbsp; &nbsp;while ( obj != NULL )<br>&nbsp;&nbsp;&nbsp; &nbsp;{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 获取shader的pass数<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int iNumPasses = shader-&gt;GetPassNum();<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; for ( int i = 0; i &lt; iNumPasses; i++ )<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 设置shader第i个pass的渲染状态<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; shader-&gt;ApplyPass( i );<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 渲染对象<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; obj-&gt;Render();<br>&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; }<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; obj = objList-&gt;GetNext();<br>&nbsp;&nbsp;&nbsp; &nbsp;}<br>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp; group = groupList-&gt;GetNext();<br>&nbsp;}<br>&nbsp;</font></td>
        </tr>
    </tbody>
</table>
</center></div>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 4px; WORD-SPACING: 1px">&nbsp;&nbsp;</p>
<div align=center>
<center>
<table height=45 cellSpacing=0 cellPadding=0 width=560 border=0>
    <tbody>
        <tr>
            <td width="100%" bgColor=#000000 height=30><font color=#ffffff>&nbsp;以<font face=Arial>pass</font>为单位渲染</font></td>
        </tr>
        <tr>
            <td width="100%" bgColor=#c0c0c0 height=18><font color=#000000><font size=2><font face=Fixedsys>&nbsp;</font> <font face=Fixedsys><br>&nbsp;// 渲染状态组链表，由场景管理程序填充<br>&nbsp;ShaderGroupList groupList;<br>&nbsp;<br>&nbsp;&#8230;&#8230;<br>&nbsp;</font> </font><font face=Fixedsys><font face="Courier New" size=2><br></font><font size=2>&nbsp;for ( int i = 0; i &lt; MAX_PASSES_NUM; i++ )<br>&nbsp;{ </font><br><font size=2>&nbsp;&nbsp;&nbsp;&nbsp; // 遍历链表中的每个组 </font></font><font size=2><br></font><font face=Fixedsys>&nbsp;&nbsp; &nbsp; ShaderGroup *group = groupList.GetFirst();<br>&nbsp;&nbsp;&nbsp;&nbsp; while ( group != NULL )<br>&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; Shader *shader = group-&gt;GetShader();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int iNumPasses = shader-&gt;GetPassNum();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // <font size=2>如果shader的pass数小于循环次数，跳过此shader<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if( i &gt;= iNumPasses )<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; group = groupList-&gt;GetNext();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; continue;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br><br></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // <font size=2>设置shader第</font>i<font size=2>个pass的渲染状态<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; shader-&gt;ApplyPass( i );<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; RenderableObjectList *objList =&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; group-&gt;GetRenderableObjectList();<br>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; // 遍历相同Shader的每个对象<br></font>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; RenderableObject *obj = objList-&gt;GetFirst();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; while ( obj != NULL )<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {<br><font size=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; obj-&gt;Render();<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; obj = objList-&gt;GetNext();<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; }<br><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; group = groupList-&gt;GetNext();<br>&nbsp;&nbsp;&nbsp;&nbsp; }<br>&nbsp;}<br>&nbsp;</font></font></font></td>
        </tr>
    </tbody>
</table>
</center></div>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 4px; WORD-SPACING: 1px">&nbsp;&nbsp;&nbsp;&nbsp;<br><font size=2>　　这两种方式各有什么优缺点呢？</font></p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 4px; WORD-SPACING: 1px"><font size=2>　　以对象为单位渲染，渲染一个对象的第一个<font face=Arial>pass</font>后，马上紧接着渲染这个对象的第二个<font face=Arial>pass</font>，而每个<font face=Arial>pass</font>的顶点和索引数据是相同的，因此第一个<font face=Arial>pass</font>将顶点和索引数据送入显卡后，显卡<font face=Arial>Cache</font>中已经有了这个对象顶点和索引数据，后续<font face=Arial>pass</font>不必重新将顶点和索引数据拷到显卡，因此速度会非常快。而问题是每个<font face=Arial>pass</font>的渲染状态都不同，这使得实际上每次渲染都要设置新的渲染状态，会产生大量的多余渲染状态切换。</font></p>
<p style="MARGIN-TOP: 3px; MARGIN-BOTTOM: 4px; WORD-SPACING: 1px"><font size=2>　　以<font face=Arial>pass</font>为单位渲染则正好相反，以<font face=Arial>Shader</font>分组，相同<font face=Arial>Shader</font>的对象一起渲染，可以只在这组开始时设置一次渲染状态，相比以对象为单位，大大减少了渲染状态切换。可是每次渲染的对象不同，因此每次都要将对象的顶点和索引数据拷贝到显卡，会消耗不少时间。<br>　　可见想减少渲染状态切换就要频繁拷贝顶点索引数据，而想减少拷贝顶点索引数据又不得不增加渲染状态切换。鱼与熊掌不可兼得 :-(<br>　　由于硬件条件和场景数据的情况比较复杂，具体哪种方法效率较高并没有定式，两种方法都有人使用，具体选用那种方法需要在实际环境测试后才能知道。</font><br>&nbsp;</p>
<h4 style="MARGIN-TOP: 6px; MARGIN-BOTTOM: 5px; WORD-SPACING: 1px">　多光源问题</h4>
<p style="MARGIN-TOP: 6px; MARGIN-BOTTOM: 5px; WORD-SPACING: 1px">&nbsp;<font color=#0000ff>待续&#8230;&#8230;</font></p>
<p style="MARGIN-TOP: 6px; MARGIN-BOTTOM: 5px; WORD-SPACING: 1px">&nbsp;</p>
<h4 style="MARGIN-TOP: 6px; MARGIN-BOTTOM: 5px; WORD-SPACING: 1px">　阴影问题</h4>
<p style="MARGIN-TOP: 6px; MARGIN-BOTTOM: 5px; WORD-SPACING: 1px">&nbsp;<font color=#0000ff>待续&#8230;&#8230;</font></p>
<p><font size=2><br>　</font></p>
<h3 style="MARGIN-TOP: 6px; MARGIN-BOTTOM: 5px; WORD-SPACING: 1px"><font size=3><a name=渲染脚本>渲染脚本</a></font></h3>
<p style="MARGIN-TOP: 6px; MARGIN-BOTTOM: 5px; WORD-SPACING: 1px"><font size=2>　　现在很多图形程序都会自己定义一种脚本文件来描述<font face=Arial>Shader</font>。</font></p>
<p style="MARGIN-TOP: 6px; MARGIN-BOTTOM: 5px; WORD-SPACING: 1px"><font size=2>　　比如较早的<font face=Arial color=#0000ff><a href="http://sourceforge.net/projects/ogre" target=_blank><u>OGRE</u></a></font>（<font face=Arial>Object-oriented Graphics Rendering Engine</font>，面向对象图形渲染引擎）的<font face=Arial>Material</font>脚本，<font face=Arial color=#0000ff><a href="http://www.heppler.com/shader/" target=_blank><u>Quake3</u></a></font>的<font face=Arial>Shader</font>脚本，以及刚问世不久的<font face=Arial>Direct3D</font>的<font face=Arial>Effect File</font>，<font face=Arial>nVIDIA</font>的<font face=Arial>CgFX</font>脚本（文件格式与<font face=Arial>Direct3D Effect File</font>兼容），<font face=Arial>ATI</font> <font face=Arial color=#0000ff><a href="http://mirror.ati.com/developer/sdk/radeonSDK/html/Tools/RenderMonkey.html" target=_blank><u>RenderMonkey</u></a></font>使用的<font face=Arial>xml</font>格式的脚本。<font face=Arial>OGRE Material</font>和<font face=Arial>Quake3 Shader</font>这两种脚本比较有历史了，不支持可编程渲染管线。而后面三种比较新的脚本都支持可编程渲染管线。</font></p>
<p style="MARGIN-TOP: 6px; MARGIN-BOTTOM: 5px; WORD-SPACING: 1px">&nbsp;</p>
<div align=center>
<center>
<table height=154 cellSpacing=1 cellPadding=0 width=560 border=0>
    <tbody>
        <tr>
            <td width=143 bgColor=#ff9900 height=31><font face=黑体>&nbsp;脚本</font></td>
            <td width=312 bgColor=#ff9900 height=31><font face=黑体>&nbsp;特性</font></td>
            <td width=41 bgColor=#ff9900 height=31><font face=黑体>&nbsp;范例</font></td>
        </tr>
        <tr>
            <td width=143 bgColor=#c0c0c0 height=26><font face=Arial color=#000000 size=2>&nbsp;OGRE Material</font></td>
            <td width=312 bgColor=#c0c0c0 height=26><font color=#000000 size=2>封装各种渲染状态，不支持可编程渲染管线</font></td>
            <td width=41 bgColor=#c0c0c0 height=26><font size=2>&nbsp;<a href="http://www.maple3d.net/article/renderstate/ogre.html" target=_blank><u><font color=#0000ff>&gt;&gt;&gt;&gt;</font></u></a></font></td>
        </tr>
        <tr>
            <td width=143 bgColor=#e0e0e0 height=26><font face=Arial color=#000000 size=2>&nbsp;Quake3 Shader</font></td>
            <td borderColor=#808080 width=312 bgColor=#e0e0e0 height=26><font color=#000000 size=2>封装渲染状态，支持一些特效，不支持可编程渲染管线</font></td>
            <td borderColor=#808080 width=41 bgColor=#e0e0e0 height=26><font size=2>&nbsp;<a href="http://www.maple3d.net/article/renderstate/quake3.html" target=_blank><u><font color=#0000ff>&gt;&gt;&gt;&gt;</font></u></a></font></td>
        </tr>
        <tr>
            <td width=143 bgColor=#c0c0c0 height=26><font face=Arial color=#000000 size=2>&nbsp;Direct3D Effect File</font></td>
            <td width=312 bgColor=#c0c0c0 height=26><font color=#000000 size=2>封装渲染状态，支持<font face=Arial>multipass</font>，支持可编程渲染管线</font></td>
            <td width=41 bgColor=#c0c0c0 height=26><font size=2>&nbsp;<a href="http://www.maple3d.net/article/renderstate/d3deffect.html" target=_blank><u><font color=#0000ff>&gt;&gt;&gt;&gt;</font></u></a></font></td>
        </tr>
        <tr>
            <td width=143 bgColor=#e0e0e0 height=26><font color=#000000 size=2><font face=Arial>&nbsp;nVIDIA CgFX</font>脚本</font></td>
            <td width=312 bgColor=#e0e0e0 height=26><font color=#000000 size=2>封装渲染状态，支持<font face=Arial>multipass</font>，支持可编程渲染管线</font></td>
            <td width=41 bgColor=#e0e0e0 height=26><font size=2>&nbsp;<a href="http://www.maple3d.net/article/renderstate/nvcgfx.html" target=_blank><u><font color=#0000ff>&gt;&gt;&gt;&gt;</font></u></a></font></td>
        </tr>
        <tr>
            <td width=143 bgColor=#c0c0c0 height=27><font color=#000000 size=2><font face=Arial>&nbsp;ATI RenderMonkey</font>脚本</font></td>
            <td width=312 bgColor=#c0c0c0 height=27><font color=#000000 size=2>封装渲染状态，支持<font face=Arial>multipass</font>，支持可编程渲染管线</font></td>
            <td width=41 bgColor=#c0c0c0 height=27><font size=2>&nbsp;<a href="http://www.maple3d.net/article/renderstate/atirm.xml" target=_blank><u><font color=#0000ff>&gt;&gt;&gt;&gt;</font></u></a></font></td>
        </tr>
    </tbody>
</table>
</center></div>
<p style="MARGIN-TOP: 6px; MARGIN-BOTTOM: 5px; WORD-SPACING: 1px">&nbsp;</p>
<p style="MARGIN-TOP: 6px; MARGIN-BOTTOM: 5px; WORD-SPACING: 1px"><font size=2>　　使用脚本来控制渲染有很多好处：</font></p>
<ul>
    <li>
    <p style="MARGIN-TOP: 6px; MARGIN-BOTTOM: 5px; WORD-SPACING: 1px"><font size=2>可以非常方便的修改一个物体的外观而不需重新编写或编译程序 </font></p>
    <li>
    <p style="MARGIN-TOP: 6px; MARGIN-BOTTOM: 5px; WORD-SPACING: 1px"><font size=2>可以用外围工具以所见即所得的方式来创建、修改脚本文件（类似<font face=Arial>ATI RenderMonkey</font>的工作方式），便于美工、关卡设计人员设定对象外观，建立外围工具与图形引擎的联系 </font></p>
    <li>
    <p style="MARGIN-TOP: 6px; MARGIN-BOTTOM: 5px; WORD-SPACING: 1px"><font size=2>可以在渲染时将相同外观属性及渲染状态的对象（也就是<font face=Arial>Shader</font>相同的对象）归为一组，然后分组渲染，对每组对象只需要在渲染前设置一次渲染状态，大大减少了多余的状态切换</font></p>
    </li>
</ul>
<img src ="http://www.cppblog.com/liangairan/aggbug/78443.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2009-03-31 10:25 <a href="http://www.cppblog.com/liangairan/articles/78443.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Frustum culling</title><link>http://www.cppblog.com/liangairan/articles/77917.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Thu, 26 Mar 2009 02:57:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/77917.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/77917.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/77917.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/77917.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/77917.html</trackback:ping><description><![CDATA[<center>by&nbsp;<font color=#ffffff><font size=2><font face=Verdana> <!--go AWAY SPAM!!!-->
<script language=javascript>document.write('<a href=\"mailto:' +  '' + '' + ''
+''
+''
+''
+''
+ 'd_picco' + ''
+''
+''
+''
+''
+ '@' + 'hotmail' +  '.' + ''
+''
+''
+''
+''
+''
+''
+ 'com\">' + 'Dion Picco' + '</a>')</script>
</font></font></font><a href="&#109;&#97;&#105;&#108;&#116;&#111;&#58;&#100;&#95;&#112;&#105;&#99;&#99;&#111;&#64;&#104;&#111;&#116;&#109;&#97;&#105;&#108;&#46;&#99;&#111;&#109;"><u><font face=Verdana color=#0000ff size=2>Dion Picco</font></u></a><br><br>
<table style="TABLE-LAYOUT: fixed" cellSpacing=0 cellPadding=0 width="80%" border=0>
    <tbody>
        <tr>
            <td width="100%"><font face="Verdana, Helvetica, Arial, Times New Roman" color=#ffffff size=3><strong>Introduction <font size=1><br><img src="http://www.flipcode.com/archives/line_grey.png"><br><br></font></strong></font></td>
        </tr>
    </tbody>
</table>
</center>
<center>
<table style="TABLE-LAYOUT: fixed" cellSpacing=0 cellPadding=0 width="80%" border=0>
    <tbody>
        <tr>
            <td width="100%"><font face="Verdana, Helvetica, Arial, Times New Roman" color=#ffe291 size=2><span style="COLOR: #000000">I always amazed by the fact that most neophyte 3D engine programmers still do not realize the simple principal and benefits of frustum culling. I often frequent the forums on flipcode and I find that there are a ton of questions regarding this subject despite the plethora of information freely available. So I抳e decided to put together this simple document outlining my frustum culling procedures that I use in my current quad-tree culled engine. There are variations and perhaps much better ways of doing some of the culling techniques but the methods presented here should suffice for learning. Before I start I want to mention one thing. I previously have used the term frustrum but I was constantly beleaguered by the denizens of the forums for this incorrect moniker. As it turns out, frustum is the correct term. I apologize to anyone whom I抳e offended... you nit-picky twits.<br><br>Most people already know what the viewing frustum is. For those who don抰, it抯 simply the area of your world visible to your current camera. The shape of the frustum is a pyramid with the nearest peak truncated at what is deemed the 憂ear?clipping plane (see Figure 1). In fact the frustum itself is (or can be) defined by 6 planes. These planes are named (surprise, surprise) the near plane, the far plane, the left plane, the right plane, the top plane and the bottom plane. Frustum culling is simply the process of determining whether or not objects are visible in this area. Since frustum culling is essentially a 3D world-space procedure, it can be processed long before we even deal with individual polygons in our pipeline. Hence, we can quickly reject on the object level, unlike backface culling for example which takes place much later in the rendering pipeline and works on a per-polygon basis. This means that we don抰 even have to send the data to the video card once the object is frustum culled which of course makes quite a difference in rendering speed. It is simply very, very fast to render nothing.<br></span><br>
            <center><img height=513 src="http://www.flipcode.com/archives/article_frustumculling01.png" width=568></center><br><br>The maximum benefit of frustum culling comes from a hierarchy culling method. This means that our world is broken down into a tree-like structure. Once we cull a top level node, we don抰 have to cull lower level nodes since they cannot be visible anyway. This means that we don抰 frustum cull ALL the objects in our world. We simply process them in a hierarchal fashion, which greatly reduces the number of frustum culls. Without a hierarchal method, frustum culling still has a good advantage over not doing it at all but it also means that it scales linearly with the number of objects in our world. In other words, 100 objects require 100 frustum culls. 1,000,000 objects require 1,000,000 culls. At some point we end up spending so much time doing the culling anyway that we are not going to notice an increase in speed. In designing a fast game, we NEVER EVER want any of our algorithms working on a linear scale unless there are only 2 or 3 items to process or there is no better way of doing it. I refuse to accept the latter. This means that a hierarchal culling method is necessary. Consider the case where we have 100 objects, with only 1 visible, and we are going to cull them in a binary fashion (very simplistic for this example). Normally in a linear method, we would simply test every single object (all 100) and check whether or not they are visible. This of course results in 100 cull checks, although it抯 possible that we could encounter an early out. Now consider a binary case. In the first check we can reduce our number to 50... the next check reduces our number to 25... the next to 13... the next to 7... the next to 4... the next to 2 and finally to 1. Six checks in total! That抯 quite a far cry from the 100. And it gets much better relative to the linear method as the number of items increases. In fact in this case, the linear method has to check N items whereas the binary method has to only check log N items (log base 2). Type some numbers into a calculator and see the difference for yourself.<br><br>For this example I am going to use a quad-tree for my hierarchal culling method. An octree or binary tree or any other structure could be used. In fact, most of the code will easily carry over. I choose a quad-tree since it抯 inherently easy to visualize. A quad-tree is essentially a 2-dimensional area constructed using a tree structure where each node has 4 children (see Figure 2). In this case, the children each occupy one quarter of the area of the parent quad-tree. This can be defined then again (recursively) for each of the children nodes. What this then forms is a hierarchy where the children nodes are contained entirely within the parent node. When we decide the parent node isn抰 visible, we can safely assume that the child nodes are also not visible. By setting our world up this way we can quickly cull LARGE amounts of our world with just a simple few culling checks. This works great for a terrain engine for example. And it抯 extendable as well. We can add our trees or bushes or rocks into this quad-tree into the smallest nodes which they entirely fit. Then when we perform our hierarchal culling and determine that a node isn抰 visible, we can also assume that any objects (trees, rocks, etc) are also not visible. It becomes a beautiful system capable of handling large worlds with lots of objects and still running very fast. And to make the best of it, it抯 very simple!<br><br>
            <center><img height=574 src="http://www.flipcode.com/archives/article_frustumculling02.png" width=576></center></font></td>
        </tr>
    </tbody>
</table>
</center><br><br>
<center>
<table style="TABLE-LAYOUT: fixed" cellSpacing=0 cellPadding=0 width="80%" border=0>
    <tbody>
        <tr>
            <td width="100%"><font face="Verdana, Helvetica, Arial, Times New Roman" color=#ffffff size=3><strong>Fundamental Methods 1 <font size=1><br><img src="http://www.flipcode.com/archives/line_grey.png"><br><br></font></strong></font></td>
        </tr>
    </tbody>
</table>
</center>
<center>
<table style="TABLE-LAYOUT: fixed" cellSpacing=0 cellPadding=0 width="80%" border=0>
    <tbody>
        <tr>
            <td width="100%"><font face="Verdana, Helvetica, Arial, Times New Roman" color=#ffe291 size=2>The first step in planning a system like this is making sure you have the basic methods of culling up and running. This means that we want to be able to construct the 6 planes of the frustum from our view/projection matrices as well as check whether a sphere is outside the frustum and whether a bounding box is outside the frustum. To be more concise, we want to know whether a sphere and box either is contained entirely within the frustum, entirely outside or intersecting the frustum. This will allow us to make more 憈weaks?to our hierarchal culling system later on. These are the methods we will look at in this section.<br><br>First let抯 try to define our frustum. The 6 planes of the frustum can be defined from the view/projection matrix which form our camera system in our rendering API. Constructing these are a bit different for Direct3D and OpenGL. I could try to explain both or even one but I would probably just confuse you more. Luckily a friend of mine wrote a great pdf covering this. I met Gil when I visited Raven Software back in 1996. I was actually hired by them to work with Gil but NAFTA regulations would not allow me to cross the border without a university degree. Damn you NAFTA! Damn you all to hell! But anyway, that抯 another story for another time. The important part is this great pdf document that very simply explains the process of extracting the planes from the matrices. I suggest you read this and understand it. Here is the link:<br><br><a href="http://www2.ravensoft.com/users/ggribb/plane%20extraction.pdf"><u><font color=#0000ff>http://www2.ravensoft.com/users/ggribb/plane%20extraction.pdf</font></u></a><br><br>Now I assume that you have a frustum class built that simply contains the 6 planes that define it. Make sure that you reconstruct this frustum and store it in your camera class each time the view or projection matrix changes and NOT each time you perform an intersection test. Our next step is to determine whether or not a sphere is contained within the frustum, outside the frustum or intersecting the frustum. This is actually a very simple process and breaks down like this: Determine whether the sphere is on the front side of a plane, the back side of a plane or intersecting a plane?do this for all 6 planes. This process itself is very simple indeed. What we need to do is to calculate the distance from the center of the sphere to the plane. If the absolute value of the distance is less than the radius of the sphere, then we are intersecting the plane. If the distance is greater than 0 then we are on the front side of the plane (and possibly inside the frustum). If it is less than 0 we are on the backside of the plane and definitely outside the frustum. Calculating the distance from the center of the sphere to the plane is as follows:<br><br><strong><em>C = center of sphere<br>N = normal of plane<br>D = distance of plane along normal from origin<br><br>Distance = DotProduct(C, N) + D</strong></em><br><br>So like I said previously, all we need to do now is to compare this distance to the radius of the sphere to determine the status of the intersection of the sphere in regards to the frustum. Here is the code that I use to perform this action:<br><br>
            <center>
            <div style="BORDER-RIGHT: #c0c0c0 1px solid; BORDER-TOP: #c0c0c0 1px solid; OVERFLOW: auto; BORDER-LEFT: #c0c0c0 1px solid; WIDTH: 100%; BORDER-BOTTOM: #c0c0c0 1px solid; BACKGROUND-COLOR: #ffffff">
            <table cellSpacing=0 cellPadding=12 width="100%" bgColor=#ffffff border=0>
                <tbody>
                    <tr>
                        <td width="100%" bgColor=#ffffff>
                        <pre><font face="Courier, Courier New" color=#000000>
                        <font color=#007f00>// tests if a sphere is within the frustrum
                        </font><font color=#0000ff>int</font> Frustrum::ContainsSphere(<font color=#0000ff>const</font> Sphere&amp; refSphere) <font color=#0000ff>const</font>
                        {
                        <font color=#007f00>// various distances
                        </font>	<font color=#0000ff>float</font> fDistance;<br><br>	<font color=#007f00>// calculate our distances to each of the planes
                        </font>	<font color=#0000ff>for</font>(<font color=#0000ff>int</font> i = 0; i &lt; 6; ++i) {<br><br>		<font color=#007f00>// find the distance to this plane
                        </font>		fDistance = m_plane[i].Normal().dotProduct(refSphere.Center())+m_plane[i].Distance();<br><br>		<font color=#007f00>// if this distance is &lt; -sphere.radius, we are outside
                        </font>		<font color=#0000ff>if</font>(fDistance &lt; -refSphere.Radius())
                        <font color=#0000ff>return</font>(OUT);<br><br>		<font color=#007f00>// else if the distance is between +- radius, then we intersect
                        </font>		<font color=#0000ff>if</font>((<font color=#0000ff>float</font>)fabs(fDistance) &lt; refSphere.Radius())
                        <font color=#0000ff>return</font>(INTERSECT);
                        }<br><br>	<font color=#007f00>// otherwise we are fully in view
                        </font>	<font color=#0000ff>return</font>(IN);
                        }
                        </font></pre>
                        </td>
                    </tr>
                </tbody>
            </table>
            </div>
            </center><br><br>Our next step is to determine (in the case of a quad-tree) the status of an axis-aligned bounding box in regards to the frustum. There are a few ways of going about this operation. In this case I simply define my bounding box as a series of 3 minimum values and 3 maximum values and then compare all 8 vertices of the box against the frustum. While this isn抰 the quickest way, it is certainly the easiest for the beginner to understand. The method for doing this is to test each and every vertex (corner) of the box against the frustum to see where they lie in relation to it. If all the points are inside the frustum, then the box is fully contained. If at least 1 point is inside the box but not all of them, then it intersects the frustum. If all the points are on the backside of a particular plane, then the box is outside. Otherwise, the box is considered intersecting. Why? Well, it is possible that none of the corners are inside the frustum itself but yet intersecting. In fact, consider the case where the frustum is contained entirely within the box. In this case, none of the points are inside the frustum but yet the box would still be considered visible. <br><br>Testing if a point is within the frustum is a simple procedure. All we need to do is to compare it to all 6 planes to make sure that it is on the front side of all of them (remember, all our planes face inwards into the frustum). Classifying a point relative to a plane is the same procedure we used in the sphere intersection method. We simply dot the point with the normal of the plane and then add the D component of the plane to that. If the value is greater than 0, then we are on the front of the plane. If it is less than 0, we are behind the plane. A value of 0 means that we are on the plane. Unless you have a specific purpose for classifying a point as on the plane or not, I wouldn抰 worry about it. I would simply use greater or equal to 0 when checking if we are on the front of the plane. (If you do have a reason for classifying a point as on the plane, then remember to NOT compare floats for equality but rather subtract them and check if the absolute value of that is less than some epsilon value). I won抰 bother to rewrite the method that I used in the sphere code but rest assured it抯 the same. Here is my method for comparing if a box is within the frustum or not:<br><br>
            <center>
            <div style="BORDER-RIGHT: #c0c0c0 1px solid; BORDER-TOP: #c0c0c0 1px solid; OVERFLOW: auto; BORDER-LEFT: #c0c0c0 1px solid; WIDTH: 100%; BORDER-BOTTOM: #c0c0c0 1px solid; BACKGROUND-COLOR: #ffffff">
            <table cellSpacing=0 cellPadding=12 width="100%" bgColor=#ffffff border=0>
                <tbody>
                    <tr>
                        <td width="100%" bgColor=#ffffff>
                        <pre><font face="Courier, Courier New" color=#000000>
                        <font color=#007f00>// tests if a AaBox is within the frustrum
                        </font><font color=#0000ff>int</font> Frustrum::ContainsAaBox(<font color=#0000ff>const</font> AaBox&amp; refBox) <font color=#0000ff>const</font>
                        {
                        Vector3f vCorner[8];
                        <font color=#0000ff>int</font> iTotalIn = 0;<br><br>	<font color=#007f00>// get the corners of the box into the vCorner array
                        </font>	refBox.GetVertices(vCorner);<br><br>	<font color=#007f00>// test all 8 corners against the 6 sides
                        </font>	<font color=#007f00>// if all points are behind 1 specific plane, we are out
                        </font>	<font color=#007f00>// if we are in with all points, then we are fully in
                        </font>	<font color=#0000ff>for</font>(<font color=#0000ff>int</font> p = 0; p &lt; 6; ++p) {
                        <font color=#0000ff>int</font> iInCount = 8;
                        <font color=#0000ff>int</font> iPtIn = 1;<br><br>		<font color=#0000ff>for</font>(<font color=#0000ff>int</font> i = 0; i &lt; 8; ++i) {<br><br>			<font color=#007f00>// test this point against the planes
                        </font>			<font color=#0000ff>if</font>(m_plane[p].SideOfPlane(vCorner[i]) == BEHIND) {
                        iPtIn = 0;
                        --iInCount;
                        }
                        }<br><br>		<font color=#007f00>// were all the points outside of plane p?
                        </font>		If(iInCount == 0)
                        <font color=#0000ff>return</font>(OUT);<br><br>		<font color=#007f00>// check if they were all on the right side of the plane
                        </font>		iTotalIn += iPtIn;
                        }<br><br>	<font color=#007f00>// so if iTotalIn is 6, then all are inside the view
                        </font>	<font color=#0000ff>if</font>(iTotalIn == 6)
                        <font color=#0000ff>return</font>(IN);<br><br>	<font color=#007f00>// we must be partly in then otherwise
                        </font>	<font color=#0000ff>return</font>(INTERSECT);
                        }
                        </font></pre>
                        </td>
                    </tr>
                </tbody>
            </table>
            </div>
            </center><br><br>We now have all the tools necessary to do frustum culling. So far so good right?</font></td>
        </tr>
    </tbody>
</table>
</center><br><br>
<center>
<table style="TABLE-LAYOUT: fixed" cellSpacing=0 cellPadding=0 width="80%" border=0>
    <tbody>
        <tr>
            <td width="100%"><font face="Verdana, Helvetica, Arial, Times New Roman" color=#ffffff size=3><strong>Optimizations 1 <font size=1><br><img src="http://www.flipcode.com/archives/line_grey.png"><br><br></font></strong></font></td>
        </tr>
    </tbody>
</table>
</center>
<center>
<table style="TABLE-LAYOUT: fixed" cellSpacing=0 cellPadding=0 width="80%" border=0>
    <tbody>
        <tr>
            <td width="100%"><font face="Verdana, Helvetica, Arial, Times New Roman" color=#ffe291 size=2>The first optimization for the above methods is very straightforward. If you study the two intersection methods you will most unquestionably notice that the sphere intersection method is significantly faster than the box intersection method. What this means is that we should perform sphere checks either instead of the box methods or at least before we check the box methods. In certain cases having a box as a bounding volume around our object can be much better since it will fit it more tightly. In these cases I would first check the bounding sphere of the object and then the bounding box. In fact, in my current engine each of my objects contains both a bounding sphere and a bounding box. The same goes for each node of my quad-tree. This way I can quickly reject objects/nodes using the sphere first and only if it passes that test do I check the box method. This is a very undemanding optimization and worth doing. All it requires is storing both a bounding sphere and box in each node/object that will be frustum culled. <br><br>The next optimization has to do with correctly traversing your hierarchal structure. In the case of the quad-tree the general method is to start at the top node and check if it抯 visible. If it is, then we then proceed to check each of the children of that node. If it is not visible, then we can stop processing. This is a recursive process for each node of the box. Once we reach the end of the tree and determine that this particular end node is visible, we send its contents to the video card for rendering. Now consider a non-terminating node (a node that contains children). If this node is completely visible, then what is the purpose of checking if the children are visible? It will only be wasted cpu cycles spent determining something you already know and that is that they are certainly visible! It is basically the opposite of not proceeding any further once you realize that the node isn抰 visible. In these cases, do not check further nodes but simply add them for rendering. The only case where you need to cull children nodes/objects is when the parent node is intersecting the frustum. This can significantly reduce your culling tests and eliminate trivial and redundant work. <br><br>The third optimization is to not check for an intersection if the camera is within the bound volume. In a case such as this we know that the bound volume intersects the frustum. We should check this node抯 children however since as I抳e said previously. For this check I merely check if the camera position is within the bounding box of this node, and if so treat it exactly as if the node is intersecting the viewing frustum. Using these optimizations, this is basically how your recursive processing method of your quad-tree should look:<br><br>
            <center>
            <div style="BORDER-RIGHT: #c0c0c0 1px solid; BORDER-TOP: #c0c0c0 1px solid; OVERFLOW: auto; BORDER-LEFT: #c0c0c0 1px solid; WIDTH: 100%; BORDER-BOTTOM: #c0c0c0 1px solid; BACKGROUND-COLOR: #ffffff">
            <table cellSpacing=0 cellPadding=12 width="100%" bgColor=#ffffff border=0>
                <tbody>
                    <tr>
                        <td width="100%" bgColor=#ffffff>
                        <pre><font face="Courier, Courier New" color=#000000>
                        <font color=#007f00>// recursively process the node for objects to farm out to it抯 managers
                        </font><font color=#0000ff>void</font> QuadTree::RecurseProcess(Camera* pPovCamera, QuadNode* pNode, <font color=#0000ff>bool</font> bTestChildren)
                        {
                        <font color=#007f00>// do we need to check for clipping?
                        </font>	If(bTestChildren) {<br><br>		<font color=#007f00>// check if we are inside this box first?</font>		<font color=#0000ff>if</font>(pNode-&gt;m_bbox.ContainsPoint(pPovCamera-&gt;Position()) == NOT_INSIDE) {<br><br>			<font color=#007f00>// test the sphere first
                        </font>			<font color=#0000ff>switch</font>(pPovCamera-&gt;Frustrum().ContainsSphere(pNode-&gt;m_sphere)) {
                        <font color=#0000ff>case</font> OUT:
                        <font color=#0000ff>return</font>;
                        <font color=#0000ff>case</font> IN:
                        bTestChildren = <font color=#0000ff>false</font>;
                        <font color=#0000ff>break</font>;
                        <font color=#0000ff>case</font> INTERSECT:
                        <font color=#007f00>// check if the box is in view
                        </font>				<font color=#0000ff>switch</font>(pPovCamera-&gt;Frustrum().ContainsAaBox(pNode-&gt;m_bbox)) {
                        <font color=#0000ff>case</font> IN:
                        bTestChildren = <font color=#0000ff>false</font>;
                        <font color=#0000ff>break</font>;
                        <font color=#0000ff>case</font> OUT:
                        <font color=#0000ff>return</font>;
                        }
                        <font color=#0000ff>break</font>;
                        }
                        }
                        }
                        <font color=#007f00>// we can now check the children or render this node? Etc
                        </font>}
                        </font></pre>
                        </td>
                    </tr>
                </tbody>
            </table>
            </div>
            </center></font></td>
        </tr>
    </tbody>
</table>
</center><br><br>
<center>
<table style="TABLE-LAYOUT: fixed" cellSpacing=0 cellPadding=0 width="80%" border=0>
    <tbody>
        <tr>
            <td width="100%"><font face="Verdana, Helvetica, Arial, Times New Roman" color=#ffffff size=3><strong>Fundamental Methods 2 <font size=1><br><img src="http://www.flipcode.com/archives/line_grey.png"><br><br></font></strong></font></td>
        </tr>
    </tbody>
</table>
</center>
<center>
<table style="TABLE-LAYOUT: fixed" cellSpacing=0 cellPadding=0 width="80%" border=0>
    <tbody>
        <tr>
            <td width="100%"><font face="Verdana, Helvetica, Arial, Times New Roman" color=#ffe291 size=2>If you抳e already implemented all the previously mentioned material and profiled you application you may have noticed that the intersection code is still occupying a fairly large percentage of your cpu. Of course this depends on your application as well as the number of objects, the depth of your quad-tree, and various other factors. Even without profiling you have probably noticed that the intersection methods are fairly intensive. Even the fastest method of testing the sphere against the frustum requires testing each and every sphere against the 6 planes of the frustum, assuming no quick-outs. There are some improvements to be made. Before I get into them lets check out the methods that we will need in order to perform them. <br><br>The first is a sphere-sphere intersection test. This is a very easy and fast method to implement. Basically the code computes the distance between the centers of the spheres and if this distance is less than the sum of the radii of the spheres, then we have an intersection. Otherwise we don抰. Here is the method that I use for determining if two spheres intersect. Notice how I use the squared radius values instead of computing the squared root for the length between the centers? This speeds up this method appreciably.<br><br>
            <center>
            <div style="BORDER-RIGHT: #c0c0c0 1px solid; BORDER-TOP: #c0c0c0 1px solid; OVERFLOW: auto; BORDER-LEFT: #c0c0c0 1px solid; WIDTH: 100%; BORDER-BOTTOM: #c0c0c0 1px solid; BACKGROUND-COLOR: #ffffff">
            <table cellSpacing=0 cellPadding=12 width="100%" bgColor=#ffffff border=0>
                <tbody>
                    <tr>
                        <td width="100%" bgColor=#ffffff>
                        <pre><font face="Courier, Courier New" color=#000000>
                        <font color=#007f00>// tests if 憈his?sphere intersects refSphere
                        </font><font color=#0000ff>bool</font> Sphere::Intersects(<font color=#0000ff>const</font> Sphere&amp; refSphere) <font color=#0000ff>const</font>
                        {
                        <font color=#007f00>// get the separating axis
                        </font>	Vector3f vSepAxis = <font color=#0000ff>this</font>-&gt;Center() - refSphere.Center();<br><br>	<font color=#007f00>// get the sum of the radii
                        </font>	<font color=#0000ff>float</font> fRadiiSum = <font color=#0000ff>this</font>-&gt;Radius() + refSphere.Radius();<br><br>	<font color=#007f00>// if the distance between the centers is less than the sum
                        </font>	<font color=#007f00>// of the radii, then we have an intersection
                        </font>	<font color=#007f00>// we calculate this using the squared lengths for speed
                        </font>	<font color=#0000ff>if</font>(vSepAxis.getSqLength() &lt; (fRadiiSum * fRadiiSum))
                        <font color=#0000ff>return</font>(<font color=#0000ff>true</font>);<br><br>	<font color=#007f00>// otherwise they are separated
                        </font>	<font color=#0000ff>return</font>(<font color=#0000ff>false</font>);
                        }
                        </font></pre>
                        </td>
                    </tr>
                </tbody>
            </table>
            </div>
            </center><br><br>The next method is to determine if a sphere and a cone intersect. This is a bit more involved method than the sphere-sphere check. Again, I could explain it but luckily for us both there is a great document by Dave Eberly explaining it on his site Magic-Software.com. The link to this article is:<br><br><a href="http://www.magic-software.com/Documentation/IntersectionSphereCone.pdf"><u><font color=#0000ff>http://www.magic-software.com/Documentation/IntersectionSphereCone.pdf</font></u></a><br><br>I find this site to be a great source of information, both on documents and on code. I highly recommend it to everyone. While the information in this article is more involved than what I presented so far, it surely isn抰 hard to understand and it抯 very easy and quick to implement. In fact, if you are math deprived then you can simply skip the end of the document and use the method provided as is. I don抰 recommend this but some people have a thing against math. So now we have these two dandy methods just sitting there... but what the heck are they used for you may ask? Well, that抯 the next section...</font></td>
        </tr>
    </tbody>
</table>
</center><br><br>
<center>
<table style="TABLE-LAYOUT: fixed" cellSpacing=0 cellPadding=0 width="80%" border=0>
    <tbody>
        <tr>
            <td width="100%"><font face="Verdana, Helvetica, Arial, Times New Roman" color=#ffffff size=3><strong>Optimizations 2 <font size=1><br><img src="http://www.flipcode.com/archives/line_grey.png"><br><br></font></strong></font></td>
        </tr>
    </tbody>
</table>
</center>
<center>
<table style="TABLE-LAYOUT: fixed" cellSpacing=0 cellPadding=0 width="80%" border=0>
    <tbody>
        <tr>
            <td width="100%"><font face="Verdana, Helvetica, Arial, Times New Roman" color=#ffe291 size=2>Yep, you guessed it. Those last two methods are used for a couple supplementary optimizations. As I mentioned at the beginning of the last section, doing checks against the frustum can be quite costly so it抯 best to avoid them if possible. You抣l notice that the sphere-sphere intersection test and the sphere-cone intersection tests are quite a bit zippier than the frustum intersection methods. So we can put those to use as a first level culling method to reduce the number of cull calls sent to the slower methods. What we need to do is to construct both a sphere and a cone around the current frustum. Then we can check our bounding spheres against this frustum sphere and then the frustum cone to grossly reject objects that are just plainly out of view. This is a very fast method (or methods) and depending on the layout of your world can result in some nice speed improvements. <br><br>
            <center><img height=524 src="http://www.flipcode.com/archives/article_frustumculling03.png" width=573></center><br><br>Constructing a sphere around the frustum isn抰 that hard but it is more work than constructing the cone oddly enough. What we want with the sphere is to have it situated in the middle of the frustum with a radius that extends from the center of the sphere to a far corner of the frustum. Why the far corner? Well, the frustum 憇preads out?as you get further away from the camera and occupies more world space. The greatest distance then from the center of the sphere is to this far corner (there are 4 of them). We need to use this greatest distance as the radius of the sphere in order to correctly bound the frustum (see Figure 3). Calculating the center of the sphere is a effortless procedure. It is simply half way between the near and far clipping planes along the view vector of the camera starting at the camera position. Calculating the radius of this sphere is a little more involved. Usually a frustum is specified with a field of view (FOV) in radians. Using a bit of trigonometry and given the fact that we should know the distance from the far clipping plane to the near clipping plane, we can calculate the spread of the view frustum at it抯 maximum distance from the camera. We do this for both the x and y extents and use the far clipping distance as our z coordinate, calling this point Q. We then calculate the length from this point Q to the point P which is defined as (0, 0, nearClip + ((farClip ?nearClip) / 2)). This forms the radius of the sphere. Here is the code that I use to calculate this:<br><br>
            <center>
            <div style="BORDER-RIGHT: #c0c0c0 1px solid; BORDER-TOP: #c0c0c0 1px solid; OVERFLOW: auto; BORDER-LEFT: #c0c0c0 1px solid; WIDTH: 100%; BORDER-BOTTOM: #c0c0c0 1px solid; BACKGROUND-COLOR: #ffffff">
            <table cellSpacing=0 cellPadding=12 width="100%" bgColor=#ffffff border=0>
                <tbody>
                    <tr>
                        <td width="100%" bgColor=#ffffff>
                        <pre><font face="Courier, Courier New" color=#000000>
                        <font color=#007f00>// calculate the radius of the frustum sphere
                        </font><font color=#0000ff>float</font> fViewLen = m_fFarPlane - m_fNearPlane;<br><br><font color=#007f00>// use some trig to find the height of the frustum at the far plane
                        </font><font color=#0000ff>float</font> fHeight = fViewLen * tan(m_fFovRadians * 0.5f);<br><br><font color=#007f00>// with an aspect ratio of 1, the width will be the same
                        </font><font color=#0000ff>float</font> fWidth = fHeight;<br><br><font color=#007f00>// halfway point between near/far planes starting at the origin and extending along the z axis
                        </font>Vector3f P(0.0f, 0.0f, m_fNearPlane + fViewLen * 0.5f);<br><br><font color=#007f00>// the calculate far corner of the frustum
                        </font>Vector3f Q(fWidth, fHeight, fViewLen);<br><br><font color=#007f00>// the vector between P and Q
                        </font>Vector3f vDiff(P - Q);<br><br><font color=#007f00>// the radius becomes the length of this vector
                        </font>m_frusSphere.Radius() = vDiff.getLength();<br><br><font color=#007f00>// get the look vector of the camera from the view matrix
                        </font>Vector3f vLookVector;
                        m_mxView.LookVector(&amp;vLookVector);<br><br><font color=#007f00>// calculate the center of the sphere
                        </font>m_frusSphere.Center() = m_vCameraPosition + (vLookVector * (fViewLen * 0.5f) + m_fNearPlane);
                        </font></pre>
                        </td>
                    </tr>
                </tbody>
            </table>
            </div>
            </center><br><br>Constructing the cone that surrounds the frustum is much simpler. If you read the previous paper then you understand that the cone is basically defined by a vertex (origin of the cone), an axis ray (facing direction of the cone) and an angle that defines the expansion of the cone. The vertex of the cone is the position of the camera. The axis ray of the cone is the facing direction of the camera. The only consideration with the cone is the calculation of the cone angle. If we choose the cone angle to be the same as the field of view of the frustum, then we create a cone that cuts off the corners of the frustum. Picture the biggest circle that fits inside a box. So we need to create the cone angle such that it encompasses the corners of the frustum instead of just the sides. Using a bit of trigonometry we can calculate this new FOV. Since we have the FOV for the frustum, we can use this (and the dimensions of the screen) to calculate the adjacent side of the triangle. Then we can calculate the distance from the center of the screen to a corner. Using these two sides of a right triangle, we can then calculate the new FOV. Simple! Constructing the cone then is purely a matter of copying these properties into the cone itself. Here is my code for doing just that:<br><br>
            <center>
            <div style="BORDER-RIGHT: #c0c0c0 1px solid; BORDER-TOP: #c0c0c0 1px solid; OVERFLOW: auto; BORDER-LEFT: #c0c0c0 1px solid; WIDTH: 100%; BORDER-BOTTOM: #c0c0c0 1px solid; BACKGROUND-COLOR: #ffffff">
            <table cellSpacing=0 cellPadding=12 width="100%" bgColor=#ffffff border=0>
                <tbody>
                    <tr>
                        <td width="100%" bgColor=#ffffff>
                        <pre><font face="Courier, Courier New" color=#000000>
                        <font color=#007f00>// set the properties of the frustum cone?vLookVector is the look vector from the view matrix in the
                        </font><font color=#007f00>// camera.  Position() returns the position of the camera.
                        </font><font color=#007f00>// fWidth is half the width of the screen (in pixels).
                        </font><font color=#007f00>// fHeight is half the height of the screen in pixels.
                        </font><font color=#007f00>// m_fFovRadians is the FOV of the frustum.
                        </font>
                        <font color=#007f00>// calculate the length of the fov triangle
                        </font><font color=#0000ff>float</font> fDepth  = fHeight / tan(m_fFovRadians * 0.5f);<br><br><font color=#007f00>// calculate the corner of the screen
                        </font><font color=#0000ff>float</font> fCorner = sqrt(fWidth * fWidth + fHeight * fHeight);<br><br><font color=#007f00>// now calculate the new fov
                        </font><font color=#0000ff>float</font> fFov = atan(fCorner / fDepth);<br><br><font color=#007f00>// apply to the cone
                        </font>m_frusCone.Axis() = vLookVector;
                        m_frusCone.Vertex() = Position();
                        m_frusCone.SetConeAngle(fFov);
                        </font></pre>
                        </td>
                    </tr>
                </tbody>
            </table>
            </div>
            </center><br><br>Having constructed this sphere and cone we can use these to quickly reject objects/nodes based on the intersection (or lack thereof) between their bounding spheres and this frustum sphere/cone. (You抣l notice that I don抰 bother to cull nodes that are beyond the end of the cone. The reason is that these nodes are culled by the sphere stage itself. The cone serves the purpose of better culling nodes that lie to the sides of the frustum.) These checks are so quick that I always perform them as the first step in my culling. Incorporating them into the quad-tree traversal method of Part 3 leads us to this new process:<br><br>
            <center>
            <div style="BORDER-RIGHT: #c0c0c0 1px solid; BORDER-TOP: #c0c0c0 1px solid; OVERFLOW: auto; BORDER-LEFT: #c0c0c0 1px solid; WIDTH: 100%; BORDER-BOTTOM: #c0c0c0 1px solid; BACKGROUND-COLOR: #ffffff">
            <table cellSpacing=0 cellPadding=12 width="100%" bgColor=#ffffff border=0>
                <tbody>
                    <tr>
                        <td width="100%" bgColor=#ffffff>
                        <pre><font face="Courier, Courier New" color=#000000>
                        <font color=#007f00>// recursively process the nodes of the quad tree
                        </font><font color=#0000ff>void</font> QuadTree::RecurseProcess(Camera* pPovCamera, QuadNode* pNode, <font color=#0000ff>bool</font> bTestChildren) {
                        <font color=#007f00>// do we need to check for clipping?
                        </font>	<font color=#0000ff>if</font>(bTestChildren) {
                        <font color=#007f00>// check if we are inside this box first...
                        </font>		<font color=#0000ff>if</font>(pNode-&gt;m_bbox.ContainsPoint(pPovCamera-&gt;Position()) == NOT_INSIDE) {<br><br>			<font color=#007f00>// check if we are in the sphere of the frustum
                        </font>			<font color=#0000ff>if</font>(!pPovCamera-&gt;FrustrumSphere().Intersects(pNode-&gt;m_sphere))
                        <font color=#0000ff>return</font>;
                        <font color=#007f00>// check if we are in the cone of the frustum
                        </font>			<font color=#0000ff>if</font>(!TestConeSphereIntersect(pPovCamera-&gt;FrustumCone(), pNode-&gt;m_sphere))
                        <font color=#0000ff>return</font>;<br><br>			<font color=#007f00>// test the bounding sphere first
                        </font>			<font color=#0000ff>switch</font>(pPovCamera-&gt;Frustrum().ContainsSphere(pNode-&gt;m_sphere)) {
                        <font color=#0000ff>case</font> OUT:
                        <font color=#0000ff>return</font>;
                        <font color=#0000ff>case</font> IN:
                        bTestChildren = <font color=#0000ff>false</font>;
                        <font color=#0000ff>break</font>;
                        <font color=#0000ff>case</font> INTERSECT:
                        <font color=#007f00>// check if the bound box is in view
                        </font>				<font color=#0000ff>switch</font>(pPovCamera-&gt;Frustrum().ContainsAaBox(pNode-&gt;m_bbox)) {
                        <font color=#0000ff>case</font> IN:
                        bTestChildren = <font color=#0000ff>false</font>;
                        <font color=#0000ff>break</font>;
                        <font color=#0000ff>case</font> OUT:
                        <font color=#0000ff>return</font>;
                        }
                        <font color=#0000ff>break</font>;<br><br>			}
                        }<br><br>	}<br><br><font color=#007f00>// we can now check the children or render this node? Etc
                        </font>}
                        </font></pre>
                        </td>
                    </tr>
                </tbody>
            </table>
            </div>
            </center></font></td>
        </tr>
    </tbody>
</table>
</center><br><br>
<center>
<table style="TABLE-LAYOUT: fixed" cellSpacing=0 cellPadding=0 width="80%" border=0>
    <tbody>
        <tr>
            <td width="100%"><font face="Verdana, Helvetica, Arial, Times New Roman" color=#ffffff size=3><strong>Conclusion <font size=1><br><img src="http://www.flipcode.com/archives/line_grey.png"><br><br></font></strong></font></td>
        </tr>
    </tbody>
</table>
</center>
<center>
<table style="TABLE-LAYOUT: fixed" cellSpacing=0 cellPadding=0 width="80%" border=0>
    <tbody>
        <tr>
            <td width="100%"><font face="Verdana, Helvetica, Arial, Times New Roman" color=#ffe291 size=2>This pretty much sums up a good introduction to frustum culling that I hope many newbie engine programmers will find practical and helpful. As you can see by reading this, none of the concepts are hard and the math is very simple. There are of course many more optimizations that can be done to further enhance the culling procedure but this should serve as a good starting point. If anything sounds vague in the document, please let me know and I抣l try to write an update to clarify. <br><br>I would like to thank Gil Gribb and Klaus Hartmann for their excellent article on plane extraction. I would also like to thank Dave Eberly for his excellent resources for this cone/sphere intersection description as well as the full site he maintains. It has been an invaluable resource to me as I抦 sure it has been for many other programmers as well. I would also like to thank Charles Bloom for his web page that first inspired me to try enclosing the frustum in both a sphere and a cone. I feel really dumb for not thinking of it myself but that抯 the beauty of sharing information. <br><br>And remember... Go vegan and exercise like hell - it WILL make you smarter or your money back. I guarantee it (not a guarantee).</font></td>
        </tr>
    </tbody>
</table>
</center>
<img src ="http://www.cppblog.com/liangairan/aggbug/77917.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2009-03-26 10:57 <a href="http://www.cppblog.com/liangairan/articles/77917.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>纹理（讲得比较详细的文章）</title><link>http://www.cppblog.com/liangairan/articles/77682.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Tue, 24 Mar 2009 00:23:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/77682.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/77682.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/77682.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/77682.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/77682.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: 纹理是增强计算机生成的三维图像的真实感的有力工具。Microsoft&#174; Direct3D&#174;支持广泛的纹理特性，并使开发人员可以很方便地使用高级纹理技术。本节讲述如何使用纹理。    纹理的基本概念    纹理坐标    纹理过滤    纹理资源    纹理环绕    纹理混合    表面 以下主题将更详细地介绍另外的纹理功能。...&nbsp;&nbsp;<a href='http://www.cppblog.com/liangairan/articles/77682.html'>阅读全文</a><img src ="http://www.cppblog.com/liangairan/aggbug/77682.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2009-03-24 08:23 <a href="http://www.cppblog.com/liangairan/articles/77682.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>[转]3ds max sdk导出插件编写的心得</title><link>http://www.cppblog.com/liangairan/articles/73376.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Tue, 10 Feb 2009 05:41:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/73376.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/73376.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/73376.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/73376.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/73376.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: &nbsp;3ds max sdk导出插件编写的心得 作者：yhchinabest来自：CG先生-3D图形插件开发网http://www.cgsir.com/目录写在前面环境配置第一个导出程序Mesh,Material,Light,Camera,让我们找到它们Mesh,Material,Light,Camera,让我们解析他们&nbsp;&nbsp;...&nbsp;&nbsp;<a href='http://www.cppblog.com/liangairan/articles/73376.html'>阅读全文</a><img src ="http://www.cppblog.com/liangairan/aggbug/73376.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2009-02-10 13:41 <a href="http://www.cppblog.com/liangairan/articles/73376.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Effect Framework（转）</title><link>http://www.cppblog.com/liangairan/articles/71357.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Tue, 06 Jan 2009 09:06:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/71357.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/71357.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/71357.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/71357.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/71357.html</trackback:ping><description><![CDATA[<table height="100%" cellSpacing=0 cellPadding=0 width="100%" border=0>
    <tbody>
        <tr>
            <td vAlign=top width="100%">
            <table height="100%" cellSpacing=0 cellPadding=0 width="100%" border=0>
                <tbody>
                    <tr class=text vAlign=top>
                        <td align=left>原创：刘宏春 </td>
                        <td class=text vAlign=top align=right>2005年5月16日 </td>
                    </tr>
                </tbody>
            </table>
            </td>
        </tr>
        <tr>
            <td class=arcontent colSpan=3><br></title>
            <!--begin Contents.Don't remove this!-->
            <style type=text/css>
            <!--
            .unnamed1 {
            color: green;
            }
            .unnamed2 {
            color: blue;
            }
            -->
            </style>
            <p><font face=宋体><strong>Effect Framework</strong> </font></p>
            <p><strong><font face=宋体>摘要</font></strong></p>
            <p><font face=宋体>本文简要介绍了在DirectX 9 SDK中提供的Effect Framework支持，以及DirectX FX文件结构和Microsoft Hight Level Shading Language的基本知识。本文假定读者对DirectX Graphics有一定了解，并正在学习DirectX Effect Framework。希望能够与各位读者共同探讨、切磋。 </font></p>
            <p><font face=宋体><strong>简介 </strong></font></p>
            <p><font face=宋体>Effect的起源 </font></p>
            <p><font face=宋体>在计算机3维场景中，物体表面的材质代表了其光学特性。最简单的材质可以表现为Diffuse颜色，Specular颜色，Emissive颜色等信息的集合；而为了表现物体表面的细节，可以 在材质中加入一张纹理——这些就构成了最基本的材质信息。在以前的Direct3D程序中，这些信息可以直接传送给设备，由设备自动根据它们来计算物体表面的光学效果。但是， 仅仅有这些基本的材质信息，已经不足以满足游戏制作者的要求和游戏玩家的要求了——他们希望场景中的材质更加复杂，具有更多的细节，更加逼真。 </font></p>
            <p><font face=宋体>在Direct3D中，除了材质的概念，还存在一个渲染状态（Render State）的概念。在Direct3D Device中存在很多的渲染状态，它们可以在Direct3D进行渲染时控制渲染的流程和效 果，从而实现某些带有特效的材质。程序员可以通过IDirect3DDevice*::SetRenderState()方法来设置这些状态。所有的渲染状态都是一些特定的数值。对状态的设置可以通过硬 编码完成，即在程序中调用SetRenderState()方法，将设置什么样的状态&#8220;写死&#8221;在程序里，但是这样做的缺点就是太不灵活了——如果想要实现一种新的渲染状态，就需要修改 程序代码。所以更好的一种方法是将为了实现某一种特效材质的一些渲染状态值记录到一个&#8220;效果文件&#8221;中，通过在程序运行时读取该文件，从中分析出这些值，并将它们作为参 数调用SetRenderState()。这样，要想实现一种新的特效，只需修改&#8220;效果文件&#8221;而不用更改代码。 </font></p>
            <p><font face=宋体>Direct3D SDK是通过Effect Framework来支持这种机制的。而前面所述的&#8220;效果文件&#8221;在Direct3D中是以*.fx文件存在的。在fx文件中保存了为实现某一特效的渲染状态，包括状态名 称和它们的对应值。所以在9.0以前版本的DirectX中就已经有Effect Framework和FX文件了，早期的Effect Framework仅仅是为了实现对渲染状态进行控制。 但是随着计算机显示硬件技术的发展，图形处理单元（GPU）正在重复CPU所走过的路——新一代的GPU已经具有了可编程特性，程序员可以通过对GPU编写一段程序来控制其渲染的 输出效果。这种程序一般称为Shader程序。目前的Shader程序分成两种，Vertex Shader和Pixel Shader。Vertex Shader主要用于对3维网格模型的每一个顶点进行处理，而Pixel Shader主要用于对要绘制到屏幕上的每一个象素进行处理。通过Shader，程序员可以制作出相当丰富的实时渲染效果。</font></p>
            <p><font face=宋体>同CPU一样，对GPU编程也是借助一定的编程语言来进行的。 </font><font face=宋体>在DirectX 8中，对GPU的编程是通过一种类似于汇编的语言进行的。而在DirectX 9中，使用了一种类似于C语言的高级语言——Microsoft High Level Shading Language (HLSL) 。</font></p>
            <p><font face=宋体>无论使用什么语言对GPU编程，都需要把编译好的Shader程序输送到显卡去。Direct3D Device可以通过SetVertexShader()和SetPixelShader()来向显卡输送Shader程序。然而，由 于Shader和材质具有十分紧密的关系——一般来说，一个Shader就是为了实现一种特殊的材质——最好是能够将Shader程序与材质进行整合。 所以，在DirectX 8和DirectX 9中，原来的Effect Framework发生了扩充——在原来的基础上加入了对Shader程序的支持。程序员可以把Vertex Shader和Pixel Shader程序以函数 的方式直接书写在FX文件中。</font></p>
            <p><font face=宋体>&nbsp;.FX文件</font></p>
            <p><font face=宋体>FX文件中的内容大致可以分成几部分： </font></p>
            <ul>
                <li><font face=宋体>预编译标志 </font>
                <li><font face=宋体>变量表 </font>
                <li><font face=宋体>结构定义 </font>
                <li><font face=宋体>函数 </font>
                <li><font face=宋体>Technique </font></li>
            </ul>
            <p><font face=宋体>预编译标志：预编译标志包括 </font></p>
            <ul>
                <li><font face=宋体>#define </font>
                <li><font face=宋体>#elif </font>
                <li><font face=宋体>#else </font>
                <li><font face=宋体>#endif </font>
                <li><font face=宋体>#error </font>
                <li><font face=宋体>#if </font>
                <li><font face=宋体>#ifdef </font>
                <li><font face=宋体>#ifndef </font>
                <li><font face=宋体>#include </font>
                <li><font face=宋体>#line </font>
                <li><font face=宋体>#pragma </font>
                <li><font face=宋体>#undef </font></li>
            </ul>
            <p><font face=宋体>其中最常用到的是#include和#define，同C语言中的意义很相似：用#include可以在一个FX文件中引入另外一个或多个文件。#define可以定义FX文件中的宏替换。例如： </font></p>
            <table cellSpacing=5 cellPadding=0 width=500 bgColor=#e0e0e0 border=0>
                <tbody>
                    <tr>
                        <td>
                        <p><span class=unnamed2><font face=宋体>#include "helper_Funcs.fx"</font></span><font face=宋体>&nbsp;&nbsp;&nbsp;&nbsp; <span class=unnamed1>//引入一个名为helper_funcs.fx的fx文件</span> <br><span class=unnamed2>#include "public_variables.fh</span>" <span class=unnamed1>//引入一个名为public_variables.fh的文件</span> <br><span class=unnamed2>#define MATRICES_COUNT 25</span>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class=unnamed1>//定义宏MATRICES_COUNT为25</span> <br><span class=unnamed2>#define VSHADER VShader_2_0</span>&nbsp;&nbsp;&nbsp; <span class=unnamed1>//定义宏VSHADER为VShader_2_0</span> </font></p>
                        </td>
                    </tr>
                </tbody>
            </table>
            <p><font face=宋体>#include带来的好处和C中也是一样的－您可以在一个头文件中定义一些公有变量、函数等，在其他文件中引用它们－就不用写很多遍了。例如： </font></p>
            <p><font face=宋体>文件public.fh: </font></p>
            <table cellSpacing=5 cellPadding=0 width=500 bgColor=#e0e0e0 border=0>
                <tbody>
                    <tr>
                        <td>
                        <p><span class=unnamed2><font face=宋体>mat4x4</font></span><font face=宋体> matWorldViewProj; <span class=unnamed1>// 4x4世界－视－投影变换矩阵</span> <br></font><span class=unnamed2><font face=宋体>float3</font></span><font face=宋体> lightPosisiton;&nbsp;&nbsp; <span class=unnamed1>// 三维光源位置向量</span> <br><span class=unnamed2>float4</span> lightColor;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class=unnamed1>&nbsp; // 光源的颜色</span> <br><span class=unnamed2>float</span> time;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class=unnamed1>// 当前时间</span> </font></p>
                        </td>
                    </tr>
                </tbody>
            </table>
            <p><font face=宋体>//定义一个名为VS_OUTPUT的结构。关于结构体定义，下文中会有介绍 <br></font></p>
            <table width=500 bgColor=#e0e0e0 border=0>
                <tbody>
                    <tr>
                        <td><span class=unnamed2><font face=宋体>struct</font></span><font face=宋体> VS_INPUT <br>{<br>&nbsp;&nbsp;&nbsp; <span class=unnamed2>float4</span> LocalPos : POSITION; <br>&nbsp;&nbsp;&nbsp; <span class=unnamed2>float3</span> Normal : NORMAL; <br>&nbsp;&nbsp;&nbsp; <span class=unnamed2>float4</span> Color : COLOR;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;<span class=unnamed2>float2</span> Texcoord : TEXCOORD0; <br>};</font></td>
                    </tr>
                </tbody>
            </table>
            <p>&nbsp; </p>
            <table cellSpacing=0 cellPadding=0 width=500 bgColor=#e0e0e0 border=0>
                <tbody>
                    <tr>
                        <td><span class=unnamed2><font face=宋体>struct VS_OUTPUT</font></span><font face=宋体><br>{<br>&nbsp;&nbsp;&nbsp;<span class=unnamed2> float4</span> WorldPos : POSITION;<br>&nbsp;&nbsp;&nbsp; <span class=unnamed2>float4</span> Color : COLOR; <br>&nbsp;&nbsp;&nbsp; <span class=unnamed2>float2</span> Texcoord : TEXCOORD0; <br>}; </font></td>
                    </tr>
                </tbody>
            </table>
            <p>&nbsp; </p>
            <p><font face=宋体>//定义一个名为CaculateWorldPosition的函数。关于函数定义，下文中会有介绍 <br></font></p>
            <table cellSpacing=0 cellPadding=0 width=500 bgColor=#e0e0e0 border=0>
                <tbody>
                    <tr>
                        <td><span class=unnamed2><font face=宋体>float3</font></span><font face=宋体> CaculateWorldPosition( <span class=unnamed2>float4</span> LocalPos ) <br>{<br>&nbsp;&nbsp;&nbsp; <span class=unnamed2>return</span> mul( LocalPos, matWorldViewProj); <br>}</font></td>
                    </tr>
                </tbody>
            </table>
            <p>&nbsp; </p>
            <p><font face=宋体>这样，当我们在另外一个文件中include这个头文件时，上面所有的定义都可以直接使用了。 </font></p>
            <p><font face=宋体>文件client.fx: </font></p>
            <table cellSpacing=0 cellPadding=0 width=500 bgColor=#e0e0e0 border=0>
                <tbody>
                    <tr>
                        <td>
                        <p class=unnamed2><font face=宋体>#include "public.fh" </font></p>
                        <p><font face=宋体><br><span class=unnamed2>VS_OUTPUT</span> VS_main( VS_INPUT In ) <span class=unnamed1>// 可以直接使用结构体定义VS_OUTPUT和VS_INPUT </span><br>{ <br>&nbsp;&nbsp;&nbsp; VS_OUTPUT Out = 0; <br>&nbsp;&nbsp;&nbsp; Out.WorldPos = CaculateWorldPosition( In.LocalPos ); <span class=unnamed1>// 可以直接使用函数</span><br>&nbsp;&nbsp;&nbsp; Out.Color = In.Color; <br>&nbsp;&nbsp;&nbsp; Out.Texcoord = In.Texcoord; <br>} </font></p>
                        </td>
                    </tr>
                </tbody>
            </table>
            <p>&nbsp;</p>
            <font face=宋体>
            <p>这样不仅可以复用代码，还可以使变量名、结构体、函数名的定义统一。 </p>
            <p>而#define不仅仅可以使某些常量具有比较有意义的名称，通过与#ifdef，#ifndef, #else, #endif等结合使用，还可以用来根据一些配置控制编译过程。 </p>
            <p>变量表</p>
            <p>每个FX文件都可以有若干参数变量，通过Effect Framework可以在程序中识别出这些参数的类型、名称和用途，这样就可以将程序中的一些参数输送到Effect中去，从而更加灵活 的控制效果。参数的类型很多，可以是int, float, matrix, texture等等。例如： </p>
            </font>
            <table cellSpacing=0 cellPadding=0 width=500 bgColor=#e0e0e0 border=0>
                <tbody>
                    <tr>
                        <td><span class=unnamed2><font face=宋体>matrix</font></span><font face=宋体> matWorld;&nbsp;&nbsp;&nbsp;&nbsp;<span class=unnamed1>//定义一个名为matWorld的矩阵类型参数变量</span> <br><span class=unnamed2>float</span> time;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class=unnamed1>//定义一个名为time的浮点类型参数变量 </span><br><span class=unnamed2>texture</span> texDiffuse; <span class=unnamed1>//定义一个名为texDiffuse的纹理类型参数变量</span></font></td>
                    </tr>
                </tbody>
            </table>
            <font face=宋体>
            <p>&nbsp;</p>
            <p>另外，每个变量还有前缀修饰符、Semantic、Annotation等，限于篇幅，在这里不再赘述，具体的介绍请参考DirectX C++帮助文档的DirectX Graphics &gt; Reference &gt; HLSL Shader Reference &gt; Variable Declaration Syntax条目。 </p>
            <p>结构定义</p>
            <p>在FX文件中可以定义结构体，这些结构体一般用于Shader函数的参数和返回值。结构体的定义与C语言方式及其类似，例如： </p>
            </font>
            <table cellSpacing=0 cellPadding=0 width=500 bgColor=#e0e0e0 border=0>
                <tbody>
                    <tr>
                        <td><span class=unnamed2><font face=宋体>struct</font></span><font face=宋体> VS_OUTPUT <span class=unnamed1>//结构名称 </span><br>{ <br>&nbsp;&nbsp;&nbsp; <span class=unnamed2>float4</span> Pos : POSITION; <span class=unnamed1>//成员变量。</span> <br>&nbsp;&nbsp;&nbsp; <span class=unnamed2>float4</span> Color : COLOR; <br>&nbsp;&nbsp;&nbsp;<span class=unnamed2> float2</span> Texcoord : TEXCOORD0; <br>}; </font></td>
                    </tr>
                </tbody>
            </table>
            <font face=宋体>
            <p>&nbsp;</p>
            <p>上面每个成员变量后面的标识符是该变量的semantic，HLSL编译器根据这个标识符来确定该变量的用处。不一定非得是结构体成员变量才有semantic，一般来说Shader的输入输出参数变量都可以有semantic。</p>
            <p>函数</p>
            <p>函数部分是在Effect Framework加入了对Vertex Shader和Pixel Shader的扩充后才加入到FX文件中的。FX文件中的函数的内容可以用汇编形式书写，也可以用HLSL编写。目前一般 都是使用HLSL。用这种语言书写的函数与C语言函数十分类似，可以说，只要学过C语言，书写Shader函数就绰绰有余了。在同一个fx文件中可以定义很多函数，在函数中也可以互 相调用，但是最终Shader程序的入口将在fx文件的Technique部分中指定。对于哪个函数是Vertex shader函数，哪个是Pixel shader函数，也是在Technique中指定的。关于Technique，将在下文中介绍。 例子： </p>
            </font>
            <table cellSpacing=0 cellPadding=0 width=500 bgColor=#e0e0e0 border=0>
                <tbody>
                    <tr>
                        <td>
                        <p><span class=unnamed2><font face=宋体>float4</font></span><font face=宋体> CalcDiffuseColor( float3 Normal ) <br>{ <br>&nbsp;&nbsp;&nbsp; <span class=unnamed2>float4</span> Color; ...<span class=unnamed1>//用于实现该函数功能的多条语句 </span><br>&nbsp;&nbsp;&nbsp; <span class=unnamed2>return</span> Color; <br>} </font></p>
                        <p><span class=unnamed2><font face=宋体>VS_OUTPUT</font></span><font face=宋体> Vertex_Shader( <span class=unnamed2>float4</span> InPos : POSITION, <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class=unnamed2>float3</span> InNor : NORMAL,&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class=unnamed2>float3</span> InTexcoord : TEXCOORD ) <br>{ <br>&nbsp;&nbsp;&nbsp; <span class=unnamed2>VS_OUTPUT</span> Out; ...<span class=unnamed1>//用于实现该函数功能的多条语句 </span><br>&nbsp;&nbsp;&nbsp; Out.Color = CalcDiffuseColor(InNor);<span class=unnamed1> //函数调用 </span><br>&nbsp;&nbsp;&nbsp; <span class=unnamed2>return</span> Out; <br>}</font></p>
                        </td>
                    </tr>
                </tbody>
            </table>
            <font face=宋体>
            <p>&nbsp;</p>
            <p>正如上文中所说，Shader程序分为两种：Vertex shader和Pixel shader。在fx函数中就有两种相应的函数：Vertex shader函数和Pixel Shader函数。Vertex shader函数的输入参数是网格模型中的每一个顶点数据，其输出是经过该函数特殊处理的顶点数据；而Pixel Shader函数的输入，则是经过硬件光栅化过程后经过插值的Vertex shader输出结果。至于Pixel shader的输出，一般就是经过该函数计算得到的一个颜色值，即要画到后备缓冲中一个象素上的颜色值。但是这个&#8220;颜色值&#8221;有时并不一定代表颜色。而且对于支持多RenderTarget的硬件，Pixel shader还可以有多个输出，分别对应不同的RenderTarget。</p>
            <p>technique </p>
            <p>technique是FX文件的主体，是真正设置各种渲染状态的地方，也是指定所使用的Shader程序入口的地方。在理解Technique前首先要理解Pass的概念。Pass是Technique的组成部分 ，一个Pass就代表了绘制时的一遍。通常为了达到一种效果，仅仅绘制一遍网格模型是不够的，需要向framebuffer中多次绘制，并利用设置渲染状态中的BlendState进行Alpha混合。这就是经常提到的&#8220;Muli-pass Rendering&#8221;。但是随着硬件越来越强大，Shader程序的功能越来越强，目前的趋势是所有的特效材质都将可以用越来越少个Pass来完成。 一个technique中，可以存在一个或多个Pass，但是至少要有一个Pass时该technique才会起实际作用。当存在多个pass时，默认情况下在渲染时将会按照pass在文件中的前后顺序 作为渲染时的前后顺序。technique和Pass中的内容都是以大括号括起来。 </p>
            <p>technique的例子： </p>
            </font>
            <table cellSpacing=0 cellPadding=0 width=500 bgColor=#e0e0e0 border=0>
                <tbody>
                    <tr>
                        <td><span class=unnamed2><font face=宋体>technique</font></span><font face=宋体> Tec_Shader_1_X <span class=unnamed1>//定义一个名为Tec_Shader_1_X的technique </span><br>{ <br>&nbsp;&nbsp;&nbsp; pass P0 <span class=unnamed1>//一个名为P0的pass</span> <br>&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; VertexShader = compile vs_1_1 Vertex_Shader();<span class=unnamed1> //设置Vertex Shader程序入口函数 </span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PixelShader = compile ps_1_1 Pixel_Shader();&nbsp;&nbsp; <span class=unnamed1>//设置Pixel Shader程序入口函数 </span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; AlphaBlendEnable= true;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class=unnamed1> //设置渲染状态&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; </span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; SrcBlend = SrcAlpha;&nbsp;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DestBlend = InvSrcAlpha;&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ... <span class=unnamed1>//其他设置</span> <br>&nbsp;&nbsp;&nbsp; } <br>&nbsp;&nbsp;&nbsp; pass P1<span class=unnamed1> //一个名为P1的pass</span> <br>&nbsp;&nbsp;&nbsp; { <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ... <br>&nbsp;&nbsp;&nbsp; } <br>} </font></td>
                    </tr>
                </tbody>
            </table>
            <font face=宋体>
            <p>&nbsp;</p>
            <p><strong>ID3DXEffect接口</strong> </p>
            <p>上面介绍了很多fx文件相关内容，但是在程序中如何读取和分析这些fx文件呢？在程序中对于读取fx文件，控制渲染状态、设置Shader程序等工作都是通过D3DX库中的ID3DXEffect接口来实现的。ID3DXEffect接口提供了大量的方法，基本上分为几个方面： </p>
            <ul>
                <li>获得Effect参数变量信息
                <li>设置Effect参数变量
                <li>获得technique信息
                <li>设置当前使用的technique
                <li>开始和结束使用当前的technique
                <li>执行一个pass（渲染绘制遍） </li>
            </ul>
            <p>ID3DXEffect接口的创建： 通过D3DX库中的D3DXCreateEffectFromFile()函数，可以根据一个指定的文件的内容来创建一个ID3DXEffect接口。在该函数执行成功后，所创建的接口中就包含了文件里所对应的 所有内容，包括参数变量表、Shader程序、technique和pass等。 </p>
            <p>ID3DXEffect接口的使用： 通过该接口的方法可以获得FX文件中的所有信息，并设置参数变量和当前的technique。所有的参数变量、technique、pass、shader等等都有自己的名称，根据这些名称，通过调用ID3DXEffect::GetParameterByName()、ID3DXEffect::GetTechniqueByName()和ID3DXEffect::GetPassByName()等方法就可以获得这些对象的句柄，从而在调用 ID3DXEFFECT::Set***()进行设置时使用句柄而不是字符串进行索引来提高效率。</p>
            <p>通过ID3DXEffect::GetParameterDesc()、ID3DXEffect::GetTechniqueDesc()和GetPassDecs等方 法可以获得关于指定对象的所有细节描述信息。 D3DX库中定义了一个D3DXPARAMETER_DESC结构来专门表示参数的类型。通过ID3DXEffect::GetParameterDesc()方法，可以为一个参数获得这样一个结构的数据。 </p>
            </font>
            <table cellSpacing=0 cellPadding=0 width=500 bgColor=#e0e0e0 border=0>
                <tbody>
                    <tr>
                        <td><span class=unnamed2><font face=宋体>typedef struct</font></span><font face=宋体> _D3DXPARAMETER_DESC <br>{ <br>&nbsp;&nbsp;&nbsp; LPCSTR Name;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //参数变量名 <br>&nbsp;&nbsp;&nbsp; LPCSTR Semantic;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //参数变量的Semantic <br>&nbsp;&nbsp;&nbsp; D3DXPARAMETER_CLASS Class; //参数变量的类别，可以是标量、矢量、矩阵、对象和结构 <br>&nbsp;&nbsp;&nbsp; D3DXPARAMETER_TYPE Type;&nbsp;&nbsp; //参数变量的类型 <br>&nbsp;&nbsp;&nbsp; UINT Rows;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//数组型参数的行数 <br>&nbsp;&nbsp;&nbsp; UINT Columns;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //数组型参数的列数 <br>&nbsp;&nbsp;&nbsp; UINT Elements;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //数组中的元素个数 <br>&nbsp;&nbsp;&nbsp; UINT Annotations;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //参数变量的Annotation个数 <br>&nbsp;&nbsp;&nbsp; UINT StructMembers;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //结构型参数变量成员的个数 <br>&nbsp;&nbsp;&nbsp; DWORD Flags;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //参数属性 <br>&nbsp;&nbsp;&nbsp; UINT Bytes;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //参数大小，以字节记 <br>} D3DXPARAMETER_DESC; </font></td>
                    </tr>
                </tbody>
            </table>
            <font face=宋体>
            <p>&nbsp;</p>
            <p>其中的参数类型可以有下列几种： </p>
            </font>
            <table cellSpacing=0 cellPadding=0 width=500 bgColor=#e0e0e0 border=0>
                <tbody>
                    <tr>
                        <td><span class=unnamed2><font face=宋体>typedef</font></span><font face=宋体> enum _D3DXPARAMETER_TYPE <br>{ <br>&nbsp;&nbsp;&nbsp; D3DXPT_VOID, //Void型指针 <br>&nbsp;&nbsp;&nbsp; D3DXPT_BOOL, //Bool型 <br>&nbsp;&nbsp;&nbsp; D3DXPT_INT, //整型 <br>&nbsp;&nbsp;&nbsp; D3DXPT_FLOAT, //浮点型 <br>&nbsp;&nbsp;&nbsp; D3DXPT_STRING, //字符串 <br>&nbsp;&nbsp;&nbsp; D3DXPT_TEXTURE, //纹理 <br>&nbsp;&nbsp;&nbsp; D3DXPT_TEXTURE1D, //一维纹理 <br>&nbsp;&nbsp;&nbsp; D3DXPT_TEXTURE2D, //二维纹理 <br>&nbsp;&nbsp;&nbsp; D3DXPT_TEXTURE3D, //三维纹理 <br>&nbsp;&nbsp;&nbsp; D3DXPT_TEXTURECUBE, //立方体环境纹理 <br>&nbsp;&nbsp;&nbsp; D3DXPT_SAMPLER, //纹理取样器 <br>&nbsp;&nbsp;&nbsp; D3DXPT_SAMPLER1D, //一维纹理取样器 <br>&nbsp;&nbsp;&nbsp; D3DXPT_SAMPLER2D, //二维纹理取样器 <br>&nbsp;&nbsp;&nbsp; D3DXPT_SAMPLER3D, //三维纹理取样器 <br>&nbsp;&nbsp;&nbsp; D3DXPT_SAMPLERCUBE, //立方体环境纹理取样器 <br>&nbsp;&nbsp;&nbsp; D3DXPT_PIXELSHADER, //Pixel Shader程序 <br>&nbsp;&nbsp;&nbsp; D3DXPT_VERTEXSHADER, //Vertex Shader程序 <br>&nbsp;&nbsp;&nbsp; D3DXPT_PIXELFRAGMENT, //Pixel Shader片断 <br>&nbsp;&nbsp;&nbsp; D3DXPT_VERTEXFRAGMENT, //Vertex Shader片断 <br>&nbsp;&nbsp;&nbsp; D3DXPT_FORCE_DWORD = 0x7fffffff <br>} D3DXPARAMETER_TYPE;</font></td>
                    </tr>
                </tbody>
            </table>
            <font face=宋体>
            <p>&nbsp;</p>
            <p>而真正要让Effect起作用，需要在绘制网格模型前后调用ID3DXEffect::BeginPass()和EndPass方法。在调用这两个函数之前和之后，还需调用ID3DXEffect::Begin()和 ID3DXEffect::End()方法来界定此次Effect设置的起止。大致的形式如下： </p>
            </font>
            <table cellSpacing=0 cellPadding=0 width=500 bgColor=#e0e0e0 border=0>
                <tbody>
                    <tr>
                        <td>
                        <p><font face=宋体>LPD3DXEffect pd3dEffect；<span class=unnamed1> //ID3DXEffect接口指针 </span><br>... <span class=unnamed1>//初始化该指针 </span><br>... <span class=unnamed1>//程序中的其他部分</span><br>... </font></p>
                        <p><font face=宋体>UINT numPasses;<span class=unnamed1> //用于接受当前所使用的technique中的pass个数 </span><br>pd3dEffect-&gt;Begin( &amp; numPasses, 0 ) <span class=unnamed1>//开始使用当前的technique </span><br><span class=unnamed2>for</span>( UINT iPass = 0; iPass &lt; numPasses; iPass ++ ) <br>{ <br>&nbsp;&nbsp;&nbsp; <span class=unnamed1>//遍历所有的pass </span><br>&nbsp;&nbsp;&nbsp; pd3dEffect-&gt;BeginPass( iPass ); <span class=unnamed1>//调用BeginPass </span><br>&nbsp;&nbsp;&nbsp; DrawMesh();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class=unnamed1>//然后进行模型的绘制。 </span><br>&nbsp;&nbsp;&nbsp; pd3dEffect-&gt;EndPass();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class=unnamed1> //调用EndPass </span><br>} <br>pd3dEffect-&gt;End();&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class=unnamed1> //不要忘记结束当前technique </span></font></p>
                        </td>
                    </tr>
                </tbody>
            </table>
            <font face=宋体>
            <p>&nbsp;</p>
            <p><strong>3DS MAX对DirectX 9 Shader Material的支持；Effect数据获取和导出</strong></p>
            <p>在FX文件中的参数，大部分都是用来调整FX文件所指定的Effect的一些细节，例如，一种带有凹凸纹理的效果，可能在FX中就有一个参数控制着凹凸不平的程度。而这些参数是需要由美工来调整的。另外，美工也需要有一个途径能够将FX文件定义的Effect赋予到一个模型或它的一部分上去。同时美工也应该能够实时的预览该Effect在模型上的实际效果。 在以前，实现这些要求只能通过自制效果预览器、模型编辑器或者为DCC软件编写插件来完成。这对于小的工作组和工期较短的项目来说是非常困难的。幸运的是，目前的DCC软件 已经开始为游戏制作提供丰富的支持功能。通过DCC软件自身就可以预览到实时Effect的效果，并调整其参数。在《龙的传说》这个项目中，我们使用3DS MAX 6.0来作为模型建立 工具和Shader预览工具。 </p>
            <p>在3DS MAX 6.0中，新加入了一种材质类型——DirectX 9 Shader材质。这种材质是基于FX文件的。一个FX文件就可以代表一种材质。它可以同MAX中的其他材质一样赋予到模型上 。在MAX的Viewport中可以实时地观察到FX文件中设计的效果。通过为FX中的参数设置特定的Semantic，可以将这些参数与MAX中的场景信息联系起来，如摄像机位置、世界变换矩 阵等。对于控制Effect效果的一些本地参数，可以通过为它们添加特定的Annotation，使MAX能够直接识别这些参数并在用户界面中显示它们的名称和调节控件。对应不同类型的参 数，MAX可以为它们生成不同的调节控件。这样，这种材质就和MAX的其他材质一样，可以更改纹理等等的参数了。 对于美工来说，通过这种用户界面就可以调整该FX材质的参数达到最好的效果。</p>
            <p>但是美工所调整的结果必须要能够保存下来才有意义。这个工作就得由程序员来完成了。要保存3DS MAX中编辑的所有内容，需要为3DS MAX编写文件导出插件，将其内部数据保存在特定格式的文件中。在《龙的传说》这个项目中，我们使用Microsoft DirectX .X 文件。如果从头开始写导出插件，工作量是相当大的；幸运的是，在Microsoft DirectX SDK Extra中提供了一个能够导出模型和3DS MAX标准材质到X文件的插件源代码。通过修改该源代码，可以使它能够导出DirectX 9 Shader材质。在3DS MAX 6 SDK中新增加了一个IDxMaterial接口，通过查询一个IMaterial接口是否为IDxMaterial接口，就可以确定该材质是否为DirectX 9 Shader材质。通过IDxMaterial接口可以获得该材质对应的FX文件的文件名，以及其参数信息。这样就可以将它们导出了。 </p>
            <p><strong>.X文件中对Effect的支持；EffectInstance和EffectDefault</strong></p>
            <p>Microsoft DirectX .X文件的格式是基于模板的、可扩展的文件格式。通过为其制定新的模板，就可以在其中加入新的内容。一般的.X文件中的内容有三维场景的物体层级关系、网格模型几何数据、材质信息、动画信息等。在DirectX的众多X文件模板中有一个模板是专门用来代表Effect的实例的。当一个FX文件的参数被美工加以调整从而具备一些特定的值之后，该 FX文件和这些参数值的集合就形成了一个Effect实例。该模板的定义如下： </p>
            </font>
            <table cellSpacing=0 cellPadding=0 width=500 bgColor=#e0e0e0 border=0>
                <tbody>
                    <tr>
                        <td><span class=unnamed2><font face=宋体>template</font><font face=宋体></font></span><font face=宋体> EffectInstance <br>{ <br>&nbsp;&nbsp;&nbsp; &lt; E331F7E4-0559-4cc2-8E99-1CEC1657928F &gt; <br>&nbsp;&nbsp;&nbsp; STRING EffectFilename; <br>&nbsp;&nbsp;&nbsp; [ ... ] <br>} </font></td>
                    </tr>
                </tbody>
            </table>
            <font face=宋体>
            <p>&nbsp;</p>
            <p>其中， EffectFilename代表了该Effect实例中的FX文件名， [ ... ]代表在其中可以插入任何X文件模板对应的数据。这样就可以代表任何类型的参数值。 </p>
            <p>然而要想让Direct3D程序能够识别[ ... ]中的内容，需要使用X文件模板中的EffectParam系列模板，包括EffectParamDWord, EffectParamFlaots, EffectParamString。通过这三种模板对应的数据，所有类型的Effect参数值都可以被记录在X文件中。 </p>
            <p>最后，EffectInstance数据需要被放置在Material数据中才可以被识别。</p>
            <p>Material模板： </p>
            </font>
            <table cellSpacing=0 cellPadding=0 width=500 bgColor=#e0e0e0 border=0>
                <tbody>
                    <tr>
                        <td><span class=unnamed2><font face=宋体>template</font></span><font face=宋体> Material <br>{ <br>&nbsp;&nbsp;&nbsp; &lt; 3D82AB4D-62DA-11CF-AB39-0020AF71E433 &gt; <br>&nbsp;&nbsp;&nbsp; ColorRGBA faceColor; <br>&nbsp;&nbsp;&nbsp; FLOAT power; <br>&nbsp;&nbsp;&nbsp; ColorRGB specularColor; <br>&nbsp;&nbsp;&nbsp; ColorRGB emissiveColor; <br>&nbsp;&nbsp;&nbsp; [...] <br>} </font></td>
                    </tr>
                </tbody>
            </table>
            <font face=宋体>
            <p>&nbsp;</p>
            <p>前面的一些颜色模板表明在Material数据中这些颜色信息是必须有的，而最后的[ ... ]则代表可以插入任何X文件模板对应的数据。我们的EffectInstance数据就可以放置在这里 。 </p>
            <p>举一个简单的例子： </p>
            </font>
            <table cellSpacing=0 cellPadding=0 width=500 bgColor=#e0e0e0 border=0>
                <tbody>
                    <tr>
                        <td><font face=宋体>Material { <span class=unnamed1>//材质 </span><br>&nbsp;&nbsp;&nbsp; 0.500000;0.500000;0.500000;1.000000;;<span class=unnamed1> //faceColor</span> <br>&nbsp;&nbsp;&nbsp; 0.000000;<span class=unnamed1> //power</span> <br>&nbsp;&nbsp;&nbsp; 0.900000;0.900000;0.900000;;<span class=unnamed1> //specularColor</span> <br>&nbsp;&nbsp;&nbsp; 0.000000;0.000000;0.000000;;<span class=unnamed1> //emissiveColor</span> <br>&nbsp;&nbsp;&nbsp; EffectInstance <br>&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class=unnamed1>//[...]，这里是EffectInstance</span> <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "SkyboxNew01.fx";&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class=unnamed1> //fx文件的文件名。通过D3DXCreateEffectFromFile()可以</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class=unnamed1> //建立该文件对应的D3DXEffect对象 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; //下面是EffectInstance中的[...] </span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; EffectParamString&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;{&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class=unnamed1>//EffectParamString，即字符串型参数值</span>&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;"TexCloudTop";&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class=unnamed1> //参数的名称，通过该名称调用ID3DXEffect::GetXXXByName()方法</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class=unnamed1>//可以得到与fx文件中对应的参数</span>。 <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "DarkClouds01.jpg";&nbsp;<span class=unnamed1> //参数的值&nbsp;</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}&nbsp;<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;EffectParamString <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class=unnamed1>//同上 </span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "TexCloudBottom"; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "DarkClouds02.jpg"; <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; EffectParamFloats <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; {&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class=unnamed1>//EffectParamFloats，即浮点数组型参数值</span> <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; "Brightness";&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class=unnamed1>//参数名称</span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 1;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class=unnamed1> //浮点数组大小</span> <br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 0.500000;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class=unnamed1> //值 </span><br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; } <br>&nbsp;&nbsp;&nbsp; } <br>} </font></td>
                    </tr>
                </tbody>
            </table>
            <font face=宋体>
            <p>&nbsp;</p>
            <p>当我们在程序中调用D3DXLoadMeshFromX()或D3DXLoadMeshHierarchyFromX()时，就可以通过其LPD3DXBUFFER *ppEffectInstances参数来接收到网格所用的所有EffectInstance的信息。</p>
            <p>在程序中，对应于X文件中的EffectInstance模板和EffectParam系列模板，有两个结构体用来代表Effect数据： </p>
            </font>
            <table cellSpacing=0 cellPadding=0 width=500 bgColor=#e0e0e0 border=0>
                <tbody>
                    <tr>
                        <td>
                        <p><span class=unnamed2><font face=宋体>typedef struct</font></span><font face=宋体> _D3DXEFFECTINSTANCE <br>{ <span class=unnamed1>//EffectInstance</span> <br>&nbsp;&nbsp;&nbsp; LPSTR pEffectFilename;&nbsp;<span class=unnamed1>//fx文件名</span> <br>&nbsp;&nbsp;&nbsp; DWORD NumDefaults; <span class=unnamed1>//参数个数</span> <br>&nbsp;&nbsp;&nbsp; LPD3DXEFFECTDEFAULT pDefaults; <span class=unnamed1>//参数数组</span> <br>} D3DXEFFECTINSTANCE, *LPD3DXEFFECTINSTANCE; </font></p>
                        <font face=宋体>
                        <p><span class=unnamed2>typedef struct </span>_D3DXEFFECTDEFAULT <br>{ <span class=unnamed1>//EffectDefault，即EffectParam </span><br>&nbsp;&nbsp;&nbsp; LPSTR pParamName;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class=unnamed1>//参数名 </span><br>&nbsp;&nbsp;&nbsp; D3DXEFFECTDEFAULTTYPE Type;<span class=unnamed1> //参数类型 </span><br>&nbsp;&nbsp;&nbsp; DWORD NumBytes;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class=unnamed1>//参数大小，以字节记 </span><br>&nbsp;&nbsp;&nbsp; LPVOID pValue;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; <span class=unnamed1>//指向参数的值的指针 </span><br>} D3DXEFFECTDEFAULT, *LPD3DXEFFECTDEFAULT; </p>
                        <p>需要注意的是，从文件中得到的参数类型只有以下几种： <br><span class=unnamed2>typedef</span> enum _D3DXEFFECTDEFAULTTYPE <br>{ <br>&nbsp;&nbsp;&nbsp; D3DXEDT_STRING = 1, <span class=unnamed1>//字符串 </span><br>&nbsp;&nbsp;&nbsp; D3DXEDT_FLOATS = 2, <span class=unnamed1>//浮点数组 </span><br>&nbsp;&nbsp;&nbsp; D3DXEDT_DWORD = 3,&nbsp; <span class=unnamed1>//长整型 </span><br>&nbsp;&nbsp;&nbsp; D3DXEDT_FORCE_DWORD = 0x7fffffff <span class=unnamed1>//此值不使用 </span><br>} D3DXEFFECTDEFAULTTYPE; </p>
                        </font></td>
                    </tr>
                </tbody>
            </table>
            <font face=宋体>
            <p>&nbsp;</p>
            <p>在调用了D3DXLoadMeshFromX()和D3DXLoadMeshHierarchyFromX()之后，X文件中的所有Effect数据信息就以上述结构体的形式放置在ppEffectInstances中了。 另外，在从3DS MAX导出参数到X文件时，对于整型和浮点数组型的变量，它们的值将直接导出到X文件中去；而对于所有的纹理贴图文件参数，导出的仅仅是该文件的文件名。所以 在程序中需要再根据这些文件名来建立纹理对象。 在《龙的传说》中，我们使用一个自定义的CEffectInstance类来处理将文件名转换为纹理对象的过程。 一般来说，建立一个完整的CEffectInstance的过程如下： </p>
            <p>根据D3DXEFFECTINSTANCE结构中的pEffectFilename字符串寻找对应的FX文件; <br>根据该FX文件建立ID3DXEffect，并将指针保存在CEffectInstance中; <br>根据D3DXEFFECTINSTANCE结构中的pDefaults设置CEffectInstance中的参数信息：&nbsp;</p>
            <blockquote dir=ltr style="MARGIN-RIGHT: 0px">
            <p><br>对于长整型和浮点数组，直接拷贝；&nbsp;<br>对于字符串，首先调用ID3DXEffect接口中的GetParameterByName()和GetParameterDesc()方法，得到该参数的类型； 然后进一步判断： </p>
            <blockquote dir=ltr style="MARGIN-RIGHT: 0px">
            <p><br>如果确实是字符串参数，则直接拷贝 <br>如果是纹理参数，则将该字符串作为纹理文件名建立纹理对象，并将指针保存在CEffectInstance中。 </p>
            </blockquote></blockquote>
            <p>而在最新推出的DirectX 9 SDK Summer 2004中，通过ID3DXEffect::BeginParameterBlock()和ID3DXEffect::EndParameterBlock()方法，我们可以将Effect参数设置过程统一绑定到一个ParamBlock句柄上。这样，在调用ID3DXEffect::Begin()之前就可以直接使用ID3DXEffect::ApplyParameterBlock()方法来设置所有被绑定的参数值。例如： </p>
            <p>[<strong>以前的做法</strong>]： </p>
            <p>在读取参数时：获得每一个参数的句柄 </p>
            </font>
            <table cellSpacing=0 cellPadding=0 width=500 bgColor=#e0e0e0 border=0>
                <tbody>
                    <tr>
                        <td><font face=宋体>
                        <p>hParam1 = pEffect-&gt;GetParameterByName( NULL, "LightPos" ); <br>hParam2 = pEffect-&gt;GetParameterByName( NULL, "LightColor" ); <br>...</p>
                        <p class=unnamed1>在实时绘制时：分别设置每一个参数 </p>
                        <p>pEffect-&gt;SetValue( hParam1, value1 ); <br>pEffect-&gt;SetValue( hParam2, value2 ); <br>... <br>pEffect-&gt;Begin(); <br><span class=unnamed1>// 绘制</span> <br>... </p>
                        </font></td>
                    </tr>
                </tbody>
            </table>
            <font face=宋体>
            <p>&nbsp;</p>
            <p>[<strong>在DirectX 9 SDK Summer 2004中的做法</strong>]： </p>
            <p>在读取参数时：绑定所有参数设置到同一句柄 </p>
            </font>
            <table cellSpacing=0 cellPadding=0 width=500 bgColor=#e0e0e0 border=0>
                <tbody>
                    <tr>
                        <td>
                        <p><font face=宋体>hParam1 = pEffect-&gt;GetParameterByName( NULL, "LightPos" ); <br>hParam2 = pEffect-&gt;GetParameterByName( NULL, "LightColor" ); <br>...<br>pEffect-&gt;BeginParameterBlock();<span class=unnamed1> // 开始绑定</span> <br>pEffect-&gt;SetValue( hParam, value1 ); <br>pEffect-&gt;SetValue( hParam, value2 ); <br>... <br>hParamBlock = pEffect-&gt;EndParameterBlock(); <span class=unnamed1>// 结束绑定，返回句柄 </span></font></p>
                        <p class=unnamed1><font face=宋体>在实时绘制时：统一设置绑定值 </font></p>
                        <font face=宋体>
                        <p>pEffect-&gt;ApplyParameterBlock( hParamBlock ); <br>pEffect-&gt;Begin; <br><span class=unnamed1>// 绘制 </span><br>... </p>
                        </font></td>
                    </tr>
                </tbody>
            </table>
            <font face=宋体>
            <p>&nbsp;</p>
            <p>这样不仅简化了在读取时对参数的分析过程，而且提高了实际绘制时参数设置过程的效率。 </p>
            <p><strong>总结</strong></p>
            <p>以上就是一些对于在DirectX 9.0中对Effect Framework的使用的简要介绍。总之，使用Effect来替代以前的标准材质是目前实时图形领域的发展趋势。通过Effect Framework，程序员和美工可以为实时三维程序实现多种多样的材质效果和视觉效果。 由于内容实在太多，限于篇幅，本文只是对Effect Framework中相关概念的一个总体概括和简要介绍，所以显得有些晦涩。在以后的文章中，将分批对这个Framework以及在其之上进行工作的流程进行比较详细的介绍。</p>
            </font></td>
        </tr>
    </tbody>
</table>
<img src ="http://www.cppblog.com/liangairan/aggbug/71357.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2009-01-06 17:06 <a href="http://www.cppblog.com/liangairan/articles/71357.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>3DS Max 插件制作学习心得（转）</title><link>http://www.cppblog.com/liangairan/articles/71230.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Mon, 05 Jan 2009 08:15:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/71230.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/71230.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/71230.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/71230.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/71230.html</trackback:ping><description><![CDATA[来源：yhforchina的专栏 - CSDNBlog<br><br>　　要从3ds Max中导出场景信息，大概有两种方式：1.利用3ds Max的sdk制作插件。2.利用3ds Max的Max Script编写场景输出脚本。两种方式各有优劣，这里仅叙述我用了数天研究3ds Max SDK并制作插件的学习心得。<br><br>　　1.前期环境配置工作。<br><br>　　首先肯定要安装带SDK的3ds Max，我安装的是3ds Max7。安装后在maxsdk\help下有个sdkapwz.zip，把这个文件解压到VS 6.0或VS 2003的application wizard路径下，启动VS就会有3ds Max plug-in的应用程序向导来生成插件程序的框架。<br><br>　　2.制作插件需要了解的几个基本概念。<br><br>　　2.1 我所了解的插件原理是：3ds Max会公布一些接口，插件制作者需要做的是实现这些接口。例如利用向导生成一个用于场景导出的插件。就会发现在生成的程序中有一个类继承于class SceneExport，而 SDK 中关于这个接口的描述是：<br><br>　　This is a base class for creating file export plug-ins. The plug-in implements methods of this class to describe the properties of the export plug-in and a method that handles the actual export process. <br><br>　　再看下面的函数说明，可以看到函数是虚函数的方式声明的，所以必须要将其所有的函数进行实现。<br><br>　　2.2 3ds Max是怎样识别插件的接口？还是以上面的场景到处插件为例，程序中还会生成一个继承于 ClassDesc2的类，这个类会实现一些关于类的ID，层次信息处理的函数。估计系统就是根据这个识别处我的插件的接口，没有具体去研究，只了解个大概。<br><br>　　2.3 如何调试所编写的插件，SDK中有说明，我这里简单说一下，将工程属性设置为Hybrid（默认是Debug）,并且把输出dle文件的路径设为3ds Max的plug-in的路径，再把调试的可执行程序设为3ds Max.exe，这样 调试的时候就会启动3ds Max主程序，其他诸如设置断点，单步等调试手段和普通程序的调试方法一样。<br><br>　　3.通过一个例子学习插件编程。 <br><br>　　这部分还真不好写，涉及到一些代码，代码中又有很多API需要讲解，API中又有很多基本知识需要说明，唉，硬着头皮来吧。<br><br>　　还是以那个场景导出类为例，可以看到，SceneExport中有个非常重要的函数需要实现：<br><br>virtual int DoExport(const TCHAR *name,ExpInterface *ei,Interface *i, <br>BOOL suppressPrompts=FALSE, DWORD options=0) = 0; <br><br>　　先看看参数：<br><br>　　name 表示要导出的文件名。<br><br>　　ei 用来枚举场景，需要注意的是：由于这个函数是由系统调用的，所以这个参数是系统传递的，不 用去思考怎么实现ExpInterface这个接口。<br><br>　　i 提供一个用来调用3ds Max方法的指针，可以把它视作一个指向3ds Max的指针。同样，这个指针 也是由系统传递的。<br><br>　　剩下两个参数暂不关心。<br><br>　　现在来研究ei和i两个参数：<br><br>　　class ExpInterface仅包含一个成员：IScene *theScene。这样的类设计的简直是&#8220;无耻&#8221;。再去研究IScene吧。IScene中有一个很重要的函数：<br><br>virtual int EnumTree( ITreeEnumProc *proc )=0; <br><br>　　根据SDK的说明，该函数的功能是：用来枚举场景中的每个INode。因此需要一个ItreeEnumProc*作为参数,由于是自己调用整个函数，因而必须自己实现ItreeEnumProc接口，还好这个接口不是很复杂，把这个回调函数实现就可以了：<br><br>virtual int callback( INode *node )=0; <br><br>　　因为是回调函数，所以node也是系统传递进来的，为了证明这一点，我们可以编程实验一下：<br><br>class MyEnumProc: public ITreeEnumProc<br>...{<br>public: int callback( INode *node )<br>...{<br>int a = 0; //在这里设置断点<br>return a; <br>}<br>}<br><br>　　在MyExport中添加这一个成员变量：<br><br>MyEnumProc MyProc; <br><br>int MyExport::DoExport(const TCHAR *name,ExpInterface *ei,Interface *i, BOOL suppressPrompts, DWORD options)<br>...{<br>/**//*在函数中添加这行代码*/<br>ei-＞theScene-＞EnumTree(&amp;this-＞MyProc ); <br><br>if(!suppressPrompts)<br>DialogBoxParam(hInstance, <br>MAKEINTRESOURCE(IDD_PANEL), <br>GetActiveWindow(), <br>MY5OptionsDlgProc, (LPARAM)this); <br>return FALSE; <br>}<br><br>　　调试这个例子，在场景中绘制三个立方体，可以看到系统会调用callback三次，这说明一个物体就是一个Node。那么怎么来导出一个Node几何信息呢？看下面这个代码。<br><br>public: int callback( INode *node )<br>...{<br>Object *lobj; <br>lobj = node-＞GetObjectRef(); <br>if (lobj-＞SuperClassID()== GEOMOBJECT_CLASS_ID)<br>...{<br>GeomObject* gobj = (GeomObject*)lobj; <br>Class_ID triID = Class_ID(TRIOBJ_CLASS_ID,0); <br>Class_ID boxID = Class_ID(BOXOBJ_CLASS_ID,0); <br>if (lobj-＞ClassID()==boxID)<br>...{<br>if (lobj-＞CanConvertToType(triID))<br>...{<br><br>TriObject *triobj = (TriObject *)lobj-＞ConvertToType<br>(0,triID); <br>Mesh mesh = triobj-＞mesh; <br>int numVerts = mesh.getNumVerts(); <br>}<br><br>IParamArray* array = lobj-＞GetParamBlock(); <br>float length = 0.0f; <br>float height = 0.0f; <br>float width = 0.0f; <br><br>array-＞GetValue( lobj-＞GetParamBlockIndex(BOXOBJ_LENGTH),<br>0, length, FOREVER); <br>array-＞GetValue( lobj-＞GetParamBlockIndex(BOXOBJ_HEIGHT),<br>0, height, FOREVER); <br>array-＞GetValue( lobj-＞GetParamBlockIndex(BOXOBJ_WIDTH),<br>0, width, FOREVER); <br>}<br>} <br><br>　　首先node-＞GetObjectRef()会返回这个节点的物体引用。关于ObjectRef有一套几何流水线的说明，这里实在是没功夫写了。接着首先判断这物体的SuperClassID是否为GEOMOBJECT_CLAS- S_ID，如果是，则再看它是否能转换为TriObject，即由三角形组成的物体，至于为什么要这样转，我只能说只有这个类可以返回一个Mesh，而通 过Mesh能够获得诸如顶点，法线，面等一般3D程序所需要的几何信息（这里只获取了该Mesh的面的个数）。当然，对于一个Box，我们可能只想获得它 的长宽高，所以，代码中又提供了另一个方法来返回其几何信息。<br><br>　　虽然只实现了这么短的代码，但却花了数天的时间，主要对3ds Max的结构不熟悉，加上SDK写得真叫一个乱，还好总算有些进展，正所谓万事开头难。下一步将研究如何导出场景的光照，物体的纹理贴图等信息。
<img src ="http://www.cppblog.com/liangairan/aggbug/71230.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2009-01-05 16:15 <a href="http://www.cppblog.com/liangairan/articles/71230.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>D3D状态</title><link>http://www.cppblog.com/liangairan/articles/70256.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Wed, 24 Dec 2008 09:37:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/70256.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/70256.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/70256.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/70256.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/70256.html</trackback:ping><description><![CDATA[<p><a name=状态><strong><span>状态</span></strong></a></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p><span>Microsoft&#174; Direct3D&#174;设备是一个状态机。应用程序设置光照、渲染和变换模块的状态，然后在渲染时传递数据给它们。</span></p>
<p><span>本节描述图形流水线用到的所有不同类型的状态。</span></p>
<ul type=disc>
    <li><u><span><a href="http://www.gesoftfactory.com/developer/States.htm#渲染状态">渲染状态</a></span></u>
    <li><u><span><a href="http://www.gesoftfactory.com/developer/States.htm#取样器状态">取样器状态</a></span></u>
    <li><u><span><a href="http://www.gesoftfactory.com/developer/States.htm#纹理层状态">纹理层状态</a></span></u>
    <li><u><span><a href="http://www.gesoftfactory.com/developer/States.htm#状态块">状态块</a></span></u> </li>
</ul>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<h1><a name=渲染状态>渲染状态</a></h1>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p>设备渲染状态控制<span>Microsoft&#174; Direct3D&#174;设备光栅化模块的行为，它们通过改变渲染状态的属性，使用何种类型的着色算法，雾属性和其它光栅化器操作来达到这个目的。</span></p>
<p><span>C++应用程序通过调用<u>IDirect3DDevice9::SetRenderState</u>方法控制渲染状态的属性。<u>D3DRENDERSTATETYPE</u>枚举类型指定所有可能的渲染状态，应用程序把一个枚举类型值作为第一个参数传递给<strong>IDirect3DDevice9::SetRenderState</strong>方法。</span></p>
<p>固定功能顶点处理由<strong><span>IDirect3DDevice9::SetRenderState</span></strong>方法和以下设备渲染状态控制。这些控制中的大多数在使用可编程顶点着色器时没有任何作用。</p>
<ul type=disc>
    <li><span>D3DRS_SPECULARENABLE </span>
    <li><span>D3DRS_FOGSTART </span>
    <li><span>D3DRS_FOGEND </span>
    <li><span>D3DRS_FOGDENSITY </span>
    <li><span>D3DRS_RANGEFOGENABLE </span>
    <li><span>D3DRS_LIGHTING </span>
    <li><span>D3DRS_AMBIENT </span>
    <li><span>D3DRS_FOGVERTEXMODE </span>
    <li><span>D3DRS_COLORVERTEX </span>
    <li><span>D3DRS_LOCALVIEWER </span>
    <li><span>D3DRS_NORMALIZENORMALS </span>
    <li><span>D3DRS_DIFFUSEMATERIALSOURCE </span>
    <li><span>D3DRS_SPECULARMATERIALSOURCE </span>
    <li><span>D3DRS_AMBIENTMATERIALSOURCE </span>
    <li><span>D3DRS_EMISSIVEMATERIALSOURCE </span>
    <li><span>D3DRS_VERTEXBLEND</span> </li>
</ul>
<p>另外，固定功能顶点处理流水线使用以下方法设置变换、材质和光照。</p>
<ul type=disc>
    <li><span>IDirect3DDevice9::SetTransform </span>
    <li><span>IDirect3DDevice9::SetMaterial </span>
    <li><span>IDirect3DDevice9::SetLight </span>
    <li><span>IDirect3DDevice9::LightEnable</span> </li>
</ul>
<p><strong><span>注意</span></strong><span><span>&nbsp;&nbsp;&nbsp;&nbsp; </span>D3DRS_SPECULARENABLE</span><span>控制像素流水线中镜面反射色的加法。</span><span>D3DRS_FOGSTART</span><span>，</span><span>D3DRS_FOGEND</span><span>和</span><span>D3DRS_FOGDENSITY</span><span>控制如何计算雾的起点、终点和像素雾的密度。</span></p>
<p>更多的信息包含在以下主题中。</p>
<h2>概述</h2>
<p><strong><u><span><a href="http://www.gesoftfactory.com/developer/States.htm#阿尔法混合状态"><span>阿尔法混合状态</span></a></span></u></strong></p>
<p><strong><u><span><a href="http://www.gesoftfactory.com/developer/States.htm#阿尔法测试状态"><span>阿尔法测试状态</span></a></span></u></strong></p>
<p><strong><u><span><a href="http://www.gesoftfactory.com/developer/States.htm#环境光状态"><span>环境光状态</span></a></span></u></strong></p>
<p><strong><u><span><a href="http://www.gesoftfactory.com/developer/States.htm#抗锯齿状态"><span>抗锯齿状态</span></a></span></u></strong></p>
<p><strong><u><span><a href="http://www.gesoftfactory.com/developer/States.htm#剔除状态"><span>剔除状态</span></a></span></u></strong></p>
<p><strong><u><span><a href="http://www.gesoftfactory.com/developer/States.htm#深度缓存状态"><span>深度缓存状态</span></a></span></u></strong></p>
<p><strong><u><span><a href="http://www.gesoftfactory.com/developer/States.htm#雾状态"><span>雾状态</span></a></span></u></strong></p>
<p><strong><u><span><a href="http://www.gesoftfactory.com/developer/States.htm#光照状态"><span>光照状态</span></a></span></u></strong></p>
<p><strong><u><span><a href="http://www.gesoftfactory.com/developer/States.htm#轮廓和填充状态"><span>轮廓和填充状态</span></a></span></u></strong></p>
<p><strong><u><span><a href="http://www.gesoftfactory.com/developer/States.htm#每顶点颜色状态"><span>每顶点颜色状态</span></a></span></u></strong></p>
<p><strong><u><span><a href="http://www.gesoftfactory.com/developer/States.htm#图元裁剪状态"><span>图元裁剪状态</span></a></span></u></strong></p>
<p><strong><u><span><a href="http://www.gesoftfactory.com/developer/States.htm#着色状态"><span>着色状态</span></a></span></u></strong></p>
<p><strong><u><span><a href="http://www.gesoftfactory.com/developer/States.htm#模板缓存状态"><span>模板缓存状态</span></a></span></u></strong></p>
<p><strong><u><span><a href="http://www.gesoftfactory.com/developer/States.htm#纹理环绕状态"><span>纹理环绕状态</span></a></span></u></strong></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p><a name=阿尔法混合状态><strong><span>阿尔法混合状态</span></strong></a></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p>一个颜色的阿尔法值控制它的透明度。启用阿尔法混合允许把一个表面上的颜色、材质和纹理根据透明度混合到另一个表面上。</p>
<p><span>更多信息请参阅<u>阿尔法纹理混合</u>和<u>纹理混合</u>。</span></p>
<p><span>C++应用程序使用<u>D3DRS_ALPHABLENDENABLE</u>渲染状态启用阿尔法透明混合。Microsoft&#174; Direct3D&#174; API允许多种类型的阿尔法混合。但是，重要的是要注意用户的三维硬件可能不完全支持所有Direct3D提供的混合状态。<u></u></span></p>
<p><span>已完成的阿尔法混合的类型取决于<strong><span>D3DRS_SRCBLEND</span></strong>和<strong><span>D3DRS_DESTBLEND</span></strong>渲染状态。源和目的混合状态须成对使用。以下示例代码显示了如何将源混合状态设置为<span>D3DBLEND_SRCCOLOR并将目的混合状态设置为D3DBLEND_INVSRCCOLOR。</span></span></p>
<p><span>//</span><span> 本例假设d3dDevice为指向IDirect3DDevice9接口的有效指针。</span></p>
<p>&nbsp;</p>
<p><span>//</span><span> 设置源混合状态。</span></p>
<p><span>d3dDevice-&gt;<span>SetRenderState(</span>D3DRS_SRCBLEND, D3DBLEND_SRCCOLOR);</span></p>
<p>&nbsp;</p>
<p><span>//</span><span> 设置目的混合状态。</span></p>
<pre><span>//</span><span> 设置目的混合状态。</span></pre>
<p><span>d3dDevice-&gt;<span>SetRenderState(</span>D3DRS_DESTBLEND, D3DBLEND_INVSRCCOLOR);</span></p>
<p><span>改变源和目的混合状态可以使物体在雾很浓或灰尘很多的环境中看起来像发光物体。例如，若应用程序在雾很浓的环境中建模了火焰，能量场，离子束或类似的发光体，则可把源和目的混合状态都设置为<span>D3DBLEND_ONE。</span></span></p>
<p><span>阿尔法混合的另一种应用是控制三维场景中的光照，也称为光照贴图。将源混合状态设置为<span>D3DBLEND_ZERO并将目的混合状态设置为D3DBLEND_SRCALPHA，会根据源的阿尔法信息使场景变暗。源图元被用作光照贴图，对帧缓存中的内容进行缩放，并在适当的时候使之变暗，这就是单色光照贴图。</span></span></p>
<p><span>应用程序可以生成彩色光照贴图，只要把源阿尔法混合状态设置为<span>D3DBLEND_ZERO，并把目的混合状态设置为D3DBLEND_SRCCOLOR。</span></span></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p><a name=阿尔法测试状态><strong><span>阿尔法测试状态</span></strong></a></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p><span>C++应用程序可以用阿尔法测试控制何时把像素被写入渲染目标表面。通过设置D3DRS_ALPHATESTENABLE渲染状态，应用程序让当前的Direct3D设备根据阿尔法测试函数测试每个像素。如果测试成功，那么就把像素写入表面。如果不成功，那么Direct3D就忽略该像素。应用程序通过<strong>D3DRS_ALPHAFUNC</strong>渲染状态选择阿尔法测试函数。应用程序可以通过<strong>D3DRS_ALPHAREF</strong>渲染状态设置一个参考阿尔法值用来和所有像素进行比较。<u></u></span></p>
<p><span>阿尔法测试常用于在光栅化几乎透明的物体时提高性能。如果正被光栅化的颜色数据比给定像素更不透明（<span>D3DPCMPCAP_GREATEREQUAL），那么该像素就被写入。否则，光栅化器就完全忽略该像素，这样就节省了将两个颜色混合所需要的处理。以下示例代码检查当前设备是否支持一个给定的比较函数，若支持，则设置比较函数的参数，用来在渲染时提高性能。</span></span></p>
<p><span>//</span><span> 本示例代码假设pCaps为一D3DCAPS9结构， </span></p>
<p><span>//</span><span> 被先前的一个IDirect3D9::GetDeviceCaps调用填充。</span></p>
<p>&nbsp;</p>
<p><span><span>if</span></span><span> (pCaps.AlphaCmpCaps &amp; D3DPCMPCAPS_GREATEREQUAL)</span></p>
<p><span>{</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span><span>dev</span>-&gt;SetRenderState(D3DRS_ALPHAREF, (DWORD)0x00000001);</span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span><span>dev</span>-&gt;SetRenderState(D3DRS_ALPHATESTENABLE, TRUE); </span></p>
<p><span><span>&nbsp;&nbsp;&nbsp; </span><span>dev</span>-&gt;SetRenderState(D3DRS_ALPHAFUNC, D3DCMP_GREATEREQUAL);</span></p>
<p><span>}</span></p>
<p>&nbsp;</p>
<p><span>//</span><span> 如果不支持比较，那么就照常渲染。唯一的缺点是没有性能上的提升。</span></p>
<p><span>并不是所有的硬件都支持全部阿尔法测试特性。可以通过调用<u><span>IDirect3D9::GetDeviceCaps</span></u>方法检查设备能力，取回设备能力后，根据希望使用的比较函数检查相关<u><span>D3DCAPS9</span></u>结构的<strong><span>AlphaCmpCaps</span></strong>成员。如果<strong><span>AlphaCmpCaps</span></strong>成员只包含<span>D3DPCMPCAPS_ALWAYS能力或D3DPCMPCAPS_NEVER能力，那么驱动程序不支持阿尔法测试。</span></span></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p><a name=环境光状态><strong><span>环境光状态</span></strong></a></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p>环境光是周围的光，从各个方向照射而来。</p>
<p><span>有关<span>Microsoft&#174; Direct3D&#174;如何使用环境光的信息，请参阅<u>直射光与环境光</u>，和<u>与光照相关的数学</u>。</span></span></p>
<p><span>C++应用程序调用<u>IDirect3DDevice9::SetRenderState</u>方法设置环境光的颜色，并将D3DRS_AMBIENT枚举类型值作为第一个参数传入。第二个参数是颜色值，默认值为零。</span></p>
<p><span>//</span><span> 本例假设d3dDevice为指向IDirect3DDevice9接口的有效指针。</span></p>
<p>&nbsp;</p>
<p><span>//</span><span> 设置环境光</span></p>
<p>&nbsp;</p>
<p><span>d3dDevice-&gt;<span>SetRenderState(</span>D3DRS_AMBIENT, 0x00202020);</span></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p><a name=抗锯齿状态><strong><span>抗锯齿状态</span></strong></a></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p><span>抗锯齿是使屏幕上的线和边缘看起来更为平滑的一种方法。<span>Microsoft&#174; Direct3D&#174;支持两种抗锯齿方法：边缘抗锯齿和全屏抗锯齿。</span></span></p>
<p><span>有关这些技术的更多细节，请参阅<u>抗锯齿</u>。</span></p>
<p><span>默认情况下，<span>Direct3D不执行抗锯齿。边缘抗锯齿需要渲染第二遍，要启用边缘抗锯齿，应该把<u>D3DRS_EDGEANTIALIAS</u>渲染状态设置为TRUE，要禁用边缘抗锯齿，应该把<u>D3DRS_EDGEANTIALIAS</u>设置为FALSE。</span></span></p>
<p><span>要启用全屏抗锯齿，应该把<strong><span>D3DRS_MULTISAMPLEANTIALIAS</span></strong>渲染状态设置为<span>TRUE。要禁用全屏抗锯齿，应该把D3DRS_MULTISAMPLEANTIALIAS设置为FALSE。</span></span></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p><a name=剔除状态><strong><span>剔除状态</span></strong></a></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p><span>Direct3D渲染图元时会剔除背向用户的图元。</span></p>
<p><span>C++应用程序使用<u>D3DRS_CULLMODE</u>渲染状态设置剔除模式，它可以被设置为<u>D3DCULL</u>枚举类型的成员。默认情况下，Direct3D把顶点逆时针排列的面作为背向面剔除。</span></p>
<p><span>以下示例代码描述了设置剔除模式的过程，这里把顶点顺时针排列的面作为背向面剔除。</span></p>
<p><span>//</span><span> 本例假设d3dDevice为指向IDirect3DDevice9接口的有效指针。</span></p>
<p><span>//</span><span> 设置剔除状态。</span></p>
<p><span>d3dDevice-&gt;<span>SetRenderState(</span>D3DRS_CULLMODE, D3DCULL_CW);</span></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p><a name=深度缓存状态><strong><span>深度缓存状态</span></strong></a></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p>深度缓存是消除隐藏线和隐藏面的一种方法。默认的情况下，<span>Direct3D不使用深度缓存。</span></p>
<p><span>有关深度缓存的概念，请参阅<u>深度缓存</u>。</span></p>
<p><span>C++应用程序用<u>D3DRS_ZENABLE</u>渲染状态更新深度缓存的状态，用<u>D3DZBUFFERTYPE</u>枚举类型成员指定新的状态值。</span></p>
<p><span>若应用程序要阻止<span>Direct3D写入深度缓存，则可在调用<u>IDirect3DDevice9::SetRenderState</u>时用<u>D3DRS_ZWRITEENABLE</u>枚举类型作为第一个参数，并将第二个参数指定为D3DZB_FALSE。</span></span></p>
<p><span>以下示例代码显示了如何将深度缓存状态设置为启用<span>z缓存。</span></span></p>
<p><span>//</span><span> 本例假设d3dDevice为指向IDirect3DDevice9接口的有效指针。</span></p>
<p><span>//</span><span> 启用z缓存</span></p>
<p><span>d3dDevice-&gt;<span>SetRenderState(</span>D3DRS_ZENABLE, D3DZB_TRUE);</span></p>
<p><span>应用程序也可以使用<u><span>D3DRS_ZFUNC</span></u>渲染状态控制<span>Direct3D用于深度缓存的比较函数。</span></span></p>
<p><span>Z偏移是把一个表面显示在另一个表面之前的一种方法，即使它们的深度值相同。可以将此技术用于多种效果。一个常用的例子是在墙上渲染影子，影子和墙具有相同的深度值，但是应用程序希望把影子显示在墙上，只要给影子赋一个z偏移值就可以让Direct3D正确地显示它们（请参阅<u>D3DRS_ZBIAS</u>）。</span></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p><a name=雾状态><strong><span>雾状态</span></strong></a></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p><span>雾效果可以赋予三维场景更强的真实感。</span><span>雾效果不仅可以用于模拟雾，也能减少远处场景的清晰度。这反映了真实世界中的情况，物体离用户越远，它们的细节就越模糊。</span></p>
<p><span>有关如何在应用程序中使用雾的更多信息，请参阅<u>雾</u>。</span></p>
<p><span>C++应用程序通过设备渲染状态控制雾。<u>D3DRENDERSTATETYPE</u>枚举类型包括许多状态，控制应用程序使用的是像素（查找表）雾还是顶点雾，雾是何颜色，系统使用的雾的公式，及公式的参数。</span></p>
<p><span>可以通过将<u><span>D3DRS_FOGENABLE</span></u>渲染状态设置为<span>TRUE启用雾。雾的颜色可以使用<u>D3DRS_FOGCOLOR</u>渲染状态设置为指定颜色，雾的颜色的阿尔法分量被忽略。</span></span></p>
<p><u><span>D3DRS_FOGTABLEMODE</span></u><span>和<u><span>D3DRS_FOGVERTEXMODE</span></u>渲染状态控制雾计算使用的公式，它们也间接控制使用何种类型的雾。这两个渲染状态都可以被设置为<u><span>D3FOGMODE</span></u>枚举类型的成员。将任何一个渲染状态设置为<span>D3DFOG_NONE相应地禁用像素雾或顶点雾。如果两个渲染状态都被设置为有效的模式，则系统只启用像素雾。</span></span></p>
<p><u><span>D3DRS_FOGSTART</span></u><span>和<u><span>D3DRS_FOGEND</span></u>渲染状态控制<span>D3DFOG_LINEAR模式下雾公式的参数。<u>D3DRS_FOGDENSITY</u>渲染状态控制指数雾模式下雾的密度。</span></span></p>
<p><span>更多信息请参阅<u>雾的参数</u>。<u></u></span></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p><a name=光照状态><strong><span>光照状态</span></strong></a></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p><span>使用<span>Microsoft&#174; Direct3D&#174;几何流水线的应用程序可以启用或禁用光照计算。只有包含顶点法向的顶点才能正确地计算光照，不含法向的顶点在所有光照计算中将使用零点积，因此没有法向的顶点得不到光照。</span></span></p>
<p><span>更多信息请参阅<u>光照的数学</u>。</span></p>
<p><span>应用程序通过将<u><span>D3DRS_LIGHTING</span></u>渲染状态设置为<span>TRUE启用Direct3D光照，这是默认的设置，应用程序通过将该渲染状态设置为FALSE禁用Direct3D光照。</span></span></p>
<p><span>光照渲染状态与可以对顶点缓存中的顶点执行的光照计算完全无关。在进行顶点处理时<u><span>IDirect3DDevice9::ProcessVertices</span></u>方法接收自己的标志用于控制光照计算。</span></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p><a name=轮廓和填充状态><strong><span>轮廓和填充状态</span></strong></a></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p>没有纹理的图元会使用它们的材质指定的颜色进行渲染，或者如果为顶点指定了颜色，就使用顶点色。可以将<u><span>D3DRS_FILLMODE</span></u>渲染状态指定为<u><span>D3DFILLMODE</span></u>枚举类型的值以选择填充图元的方法。</p>
<p><span>要启用抖动，应用程序必须把<u><span>D3DRS_DITHERENABLE</span></u>枚举类型值作为第一个参数传给<u><span>IDirect3DDevice9::SetRenderState</span></u>，并把第二个参数设置为<span>TRUE，把第二个参数设置为FALSE则禁用抖动。</span></span></p>
<p><span>有时，画一条线中的最后一个像素可能导致与周围图元的重叠。可以使用<u><span>D3DRS_LASTPIXEL</span></u>枚举值控制这种情况。但是，未经深思熟虑最好不要改变这个设置。在有些情况下，禁止渲染最后一个像素可能会导致图元之间的缝隙。<u></u></span></p>
<p><span>通过设置适当的画线模式可以画物体的轮廓。默认的画线状态是画实线。更多信息请参阅<u><span>Direct3D扩展（D3DX）线段绘制</span></u>渲染状态。</span></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p><a name=每顶点颜色状态><strong><span>每顶点颜色状态</span></strong></a></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p>当使用弹性顶点格式（<span>FVF）编码时，顶点可以同时包含顶点颜色和顶点法向的信息。默认情况下， Microsoft&#174; Direct3D&#174;在计算光照时使用这些信息。要设置是否把顶点颜色用于光照计算，应该调用<u>IDirect3DDevice9::SetRenderState</u>方法，把<u>D3DRS_COLORVERTEX</u>作为第一个参数，把第二个参数设置为FALSE禁用顶点颜色光照，或TRUE启用之。</span></p>
<p><span>若每顶点颜色被启用，则应用程序可以配置系统从何处取得源顶点颜色信息。<u><span>D3DRS_AMBIENTMATERIALSOURCE</span></u>，<u><span>D3DRS_DIFFUSEMATERIALSOURCE</span></u>，<u><span>D3DRS_EMISSIVEMATERIALSOURCE</span></u>和<u><span>D3DRS_SPECULARMATERIALSOURCE</span></u>渲染状态控制相应环境反射色、漫反射色、放射色和镜面反射颜色成员的来源。每个状态可以被设置为<u><span>D3DMATERIALCOLORSOURCE</span></u>枚举类型的成员，该枚举类型定义了一些常数，通知系统是使用当前材质的颜色、顶点的漫反射颜色还是顶点的镜面反射颜色作为指定颜色成员的来源。</span></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p><a name=图元裁剪状态><strong><span>图元裁剪状态</span></strong></a></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p>如果图元的一部分位于视区外，那么<span>Microsoft&#174; Direct3D&#174;可以对此类图元进行裁剪。在C++应用程序中，Direct3D裁剪由<u>D3DRS_CLIPPING</u>渲染状态控制。可以将该渲染状态设置为TRUE（默认值）启用图元裁剪，或将之设置为FALSE禁用Direct3D裁剪服务。</span></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p><a name=着色状态><strong><span>着色状态</span></strong></a></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p><span>Direct3D同时支持平面和高洛德着色，默认情况下使用高洛德着色。要控制当前的着色模式，C++应用程序可以给<u>D3DRS_SHADEMODE</u>渲染状态指定一个<u>D3DSHADEMODE</u>枚举类型值。</span></p>
<p><span>以下<span>C++示例代码显示了把着色状态设置为平面着色模式的过程。</span></span></p>
<p><span>//</span><span> 本例假设d3dDevice为指向IDirect3DDevice9接口的有效指针。</span></p>
<p><span>//</span><span> 设置着色模式。</span></p>
<p><span>d3dDevice-&gt;<span>SetRenderState(</span>D3DRS_SHADEMODE, D3DSHADE_FLAT);</span></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p><a name=模板缓存状态><strong><span>模板缓存状态</span></strong></a></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p>应用程序使用模板缓存决定是否把一个像素写入到渲染目标表面。</p>
<p><span>细节请参阅<u>模板缓存技术</u>。</span></p>
<p><span>C++应用程序通过调用<u>IDirect3DDevice9::SetRenderState</u>方法启用或禁用模板缓存，应该将<u>D3DRS_STENCILENABLE</u>作为第一个参数的值，并相应地把第二个参数设置为TRUE或FALSE启用或禁用模板缓存。</span></p>
<p><span>通过调用<strong><span>IDirect3DDevice9::SetRenderState</span></strong>，可以设置<span>Microsoft&#174; Direct3D&#174;执行模板测试时使用的比较函数，只需将第一个参数的值设置为<u>D3DRS_STENCILFUNC</u>，并把<u>D3DCMPFUNC</u>枚举类型值作为第二个参数的值传入。</span></span></p>
<p><span>模板参考值是在模板缓存中的值，模板函数用它进行测试。默认情况下，模板参考值为零。应用程序可以调用<u><span>IDirect3DDevice9::SetRenderState</span></u>设置它的值，只需将<u><span>D3DRS_STENCILREF</span></u>作为第一个参数传入，并把第二个参数的值设置为新的参考值。</span></p>
<p><span>Direct3D在对每个像素执行模板测试前，会对模板参考值和模板掩码值进行按位与操作，并把得到的结果用模板比较函数与模板缓存的内容进行比较。应用程序可以调用<u>IDirect3DDevice9::SetRenderState</u>设置模板掩码值，只需将<u>D3DRS_STENCILMASK</u>作为第一个参数传入，并把第二个参数设置为新的模板掩码值。</span></p>
<p><span>要设置模板测试失败时<span>Direct3D采取的行动，可以调用<strong>IDirect3DDevice9::SetRenderState</strong>并将<u>D3DRS_STENCILFAIL</u>作为第一个参数传入，第二个参数必须被设为<u>D3DSTENCILCAPS</u>枚举类型的成员。</span></span></p>
<p><span>应用程序也可以控制当模板测试通过但是<span>z缓存测试失败时Direct3D如何响应，只需调用<strong>IDirect3DDevice9::SetRenderState</strong>，将<u>D3DRS_STENCILZFAIL</u>作为第一个参数传递，并把第二个参数设为<strong>D3DSTENCILCAPS</strong>枚举类型的成员。</span></span></p>
<p><span>另外，应用程序还可以控制当模板测试和<span>z缓存测试都通过时Direct3D做什么，只需调用<strong>IDirect3DDevice9::SetRenderState</strong>并将<u>D3DRS_STENCILPASS</u>作为第一个参数，并把第二个参数设为<strong>D3DSTENCILCAPS</strong>枚举类型的成员。</span></span></p>
<p><span>写掩码可以用于修改将要写入模板缓存的值。要设置模板缓存写掩码，只需调用<strong><span>IDirect3DDevice9::SetRenderState</span></strong>，并将<u><span>D3DRS_STENCILWRITEMASK</span></u>作为第一个参数传递，并把第二个参数设为写掩码的值。</span></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p><a name=纹理环绕状态><strong><span>纹理环绕状态</span></strong></a></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p><u><span>D3DRS_WRAP0</span></u>到<u><span>D3DRS_WRAP7</span></u>渲染状态启用或禁用设备的多重纹理级联中各种纹理的<span>u和v环绕。可以把这些渲染状态设置为标志D3DWRAPCOORD_0，D3DWRAPCOORD_1，D3DWRAPCOORD_2和D3DWARPCOORD_3的组合，相应地启用纹理在第一、第二、第三和第四个方向上的环绕，若使用零值，则禁用所有环绕。默认情况下，所有纹理的所有方向上的纹理环绕都被禁用。</span></p>
<p><span>有关的概念综述请参阅<u>纹理环绕</u>。</span></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<h1><a name=取样器状态>取样器状态</a></h1>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p>取样状态控制诸如过滤、<span>tiling及寻址等与取样有关的操作。</span></p>
<h2>取样状态</h2>
<p><span>SetSamplerState设置取样器状态（包括tessellator中对位移贴图进行取样的状态），为了能够在编译时检测出正在移植Microsoft&#174; DirectX&#174; 8.x的应用程序，这些状态已经被重新命名并以D3DSAMP_前缀开头。这些状态包括：</span></p>
<ul type=disc>
    <li><span>D3DSAMP_ADDRESSU, D3DSAMP_ADDRESSV, D3DSAMP_ADDRESSW </span>
    <li><span>D3DSAMP_BORDERCOLOR </span>
    <li><span>D3DSAMP_MAGFILTER, D3DSAMP_MINFILTER, D3DSAMP_MIPFILTER </span>
    <li><span>D3DSAMP_MIPMAPLODBIAS </span>
    <li><span>D3DSAMP_MAXMIPLEVEL </span>
    <li><span>D3DSAMP_MAXANISOTROPY</span> </li>
</ul>
<p>在<span>DirectX 9.0中，使用像素着色器2.0版时，虽然纹理坐标的数量仍被限制为8个，但每一趟最多可以支持16个纹理表面。有些纹理层状态与表面相关，有些与坐标集相关，有些与顶点处理相关，而有些则与像素处理相关。<u>IDirect3DDevice9::SetSamplerState</u>方法可以指定纹理过滤、平铺、截取、MIPLOD等状态，最多可以有16个取样器。</span></p>
<p><span>C++应用程序通过调用<strong>IDirect3DDevice9::SetSamplerState</strong>方法控制与纹理有关的取样状态。<u>D3DSAMPLERSTATETYPE</u>枚举类型用于指定取样状态。</span></p>
<h2>相关主题</h2>
<ul type=disc>
    <li><u><span><a href="http://www.gesoftfactory.com/developer/States.htm#纹理层状态"><span>纹理层状态</span></a></span></u> </li>
</ul>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<h1><a name=纹理层状态>纹理层状态</a></h1>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p>纹理层状态控制纹理坐标的生成及纹理坐标的状态，如环绕模式。</p>
<p><span>C++应用程序通过调用<u>IDirect3DDevice9::SetTextureStageState</u>方法控制与纹理有关的状态。<u>D3DTEXTURESTAGESTATETYPE</u>枚举类型定义了所有与纹理有关的渲染状态，应用程序应该将<strong>D3DTEXTURESTAGESTATETYPE</strong>枚举类型值作为第一个参数传递给<strong>IDirect3DDevice9::SetTextureStageState</strong>方法。</span></p>
<p>应用程序可以通过调用<u><span>IDirect3DDevice9::SetTexture</span></u>方法设置某一层的纹理。</p>
<h2><span>SetTextureStageState</span></h2>
<p><span>SetTextureStageState现在可以设置以下状态。</span></p>
<ul type=disc>
    <li><span>固定功能顶点处理状态。这些状态控制对纹理坐标的操控：<span>D3DTSS_TEXTURETRANSFORMFLAGS和 D3DTSS_TEXCOORDINDEX。最多可以设置八个（因为Direct3D总是支持八组纹理坐标）</span></span>
    <li><span>固定功能像素着色器状态（以前的<span>TextureStageState）。D3DTSS_COLOROP, D3DTSS_ALPHAOP, D3DTSS_COLORARG0, D3DTSS_COLORARG1, D3DTSS_COLORARG2, D3DTSS_ALPHAARG0, D3DTSS_ALPHAARG1, D3DTSS_ALPHAARG2, D3DTSS_BUMPENVMAT00, D3DTSS_BUMPENVMAT01, D3DTSS_BUMPENVMAT10, D3DTSS_BUMPENVMAT11, D3DTSS_BUMPENVLSCALE, D3DTSS_BUMPENVLOFFSET, and D3DTSS_RESULTARG。这些状态最多可有MaxTextureBlendStages组可供设置。</span></span> </li>
</ul>
<p><span>D3DTSS_TEXCOORDINDEX是一个固定功能顶点处理状态。如果使用可编程顶点着色器，那么这个状态被忽略。</span></p>
<p>应用程序可以使用的纹理取样器的数量由像素着色器的版本决定。</p>
<ul type=disc>
    <li><span>固定功能像素着色器：<span>MaxTextureBlendStages/MaxSimultaneousTextures个纹理取样器</span></span>
    <li><span>ps.1.1到ps.1.3: 4个纹理取样器。</span>
    <li><span>ps.1.4：6个纹理取样器。</span>
    <li><span>ps.2.0：16个纹理取样器。</span>
    <li><span>支持<span>Microsoft DirectX&#174; 9.0位移贴图的设备将支持一个额外的取样器（D3DDMAPSAMPLER），它在tesselator对位移贴图进行取样。</span></span> </li>
</ul>
<p>有关更多纹理混合的信息，请参阅<u>纹理混合</u>。</p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p><a name=状态块><strong><span>状态块</span></strong></a></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p><span>一个状态块是一组设备状态、渲染状态、光照和材质的参数、变换状态、纹理层状态和当前纹理信息。状态块是设备当前状态的一个记录，或者是被显式地记录下来的。可以通过单独的一次调用把记录应用于设备。渲染设备可以优化设备状态块以加速应用程序所要求的一般顺序的状态改变。另外，设备状态块也可以使改变设备状态更为方便。</span></p>
<p><span>C++应用程序可以</span><span>在</span><span>调用<u><span>IDirect3DDevice9::EndStateBlock</span></u>方法结束记录一个状态块，以及调用<u><span>IDirect3DDevice9::CreateStateBlock</span></u>方法保存一组预定义的设备状态数据时，得到一个状态块句柄。</span></p>
<p><span>更多信息包含在以下主题中。</span></p>
<p><strong><span>概述</span></strong></p>
<p><u><span><a href="http://www.gesoftfactory.com/developer/States.htm#创建预定义的状态块">创建预定义的状态块</a></span></u></p>
<p><u><span><a href="http://www.gesoftfactory.com/developer/States.htm#记录状态块">记录状态块</a></span></u></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p><a name=创建预定义的状态块><strong><span>创建预定义的状态块</span></strong></a></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p><u><span>IDirect3DDevice9::CreateStateBlock</span></u><span>方法创建一个新的状态块，它包含全部的设备状态或是仅与顶点或像素处理相关的设备状态。<strong><span>IDirect3DDevice9::CreateStateBlock</span></strong>方法接收两个参数，第一个参数代表要在新的状态块中记录的状态信息的类型，第二个参数是返回句柄的地址，用于接收当调用成功时返回的有效状态块的句柄。</span></p>
<p><span>第一个参数为<u><span>D3DSTATEBLOCKTYPE</span></u>枚举类型，可以从以下三种选择：</span></p>
<ul type=disc>
    <li><span>保存顶点和像素状态。</span>
    <li><span>只保存顶点状态。</span>
    <li><span>只保存像素状态。</span> </li>
</ul>
<p>要了解可以保存的状态的全部列表，请参阅<u><span>D3DSTATEBLOCKTYPE</span></u>。</p>
<p><span>一定要检查<strong><span>IDirect3DDevice9::CreateStateBlock</span></strong>方法的错误代码，如果该方法失败，很可能是显示模式改变了。为了从此类失败中恢复，应用程序应该重新创建表面，然后重新创建状态块。</span></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p><a name=记录状态块><strong><span>记录状态块</span></strong></a></p>
<div align=center><span>
<hr align=center width="100%" SIZE=1>
</span></div>
<p><u><span>IDirect3DDevice9</span></u><span>接口提供<u><span>IDirect3DDevice9::BeginStateBlock</span></u>方法，在应用程序设置设备状态时，该方法会把它们记录到一个状态块中。该方法使系统开始把设备状态的改变记录到一个状态块中，而不是将它们（新的设备状态）应用于设备。可被记录的状态列表请参阅<u><span>IDirect3DDevice9::BeginStateBlock</span></u>。</span></p>
<p><span>当应用程序完成对状态块的记录后，应该调用<u><span>IDirect3DDevice9::EndStateBlock</span></u>方法通知系统以结束记录。该方法返回一个状态块的句柄，应用程序应该将接收状态块句柄的变量的地址作为<em><span>pToken</span></em>参数传入。应用程序可以根据需要用这个句柄应用该状态块，记录新的状态数据到状态块中，以及在不再需要该状态块时将之删除。</span></p>
<p><span>一定要检查<strong><span>IDirect3DDevice9::EndStateBlock</span></strong>方法的错误代码，如果该方法失败，很可能是因为显示模式改变了。应该合理地设计应用程序，使之能够从此类失败中恢复，只要重新创建表面并再次记录状态块就可以达到这个目的。</span></p>
<img src ="http://www.cppblog.com/liangairan/aggbug/70256.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2008-12-24 17:37 <a href="http://www.cppblog.com/liangairan/articles/70256.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>看不明白的HLSL光照代码</title><link>http://www.cppblog.com/liangairan/articles/69648.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Wed, 17 Dec 2008 06:32:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/69648.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/69648.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/69648.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/69648.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/69648.html</trackback:ping><description><![CDATA[<p>一般来说normalMap都是从高模获得,用来使低模渲染获得更好的颜色效果</p>
<p>帖上一段凹凸贴图的hlsl,不一定要用shader实现,看懂原理就行了</p>
<p>struct&nbsp;VS_INPUT<br>{<br>float4&nbsp;position&nbsp;&nbsp;:&nbsp;POSITION;<br>float3&nbsp;normal&nbsp;&nbsp;&nbsp;&nbsp;:&nbsp;NORMAL;<br>float3&nbsp;tangent&nbsp;&nbsp;&nbsp;:&nbsp;TANGENT;<br>float2&nbsp;texcoord0&nbsp;:&nbsp;TEXCOORD0;<br>};</p>
<p>struct&nbsp;VS_OUTPUT<br>{<br>float4&nbsp;position&nbsp;:&nbsp;POSITION;<br>float3&nbsp;color0&nbsp;&nbsp;&nbsp;:&nbsp;COLOR0;<br>float2&nbsp;texcoord0&nbsp;:&nbsp;TEXCOORD0;<br>};</p>
<p>VS_OUTPUT&nbsp;VS_PerpixelDiffuseLighting(&nbsp;VS_INPUT&nbsp;In&nbsp;)<br>{<br>VS_OUTPUT&nbsp;Out;<br>Out.position&nbsp;=&nbsp;mul(&nbsp;In.position,&nbsp;matWorldViewProj&nbsp;);<br>//Out.position.z&nbsp;-=&nbsp;0.01;<br>//把光照方向转到世界坐标中,这样可以避免把normal图中得到的每点的normal值<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//都转到屏幕坐标上来,节约运算</p>
<p>float3&nbsp;worldNormal&nbsp;=&nbsp;normalize(mul(&nbsp;In.normal,&nbsp;(float3x3)matWorld&nbsp;));<br>float3&nbsp;worldTangent&nbsp;=&nbsp;normalize(mul(&nbsp;In.tangent,&nbsp;(float3x3)matWorld&nbsp;));&nbsp;<br>float3&nbsp;worldBinormal&nbsp;=&nbsp;cross(&nbsp;worldNormal,&nbsp;worldTangent&nbsp;);<br><br>float3&nbsp;L&nbsp;=&nbsp;normalize(&nbsp;lightDir.xyz&nbsp;);<br><br>float3x3&nbsp;tangentBase&nbsp;=&nbsp;{&nbsp;worldTangent,&nbsp;worldBinormal,&nbsp;worldNormal&nbsp;};<br><br>float3&nbsp;LInTangent&nbsp;=&nbsp;mul(&nbsp;tangentBase,&nbsp;-L&nbsp;);<br>//颜色值只能是0-1.normal可以是-1-1,这里要进行一次转化<br>Out.color0&nbsp;=&nbsp;float4(&nbsp;0.5&nbsp;*&nbsp;LInTangent&nbsp;+&nbsp;0.5,&nbsp;1&nbsp;);<br>Out.texcoord0&nbsp;=&nbsp;In.texcoord0;<br><br><br>return&nbsp;Out;<br>}</p>
<p>float4&nbsp;PS_PerpixelDiffuseLighting(&nbsp;VS_OUTPUT&nbsp;In&nbsp;)&nbsp;:&nbsp;COLOR<br>{<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//取得normal图上每点的normal<br>float3&nbsp;pixelNormal&nbsp;=&nbsp;tex2D(&nbsp;sNormal,&nbsp;In.texcoord0&nbsp;);<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//&nbsp;2*(pixelNormal-0.5)&nbsp;是把0-1转化为-1到1<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;//这一步就是normalmap的使用了,把光线方向与normal&nbsp;dot一下<br>float&nbsp;diffuse&nbsp;=&nbsp;dot(&nbsp;2*(In.color0-0.5),&nbsp;2*(pixelNormal-0.5)&nbsp;);<br><br>float3&nbsp;texTerm&nbsp;=&nbsp;tex2D(&nbsp;sDiffuse,&nbsp;In.texcoord0&nbsp;);<br>float4&nbsp;finalColor&nbsp;=&nbsp;{&nbsp;texTerm*diffuse,&nbsp;1&nbsp;};<br>return&nbsp;finalColor;<br>}</p>
<p>这样作的最终目标是让低模能够获得高模一样的光照效果&nbsp;</p>
<img src ="http://www.cppblog.com/liangairan/aggbug/69648.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2008-12-17 14:32 <a href="http://www.cppblog.com/liangairan/articles/69648.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>FreeType2字体转换到D3D纹理 </title><link>http://www.cppblog.com/liangairan/articles/69603.html</link><dc:creator>魔鬼螳螂</dc:creator><author>魔鬼螳螂</author><pubDate>Tue, 16 Dec 2008 13:50:00 GMT</pubDate><guid>http://www.cppblog.com/liangairan/articles/69603.html</guid><wfw:comment>http://www.cppblog.com/liangairan/comments/69603.html</wfw:comment><comments>http://www.cppblog.com/liangairan/articles/69603.html#Feedback</comments><slash:comments>1</slash:comments><wfw:commentRss>http://www.cppblog.com/liangairan/comments/commentRss/69603.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/liangairan/services/trackbacks/69603.html</trackback:ping><description><![CDATA[&nbsp;&nbsp;&nbsp;&nbsp; 摘要: Font.h#ifndef&nbsp;__Font_H__#define&nbsp;__Font_H__#include&nbsp;&lt;vector&gt;#include&nbsp;&lt;D3D9.h&gt;typedef&nbsp;unsigned&nbsp;char&nbsp;uint8;template&lt;typename&nbsp;T&gt;&nbsp;struct&nbs...&nbsp;&nbsp;<a href='http://www.cppblog.com/liangairan/articles/69603.html'>阅读全文</a><img src ="http://www.cppblog.com/liangairan/aggbug/69603.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/liangairan/" target="_blank">魔鬼螳螂</a> 2008-12-16 21:50 <a href="http://www.cppblog.com/liangairan/articles/69603.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>