2013年6月6日 星期四

OpenGL基本瞭解(十四) (光照基本資料)


一. 現實中的光照模型

1. 顏色是什麼?
在沒有學過光學的人的認識中,顏色是物體的自身屬性,很多的物體的顏色是一成不變的,比如雪是白色,樹葉是綠色,血液是紅色。但如果我們試着呆在一 個隔絕任何光線的純黑小屋中,我們會發現所有顏色都消失了,於是我們可以得到這样的結論:顏色並不是這样物體自身的屬性,不同的顏色,是物體表面光學特性 和光線相互作用的結果。
日光是所有可見光的集合,照射與物體表面時,某些波長的光被吸收,某些波長的光線則被反射,反射的光線最終進入人的眼睛,投影在視網膜,形成了顏色。比如紅色就是物體表面反射紅光,吸收其餘光線的結果。
2. 物體表面和光線不同夾角對顏色產生的影響:
我們都有這样的經曆,玻璃杯,瓷器等表面光滑的物件,無論表面原來的顏色是什麼,在日光下總會有一塊純白的“高光”區域,並且這個區域會隨着光照方 向的變化或者人觀察角度的變化,在物體表面移動。說明對於表面光滑的物體而言,顏色不但和表面特性,光線相關,還和光照角度和觀察角度相關。
3. 物體表面光滑度對顏色的影響:
同一束光線,照在同样顏色的光滑表面上和粗糙表面上,視覺效果也是不同的,明顯的區別是,在光滑表面上,通常都有那種前面說的高光效果,而且越光滑 越明顯,在粗糙表面上,那種高光效果消失了,顏色的分布變的很均勻,顏色的變化也變的很平滑, 改變光線照射的角度,依然會對顏色的分布產生影響,但是似乎已經和觀察角度無關了。這種反射,被稱为漫反射,實際上漫反射依然嚴格遵循光線的反射和折射定 律,只不過光線在接觸粗糙表面時,在微觀尺度上表面的不規則形狀之間進行了很多次的鏡面反射,因此出射角度已經隨機分布。 不過在計算機處理漫反射時,如果對微觀尺度上的鏡面反射進行完整的模擬,計算量過於龐大,所以會將其近似为均勻向所有方向散射。
4. 自發光物體在光線中產生的顏色:
如果我們要模擬的物體本身是個光源,那麼它在外部光源下會呈現什麼? 我們可以自己動手實驗 - 比如將發綠光的物體,置於紅色外部光源下,可以觀察到黃色。因此我們可以得到的結論是自發光體的顏色由自發光顏色和外部光源叠加產生。


运行 OpenGL 程序在屏幕上显示的最终颜色,受场景中光线的特性以及物体反射和吸收光的属性(即材质)影响。 

在OpenGL 光照模型中光源和光照效果可以细分为红,绿,蓝三个部分,光源由红,绿,蓝强度来定义,而物体表面材料由其反射红,绿,蓝的程度和方向来定义。OpenGL 光照模型使用的计算公式是对于现实世界光照的一个近似但效果非常好并适合快速计算。

OpenGL 光照模型中定义的光源可以分别控制,打开或关闭,OpenGL ES支持最多八个光源。

OpenGL 光照模型中最终的光照效果可以分为四个组成部分:Emitted(光源), ambient(环境光),diffuse(漫射光)和specular(镜面反射光),最终结果由这四种光叠加而成。

http://www.imobilebbs.com/wordpress/wp-content/uploads/2011/07/20110725002.png

1. Emitted (emission)发射光: 一般只发光物体或者光源,这种光不受其它光源的影响。

2. ambient 環境光: 指光线经过多次反射后已经无法得知其方向(可以看作来自所有方向),可以成为环境光,该光源如果射到某个平面,其反射方向为所有方向。 Ambient 不依赖于光源的方向。就是哪些在环境中进行了充分散射的光,而无法分辨其方向的光。光线在物体表面上向各个方向上均匀泛射,场景中的物体都会泛射光,这些泛射光又会照射到其他 物体上继续被泛射,直到光子能量耗尽为止,这样整个场景中散布着这样的泛射光。在编程时,可通过设置一个颜色常量来表示环境光或使用 ambient occlusion map (环境闭包贴图)来处理环境光。在 OpenGL 中,全局环境光的强度为 (0.2, 0.2, 0.2, 1.0),这弱弱的白色全局环境光确保即使没有额外的光源,场景中的物体依然是可见的。在白天非陽光直射,和外部光源很多的情況下,現實環境中會彌散着幾乎一致的亮度,OpenGL將這部分光稱作環境光,它给3D世界整體亮度設定了基線。

3. diffuse 漫反射光:当一束平行的入射光线射到粗糙的表面时,因面上凹凸不平,所以入射线虽然互相平行,由于各点的法线方向不一致,造成反射光线向不同 的方向无规则地反射,这种反射称之为“漫反射”或“漫射”。这个反射的光则称为漫射光。漫射光射到某个平面时,其反射方向也为所有方向。diffuse 只依赖于光源的方向和法线的方向。因此為只和表面與光線入射夾角有關的光線。我们之所以能看到物体,就是因为物体将入射的光然后向各个方向反射(所以称之为漫反射)。物体的漫反射材质属性对物体的颜色起着决定性作用。

4. specular 鏡面反射光: 一般指物体被光源直射的高亮区域,也可以成为镜面反射区,如金属。specular依赖于光源的方向,法线的方向和视角的方向。不但和表面與光線入射夾角有關,而且還和觀察角度有關的光線,並且根據表面光滑程度,呈現不同亮度,越光滑,越容易產生高光區域。在 OpenGL 中,镜面光的强度可通过光泽度(shininess)来调节。 

 

同样的,對應於光線的類型,OpenGL也定義了3種材質屬性(Material):
Ambient,Diffuse, Specular. 每種材質類型的值實際上是代表各自的光線類型在最終合成的亮度上的權重 。也可以看作在物體表面,鏡面反射,漫反射和環境光反射各占多大成分。

尽管光源可能只发送某一频率的光线,但ambient,diffuse和specular可能不同。比如使用白光照射一堵红墙,散射的光线可能为红色。OpenGL允许为光源分别设置红,绿,蓝三个元素的值。
最终决定所看到物体的颜色除了光源的颜色和方向外,还取决于物体本身的颜色,比如红色的光照在红色的物体和蓝色的物体,最终看到的物体一个还是红 色,一个为黑色。OpenGL 中对物体材料(Material)的颜色是通过其反射红,绿,蓝的比例来定义的。 和光源一样,物体的颜色也可以有不同的ambient,diffuse和specular,表现为反射这些光的比例。ambient,diffuse反射 通常为同样的颜色,而specular常常表现为白色或灰色光,如使用白光照射一个红色的球,球的大部分区域显示为红色,而高亮区域为白色。




如何决定入射光和表面的夾角 - 法向量:
OpenGL中的表面,都是由三角形組成的,三角形的三個頂點,可以决定一個平面,凡是平面,就會有法線,所以我們只要知道了這個平面的法向量,那麼
入射光和平面的 90入射光向量和法向量的
不過不同於前述的一個三角形對應一個法向量,OpenGL中的法線向量是和頂點聯系在一起的(具體原因也許是數據組織的方便性),因此在光照模型 中,需要给每個頂點指定法向量,如不指定,則默認为(0,0,1)。最終三角形內部各個位置的法向量也由3個頂點的法向量線性插值而來。不過通常我們會保 持三角形三個頂點的法向量相同。


