OpenGL ES 2.0 (iOS)[03]:熟練圖元繪制,玩轉二維圖形


學習這篇:

文章的大前提是,你得有《OpenGL ES 2.0 (iOS): 一步從一個小三角開始》的基礎知識。


本文核心目的就是熟練圖形的分析與繪制


目錄

零、目標+準備

一、圖元繪制之線

0.工程目錄
1. 繪制單一、交叉的線
2. 繪制折線
3. 繪制幾何圖形
4. 繪制三角化的幾何圖形
5. 繪制曲線、圓形

二、圖元繪制之三角形

0.工程目錄
1. 繪制基本幾何圖形

三、圖元繪制之點精靈(內容為空)

四、練練手

0.工程目錄
1. 繪制一棵卡通樹
2. 繪制一張卡片
3. 繪制一棵草

零、目標+準備

  1. 目標
Geometries
  1. 準備
  • 觀察所有圖形,發現它們都是點與點之間的連線(直線或曲線),組成一個幾何形狀( _ 好像有點廢話);

  • 除了點線的問題外,還可以知道幾何形狀,有交疊、閉環、開環三種情況;

  • 除此之外,還有填充色有無的問題;

  • A、根據 OpenGL ES 的特點,歸納總結:

    • a. 要繪制這些圖形,需要控制頂點的數量
    • b. 控制頂點與頂點之間的連接情況,Strip 或 Loop(Fan) 或 沒關系
    • c. 控制圖形的填充色,即 Fragment Shader 與 Vertex Shader 之間的顏色傳遞問題;
  • B、OpenGL ES 下控制數據源與繪制方式的函數有那些?(VBO模式)

    • a. 綁定 VBO 數據 glBufferData
    • b. 繪制數據 glDrawArrays/glDrawElements
    • c. 繪制模式有:
      • GL_POINTS (點)
      • GL_LINES/GL_LINE_STRIP/GL_LINE_LOOP (線)
      • GL_TRIANGLES/GL_TRIANGLE_STRIP/GL_TRIANGLE_FAN (面)

所以本文就是根據圖形的形態,選擇適當的繪制方式,去繪制圖形;核心目的就是熟練圖形的分析與繪制;
因為是練習圖元,所以學習的重點在,數據綁定和圖形繪制這一塊;


一、圖元繪制之線

Lines,多條線的意思;

Line Strip , 指首尾相接的線段,第一條線和最后一條線沒有連接在一起;

Line Loops, 指首尾相接的線段,第一條線和最后一條線連接在一起,即閉合的曲線;
模式 線與點的數量關系
GL_LINES nPoints = 2 * mLines
GL_LINE_STRIP nPoints = mLines + 1
GL_LINE_LOOP nPoints = mLines

ep: 上圖中的圖形

模式 線與點的數量關系
GL_LINES v0~v5( 6 ) = 2 * 3
GL_LINE_STRIP v0~v3( 4 ) = 3 + 1
GL_LINE_LOOP v0~v4( 5 ) = 5

0.工程目錄

完整的線元工程在,這一章的結尾;


工程目錄

圖中紅色箭頭所指的就是要修改的類,其中 VFVertexDatasManager 類是核心,它是負責整個工程的數據綁定和圖形繪制的;
藍色框所指的都是工程中的靜態頂點數據(當然你也可以動態生成并進行綁定繪制);

1. 繪制單一、交叉的線

LINES
  • 圖形分析
  • 首先它們都是線,所以選擇的是 線模式;
  • 左側就是一條線 -> GL_LINES,有兩個頂點坐標,而且坐標是左底右高
  • 右側是兩條交叉線 -> GL_LINES,有四個頂點坐標

nPoints = 2 * mLines

  • 開始寫代碼
    • 數據源準備
// 位于 VFBaseGeometricVertexData.h
// 單線段
static const VFVertex singleLineVertices[] = {
      { 0.5f,  0.5f, 0.0f},
      {-0.5f, -0.5f, 0.0f},
};
// 交叉線
static const VFVertex crossLinesVertices[] = {
      // Line one
      { 0.5f,  0.5f, 0.0f},
      {-0.5f, -0.5f, 0.0f},
      // Line Two
      {-0.53f, 0.48f, 0.0f},
      { 0.55f, -0.4f, 0.0f},
};
  • 修改數據綁定方法
  /**
   *  裝載數據
   */
    - (void)attachVertexDatas {
      self.currentVBOIdentifier = [self createVBO];
      self.drawInfo = [self drawInfoMaker];
      if (self.drawInfo.elementDataPtr) {
          self.currentElementVBOIdentifier = [self createVBO];
          [self bindVertexDatasWithVertexBufferID:self.currentElementVBOIdentifier
                                       bufferType:GL_ELEMENT_ARRAY_BUFFER
                                     verticesSize:self.drawInfo.elementDataSize
                                         datasPtr:self.drawInfo.elementDataPtr];
      }
      [self bindVertexDatasWithVertexBufferID:self.currentVBOIdentifier
                                   bufferType:GL_ARRAY_BUFFER
                                 verticesSize:self.drawInfo.dataSize
                                     datasPtr:self.drawInfo.dataPtr]; // CPU 內存首地址
      [self attachVertexArrays];
  }

關鍵的方法是- (void)bindVertexDatasWithVertexBufferID: bufferType: verticesSize: datasPtr:,如下:

  /**
   *  使用頂點緩存對象
   *
   *  @param vertexBufferID 頂點緩存對象標識
   */
    - (void)bindVertexDatasWithVertexBufferID:(GLuint)vertexBufferID
                                   bufferType:(GLenum)bufferType
                                 verticesSize:(GLsizeiptr)size
                                     datasPtr:(const GLvoid*)dataPtr {
    
      glBindBuffer(bufferType, vertexBufferID);
      // 創建 資源 ( context )
      glBufferData(bufferType,        // 緩存塊 類型
                   size,              // 創建的 緩存塊 尺寸
                   dataPtr,           // 要綁定的頂點數據
                   GL_STATIC_DRAW);   // 緩存塊 用途
}

