2013年5月16日 星期四

OpenGL基本瞭解(十一) 投影變換及坐標系統

投影變換及坐標系統

官方說明資料   https://www.opengl.org/wiki/Main_Page


在OPENGL中采用的是三維笛卡爾坐標系。

PC版本OpenGl支持多種多邊形,但是在OpenGL ES下只支持三角形。
也就是說,所有圖形都由三角形構建,類似積分的原理。


在计算机图形学中有两种类型的投影方式:透视投影与正交投影。采用透视投影,物体越远越小,这样更接具真实性。正交投影往往用于2D绘制








投影變換

在OpenGL中,如果想对模型进行操作,就要对这个模型的状态(当前的矩阵)乘上这个操作对应的一个矩阵.
如果乘以变换矩阵(平移, 缩放, 旋转), 那相乘之后, 模型的位置被变换;
如果乘以投影矩阵(将3D物体投影到2D平面), 相乘后, 模型的投影方式被设置;
如果乘以纹理矩阵(), 模型的纹理方式被设置.
而用来指定乘以什么类型的矩阵, 就是glMatriMode(GLenum mode);
glMatrixMode有3种模式: GL_PROJECTION 投影, GL_MODELVIEW 模型视图, GL_TEXTURE 纹理.
所以,在操作投影矩阵以前,需要调用函数:
glMatrixMode(GL_PROJECTION); //将当前矩阵指定为投影矩阵
然后把矩阵设为单位矩阵:
glLoadIdentity();

然后调用glFrustum()或gluPerspective(),它们生成的矩阵会与当前的矩阵相乘,生成透视的效果;




有兩種投影(透視投影及正射投影)。投影要在創建的時候設置。

透視投影

一個函數:
void glFrustum(GLdouble left,GLdouble Right,GLdouble bottom,GLdouble top,GLdouble near,GLdouble far);
Ø  前四個参數是攝像頭視口。正常情況是成比例的。
Ø  第五個是近平面:可以認为近平面就對應着屏幕視口,不過近平面是Y向上,屏幕視口是Y向下。
Ø  第六個是遠平面:遠平面的一個作用是做裁切用。只有在近平面,元平面之間的物體才被顯示,超過的就裁切掉。

创建一个透视型的视景体。其操作是创建一个透视投影的矩阵,并且用这个矩阵乘以当前矩阵。这个函数的参数只定义近裁剪平面的左下角点和右上角点的三维空间 坐标,即(left,bottom,-near)和(right,top,-near);最后一个参数far是远裁剪平面的离视点的距离值,其左下角点和 右上角点空间坐标由函数根据透视投影原理自动生成。near和far表示离视点的远近,它们总为正值(near/far 必须>0)。






原始程式碼: https://www.opengl.org/wiki/GluPerspective_code

void glhFrustumf2(float *matrix, float left, float right, float bottom, float top,
                  float znear, float zfar)
{
    float temp, temp2, temp3, temp4;
    temp = 2.0 * znear;
    temp2 = right - left;
    temp3 = top - bottom;
    temp4 = zfar - znear;
    matrix[0] = temp / temp2;
    matrix[1] = 0.0;
    matrix[2] = 0.0;
    matrix[3] = 0.0;
    matrix[4] = 0.0;
    matrix[5] = temp / temp3;
    matrix[6] = 0.0;
    matrix[7] = 0.0;
    matrix[8] = (right + left) / temp2;
    matrix[9] = (top + bottom) / temp3;
    matrix[10] = (-zfar - znear) / temp4;
    matrix[11] = -1.0;
    matrix[12] = 0.0;
    matrix[13] = 0.0;
    matrix[14] = (-temp * zfar) / temp4;
    matrix[15] = 0.0;
}


另一個函數:
void gluPerspective(GLdouble fovy,GLdouble aspect,GLdouble zNear, GLdouble zFar);
Ø  参數fovy定義視野在X-Z平面的角度,範圍是[0.0, 180.0];
Ø  参數aspect是投影平面寬度與高度的比率;
Ø  参數zNear和Far分別是遠近裁剪面沿Z負軸到視點的距離,它們總为正值。

OpenGL的视角设定可以直接调用gluPerspective函数,但是OpenGL ES版本并不支持这个用法。ES的版本中只能支持glFrustum的函数调用。

