2012年4月15日 星期日

OpenGL ES 入門: 六. 紋理及紋理映射之一

since: 2012/04/11
update: 2012/04/15

reference:
1. 原文:
iPhone Development: OpenGL ES From the Ground Up, Part 6: Textures and Texture Mapping

2. 翻譯:
從零開始學習OpenGL ES之六 – 紋理及紋理映射

紋理及紋理映射: 建立紋理與載入圖像

A. 前言  
      在 OpenGL ES 中另一種為多邊形定義顏色創建材質的方法是將紋理映射到
      多邊形. 這是一種很實用的方法, 它可以產生很漂亮的外觀並節省大量的處理器
      時間. 因為使用簡單的幾何體通過紋理映射的方法比使用材質的複雜幾何體的
      渲染快得多.

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

B. 紋理映射的前置作業
      1. 開啓 GLView.h 檔案, 修改如下:
....
@protocol GLViewDelegate

@required
- (void)setupView:(GLView *)view;

@optional
//@update for drawing
- (void)drawView:(GLView *)view;
- (void)drawTriangle3D; // 畫三角形
- (void)drawSquare; // 畫正方形
- (void)drawVertexColor; // 畫頂點顏色
- (void)drawIcosahedron; // 畫二十面體
- (void)drawPerspective; // 畫透視多面體
- (void)drawLight; // 畫多面體光效
- (void)drawSpheres; // 畫球體
- (void)TextureMapping; // 紋理映射

@end

      2. 開啓 GLView.m 檔案, 修改如下:
....
- (void)drawView
{
....
    //@update for drawing
    //[self.delegate drawTriangle3D]; // 畫三角形
    //[self.delegate drawSquare]; // 畫正方形
    //[self.delegate drawVertexColor]; // 畫頂點顏色
    //[self.delegate drawIcosahedron]; // 畫二十面體
    //[self.delegate drawPerspective]; // 畫透視多面體
    //[self.delegate drawLight]; // 畫多面體光效
    //[self.delegate drawSpheres]; // 畫球體
    [self.delegate TextureMapping]; // 紋理映射
....
}
....

      3. 開啓 ViewController.m 檔案, 修改如下:
....
// 紋理映射
- (void)TextureMapping
{
    NSLog(@"ViewController => TextureMapping");
   
    static GLfloat rot = 0.0;
   
    glColor4f(0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
   
    static const Vertex3D vertices[] = {
        {-1.0,  1.0, -0.0},
        { 1.0,  1.0, -0.0},
        {-1.0, -1.0, -0.0},
        { 1.0, -1.0, -0.0}
    };
   
    static const Vector3D normals[] = {
        {0.0, 0.0, 1.0},
        {0.0, 0.0, 1.0},
        {0.0, 0.0, 1.0},
        {0.0, 0.0, 1.0}
    };
   
    glLoadIdentity();
    glTranslatef(0.0, 0.0, -3.0);
    glRotatef(rot, 1.0, 1.0, 1.0);
       
    glVertexPointer(3, GL_FLOAT, 0, vertices);
    glNormalPointer(GL_FLOAT, 0, normals);
       
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
   
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
       
    static NSTimeInterval lastDrawTime;
    if (lastDrawTime)
    {
        NSTimeInterval timeSinceLastDraw = [NSDate timeIntervalSinceReferenceDate] - lastDrawTime;
        rot +=  60 * timeSinceLastDraw;               
    }
    lastDrawTime = [NSDate timeIntervalSinceReferenceDate];
}
....

      4. 編譯並執行:
          三維旋轉之四方形薄片

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

C. 啟動功能
      1. 說明:
          為了使用紋理, 我們需要打開 OpenGL 的一些開關以啟動我們需要的一些功能:

          a.  glEnable(GL_TEXTURE_2D)
              這個函數啟動所有兩維圖像的功能. 這個呼叫是必不可缺的; 如果你沒有啟動
              此功能, 那麼你就無法將圖像映射到多邊形上. 它可以在需要時啟動和關閉,
              但是通常不需要這樣做. 你可以啟動此功能而在繪圖時並不使用它, 所以通常
              只需在 ViewController 的 setupView: 方法中呼叫一次.

          b. glEnable(GL_BLEND)
             這個函數啟動了混色(blending) 功能. 混色提供了通過指定來源和目標怎樣
             組合而合成圖像的功能. 例如, 它可以允許你將多個紋理映射到多邊形中以產生
             一個有趣的新的紋理. 然而在 OpenGL 中, "混色" 是指合成任何圖像圖像與
             多邊形
表面合成, 所以即使你不需要將多個圖像混合, 你也需要啟動此功能.

          c. glBlendFunc(GL_ONE, GL_SRC_COLOR)
             這個函數指定了使用的混色方法. 混色函數定義了來源圖像怎樣與目標圖像
             表面合成. OpenGL 將計算出(根據我們提供的信息)怎樣將來源紋理的一個像素
             映射到繪製此像素的目標多邊形的一部分.

             (1). 一旦 OpenGL ES 決定怎樣把一個像素從紋理映射到多邊形, 它將使用指定
                   的混色函數來確定最終繪製的各像素的最終值. glBlendFunc() 函數決定
                   我們將怎樣進行混色運算, 它採用了兩個參數: 第一個參數定義了怎樣使用
                   源紋理. 第二個則定義了怎樣使用目標顏色或紋理.

             (2). 在本文中, 我們希望繪製的紋理完全不透明而忽略多邊形中現存的顏色
                    或紋理, 所以我們設置來源為 GL_ONE, 它表示來源圖像(被映射的紋理)
                    中各顏色通道的值將乘以 1.0 或者換句話說, 以完全顏色密度使用. 目標
                    設置為 GL_SRC_COLOR, 它表示要使用來源圖像中被映射到多邊形
                    特定點的顏色. 此混色函數的結果是一個完全不透明的紋理. 這可能是
                    最常用情況.

             (3). 注意: 如果你已經使用過 OpenGL 的混色功能, 你應該知道 OpenGL ES
                                並不支持所有 OpenGL 支持的混色功能. 下面是 OpenGL ES 支持的:
                                GL_ZERO, GL_ONE, GL_SRC_COLOR,
                                GL_ONE_MINUS_SRC_COLOR, GL_DST_COLOR,
                                GL_ONE_MINUS_DST_COLOR, GL_SRC_ALPHA,
                                GL_ONE_MINUS_SRC_ALPHA, GL_DST_ALPHA,
                                GL_ONE_MINUS_DST_ALPHA, 和
                                GL_SRC_ALPHA_SATURATE (它僅用於來源).

      2. 開啓 ViewController.m 檔案, 修改如下:       
....
-(void)setupView:(GLView *)view
{
....
    // 建構一個對應的座標系統
    glViewport(0, 0, rect.size.width, rect.size.height);  
    glMatrixMode(GL_MODELVIEW);
    
    //@add for Texture Mapping (Turn necessary features on)
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_SRC_COLOR);

    //@add
    glShadeModel(GL_SMOOTH);
 
    //@add for 啟動光效
    glEnable(GL_LIGHTING);
    
    //@add for 啟動第一個光源
    glEnable(GL_LIGHT0);
....
}
....
--------------------------------------------------------------------------------