還有- (VFLineDrawInfo)drawLineInfoMaker 方法,生成相應圖形的數據源信息,如下:

// 位于 VFVertexDatasManager 類的
// - (VFLineDrawInfo)drawLineInfoMaker; 方法中
        case VFDrawGeometryType_SingleLine: {
            
            dataSize                = sizeof(singleLineVertices);
            dataPtr                 = singleLineVertices;
            verticesIndicesCount    = (GLsizei)(sizeof(singleLineVertices) /
                                                sizeof(singleLineVertices[0]));
            primitiveMode           = VFPrimitiveModeLines;
            
            break;
        }
        case VFDrawGeometryType_CrossLines: {
            
            dataSize                = sizeof(crossLinesVertices);
            dataPtr                 = crossLinesVertices;
            verticesIndicesCount    = (GLsizei)(sizeof(crossLinesVertices) /
                                                sizeof(crossLinesVertices[0]));
            primitiveMode           = VFPrimitiveModeLines;
            
            break;
        }

其中 @property (assign, nonatomic) VFDrawInfo drawInfo; 是定義的數據源信息結構體,具體信息如下:

// 位于 VFVertexDatasManager 類中
typedef struct {
      // 數據所占的內存大小
      GLsizeiptr dataSize;
      // 數據的內存首地址
      const GLvoid *dataPtr;
      // 需要繪制的點數量
      GLsizei verticesIndicesCount;
      // 圖元的繪制類型
      VFPrimitiveMode primitiveMode;
      // 下標數據所占的內存大小
      GLsizeiptr elementDataSize;
      // 下標內存首地址
      const GLvoid *elementDataPtr;
      // 下標個數
      GLsizei elementIndicesCount;
} VFDrawInfo;
  • 修改繪制方法,直接獲取信息即可
// 位于 VFVertexDatasManager 類中
#define GPUVBOMemoryPtr    (0)
/**
  *  繪制圖形
  */
 - (void)draw {
   glLineWidth(DEFAULT_LINE_WITH);
   if (self.drawInfo.elementIndicesCount) {
      glDrawElements(self.drawInfo.primitiveMode,
                     self.drawInfo.elementIndicesCount,
                     GL_UNSIGNED_BYTE,
                     GPUVBOMemoryPtr);  // GPU 內存中的首地址
     return;
 }
   glDrawArrays(self.drawInfo.primitiveMode,
                StartIndex, // 就是 0
                self.drawInfo.verticesIndicesCount);
}

其中 glLineWidth函數是修改線的寬度的;
glDrawElements是繪制下標的方法;這里不需要用到,所以先不解釋;

  • 修改圖形顯示
// 位于 VFVertexDatasManager 類中
   /**
   *  繪制的幾何圖形類型
   */
 @property (assign, nonatomic) VFDrawGeometryType drawGeometry;

  // 位于 VFRenderWindow 類
 // 位于 .m 文件的 263 行
  /**
   *  裝載頂點數據
   */
   - (void)prepareVertexDatas {
     [self.vertexManager setDrawGeometry:VFDrawGeometryType_CrossLines];
     [self.vertexManager attachVertexDatas];
}

這里新增了一個枚舉類型的變量,drawGeometry ,目的是方便外部類進行操控,而進行何種類型圖形的繪制渲染,VFDrawGeometryType 定義如下:

// VFVertexDatasManager .h 文件中
typedef NS_ENUM(NSUInteger, VFDrawGeometryType) {

      VFDrawGeometryType_SingleLine = 0,  // 單條線
      VFDrawGeometryType_CrossLines,      // 交叉線
    
      VFDrawGeometryType_MountainLines,   // 拆線山
    
      VFDrawGeometryType_TriangleLines,   // 線三角
      VFDrawGeometryType_RectangleLines,  // 線正方形
      VFDrawGeometryType_PentagonsLines,  // 線五邊形
      VFDrawGeometryType_HexagonsLines,   // 線六邊形
      VFDrawGeometryType_TrapezoidLines,  // 線梯形
      VFDrawGeometryType_PentagramLines,  // 線五角星
      VFDrawGeometryType_RoundLines,      // 線圓
    
      VFDrawGeometryType_LowPolyRectLines,// LP 線正方形
      VFDrawGeometryType_LowPolyPentLines,// LP 線五邊形
      VFDrawGeometryType_LowPolyHexLines, // LP 線六邊形
      VFDrawGeometryType_LowPolyTrazLines,// LP 線梯形
      VFDrawGeometryType_LowPolyStarLines,// LP 線五角星
    
      VFDrawGeometryType_BezierMountain,  // Bezier 山
      VFDrawGeometryType_BezierRound,     // Bezier 圓
      VFDrawGeometryType_BezierOval,      // Beizer 橢圓
};

這一節只是,單線與交叉線的繪制;

  • 程序運行結果

2. 繪制折線

LINE STRIP MOUN
  • 圖形分析
  • 首先這是一條線,所以選擇的是 線模式;
  • 但是它是一條折線,即多段線首尾相接組成的線,而且沒有閉合,GL_LINES_STRIP 模式;
  • 有 7 個頂點,6條線 (nPoints = mLines + 1)
  • 開始寫代碼
    • 數據源
// 位于 VFBaseGeometricVertexData.h
// 折線(山丘)
static const VFVertex mountainLinesVertices[] = {
    // Point one
    {-0.9f, -0.8f, 0.0f},
    
    // Point Two
    {-0.6f, -0.4f, 0.0f},
    
    // Point Three
    {-0.4f, -0.6f, 0.0f},
    
    // Point Four
    { 0.05f, -0.05f, 0.0f},
    
    // Point Five
    {0.45f, -0.65f, 0.0f},
    
    // Point Six
    { 0.55f,  -0.345f, 0.0f},
    
    // Point Seven
    { 0.95f, -0.95f, 0.0f},
};
  • 修改數據綁定方法
    在 drawLineInfoMaker 類中增加新的內容,其它不變;
