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

半紙淵發表於2017-12-14

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


目錄

零、目標+準備

一、圖元繪製之線

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

二、圖元繪製之三角形

  1. 工程目錄
  2. 繪製基本幾何圖形

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

四、練練手

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

零、目標+準備

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, 指首尾相接的線段,第一條線和最後一條線連線在一起,即閉合的曲線;

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

模式 線與點的數量關係
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 橢圓
};
複製程式碼

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

  • 程式執行結果
    OpenGL ES 2 0 (iOS)[03]:熟練圖元繪製,玩轉二維圖形

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];
}
複製程式碼
  • 程式執行結果
    OpenGL ES 2 0 (iOS)[03]:熟練圖元繪製,玩轉二維圖形

3. 繪製幾何圖形

Triangle2Round.gif
LINE LOOP

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

nPoints = mLines

  • 開始寫程式碼

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

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

      OpenGL ES 2 0 (iOS)[03]:熟練圖元繪製,玩轉二維圖形
      這個類的計算原理是,建立極座標系,確定起始點,再迴圈增加旋轉角度,就可以得到所有的點,包括圓的點*(圓即正多邊形,不過它的邊數已經多到細到人眼無法識別,而出現曲線的效果,就像這一小節開始的動態圖一樣的原理,當然橢圓的點集也可以通過這種方式得到)*;

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

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

    OpenGL ES 2 0 (iOS)[03]:熟練圖元繪製,玩轉二維圖形
    紅框處的,就是點的生成方法;箭頭所指的函式是把生成的點資料按照一定的格式寫入檔案的方法(檔案會自動建立);

    下面是具體的資料:

// 三角形
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: 動態計算點 這裡

    檔案
    應用

  • 開始寫程式碼

    • 資料來源都在 檔案中,紅框處
      OpenGL ES 2 0 (iOS)[03]:熟練圖元繪製,玩轉二維圖形
    • 增加 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

  • 完整的線元工程, Github:DrawGeometries_Lines


二、圖元繪製之三角形

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
當然你也可以按照自己的方式進行分解,一定要遵守這裡的點、三角形關係
OpenGL ES 2 0 (iOS)[03]:熟練圖元繪製,玩轉二維圖形
不然圖形是不能正確地繪製出來的;

  • 這裡容易出問題的是最後一個圖形(五角星形),三角形與點的關係: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. 工程目錄

OpenGL ES 2 0 (iOS)[03]:熟練圖元繪製,玩轉二維圖形
取消了採用結構體存取資料的方式,改用 Model 類,方便 OC 處理和傳輸;

1. 繪製一棵卡通樹

Tree

提示:進行兩次的 glDraw* 呼叫,分別繪製外邊的線和內部的填充圖

2. 繪製一張卡片

Card

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

3. 繪製一棵草

Grass

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

完整的挑戰專案:Github DrawGeometries_Challenge

相關文章