Franco.Chou's Space

Franco's Tech Blog

Monthly Archives: 4月 2010

三维场景中worldMatrix,viewMatrix,projectionMatrix详解(篇一)


因为最近工作的需要,要用到HLSL。这样就需要接触到硬件的底层。那么搞清楚一些概念就非常的有必要。

(以下是三个矩阵的基本概念,非初学者可以先跳过)

———————————————————————————————————————

对D3D,OpenGL或者XNA有所接触人就会知道:构建一个三维场景需要用到三个重要的Matrix,它们分别是:WorldMatrix,ViewMatrix,ProjectionMatrix.

它们的作用分别如下:

WorldMatrix:定义世界坐标的矩阵。

worldMatrix = Matrix.CreateScale(0.1f) * Matrix.CreateTranslation(0, 0, 0);

/*从以上代码中我们可以知道,世界坐标就是将我们的“三维世界”的中心定义在(0,0,0)点,大小为0.1倍*/  (如果这样讲还不够清晰的话,我们可以先理解为,世界坐标的作用就是“将我们的整个三维世界搬到(0,0,0)点”这样一个简单的作用就可以了)

 

ViewMatrix:定义视角的矩阵。

在XNA中申明句式是:viewMatrix = Matrix.CreateLookAt(camPosition, camTarget, camUpVector);

/*从以上代码可以看到,viewMatrix所需要的参数和它的作用非常匹配。事实上一个人的视角(view)正是取决于camera的三个属性*/

 

ProjectionMatrix:将三维场景中的坐标映射到屏幕上的矩阵。

在XNA中申明的句式是:Matrix.CreatePerspectiveFieldOfView(viewAngle, aspectRatio,nearPlane, farPlane);

/*从以上代码可以知道,projectionmatrix跟我们“眼睛的视野”有关系。因为人眼观察事物总有一个角度,最近距离和最远距离*/

 

确定了以上三个参数,我们就可以在显示器中正确的描绘特定的一个点。

———————————————————————————————————————–

(中学者可以继续了…)

这个时候,其实我们心中还是一团迷雾。虽然我们可以确定以上三个变量可以确立三维坐标和屏幕之间的正确联系,我们还是不太明白,在这个过程中具体发生了什么?为什么要用矩阵表示呢? 虽然作为初学者的我们了解三维坐标转换都是通过矩阵来进行的,那么以上三个矩阵又是如何表示的呢?

利用工具.net reflector,我们进入以上三个函数内部进行观察:(以下做摘录)

public static Matrix CreateScale(float xScale, float yScale, float zScale)
{
    Matrix matrix;
    float num3 = xScale;
    float num2 = yScale;
    float num = zScale;
    matrix.M11 = num3;
    matrix.M12 = 0f;
    matrix.M13 = 0f;
    matrix.M14 = 0f;
    matrix.M21 = 0f;
    matrix.M22 = num2;
    matrix.M23 = 0f;
    matrix.M24 = 0f;
    matrix.M31 = 0f;
    matrix.M32 = 0f;
    matrix.M33 = num;
    matrix.M34 = 0f;
    matrix.M41 = 0f;
    matrix.M42 = 0f;
    matrix.M43 = 0f;
    matrix.M44 = 1f;
    return matrix;
}

 

public static Matrix CreateTranslation(float xPosition, float yPosition, float zPosition)
{
    Matrix matrix;
    matrix.M11 = 1f;
    matrix.M12 = 0f;
    matrix.M13 = 0f;
    matrix.M14 = 0f;
    matrix.M21 = 0f;
    matrix.M22 = 1f;
    matrix.M23 = 0f;
    matrix.M24 = 0f;
    matrix.M31 = 0f;
    matrix.M32 = 0f;
    matrix.M33 = 1f;
    matrix.M34 = 0f;
    matrix.M41 = xPosition;
    matrix.M42 = yPosition;
    matrix.M43 = zPosition;
    matrix.M44 = 1f;
    return matrix;
}

 