// 位于 VFVertexDatasManager 類的
// - (VFLineDrawInfo)drawLineInfoMaker; 方法中
        case VFDrawGeometryType_MountainLines: {
            
            dataSize                = sizeof(mountainLinesVertices);
            dataPtr                 = mountainLinesVertices;
            verticesIndicesCount    = (GLsizei)(sizeof(mountainLinesVertices) /
                                                sizeof(mountainLinesVertices[0]));
            primitiveMode           = VFPrimitiveModeLineStrip;
            
            break;
        }
  • 修改圖形的顯示
// 位于 VFRenderWindow 類
// 位于 .m 文件的 263 行
/**
 *  裝載頂點數據
 */
 - (void)prepareVertexDatas {
    [self.vertexManager  setDrawGeometry:VFDrawGeometryType_MountainLines];
    [self.vertexManager attachVertexDatas];
}
  • 程序運行結果

3. 繪制幾何圖形

Triangle2Round.gif
LINE LOOP
  • 圖形分析
    多段線首尾相接組成的幾何形狀,GL_LINES_LOOP 模式;

nPoints = mLines

  • 開始寫代碼
    • 數據源(從左至右),其中五角星這個數據,可以利用內五邊形與外五邊形相結合的方法(當然內五邊形的點要做一個角度旋轉),生成相應的點;

      所有的點,都通過程序動態生成,如下:

這個類的計算原理是,建立極坐標系,確定起始點,再循環增加旋轉角度,就可以得到所有的點,包括圓的點(圓即正多邊形,不過它的邊數已經多到細到人眼無法識別,而出現曲線的效果,就像這一小節開始的動態圖一樣的原理,當然橢圓的點集也可以通過這種方式得到)

這兩個類在另外的工程里面, Github: 動態計算點

它的小應用,你可以按照自己的想法盡情改寫......


紅框處的,就是點的生成方法;箭頭所指的函數是把生成的點數據按照一定的格式寫入文件的方法(文件會自動創建);

下面是具體的數據:

// 三角形
static const VFVertex triangleLinesVertices[] = {
    // Point one
      {0.000000, 0.500000, 0.000000},

    // Point Two
      {-0.433013, -0.250000, 0.000000},

    // Point Three
      {0.433013, -0.250000, 0.000000},
};
// 四邊形
static const VFVertex rectangleLinesVertices[] = {
    // Point one
      {-0.353553, 0.353553, 0.000000},
    
    // Point Two
      {-0.353553, -0.353553, 0.000000},
    
    // Point Three
      {0.353553, -0.353553, 0.000000},
    
    // Point Four
      {0.353553, 0.353553, 0.000000},
};
// 五邊形
static const VFVertex pentagonsLinesVertices[] = {
    // Line one
      {0.000000, 0.500000, 0.000000},
      
    // Line Two
      {-0.475528, 0.154509, 0.000000},
    
    // Line Three
      {-0.293893, -0.404509, 0.000000},
    
    // Line Four
      {0.293893, -0.404509, 0.000000},
    
    // Line Five
      {0.475528, 0.154509, 0.000000},
};
// 六邊形
static const VFVertex hexagonsLinesVertices[] = {
    // Point one
      {0.000000, 0.500000, 0.000000},
    
    // Point Two
      {-0.433013, 0.250000, 0.000000},
    
    // Point Three
      {-0.433013, -0.250000, 0.000000},
    
    // Point Four
      {-0.000000, -0.500000, 0.000000},
    
    // Point Five
      {0.433013, -0.250000, 0.000000},
    
    // Point Six
      {0.433013, 0.250000, 0.000000},
};
// 梯形
static const VFVertex trapezoidLinesVertices[] = {
    // Point one
      {0.430057, 0.350000, 0.000000},
    
    // Point Two
      {-0.430057, 0.350000, 0.000000},
    
    // Point Three
      {-0.180057, -0.350000, 0.000000},
    
    // Point Four
      {0.180057, -0.350000, 0.000000},
};
// 五角星形
static const VFVertex pentagramLinesVertices[] = {
    // Point one
      {0.000000, 0.500000, 0.000000},
    
    // Point Two
      {-0.176336, 0.242705, 0.000000},
    
    // Point Three
      {-0.475528, 0.154509, 0.000000},
    
    // Point Four
      {-0.285317, -0.092705, 0.000000},
    
    // Point Five
      {-0.293893, -0.404509, 0.000000},
    
    // Point Six
      {-0.000000, -0.300000, 0.000000},
    
    // Point Seven
      {0.293893, -0.404509, 0.000000},
    
    // Point Eight
      {0.285317, -0.092705, 0.000000},
    
    // Point Nine
      {0.475528, 0.154509, 0.000000},
    
    // Point Ten
      {0.176336, 0.242705, 0.000000},
};

圓的頂點數據在單獨的文件中, VFRound.h,也是通過動態點生成的【因為點太多,所以單獨放在一個文件中進行管理】;

  • 修改數據綁定方法,在 drawLineInfoMaker 方法中增加新的內容