二,光照的计算


OpenGL色的最合成:
我們已經知道了這4種類型的光線,以及它們各自的反射亮度受哪些因素的影響,那麼它們如何互相影響,最終决定一個頂點的顏色呢?
OpenGL给出了一套光線合成的標准公式,不論真實世界的光線是否真由如此組成,但這套公式無疑從經驗上是最接近現實效果的。
以下是OpenGLES1.1的固定渲染管線中,它們的合成公式:

vertex color =
the material emission at that vertex +
the global ambient light scaled by the material's ambient property at that vertex +
the ambient, diffuse, and specular contributions from all the light sources, properly attenuated
最終合成的顏色,即由自發光,全局環境光和所有點光源三部分叠加而成.

Ambient + Diffuse + Specular = Final (圖右)

Ambient + Diffuse + Specular = Final

其中全局環境光的公式为:
global ambient light = ambientlight model * ambientmaterial
點光源部分又由ambient,diffuse,specular三種光源各自的公式相加而成,並且光強按離光源的距離衰減:

contribution = attenuation factor * spotlight effect *
(ambient term + diffuse term + specular term)


另外衰減因子(attenuation factor) 和距離相關,距離越大,衰減因子越小。如不考慮衰減,則可用常量1代替。

光照计算的终极公式

光照颜色 = 发射颜色 + 全局环境颜色 + (环境颜色 + 漫反射颜色 + 镜面反射颜色) × 聚光灯效果 × 衰减因子
如果场景中有多个光源(包括环境光),那么分别计算来每个光源的光照颜色,然后把这些光照颜色累加即可。如果物体不发射光,则没有发射颜色这一成分。

以下是個別次項目的計算公式

1. 发射光计算

前面说过,在 OpenGL 中是给物体材质设置发射颜色来模拟发射光的,因此它的计算非常简单:
发射颜色 = 物体的发射材质颜色



2. 环境光计算

前面说到,一般我们是设置一个颜色常量来表示环境光(或来自 ambient occlusion map),因此环境光的计算也是很简单的。

其中環境光源公式:
ambient term=ambientlight *ambientmaterial
(环境颜色 = 光源的环境光颜色 × 物体的环境材质颜色)


3. 漫反射光计算

散射光源公式:

漫反射颜色 = 光源的漫反射光颜色 × 物体的漫反射材质颜色 × DiffuseFactor
其中漫反射因子 DiffuseFactor 是光线与顶点法线向量的点积:
(df)DiffuseFactor = max(0, dot(N, L))      ***  0 <= df <= 1

在图形学中,点积几何意义其实就是表示两个向量之间夹角的 cos 值。因此这个公式直观地揭示了漫反射的规律:顶点法线正对入射光线,漫反射效果最强,顶点法线背对入射光线(角度大于等于90度)就完全没有漫反射效果。

diffuse_light

In the diagram, L is the unit length vector pointing to the light source, and N is the surface normal, which is a unit-length vector that's perpendicular to the surface.

注意:在光照计算中,顶点法线必须是经过规范化的(normalize)。



如果將光源設在無限遠處,則可以簡化公式如下




我们可以设置一个颜色常量来表示漫反射材质颜色,来得到平滑颜色的表面。但有些物体表面是很粗糙的,如裂缝,隆起,刮痕等。为了获得这样的粗糙表面 效果,可以使用 displaced polygon 技术,但这需要大量的计算,效率低下。另外一种更为高效的方式就是使用凹凸贴图(bump map)。凹凸贴图就是一种纹理,其内容包含编码在RGB颜色空间中的经过扰动的顶点法线。在使用凹凸贴图进行漫反射计算时时,首先和前面一样用正常法线 计算出 NormalDiffuseFactor,再根据扰动的顶点法线计算出 PerturbedDiffuseFactor,然后将两者相乘作为最终的 DiffuseFactor。如下图所示:

 diffuse  diffuse_bump_map


4. 鏡面光源公式:

镜面反射颜色 (Specular Lighting)= 光源的镜面光颜色 × 物体的镜面材质颜色 × SpecularFactor
SpecularFactor = power(max(0, dot(N, H)), shininess)
H = normalise(L + E)

當觀眾在無限遠處時,E可以簡化成  [0, 0, 1]

与漫反射不同,镜面反射受观察者的位置影响,这一点在上面的计算公式中可以清楚地看出来。

1. H 向量是视线向量 E 与光线向量 L 的半向量(注意:它经过规划化的)H向量又稱為半角(half-angle),因為它是位於E與L的中間。

2. L代表的是光線入射方向的反向,其几何意义就是视线与光线夹角的平分线。

3. 而 H 和 N 的点积的几何意义就是说这个平分线与法线的夹角的 cos 值,然后将这个 cos 值进行 shininess 次乘方计算得到最终的镜面反射因子 Specularfactor。

这里的关键点在于视线与光线的平分线与法线的点积计算上,这表示,当视线与光线在表面处的反射光线夹角(可看成是N与H的夹角)越少时,镜面反射效果最明显(角度越小,cos 值越大)
一般可使用常量颜色作为镜面材质颜色,但这样得到的效果有时候并不理想。同漫反射一样,我们也可以使用镜面贴图来获得更好的镜面反射效果。镜面贴图通过控制物体表示上特定像素允许的镜面反射强度来获得较为真实的效果。



specular_theroy
 

普通镜面反射                               镜面贴图反射
specular   specular_map

5. 衰减因子

光源发射的光线在其传播过程中,会与空气中的其他粒子碰撞,其能量会逐渐衰减。在 OpenGL 中,这是通过将光照强度乘以随传播距离变化的衰减因子来模拟实现的。这个衰减因子的计算公式如下:
衰减因子 = 1.0/(距离衰减常量 + 线性衰减常量 × 距离 + 二次衰减常量 × 距离的平方)
其中距离衰减常量,线性衰减常量和二次衰减常量均为常量值。
注意:环境光,漫反射光和镜面光的强度都会受随着距离的增大而衰减,只有发射光和全局环境光的强度不会受此影响。


6. 聚光灯因子

在这里,我们讨论聚光灯的发射光照计算(也即位置型光源:如台灯,相对方向性光源:如太阳)。聚光灯就是朝某个特定发射发射光线的光源。你可以想象 下漆黑的夜晚里,一个手电筒给与你光明的这个场景,手电筒就是一个很好的聚光灯示例。聚光灯的计算分为两部分:在光线照射角度范围之外的部分被忽略,只有 在照射角度范围之内的部分需要计算。
聚光灯夹角cos值 = power(max(0, dot(单位光源向量, 单位光线向量)), 聚光灯指数)
其中单位光线向量是从光源指向顶点的单位向量,聚光灯指数表示聚光灯的亮度程度。前面说过点积的几何意义就是表示角度的,这里聚光灯因子就表示光源向量与光线向量之间的夹角。