创建一个对称的透视型视景体,但它的参数定义于前面的不同,如图。其操作是创建一个对称的透视投影矩阵,并且用这个矩阵乘以当前矩阵。参数fovy定义视 野在Y-Z平面的角度,范围是[0.0, 180.0];参数aspect是投影平面宽度与高度的比率;参数Near和Far分别是近远裁剪面到视点(沿Z负轴)的距离,它们总为正值。
  以上两个函数缺省时,视点都在原点,视线沿Z轴指向负方向。

void gluPerspective(
  GLdouble fovy, //角度
  GLdouble aspect,//视景体的宽高比
  GLdouble zNear,//沿z轴方向的两裁面之间的距离的近处
  GLdouble zFar //沿z轴方向的两裁面之间的距离的远处
  );










在OPENGL中我们可以使用gluPerspective来设置视椎体。PC版的gluPerspective在OPENGL3.0以後就好像不見了。
但是在OpenGLES中却没有提供这样的实用库支持,其实我们可以自己来完成这个函数的功能。代码如下:

//matrix will receive the calculated perspective matrix.
//You would have to upload to your shader
// or use glLoadMatrixf if you aren't using shaders.
void glhPerspectivef2(float *matrix, float fovyInDegrees, float aspectRatio,
                      float znear, float zfar)
{
    float ymax, xmax;
    float temp, temp2, temp3, temp4;
    ymax = znear * tanf(fovyInDegrees * M_PI / 360.0);
    //ymin = -ymax;
    //xmin = -ymax * aspectRatio;
    xmax = ymax * aspectRatio;
    glhFrustumf2(matrix, -xmax, xmax, -ymax, ymax, znear, zfar);
}


正射投影

正射投影的最大一個特點是無論物體距離相機多遠,投影後的物體大小尺寸不變。

函數:
void glOrtho(GLdouble left,GLdouble right,GLdouble bottom,GLdouble top, GLdouble near,GLdouble far)

六个参数, 前两个是x轴最小坐标和最大坐标,中间两个是y轴,最后两个是z轴值
它创建一个平行视景体(就是一个长方体空间区域)。
实际上这个函数的操作是创建一个正射投影矩阵,并且用这个矩阵乘以当前矩阵。
其中近裁剪平面是一个矩形,矩形左下角点三维空间坐标是(left,bottom,-near),
右上角点是(right,top,-near);远裁剪平面也是一个矩形,左下角点空间坐标是(left,bottom,-far),右上角点是(right,top,-far)。
注意,所有的near和far值同时为正或同时为负, 值不能相同。如果没有其他变换,正射投影的方向平行于Z轴,且视点朝向Z负轴。这意味着物体在视点前面时far和near都为负值,物体在视点后面时far和near都为正值。
只有在视景体里的物体才能显示出来。
如果最后两个值是(0,0),也就是near和far值相同了,视景体深度没有了,整个视景体都被压成个平面了,就会显示不正确。
























设置视图变换(Setting the View Transform)

设置视图矩阵最简单的方法就是用LookAt方法,它并不是OpenGL ES的内置函数,但是可以自已快速实现。它有三个参数:相机位置,目标位置,一个”up”向量表示相机朝向>

通过三个向量的传入,LookAt就可以生成一个变换矩阵,否则就得用基本的变换(缩放,移动,旋转)来生成。

一個參考的 LookAt函数
mat4 LookAt(const vec3& eye, const vec3& target, const vec3& up)
{
vec3 z = (eye - target).Normalized(); vec3 x = up.Cross(z).Normalized(); vec3 y = z.Cross(x).Normalized();
mat4 m;
m.x = vec4(x, 0);
m.y = vec4(y, 0);
m.z = vec4(z, 0);
m.w = vec4(0, 0, 0, 1);
vec4 eyePrime = m * -eye; m = m.Transposed();
m.w = eyePrime;
return m;
}


一旦设置了设影矩阵, 就设定了视野。视锥表示眼在金字塔顶部的一个锥体。

基于金字塔的顶点(称为视野)的角度,可以计算一个视锥。开发者认为这样比指定六面更加直观。示例2.2中方法有四个参数:视角,金字塔宽高比,远与近裁剪面。