//  位于 VFVertexDatasManager 類的
// - (VFLineDrawInfo)drawLineInfoMaker; 方法中
        case VFDrawGeometryType_TriangleLines: {
            
            dataSize                = sizeof(triangleLinesVertices);
            dataPtr                 = triangleLinesVertices;
            verticesIndicesCount    = (GLsizei)(sizeof(triangleLinesVertices) /
                                                sizeof(triangleLinesVertices[0]));
            primitiveMode           = VFPrimitiveModeLineLoop;
            
            break;
        }
        case VFDrawGeometryType_RectangleLines: {
            
            dataSize                = sizeof(rectangleLinesVertices);
            dataPtr                 = rectangleLinesVertices;
            verticesIndicesCount    = (GLsizei)(sizeof(rectangleLinesVertices) /
                                                sizeof(rectangleLinesVertices[0]));
            primitiveMode           = VFPrimitiveModeLineLoop;
            
            break;
        }
        case VFDrawGeometryType_PentagonsLines: {
            
            dataSize                = sizeof(pentagonsLinesVertices);
            dataPtr                 = pentagonsLinesVertices;
            verticesIndicesCount    = (GLsizei)(sizeof(pentagonsLinesVertices) /
                                                sizeof(pentagonsLinesVertices[0]));
            primitiveMode           = VFPrimitiveModeLineLoop;
            
            break;
        }
        case VFDrawGeometryType_HexagonsLines: {
            
            dataSize                = sizeof(hexagonsLinesVertices);
            dataPtr                 = hexagonsLinesVertices;
            verticesIndicesCount    = (GLsizei)(sizeof(hexagonsLinesVertices) /
                                                sizeof(hexagonsLinesVertices[0]));
            primitiveMode           = VFPrimitiveModeLineLoop;
            
            break;
        }
        case VFDrawGeometryType_TrapezoidLines: {
            
            dataSize                = sizeof(trapezoidLinesVertices);
            dataPtr                 = trapezoidLinesVertices;
            verticesIndicesCount    = (GLsizei)(sizeof(trapezoidLinesVertices) /
                                                sizeof(trapezoidLinesVertices[0]));
            primitiveMode           = VFPrimitiveModeLineLoop;
            
            break;
        }
        case VFDrawGeometryType_PentagramLines: {
            
            dataSize                = sizeof(pentagramLinesVertices);
            dataPtr                 = pentagramLinesVertices;
            verticesIndicesCount    = (GLsizei)(sizeof(pentagramLinesVertices) /
                                                sizeof(pentagramLinesVertices[0]));
            primitiveMode           = VFPrimitiveModeLineLoop;
            
            break;
        }
        case VFDrawGeometryType_RoundLines: {
            
            dataSize                = sizeof(roundGeometry);
            dataPtr                 = roundGeometry;
            verticesIndicesCount    = (GLsizei)(sizeof(roundGeometry) /
                                                sizeof(roundGeometry[0]));
            primitiveMode           = VFPrimitiveModeLineLoop;
            
            break;
        }
  • 圖形顯示類(VFRenderWindow )也做相應的修改即可,位于 .m 文件的 263 行;

  • 程序運行結果


    TRI-ROUND

4. 繪制三角化的幾何圖形(Low Poly)

TRIANGLE STRIP FAN PLO
  • 圖形分析
    • 首先它們都是由線組成,線模式
    • 其次,它們的線是閉合的,首尾相接?GL_LINES_LOOP ?
    • 所謂首尾相接,形成閉合圖形,是起點直接到達終點,就是說起點只會被經過一次,就是最后閉合的那一次;觀察圖形,起點如果只被經過一次,能不能用線繪制出來,很難吧,特別是最后一個,所以這里直接用 GL_LINES_STRIP 模式,之后任意編排線經過點的順序,即可。(當然,如果你有興趣的話,也可以寫一個算法去計算點被經過最少的次數下,圖形可以完整繪制出來)**
    • 點可能會多次被經過,那么就是說,這個點要被程序調度多次,但是 glDrawArrays 只能一個頂點被調度一次啊。所以這里要用它的兄弟函數 glDrawElements 這個函數的意思就是繪制成員,頂點數據的下標就是它的成員,即通過頂點數據的成員來訪問數據而進行靈活繪制。

glDrawElements 根據頂點數據在內存的下標進行繪制的方法

glDrawElements
void glDrawElements(GLenum mode, GLsizei count,GLenum type, const GLvoid* indices)
mode 只能是以下幾種:GL_POINTS、GL_LINES、GL_LINE_STRIP、GL_LINE_LOOP、GL_TRIANGLES、GL_TRIANGLE_STRIP、GL_TRIANGLE_FAN
count indices 的數量
type 下標的數據類型:GL_UNSIGNED_BYTE、GL_UNSIGNED_SHORT、GL_UNSIGNED_INT(它只能在使用了OES_element_index_uint 才能使用)
indices 下標在內存中的首地址(如果使用了 VBO,就是 GPU 內存中的首地址,若不是,則為 CPU 內存中的首地址)
  • 開始寫代碼
    • VFLineDrawInfo 增加了對下標繪制的支持
typedef struct {
      // 數據所占的內存大小
      GLsizeiptr dataSize;
      // 數據的內存首地址
      const GLvoid *dataPtr;
      // 需要繪制的點數量
      GLsizei verticesIndicesCount;
      // 圖元的繪制類型
      VFPrimitiveMode primitiveMode;
      // 下標數據所占的內存大小
      GLsizeiptr elementDataSize; // 在這.....
      // 下標內存首地址
      const GLvoid *elementDataPtr; // 在這.....
      // 下標個數
      GLsizei elementIndicesCount; // 在這.....
} VFLineDrawInfo;
  • 在原來的線數據基礎下,增加對應圖形的下標數據
    這里選取下標的原則是,讓每一個點都盡可能少地被經過,從而完成圖形的繪制,目的就是為了節省資源。
// 四邊形的下標數據
static const GLubyte rectangleElementIndeices[] = {
      0, 1, 2,
      3, 0, 2,
};
// 五邊形的下標數據
static const GLubyte pentagonsElementIndeices[] = {
      4, 1, 0, 4,
      3, 1, 2, 3,
};
// 六邊形的下標數據
static const GLubyte hexagonsElementIndeices[] = {
      5, 1, 0, 5,
      4, 1, 2, 4,
      3, 2,
};
// 梯形的下標數據
static const GLubyte trapezoidElementIndeices[] = {
    1, 2, 3, 0,
    1, 3,
};
//五角星形的下標數據
static const GLubyte pentagramElementIndeices[] = {
      1, 2, 3, 4,
      5, 6, 7, 8,
      9, 0, 1,
      9, 7, 5, 3, 1,
      5, 7, 1 
};
  • 修改數據綁定方法