而为了模拟真实聚光灯光环效果,在照射角度范围之内与之外的接壤处,设置一个渐变过渡区域,以避免光照从有到无巨变:
无过渡
有过渡
spotlight spotlight_falloff
增加过渡区之后的计算如下:
spotlight_falloff_compute
内环是完全光照的,而过渡区域(中环)是从完全光照到完全没有光照的渐变过渡,外环是完全没有光照的。
聚光灯因子 = clamp((外环的聚光灯角度cos值 - 当前顶点的聚光灯角度cos值)/(外环的聚光灯角度cos值 - 内环聚光灯角度cos值), 0, 1)
clamp 函数是将聚光灯因子限定在[0, 1]之间,这样,内环是完全光照的,而中环是从完全光照到没有光照的渐变过渡,外环是没有光照的。因此:
聚光灯效果 = 聚光灯光源颜色  × 聚光灯因子




三,高洛德着色(Gouraud Shading)与冯氏着色(Phong Shading)

在图形渲染中有两种着色方式,高洛德着色与冯氏着色。高洛德着色也被称为Per-Vertex着色,它是在顶点着色阶段对顶点进行颜色计算,然后在 光栅化阶段对这些顶点颜色进行线性插值形成片元的颜色;冯氏着色也被称为Per-Pixel像素着色,它是在片元着色阶段对每一个片元(像素)进行颜色计 算。无疑,插值的颜色效果没有针对每一个片元进行颜色计算的效果好(除非你的图元切分到像素近似大小,不过这样 GPU 肯定吃不消,计算量巨大!)。
Gouraud 着色 Phong 着色
Gauroud Phong




參考網頁
http://www.cnblogs.com/kesalin/archive/2012/12/29/light_theroy.html
http://rritw.com/a/bianchengyuyan/C__/20130306/318310.html

2013年6月5日 星期三

Xcode 上可參考使用的math library


1. oolongengine

Logo

http://code.google.com/p/oolongengine/

The Oolong Engine is written in C++ with some help from Objective-C. It will help you to create new games and port existing games to the iPhone, the iPod touch and the iPad. Here is its feature list:
  • OpenGL ES 1.1 and OpenGL 2.0 (> iPhone 3GS, iPod touch third gen, iPad) support
  • Math library that supports floating-point calculations with an interface very similar to the D3D math library
  • Support for numerous texture formats including the PowerVR 2-bit, 4-bit and normal map compression formats
  • Support for PowerVR's POD (Scene and Meshes), .3DS and .blend file formats
  • Touch screen support
  • Accelerometer support
  • Text rendering to support a basic UI
  • Timing: several functions that can replace rdstc, QueryPerformance etc.
  • Profiler: industry proven in-game profiler
  • Resources streaming system
  • Bullet SDK support (for 3D Physics)
  • Audio engine with OpenAL support
  • Networking with the ENet library
  • Industry proven memory manager from http://www.fluidstudios.com

2013年6月4日 星期二

OpenGL基本實作(九)

將iphone 3D上SimpleWireframe這個例子轉移到Ipad上,以瞭解細節設計,並在程式上加上個別注解。此例是實作五的另一個版本,複雜度稍高一點。

1. 首先開一個新的專案




2.加入所需檔案



 3. 先從Interfaces.hpp開始

#pragma once
#include "Vector.hpp"
#include "Quaternion.hpp"
#include <vector>
#include <string>

using std::vector;
using std::string;

enum VertexFlags {
    VertexFlagsNormals = 1 << 0,  //  ==1
    VertexFlagsTexCoords = 1 << 1,  // ==2
};

struct IApplicationEngine {
    virtual void Initialize(int width, int height) = 0;
    virtual void Render() const = 0;
    virtual void UpdateAnimation(float timeStep) = 0;
    virtual void OnFingerUp(ivec2 location) = 0;
    virtual void OnFingerDown(ivec2 location) = 0;
    virtual void OnFingerMove(ivec2 oldLocation, ivec2 newLocation) = 0;
    virtual ~IApplicationEngine() {}
};

struct ISurface {
    virtual int GetVertexCount() const = 0;
    virtual int GetLineIndexCount() const = 0;
    virtual int GetTriangleIndexCount() const = 0;
    virtual void GenerateVertices(vector<float>& vertices,
                                  unsigned char flags = 0) const = 0;
    virtual void GenerateLineIndices(vector<unsigned short>& indices) const = 0;
    virtual void GenerateTriangleIndices(vector<unsigned short>& indices) const = 0;
    virtual ~ISurface() {}
};

//Describes the dynamic visual properties of a surface; gets passed from the application engine to the rendering engine at every frame.
struct Visual {
    vec3 Color;
    ivec2 LowerLeft;
    ivec2 ViewportSize;
    Quaternion Orientation;
};

struct IRenderingEngine {
    virtual void Initialize(const vector<ISurface*>& surfaces) = 0;
    virtual void Render(const vector<Visual>& visuals) const = 0;
    virtual ~IRenderingEngine() {}
};

// 此處的CreateApplicationEngine有重複到Function name,因此使用namespace
namespace ParametricViewer { IApplicationEngine* CreateApplicationEngine(IRenderingEngine*); }
//namespace ObjViewer    { IApplicationEngine* CreateApplicationEngine(IRenderingEngine*, IResourceManager*); }
//namespace Darwin       { IResourceManager* CreateResourceManager(); }
namespace WireframeES1 { IRenderingEngine* CreateRenderingEngine(); }
namespace WireframeES2 { IRenderingEngine* CreateRenderingEngine(); }

4.  ParametricEquations.hpp 六個3D Model的 Equation

#include "ParametricSurface.hpp"

class Cone : public ParametricSurface {
public:
    Cone(float height, float radius) : m_height(height), m_radius(radius)
    {
        ParametricInterval interval = { ivec2(20, 20), vec2(TwoPi, 1), vec2(30, 20) };
        SetInterval(interval);
    }
    vec3 Evaluate(const vec2& domain) const
    {
        float u = domain.x, v = domain.y;
        float x = m_radius * (1 - v) * cos(u);
        float y = m_height * (v - 0.5f);
        float z = m_radius * (1 - v) * -sin(u);
        return vec3(x, y, z);
    }
private:
    float m_height;
    float m_radius;
};

class Sphere : public ParametricSurface {
public:
    Sphere(float radius) : m_radius(radius)
    {
        ParametricInterval interval = { ivec2(20, 20), vec2(Pi, TwoPi), vec2(20, 35) };
        SetInterval(interval);
    }
    vec3 Evaluate(const vec2& domain) const
    {
        float u = domain.x, v = domain.y;
        float x = m_radius * sin(u) * cos(v);
        float y = m_radius * cos(u);
        float z = m_radius * -sin(u) * sin(v);
        return vec3(x, y, z);
    }
private:
    float m_radius;
};

class Torus : public ParametricSurface {
public:
    Torus(float majorRadius, float minorRadius) :
    m_majorRadius(majorRadius),
    m_minorRadius(minorRadius)
    {
        ParametricInterval interval = { ivec2(20, 20), vec2(TwoPi, TwoPi), vec2(40, 10) };
        SetInterval(interval);
    }
    vec3 Evaluate(const vec2& domain) const
    {
        const float major = m_majorRadius;
        const float minor = m_minorRadius;
        float u = domain.x, v = domain.y;
        float x = (major + minor * cos(v)) * cos(u);
        float y = (major + minor * cos(v)) * sin(u);
        float z = minor * sin(v);
        return vec3(x, y, z);
    }
private:
    float m_majorRadius;
    float m_minorRadius;
};