public static Matrix CreateLookAt(Vector3 cameraPosition, Vector3 cameraTarget, Vector3 cameraUpVector)
{
    Matrix matrix;
    Vector3 vector = Vector3.Normalize(cameraPosition - cameraTarget);
    Vector3 vector2 = Vector3.Normalize(Vector3.Cross(cameraUpVector, vector));
    Vector3 vector3 = Vector3.Cross(vector, vector2);
    matrix.M11 = vector2.X;
    matrix.M12 = vector3.X;
    matrix.M13 = vector.X;
    matrix.M14 = 0f;
    matrix.M21 = vector2.Y;
    matrix.M22 = vector3.Y;
    matrix.M23 = vector.Y;
    matrix.M24 = 0f;
    matrix.M31 = vector2.Z;
    matrix.M32 = vector3.Z;
    matrix.M33 = vector.Z;
    matrix.M34 = 0f;
    matrix.M41 = -Vector3.Dot(vector2, cameraPosition);
    matrix.M42 = -Vector3.Dot(vector3, cameraPosition);
    matrix.M43 = -Vector3.Dot(vector, cameraPosition);
    matrix.M44 = 1f;
    return matrix;
}

 

public static Matrix CreatePerspectiveFieldOfView(float fieldOfView, float aspectRatio, float nearPlaneDistance, float farPlaneDistance)
{
    Matrix matrix;
    if ((fieldOfView <= 0f) || (fieldOfView >= 3.141593f))
    {
        throw new ArgumentOutOfRangeException("fieldOfView", string.Format(CultureInfo.CurrentCulture, FrameworkResources.OutRangeFieldOfView, new object[] { "fieldOfView" }));
    }
    if (nearPlaneDistance <= 0f)
    {
        throw new ArgumentOutOfRangeException("nearPlaneDistance", string.Format(CultureInfo.CurrentCulture, FrameworkResources.NegativePlaneDistance, new object[] { "nearPlaneDistance" }));
    }
    if (farPlaneDistance <= 0f)
    {
        throw new ArgumentOutOfRangeException("farPlaneDistance", string.Format(CultureInfo.CurrentCulture, FrameworkResources.NegativePlaneDistance, new object[] { "farPlaneDistance" }));
    }
    if (nearPlaneDistance >= farPlaneDistance)
    {
        throw new ArgumentOutOfRangeException("nearPlaneDistance", FrameworkResources.OppositePlanes);
    }
    float num = 1f / ((float) Math.Tan((double) (fieldOfView * 0.5f)));
    float num9 = num / aspectRatio;
    matrix.M11 = num9;
    matrix.M12 = matrix.M13 = matrix.M14 = 0f;
    matrix.M22 = num;
    matrix.M21 = matrix.M23 = matrix.M24 = 0f;
    matrix.M31 = matrix.M32 = 0f;
    matrix.M33 = farPlaneDistance / (nearPlaneDistance - farPlaneDistance);
    matrix.M34 = -1f;
    matrix.M41 = matrix.M42 = matrix.M44 = 0f;
    matrix.M43 = (nearPlaneDistance * farPlaneDistance) / (nearPlaneDistance - farPlaneDistance);
    return matrix;
}

 

限于篇幅的关系,我们将在下一篇中详解这些函数的具体在做些什么,以及在三维场景中具体的作用如何。

对了,今天稍微看了下<程序员羊皮卷>,还不错,适合大学生读。skydrive里面有收藏,给个下载链接吧:

http://cid-2840b8550205b1bc.skydrive.live.com/embedicon.aspx/.Public/%e7%a8%8b%e5%ba%8f%e5%91%98%e7%be%8a%e7%9a%ae%e5%8d%b7%e4%b8%8b%e8%bd%bd%e7%89%88.pdf

从今天起,开始利用Windows Live Writer 写东西,开心!!


  • 测试微软雅黑的效用
  • Test
  • 支持我最喜欢的中文字体:微软雅黑,还有一系列其他字体可以利用!!!
  • Support my favourite English font: Verdana!  Happy!!
  • 还有   Comic  !

不错的CG,只是配音有点…..

HLSL简介 — 继续shading


毕业论文写到要崩溃… 要写50页是什么情况..  刚写了个中英文摘要就要死掉了.. 唉
所以,偷懒一下…. 回顾下前天看的shading…
 
前面讲到,XNA的basiceffect是实现不了很多复杂的shading的,要自定一些更加复杂的shading就要介绍一种重要的语言:HLSL(high levle shading language)。
 