綁定新增加的下標數據支持,使用 VBO 的方式(雖然前面已經寫過,這里重溫一下,因為這里都是真正的應用)

// 核心方法
/**
 *  裝載數據
*/
  - (void)attachVertexDatas {
    self.currentVBOIdentifier = [self createVBO];
    
    self.lineInfo = [self drawLineInfoMaker];
    
    if (self.lineInfo.elementDataPtr) {
        self.currentElementVBOIdentifier = [self createVBO];
        [self bindVertexDatasWithVertexBufferID:self.currentElementVBOIdentifier
                                     bufferType:GL_ELEMENT_ARRAY_BUFFER
                                   verticesSize:self.lineInfo.elementDataSize
                                       datasPtr:self.lineInfo.elementDataPtr];
    }
    
    [self bindVertexDatasWithVertexBufferID:self.currentVBOIdentifier
                                 bufferType:GL_ARRAY_BUFFER
                               verticesSize:self.lineInfo.dataSize
                                   datasPtr:self.lineInfo.dataPtr]; // CPU 內存首地址
    
    [self attachVertexArrays];
}

在 drawLineInfoMaker 方法中新增內容:

// drawLineInfoMaker 里面的新增內容
        case VFDrawGeometryType_LowPolyRectLines: {
            
            dataSize                = sizeof(rectangleLinesVertices);
            dataPtr                 = rectangleLinesVertices;
            elementDataSize         = sizeof(rectangleElementIndeices);
            elementDataPtr          = rectangleElementIndeices;
            elementIndicesCount     = (GLsizei)(sizeof(rectangleElementIndeices) /
                                                sizeof(rectangleElementIndeices[0]));
            primitiveMode           = VFPrimitiveModeLineStrip;
            
            break;
        }
        case VFDrawGeometryType_LowPolyPentLines: {
            
            dataSize                = sizeof(pentagonsLinesVertices);
            dataPtr                 = pentagonsLinesVertices;
            elementDataSize         = sizeof(pentagonsElementIndeices);
            elementDataPtr          = pentagonsElementIndeices;
            elementIndicesCount     = (GLsizei)(sizeof(pentagonsElementIndeices) /
                                                sizeof(pentagonsElementIndeices[0]));
            primitiveMode           = VFPrimitiveModeLineStrip;
            
            break;
        }
        case VFDrawGeometryType_LowPolyHexLines: {
            
            dataSize                = sizeof(hexagonsLinesVertices);
            dataPtr                 = hexagonsLinesVertices;
            elementDataSize         = sizeof(hexagonsElementIndeices);
            elementDataPtr          = hexagonsElementIndeices;
            elementIndicesCount     = (GLsizei)(sizeof(hexagonsElementIndeices) /
                                                sizeof(hexagonsElementIndeices[0]));
            primitiveMode           = VFPrimitiveModeLineStrip;
            
            break;
        }
        case VFDrawGeometryType_LowPolyTrazLines: {
            
            dataSize                = sizeof(trapezoidLinesVertices);
            dataPtr                 = trapezoidLinesVertices;
            elementDataSize         = sizeof(trapezoidElementIndeices);
            elementDataPtr          = trapezoidElementIndeices;
            elementIndicesCount     = (GLsizei)(sizeof(trapezoidElementIndeices) /
                                                sizeof(trapezoidElementIndeices[0]));
            primitiveMode           = VFPrimitiveModeLineStrip;
            
            break;
        }
        case VFDrawGeometryType_LowPolyStarLines: {
        
            dataSize                = sizeof(pentagramLinesVertices);
            dataPtr                 = pentagramLinesVertices;
            elementDataSize         = sizeof(pentagramElementIndeices);
            elementDataPtr          = pentagramElementIndeices;
            elementIndicesCount     = (GLsizei)(sizeof(pentagramElementIndeices) /
                                                sizeof(pentagramElementIndeices[0]));
            primitiveMode           = VFPrimitiveModeLineStrip;
            
            break;
        }
// 修改的數據綁定方法
/**
 *  使用頂點緩存對象
 *
 *  @param vertexBufferID 頂點緩存對象標識
 */
  - (void)bindVertexDatasWithVertexBufferID:(GLuint)vertexBufferID
                               bufferType:(GLenum)bufferType
                             verticesSize:(GLsizeiptr)size
                                 datasPtr:(const GLvoid*)dataPtr {
    
    glBindBuffer(bufferType, vertexBufferID);
    
    // 創建 資源 ( context )
    glBufferData(bufferType,        // 緩存塊 類型
                 size,              // 創建的 緩存塊 尺寸
                 dataPtr,           // 要綁定的頂點數據
                 GL_STATIC_DRAW);   // 緩存塊 用途
}
  • 數據繪制方法中的下標繪制支持
// 修改的繪制方法
#define GPUVBOMemoryPtr    (0)
/**
 *  繪制圖形
 */
  - (void)draw {
    
    glLineWidth(DEFAULT_LINE_WITH);
    
    if (self.lineInfo.elementIndicesCount) {
        glDrawElements(self.lineInfo.primitiveMode,
                       self.lineInfo.elementIndicesCount,
                       GL_UNSIGNED_BYTE,
                       GPUVBOMemoryPtr);  // GPU 內存中的首地址
        return;
    }
    
    glDrawArrays(self.lineInfo.primitiveMode,
                 0,
                 self.lineInfo.verticesIndicesCount);
}
  • 程序運行結果