示例方法有四个参数:视角,金字塔宽高比,远与近裁剪面。

void VerticalFieldOfView(float degrees, float aspectRatio, float near, float far)
{
     float top = near * std::tan(degrees * Pi / 360.0f);
     float bottom = -top;
     float left = bottom * aspectRatio;
     float right = top * aspectRatio;
    
     glFrustum(left, right, bottom, top, near, far);
}







息明點的位置

根據修行不難這本書所述

人坐在椅子上,臀部開始到肚臍兩者的中間位置。

由圖中看大約是下面虛線再下面一點點。
看起來就是下丹田的位置。





2013年5月14日 星期二

OpenGL基本瞭解(十) 齊次座標的矩陣運算

OPENGL 運算需要用到齊次座標的矩陣運算,因此將一些網路上找到的相關資料整理一下,作為參考

主要參考網址  
http://itouchs.blogspot.tw/2012/05/opengl-nate-robins-tutors-01.html
http://www.opengl-tutorial.org/beginners-tutorials/tutorial-3-matrices/

電腦圖學參考網址 http://caterpillar.onlyfun.net/Gossip/ComputerGraphics/



Transformation(轉換)

A. Homogeneous coordinates(齊次座標): 

      1. 齊次座標可以讓我們使用同一種數學方程式, 來對(頂點)向量處理:
          平移
, 縮放旋轉的問題.

      2. 在齊次座標中, 我們引入了 w 參數到原本三維的座標: (x, y ,z) 裡:
           (x, y, z, w), w 代表座標軸的遠近參數. 要將齊次座標轉換為三維座標,
           其方式為: (x/w,  y/w, z/w).

      3. (x, y, z, w) 在齊次座標中:
          當 w == 1, 代表此為空間中的一個位置(頂點). => (x/1, y/1, z/1) = (x, y, z)
          當 w == 0, 代表此為一個向量. => (x/0, y/0, z/0)

-------------------------------------------------------------------------------------

B. Transformation Matrices(轉換矩陣):
     1. 矩陣介紹:
          a. 簡單來說, 矩陣是一個在預先定義好(row, 水平向)與(column, 垂直向)
              的架構裡存放數值陣列. 舉例: 2(row) x 3(column) 矩陣如下所示:

          b. 在 3D 繪圖中, 我們會使用 4x4 矩陣. 藉由將矩陣與頂點相乘, 可以用來
              轉換 (x,y,z,w) 頂點:    

             (4x4)矩陣 x 頂點 = 轉換的頂點 (x', y', z', w')





glMultMatrixf(translation);


****************************************************************

     2. Identity Matrix(單位矩陣):
          a. 說明: 在線性代數中, n 階單位矩陣, 是一個 n x n 的方形矩陣, 其主對角線元素
                         為 1, 其餘元素為 0.

          b. 單位矩陣乘以任何座標(x, y, z, w), 其結果還是原始座標(x, y, z, w).



glLoadIdentity();


****************************************************************

    3. Translation Matrices(位移矩陣):
           a. 位移矩陣如下所示, 其中 X, Y, Z 分別為各軸的位移量.
           b. 例如: 將頂點 (10,10,10,1) 往 X 軸方向位移 10 個單位, 結果為: (20,10,10,1)
               (備註: 在此 w = 1, 所以是頂點)

           c. 接著, 來看看一個朝著負 Y 軸方向的向量 (0, 0, -1, 0), 往 X 軸方向位移 10
               單位, 其結果會是如何? (備註: 在此 w = 0, 所以是向量)
               結果: 原始向量不會改變.









float translation[16] = { 1, 0, 0, 0,
0, 1, 0, 0,
0, 0, 1, 0,
tx, ty, tz, 1 };


// The following two statements are equivalent.下面两种方法等效
glMultMatrixf(translation);
glTranslatef(tx, ty, tz);




****************************************************************

    4. Scaling Matrices(縮放矩陣):
          a. 縮放矩陣如下所示, 其中 X, Y, Z 分別為各軸的縮放比例.
          b. 例如, 對一個座標(x, y, z, w)在各方向放大 2 個單位:
          c. 備註:
               (1). w 值不會變.
               (2). 單位矩陣縮放矩陣的特例, 當其縮放比例: (X,Y,Z) = (1,1,1).
               (3). 單位矩陣亦為位移矩陣的特例, 當其各軸位移量: (X,Y,Z) = (0,0,0).