class TrefoilKnot : public ParametricSurface {
public:
    TrefoilKnot(float scale) : m_scale(scale)
    {
        ParametricInterval interval = { ivec2(60, 15), vec2(TwoPi, TwoPi), vec2(100, 8) };
        SetInterval(interval);
    }
    vec3 Evaluate(const vec2& domain) const
    {
        const float a = 0.5f;
        const float b = 0.3f;
        const float c = 0.5f;
        const float d = 0.1f;
        float u = (TwoPi - domain.x) * 2;
        float v = domain.y;
       
        float r = a + b * cos(1.5f * u);
        float x = r * cos(u);
        float y = r * sin(u);
        float z = c * sin(1.5f * u);
       
        vec3 dv;
        dv.x = -1.5f * b * sin(1.5f * u) * cos(u) -
        (a + b * cos(1.5f * u)) * sin(u);
        dv.y = -1.5f * b * sin(1.5f * u) * sin(u) +
        (a + b * cos(1.5f * u)) * cos(u);
        dv.z = 1.5f * c * cos(1.5f * u);
       
        vec3 q = dv.Normalized();
        vec3 qvn = vec3(q.y, -q.x, 0).Normalized();
        vec3 ww = q.Cross(qvn);
       
        vec3 range;
        range.x = x + d * (qvn.x * cos(v) + ww.x * sin(v));
        range.y = y + d * (qvn.y * cos(v) + ww.y * sin(v));
        range.z = z + d * ww.z * sin(v);
        return range * m_scale;
    }
private:
    float m_scale;
};

class MobiusStrip : public ParametricSurface {
public:
    MobiusStrip(float scale) : m_scale(scale)
    {
        ParametricInterval interval = { ivec2(40, 20), vec2(TwoPi, TwoPi), vec2(40, 15) };
        SetInterval(interval);
    }
    vec3 Evaluate(const vec2& domain) const
    {
        float u = domain.x;
        float t = domain.y;
        float major = 1.25;
        float a = 0.125f;
        float b = 0.5f;
        float phi = u / 2;
       
        // General equation for an ellipse where phi is the angle
        // between the major axis and the X axis.
        float x = a * cos(t) * cos(phi) - b * sin(t) * sin(phi);
        float y = a * cos(t) * sin(phi) + b * sin(t) * cos(phi);
       
        // Sweep the ellipse along a circle, like a torus.
        vec3 range;
        range.x = (major + x) * cos(u);
        range.y = (major + x) * sin(u);
        range.z = y;
        return range * m_scale;
    }
private:
    float m_scale;
};

class KleinBottle : public ParametricSurface {
public:
    KleinBottle(float scale) : m_scale(scale)
    {
        ParametricInterval interval = { ivec2(20, 20), vec2(TwoPi, TwoPi), vec2(15, 50) };
        SetInterval(interval);
    }
    vec3 Evaluate(const vec2& domain) const
    {
        float v = 1 - domain.x;
        float u = domain.y;
       
        float x0 = 3 * cos(u) * (1 + sin(u)) +
        (2 * (1 - cos(u) / 2)) * cos(u) * cos(v);
       
        float y0  = 8 * sin(u) + (2 * (1 - cos(u) / 2)) * sin(u) * cos(v);
       
        float x1 = 3 * cos(u) * (1 + sin(u)) +
        (2 * (1 - cos(u) / 2)) * cos(v + Pi);
       
        float y1 = 8 * sin(u);
       
        vec3 range;
        range.x = u < Pi ? x0 : x1;
        range.y = u < Pi ? -y0 : -y1;
        range.z = (-2 * (1 - cos(u) / 2)) * sin(v);
        return range * m_scale;
    }
    bool InvertNormal(const vec2& domain) const
    {
        return domain.y > 3 * Pi / 2;
    }
private:
    float m_scale;
};


5. ParametricSurface.hpp/cpp 計算 Vertex及 Indices

ParametricSurface.hpp

struct ParametricInterval {
    ivec2 Divisions;
    vec2 UpperBound;
    vec2 TextureCount;
};

class ParametricSurface : public ISurface {
public:
    int GetVertexCount() const;
    int GetLineIndexCount() const;
    int GetTriangleIndexCount() const;
    void GenerateVertices(vector<float>& vertices, unsigned char flags) const;
    void GenerateLineIndices(vector<unsigned short>& indices) const;
    void GenerateTriangleIndices(vector<unsigned short>& indices) const;
protected:
    void SetInterval(const ParametricInterval& interval);
    virtual vec3 Evaluate(const vec2& domain) const = 0;
    virtual bool InvertNormal(const vec2& domain) const { return false; }
private:
    vec2 ComputeDomain(float i, float j) const;
    ivec2 m_slices;
    ivec2 m_divisions;
    vec2 m_upperBound;
    vec2 m_textureCount;
};




ParametricSurface.cpp

#include "ParametricSurface.hpp"

void ParametricSurface::SetInterval(const ParametricInterval& interval)
{
    m_divisions = interval.Divisions;
    m_upperBound = interval.UpperBound;
    m_textureCount = interval.TextureCount;
    m_slices = m_divisions - ivec2(1, 1);
}

int ParametricSurface::GetVertexCount() const
{
    return m_divisions.x * m_divisions.y;
}

int ParametricSurface::GetLineIndexCount() const
{
    return 4 * m_slices.x * m_slices.y;
}

int ParametricSurface::GetTriangleIndexCount() const
{
    return 6 * m_slices.x * m_slices.y;
}

vec2 ParametricSurface::ComputeDomain(float x, float y) const
{
    return vec2(x * m_upperBound.x / m_slices.x, y * m_upperBound.y / m_slices.y);
}

void ParametricSurface::GenerateVertices(vector<float>& vertices,
                                         unsigned char flags) const
{    // 其中 VertexFlagsNormals == 1  ,  VertexFlagsTexCoords == 2 , flags == 0
    int floatsPerVertex = 3;
    if (flags & VertexFlagsNormals)
        floatsPerVertex += 3;
    if (flags & VertexFlagsTexCoords)
        floatsPerVertex += 2;
   
    vertices.resize(GetVertexCount() * floatsPerVertex); // 重新制訂記憶區塊大小
    float* attribute = &vertices[0];  // attribute 指向Vertices的第0個位置
   

    for (int j = 0; j < m_divisions.y; j++) {
        for (int i = 0; i < m_divisions.x; i++) {
           
            // Compute Position
            vec2 domain = ComputeDomain(i, j);
            vec3 range = Evaluate(domain);
            attribute = range.Write(attribute);
            // 內部的this 就是 range 本身, 將attribute寫到 range所指的位置,然後range的位置指向下一個 Vertices
            // 書本解釋 Copy the vec3 position into the flat floating-point buffer. The Write method returns an updated pointer.
           

           
            //printf("VertexFlagsNormals = %d", VertexFlagsNormals);
           
           
            // 本例不做 Normal 及 Texture
            // Compute Normal
            if (flags & VertexFlagsNormals) {  // VertexFlagsNormals  ==1
               
                float s = i, t = j;  // 本例此處永不進入  flags == 0
               
                // Nudge the point if the normal is indeterminate.
                if (i == 0) s += 0.01f;
                if (i == m_divisions.x - 1) s -= 0.01f;
                if (j == 0) t += 0.01f;
                if (j == m_divisions.y - 1) t -= 0.01f;
               
                // Compute the tangents and their cross product.
                vec3 p = Evaluate(ComputeDomain(s, t));
                vec3 u = Evaluate(ComputeDomain(s + 0.01f, t)) - p;
                vec3 v = Evaluate(ComputeDomain(s, t + 0.01f)) - p;
                vec3 normal = u.Cross(v).Normalized();
                if (InvertNormal(domain))
                    normal = -normal;
                attribute = normal.Write(attribute);
            }
           
            // Compute Texture Coordinates
            if (flags & VertexFlagsTexCoords) {
                float s = m_textureCount.x * i / m_slices.x;  // 本例此處永不進入  flags == 0
                float t = m_textureCount.y * j / m_slices.y;
                attribute = vec2(s, t).Write(attribute);
            }
        }
    }
}