Rect-Star

5. 繪制曲線、圓形

BAISER
  • 圖形分析

    • 首先,它們都是曲線,它們都可以通過 GL_LINE_STRIP 條帶來進行繪制,而且后者也可能通過 GL_LINE_LOOP 進行繪制;
    • 根據上一節的圓可以知道,只要線足夠短,以致人眼無法分辨,那么折線就可以形成曲線,但是有個問題?左邊的,折線怎么控制它的方向呢,第一個點與第二個點之間的折線彎曲程度,要怎么才能生成它的點集呢?
    • OpenGL 是以點為基礎進行圖元的繪制的,那么只要有一個方法動態地根據固定點去控制之間曲線點的生成,問題就解決了。坐標與點,那么肯定是函數,要生成曲線,貝塞爾曲線函數就可以了(如果想不到,回憶你所見過的任一個圖形繪制軟件,就秒懂了,如:PS 的鋼筆工具, skecth 的鋼筆工具......)。
  • 知識補充( 貝塞爾曲線 )
    請看下面的 word/pdf 文檔《貝塞爾曲線推導》
    書寫貝塞爾曲線函數如下,具體實現也在Github: 動態計算點 這里

    文件

    應用

  • 開始寫代碼

    • 數據源都在 文件中,紅框處
  • 增加 VFDrawGeometryType 內容
    VFDrawGeometryType_BezierMountain,
    VFDrawGeometryType_BezierRound,
    VFDrawGeometryType_BezierOval,
  • drawLineInfoMaker 里面的新增內容
        case VFDrawGeometryType_BezierMountain: {
            
            dataSize                = sizeof(_BEZMountain);
            dataPtr                 = _BEZMountain;
            verticesIndicesCount    = (GLsizei)(sizeof(_BEZMountain) /
                                                sizeof(_BEZMountain[0]));
            primitiveMode           = VFPrimitiveModeLineStrip;
            
            break;
        }
        case VFDrawGeometryType_BezierRound: {
        
            dataSize                = sizeof(_BEZRound);
            dataPtr                 = _BEZRound;
            verticesIndicesCount    = (GLsizei)(sizeof(_BEZRound) /
                                                sizeof(_BEZRound[0]));
            primitiveMode           = VFPrimitiveModeLineStrip;
            
            break;
        }
        case VFDrawGeometryType_BezierOval: {
            
            dataSize                = sizeof(_BEZOval);
            dataPtr                 = _BEZOval;
            verticesIndicesCount    = (GLsizei)(sizeof(_BEZOval) /
                                                sizeof(_BEZOval[0]));
            primitiveMode           = VFPrimitiveModeLineStrip;
            
            break;
        }
  • 當然圖形顯示類,也要改咯!

  • 程序運行結果

Bezier

二、圖元繪制之三角形

Triangles,就是多個三角形;

Triangle Strip, 指條帶,相互連接的三角形;

Triangle Fan, 指扇面,相互連接的三角形;

圖1:三角形模式
圖2:STRIP
圖3:FAN
模式 三角形與點的數量關系
GL_TRIANGLES nPoints = 3 * mTriangles
GL_TRIANGLE_STRIP nPoints = mTriangles + 2
GL_TRIANGLE_FAN nPoints = mTriangles + 2

ep: 圖1 中的圖形

模式 三角形與點的數量關系
GL_TRIANGLES v0~v5( 6 ) = 3 * 2
GL_TRIANGLE_STRIP v0~v4( 5 ) = 3+ 2
GL_TRIANGLE_FAN v0~v4( 5 ) = 3+ 2

0. 工程目錄

工程目錄

這里沒有什么太大的變化,只是數據的集合發生了一些變化而已;

1. 繪制基本幾何圖形

TRIANGLE STRIP FAN
  • 圖形分析
    • 首先,第一張圖片每一個圖形都是一個面,但是 OpenGL 只能直接繪制三角面,所以必須把圖形分解成三角面才能進行繪制;
    • 以下就是分解成三角面之后的圖形:
TRIANGLE LINESON

當然你也可以按照自己的方式進行分解,一定要遵守這里的點、三角形關系

不然圖形是不能正確地繪制出來的;

  • 這里容易出問題的是最后一個圖形(五角星形),三角形與點的關系:10(點的數量) = 10(分割出來的三角形數量) + 2,很明顯是不相等的,所以 10 個點是不可能繪制出來這個圖形的,只能再增加兩個點;除了點的數量問題外,它還不是一個條帶(或者說用條帶來描述并不合適),它更適合用扇面來描述,即 GL_TRIANGLE_FAN;

  • 開始寫代碼

    • 數據源,它們都可以通過 FAN 或 STRIP 進行繪制,當然那個點用得少而且圖形繪制完整,以及方便,就用那個;像五角星那個圖形這么麻煩,當然不做兩種試驗了;STRIP 模式下的點的分布要特別注意,偶數下標在上面,奇數下標在下面【把圖形壓扁,你就能看出來了】