这里是来自百度百科的解释:
高级着色器语言(High Level Shader Language,简称HLSL),由微软拥有及开发的一种语言,只能供微软的Direct3D使用。 HLSL是微软抗衡GLSL的产品,同时不能与OpenGL标准兼容。他跟Nvidia的Cg非常相似。
  HLSL的主要作用为将一些复杂的图像处理,快速而又有效率地在显示卡上完成,与组合式或低阶Shader Language相比,能降低在编写复杂特殊效果时所发生编程错误的机会。
  RenderMan 是另一个一个非常流行的描述语言,它常被用于和CPU一起在渲染场景中产生电影效果。最近,微软公司OpenGL都开发了他们各自的的高级描述语言(HLSL)和OpenGL描述语言(GLSL)用于在GPU上实现实时Shader。 现在HLSL已经整合到了 DirectX 9中,HLSL 独立的工作在 Windows 平台上。同样的, OpenGL 1.5 也开始包含了 GLSL 作为它的一个标准组件。 这些高级语言加速了shader的开发过程。
  创建一个完整的 shader,那么新的为GPU开发的描述语言就要和像C++这样最热门的程序设计语言一起工作,尽管仅仅只是设置大量的乏味的参数,但是C++ 是最快的,其他语言仅仅也只能够建立 shaders。
 
我们知道,计算shading是非常耗时的,所以HLSL是一种直接跟GPU打交道的语言,所以计算速度相对比较快。
 
关于HLSL的细节我先不讲…因为我还不会… 正在啃《HLSL初级教程》ing….. 所以,敬请期待…… = =
 

计算几何 — 凸包的计算


虽然time limited, anyway anyway..我还是要写点东西的!
4.11买了三本书 《计算几何-算法与应用》,《交互式计算机图形学》,《模式识别》。也算是一点雄心壮志的开始。
请不要说我三心二意,昨天弄shading,今天搞计算几何,其实一点都不冲突。现在也没有整块的时间来搞这些东西,只能做“调剂心情”用。况且交叉着学习对脑子也好些。昨天是看到HLSL停下的(虽然blog才写到per-vertex shading,其实已经看到per-pixel了,没写而已),鉴于HLSL比较重要,又是新东西,我才稍微放一放。
 
好了,根据昨天定下的规矩,我介绍一下参考资料:
《Computational Gemometry algorithms and applications》 Authors:Mark de Berg, Otfried Cheong, Marc van Kerveld, Mark Overmars 即《计算几何》 Chapter 1
 
在计算几何中,有个最基本的概念叫做 凸包 。我们知道,凸包是“凸”的,但这个只是我们的口头定义,数学定义是什么呢?
定义:平面的一个子集S被称为是“凸”的,当且仅当对于任意两点p,q属于S,线段(p,q)完全属于S。集合S的凸包CH(S),就是包含S的最小凸集–更准确地说,它是包含S的所有凸集的交。
左图是凸包, 右图则不是,这非常好理解。
 
那么,我再给出另外一个非官方的定义:计算凸包
计算凸包是什么意思呢?
给定平面点集 P={p1,….,pn},通过计算从P中选出若干点,它们沿着顺时针方向一次对应于CH(P)的各个顶点。(如下图)
给出点集P,输出其凸包的表示。(凸包:convex hull).
 
通俗的说就是从一堆点中,找出能那些构成包围这些点的凸包的点。
 
那么,我们就可以构造如下算法:
 

Algorithm SLOWCONVEXHULL(P)
Input. A set P of points in the plane.
Output. A list L containing the vertices of CH(P) in clockwise order.

1. E   / 0.
2. for all ordered pairs (p,q) in P X P with p not equal to q                       //遍历所有互不相等的有序对<p,q>
3.    do  valid  <–  true                                                                          //将valid置为true
4.         for all points r in P not equal to p or q                                        //在集合P中遍历所有和p,q不相等的点r
5.              do if r lies to the left of the directed line from p to q               //如果发现r在p或q的左侧(由于是伪码,该算法被视为已经实现)
6.                      then valid <– false.                                                       //将valid置为false
7.         if valid then Add the directed edge pq to E.                                 //将valid为true的有序对加入E中
8. From the set E of edges construct a list L of vertices of CH(P), sorted in clockwise order.