void ParametricSurface::GenerateLineIndices(vector<unsigned short>& indices) const
{
    indices.resize(GetLineIndexCount());
    vector<unsigned short>::iterator index = indices.begin();
    for (int j = 0, vertex = 0; j < m_slices.y; j++) {
        for (int i = 0; i < m_slices.x; i++) {
            int next = (i + 1) % m_divisions.x;
            *index++ = vertex + i;
            *index++ = vertex + next;
            *index++ = vertex + i;
            *index++ = vertex + i + m_divisions.x;
        }
        vertex += m_divisions.x;
    }
}

void
ParametricSurface::GenerateTriangleIndices(vector<unsigned short>& indices) const
{
    indices.resize(GetTriangleIndexCount());
    vector<unsigned short>::iterator index = indices.begin();
    for (int j = 0, vertex = 0; j < m_slices.y; j++) {
        for (int i = 0; i < m_slices.x; i++) {
            int next = (i + 1) % m_divisions.x;
            *index++ = vertex + i;
            *index++ = vertex + next;
            *index++ = vertex + i + m_divisions.x;
            *index++ = vertex + next;
            *index++ = vertex + next + m_divisions.x;
            *index++ = vertex + i + m_divisions.x;
        }
        vertex += m_divisions.x;
    }
}


6. ApplicationEngine.ParametricViewer.cpp此處應用了右圖的做法,加入一個Application接面,作為軌跡球對應手指轉動的計算。



#include "Interfaces.hpp"
#include "ParametricEquations.hpp"

using namespace std;

namespace ParametricViewer { // 有一些function命名可能會與其他重複,因此使用namespace
   
    static const int SurfaceCount = 6;
    static const int ButtonCount = SurfaceCount - 1;
   
    struct Animation {
        bool Active;
        float Elapsed;
        float Duration;
        Visual StartingVisuals[SurfaceCount];
        Visual EndingVisuals[SurfaceCount];
    };
   
    class ApplicationEngine : public IApplicationEngine {
    public:
        ApplicationEngine(IRenderingEngine* renderingEngine);
        ~ApplicationEngine();
        void Initialize(int width, int height);
        void OnFingerUp(ivec2 location);
        void OnFingerDown(ivec2 location);
        void OnFingerMove(ivec2 oldLocation, ivec2 newLocation);
        void Render() const;
        void UpdateAnimation(float dt);
    private:
        void PopulateVisuals(Visual* visuals) const;
        int MapToButton(ivec2 touchpoint) const;
        vec3 MapToSphere(ivec2 touchpoint) const;
        float m_trackballRadius;
        ivec2 m_screenSize;
        ivec2 m_centerPoint;
        ivec2 m_fingerStart;
        bool m_spinning;
        Quaternion m_orientation;
        Quaternion m_previousOrientation;
        int m_currentSurface;
        ivec2 m_buttonSize;
        int m_pressedButton;
        int m_buttonSurfaces[ButtonCount];
        Animation m_animation;
        IRenderingEngine* m_renderingEngine;
    };
   
    IApplicationEngine* CreateApplicationEngine(IRenderingEngine* renderingEngine)
    {
        return new ApplicationEngine(renderingEngine);
    }
   
    ApplicationEngine::ApplicationEngine(IRenderingEngine* renderingEngine) :
    m_spinning(false),
    m_pressedButton(-1),
    m_renderingEngine(renderingEngine)
    {
        m_animation.Active = false;
        m_buttonSurfaces[0] = 0;
        m_buttonSurfaces[1] = 1;
        m_buttonSurfaces[2] = 4;
        m_buttonSurfaces[3] = 3;
        m_buttonSurfaces[4] = 2;
        m_currentSurface = 5;
    }
   
    ApplicationEngine::~ApplicationEngine()
    {
        delete m_renderingEngine;
    }
   
    void ApplicationEngine::Initialize(int width, int height)
    {
        m_trackballRadius = width / 3;
        m_buttonSize.y = height / 10;
        m_buttonSize.x = 4 * m_buttonSize.y / 3;
        m_screenSize = ivec2(width, height - m_buttonSize.y);
        m_centerPoint = m_screenSize / 2;
       
        vector<ISurface*> surfaces(SurfaceCount);
        surfaces[0] = new Cone(3, 1);  // 設定半徑及高,其他
        surfaces[1] = new Sphere(1.4f);
        surfaces[2] = new Torus(1.4f, 0.3f);
        surfaces[3] = new TrefoilKnot(1.8f);
        surfaces[4] = new KleinBottle(0.2f);
        surfaces[5] = new MobiusStrip(1);
        m_renderingEngine->Initialize(surfaces);
        for (int i = 0; i < SurfaceCount; i++)
            delete surfaces[i];
    }
   
    void ApplicationEngine::PopulateVisuals(Visual* visuals) const
    {
        //設定所有圖像的顏色與大小,包含主圖及Button
        for (int buttonIndex = 0; buttonIndex < ButtonCount; buttonIndex++) {
           
            int visualIndex = m_buttonSurfaces[buttonIndex];
            visuals[visualIndex].Color = vec3(0.25f*3, 0.25f, 0.25f);  // Button上的顏色
            if (m_pressedButton == buttonIndex)
                visuals[visualIndex].Color = vec3(0.5f, 0.5f*3, 0.5f);  // 點選後的Button顏色
           
            // 設定每一個Button上的圖案大小
            visuals[visualIndex].ViewportSize = m_buttonSize;
            visuals[visualIndex].LowerLeft.x = buttonIndex * m_buttonSize.x;
            visuals[visualIndex].LowerLeft.y = 0;
            visuals[visualIndex].Orientation = Quaternion(); // 基本button的旋轉值為0
        }
       
        // 顯示主圖的顏色,m_spinning代表手指按下的狀態,蓋掉前面所設的值
        visuals[m_currentSurface].Color = m_spinning ? vec3(1, 0, 0.75f) : vec3(1*3, 1, 0.5f);
        visuals[m_currentSurface].LowerLeft = ivec2(0, m_buttonSize.y);
        visuals[m_currentSurface].ViewportSize = ivec2(m_screenSize.x, m_screenSize.y);
        visuals[m_currentSurface].Orientation = m_orientation; //主圖的旋轉值
    }
   