float scale[16] = { sx, 0,  0,  0,
      0,  sy, 0,  0,
      0,  0,  sz, 0
      0,  0,  0,  1 };
    
     // The following two statements are equivalent.下面两种方法等效
     glMultMatrixf(scale);
      glScalef(sx, sy, sz);




****************************************************************

     5. Rotation Matrices(旋轉矩陣):
      (圖片來源: 齊次座標)

****************************************************************

     6. Culumating Transformations(轉換計算):
          a. 例如: 轉換的座標 = 縮放矩陣 x 旋轉矩陣 x 位移矩陣 x 原始座標
              說明: 實際上矩陣相乘的順序為: 位移(先), 旋轉, 縮放(最後). 
                        (從右到左, 離遠始座標最近的先計算)

          b. 事實上, 以上的順序也是遊戲角色與其它物品常會使用到的:
              先置放位置(位移), 然後改變方向(旋轉), 接著縮放大小(如果有需要的話).

-----------------------------------------------------------------------------------


C. The Model, View and Projection Matrices
      1. 說明:
         Model, ViewProjection(投影) 矩陣, 是一個可以用來清楚的區分轉換的方便工具.

****************************************************************

      2. The Model Matrix:
          a. Model Space:
              所有的頂點是相對於此 model 的中心點而定義的, 亦即物件的中心點是 (0,0,0).
          b. World Space:
              所有的頂點是相對於世界的中心而定義的.
            c. 藉由 Model Matrix, 我們將一個 Model 從 Model Space 移到了 World Space.
****************************************************************

      3. The View Matrix:
           a. Camera Space:
               所有的頂點是相對於 camera 而定義的.


           b. 最初 camera 是在 World Space 的原點(0,0,0), 我們可藉由移動 camera 來
               相對的移動整個世界(從 camera 的觀點來看). 
           c. 所以, 將 camera 向右 (正 X 軸) 移動 3 個單位, 等同於將整個世界(包含世界
               座標裡的 Model)向左 (負 X 軸) 移動 3 個單位. 

           d. 藉由 View Matrix, 我們將一個 Model 從 World Space 移到了 Camera Space.

****************************************************************

      4. The Projection Matrix:
            a. Homogeneous Space:
                所有被定義在一個小立方體(cube)裡的頂點, 才會在螢幕上呈現.

            b. 在 Camera Space 中, 座標為 (0, 0) 的點將會被畫在螢幕的中心

            c. Perspective Projection(透視投影):
                我們不能只用 (x, y) 座標來決定物件擺放在螢幕的位置, 也要將物件到 camera
                的距離(z)計算在內. 對於二個有相似 x, y 座標的頂點, 其中有較大 z 座標的頂點
                (代表距離 camera 較近), 將會比另一個頂點更位在螢幕的中心處. 這就叫做
                透視投影
.

            d. 最後, 藉由 Projection Matrix, 我們將一個 Model 從 Camera Space 移到了
                 Homogeneous
Space.


2013年5月13日 星期一

OpenGL基本瞭解(九) 四元數在OPENGL的應用

Quaternion 四元數在OPENGL的應用

依照 Wiki 上的一篇介紹 Rotation/Orientation 表示法的 文章說法,要描述一個物體在三維空間內的旋轉(Rotation)/面向(Orientation)可以用很多不同的代數系統來表示。其中在 3D 繪圖裡最常用的三種是矩陣系統(Matrix)、尤拉系統(Euler Axis & Angles)、以及四元數系統(Quaternion)。

尤拉系統用於旋轉時會遇到一個很不幸的問題 - 萬向節鎖 (Gimbal Lock)。一篇說明什麼是萬向節鎖的文章

三維動畫師最厭惡的情況之一『萬向鎖(Gimbal Lock)』就是這個傢伙的問題。按不同軸以優拉角旋轉幾次後,出現x,y,z三個軸完全變成同向的情況,也就是說,優拉角很容易出現旋轉到最後只剩一個方向可以旋轉的情況,這就是恐怖的『萬向鎖』。