原理非常简单,在P X P 笛卡尔乘积中遍历互不相等的有序对<p,q>,如果所有的其他点都在它的右侧,则该序列被视为一个edge.加入E中。

你可能会对“所有点都在该边右侧”这个判断条件产生疑惑。明明对于p4-p5 这条边来说,所有的点都在它的左边啊,但它也是一个edge呢!

原因在于,你没有看清楚,这是有序对。对于p4-p5这条边来说,判断它正确的方向是,p4–>p5. 那么在这样的情况下,是不是所有的其他点都在它的右边了呢?

显然,这个算法还没有考虑到一些其他精度上的问题,还有“退化”的问题,更糟糕的是,它的时间复杂度是O(N*N*N),非常的暴力。所以,我们提出了另外一种方法:递增式算法(incremetal algorithm)

Algorithm CONVEXHULL(P)
Input. A set P of points in the plane.
Output. A list containing the vertices of CH(P) in clockwise order.

1. Sort the points by x-coordinate, resulting in a sequence p1; : : : ; pn.                                //将p1..pn按照x排序,从小到大
2. Put the points p1 and p2 in a list Lupper, with p1 as the first point.                                  //将p1,p2先加入Lupper(上凸包)   
3. for i 3 to n
4.       do Append pi to Lupper.                                                                                        //从p3开始,依次加入Lupper
5.            while Lupper contains more than two points and the last three points in Lupper do not make a right turn     //如果末尾三个点构成不是一个右拐,
6.               do Delete the middle of the last three points from Lupper.                               //将倒数第二个点删除
7. Put the points pn and pn􀀀1 in a list Llower, with pn as the first point.
8. for i n-2 downto 1
9.        do Append pi to Llower.                                                                                       //下凸包同理
10.            while Llower contains more than 2 points and the last three points in Llower do not make a right turn
11.                 do Delete the middle of the last three points from Llower.
12. Remove the first and the last point from Llower to avoid duplication of the points where the upper and lower hull meet.
13. Append Llower to Lupper, and call the resulting list L.
14. return L

因为在上下凸包的构造过程中,所有for循环(while循环)执行次数不会超过n(每个点至多被删一次),那么上下凸包的计算复杂度各为O(n),第一步排序的时间复杂度最低为O(nlogn),故,这个算法总复杂度为:O(nlogn)。

这里也包含了一个定理:给定包含n个点的任意一个平面点集,其凸包都可以在O(nlogn)时间内构造出来。

END

如何在物体表面渲染光照效果 — 光照强度篇


其实,定了计划表之后我是得用一些实际行动来表示我是有诚意来做的!恩,对。所以,写Blog是自我监督的好办法。也希望得到虚拟观众的监督!
现在复试也搞完了,也没有什么其他理由再来偷懒了。虽然要求1,2W字的毕业论文还没有开始动笔,但是毕竟编码部分70%完成了,心里也安慰一些。不过他们要求19号交初稿我也没有办法,所以这两天还是得好好花点时间在写论文上(虽然我真的是非常讨厌纸面上的东西….),所以写blog的时间也许会相对来说少一点,但我还是会尽量养成习惯,一天掌握一个知识点。(好像刚说完雄心壮志就来找理由的感觉= =)。还有,废话很多…. 好了,不罗嗦了,写写今天学的东西。
 
哦,顺便提提这个Tag的作用。今天学习了如何渲染场景中的光照问题。我发现没法插入任何一个之前写的Tag,虽然平台是在XNA上,但是算法原理是共通的呀…所以就新增了这个Tag。以便以后利用。还有,既然是这个Tag的第一篇文章,那么也要讲讲一些原则:我肯定也是根据某些大牛写的书或者blog或者线上资料来学习的,所以我都会给出这些资料的链接或者资料,以便大家去查询。但是,我肯定也不是把他们的东西原封不动的东西搬过来,经常会祛繁求简,或者做些翻译之类的。这些注释都会在文章的开首标出。
 

正文开始:

如何在物体表面渲染光照效果 — 光照强度篇

<参考资料来源:XNA 3.0 Game Programming Recipes: A Problem-Solution Approach  Author: Riemer Groogjans>

CHAPTER 6  Adding Light to Your Scene in XNA 3.0    6-1   6-2 