D. 建立紋理
      1. 說明
           a. 一旦你啟動了紋理和混色, 就可以開始建立紋理了. 通常紋理是在開始顯示
               3D 物體給用戶前程序開始執行時或遊戲每關開始加載時建立的. 這不是
               必須的, 但卻是一個好的建議, 因為建立紋理需要佔用一些處理器時間, 如果
               在你開始顯示一些複雜的幾何體時進行此項工作, 會引起明顯的程序停頓.

           b. OpenGL 中的每一個圖像都是一個紋理, 紋理是不能直接顯示給最終用戶的,
                除非它映射到物體上. 但是有一個小小的例外, 就是對允許你將圖像繪製於
                指定點的所謂點精靈(point sprites), 但它有自己的一套規則, 所以那是一個
                單獨的主題. 通常的情況下, 任何你希望顯示給用戶的圖像必須放置在由頂點
                定義的三角形中, 有點像貼在上面的黏貼紙.

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

      2. 產生紋理名稱
           a. 為建立一個紋理, 首先必須通知 OpenGL ES 產生一個紋理名稱. 這是一個
               令人迷惑的術語, 因為紋理名稱實際上是一個數字: 更具體的說是一個 GLuint.
               儘管 "名稱" 可以指任何字串, 但對於 OpenGL ES 紋理並不是這樣. 它是一個
               代表指定紋理的整數值. 每個紋理由一個獨一無二的名稱表示, 所以傳遞紋理
               名稱給 OpenGL 是我們區別所使用紋理的方式.

           b. 然而在產生紋理名稱之前, 我們要定義一個保存單個或多個紋理名稱的
               GLuint 陣列: GLuint texture[1]; 儘管只有一個紋理, 但使用一個元素的陣列
               而不是一個 GLuint 仍是一個好習慣. 當然, 仍然可以定義單個 GLuint 進行
               強制呼叫. 在程序式程式中, 紋理通常存於一個全域陣列中, 但在 Objective-C
               程式中, 使用實體變數儲存紋理名稱更為常見. 下面是代碼:
               glGenTextures(1, &texture[0]);

           c. 你可以呼叫 glGenTextures() 產生多個紋理; 傳遞給 OpenGL ES 的第一個
               參數指示了要產生幾個紋理. 第二個參數需要是一個具有足夠空間保存
               紋理名稱的陣列. 我們只有一個元素, 所以只要求 OpenGL ES 產生一個紋理
               名稱. 在呼叫之後, texture[0] 將保持紋理的名稱, 我們將在任何與紋理有關的
               地方都使用 texture[0] 來表示這個特定紋理.

           d. 開啓 ViewController.h 檔案, 修改如下:
....
@interface ViewController : UIViewController <GLViewDelegate>
{
    //@add for draw Spheres
    Vertex3D    *sphereTriangleStripVertices;   // 構成球面的三角形區塊之頂點
    Vector3D    *sphereTriangleStripNormals;    // 構成球面的三角形區塊之法線
    GLuint      sphereTriangleStripVertexCount; // 構成球面的三角形區塊之頂點數量
   
    Vertex3D    *sphereTriangleFanVertices;     // 構成球面的三角形扇狀之頂點
    Vector3D    *sphereTriangleFanNormals;      // 構成球面的三角形扇狀之法線
    GLuint      sphereTriangleFanVertexCount;   // 構成球面的三角形扇狀之頂點數量
   
    //@add for Texture Mapping
    GLuint texture[1];
}
....

           e. 開啓 ViewController.m 檔案, 修改如下:
....
-(void)setupView:(GLView *)view
{
....
    //@add for Texture Mapping (Turn necessary features on)
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_SRC_COLOR);
   
    //@add for Texture Mapping
    //
    // Bind the number of textures we need, in this case one.
    glGenTextures(1, &texture[0]);

    //@add
    glShadeModel(GL_SMOOTH);
   
    //@add for 啟動光效
    glEnable(GL_LIGHTING);
   
    //@add for 啟動第一個光源
    glEnable(GL_LIGHT0);
....
}
....

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

      3. 紋理綁定
           a. 說明:
               產生紋理名稱後, 在為紋理提供圖像資料之前, 我們必須綁定紋理. 綁定使得
               指定紋理處於啟用狀態. 一次只能啟用一個紋理. 啟用的或 "被綁定" 的紋理
               是繪製多邊形時使用的紋理, 也是新紋理資料將載入其上, 所以在提供圖像
               資料前必須綁定紋理. 這意味著每個紋理至少被綁定一次以為 OpenGL ES
               提供此紋理的資料. 運行時, 可能再次綁定紋理(但不會再次提供圖像資料)
               以指示繪圖時要使用此紋理. 紋理綁定很簡單:

               glBindTexture(GL_TEXTURE_2D, texture[0]);

               因為我們使二維圖像建立紋理, 所以第一個參數永遠是 GL_TEXTURE_2D.
               標準 OpenGL 支持其他類型的紋理, 但目前分佈在 iPhone 上的 OpenGL ES
               版本只支持二維紋理, 坦白地說, 甚至在標準 OpenGL 中, 二維紋理的使用
               也遠比其他類型要多得多. 第二個參數是我們需要綁定的紋理名稱. 呼叫此
               函數後, 先前產生了紋理名稱的紋理將成為啟用的紋理.

           b. 開啓 ViewController.m 檔案, 修改如下:
....
-(void)setupView:(GLView *)view
{
....
    //@add for Texture Mapping (Turn necessary features on)
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_SRC_COLOR);
   
    //@add for Texture Mapping
    //
    // Bind the number of textures we need, in this case one.
    glGenTextures(1, &texture[0]);
    glBindTexture(GL_TEXTURE_2D, texture[0]);

    //@add
    glShadeModel(GL_SMOOTH);
   
    //@add for 啟動光效
    glEnable(GL_LIGHTING);
   
    //@add for 啟動第一個光源
    glEnable(GL_LIGHT0);
....
}
....

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


      4. 配置圖像
           a. 在第一次綁定紋理後, 在 iPhone上, 必須設定兩個參數, 否則紋理將不會
               正常顯示:
               glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
               glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);

           b. 必須設置這兩個參數的原因是預設狀態下 OpenGL 設置了使用所謂 mipmap. 
              Mipmap是一個圖像不同尺寸的組合, 它允許 OpenGL 選擇最為接近的尺寸
              版本以避免過多的插值計算並且在物體遠離觀察者時通過使用更小的紋理
              來更好地管理記憶體. 感謝向量單元和繪圖晶片, iPhone 在圖像插值方面做得
              很好, 所以我們不需要考慮 mipmap. 目前要討論的是怎樣讓 OpenGL ES 通過
              線性插值調整圖像到所需的尺寸. 因為 GL_TEXTURE_MIN_FILTER 用於紋理需要
              被收縮到適合多邊形的尺寸的情形, 而 GL_TEXTURE_MAG_FILTER 則用於紋理被
              放大到適合多邊形的尺寸的情況下, 所以必須進行兩次呼叫. 在兩種情況下, 
              我們傳遞 GL_LINEAR 以通知 OpenGL 以簡單的線性插值方法調整圖像.

           c.  開啓 ViewController.m 檔案, 修改如下:
....

-(void)setupView:(GLView *)view
{
....
    //@add for Texture Mapping (Turn necessary features on)
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_SRC_COLOR);
   
    //@add for Texture Mapping
    //
    // Bind the number of textures we need, in this case one.
    glGenTextures(1, &texture[0]);
    glBindTexture(GL_TEXTURE_2D, texture[0]);
    // Configuring the Image
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);


    //@add
    glShadeModel(GL_SMOOTH);
   
    //@add for 啟動光效
    glEnable(GL_LIGHTING);
   
    //@add for 啟動第一個光源
    glEnable(GL_LIGHT0);
....
}
....

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

E. 載入圖像資料
      1. 使用 UIImage 的方法
          開啓 ViewController.m 檔案, 修改如下:
....
-(void)setupView:(GLView *)view

{
....
    //@add for Texture Mapping (Turn necessary features on)
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_SRC_COLOR);
   
    //@add for Texture Mapping
    //
    // Bind the number of textures we need, in this case one.
    glGenTextures(1, &texture[0]);
    glBindTexture(GL_TEXTURE_2D, texture[0]);
    // Configuring the Image
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);    

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);


//@Texture Mapping: Loading the Image Data
//
//@add: The PVRTC Approach   
#ifdef USE_PVRTC_TEXTURE   
    NSLog(@"USE_PVRTC_TEXTURE");   