    void ApplicationEngine::Render() const
    {
        vector<Visual> visuals(SurfaceCount);
       
        if (!m_animation.Active) {
            PopulateVisuals(&visuals[0]);
        } else {
            float t = m_animation.Elapsed / m_animation.Duration;
           
            for (int i = 0; i < SurfaceCount; i++) {
               
                // 找出起始的visuals[x] 及最後的visuals[y]
                const Visual& start = m_animation.StartingVisuals[i];
                const Visual& end = m_animation.EndingVisuals[i];
               
                Visual& tweened = visuals[i];  // 這時的 visuals[]中是空的
               
                // 以下將所有的值重新填到新的visuals[]中,根據現在所見的狀態
                tweened.Color = start.Color.Lerp(t, end.Color); //將顏色做線性插補取得時間變化值
                tweened.LowerLeft = start.LowerLeft.Lerp(t, end.LowerLeft);
                tweened.ViewportSize = start.ViewportSize.Lerp(t, end.ViewportSize);
                tweened.Orientation = start.Orientation.Slerp(t, end.Orientation);
            }
        }
       
        m_renderingEngine->Render(visuals);
    }
   
    void ApplicationEngine::UpdateAnimation(float dt)
    {
        if (m_animation.Active) {  // 改選成另一個物件
            m_animation.Elapsed += dt;
            if (m_animation.Elapsed > m_animation.Duration)
                m_animation.Active = false;
        }
    }
   
    // 3. 手指離開
    void ApplicationEngine::OnFingerUp(ivec2 location)
    {
        m_spinning = false;
       
        if (m_pressedButton != -1 && m_pressedButton == MapToButton(location) &&
            !m_animation.Active)  // 如果按選了其他的物件,就進行以下的程序
        {
            m_animation.Active = true;
            m_animation.Elapsed = 0;
            m_animation.Duration = 0.25f;
           
            PopulateVisuals(&m_animation.StartingVisuals[0]);
            swap(m_buttonSurfaces[m_pressedButton], m_currentSurface); // 點選的Button圖像與主圖像交換
            PopulateVisuals(&m_animation.EndingVisuals[0]);
        }
       
        m_pressedButton = -1;
    }
   
    //  1. 壓下手指
    void ApplicationEngine::OnFingerDown(ivec2 location)
    {
        m_fingerStart = location;
        m_previousOrientation = m_orientation; // 取得現在的旋轉值
        m_pressedButton = MapToButton(location);
        if (m_pressedButton == -1)
            m_spinning = true;
    }
   
    // 2. 移動手指讓物件轉動
    void ApplicationEngine::OnFingerMove(ivec2 oldLocation, ivec2 location)
    {
        if (m_spinning) {
            vec3 start = MapToSphere(m_fingerStart);
            vec3 end = MapToSphere(location);
            Quaternion delta = Quaternion::CreateFromVectors(start, end); // 取得方向向量
            m_orientation = delta.Rotated(m_previousOrientation); // 根據前一個旋轉值,計算再次旋轉後的值,此為主圖所用
        }
       
        if (m_pressedButton != -1 && m_pressedButton != MapToButton(location))
            m_pressedButton = -1;
    }
   
    // 確認所按的位置為主圖像的位置,並回傳所按的點,經計算後的3D位置。
    vec3 ApplicationEngine::MapToSphere(ivec2 touchpoint) const
    {
        vec2 p = touchpoint - m_centerPoint;
       
        // Flip the Y axis because pixel coords increase towards the bottom.
        p.y = -p.y;
       
        const float radius = m_trackballRadius;
        const float safeRadius = radius - 1;
       
        if (p.Length() > safeRadius) {
            float theta = atan2(p.y, p.x);
            p.x = safeRadius * cos(theta);
            p.y = safeRadius * sin(theta);
        }
       
        float z = sqrt(radius * radius - p.LengthSquared());
        vec3 mapped = vec3(p.x, p.y, z);
        return mapped / radius;
    }
   
    // 確認按到了Button 的位置,並回傳所按的Button代號。
    int ApplicationEngine::MapToButton(ivec2 touchpoint) const
    {
        if (touchpoint.y  < m_screenSize.y - m_buttonSize.y)
            return -1;
       
        int buttonIndex = touchpoint.x / m_buttonSize.x;
        if (buttonIndex >= ButtonCount)
            return -1;
       
        return buttonIndex;
    }
   
}


7. RenderingEngine.WireframeES1.cpp

#include <OpenGLES/ES1/gl.h>
#include <OpenGLES/ES1/glext.h>
#include "Interfaces.hpp"
#include "Matrix.hpp"

namespace WireframeES1 {
   
    struct Drawable {
        GLuint VertexBuffer;
        GLuint IndexBuffer;
        int IndexCount;
    };
   
    class RenderingEngine : public IRenderingEngine {
    public:
        RenderingEngine();
        void Initialize(const vector<ISurface*>& surfaces);
        void Render(const vector<Visual>& visuals) const;
    private:
        vector<Drawable> m_drawables;
        GLuint m_colorRenderbuffer;
        mat4 m_translation;
    };
   
    IRenderingEngine* CreateRenderingEngine()
    {
        return new RenderingEngine();
    }
   
    RenderingEngine::RenderingEngine()
    {
        glGenRenderbuffersOES(1, &m_colorRenderbuffer);
        glBindRenderbufferOES(GL_RENDERBUFFER_OES, m_colorRenderbuffer);
    }
   
    void RenderingEngine::Initialize(const vector<ISurface*>& surfaces)
    {
        vector<ISurface*>::const_iterator surface;
        for (surface = surfaces.begin(); surface != surfaces.end(); ++surface) {
           
            // Create the VBO for the vertices.
            vector<float> vertices;
            (*surface)->GenerateVertices(vertices);
            GLuint vertexBuffer;
            glGenBuffers(1, &vertexBuffer);  //設定GPU memory 給 Vertex用
            glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
            glBufferData(GL_ARRAY_BUFFER,   // 將vertex資料存到GPU memory
                         vertices.size() * sizeof(vertices[0]),  // 確認記憶體大小
                         &vertices[0],
                         GL_STATIC_DRAW);
           
            // Create a new VBO for the indices if needed.
            int indexCount = (*surface)->GetLineIndexCount();
            GLuint indexBuffer;
            if (!m_drawables.empty() && indexCount == m_drawables[0].IndexCount) {
                indexBuffer = m_drawables[0].IndexBuffer;
            } else {
                vector<GLushort> indices(indexCount);
                (*surface)->GenerateLineIndices(indices);  // 4個 indices為一組
                glGenBuffers(1, &indexBuffer);
                glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
                glBufferData(GL_ELEMENT_ARRAY_BUFFER,
                             indexCount * sizeof(GLushort),
                             &indices[0],
                             GL_STATIC_DRAW);  // 表示该缓存区不会被修改
            }
           
            Drawable drawable = { vertexBuffer, indexBuffer, indexCount};
            m_drawables.push_back(drawable);
        }
       
        // Create the framebuffer object.
        GLuint framebuffer;
        glGenFramebuffersOES(1, &framebuffer);
        glBindFramebufferOES(GL_FRAMEBUFFER_OES, framebuffer);
        glFramebufferRenderbufferOES(GL_FRAMEBUFFER_OES, GL_COLOR_ATTACHMENT0_OES,
                                     GL_RENDERBUFFER_OES, m_colorRenderbuffer);
        glBindRenderbufferOES(GL_RENDERBUFFER_OES, m_colorRenderbuffer);
       
        glEnableClientState(GL_VERTEX_ARRAY);
        m_translation = mat4::Translate(0, 0, -7);
    }
   
