精华内容
下载资源
问答
  • OpenGL坐标变换

    千次阅读 2018-08-16 19:03:52
    基础概述 众所周知,OpenGL是一个3D图形库,在终端设备上广泛使用。但是我们的显示设备都是2D平面,那么OpenGL怎么把3D...通过OpenGL坐标变换,我们可以在一个给定的观察视角下,把3D物体投影到2D屏幕上,再经过后...

    基础概述

    众所周知,OpenGL是一个3D图形库,在终端设备上广泛使用。但是我们的显示设备都是2D平面,那么OpenGL怎么把3D图形映射到2D屏幕那?这就是OpenGL坐标变换所要完成的工作。 
    一般情况下,我们总是通过一个2D屏幕,观察3D世界。因此,我们实际看到的是3D世界在2D屏幕上的一个投影。通过OpenGL坐标变换,我们可以在一个给定的观察视角下,把3D物体投影到2D屏幕上,再经过后面的光栅化和片元着色,整个3D物体就映射成了2D屏幕上的像素。 
    OpenGL的坐标变换流程如下所示: 
    OpenGL坐标变换过程 
    我们先简单看下整个流程: 
    1. 首先,输入顶点一般是以本地坐标表示的3D模型。本地坐标是为了研究孤立的3D模型,坐标原点一般都是模型的中心。每个3D模型都有自己的本地坐标系(Local Coordinate),互相之间没有关联。 
    2. 当我们需要同时渲染多个3D物体时,需要把不同的3D模型,变换到一个统一的坐标系,这就是世界坐标系(World Coordinate)。把物体从本地坐标系变换到世界坐标系,是通过一个Model矩阵完成的。模型矩阵可以实现多种变换:平移(translation)、缩放(scale)、旋转(rotation)、镜像(reflection)、错切(shear)等。例如:通过平移操作,我们可以在世界坐标系的不同位置绘制同一个3D模型; 
    3. 世界坐标系中的多个物体共同构成了一个3D场景。从不同的角度观察这个3D场景,我们可以看到不同的屏幕投影。OpenGL提出了摄像头坐标系的概念,即从摄像头位置来观察整个3D场景。把物体从世界坐标系变换到摄像头坐标系,是通过一个View矩阵完成的。视图矩阵定义了摄像头的位置、方向向量和上向量等构成摄像头坐标系的基础信息。View矩阵左乘世界坐标系中顶点A的坐标,就把顶点A变换到了摄像头坐标系。同一个3D物体,在世界坐标系中,拥有一个世界坐标;在摄像头坐标系中,拥有一个摄像头坐标,View变换就是负责把物体的坐标从世界坐标系变换到摄像头坐标系。 
    4. 因为我们是从一个2D屏幕观察3D场景,而屏幕本身不是无限大的。所以当从摄像头的角度观察3D场景时,可能无法看到整个场景,这时候就需要把看不到的场景裁减掉。投影变换就是负责裁剪工作,投影矩阵指定了一个视见体(View Frustum),在视见体内部的物体会出现在投影平面上,而在视见体之外的物体会被裁减掉。投影包括很多类型,OpenGL中主要考虑透视投影(Perspective Projection)和正交投影(Orthographic Projection),两者的区别在后面会详细介绍。除此之外,通过Projection矩阵,可以把物体从摄像头坐标系变换到裁剪坐标系。在裁剪坐标下,X、Y、Z各个坐标轴上会指定一个可见范围,超过可见范围的顶点(vertex)都会被裁剪掉。 
    5. 每个裁剪坐标系指定的可见范围可能是不同的,为了得到一个统一的坐标系,需要对裁剪坐标进行透视除法(Perspective Division),得到NDC坐标(Normalized Device Coordinates - 标准化设备坐标系)。透视除法就是将裁剪坐标除以齐次分量W,得到NDC坐标:

    在NDC坐标系中,X、Y、Z各个坐标轴的区间是[-1,1]。因此,可以把NDC坐标系看做作一个边长为2的立方体,所有的可见物体都在这个立方体内部。 
    6. NDC坐标系的范围是[-1,1],但是我们的屏幕尺寸是千变万化的,那么OpenGL是如何把NDC坐标映射到屏幕坐标的那?视口变换(Viewport Transform)就是负责这块工作的。在OpenGL中,我们只需要通过glViewport指定绘制区域的坐标和宽高,系统会帮我们自动完成视口变换。经过视口变换,我们就得到了2D屏幕上的屏幕坐标。需要注意的是:屏幕坐标与屏幕的像素位置是不一样的,屏幕坐标是屏幕上任意一个顶点的精确位置,可以是任意小数。但是像素位置只能是整数(具体的某个像素)。这里的视口变换是从NDC坐标变换到屏幕坐标,还没有生成最终的像素位置。从屏幕坐标映射到对应的像素位置,是后面光栅化完成的。

     

    在OpenGL中, 本地坐标系、世界坐标系和摄像头坐标系都属于右手坐标系,而最终的裁剪坐标系和标准化设备坐标系属于左手坐标系。 
    左右手坐标系的示意图如下所示,其中大拇指、食指、其余手指分别指向x,y,z轴的正方向。 

    下面我们分别来看下模型变换、视图变换、投影变换和视口变换的推导和使用。

    模型变换

    模型变换通过对3D模型执行平移、缩放、旋转、镜像、错切等操作,来调整模型在世界坐标系中的位置。模型变换是通过模型矩阵来完成的,我们看下每种模型矩阵的推导过程。

    平移变换

    平移就是将一个顶点A = (x,y,z),移动到另一个位置 =(,,),移动距离D =  - A = ( - x , - y , - z) = ( ,  , ),所以可以用顶点A来表示: 

    ()


    通过平移矩阵来表示如下所示: 

    其中就是平移变换矩阵,表示X轴上的位移,表示Y轴上的位移,表示Z轴上的位移。 
    虽然看上去很繁琐,但是在OpenGL中,我们可以通过GLM库来实现平移变换。

     

     
    1. glm::mat4 model; // 定义单位矩阵
    2. model = glm::translate(model, glm::vec3(1.0f, 1.0f, 1.0f));

    上述代码定义了平移模型矩阵,表示在X、Y、Z轴上同时位移1。

    缩放变换

    可以在X、Y和Z轴上对物体进行缩放,3个坐标轴相互独立。对于以原点为中心的缩放,假设顶点A(x,y,z)在X、Y和Z轴上分别放大、、倍,那么可以得到放大后的顶点 =( * x , * y ,  * z),通过缩放矩阵来表示如下所示: 


    其中就是缩放变换矩阵。 
    默认情况下,缩放的中心点是坐标原点,如果我们要以指定顶点P( ,  , )为中心对物体进行缩放。那么可以按照如下步骤操作: 
    1. 把顶点P移动到坐标原点 
    2. 以坐标原点为中心,旋转指定角度 
    3. 把顶点P移动回原来的位置 
    整个过程可以简化成一个矩阵: 

    在OpenGL中,我们可以通过GLM库来实现缩放变换:

     

     
    1. glm::mat4 model; // 定义单位矩阵
    2. model = glm::scale(model, glm::vec3(2.0f, 0.5f, 1.0f);

    上述代码定义了缩放模型矩阵,表示在X轴上fa2倍,Y轴上缩小0.5倍、Z轴上保持不变。

    旋转变换

    在3D空间中,旋转需要定义一个旋转轴和一个角度。物体会沿着给定的旋转轴旋转指定角度。 
    我们首先看下,沿着Z轴旋转的旋转矩阵是怎样的? 
    假设有一个顶点P,原始坐标为 ( ,  , ),离原点的距离是,沿着Z轴顺时针旋转度,新的坐标为( ,  , ),如下所示: 
    围绕Z轴旋转 
    因为旋转前后,z坐标不变,所以暂时忽略,那么可以得到: 





    根据上述公式,可以得到围绕Z轴的旋转矩阵: 

    同理,可以得到围绕X轴的旋转矩阵: 

    同理,可以得到围绕Y轴的旋转矩阵: 

     

    在OpenGL中,我们可以通过GLM库来实现旋转变换:

     
    1. glm::mat4 model; // 定义单位矩阵
    2. model = glm::rotate(model, glm::radians(-45.0f), glm::vec3(0.4f, 0.6f, 0.8f));

    上述代码表示:围绕向量(0.4f, 0.6f, 0.8f),顺时针旋转45度。

    在进行旋转操作时,经常有一个困惑:顺时针是正方向,还是逆时针是正方向? 
    其实,存在一个左手规则和右手规则,可以用于判断物体绕轴旋转时的正方向。 
    左手规则和右手规则 
    在OpenGl中,我们使用右手规则,大拇指指向旋转轴的正方向,其余手指的弯曲方向即为旋转正方向。所以上面的-45度是顺时针旋转。

    模型变换的顺序问题

    因为矩阵不满足交换律,所以平移、旋转和缩放的顺序十分重要, 
    一般是先缩放、再旋转、最后平移。当然最终还是要考虑实际情况。 
    还有一点需要注意,GLM操作矩阵的顺序和实际效果是相反的。如下所示,虽然书写顺序是:平移、旋转和缩放,但是实际最终的模型矩阵是:先缩放、再旋转、最后平移。

     
    1. glm::mat4 model; // 定义单位矩阵
    2. model = glm::translate(model, glm::vec3(1.0f, 1.0f, 1.0f));
    3. model = glm::rotate(model, glm::radians(-45.0f), glm::vec3(0.4f, 0.6f, 0.8f));
    4. model = glm::scale(model, glm::vec3(2.0f, 0.5f, 1.0f);

    视图变换

    经过模型变换,都有的坐标都处于世界坐标系中,本节就是以摄像头的角度观察整个世界空间。首先需要定义一个摄像头坐标系。 
    一般情况下,定义一个坐标系需要以下参数: 
    1. 指定坐标系的维度:2D、3D、4D等。 
    2. 定义坐标空间的轴向量,例如:X轴、Y轴、Z轴,这些向量称为基向量,基向量一般都是正交的。坐标系中的所有顶点都是通过基向量表示的。 
    3. 坐标系的原点O,原点是坐标系中所有其他点的参考点。 
    简单来说,坐标系=(基向量,原点O)

    同一个顶点,在不同的坐标系中拥有不同的坐标,那怎么才能把世界坐标系中的顶点坐标,变换到摄像头坐标系那? 
    要实现不同坐标系之间的坐标转换,需要计算一个变换矩阵。这个矩阵就是坐标系A中的原点和基向量在另一个坐标系B下的坐标表示。假设存在A坐标系B坐标系以及顶点V,那么顶点V在A和B坐标系下的坐标变换公式如下所示: 

     

     

    简单解释一下:

     
    1. 顶点V在A坐标系的坐标 = B坐标系的基向量和原点在A坐标系下的坐标表示构成的变换矩阵 * 顶点V在B坐标系的坐标;
    2.  
    3. 顶点V在B坐标系的坐标 = A坐标系的基向量和原点在B坐标系下的坐标表示构成的变换矩阵 * 顶点V在A坐标系的坐标

    其中,和互为逆矩阵。所以坐标系之间的切换,关键就是求出坐标系之间互相表示的变换矩阵。那么矩阵应该怎么计算那?假设坐标系A的三个基向量和原点在B坐标空间的单位坐标向量分别是、、和,那么矩阵如下所示: 


    矩阵的计算方式也类似,此处不再赘述。

     

    下面我们看下OpenGL的视图变换矩阵是怎么计算出来的? 
    现在存在两个坐标系:世界坐标系W和摄像头坐标系E,还有一个顶点V,并且知道顶点V在世界坐标系的坐标 = (,,),那么顶点V在摄像头坐标系下的坐标是多少那?根据上面的公式可知,我们首先需要计算出矩阵。

    众所周知,世界坐标系的原点O = (0,0,0),三个基向量分别是,X轴:(1,0,0)、Y轴:(0,1,0)、Z轴:(0,0,1)。 
    理论上,定义一个摄像头坐标系,需要4个参数: 
    1. 摄像头在世界坐标系中的位置(摄像头坐标系的原点) 
    2. 摄像头的观察方向(摄像头坐标系的Z基向量) 
    3. 一个指向摄像头右侧的向量(摄像头坐标系的X基向量) 
    4. 一个指向摄像头上方的向量(摄像头坐标系的Y基向量)。

    通过上述4个参数,我们实际上创建了一个三个单位轴相互垂直的,以摄像机位置为原点的坐标系。 
    摄像头坐标系

    在使用过程中,我们只需要指定3个参数: 
    1. 摄像机位置向量() 
    2. 摄像机指向的目标位置向量() 
    3. 指向摄像头上方的向量()

    接下来是根据上面3个参数,推导出摄像头坐标系单位基向量的步骤: 
    1. 首先计算摄像头的方向向量(方向向量是摄像头坐标系的Z轴正方向,和实际的观察方向是相反的)。 


    然后计算出单位方向向量 

    2. 根据上向量和单位方向向量确定摄像头的右向量 

    然后计算出单位右向量 

    3. 根据单位右向量和单位方向向量确定单位上向量 

     

    这样,就确定了摄像头坐标系的三个单位基向量:、和以及摄像头的位置向量。这四个参数一起确定了摄像头坐标系:摄像头位置是坐标原点,单位右向量指向正X轴,单位上向量指向正Y轴,单位方向向量指向正Z轴。

    现在我们已经定义了一个摄像头坐标系,下一步就是把世界坐标系中的顶点V = (,,),变换到这个摄像头坐标系。根据上文可知,顶点V在摄像头坐标系E的坐标计算过程如下所示: 


    所以关键是计算变换矩阵,而根据摄像头坐标系的基向量和原点在世界空间中的坐标表示,我们可以得到: 

    那么最终的变换矩阵如下所示: 

    其中,dot函数表示向量的点积,是一个标量。最终,顶点V在摄像头坐标系下的坐标如下所示: 

     

    上面的矩阵就是View变换矩阵。

    下面看一个案例:假设摄像头的坐标是(0, 0, 3),摄像头的观察方向是世界坐标系的原点(0,0,0),上向量是(0,1,0),顶点V在世界坐标系的坐标为(1,1,0),那么可以计算出摄像头坐标系的基向量和原点如下所示: 
    1.  =  
    2.  =  
    3.  =  
    4.  =  
    所以对应的View变换矩阵就是: 

     

    最后,顶点V在摄像头坐标系的坐标就是: 

     

    虽然上述流程很复杂,但在OpenGL中,我们可以通过GLM库定义View矩阵。针对上述案例,通过lookAt函数就可以得到View矩阵。

     
    1. glm::mat4 view;
    2. view = glm::lookAt(glm::vec3(0.0f, 0.0f, 3.0f),glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 1.0f, 0.0f));

    经过验证,通过lookAt函数得到View矩阵为: 

     

    很显然,通过lookAt函数得到View矩阵和上面我们推导的View矩阵是一致的。

    投影变换

    前面经过模型变换和视图变换后,3D模型已经处于摄像头坐标系中。本节的投影变换将物体从摄像头坐标系变换到裁剪坐标系,为下一步的视口变换做好准备。 
    投影变换通过指定视见体来决定场景中哪些物体可以呈现在屏幕上。在视见体中的物体会出现在投影平面上,而在视见体之外的物体不会出现在投影平面上。在OpenGL中,我们主要考虑透视投影和正交投影,两者的区别如下所示: 
    透视投影和正交投影
    上图中,红色和黄色球在视见体内,因而呈现在投影平面上;绿色球在视见体外,所以没有投影到近平面上。除此之外,透视投影会根据物体的Z坐标,决定物体在投影平面的大小,原则是:远小近大,符合生活常识。而正交投影不考虑物体Z坐标,所有物体在投影平面上保持原来的大小。

    不管透视投影,还是正交投影,都可以通过指定(GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble near, GLdouble far)6个参数来指定视见体。(left,bottom)指定了近裁剪面左下角的坐标,(right,top)指定了近裁剪面右上角的坐标,-near表示近裁剪面,−far表示远裁剪面。下面需要利用这6个参数,推导投影矩阵。

    在摄像头坐标系下,摄像头指向-z轴,所以近裁剪面z=−near,远裁剪面z=−far。并且OpenGL是在近平面上成像的。

    通过上述6个参数指定的透视投影变换如下所示: 
    透视投影变换

    通过上述6个参数指定的正交投影变换如下所示: 
    正交投影变换

    投影变换和透视除法后,摄像头坐标系中的顶点被映射到一个标准立方体中,即NDC坐标系。其中X轴上:[left,right]映射到[−1,1],Y轴上:[bottom,top]映射到[-1,1]中,Z轴上:[near,far]映射到[−1,1],下面的矩阵推导会利用这里的映射关系。下面我们分别看下两种投影矩阵的推导过程。

    透视投影

    透视投影和透视除法的坐标映射如下所示: 
    投影映射关系

    上图中,摄像头坐标系是右手坐标系,NDC是左手坐标系,NDC坐标系的Z轴指向摄像头坐标系的-Z轴方向。

    假设顶点V在摄像头坐标系的坐标 = ( ,  ,  , ),变换到裁剪坐标系的坐标 = ( ,  ,  , ),透视除法到NDC坐标系的坐标 = ( ,  ,  , )。我们的目标是计算出投影矩阵,使得: 


    同时,可得到透视除法的变换: 

    首先,我们看下投影矩阵对X轴和Y轴的变换。顶点P投影到近平面后,得到顶点 = ( ,  , −near)。具体示意图如下所示: 
    X轴映射 Y轴映射 
    利用三角形的相似性,通过左图可知: 

    所以,可以得到X轴上的投影值: 

    同理,通过右图,可以得到Y轴上的投影值: 

    由(1)(2)公式可以发现,他们都除以了分量,并且与之成反比。这可以作为透视除法的一个线索,因此我们的矩阵如下所示: 

    也就是说。

     

    接下来,我们根据、与NDC坐标的映射关系,推导出的前两行。 
    满足[left,right]映射到[-1,1],如下所示: 
    Mapping from $x_p$ to $x_n$ 
    因为是线性映射关系,所以可以设置线性方程,求出系数K和常量P。 


    通过代入[left,right]到[-1,1]的映射关系,可以得到线性方程: 

    将上面的公式(1)代入公式(3),可得: 

    又因为,所以可以进一步简化公式: 

    根据公式(5),可以进一步得到矩阵: 

     

    OK,继续看下的映射关系:满足[bottom,top]映射到[-1,1],如下所示:Mapping from $y_p$ to $y_n$ 
    同理,根据线性映射关系,可以得到如下公式: 


    又因为,所以可以进一步简化公式: 

    根据公式(7),可以进一步得到矩阵: 

    接下来需要计算的系数,这和、的计算方式不同,因为摄像头坐标系的坐标投影到近平面后总是-near。同时我们知道与x和y分量无关,因此,可进一步得到矩阵: 

    因为,所以可以得到: 

    又因为摄像头坐标系中 = 1,所以进一步得到: 

    同样的,代入与的映射关系:[-near,-far]映射到[-1,1],可得到: 

    又因为,可以进一步简化得到和的关系: 

    由公式(9)就可以知道A和B了,因此,最终的矩阵: 

    一般情况下,投影的视见体都是对称的,即满足left=−right,bottom=−top,那么可以得到: 


    则矩阵可以简化为: 

     


    除了可以通过(left,right,bottom,top,near,far)指定透视投影矩阵外,还可以通过函数glm::perspective指定视角(Fov)、宽高比(Aspect)、近平面(Near)、远平面(Far)来生成透视投影矩阵,如下所示,指定了45度视角,近平面和远平面分别是0.1f和100.0f:

     
    1. glm::mat4 proj = glm::perspective(glm::radians(45.0f), width/height, 0.1f, 100.0f);

    观察视角的示意图如下所示: 
    视角 
    通过视角指定的透视投影变换如下所示: 
     
    通过视角指定的透视投影矩阵的视见体是对称的: 
    透视投影矩阵的对称视见体 
    由上图可知,近平面的宽和高如下所示: 



    因为视见体是对称的,所以把公式(10)(11)代入已有的矩阵,可以得到由视角Fov表示的矩阵,如下所示: 

    通过矩阵左乘摄像头坐标系中的顶点,就把这些顶点变换到了裁剪坐标系。然后再经过透视除法,就变换到了NDC坐标系。

     

    正交投影

    相比于透视投影矩阵,正交投影矩阵要简单一些,如下所示: 
    正交投影
    因为正交投影不考虑远小近大的情况,所以正交投影矩阵的第4行始终为。

    对于正交投影变换,投影到近平面的坐标( , ) = ( , ),因此可以直接利用与、与、与的线性映射关系,求出线性方程系数。X、Y、Z轴的映射关系如下所示:

    映射关系映射值示意图
    与的映射关系[left , right]  [-1 , 1]$x_e$与$x_n$的映射关系
    与的映射关系[bottom , top]  [-1 , 1]$y_e$与$y_n$的映射关系
    与的映射关系[near , far]  [-1 , 1]$z_e$与$z_n$的映射关系

    根据上述的映射关系,同时摄像头坐标系的 = 1,可以得到三个线性方程,如下所示: 

    和的映射关系

     

    和的映射关系

     

    和的映射关系


    根据上述3个线性方程,可以得到正交投影矩阵: 

    如果视见体是对称的,即满足left=−right,bottom=−top,那么可以得到: 


    则正交投影矩阵可以进一步简化为: 

     

    视口变换

    经过投影变换和透视除法后,我们裁减掉了不可见物体,得到了NDC坐标。最后一步是把NDC坐标映射到屏幕坐标(,  , )。如下所示: 
    NDC坐标变换到屏幕坐标 
    在映射到屏幕坐标时,我们需要指定窗口的位置、宽高和深度。如下所示:

     
    1. //指定窗口的位置和宽高
    2. glViewport(GLint x , GLint y , GLsizei width , GLsizei height);
    3. //指定窗口的深度
    4. glDepthRangef(GLclampf near , GLclampf far);

    那么可以NDC坐标和屏幕坐标的线性映射关系:

    映射关系映射值
    与的映射关系[-1 , 1]  [x , x + width]
    与的映射关系[-1 , 1]  [y , y + height]
    与的映射关系[-1 , 1]  [near , far]

    因此,可以设置线性方程,求出系数K和常量P。 


    把上述映射关系代入线性方程,可以得到各个分量的参数值。

     

    坐标分量线性方程的系数K线性方程的常量P
    X分量线性方程 x + 
    Y分量线性方程 y + 
    Z分量线性方程  

    通过上述各个坐标分量值,可以得到视口变换矩阵: 


    因此,通过ViewPort矩阵左乘NDC坐标,就得到了屏幕坐标。

     

    对于2D屏幕,nearfar一般为0。因此ViewPort矩阵的第三行都是0。所以经过视口变换后,屏幕坐标的Z值都是0。

    至此,OpenGL的整个坐标变换过程都介绍完了,关键还是要多实践、实践、实践!!!

    展开全文
  • opengl坐标变换

    2011-12-13 22:20:23
    opengl画图时,会涉及到坐标变换,此实例会有很好的帮助。
  • OPENGL坐标 变换

    千次阅读 2012-12-02 19:14:25
    OPENGL坐标 变换,世界坐标系都不动,以屏幕中心为原点(0, 0, 0),你面对屏幕,右边是x正轴,上面是y正轴,屏幕指向你的为z正轴。当前绘图坐标系是绘制物体时的坐标系。程序刚初始化时,世界坐标系和当前绘图坐标 系...

    OPENGL坐标 变换,世界坐标系都不动,以屏幕中心为原点(0, 0, 0),你面对屏幕,右边是x正轴,上面是y正轴,屏幕指向你的为z正轴。当前绘图坐标系是绘制物体时的坐标系。程序刚初始化时,世界坐标系和当前绘图坐标 系是重合的,当用glTranslatef()等变换函数做移动和旋转时,都是改变的当前绘图坐标系,改变的位置都是当前绘图坐标系相对自己的x, y,z轴所做的改变,改变以后,再用glVertex3f()等绘图函数绘图时,都是在当前绘图坐标系进行绘图,所有的函数参数也都是相对当前绘图坐标系 来讲的。

    这里我们以GL为 概念。当我们输入glutSolidCube(4)的时候,它会产生这样的数 据:glVertex3f(2,2,2),glVertex3f(-2,-2,-2)等等,也就是长度为4的一个立方体。注意我们画的这个立方体的位置, 肯定是出现在“世界”的中心位置。如果我们希望它移动到其他的位置呢?只能先glTranslatef(),再glutSolidCube。这个 glTranslatef作用在MODELVIEW_MATRIX上,具体的形式请到OpenGL Wiki上看,那里连载了RedBook。

    比如我们输入glTranslatef(1.0,0,0);glutSolidCube(4),其实它产生的“真正顶点”是,(3,2,2), (-1,-2,-2),统统向x方向移动了一个位置。如果你在自己的范例程序里看不到是因为perspective中的far near planes没有设置好。这样我们就仿佛实现了平移以及旋转等等操作,注意,是仿佛。

    我们把变换顶点的矩阵一般称为MV(Model View)矩阵,把和在一起一步到位的矩阵称为MVP矩阵,在GLSL中就有gl_ModelViewMatirx和 gl_ModelViewProjectionMatrix这两个Uniform Matirx。我们输入一个顶点,希望把它放到这个世界的正确位置上,就需要乘以适合它自己的MV,因为不一样的模型当然需要不一样的世界位置。矩阵乘法 就可以完成这项神奇的工作。可是向量的概念则很大不同。

    一个正方体,只要它在我们的映像中从头到尾都是方方正正的立在场景中,它的向量,无论朝上朝下都应该是相同的,比如(1,0,0)左边的面,只要我们不旋 转这个正方体,它在Local坐标系还是变换后也应该是(1,0,0),这个时候我们用哪个矩阵呢?用MV显然不同,就需要用MV的Inverse Transpose。在线性代数中,求一个矩阵的Inverse然后Transpose,与先求Transpose再求Inverse,这两边是完全相等 的。在GLSL中,其实gl_Normal*gl_NormalMatrix等同于 gl_Normal*gl_ModelViewInverseTransposeMatrix。REDBOOK是这样说的:

    In other words, normal vectors are transformed by the inverse transpose of the transformation that transforms points.

    展开全文
  • OpenGl 坐标转换

    2016-06-20 13:10:36
    下面这篇文章详细讲述了OpenGL里的坐标转换,清晰,明了。但是其所谓的渲染管线只包括modelview 转换 和 投影变换,我觉得不是这样的。这只是从坐标角度吧。比如什么顶点着色、光栅化、送至帧缓存都没有涉及到。 ...

    下面这篇文章详细讲述了OpenGL里的坐标转换,清晰,明了。但是其所谓的渲染管线只包括modelview 转换 和 投影变换,我觉得不是这样的。这只是从坐标角度吧。比如什么顶点着色、光栅化、送至帧缓存都没有涉及到。

    原文地址:http://blog.csdn.net/zhulinpptor/article/details/5897102

    1. OpenGL 渲染管线

    OpenGL渲染管线分为两大部分,模型观测变换(ModelView Transformation)投影变换(Projection Transformation)。做个比喻,计算机图形开发就像我们照相一样,目的就是把真实的场景在一张照相纸上表现出来。那么观测变换的过程就像是我们摆设相机的位置,选择好要照的物体,摆好物体的造型。而投影变换就像相机把真实的三维场景显示在相纸上一样。下面就分别详细的讲一下这两个过程。

    1.1模型观测变换

    让我们先来弄清楚OpenGL中的渲染管线。管线是一个抽象的概念,之所以称之为管线是因为显卡在处理数据的时候是按照一个固定的顺序来的,而且严格按照这个顺序。就像水从一根管子的一端流到另一端,这个顺序是不能打破的。先来看看下面的图1:

    1                     图1 OPENGL渲染管线                                 

    图中显示了OpenGL图形管线的主要部分,也是我们在进行图形编程的时候常常要用到的部分。一个顶点数据从图的左上角(MC)进入管线,最后从图的右下角(DC)输出MC是Model Coordinate的简写,表示模型坐标DCDevice Coordinate的简写,表示设备坐标。当然DC有很多了,什么显示器,打印机等等。这里DC我们就理解成常说的屏幕坐标好了。MC当然就是3D坐标了(注意:我说的3D坐标,而不是世界坐标),这个3D坐标就是模型坐标,也说成本地坐标(相对于世界坐标)。MC要经过模型变换(Modeling Transformation)才变换到世界坐标,图2:

    2图2 世界坐标系和模型坐标系

    变换到世界坐标WC(World Coordinate)说简单点就是如何用世界坐标系来表示本地坐标系中的坐标。为了讲得更清楚一些,这里举个2D的例子。如图3:

    3图3 世界坐标系和模型坐标系的计算

    图中红色坐标系是世界坐标系WC,绿色的是模型坐标系MC。现在有一个顶点,在模型坐标系中的坐标为(1,1),现在要把这个模型坐标转换到世界坐标中来表示。从图中可以看出,点(1,1)在世界坐标系中的坐标为(3,4),现在我们来通过计算得到我们希望的结果。首先我们要把模型坐标系MC在世界坐标系中表示出来,使用齐次坐标(Homogeneous Coordinate )可以表示为矩阵(注意,本教程中使用的矩阵都是以列向量组成):gif.latex 其中,矩阵的第一列为MC中x轴在WC中的向量表示第二列为MC中y轴WC中的向量表示第三列为MC中的原点在WC中的坐标。对齐次坐标系不了解的同学,请先学习游戏数学方面的知识。有了这个模型变换矩阵后,用这个矩阵乘以在MC中表示的坐标就可以得到该坐标在世界坐标系中的坐标。所以该矩阵和MC中的坐标(1,1)相乘有:

    gif.latex2这也正是我们需要的结果。现在让我们把相机坐标也加进去,相机坐标也称为观测坐标(View Coordinate),如图4和图5。

    4图4 ModelView变换的三个坐标系

    5图5 ModelView变换计算

    来看看MC坐标中的点(1,1)如何在相机坐标中表示。从图5中可以直接看出MC中的点(1,1)在相机坐标系VC中为(-2,-2)。和上面同样的道理,我们可以写出相机坐标系VC在世界标系WC中可以表示为:

    gif.latex3那么世界坐标系中的点转换为相机坐标系中的点我们就需求VC的逆矩阵:

    gif.latex4那么世界坐标系WC中的点(3,4)在相机坐标系VC中坐标为:

    gif.latex5上面的变换过程,就是可以把模型坐标变换为相机坐标。在OpenGL中,当我们申明顶点的时候,有时候说的是世界坐标,这是因为初始化的时候世界坐标系、模型坐标系和相机坐标系是一样的,重合在一起的。所以OpenGL中提供了模型观测变换,它是把模型坐标系直接转换为相机坐标系,如图4。现在我们已经计算得到了VC-1和MC,如果把VC-1和MC相乘,就可以得到模型坐标在相机坐标中的表示。为了得到模型坐标系中的坐标在相机坐标系中的表示,这就是OpenGL中的ModelView变换矩阵。这也是ModelView变换的名字的由来,它是通过了上面两个步骤得到的。那么这里,ModelView变换矩阵M为:

    gif.latex6现在只要用上面的模型观测矩阵M乘以模型坐标系MC中的坐标就可以得到相机坐标系中的坐标了。模型观测变换的关键就是要得到相机坐标系中的坐标,因为光照等计算都是在这个这个坐标系中完成的。下面我们实际OpenGL程序中检查一下。在程序中,为了计算方便,我们使用图6中的模型。

    6图6 ModelView变换计算模型

    根据图中的数据,我们分别可以写出对应MC和VC-1,从而求得观测变换矩阵M

    gif.latex7现在程序中用glGetFloatv()这个函数来获得当前矩阵数据来检查一下。

    
     
    1. float m[16] = {0}; //用来保存当前矩阵数据  
    2. glMatrixMode(GL_MODELVIEW);  
    3. glLoadIdentity();  
    4. glGetFloatv(GL_MODELVIEW_MATRIX, m);   
    5. //相机设置,View 变换  
    6. gluLookAt(0.0, 0.0, 5.0,  
    7. 0.0, 0.0, 0.0,  
    8. 0.0, 1.0, 0.0);  
    9. glGetFloatv(GL_MODELVIEW_MATRIX, m);   
    10. //投影设置  
    11. glMatrixMode(GL_PROJECTION);  
    12. glLoadIdentity();  
    13. glOrtho(-10,10,-10,10,-10,10);  
    14. glMatrixMode(GL_MODELVIEW);   
    15. //Modeling变换  
    16. glTranslatef(0, 0, -3);  
    17. glGetFloatv(GL_MODELVIEW_MATRIX, m);  
    18. glBegin(GL_POINTS);  
    19. glVertex3f(1,1,0);  
    20. glEnd();  

    如果在上面程序段中最后一个glGetFloatv(GL_MODELVIEW_MATRIX, m)处设定断点的话,就可以看到图7所显示的数据。

    7图7 ModelView变换矩阵数据

    到这里,整个ModelView变换就完成了。通过ModelView变换后得到是相机坐标系内的坐标。在这个坐标系内典型的计算就是法线了。现在再来看看后面一个阶段。

     

    //

    我的理解:ModelView 变换矩阵,就是完成从模型坐标到View坐标的转换,是坐标系之间的大变换。注意,modelview 既有model,也有view。不只是一个model的矩阵。

    只是对model的进行平移或旋转的函数为  glTranslatef等函数,称作模型变换!它的坐标是基于模型本身的,即位于模型坐标系类,比如glTranslatef(0, 0, -3);的3个坐标值。

    只是针对view进行设置的函数为  gluLookAt,它的坐标系是view坐标系,比如

    1. gluLookAt(0.0, 0.0, 5.0,  
    2. 0.0, 0.0, 0.0,  
    3. 0.0, 1.0, 0.0);
    它里面的坐标的原点位于 相机坐标系的原点。参看下面的投影变换。

    /


    1.2投影变换

    先还是复习一下OpenGL的渲染管线。图1中可以看到,在投影变换(Projection Transformation)中也分为两个部分,第一个部分是将上个阶段得到的坐标转换为平面坐标,第二个部分是将转换后的平面坐标在进行归一化并进行剪裁。一般地,将三维坐标转换为平面坐标有两种投影方式:正交投影(Orthogonal Projection)和透视投影(Perspective Projection)

    1.2.1 正交投影

    正交投影很简单,如图8,对于三维空间中的坐标点和一个二维平面,要在对应的平面上投影,只需将非该平面上的点的坐标分量改为该平面上的坐标值,其余坐标不变。

    8图8 正交投影

    比如将点(1,1,5)正交投影到z=0的平面上,那么投影后的坐标为(1,1,0)。在openGL中,设置正交投影可以使用函数:

      
    1. glOrtho (GLdouble left, GLdouble right, GLdouble bottom, GLdouble top, GLdouble zNear, GLdouble zFar)  

    该函数可以设置正交投影的投影空间,在该空间以外的坐标点就不会被投影到投影平面上。函数中的六个参数分是投影空间六个平面,如图9:

    9

    图9 OpenGL正交投影空间和投影变换

    在图9中,大的投影空间是根据这六个参数设置的投影空间,OpenGL会自动将该空间归一化,也就是将该空间或立方体转化为变长为1的正六面体投影空间,并且该证六面体的中心在相机坐标系的原点。一旦设置使用glortho函数设置投影空间,OpenGL会生成投影矩阵。这个矩阵的作用就是将坐标进行正交投影并且将投影后的坐标正规化(转换到-1到1之间)。要注意的是,生成该矩阵的时候,OpenGL会把右手坐标系转换为左手坐标系。原因很简单,右手坐标系的Z轴向平面外的,这样不符合我们的习惯。该矩阵的矩阵推导这里就不详细说明了,不了解的同学可以参考游戏数学方面资料,这里只给出正交投影矩阵。

    15这个矩阵看来很复杂,其实计算很简单。举个例子,现在设置了这样的正交投影空间glOrtho(-10,10,-10,10,-10,10),这是个正六面体空间,变长为10。把这些参数带入上面的矩阵可以得到

    gif.latex8现在还是在OpenGL程序中来检查一下。在OpenGL程序中添加下面代码段:

    
      
    1. //投影设置  
    2. glMatrixMode(GL_PROJECTION);  
    3. glLoadIdentity();  
    4. glOrtho(-10,10,-10,10,-10,10);  
    5. glMatrixMode(GL_MODELVIEW);  
    6. glGetFloatv(GL_PROJECTION_MATRIX,m)  
    glGetFloatv(GL_PROJECTION_MATRIX,m)处设定断点就可以看到图10中所显示的信息。

    10图10 正交变换矩阵数据 

    1.2.2透视投影

    透视投影和正交投影最大的区别就是透视投影具有远近感。

    11

    图11 透视投影

    透视投影采用了图11中的模型,这样的模型就是保证远的物体看起来小,近的物体看起来大。 在OpenGL中设置透视投影可以使用函数:

    
      
    1. void APIENTRY gluPerspective (GLdouble fovy, GLdouble aspect, GLdouble zNear, GLdouble zFar);  

    该函数也会根据给定的参数生成一个投影空间。如图11中,该投影空间是一个截头体。同样地,OpenGL会自动生成透视投影矩阵,该矩阵也会让3D坐标投影在投影平面上,并且将投影后的坐标也进行正规化。下面也直接给出OpenGL中使用的透视投影矩阵。

    16下面在OpenGL中添加下面代码段:

    
      
    1. //投影设置  
    2. glMatrixMode(GL_PROJECTION);  
    3. glLoadIdentity();  
    4. gluPerspective(45, 1.0, 1.0, 100);  
    5. glMatrixMode(GL_MODELVIEW);  
    6. glGetFloatv(GL_PROJECTION_MATRIX,m)  

    设置断点后,我们可以看到图12中显示的数据。

    12图12 透视变换矩阵数据

    到此为止,整个投影变换就完成了。透过投影变换后得到的是正规化的投影平面坐标。这为下一个阶段的视口变换(View port Transformation)做好了准备。

    1.3视口变换

    现在到了最后一个阶段了。这个阶段叫做视口变换,它把上个阶段得到的正规化的投影坐标转化为windows 窗口坐标。视口变换会将投影平面上的画面映射到窗口上。在OpenGL中可以使用函数

      
    1. GLAPI void GLAPIENTRY glViewport (GLint x, GLint y, GLsizei width, GLsizei height);  

    来进行对窗口的映射,如图13。

    13
    图13 视口变换glViewport(width/2, 0, width/2, height/2)

    举个例子说明,比如上个阶段中得到了一个顶点的坐标为(0,0,0.5,1),根据这个坐标,该顶点位于投影平面的正中间。如果将该点映射到大小为50*50的窗口上时,那么它应该位于屏幕的中间,坐标为(25,25, 0.5,1)。当然这里深度值0.5是不会改变的。有的同学肯定有疑问了,既然投影到了窗口上,那么还要深度值0.5干什么?这里要注意的是,虽然在窗口上显示时只需要x,y坐标就够了,但是要在2D窗口上显示3D图形时深度值是不可少的。这里的深度值不是用于显示,而是用于在光栅化的时候进行深度测试。

    OpenGL也会根据glViewport函数提供的参数值生成一个视口变换矩阵

    17该矩阵把上个阶段得到的正规化坐标映射到窗口上,并且将正规化坐标中的深度值在转换到0到1之间。所以在深度缓冲中最大值为1,最小值为0。视口变换结束后,OpenGL中主要的图形管线阶段就算完成了,后面就是光栅化等等操作。再来回顾一下图1,现在相信大家对这个渲染管线有了一定的认识了,也明白了每一个阶段对应的变换矩阵以及如何进行坐标之间的转换的。

    2. 屏幕坐标转换为世界坐标

    通过前面的教程,以及现在大家对OpenGL整个渲染管线理解后,现在要将屏幕上一点坐标转换为世界坐标就比较容易了。从图形管线的开始到结束,一个模型坐标系中的坐标被转化为了屏幕坐标,那么现在把整个过程倒过来的话,屏幕上一点坐标也可以转为为世界坐标。只要在对应的阶段求得对应变换矩阵的逆矩阵,就可以得到前一个阶段的坐标。这整个过程可以用图14表示。

    14图14屏幕坐标转换为世界坐标

    图中显示的过程完全就是OpenGL渲染管线的逆过程,通过这个过程,屏幕上的点就可以转化为世界坐标系中的点了。可能又有的同学要问,当鼠标点击屏幕上一点的时候并没有深度信息,转换的时候要怎么办呢?这个时候可以使用OpenGL函数

    
      
    1. void glReadPixels (GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLvoid *pixels);  

    该函数能够获得屏幕上一点对应像素的深度信息。有了这个深度信息,就可以利用上面过程把屏幕上一点转换为世界坐标了。在OpenGL中,上面的过程其实已经有现成的函数可以使用,那就是

    
      
    1. int APIENTRY gluUnProject (  
    2.     GLdouble  winx, GLdouble  winy,  
    3.     GLdouble  winz,  
    4.     const GLdouble modelMatrix[16],  
    5.     const GLdouble projMatrix[16],  
    6.     const GLint    viewport[4],  
    7.     GLdouble  *objx,  GLdouble  *objy,  
    8.     GLdouble       *objz);  

    该函数直接将屏幕上一点转换对应的世界坐标,该函数的内部实现其实还是上面的那么逆过程。下面给出利用该函数获取世界坐标的代码段。

      
    1. GVector screen2world(int x, int y)  
    2. {  
    3.        GLint viewport[4];  
    4.        GLdouble modelview[16];  
    5.        GLdouble projection[16];  
    6.        GLfloat winX, winY, winZ;  
    7.        GLdouble posX, posY, posZ;  
    8.        glGetDoublev(GL_MODELVIEW_MATRIX, modelview);  
    9.        glGetDoublev(GL_PROJECTION_MATRIX, projection);  
    10.        glGetIntegerv(GL_VIEWPORT, viewport);  
    11.        winX = (float)x;  
    12.        winY = (float)viewport[3] - (float)y;  
    13.        glReadPixels(x, int(winY), 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT, &winZ);  
    14.        gluUnProject(winX, winY, winZ, modelview, projection, viewport, &posX, &posY, &posZ);  
    15.        GVector v(4, posX, posY, posZ, 1.0);  
    16.        return v;  
    17. }  
    代码中函数返回类型GVector是用户定义的向量类,返回的是齐次坐标。
    展开全文
  • openGL坐标变换

    2017-03-16 11:17:26
    实际上,相机和三维物体是相对位置,相机移动位置也可以理解为物体反向移动位置,因此这两步可以理解为一步,即调整三维物体和视点的位置,将世界坐标系下的物体变换到观察坐标系下。在计算机图形学中,定义模型矩阵...
    三维观察原理类似于拍一张照片的过程。(类似于red book )
    系列框图的左右对照来看,
    照相的第一步是将照相机调整位置置于三角架上;
    第二步是将物体调整位置放在相机的视野当中。实际上,相机和三维物体是相对位置,相机移动位置也可以理解为物体反向移动位置,因此这两步可以理解为一步,即调整三维物体和视点的位置,将世界坐标系下的物体变换到观察坐标系下。在计算机图形学中,定义模型矩阵(Modeling Matrix)实现了三维图形的几何变换,即平移、旋转和缩放。
    第三步相机进行调焦以便看清楚物体,并按下快门将三维物体投影在二维胶片上;在计算机图形学中,定义相应的投影矩阵(ProjectionMatrix),进行投影变换,把三维物体从观察坐标系转换到投影坐标系中。
    第四步是胶片的冲洗和裁剪。相应计算机图形学中,定义视口矩阵(ViewingMatrix),进行视口变换和裁剪,决定二维图象的大小。将物体从二维投影坐标系下转换到设备坐标系下。
    这样,一个三维空间里的物体被投影到二维平面上了,也就能在二维的计算机屏幕上正确显示了。这样的过程
    叫做三维观察流水线

    根据三维观察的过程,可以总结出三维变换流水线,

    三维物体坐标的变换过程:

    首先是模型坐标转换到世界坐标,

    然后转换到观察坐标,

    再到投影坐标,      

    再到设备坐标。      

    展开全文
  • opengl 坐标变换

    2013-10-07 23:01:51
    1. 坐标变换序列 对于OpenGL来说,从物体的世界坐标系到显示器的屏幕坐标,需要经过四个变换阶段,如下图所示。 首先通过Model-View Matrix将Obect Coordinates变换为Eye Coordinates(以观察者为原
  • OpenGL 坐标转换

    2014-09-05 15:31:01
    OpenGL绘图过程中,需要通过模型转换
  • opengl坐标转换

    2014-09-26 21:32:18
    1. 在opengl的世界中,(实际上就是view坐标系中,因为最终都是在view坐标系中进行观察模型的)任何一个vectex(顶点)都是由顶点坐标和ModelView变换矩阵唯一确定(注:该矩阵的下标即是该矩阵的存储顺序,即列主序...
  • OPENGL坐标变换

    2014-01-08 16:55:56
    1.glTranslate(x,y,z) 2.glScale(x,y,z) 3.glRotate*(angle,x,y,z) glRotate产生一个围绕vector(x,y,x),角度为angle...其中,c = cos(angle),s = sin(angle),并且||(x, y, z)|| = 1(如果不是,OpenGL ES会
  • OpenGL坐标转换

    千次阅读 2015-09-07 15:03:09
    1. OpenGL 渲染管线 ...OpenGL渲染管线分为两大部分,模型观测变换(ModelView Transformation)和投影变换(Projection Transformation)。做个比喻,计算机图形开发就像我们照相一样,目的就是把真实的场景在一
  • OpenGL: 屏幕坐标向OpenGL坐标转换

    千次阅读 2014-08-15 23:27:13
    屏幕坐标向OpenGL坐标转换 很多人用OpenGL绘图会遇到一个问题即屏幕坐标向OpenGL坐标转换,在网上流传着如下类似的代码: 注:(x, y)是屏幕坐标,(winX, winY, winZ)是视景体坐标及深度坐标,(posX, posY, posZ是...
  • OpenGL坐标变换专题

    2015-01-01 15:35:00
    OpenGL坐标变换专题(转) OpenGL通过相机模拟、可以实现计算机图形学中最基本的三维变换,即几何变换、投影变换、裁剪变换、视口变换等,同时,OpenGL还实现了矩阵堆栈等。理解掌握了有关坐标变换的内容,...
  • OpenGL 坐标变换(1)

    2015-10-30 10:05:24
    OpenGL学习脚印: OpenGL 坐标变换 写在前面  本节内容翻译和整理自http://www.songho.ca songho的博客《OpenGL Transformation》内容,以... 通过本节,你可以了解到: ... OpenGL坐标变换过程  理解OpenGL矩阵计算
  • 前面几节分别介绍了模型变换,视变换,以及给出了投影矩阵和视口变换矩阵的推导,本节从全局把握一遍OpenGL坐标转换的过程,从整体上认识坐标变换过程。相关矩阵的数学推导过程请参考前面几节对应的内容。 通过本节...
  • OpenGL坐标变换.pdf

    2020-03-18 11:37:05
    OpenGL 有一个限制——在同一个绘制过程中,相同的缓存不能同时作为输入和输出。这意味着如果我们想要在一个顶点缓冲区中更新粒子,我们实际上需要两个 transform feedback 缓存并在它们之间进行切换。在第 N 帧的...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 26,247
精华内容 10,498
关键字:

opengl坐标变换