//@add: the UIImage Approach  
//
// 使用任何 UIImage 支援的圖像資料然後轉換成 OpenGL ES 接受的資料格式
#else
    NSLog(@"USE_UIIMAGE_TEXTURE");
   
    NSString *path = [[NSBundle mainBundle] pathForResource:@"texture" ofType:@"png"];
    NSData *texData = [[NSData alloc] initWithContentsOfFile:path];
    UIImage *image = [[UIImage alloc] initWithData:texData];
   
    if (image == nil)
        NSLog(@"Do real error checking here");
   
    GLuint width = CGImageGetWidth(image.CGImage);
    GLuint height = CGImageGetHeight(image.CGImage);
    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
    void *imageData = malloc( height * width * 4 );

    CGContextRef context = CGBitmapContextCreate( imageData, width, height, 8, 4 * width, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big );
       
    CGColorSpaceRelease( colorSpace );
    CGContextClearRect( context, CGRectMake( 0, 0, width, height ) );
    CGContextDrawImage( context, CGRectMake( 0, 0, width, height ), image.CGImage );
   
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
   
    CGContextRelease(context);
   
    free(imageData);
   
#endif

    //@add
    glShadeModel(GL_SMOOTH);
   
    //@add for 啟動光效
    glEnable(GL_LIGHTING);
   
    //@add for 啟動第一個光源
    glEnable(GL_LIGHT0);
....
}

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

      2. 使用 PVRTC 的方法
          a. 說明:
              iPhone 的繪圖晶片(PowerVR MBX)對一種稱為 PVRTC 的壓縮技術提供
              硬體支援, Apple 推薦在開發 iPhone 應用程式時使用 PVRTC 紋理. 他們
              甚至提供了一篇很好的 技術筆記 描述了怎樣通過使用隨開發工具安裝
              的命令行程式將標準圖像文件轉換為 PVRTC 紋理的方法.

              你應該知道當使用 PVRTC 時與標準 JPEG 或 PNG 圖像相比有可能有些
               圖像質量會下降. 是否值得在你的程式中做出一些犧牲取決於一些因素,
               但使用 PVRTC 紋理可以節省大量的記憶體空間. 你想要手動指定圖像的
               高和寬, 雖然沒有 Objective-C 類別可以解析 PVRTC 資料獲取其寬和高
               的資訊, 但載入 PVRTC 資料到當前綁定的紋理實際上甚至比載入普通圖像
               文件更為簡單.

          b. 開啓 ViewController.m 檔案, 修改如下:
....
-(void)setupView:(GLView *)view

{
....
    //@add for Texture Mapping (Turn necessary features on)
    glEnable(GL_TEXTURE_2D);
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_SRC_COLOR);
   
    //@add for Texture Mapping
    //
    // Bind the number of textures we need, in this case one.
    glGenTextures(1, &texture[0]);
    glBindTexture(GL_TEXTURE_2D, texture[0]);
    // Configuring the Image
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);    

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);


//@Texture Mapping: Loading the Image Data
//
//@add: The PVRTC Approach   
//
// 使用預設的 texturetool 設置載入一個 512×512 的 PVRTC 紋理
#ifdef USE_PVRTC_TEXTURE   
    NSLog(@"USE_PVRTC_TEXTURE");   

    NSString *path = [[NSBundle mainBundle] pathForResource:@"texture" ofType:@"pvrtc"];

    NSData *texData = [[NSData alloc] initWithContentsOfFile:path];
   
    // This assumes that source PVRTC image is 4 bits per pixel and RGB not RGBA
    // If you use the default settings in texturetool, e.g.:
    //
    //         texturetool -e PVRTC -o texture.pvrtc texture.png
    //
    // then this code should work fine for you. Notice, the source image has had
    // its y-axis inverted to deal with the t-axis inversion issue.
    glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGB_PVRTC_4BPPV1_IMG, 512, 512, 0, [texData length], [texData bytes]);

//@add: the UIImage Approach  
//
// 使用任何 UIImage 支援的圖像資料然後轉換成 OpenGL ES 接受的資料格式
#else
    NSLog(@"USE_UIIMAGE_TEXTURE");