    void RenderingEngine::Render(const vector<Visual>& visuals) const
    {
        glClearColor(0.5f, 0.5f, 0.5f, 1);
        glClear(GL_COLOR_BUFFER_BIT);
       
        vector<Visual>::const_iterator visual = visuals.begin();
        for (int visualIndex = 0; visual != visuals.end(); ++visual, ++visualIndex) {
           
            // Set the viewport transform.
            ivec2 size = visual->ViewportSize;
            ivec2 lowerLeft = visual->LowerLeft;
            glViewport(lowerLeft.x, lowerLeft.y, size.x, size.y);
           
            // Set the model-view transform.
            mat4 rotation = visual->Orientation.ToMatrix();
            mat4 modelview = rotation * m_translation;
            glMatrixMode(GL_MODELVIEW);
            glLoadMatrixf(modelview.Pointer());
           
            // Set the projection transform.
            float h = 4.0f * size.y / size.x;
            mat4 projection = mat4::Frustum(-2, 2, -h / 2, h / 2, 5, 10);
            glMatrixMode(GL_PROJECTION);
            glLoadMatrixf(projection.Pointer());
           
            // Set the color.
            vec3 color = visual->Color;
            glColor4f(color.x, color.y, color.z, 1);
           
            // Draw the wireframe.
            int stride = sizeof(vec3);
            const Drawable& drawable = m_drawables[visualIndex];
            glBindBuffer(GL_ARRAY_BUFFER, drawable.VertexBuffer);
            glVertexPointer(3, GL_FLOAT, stride, 0);
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, drawable.IndexBuffer);
            glDrawElements(GL_LINES, drawable.IndexCount, GL_UNSIGNED_SHORT, 0);
        }
    }
   
}


8.  RenderingEngine.WireframeES2.cpp

#include <OpenGLES/ES2/gl.h>
#include <OpenGLES/ES2/glext.h>
#include "Interfaces.hpp"
#include "Matrix.hpp"
#include <iostream>

namespace WireframeES2 {
   
#define STRINGIFY(A)  #A

#include "./Shaders/Simple.es2.vert"
#include "./Shaders/Simple.es2.frag"

   
    struct Drawable {
        GLuint VertexBuffer;
        GLuint IndexBuffer;
        int IndexCount;
    };
   
    class RenderingEngine : public IRenderingEngine {
    public:
        RenderingEngine();
        void Initialize(const vector<ISurface*>& surfaces);
        void Render(const vector<Visual>& visuals) const;
    private:
        GLuint BuildShader(const char* source, GLenum shaderType) const;
        GLuint BuildProgram(const char* vShader, const char* fShader) const;
        vector<Drawable> m_drawables;
        GLuint m_colorRenderbuffer;
        GLint m_projectionUniform;
        GLint m_modelviewUniform;
        GLuint m_positionSlot;
        GLuint m_colorSlot;
        mat4 m_translation;
    };
   
    IRenderingEngine* CreateRenderingEngine()
    {
        return new RenderingEngine();
    }
   
    RenderingEngine::RenderingEngine()
    {
        glGenRenderbuffers(1, &m_colorRenderbuffer);
        glBindRenderbuffer(GL_RENDERBUFFER, m_colorRenderbuffer);
    }
   
    void RenderingEngine::Initialize(const vector<ISurface*>& surfaces)
    {
        vector<ISurface*>::const_iterator surface;
        for (surface = surfaces.begin(); surface != surfaces.end(); ++surface) {
           
            // Create the VBO for the vertices.
            vector<float> vertices;
            (*surface)->GenerateVertices(vertices);
            GLuint vertexBuffer;
            glGenBuffers(1, &vertexBuffer);
            glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
            glBufferData(GL_ARRAY_BUFFER,
                         vertices.size() * sizeof(vertices[0]),
                         &vertices[0],
                         GL_STATIC_DRAW);
           
            // Create a new VBO for the indices if needed.
            int indexCount = (*surface)->GetLineIndexCount();
            GLuint indexBuffer;
            if (!m_drawables.empty() && indexCount == m_drawables[0].IndexCount) {
                indexBuffer = m_drawables[0].IndexBuffer;
            } else {
                vector<GLushort> indices(indexCount);
                (*surface)->GenerateLineIndices(indices);
                glGenBuffers(1, &indexBuffer);
                glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);
                glBufferData(GL_ELEMENT_ARRAY_BUFFER,
                             indexCount * sizeof(GLushort),
                             &indices[0],
                             GL_STATIC_DRAW);
            }
           
            Drawable drawable = { vertexBuffer, indexBuffer, indexCount};
            m_drawables.push_back(drawable); // 新增drawable至 m_drawables 的尾端,必要時會進行記憶體配置。
        }
       
        // Create the framebuffer object.
        GLuint framebuffer;
        glGenFramebuffers(1, &framebuffer);
        glBindFramebuffer(GL_FRAMEBUFFER, framebuffer);
       
        // 设置FrameBuffer并使用glFramebufferRenderBuffer相互关联
        glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, //
                                  GL_RENDERBUFFER, m_colorRenderbuffer);
        glBindRenderbuffer(GL_RENDERBUFFER, m_colorRenderbuffer);
       
        // Create the GLSL program.
        GLuint simpleProgram = BuildProgram(SimpleVertexShader, SimpleFragmentShader);
        glUseProgram(simpleProgram);
        m_positionSlot = glGetAttribLocation(simpleProgram, "Position");
        m_colorSlot = glGetAttribLocation(simpleProgram, "SourceColor");
        glEnableVertexAttribArray(m_positionSlot);
       
        // Set up some matrices.
        m_translation = mat4::Translate(0, 0, -7);
        m_projectionUniform = glGetUniformLocation(simpleProgram, "Projection");
        m_modelviewUniform = glGetUniformLocation(simpleProgram, "Modelview");
    }
   
    void RenderingEngine::Render(const vector<Visual>& visuals) const
    {
        glClearColor(0.5f, 0.5f, 0.5f, 1);
        glClear(GL_COLOR_BUFFER_BIT);
       
        vector<Visual>::const_iterator visual = visuals.begin();
       
        // 將所有的圖像都畫出,Button上的圖像,使用相對小的Size(64,48), 主圖大小為(320,432)
        // 0~5, 5指的是主畫面

        for (int visualIndex = 0; visual != visuals.end(); ++visual, ++visualIndex) {
           
            // Set the viewport transform.
            ivec2 size = visual->ViewportSize;
            ivec2 lowerLeft = visual->LowerLeft;
            glViewport(lowerLeft.x, lowerLeft.y, size.x, size.y);
           
            // Set the model-view transform.
            mat4 rotation = visual->Orientation.ToMatrix();  // 只有主圖像的旋轉四元值被讀入,並轉為矩陣。
            mat4 modelview = rotation * m_translation;   // m_translation  =[0,0, -7]
            glUniformMatrix4fv(m_modelviewUniform, 1, 0, modelview.Pointer());
           
            // Set the projection transform.
            float h = 4.0f * size.y / size.x;
            mat4 projectionMatrix = mat4::Frustum(-2, 2, -h / 2, h / 2, 5, 10);
            glUniformMatrix4fv(m_projectionUniform, 1, 0, projectionMatrix.Pointer());
           
            // Set the color.
            vec3 color = visual->Color;
            glVertexAttrib4f(m_colorSlot, color.x, color.y, color.z, 1);
           
            // Draw the wireframe.
            int stride = sizeof(vec3);
            const Drawable& drawable = m_drawables[visualIndex];
            glBindBuffer(GL_ARRAY_BUFFER, drawable.VertexBuffer);
            glVertexAttribPointer(m_positionSlot, 3, GL_FLOAT, GL_FALSE, stride, 0);
            glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, drawable.IndexBuffer);
            glDrawElements(GL_LINES, drawable.IndexCount, GL_UNSIGNED_SHORT, 0);
        }
    }
   
    GLuint RenderingEngine::BuildShader(const char* source, GLenum shaderType) const
    {
        GLuint shaderHandle = glCreateShader(shaderType);
        glShaderSource(shaderHandle, 1, &source, 0);
        glCompileShader(shaderHandle);
       
        GLint compileSuccess;
        glGetShaderiv(shaderHandle, GL_COMPILE_STATUS, &compileSuccess);
       
        if (compileSuccess == GL_FALSE) {
            GLchar messages[256];
            glGetShaderInfoLog(shaderHandle, sizeof(messages), 0, &messages[0]);
            std::cout << messages;
            exit(1);
        }
       
        return shaderHandle;
    }
   
    GLuint RenderingEngine::BuildProgram(const char* vertexShaderSource,
                                         const char* fragmentShaderSource) const
    {
        GLuint vertexShader = BuildShader(vertexShaderSource, GL_VERTEX_SHADER);
        GLuint fragmentShader = BuildShader(fragmentShaderSource, GL_FRAGMENT_SHADER);
       
        GLuint programHandle = glCreateProgram();
        glAttachShader(programHandle, vertexShader);
        glAttachShader(programHandle, fragmentShader);
        glLinkProgram(programHandle);
       
        GLint linkSuccess;
        glGetProgramiv(programHandle, GL_LINK_STATUS, &linkSuccess);
        if (linkSuccess == GL_FALSE) {
            GLchar messages[256];
            glGetProgramInfoLog(programHandle, sizeof(messages), 0, &messages[0]);
            std::cout << messages;
            exit(1);
        }
       
        return programHandle;
    }
   
}