有一個影片可以作為說明  https://www.youtube.com/watch?v=rsKy-4dbA04

要避免萬向節鎖,一個方法為是轉換成四元數系統。


四元數是一個很深奧的數學概念,用來解決尤拉系統的旋轉問題只是它廣大應用中的一小部分。所以若直接去查閱有關四元數的教學文章是很吃力且發散的。兩篇文章不錯的說明: 一篇是 Wiki 上專門講解四元數用於旋轉問題的文章,另一篇是 GameDev 上介紹四元數的實例用途的文章。 建議以 GameDev 那篇為主、Wiki 那篇為輔去閱讀,因為 Wiki 那篇還是有一些數學導證,對於程式開發人員來說比較不是那麼重要。GameDev 那篇裡面提到的那些不同系統間的轉換公式,都是以 OpenGL 使用的右手座標系統為準去推導的。Wiki 那篇後面有一個章節比較了三個表示法在不同操作時的效能差異。很有趣的是,在進行連續多組旋轉互相乘積(組合)時,四元數所需的運算量比矩陣少的; 但是在進行與點乘積(取變換後位置)時,因為四元數還是要先轉換回矩陣才有辦法乘,所以是矩陣佔優勢。

有關四元數的資料可參考 wiki >>  http://zh.wikipedia.org/wiki/%E5%9B%9B%E5%85%83%E6%95%B8

在3D運算中的使用可參考 http://blog.csdn.net/kesalin/article/details/2187347


但四元數不是『絕對完美』,因為插值的時候過渡速率不恆定,且很難解決。不過這比起『恐怖萬向鎖』已經是很小的問題。


關於參考的OPENGL數學運算代碼可參考 
http://www.linuxgraphics.cn/opengl/opengl_quaternion.html

http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/quaternions/index.htm


對於3D的旋轉可參考Yaw, Pitch, Roll的含义 

主要參考網頁
1.   http://blog.roodo.com/sayaku/archives/19544672.html

2.   http://coazure-code.blogspot.tw/2010/01/pv3d.html

3.   http://www.wretch.cc/blog/zevoid/658309



以下是參考程式碼的轉貼

#pragma once

#include "Vector.h"
#include "GLESMath.h"

struct Quaternion
{
    float x;
    float y;
    float z;
    float w;
   
    Quaternion();
    Quaternion(float x, float y, float z, float w);
   
    Quaternion Slerp(float mu, const Quaternion& q) const;
    Quaternion Rotated(const Quaternion& b) const;
    Quaternion Scaled(float scale) const;
   
    float Dot(const Quaternion& q) const;
    void ToMatrix4(KSMatrix4 * m) const;
    Vector4<float> ToVector() const;
    void ToIdentity();
   
    Quaternion operator-(const Quaternion& q) const;
    Quaternion operator+(const Quaternion& q) const;
    bool operator==(const Quaternion& q) const;
    bool operator!=(const Quaternion& q) const;
   
    void Normalize();
    void Rotate(const Quaternion& q);
   
    static Quaternion CreateFromVectors(const Vector3<float>& v0, const Vector3<float>& v1);
    static Quaternion CreateFromAxisAngle(const Vector3<float>& axis, float radians);
};

inline Quaternion::Quaternion() : x(0), y(0), z(0), w(1)
{}

inline Quaternion::Quaternion(float x, float y, float z, float w) : x(x), y(y), z(z), w(w)
{}

inline void Quaternion::ToIdentity()
{
    x = y = z = 0;
    w = 1.0;
}

// Ken Shoemake's famous method.
inline Quaternion Quaternion::Slerp(float t, const Quaternion& v1) const
{
    const float epsilon = 0.0005f;
    float dot = Dot(v1);
   
    if (dot > 1 - epsilon) {
        Quaternion result = v1 + (*this - v1).Scaled(t);
        result.Normalize();
        return result;
    }
   
    if (dot < 0)
        dot = 0;
   
    if (dot > 1)
        dot = 1;
   
    float theta0 = acos(dot);
    float theta = theta0 * t;
   
    Quaternion v2 = (v1 - Scaled(dot));
    v2.Normalize();
   
    Quaternion q = Scaled(cos(theta)) + v2.Scaled(sin(theta));
    q.Normalize();
    return q;
}