....
#endif

    //@add
    glShadeModel(GL_SMOOTH);
   
    //@add for 啟動光效
    glEnable(GL_LIGHTING);
   
    //@add for 啟動第一個光源
    glEnable(GL_LIGHT0);
....
}

          c. 備註:
             產生 PVRTC 檔案的方式如下:
             $ cd /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/usr/bin

             $ sudo ./texturetool -e PVRTC -o /Lanli/texture.pvrtc /Lanli/texture.png
               (說明: 來源檔案: /Lanli/texture.png ; 輸出檔案: /Lanli/texture.pvrtc )

             $ chmod 755 /Lanli/texture.pvrtc

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

      3. 紋理的限制
          a. 用於紋理的圖像寬和高必須為乘方, 比如 2, 4, 8, 16, 32, 64, 128, 256, 512,
              或 1024. 例如圖像可能為 64×128 或 512×512.

          b. 當使用 PVRTC 壓縮圖像時, 有一個額外的限制: 來源圖像必須是正方形,
              所以你的圖像應該為 2×2, 4×4 8×8, 16×16, 32×32, 64×64, 128×128,
              256×256, 等等. 如果你的紋理本身不是正方形, 那麼你只需為圖像加上黑邊
              使圖像成為正方形, 然後映射紋理使得你需要的部分顯示在多邊形上.

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

F. 紋理坐標
     1. 說明
        a. 當紋理映射啟動後, 繪圖時, 你必須為 OpenGL ES 提供其他資料, 即頂點陣列
            中各頂點的紋理坐標. 紋理坐標定義了圖像的哪一部分將被映射到多邊形.

        b. 為了使用紋理坐標陣列, 我們必須啟動它:
             glEnableClientState(GL_TEXTURE_COORD_ARRAY);

        c. 接著, 傳遞紋理坐標:
             glTexCoordPointer(2, GL_FLOAT, 0, texCoords);

        d. 開啓 ViewController.m 檔案, 修改如下:
....
- (void)TextureMapping
{
    NSLog(@"ViewController => TextureMapping");
   
    static GLfloat rot = 0.0;
   
    glColor4f(0.0, 0.0, 0.0, 0.0);
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
   
    glEnableClientState(GL_VERTEX_ARRAY);
    glEnableClientState(GL_NORMAL_ARRAY);
    glEnableClientState(GL_TEXTURE_COORD_ARRAY);
   
    static const Vertex3D vertices[] = {
        {-1.0,  1.0, -0.0},
        { 1.0,  1.0, -0.0},
        {-1.0, -1.0, -0.0},
        { 1.0, -1.0, -0.0}
    };
   
    static const Vector3D normals[] = {
        {0.0, 0.0, 1.0},
        {0.0, 0.0, 1.0},
        {0.0, 0.0, 1.0},
        {0.0, 0.0, 1.0}
    };
   
    static const GLfloat texCoords[] = {
        0.0, 1.0,
        1.0, 1.0,
        0.0, 0.0,
        1.0, 0.0
    };

    glLoadIdentity();
    glTranslatef(0.0, 0.0, -3.0);
    glRotatef(rot, 1.0, 1.0, 1.0);
   
    glVertexPointer(3, GL_FLOAT, 0, vertices);
    glNormalPointer(GL_FLOAT, 0, normals);
    glTexCoordPointer(2, GL_FLOAT, 0, texCoords);

    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
   
    glDisableClientState(GL_VERTEX_ARRAY);
    glDisableClientState(GL_NORMAL_ARRAY);
    glDisableClientState(GL_TEXTURE_COORD_ARRAY);
   
    static NSTimeInterval lastDrawTime;
    if (lastDrawTime)
    {
        NSTimeInterval timeSinceLastDraw = [NSDate timeIntervalSinceReferenceDate] - lastDrawTime;
        rot +=  60 * timeSinceLastDraw;               
    }
    lastDrawTime = [NSDate timeIntervalSinceReferenceDate];
}
....

        e. 編譯並執行:
           原始紋理:

           執行結果: 發現圖像的 y 軸完全顛倒了.

        f. 使用 PVRTC 的方式:
           開啓 ViewController.m 檔案, 修改如下:
....
-(void)setupView:(GLView *)view
{
....
//@add: 使用 PVRTC 的方式:
#ifndef USE_PVRTC_TEXTURE
#define USE_PVRTC_TEXTURE   
#endif
   
//@Texture Mapping: Loading the Image Data
//
//@add: The PVRTC Approach   
//
// 使用預設的 texturetool 設置載入一個 512×512 的 PVRTC 紋理   
#ifdef USE_PVRTC_TEXTURE   
    NSLog(@"USE_PVRTC_TEXTURE");   
....
}
....


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

     2. T-軸翻轉之謎
        a. 說明
            以 OpenGL 的角度來看, 我們並未做錯任何事情, 但結果卻是完全錯誤. 原因
            在於 iPhone 的特殊性.  iPhone 中用於 Core Graphics 的圖像坐標系統並非
            與 OpenGL ES 一致, 其 y 軸在螢幕從上到下而增加. 當然在 OpenGL ES
            正好相反, 它的 y 軸從下向上增加. 其結果就是我們早先傳遞給 OpenGL ES
            中的圖像資料從 OpenGL ES 的角度看完全顛倒了. 所以, 當我們使用標準的
            OpenGL ST 映射坐標映射圖像時, 我們得到了一個翻轉的圖像.

        b. 普通圖像的修正
            (1). 當使用非 PVRTC 圖像時, 你可以在傳遞資料到 OpenGL ES 之前就翻轉
                   圖像的坐標, 將下面兩行代碼到紋理載入中建立 OpenGL 環境的語法之後:
                   (這將翻轉繪製內容的坐標系統)
                   CGContextTranslateCTM (context, 0, height);
                   CGContextScaleCTM (context, 1.0, -1.0);

            (2). 開啓 ViewController.m 檔案, 修改如下:
....
-(void)setupView:(GLView *)view
{
....
#ifdef USE_PVRTC_TEXTURE
....
//@add: the UIImage Approach 
#else
....
    CGContextRef context = CGBitmapContextCreate( imageData, width, height, 8, 4 * width, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big );
   
    //@update: Flip the Y-axis
    CGContextTranslateCTM (context, 0, height);
    CGContextScaleCTM (context, 1.0, -1.0);
....
}
....

       c. PVRTC 圖像的修正
          由於沒有 UIKit 類別可以載入或處理 PVRTC 圖像, 所以沒有一個簡單的方法
          翻轉壓縮紋理的坐標系統. 當然, 我們還是有些方法處理這個問題. 一種方法是
          使用諸如 AcornPhotoshop 之類的程式中將圖像轉換為壓縮紋理前簡單地
          進行垂直翻轉. 這看似小詭計的方法在很多情況下是最好的解決方法, 因為所有
          的處理都是事前進行的, 所以運行時不需要額外的處理時間而且還允許壓縮和
          未壓縮圖像具有同樣的紋理坐標陣列. 另一種方法是將 t 軸的值減一. 儘管減法
          是很快的, 但其佔用的時間還是會累積, 所以在大部分情況下, 盡量要避免繪圖
          時進行的轉換工作. 不論是翻轉圖像或翻轉紋理坐標, 都要在顯示前進行載入時
          進行.

        d. 編譯並執行:

9 則留言:

  1. 你好,
    謝謝你的教學,
    讓我這個新手獲益良多

    不過當我嘗試建立多個GLView元件繪製各自的texture時
    卻始終只有第一個GLView的drawView可以正常繪製

    請問您有遇到這樣的問題嗎?
    或是您有建議處理多個OpenGL UIView繪圖的方式?

    謝謝!

    回覆刪除
  2. 你好, 不客氣 ~
    我也只是將學習的過程記錄下來, 以供將來參考.
    關於你提到的問題, 提供以下2點作為參考:

    1. OpenGL 是屬於 State Machine 的機制, 因此例如在:
    - (void)TextureMapping 函式中, 使用了 glColor4f 與 glClear 會將
    之前繪製的東西都清除掉. 因此你需要將這二個方法適當地註解掉.

    2. 如果要使用多個紋理來映射到不同的幾何圖形,
    你可能需要為不同的紋理映射, 作不同的設定調整,
    如:

    GLuint texture[1]; ----> GLuint texture[n];
    glGenTextures();
    glBindTexture();
    載入不同的圖像
    設定不同的紋理坐標

    回覆刪除
  3. 謝謝你的建議,我目前的作法是:
    在AppDelegate中初始四個GLView和四個ViewController弄成四分割畫面,
    每個GLView的Delegate都設定給各自的ViewController
    (也就是一個ViewController對應一個GLView)

    每個GLView再addSubview一個自定UIButton,
    按下時會先呼叫glTexImage2D更新image data到texture
    再呼叫drawView繪圖

    程式執行時
    四個GLViewr依序初始化,
    並正常執行layoutSubviews的drawView畫出各自的texture
    但是按下每個GLView的UIButton時,
    卻只有第一個GLView可以正常畫出新的texture
    Orz...

    您的兩點建議看起來是針對一個GLView繪製多個texture array
    是不是iOS上就只能一個OpenGL UIView元件
    而不能夠同時有多個呢?

    回覆刪除
  4. 你好, 我的看法如下:

    1. 在 AppDelegate 中: self.window.rootViewController = viewController;
    所以在同一個畫面裡, 應該只能有一個 rootViewController,
    你一次設定了多個 viewController, 有可能某些被覆蓋掉.

    2. 在同一個 viewController 底下的第一層, 是只能有一個 view:
    self.viewController.view = self.glView;

    3. 當然, 你是可以在 self.glView 底下 add 多個 Subview.
    如此的話, viewController 底下的第一層 view 就可設成一般的 UIView,
    在此 UIView 底下 add 多個 glView. 並且每個 glView 的範圍不會去
    相互覆蓋到, 還有可能也要有各自的 glContext.

    4. 這樣子, 感覺是複雜化了.

    回覆刪除
  5. 備註:
    1. 有些 rootViewController 下可以加入其它的 ViewController, ex:
    UITabBarController *tabBarController;
    tabBarController = [[UITabBarController alloc] init];
    self.window.rootViewController = tabBarController;
    [tabBarController setViewControllers:[NSArray arrayWithObjects:viewController, nil]];

    回覆刪除
  6. 不好意思, 我沒有實際去測試, 另外想到的一點可能是:
    Multiple Viewports 的問題.

    相關參考:
    http://stackoverflow.com/questions/11349506/multiple-viewport-opengl-es
    http://blog.163.com/bingcaihuang@126/blog/static/19894212201092641447533/
    http://gamesfromwithin.com/using-multiple-opengl-views-and-uikit

    回覆刪除
  7. 好像真的有點複雜了~ XD

    其實我本來只是很直覺的想說
    讀完範例一個GLView搭配一個ViewController可以正常drawView
    那要同時畫四個不同的texture的話
    就直接init四個GLView和四個ViewController的instance
    再呼叫各自GLView的drawView就好了

    我現在AppDelegate裡的UIWindow架構是這樣:
    self.window.rootViewController = mainViewController
    這個mainViewController不是您範例裡修改的
    而是透過XCode加入的預設UIViewController
    將來視需求可能會替換成UITabBarViewController之類的

    然後在mainViewController.view中加入四個GLView
    每個GLView設定對應各自的ViewController <- 範例裡修改的

    結果發現始終只有第一個初始化的GLView可以正常...Orz

    不知道我目前的架構是不是就是您第三點提到的...?

    回覆刪除
  8. 謝謝您貼的連結建議
    我會再試試看 : )

    回覆刪除
  9. 是的沒錯, 不過我建議:
    倒不如將每個 GLView 的 delegate 設成該 GLView 本身,
    就不需要再對應各自的 ViewController.

    回覆刪除

注意:只有此網誌的成員可以留言。