zoukankan      html  css  js  c++  java
  • 基本3D变换之World Transform, View Transform and Projection Transform

    作者:i_dovelemon

    来源:CSDN

    日期:2014 / 9 / 28

    主题:World Transform, View Transform , Projection Transform




    引言

                 在3D图形学中。基本几何变换是一个很重要的操作。可以说,整个3D图形可以有效的显示,就是因为几个很重要的基础3D变换贡献的。在前面的文章中,向大家承诺了,要具体的解说在3D图形学中的三个主要的坐标变换。今天,就来像大家讲述。DirectX是怎样进行变换。



    变换的目的

                   在我们解说详细的变换工作之前,我们须要知道,为什么须要进行变换?在3D图形学中。有非常多不同的坐标系统。比方说,模型坐标系统,世界坐标系统,视空间坐标系统。裁剪空间坐标系统等等。

    为什么须要如此之多的坐标系统了?那是由于,不同的工作。在不同的坐标系统中进行,会给我们的工作带来非常多方便。

    比方说,我们定义模型的时候,在模型的坐标空间中定义。而不是在世界空间坐标系里面定义。在模型坐标里面,我们可以仅仅关心模型的基本构造,它的外形等,不须要考虑它将来会放在场景中的哪个地方,面朝的方向是哪里。这样做就行大大的降低我们的工作。所以,一句话。进行坐标变换的目的就是为了简化工作,让我们的工作更加easy的完毕而已(尽管初学来说。好像没有简化我们的工作???)。



    怎样变换
    

                    在前面我写过一篇文章,3D图形变换

    在这篇文章中,讲述了怎样使用矩阵来进行同一个维度的坐标变换。

    只是,这里解说的变换,仅仅是对那些仅仅须要进行平移,旋转的坐标系统来说的。对于要进行缩放的坐标变换,并非使用这样的方法。

    有时候,我们须要依据变换的各种条件,来推导出终于变换的矩阵。这样的方法在那些变换后的矩阵非常难用其它的坐标系统来描写叙述它们的坐标基的时候使用。



    DirectX变换流程
    

                       在DirectX中,有三种变换,是每个3D应用程序,每一帧都会使用到的变换。

    它们各自是World Transform(世界坐标变换)。 View Transform(视空间坐标变换,又称相机坐标变换)和最麻烦的Perspective Projection Transform(透视投影变换。正交投影不是常常使用的变换。这里不介绍这样的)。以下,我们来一一的介绍这些变换的作用,以及怎样构建他们。



    World Transform
    

                     世界坐标变换,顾名思义,是将模型坐标变换到世界空间中的变换。我们要知道。不论什么的变换都能够拆分称为平移,旋转。缩放,这三个大的变换方式。对于世界变换来说,我们全然能够依照3D图形变换中描写叙述的那样来进行坐标的平移和旋转变换。假设须要将模型进行缩放的话。我们在构建一个缩放的矩阵(这个矩阵在不论什么3D书籍上都有介绍。这里不再赘述),然后把这个缩放矩阵。与前面的矩阵结合起来,就能够完毕一个世界坐标变换的功能了。这是三个坐标变换里面最简单的变换。



    View Transform
    


                         在3D空间中,我们会定义一个虚拟的实际上在3D空间中并没实用不论什么模型表示的相机。

    我们可以在程序界面上看到的内容。都是通过相机的位置,和属性等等来构建的。

    相机变换的作用是将原本相对于世界坐标中心的物体。变换到以相机为坐标中心的相机坐标空间中去。由于非常多的处理。在世界坐标空间中进行比較麻烦,如物体剔除,背面消除等等。这些工作假设可以在相机坐标空间中进行的话。那么就会轻松非常多。进行相机坐标变换我们须要定义相机的属性。

    在DirectX中使用的是一种称之为UVN的相机模型。我们定义了相机的三个坐标基向量Right,Up和Look向量在世界坐标系中的表示,以及相机在世界坐标空间的位置。这样,我们仅仅要简单的使用3D图形变换里面介绍的方法。将这三个向量和位置属性构成例如以下的矩阵:

                                                                                                   

                         只是读者须要注意:这里的矩阵,是将相机空间里面的坐标变换到世界坐标空间中去的矩阵。

    我们须要的是相反效果的矩阵,那么我们仅仅要求这个矩阵的逆矩阵就能够了。求逆的过程。在不论什么的线性代数书上都有,这里直接给出逆矩阵:

                                                                                         

                           读者可以发现,这里的矩阵就是DirectX中使用的矩阵了。它的推导过程就是这么的简单。



    Perspective Projection Transform
    

                           投影变换是这三个中最复杂的变换。在DirectX中,它的投影变换矩阵做了非常多的事情。这也就导致了它的变换矩阵十分的复杂。

    所以,为了可以具体的掌握这个变换过程。我们先要弄清楚,进行这个变换的目的何在。

                          在DirectX中,投影变换的目的,是让3D的模型数据等变换称为2D的图像,从而显示在屏幕上,这个过程称之为投影。

    投影意味着,我们须要对模型的X和Y坐标进行一些变换,从而可以依据它在相机空间中的上下左右顺序,正确的绘制在屏幕上。同一时候。由于是3D空间,模型的图像之间存在这遮罩的关系,远的物体会被近的物体遮挡住,我们相同还须要保存一些信息,从而可以推断哪一个在前面,哪一个在后面。这个值自然就是Z值了。Z值越小,说明了它越靠近相机。那么它后面的物体就不应该被绘制。由于都被它挡住了。

                           我们把DirectX的投影变换分为两个部分。一个部分是真真的投影过程。将相机坐标空间里面的模型的X和Y坐标正确的投影到我们选定的一个平面上来。由于要终于构成一个2D的图像,所以非常自然的想到将3D空间的物体坐标投影的一个平面上来。这个平面,在图形学上被称为投影面。

    投影面的选择是随意的,仅仅要可以非常方便的进行处理就可以。在DirectX中选择近裁剪面为投影平面。可是,不同的程序,可能选择不同的尺寸作为程序的显示窗体。DirectX为了将这层关系忽略掉,它统一的将模型的X和Y坐标变换到[-1,1]这个范围来。这样。在最后仅仅要依据屏幕的宽和高分别乘以这个变换后的值,就行得到在屏幕上的坐标了(这个就是视口变化的原理)。同一时候。变换到[-1,1]这个范围。我们对模型进行3D裁剪时的操作也会变得十分的简单。

                            另外一个部分就是对遮罩关系的Z值进行保存的工作。我们须要将图像的先后关系保存起来。在上面解说过进行投影过后,他们的点都在近裁剪面上了,那么他们的Z值将都是近裁剪面的Z值,也就是说Z值的遮罩信息丢弃了。

    所以,我们须要进行一些工作来将这个Z值信息保存下来。

                            好了,在知道了DirectX的透视投影变换做了哪些工作之后,我们来实际的进行矩阵的推导。

                            在推导之前,我们来看下相机所形成的视域体的结构:

                                                                               

                           这个结构体,是由近裁剪面(Front Clipping Plane)  和远裁剪面(Back Clipping Plane)截取相机的视野形成的椎体而得到的一个台体。

    这个台体里面的模型就是将要变换到投影平面上的模型。

                           我们选取Y-Z平面来观察下投影过程。这个过程相同可以用在X-Z平面上:

                                        

                            从图中。我们能够看到在视域体里面的点P(px, py, pz)经过与相机的原点的连线与近平面的交点为P‘。

    P’即为投影过程得到的投影点。

    P‘的坐标非常easy计算出来。

    仅仅要利用相似三角形的原理,我们非常easy的得出例如以下的结论:

                                                                                                                    P’.y / n = P.y / P.z  == P'.y = P.y * n / P.z              (1)

                             同理。我们对X-Z平面进行相同的操作。能够得到例如以下的结论:

                                                                                                                   P‘.x / n = P.x / P.z == P'.x = P.x * n / P.z                 (2)

                             即投影点的坐标为:

                                                                                                                   P' = (P.x * n / P.z ,  P.y * n / P.z, P.z)                       (3)

                              我们在上面讨论过。为了可以简单的进行3D裁剪,忽略尺寸的大小。我们须要将投影坐标变换到[-1,1]这个范围来。

    也就是说,我们还须要另外的处理,如果这个过程得到的点是P’‘。

    那么就有例如以下的结论:

                                                                                                                   P''.x / P'.x = 2 / W ,  P’‘.y / P'.y = 2 / H                   (4)

                              这个结论是由于变换后的X,Y的范围和原来的X,Y范围是线性变化的。所以我们能够利用同比的概念来进行比較,从而得到了在[-1,1]范围里面的X,Y坐标分别为:

                                                                                                                   P’‘.x = 2 * P'x / W ,   P''.y = 2 * P'.y / H                    (5)  (当中W是投影平面的宽度,H是投影平面的高度)

                              将公式(3)中的数据带入公式(5),得到例如以下的结论:

                                                                                                                  P''.x = 2 * P.x * n / (P.z * W) , P''.y = 2 * P.y * n / (P.z * H)   (6)

                              因为H,W各自是投影平面的高度和宽度(即终于屏幕的高度和宽度),所以得到例如以下的结论:

                                                                                                                 P’’.y = P.y / P.z * cot(a)                                               (7)  cot(a) = 2 * n / H , a为相机的上下视野角度的一半

                              在DirectX中。还传入了一个宽高比Aspect = W / H, 即得到 :

                                                                                                                  W = H * Aspect                                                          (8)

                              将公式(8)带入公式(6),得到P‘’.x:

                                                                                                                  P''.x = 2 * P.x * n / (P.z * H * Aspect) = (P.x / P.z ) * (cot(a) / Aspect)   (9)

                               好了,第一部分的推导已经完成了,我们已经知道了怎样将模型的X。Y坐标变换到[-1,1]空间中去了。

    剩下的部分就是怎样保存Z值了。

                               由于经过上面的变换之后,模型坐标的Z值都变成了近裁剪面的值n了。而我们须要的是Z值的先后关系。

    我们发现这个关系就保存在原来未进行上面的变换的相机空间坐标的Z值中。也就是说,经过相机变换后的Z坐标已经可以非常好的推断哪些点是在哪些点的后面了。

    我们可以将这个值保存下来。可是DirectX并没有这么做。那是由于。这个Z值可能非常大,也可能非常小。这就会导致Z缓存在设计上的困难。所以,使用对X,Y坐标相同的方法,不同的是这次变换到[0,1]范围来。

    读者看到这里可能想,那么就简单的使用例如以下的公式:

                                                                                                                  P‘’.z  = (P''.z  - n )  /  (f - n)                                              (10)

                                的确,上面的公式看起来似乎是正确的。和上面相同的使用同比的概念来进行。可是,假设读者这么觉得就大错特错了。

    同比概念可以得出这种结论的前提是Z值的变换是线性的。我们这里经过投影变换后的数据。将来都要在光栅化函数里面。通过在屏幕坐标空间线性插值来进行像素颜色的计算,填充,从而实现光栅化。对于X,Y来说。他们在屏幕空间依旧是线性变换的。

                               可是对于Z坐标来说。我们并不希望它在屏幕空间中是线性变换的。想象一下,我们看到的一个物体,都是靠近我们的边缘比較清晰。远的边缘比較模糊。这显示在数据上就是近的边缘,它的纹理坐标的变化比較细微,比較缓慢,而远的变化比較大,比較迅速。

    这个概念,在图形学上称之为透视修正,是为了可以营造出真真意义上的透视感觉而提出的概念。

                               所以,DirectX也将这个概念增加进去了。尽管Z值并非线性变换的。可是实现上面的透视效果的时候,1/z的值是呈线性变化的。

    也就是说。我们在进行光栅化处理的时候,可以对纹理坐标进行线性插值了。

    仅仅要插值计算的时候使用的是1/z来进行的就可以。

    这样。原本不是线性变换的纹理坐标,我们可以通过线性插值的方法来进行计算了。

                              讲到这里,大家可能有点晕晕乎乎的了。

    总之中的一个句话,为了可以让显示的图像效果上更加的真实,我们须要将1/z的信息保存起来。而不是Z的信息。

                               好了。也就是说,我们仅仅要将原来的z值变成1/z,然后进行变换,到[0,1]空间中就可以。

                               实际上,在这里。我们直接保存1/z作为新的Z值也就能够了,可是这种话。我们在进行Z-Test的时候。就不是直观上的哪一个Z值小就靠前。

    读者想象看,假设Z值小的话。因为Z值是1/z'计算出来的,那么原来相机空间中的Z‘值就大的值,也就是说它反而是靠后的。

                               DirectX在这里有做了一层工作。它希望保留这样的直观的印象,当Z值小的时候,就表示该像素是靠前的。Z值大的时候。就表示该像素靠后。

                               所以。DirectX在1/z的基础上,在进行了一次线性变换。因为线性变换并不改变值的线性变化属性,所以能够再次使用。尽管会改变值。可是我们并非使用值来获取像素的精确位置,而是推断他们的先后关系,仅仅要这种关系不变,它的值是多少。一点关系都没有。

                               线性变化关系即是正比关系,我们能够用y = ax + b这种一次函数来表示。

                               所以,DirectX希望满足例如以下的条件:

                                                                                        Z' = a * 1 / Z + b 

                                当Z = n的时候,也就是近期的时候。Z’值最小。为0。

                                当Z = f的时候,也就是最远的时候,Z‘值最大,为1;

                                所以。得出例如以下的公式:

                                                                                    0 = a * 1 / n + b

                                                                                    1 =  a * 1/ f +  b

                                 解这个一元一次方程组,得到例如以下的解:

                                                                                                                                  a = fn / (n - f) , b = f / (f - n)                       (11)

                                  所以,综合(7)(9)(11),我们得到:

                                                                                                                                  X' = X / Z * (cot(a) / Aspect)

                                                                                                                                  Y' = Y / Z * cot(a)                                         (12)

                                                                                                                                  Z ' =( fn/(n - f) ) * Z + f / (f - n)

                                                                                                                                  W’ = 1

                                  上面的变换最后用一个矩阵表示为:

                                                                                                                   [X, Y , Z , W]  * M = [ X', Y', Z' , W']                                                    

                                   得这个矩阵为:

                                                                                                                               

                                    这个矩阵。和DirectX SDK中关于函数D3DXMatrixPerspectiveLH中使用的矩阵一致。它的来源就是这种。




    总结
    

                                    矩阵变换是3D图形学必不可少的一个工具,对于有志于深入学习的同学来说,不能过分依赖于API。而须要自己设计来完毕这些功能。仅仅有掌握全部这些细节,才可以建成更加高级的引擎。


    參考资料:http://www.cnblogs.com/graphics/archive/2012/07/25/2582119.html

                        DirectX SDK 文档中Transform一节




  • 相关阅读:
    web.xml
    ibatis配置
    ibatis基本语法
    XML文件解析
    进制转换
    BaseAction
    【编译】StreamInsight简介
    秒懂C#通过Emit动态生成代码
    百度地图应用开发(二)
    ListView与Adapter的那些事儿
  • 原文地址:https://www.cnblogs.com/claireyuancy/p/6953056.html
Copyright © 2011-2022 走看看