Code Website: www.apress.com 强烈建议去下载书中的源码运行!!对学习非常有帮助!

Approach:  在我们学习三维场景渲染的时候,必然就要谈到光照的问题。但是很多初学者觉得这个是显卡的事情,不需要我们关心。因为在DX或者OpenGL中都有简单的几句就可以解决光照问题,而XNA中用BasicEffect就可以了。但事实上,我们很多时候是需要一些复杂的光照效果。如蜡烛,烟火等等复杂效果。那么需要了解一些基本的问题。比如说:如何计算物体表面的光照强度呢?如何渲染阴影呢?等等一些问题。

下面,我就从最简单的单点光源开始。

我们知道,在三维场景中,组成各个Model的最小单位是三角形。那么我们在渲染中要解决的问题事实上是如何处理光照和这些二维的三角形的关系。这个时候事实上已经简化到如何处理:一条射线和一个三角形平面的关系的问题。

首先要介绍一个重要概念:Normal. 我们把与三角形垂直的方向称作Normal(通常定义物体表面向外的方向为Normal的正方向)。通常我们把Normal作为三角形三个顶点的一个属性。(记住这一点,非常重要)。

那么如下图所示:

最下面的Light Direction代表的是光照的方向,而上面的各个带箭头的是各个三角平面的Normal,我们发现,各个Normal在Light Direction方向都有一个黑色的投影,而这个投影的长度代表了光照的强度。所以再这种情况下,假如平面和光照的方向平行,投影为0,这个平面上像素的光照强度也就为0;而假如两者相互垂直的话,光照强度达到最大。

1.事实上,显卡在计算物体表面光照强度的时候还要根据两个变量:光照的方向和Normal的长度。任何一个参数的变化都会影响投影的大小。故我再介绍另一个操作:Normalize(). Normalize的作用是将一个向量的长度变为单位长度,也就是1.  将光照的方向和Normal都进行Normalize操作后,就可以简化这个问题:这样光照强度也就只跟角度有关了。

2.还有一个问题就是WorldMatrix的应用。事实上,当我们程序里面对物体做某些操作,如改变位置和大小时,本身物体postion,scale等属性并不会发生变化。所以我们需要记录这些变化,反应在渲染某个物体时的WorldMatrix上,这样才能正确的渲染它。(这点不但仅仅用于光照上,其他的渲染也是一样的。还有在做碰撞检测的时候,也要注意你提取的Model的position等属性不一定是你Model现在真正的“属性”,因为在其他地方移动或改变后其本省的属性并不会变化)。

好了,讲完两个注意点。我们来看看渲染的效果如何:

通常,我会认为,三角形三个顶点的Normal属性应该都是相同的,如下图的情况(两个三角片共享一边,也是经常会出现的情况):

左图的:0,1,2拥有相同的Normal,而3,4,5拥有相同的Normal,这个时候,渲染的效果如下图所示:

我们会发现中间光照不够平滑,那么还需要解决这个问题。

解决方法是:让1,4和2,3设为相同的Normal,具体方法是:初始化跟前面一样,同一个三角形顶点的Normal保持相同。但出现共享一边的情况,就需要将共享边的两个端点设为相同。具体方法是:顶点:1,4和2,3的Normal值就是设为两者的相加。这里要注意相加后还要对他们进行Normalize()的处理。(原因前面已经说明)

这样就能让中间的变化更加平滑。(注意:三角的三个顶点的Normal值不一定要相同,三个Normal值不同的话,显卡会自动根据这三个值来计算这个三角形中每个像素的渲染情况。具体细节我目前还不太清楚= =,估计也是通过计算每个像素和三个顶点的距离作为权值再乘以相应的Normal投影值吧。

得到的效果如下:

这样中间的变化就变平滑了。

(具体源码就不贴了,再次强烈建议大家去www.apress.com去下载源码来学习!)

 

 

怨念自己的时间观念!!上约束时间表一张!!


毕业设计的初步成果


虽然很不清晰,还是凑合着看吧。 建议拖着看完,录得挺啰嗦的。 <改日讲完整原理>

首先是流程图  –>   接着是展示待测试的6张图片–>   然后是依次测试每张图片的效果

    http://www.tudou.com/v/mgLb9c4A9Qs