﻿<?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++博客-Fly 3D</title><link>http://www.cppblog.com/tuya1988/</link><description>游戏编程学习博客</description><language>zh-cn</language><lastBuildDate>Tue, 14 Apr 2026 23:09:03 GMT</lastBuildDate><pubDate>Tue, 14 Apr 2026 23:09:03 GMT</pubDate><ttl>60</ttl><item><title>关于纹理</title><link>http://www.cppblog.com/tuya1988/archive/2009/12/26/104113.html</link><dc:creator>陈翔飞</dc:creator><author>陈翔飞</author><pubDate>Sat, 26 Dec 2009 01:47:00 GMT</pubDate><guid>http://www.cppblog.com/tuya1988/archive/2009/12/26/104113.html</guid><wfw:comment>http://www.cppblog.com/tuya1988/comments/104113.html</wfw:comment><comments>http://www.cppblog.com/tuya1988/archive/2009/12/26/104113.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/tuya1988/comments/commentRss/104113.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/tuya1988/services/trackbacks/104113.html</trackback:ping><description><![CDATA[<p>今天看Normal Mapping,翻出来一篇以前收集的文章。发出来共享下。</p>
<p>&nbsp;首先我想说，对于凹凸贴图在计算机图形领域中的研究，最早开始于70年代末，至今已经有接近30年历史了。NormalMap只是一种目前很流行的凹凸贴图技术，而这里将会介绍一些目前游戏和在XBOX360和PlayStation3这种新世代主机上将会运用的凹凸贴图技术。</p>
<p>BumpMapping</p>
<p>做过CG的朋友一定比FXCarl还要更早的认识BumpMap。这种贴图是一种灰度图，用表面上灰度的变化来描述目标表面的凹凸，因此这种贴图是黑白的，如果节省空间的画，甚至可以把贴图的Alpha通道征用来用作Bump。值得注意的是，这种贴图表面上存储的东西是高度域－－即每个点和原始表面的高度差，记住，每个点的颜色不是色彩，是高度，一个数值！因此，对这个贴图做任何的操作都会影响到这个物体3D的外观质感。不能凭感觉用事。</p>
<p>在游戏中，所使用的算法确切的说应该叫做fake bump mapping ，假凹凸贴图。因为在游戏中BumpMap并没有改变物体的表面而只是影响光照的结果，欺骗眼睛而已。最简单的做法是，直接把BumpMap叠加在已经渲染好的表面上，造成亮度上的扰动，从而让人以为是凹凸的－－这个很容易理解，把一面白色的墙面有技巧的部分划成灰色就会变成蚀痕，这些诸位会比小的更擅长。而计算复杂度是基本加减法。这个所谓的 FakeBumpMapping 从Geforce2就开始硬件支持，但是从来没有大范围的应用过。</p>
<p>不过有趣的是，BumpMap这个东西却从未过时，在后来的渲染算法中，其储存表面高度域的特性仍然发挥着巨大的作用。我们后文再提</p>
<p><span id=more-20></span></p>
<p>NormalMapping</p>
<p>NormalMapping在游戏领域中的实践是一个非常值得记住的时期－－Geforce3上市，GPU概念出现，硬件可编程流水线的出现（Shaders），NormalMapping是一种凹凸贴图技术，它的另外一个名字叫做Dot3 bump mapping。</p>
<p>用于实现它的控制纹理是一张叫做NormalMap的纹理，也是目前大家在讨论如何之作的那种。我们先说说这张叫做NormalMap的图。这张图中存储的东西是每个原始表面法线的迭代，说起来有点复杂，但是不难理解。举例说我们的说面，一般在游戏的3D模型上，表面法线就像是一根站立于桌面的钢笔，垂直向上。而NormalMap中存储的东西就是我们这支表示表面法线方向的钢笔所&#8220;应该&#8221;指向的方向－－比如说朝左边倾斜15度。</p>
<p>NormalMap有两种主要形式，一种叫做世界空间的NormalMap，一种叫做切空间的NormalMap。第一种在游戏中没有实用价值，我们说第二种，也就是大家最常见的一种。</p>
<p>那么，为什么我们看到的NormalMap会有这么奇怪的颜色呢？其实NormalMap和BumpMap一样，即它显示出来的颜色和它所起的作用是没有直接联系的。大家一定对空间坐标的概念非常熟悉了。在NormalMap的定义中，有一个事先的约定，这个约定就是－－原本表面的垂直方向，我们称为Z轴；而表面的UV坐标两个方向，分别对应X轴和Y轴。（确切的说，应该是称作切线和负法线，但是这两个东西和大家熟悉的UV坐标刚好重叠，所以就用大家更习惯的说法了）然后我们知道如果我们在XYZ轴上各取一个点，这个点的取值位置在-1到1之间，那么我们就可以得到一个指向任何方向的法线方向（不用多解释，大家知道法线是一个向量，向量有方向和长度两个概念，但是对法线来说，长度是不需要的）。但是，请大家注意，我们在描述色彩的时候，RGB三个通道的取值范围都是从零开始的。可是当我们尝试把一个任意的法线保存在一张纹理中的时候，会面临取负值的问题。因此我们要把法线做压缩。方法很简单，把XYZ每个轴上的法线投影长度进行N＋1/2的运算。这样就把所有的法线压缩到了0和1的范围里。然后我们把XYZ的方向分别存储在RGB三个通道中。似乎我们还没有说到关于为什么NormalMap会是蓝兮兮的原因是吧。那么现在就是公布结果的时候了！首先，我们知道如果在一个物体表面，法线垂直向上，那么它的XYZ坐标是多少？是0,0,1对不对?然后我们把这个数字按照我们前面所说的压缩方法进行压缩，每个数字加1然后再除以2，那么我们得到的是0.5,0.5,1对不对？好我们把它代入到RGB中，那么我们会得到128,128,255对不对？好了，试试看在调色板里的颜色吧！</p>
<p>P.S.现在FXCarl和你猜个谜，看看FXCarl说的对不对。现在我们在NormalMap上看见一个颜色，这个颜色是219,128,219。那么这个表面的法线方向是垂直向右偏45度。大家用MAX做一个NormalMap看看FXCarl说的对不对？</p>
<p>如果你还没有理解NormalMap的意思，或者说你有兴趣再深入了解一些，那么FXCarl再和你说的深入一些。不知道大家对于切空间的理解是什么？我们来个实验，找三支笔。然后其中两只笔在桌面放成互相成90度，笔尾接笔尾。最后我们把第三支笔，笔尖向上，笔尾和那两只桌面上的笔的笔尾叠在一个点上。注意看我们的三支笔！这三支笔就是这张桌面上这个点的切空间坐标了！大家一定想到了原来我们的NormalMap中存储的表面法线方向原来就是一个切空间向量啊，恩没错，就是切空间向量。但是似乎看起来切空间没什么作用是不是？呵呵，我们不妨把桌面换成一个篮球。记住，保持三支笔的互相关系，然后用三支笔并在一起的笔尾去接触篮球的表面。呵呵，发现了没有？切空间的优势在于，在任意表面上，切空间中的坐标都是有效的！也就是说始用切空间中的数据就可以做到和3D模型的复杂度无关！你可以用在任意的表面，甚至这个表面一直在动也不会影响到NormalMap发挥作用，你说这个切空间是不是很有用呢？</p>
<p>让我们回到开头，大家就会发现，如果使用世界空间的NormalMap会有什么样的结果呢？嘿嘿那样会造成一个很尴尬的结果，比如说我们做了一个人物身上的NormalMap，可是我们的场景中有两个一样的人物，但是他们的姿势和面对的角度都不一样。那么&#8230;&#8230;My God ～肯定有一个人物的NormalMap是没法适用的！而用切空间的NormalMap就没有问题了。恩，不过这个大家可以放心，MAX或者Maya做出来的NormalMap都是切空间的NormalMap，证明的方法很简单&#8230;&#8230;看看这张贴图是不是主要由蓝色构成的&#8230;&#8230;</p>
<p>OK，下面是重头戏，告诉大家NormalMap是如何发生作用的。</p>
<p>使用NormalMap的先决条件－－逐像素著色。先来说一下传统著色，传统游戏使用的是一个Phong光照模型的简化版，甚至有游戏使用Ground模型。这两种算法的方式都是只对物体3D模型的顶点计算光照，而3D表面上的大面积区域则使用差值填充。逐像素著色是到了Shaders出现之后才有的，因此NormalMapping也是一个Shaders必须的算法。计算一个物体表面漫反射光照的公式是很简单的NdotL－－什么是NdotL，就是物体表面的法线和光照方向的点积。点积是一个线性代数的问题，美术朋友们可以不用深究，写成程序也很容易：Diffuse = saturate(Mul(Normal,Light));。想要简单的理解就是－－光线的方向矢量在法线矢量上的投影，然后这个投影的结果变成黑白中间的一个值。我们同样举个简单的例子，用两支笔放在桌面上，然后一支笔不动，令一支笔笔尾和第一支笔的笔尾相连，不动，然后以共同的笔尾做为圆心，移动笔。这时如果我们从一支笔尖往另外一支笔的笔杆上垂直拉一条线（一条垂线）就会看到这时移动后的一支笔在原本的笔杆所投影的长度（就是一支笔的笔尖连垂线到另一支笔的笔杆上的位置，这个位置沿着笔杆到共同笔尾的长度）会越来越短，当两支笔垂直的时候，投影的结果就是零－－没有光照贡献了。这个容易理解，当光线的方向和一个表面绝对平行的时候，这个表面就会再也接受不到光线了。现在我们引入NormalMap。这时我们的光照计算和以往有点不同，我们把表面的法线用NormalMap中存储的法线来替代。这样当我们在计算表面光照情况的时候，就会因为法线不断的变化而产生比原来丰富的多的明暗变化。</p>
<p>至于为什么会感觉出凹凸来这个就是人的眼睛自己骗自己了&#8230;&#8230;其实那里本没有凹凸的，但是我们人眼睛太多管闲事了。就像Windows的按钮哪个纯平面的东西我们还以为是凸出来的呢。</p>
<p>NormalMap看来可以增加细节，但是它的缺点也很明显。不过在说缺点之前，要提前说一句－－NormalMap带来的优势是远远大于它的缺点的。因此仍然是个极好的东西，不要对它有偏见，特别是在我们后面介绍的更牛的技术前面，千万不要。最大的也是最明显的缺点应该就是它的视角问题。因为NormalMap只是改变的表面上的光照结果，并没有改变表面上的形状。因此，表面上看来，似乎只要是不接近水平，NormalMap就不会有视角问题。其实不然，NormalMap因为不能实现自身内部的遮挡，因此不能表现平面上凹凸起伏比较大的场合。比如说我们一个桌面上突出一块，然后在突出的这块东西边上放一支牙签。如果用NormalMap表现，会发现。根据经验，这个凸起会很轻易的挡住我们的视线，让我们看不见那支牙签。可是NormalMap却不会这么做。因此我们一直能看见障碍物背后的东西，这一点是个问题－－也就是说只有在垂直于平面的时候NormalMap才会发挥最好的作用。这样一来，NormalMap只能用在大家对遮挡关系不敏感的场合，比如场景等，不是不能用于人物，而是用NormalMap的人物不太经得起特写，放大了，角度刁钻了都容易穿帮。</p>
<p>虽然NormalMap有个不能平视的巨大问题，但是依然是好处远大于小障碍，因此还是非常值得推广的。后面的几种新兴算法其实都是由NormalMapping发展起来的，因此做为基础的东西，也还是最有理解价值的。</p>
<p>P.S.关于NormalMap的一点秘籍。注意理解&#8230;&#8230;NormalMap其实并不是从低模的表面凸出高模的细节的，而是把高模中比最高点的位置低的地方凹进去的！因此低模要比高模大一点点才会很准。大家可以想像成我们是用一个比高模稍微大一点点尺寸的低模石膏模型来把高模雕刻出来的。</p>
<p>P.S.2.关于NormalMap的做法，其实早期发明NormalMap的时候还没有MAX这种这么方便生成NormalMap的方法，NormalMap都是从BumpMap计算得到的，因此其实通过很简单的算法就可以从BumpMap算出NormalMap的，甚至可以On The Fly（就是让游戏引擎直接读BumpMap然后转换成NormalMap）。因此对于一些建起模来效率很低，但是又能明显增加表面细节的东西，例如水泥表面的颗粒，用画Bump的方式来做是个更好的主意，然后交给技术美工去搞定好了－－当然你会用Z－Bursh那就当我什么都没说了，呵呵。说来FXCarl估计MAX生成法线图的方式也是比较高低模上每个点的高度偏移，然后生成每个UV图素上的高度差来得到一个BumpMap，然后再从BumpMap变成NormalMap。</p>
<p>Parallax mapping 视差贴图 （因为后面的算法都是基于NormalMap的应用，可能看上去没有NormalMapping那么长了，但内容肯定一样精彩的！）</p>
<p>视差贴图是一种NormalMapping算法的增强算法，其本质上和NormalMapping没有区别。优势是只需要增加3个HLSL语句和一个控制纹理通道（只需要几个GPU指令，代价小到可以忽略）就可以显著的增加物体表面的深度感。但是NormalMap中出现的问题，Parallax mapping基本上都有－－特别是视角接近平行的时候，凹凸感消失的问题，并没有明显改善－－其实这个使用NormalMap带来的问题就像是液晶屏的可视角度问题一样令人挥之不去。或者按照FXCarl个人的说法－－Parallax mapping才是真正具有实用价值的NormalMapping。</p>
<p>目前实践证明，这种技术非常适合XBOX360和PS3这样的新世代游戏主机（都上市一年了还用次世代&#8230;&#8230;我真受不了现在有些人）。例如360游戏SEGA死刑犯就是使用的和PC游戏FEAR一样的Monolith引擎－－使用Parallax mapping。</p>
<p>Parallax mapping使用的还是单张的控制纹理。一张NormalMap。如果我们用AcdSee来看这张NormalMap，我们会发觉似乎和NormalMapping用的控制纹理是一样的。而如果我们打开这张NormalMap的Alpha通道，就会发现其中的玄机所在。原来Alpha通道里存储的是对应这张NormalMap的BumpMap！（就是HeightMap，就是用饱和度记录表面高度）</p>
<p>现在插入一点理论课程。大家留心读上面的文字，会看见一个控制纹理的词汇。这个词汇是这里要重点解释的。因为理解控制纹理，在成为新世代美工的需求中是相当重要的。按照大家这么多年美术做下来的经验，对于图素(Texel,纹理上的一点)的理解肯定是RBG3个色彩带一个表示透明度的Alpha通道。但是在我们的渲染器和程序员的眼里，它可不是我们美术朋友们看见的东西。他们看见的是一个4通道的矢量（其实可以理解成四个数的组合）。这4个数字的取值范围分别是0到255。通过这个空间，其实可以用来做更多别的事情－－最常用的就是记录表面的物理细节。至于为什么要用控制纹理呢？FXCarl前两天听一个朋友有这么个说法：我觉得NormalMap的效果也没什么，直接画也画的出来的。其实这个说法一点都没错，但是要知道，这个想法是过时的。因为NormalMap并非用于著色，而是用于更真实的生成色彩。重所周知，用画的方式，做静帧固然可以做到无限好。可是动起来怎么办？如何才能保证在不同的光照关系下依然保证最终著色的结果正确？唯一的做法，就是每帧重画一遍。如何才能做到最有效的重画？那就要把重画的参考告诉我们的渲染器，让它来帮你做一些简单的工作，这就是控制纹理的作用－－把你想要实时改变的东西告诉渲染器。其实控制纹理的范围很大，除了NormalMap还有很多，比如说Nvidia的DEMO曾经用纹理存储物体表面在阳光下的色彩变化规律。把艺术家想要实时改变的东西压缩在纹理中告诉渲染器是一件相当有挑战性的工作，当然也会获得更令人赞叹的画面。请接受控制纹理，那是让艺术家把一个瞬间的精确著色变成一个普遍适用的著色的利器！</p>
<p>Parallax mapping是如何达到增加NormalMap的效果的呢。我们要从NormalMap的特性说起。我们假设在NormalMap表面制作一个凸起。然后我们转转角度看看。我们会发现，其实这个凸起的背对我们视线的面～并不会因为我们视角的逐渐放平而消失－－这显然是不正确的，要知道背后的东西应该是看不见的才对。因此Parallax mapping就是来缓解这个问题的，具体的代码这里不提。我来试着白话解释一下原理。其实为了不让我们看见&#8220;不该看的东西&#8221;应该试着挪动纹理坐标&#8230;&#8230;把那个不该给玩家看见的图素（Texel）跳过去。也就是说根据高度图提供的数据，把那个位置较低那个纹理的后面的纹理向前拉。相当于在图素采样的时候刻意的把那个图素跳过去。这样那个不该被玩家看见的像素就会因为图素的消失而不见了－－很明显，这个算法是不太站得住脚的，虽然计算的时候会参考玩家视线的角度。但仍然是一种来自于经验的估算。值得欣慰的是，对于本身NormalMap所需要表现的微小细节来说，这样的改进已经看上去不错。因此开始有大量的游戏决定采用。特别是它的优点是所消耗的代价极为有限，而需要增加的工作量只是让美工把高度图保存到Alpha通道里而已。很划算。</p>
<p>但是对于技术研究者来说，这样的表现显然还是不够令人满意的。因此，顺着视差贴图的思路向下发展，借助ShaderModel3.0的出现。出现了一个真正从物理上改变物体表面的算法，这就是我们下一篇文章需要介绍的Displacement Mapping&nbsp;</p>
<p>&nbsp;</p>
<p>Displacement mapping 位移贴图</p>
<p>和前面说的几种方式不同，DisplacementMapping是一种真正改变物体表面的方式。通过一种称为micropolygons（微多边形）tessellate（镶嵌）的技巧来实现真正的改变物体表面的细节。</p>
<p>具体流程是这样的。首先，根据屏幕的分辨率，在模型的可见面上镶嵌和最终象素尺寸相同的微多边形。这个过程叫做镶嵌。然后读取一张Bump贴图。根据表面的灰度确定高度。然后根据镶嵌所得到的多边形，沿着原先的表面法线方向移动微多边形。接着再为新的多边形确定好新的法线方向。此时，物体的表面确实已经真的增加出了细节。</p>
<p>其实这种技巧，我们在使用ZBrush的时候就可以看见了。大家用过Zbrush的时候会知道，在表面刷过的细节，只有在画面静止下来之后才会越来越清晰。而微多边形镶嵌起到的就是类似的作用。只增强面对屏幕的多边形的表面粗糙细节，而不是整个模型。因此性能代价并不会像直接上高模那么大。相比来说位移贴图在效果上是没有任何瑕疵的，但是也未必没有缺点。</p>
<p>首先就是，对硬件的要求很高，必须支持ShaderMode3.0才可以，因为只有支持SM3才可以在顶点阶段进行纹理操作。同时镶嵌对于性能的消耗也不小。不过其实就对于GPU的压力而言，反而似乎要更合理一些（因为对顶点的运算要求提高，对象素级别的运算要求反而没有影响）想必在将来的DX10统一渲染构架中会更有价值。</p>
<p>和我们介绍的所有凹凸贴图技术相比，位移贴图是唯一真正改变多边形表面几何形状的方法。相比之后将要介绍的切空间光线追踪算法，这种算法的性能消耗虽然并不占优，但其实要更为合理。给予画面更多特效的机会，同时更有趣的是，其实他和其他基于象素着色的凹凸贴图并没有什么冲突。其实这种位移贴图在新世代主机的游戏中大家都有可能见到。只是可能不是大家想得到的地方。</p>
<p>它可以用来实时生成大面积的户外地形！这是其他任何凹凸贴图方式所不能比拟的！</p>
<p>&nbsp;</p>
<p>ReliefMapping 和 ParallaxOcclusionMapping 和 ConeMapping 浮雕纹理贴图和视差阻塞贴图和圆锥跟踪贴图</p>
<p>三种利用切空间光线追踪先进算法的技术，三种方法分别是光线跟踪，光束跟踪和圆锥跟踪算法。当然可以说后面的都是第一种的改进。这些都是面向未来的技术，很有前途。ReliefMapping甚至现在可以实现扣环这样的复杂几何表面，并且可以处理不能平视的问</p>
<img src ="http://www.cppblog.com/tuya1988/aggbug/104113.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/tuya1988/" target="_blank">陈翔飞</a> 2009-12-26 09:47 <a href="http://www.cppblog.com/tuya1988/archive/2009/12/26/104113.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>2D变换矩阵</title><link>http://www.cppblog.com/tuya1988/archive/2009/12/23/103749.html</link><dc:creator>陈翔飞</dc:creator><author>陈翔飞</author><pubDate>Wed, 23 Dec 2009 00:29:00 GMT</pubDate><guid>http://www.cppblog.com/tuya1988/archive/2009/12/23/103749.html</guid><wfw:comment>http://www.cppblog.com/tuya1988/comments/103749.html</wfw:comment><comments>http://www.cppblog.com/tuya1988/archive/2009/12/23/103749.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/tuya1988/comments/commentRss/103749.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/tuya1988/services/trackbacks/103749.html</trackback:ping><description><![CDATA[<p>平时开发程序，免不了要对图像做各种变换处理。有的时候变换可能比较复杂，比如平移之后又旋转，旋转之后又平移，又缩放。</p>
<p>直接用公式计算，不但复杂，而且效率低下。这时可以借助变换矩阵和矩阵乘法，将多个变换合成一个。 最后只要用一个矩阵对每个点做一次处理就可以得到想要的结果。</p>
<p>&nbsp;另外，矩阵乘法一般有硬件支持，比如3D 图形加速卡，处理3D变换中的大量矩阵运算，比普通CPU 要快上1000倍。</p>
<p>下面是3类基本的2D图形变换。&nbsp;</p>
<p><strong>平移：</strong></p>
<p>设某点向x方向移动 dx, y方向移动 dy ,[x,y]为变换前坐标， [X,Y]为变换后坐标。</p>
<p>则 X = x+dx; &nbsp;Y = y+dy;</p>
<p>以矩阵表示：</p>
<p>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;1 &nbsp; &nbsp;0 &nbsp; &nbsp;0</p>
<p>[X, Y, 1] = [x, y, 1][ 0 &nbsp; &nbsp;1 &nbsp; &nbsp;0 &nbsp;] ;&nbsp;</p>
<p>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;dx &nbsp;dy &nbsp; 1</p>
<p>&nbsp;&nbsp;1 &nbsp; &nbsp;0 &nbsp; &nbsp;0</p>
<p>&nbsp;&nbsp;0 &nbsp; &nbsp;1 &nbsp; &nbsp;0 &nbsp; 即平移变换矩阵。&nbsp;</p>
<p>&nbsp;&nbsp;dx &nbsp;dy &nbsp; 1&nbsp;</p>
<p>&nbsp;</p>
<p><strong>&nbsp;旋转</strong>：</p>
<p>&nbsp;旋转相比平移稍稍复杂：</p>
<p>&nbsp;设某点与原点连线和X轴夹角为b度，以原点为圆心，逆时针转过a度 &nbsp;, 原点与该点连线长度为R, [x,y]为变换前坐标， [X,Y]为变换后坐标。</p>
<p>&nbsp;&nbsp;x = Rcos(b) ; y = Rsin(b);</p>
<p>&nbsp;&nbsp;X = Rcos(a+b) =&nbsp;Rcosacosb - Rsinasinb = xcosa - ysina; (合角公式)</p>
<p>&nbsp;&nbsp;Y = Rsin(a+b) =&nbsp;Rsinacosb + Rcosasinb = xsina + ycosa&nbsp;;</p>
<p><br></p>
<p>&nbsp;&nbsp;用矩阵表示：</p>
<p>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;cosa &nbsp; sina &nbsp;0</p>
<p>&nbsp;[X, Y, 1] = [x, y, 1][-sina &nbsp;cosa &nbsp;0 &nbsp;]&nbsp;</p>
<p>&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; 0 &nbsp; &nbsp; &nbsp; &nbsp;0 &nbsp; &nbsp; 1</p>
<p>&nbsp;&nbsp;cosa &nbsp; sina &nbsp;0</p>
<p>&nbsp;-sina &nbsp;cosa &nbsp;0 &nbsp;为旋转变换矩阵。</p>
<p>&nbsp;&nbsp; 0 &nbsp; &nbsp; &nbsp; 0 &nbsp; &nbsp; 1&nbsp;</p>
<p>&nbsp;</p>
<p><strong>&nbsp;缩放</strong></p>
<p>&nbsp;设某点坐标，在x轴方向扩大 sx倍，y轴方向扩大 sy倍，[x,y]为变换前坐标， [X,Y]为变换后坐标。</p>
<p>&nbsp;X = sx*x; Y = sy*y;</p>
<p>则用矩阵表示：</p>
<p>&nbsp;&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; sx &nbsp; &nbsp;0 &nbsp; &nbsp;0</p>
<p style="MARGIN: 5px auto; TEXT-INDENT: 0px">[X, Y, 1] = [x, y, 1][ 0 &nbsp; &nbsp;sy &nbsp; &nbsp;0 &nbsp;] ;&nbsp;</p>
<p style="MARGIN: 5px auto; TEXT-INDENT: 0px">&nbsp;&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp;0 &nbsp; &nbsp; 0 &nbsp; &nbsp; 1</p>
<p>&nbsp;sx &nbsp; &nbsp;0 &nbsp; &nbsp;0</p>
<p>&nbsp;0 &nbsp; &nbsp;sy &nbsp; &nbsp;0 &nbsp;即为缩放矩阵。&nbsp;</p>
<p>&nbsp;0 &nbsp; &nbsp; 0 &nbsp; &nbsp; 1</p>
<p>&nbsp;</p>
<p>&nbsp;2D基本的模型视图变换，就只有上面这3种，所有的复杂2D模型视图变换，都可以分解成上述3个。</p>
<p>比如某个变换，先经过平移，对应平移矩阵A， 再旋转, 对应旋转矩阵B，再经过缩放，对应缩放矩阵C.</p>
<p>则最终变换矩阵 T = ABC. 即3个矩阵<strong>按变换先后顺序</strong>依次相乘(矩阵乘法不满足交换律，因此先后顺序一定要讲究)。</p>
<img src ="http://www.cppblog.com/tuya1988/aggbug/103749.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/tuya1988/" target="_blank">陈翔飞</a> 2009-12-23 08:29 <a href="http://www.cppblog.com/tuya1988/archive/2009/12/23/103749.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Unraveling Beziér Splines (转载)</title><link>http://www.cppblog.com/tuya1988/archive/2009/12/19/103514.html</link><dc:creator>陈翔飞</dc:creator><author>陈翔飞</author><pubDate>Sat, 19 Dec 2009 03:01:00 GMT</pubDate><guid>http://www.cppblog.com/tuya1988/archive/2009/12/19/103514.html</guid><wfw:comment>http://www.cppblog.com/tuya1988/comments/103514.html</wfw:comment><comments>http://www.cppblog.com/tuya1988/archive/2009/12/19/103514.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.cppblog.com/tuya1988/comments/commentRss/103514.html</wfw:commentRss><trackback:ping>http://www.cppblog.com/tuya1988/services/trackbacks/103514.html</trackback:ping><description><![CDATA[<p>Unraveling Bezi&#233;r Splines <br>by Justin Reynen</p>
<p>原文网址：<a href="http://www.gamedev.net/reference/articles/article888.asp">http://www.gamedev.net/reference/articles/article888.asp</a></p>
<p>简介</p>
<p>毫无疑问，您已经听说过这些用于处理曲线和曲面的新技术，二次Bezier曲线，三次Bezier曲线，非均匀有理B样条(NURBS)，并且非常迫切的想要把所有这些东西搞清楚，甚至可能尝试将这些技术中的一种加入到您正在制作的游戏或演示中。</p>
<p>这篇文章是帮您揭开这些谜团的最好的开始。</p>
<p>在本文中，我会向你解释Bezier曲线的数学原理，编写程序画出一条二次Bezier样条（最简单的样条），然后你就可以自己实现任意你所需要次数的Bezier曲线了。</p>
<p>Bezier曲线的数学原理</p>
<p>好了，我们将要作的第一件事是温习一下Bernsteins基本方程。在下面我会给出一个公式，别着急，你看，她很简单！</p>
<p>1 = t + (1 - t)</p>
<p>T为任意值等式都成立，但是我们所关注的T值是在0和1之间。</p>
<p>好，现在我们已经了解Bernsteins基本方程了，下面将要开始生成曲线的工作。我们选择二次Bezier曲线作为切入点，因为它是最容易实现的。。</p>
<p>好了，现在已经确定我们想要一条二次Bezier曲线，我们需要从万能的基本方程派生出我们需要的方程，现在就开始吧！</p>
<p>对于二次Bezier曲线，我们需要将Bernsteins基本方程的两段同时取平方；对于三次Bezier曲线，我们就去立方，以此类推。下面是两边取平方后的Bernsteins基本方程</p>
<p>1^2 = (t + (1 - t))^2</p>
<p>&nbsp;</p>
<p>1 = t^2 + 2*t*(1-t) + (1-t)*(1-t)</p>
<p>可能你注意到有很多项我没有展开，因为这样更有助于理解和编码。</p>
<p>好的，现在我们获得了从基本方程衍生出来的3个函数。你可能会问他们在哪儿，就在你的面前！我们的函数就是等式右侧的那3个多项式。我们将这3个函数取名为Bx(t)。</p>
<p>#define B1(t)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (t*t)</p>
<p>#define B2(t)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; (2*t*(1-t))</p>
<p>#define B3(t)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ((1-t)*(1-t))</p>
<p>你应该注意到，对于二次Bezier曲线有三个函数，对于三次Bezier曲线有四个参数，以此类推。在本文的演示程序中我已经计算了三次和四次曲线的方程，请查看"Functions.txt"</p>
<p>好了，现在我们已经搞清楚Bezier曲线的简单数学意义，接下来让我们谈一下编码吧。</p>
<p>编码实现Bezier曲线</p>
<p>好的，我们已经准备好了基本函数，现在是充分利用他们的时候了。</p>
<p>我认为现在你应该意识到与这3个函数相对应，有3个控制点，我们将这些点起名叫Cx。你可以向这样来存储这些点：</p>
<p>typedef struct sCPoint</p>
<p>{</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int x;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; int y;</p>
<p>}&nbsp; C_POINT;</p>
<p>注：我们将要生成的是2DBezier曲线，如果你想要生成3DBezier曲线的话可以在结构中加入一个z元素。</p>
<p>C_POINT controlP[3];</p>
<p>好了，现在我们将要接触在屏幕上绘制Bezier曲线的真正代码了。目前，我们的曲线只存在于想象之中，而且我们只有数学方程。我们需要做的就是利用数学方程，沿着曲线将点画在屏幕上。</p>
<p>这就给我们带来了最后一个问题&#8220;采样率&#8221;。采样率就是我们在曲线上画点时，每绘制一个点需要沿着曲线移动的距离。采样率是个小数，你可以简单的把它理解为百分比。例如采样率0.5的含义就是：从曲线的一端开始，在这里绘制一个点；然后沿曲线移动50%，然后绘制一个点，然后再移动50%（到达曲线的末端），绘制最后一个点。这样就会在曲线的左端、中间和右端共绘制了3个点。我们需要一个计数器来记录已经绘制的曲线的百分比，然后我们每次对计数器累加，直到计数器到达1为止。定制的采样率越小，我们绘制的点就越多，曲线看起来就越精细。</p>
<p>//Note: it is assumed that each controlP has already been filled out with</p>
<p>//an x and y coordinate.</p>
<p>&nbsp;</p>
<p>double count;&nbsp; //used as our counter</p>
<p>double detailBias; //how many points should we put on our curve.</p>
<p>&nbsp;</p>
<p>double x,y; //used as accumulators to make our code easier to read</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>detailBias = 1 / 50; //we'll put 51 points on out curve (0.02 detail bias)</p>
<p>&nbsp;</p>
<p>do</p>
<p>{</p>
<p>x = controlP[0].x*B1(count) + controlP[1].x*B2(count) + controlP.x[2]*B2(count);</p>
<p>y = controlP[0]*B1.y(count) + controlP[1].y*B2(count) + controlP.y[2]*B2(count);</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PutPixel(x,y,RGB(255,255,255));</p>
<p>&nbsp;</p>
<p>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; count += detailBias;</p>
<p>&nbsp;</p>
<p>}while( count &lt;= 1);</p>
<p>请注意我们是如何获得x和y的值。我们用控制点与相应的基本方程相乘，然后将他们加在一起。我们对x和y坐标进行了这样的操作，如果我们是工作在3维空间，那么我们也要对z坐标进行一样的运算。</p>
<p>用Bezier曲线创建曲面</p>
<p>我现在就要揭开如何绘制Bezier曲面的神秘面纱。这是个很大的课题，完全可以把它单独拿出来写一篇文章，而且它也超出了本文的研究范畴。我将只介绍二次Bezier曲面，并且没有提供演示代码。我将会介绍如何生成曲面上的点，你可以很容易的将其改造成3次或者4次曲面。</p>
<p><br>上面这幅图是最基本的差值曲面，它由2次Bezier曲线组成。它有9个控制点，你可以想象这幅图的x轴沿7、8、9方向，y轴沿7、4、1方向。为了让事情简单，我把采样率定为0.25，这样我们在每条线上都会产生5个点。在整个面上将会生成25个点。我们可以简单的把这些点以行和列分组，这样我们的数据结构就会像这样：</p>
<p>C_POINT points[5][5];</p>
<p>我们要做的工作就是对每一条曲线进行插值，产生新的控制点，然后再由新生成的控制点继续插值，这样就生成了我们需要的曲面。</p>
<p>In Conclusion</p>
<p>我希望这篇文章可以帮助你了解Bezier曲线的用法并且能够将他们应用到你的游戏或者演示当中。在本文结束之前，提出一些关于Bezier曲线的优点和缺点：</p>
<p>Pro's: </p>
<p>在层次细节模型当中，曲面是一个非常好的用于减少绘制多边形数量的工具。你可以很容易的将这些面减少到只有4个多边形（对于2次方程），而且你可以很容易的做到这一点（通过改变采样率）。它也可以很容易的使表面变得平滑。另外，如果你计划添加摄像机移动功能的话，Bezier曲线对于生成平滑的移动路线，动画移动来说是至关重要的。</p>
<p>Con's: </p>
<p>曲面对于大多数引擎来说最主要的缺点就是要花费太长的时间将曲面变成多边形，你在载入时做这些事情也不会有太大改变。但是如果你每一帧都进行计算的话，你可能会注意到它存在巨大的性能问题。这就是大多数引擎开发者对曲面发牢骚的最主要原因。</p>
<p>(happykevins译)</p>
<p>&nbsp; <br></p>
<img src ="http://www.cppblog.com/tuya1988/aggbug/103514.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.cppblog.com/tuya1988/" target="_blank">陈翔飞</a> 2009-12-19 11:01 <a href="http://www.cppblog.com/tuya1988/archive/2009/12/19/103514.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>