inline Quaternion Quaternion::Rotated(const Quaternion& b) const
{
    Quaternion q;
    q.w = w * b.w - x * b.x - y * b.y - z * b.z;
    q.x = w * b.x + x * b.w + y * b.z - z * b.y;
    q.y = w * b.y + y * b.w + z * b.x - x * b.z;
    q.z = w * b.z + z * b.w + x * b.y - y * b.x;
    q.Normalize();
    return q;
}

inline Quaternion Quaternion::Scaled(float s) const
{
    return Quaternion(x * s, y * s, z * s, w * s);
}

inline float Quaternion::Dot(const Quaternion& q) const
{
    return x * q.x + y * q.y + z * q.z + w * q.w;
}

inline void Quaternion::ToMatrix4(KSMatrix4 * result) const
{
    const float s = 2;
    float xs, ys, zs;
    float wx, wy, wz;
    float xx, xy, xz;
    float yy, yz, zz;
    xs = x * s;  ys = y * s;  zs = z * s;
    wx = w * xs; wy = w * ys; wz = w * zs;
    xx = x * xs; xy = x * ys; xz = x * zs;
    yy = y * ys; yz = y * zs; zz = z * zs;
   
    result->m[0][0] = 1 - (yy + zz);
    result->m[0][1] = xy + wz;
    result->m[0][2] = xz - wy;
    result->m[0][3] = 0;
   
    result->m[1][0] = xy - wz;
    result->m[1][1] = 1 - (xx + zz);
    result->m[1][2] = yz + wx;
    result->m[1][3] = 0;
   
    result->m[2][0] = xz + wy;
    result->m[2][1] = yz - wx;
    result->m[2][2]= 1 - (xx + yy);
    result->m[2][3] = 0;
   
    result->m[3][0] = 0;
    result->m[3][1] = 0;
    result->m[3][2] = 0;
    result->m[3][3] = 1;
}

inline Vector4<float> Quaternion::ToVector() const
{
    return Vector4<float>(x, y, z, w);
}

inline Quaternion Quaternion::operator-(const Quaternion& q) const
{
    return Quaternion(x - q.x, y - q.y, z - q.z, w - q.w);
}

inline Quaternion Quaternion::operator+(const Quaternion& q) const
{
    return Quaternion(x + q.x, y + q.y, z + q.z, w + q.w);
}

inline bool Quaternion::operator==(const Quaternion& q) const
{
    return x == q.x && y == q.y && z == q.z && w == q.w;
}

inline bool Quaternion::operator!=(const Quaternion& q) const
{
    return !(*this == q);
}

inline void Quaternion::Normalize()
{
    *this = Scaled(1 / sqrt(Dot(*this)));
}

inline void Quaternion::Rotate(const Quaternion& q2)
{
    Quaternion q;
    Quaternion& q1 = *this;
   
    q.w = q1.w * q2.w - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z;
    q.x = q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y;
    q.y = q1.w * q2.y + q1.y * q2.w + q1.z * q2.x - q1.x * q2.z;
    q.z = q1.w * q2.z + q1.z * q2.w + q1.x * q2.y - q1.y * q2.x;
   
    q.Normalize();
    *this = q;
}

// Compute the quaternion that rotates from a to b, avoiding numerical instability.
// Taken from "The Shortest Arc Quaternion" by Stan Melax in "Game Programming Gems".
//
inline Quaternion Quaternion::CreateFromVectors(const Vector3<float>& v0, const Vector3<float>& v1)
{
    if (v0 == -v1)
        return Quaternion::CreateFromAxisAngle(vec3(1, 0, 0), Pi);
   
    Vector3<float> c = v0.Cross(v1);
    float d = v0.Dot(v1);
    float s = sqrt((1 + d) * 2);
   
    Quaternion q;
    q.x = c.x / s;
    q.y = c.y / s;
    q.z = c.z / s;
    q.w = s / 2.0f;
   
    return q;
}

inline Quaternion Quaternion::CreateFromAxisAngle(const Vector3<float>& axis, float radians)
{
    Quaternion q;
    q.w = cos(radians / 2);
    q.x = q.y = q.z = sin(radians / 2);
    q.x *= axis.x;
    q.y *= axis.y;
    q.z *= axis.z;
   
    return q;
}