9. GLView.h/.mm

GLView.h

#import <UIKit/UIKit.h>

#import "Interfaces.hpp"
#import <QuartzCore/QuartzCore.h>

@interface GLView : UIView
{
@private
    IApplicationEngine* m_applicationEngine;
    IRenderingEngine* m_renderingEngine;
    EAGLContext* m_context;
    float m_timestamp;
}

- (void) drawView: (CADisplayLink*) displayLink;


@end


GLView.mm

#import "GLView.h"

@implementation GLView

+ (Class) layerClass
{
    return [CAEAGLLayer class];
}

- (id) initWithFrame: (CGRect) frame
{
    if (self = [super initWithFrame:frame])
    {
        CAEAGLLayer* eaglLayer = (CAEAGLLayer*) self.layer;
        eaglLayer.opaque = YES;
       
        //EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
        EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES1;
       
       
        m_context = [[EAGLContext alloc] initWithAPI:api];
       
        if (!m_context) {
            api = kEAGLRenderingAPIOpenGLES1;
            m_context = [[EAGLContext alloc] initWithAPI:api];
        }
       
        if (!m_context || ![EAGLContext setCurrentContext:m_context]) {
            //[self release];
            return nil;
        }
       
       
        if (api == kEAGLRenderingAPIOpenGLES1) {
            NSLog(@"Using OpenGL ES 1.1");
            m_renderingEngine = WireframeES1::CreateRenderingEngine();
        } else {
            NSLog(@"Using OpenGL ES 2.0");
            m_renderingEngine = WireframeES2::CreateRenderingEngine(); // 完成 m_colorRenderbuffer 設定
        }
       
        m_applicationEngine = ParametricViewer::CreateApplicationEngine(m_renderingEngine);
       
        [m_context
         renderbufferStorage:GL_RENDERBUFFER
         fromDrawable: eaglLayer];
       
        int width = CGRectGetWidth(frame);
        int height = CGRectGetHeight(frame);
        m_applicationEngine->Initialize(width, height);
       
        [self drawView: nil];
        m_timestamp = CACurrentMediaTime();
       
        CADisplayLink* displayLink;
        displayLink = [CADisplayLink displayLinkWithTarget:self
                                                  selector:@selector(drawView:)];
       
        [displayLink addToRunLoop:[NSRunLoop currentRunLoop]
                          forMode:NSDefaultRunLoopMode];
    }
    return self;
}

- (void) drawView: (CADisplayLink*) displayLink
{
    if (displayLink != nil) {
        float elapsedSeconds = displayLink.timestamp - m_timestamp;
        m_timestamp = displayLink.timestamp;
        m_applicationEngine->UpdateAnimation(elapsedSeconds);
    }
   
    m_applicationEngine->Render();
    [m_context presentRenderbuffer:GL_RENDERBUFFER];
}

- (void) touchesBegan: (NSSet*) touches withEvent: (UIEvent*) event
{
    UITouch* touch = [touches anyObject];
    CGPoint location  = [touch locationInView: self];
    m_applicationEngine->OnFingerDown(ivec2(location.x, location.y));
}

- (void) touchesEnded: (NSSet*) touches withEvent: (UIEvent*) event
{
    UITouch* touch = [touches anyObject];
    CGPoint location  = [touch locationInView: self];
    m_applicationEngine->OnFingerUp(ivec2(location.x, location.y));  //傳入所按的平面位置
}


- (void) touchesMoved: (NSSet*) touches withEvent: (UIEvent*) event
{
    UITouch* touch = [touches anyObject];
    CGPoint previous  = [touch previousLocationInView: self];
    CGPoint current = [touch locationInView: self];
    m_applicationEngine->OnFingerMove(ivec2(previous.x, previous.y),
                                      ivec2(current.x, current.y));
}


@end

 

10. mainViewController.h/.mm


mainViewController.h

#import <UIKit/UIKit.h>
#include "GLView.h"

@interface mainViewController : UIViewController
{
  UIWindow* m_window;
  GLView* m_view;
}
@end


mainViewController.mm

#import "mainViewController.h"

@interface mainViewController ()

@end

@implementation mainViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
   
    CGRect screenBounds = [[UIScreen mainScreen] bounds];
   
    m_window = [[UIWindow alloc] initWithFrame: screenBounds];
    m_view = [[GLView alloc] initWithFrame: screenBounds];
   
    [m_window addSubview: m_view];
    [m_window makeKeyAndVisible];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end


11. 最後是UI控制 mainViewController.h/.mm

mainViewController.h


#import <UIKit/UIKit.h>
#include "GLView.h"

@interface mainViewController : UIViewController
{
  UIWindow* m_window;
  GLView* m_view;
}
@end

mainViewController.mm


#import "mainViewController.h"

@interface mainViewController ()

@end

@implementation mainViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.
   
    CGRect screenBounds = [[UIScreen mainScreen] bounds];
   
    m_window = [[UIWindow alloc] initWithFrame: screenBounds];
    m_view = [[GLView alloc] initWithFrame: screenBounds];
   
    [m_window addSubview: m_view];
    [m_window makeKeyAndVisible];
}

- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

@end







12.結果,因為simulator直立的顯示太大,因此將其旋轉