// 三角形
static const VFVertex triangleTrianglesVertices[] = {
      // Point V0
      {0.000000, 0.500000, 0.000000},
    
      // Point V1
    {-0.433013, -0.250000, 0.000000},
    
      // Point V2
      {0.433013, -0.250000, 0.000000},
};
// 四邊形( 0,1,2,3,0,2 )
static const VFVertex rectangleTrianglesVertices[] = {

      // GL_TRIANGLE_FAN
      // Point V0
    {-0.353553, 0.353553, 0.000000},    // V0
    
      // Point V1
    {-0.353553, -0.353553, 0.000000},   // V1
    
      // Point V2
    {0.353553, -0.353553, 0.000000},    // V2
    
      // Point V3
       {0.353553, 0.353553, 0.000000},     // V3
    
    // GL_TRIANGLE_STRIP
//    // Point V0
//    {-0.353553, 0.353553, 0.000000},    // V0
//    
//    // Point V1
//    {-0.353553, -0.353553, 0.000000},   // V1
//    
//    // Point V3
//    {0.353553, 0.353553, 0.000000},     // V3
//    
//    // Point V2
//    {0.353553, -0.353553, 0.000000},    // V2
};
// 五邊形
static const VFVertex pentagonsTrianglesVertices[] = {
    
    // GL_TRIANGLE_FAN
//    // Point V0
//  {0.000000, 0.500000, 0.000000},
//    
//    // Point V1
//  {-0.475528, 0.154509, 0.000000},
//    
//    // Point V2
//  {-0.293893, -0.404509, 0.000000},
//    
//    // Point V3
//  {0.293893, -0.404509, 0.000000},
//    
//    // Point V4
//  {0.475528, 0.154509, 0.000000},
    
      // GL_TRIANGLE_STRIP
      // Point V1
      {-0.475528, 0.154509, 0.000000},
      
      // Point V2
      {-0.293893, -0.404509, 0.000000},
    
      // Point V0
      {0.000000, 0.500000, 0.000000},
    
      // Point V3
      {0.293893, -0.404509, 0.000000},
    
      // Point V4
      {0.475528, 0.154509, 0.000000},
};
// 六邊形
static const VFVertex hexagonsTrianglesVertices[] = {
    
      // GL_TRIANGLE_FAN
      // Point V0
    {0.000000, 0.500000, 0.000000},
    
      // Point V1
    {-0.433013, 0.250000, 0.000000},
    
      // Point V2
    {-0.433013, -0.250000, 0.000000},
    
      // Point V3
    {-0.000000, -0.500000, 0.000000},
    
      // Point V4
      {0.433013, -0.250000, 0.000000},
    
      // Point V5
      {0.433013, 0.250000, 0.000000},
    
    // GL_TRIANGLE_STRIP
//    // Point V1
//    {-0.433013, 0.250000, 0.000000},
//    
//    // Point V2
//    {-0.433013, -0.250000, 0.000000},
//    
//    // Point V0
//    {0.000000, 0.500000, 0.000000},
//    
//    // Point V3
//    {-0.000000, -0.500000, 0.000000},
//    
//    // Point V4
//    {0.433013, -0.250000, 0.000000},
//    
//    // Point V5
//    {0.433013, 0.250000, 0.000000},
//    
//    // Point V0
//    {0.000000, 0.500000, 0.000000},
};
// 梯形
static const VFVertex trapezoidTrianglesVertices[] = {
    
      // GL_TRIANGLE_FAN
  //    // Point V0
  //    {0.430057, 0.350000, 0.000000},
  //    
  //    // Point V1
  //    {-0.430057, 0.350000, 0.000000},
  //    
  //    // Point V2
  //    {-0.180057, -0.350000, 0.000000},
   //    
  //    // Point V3
  //    {0.180057, -0.350000, 0.000000},
    
      // GL_TRIANGLE_STRIP
      // Point V0
      {0.430057, 0.350000, 0.000000},
    
      // Point V1
      {-0.430057, 0.350000, 0.000000},
    
      // Point V3
      {0.180057, -0.350000, 0.000000},
      
      // Point V2
      {-0.180057, -0.350000, 0.000000},
};
// 五角星形 10 = (n - 2) -> n = 12
static const VFVertex pentagramTrianglesVertices[] = {
    
      // GL_TRIANGLE_FAN
      // Point V0
      {0.000000, 0.000000, 0.000000}, // 在原來的基礎上,增加的起點
    
      // Point V1
    {0.000000, 0.500000, 0.000000},
    
      // Point V2
    {-0.176336, 0.242705, 0.000000},
    
      // Point V3
    {-0.475528, 0.154509, 0.000000},
    
      // Point V4
    {-0.285317, -0.092705, 0.000000},
    
      // Point V5
      {-0.293893, -0.404509, 0.000000},
    
      // Point V6
    {-0.000000, -0.300000, 0.000000},
    
      // Point V7
      {0.293893, -0.404509, 0.000000},
    
      // Point V8
      {0.285317, -0.092705, 0.000000},
    
      // Point V9
      {0.475528, 0.154509, 0.000000},
    
      // Point V10
      {0.176336, 0.242705, 0.000000},
    
      // Point V11
    {0.000000, 0.500000, 0.000000},// 在原來的基礎上,增加的終點
};
  • 數據的綁定(與線元一致),只是修改了 VFDrawGeometryType 枚舉和 drawLineInfoMaker 方法而已;
    • attachVertexDatas
    /**
     *  裝載數據
     */
      - (void)attachVertexDatas {
        self.currentVBOIdentifier = [self createVBO];
        self.lineInfo = [self drawLineInfoMaker];
        if (self.lineInfo.elementDataPtr) {
            self.currentElementVBOIdentifier = [self createVBO];
            [self bindVertexDatasWithVertexBufferID:self.currentElementVBOIdentifier
                                         bufferType:GL_ELEMENT_ARRAY_BUFFER
                                       verticesSize:self.lineInfo.elementDataSize
                                           datasPtr:self.lineInfo.elementDataPtr];
      }
        [self bindVertexDatasWithVertexBufferID:self.currentVBOIdentifier
                                     bufferType:GL_ARRAY_BUFFER
                                   verticesSize:self.lineInfo.dataSize
                                       datasPtr:self.lineInfo.dataPtr]; // CPU 內存首地址
        [self attachVertexArrays];
}
- VFDrawGeometryType
// 在這呢......
typedef NS_ENUM(NSUInteger, VFDrawGeometryType) {
      VFDrawGeometryType_TriangleTriangles = 0,
      VFDrawGeometryType_RectangleTriangles,
      VFDrawGeometryType_PentagonsTriangles,
      VFDrawGeometryType_HexagonsTriangles,
      VFDrawGeometryType_TrapezoidTriangles,
      VFDrawGeometryType_PentagramTriangles,
      VFDrawGeometryType_RoundTriangles,
};
- drawInfoMaker 方法
// - (VFDrawInfo)drawInfoMaker 方法
// 在這呢......
    switch (self.drawGeometry) {
        case VFDrawGeometryType_TriangleTriangles: {
            
            dataSize                = sizeof(triangleTrianglesVertices);
            dataPtr                 = triangleTrianglesVertices;
            verticesIndicesCount    = (GLsizei)(sizeof(triangleTrianglesVertices) /
                                                sizeof(triangleTrianglesVertices[0]));
            primitiveMode           = VFPrimitiveModeTriangles;
            
            break;
        }
        case VFDrawGeometryType_RectangleTriangles: {
            
            dataSize                = sizeof(rectangleTrianglesVertices);
            dataPtr                 = rectangleTrianglesVertices;
            verticesIndicesCount    = (GLsizei)(sizeof(rectangleTrianglesVertices) /
                                                sizeof(rectangleTrianglesVertices[0]));
            primitiveMode           = VFPrimitiveModeTriangleFan;
            
            break;
        }
        case VFDrawGeometryType_PentagonsTriangles: {
            
            dataSize                = sizeof(pentagonsTrianglesVertices);
            dataPtr                 = pentagonsTrianglesVertices;
            verticesIndicesCount    = (GLsizei)(sizeof(pentagonsTrianglesVertices) /
                                                sizeof(pentagonsTrianglesVertices[0]));
            primitiveMode           = VFPrimitiveModeTriangleStrip;
            
            break;
        }
        case VFDrawGeometryType_HexagonsTriangles: {
            
            dataSize                = sizeof(hexagonsTrianglesVertices);
            dataPtr                 = hexagonsTrianglesVertices;
            verticesIndicesCount    = (GLsizei)(sizeof(hexagonsTrianglesVertices) /
                                                sizeof(hexagonsTrianglesVertices[0]));
            primitiveMode           = VFPrimitiveModeTriangleFan;
            
            break;
        }
        case VFDrawGeometryType_TrapezoidTriangles: {
            
            dataSize                = sizeof(trapezoidTrianglesVertices);
            dataPtr                 = trapezoidTrianglesVertices;
            verticesIndicesCount    = (GLsizei)(sizeof(trapezoidTrianglesVertices) /
                                                sizeof(trapezoidTrianglesVertices[0]));
            primitiveMode           = VFPrimitiveModeTriangleStrip;
            
            break;
        }
        case VFDrawGeometryType_PentagramTriangles: {
            
            dataSize                = sizeof(pentagramTrianglesVertices);
            dataPtr                 = pentagramTrianglesVertices;
            verticesIndicesCount    = (GLsizei)(sizeof(pentagramTrianglesVertices) /
                                                sizeof(pentagramTrianglesVertices[0]));
            primitiveMode           = VFPrimitiveModeTriangleFan;
            
            break;
        }
        case VFDrawGeometryType_RoundTriangles: {
            
            dataSize                = sizeof(roundGeometry);
            dataPtr                 = roundGeometry;
            verticesIndicesCount    = (GLsizei)(sizeof(roundGeometry) /
                                                sizeof(roundGeometry[0]));
            primitiveMode           = VFPrimitiveModeTriangleFan;
            
            break;
        }
    }
- draw 方法
#define GPUVBOMemoryPtr    (0)
  /**
     *  繪制圖形
   */
      - (void)draw {
    
        if (self.lineInfo.elementIndicesCount) {
            glDrawElements(self.lineInfo.primitiveMode,
                           self.lineInfo.elementIndicesCount,
                           GL_UNSIGNED_BYTE,
                           GPUVBOMemoryPtr);  // GPU 內存中的首地址
          return;
    }
    
        glDrawArrays(self.lineInfo.primitiveMode,
                     StartIndex, // 0
                     self.lineInfo.verticesIndicesCount);
}
  • 同樣要修改圖形顯示類(VFRenderWindow).m 文件的 263 行;

  • 程序運行結果

TRI-ROUND Triangle

完整的程序代碼: Github DrawGeometries_Triangles


三、圖元繪制之點精靈

這里不進行詳細講解,個人感覺在這里講沒什么意思,還是放在 Texture 紋理部分進行詳細講解會比較有用,而且好玩;

如果只是學習 gl_PointSize 的話沒意思,結合 gl_PointCoord 去學習反而更有趣,不過這里要有紋理的知識,所以先行不講了;


四、練練手

Challenges

這里的目的不是為了繪制它們而進行繪制,而是針對圖元繪制做一個深入的學習,要學習分析圖形和尋找合適有效的繪制方式,而且還要做到判斷數據的大致生成方法方式是什么,不然你永遠都只是一個只會搞代碼的搬運工而已;

編程可不僅僅是搞代碼;

0. 工程目錄

取消了采用結構體存取數據的方式,改用 Model 類,方便 OC 處理和傳輸;

1. 繪制一棵卡通樹

Tree

提示:進行兩次的 glDraw* 調用,分別繪制外邊的線和內部的填充圖

2. 繪制一張卡片

Card

提示:把數據分成左、右、右中線,三種,原因是左邊的數據是用貝塞爾曲線生成數據量非常大;主要是利用 glBufferSubData 與 glBufferData 的結合,以及 glVertexAttribPointer 的配合;

3. 繪制一棵草

Grass

注意:盡可以地用肉眼去判斷線的走向,用 動態計算點 的類做實驗,不斷成長起來吧。

完整的挑戰項目:Github DrawGeometries_Challenge

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,001評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,786評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,986評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,204評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,964評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,354評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,410評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,554評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,106評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,918評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,093評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,648評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,342評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,755評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,009評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,839評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,107評論 2 375

推薦閱讀更多精彩內容