OpenGL ES 2.0 (iOS)[03]:熟练图元绘制,玩转二维图形


学习这篇:

文章的大前提是,你得有《OpenGL ES 2.0 (iOS): 一步从一个小三角开始》的基础知识。


本文核心目的就是熟练图形的分析与绘制


目录

零、目标+准备

一、图元绘制之线

0.工程目录
1. 绘制单一、交叉的线
2. 绘制折线
3. 绘制几何图形
4. 绘制三角化的几何图形
5. 绘制曲线、圆形

二、图元绘制之三角形

0.工程目录
1. 绘制基本几何图形

三、图元绘制之点精灵(内容为空)

四、练练手

0.工程目录
1. 绘制一棵卡通树
2. 绘制一张卡片
3. 绘制一棵草

零、目标+准备

  1. 目标
OpenGL ES 2.0 (iOS)[03]:熟练图元绘制,玩转二维图形_第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]:熟练图元绘制,玩转二维图形_第2张图片
模式 线与点的数量关系
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.工程目录

完整的线元工程在,这一章的结尾;


OpenGL ES 2.0 (iOS)[03]:熟练图元绘制,玩转二维图形_第3张图片
工程目录

图中红色箭头所指的就是要修改的类,其中 VFVertexDatasManager 类是核心,它是负责整个工程的数据绑定和图形绘制的;
蓝色框所指的都是工程中的静态顶点数据(当然你也可以动态生成并进行绑定绘制);

1. 绘制单一、交叉的线

OpenGL ES 2.0 (iOS)[03]:熟练图元绘制,玩转二维图形_第4张图片
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]:熟练图元绘制,玩转二维图形_第5张图片

2. 绘制折线

OpenGL ES 2.0 (iOS)[03]:熟练图元绘制,玩转二维图形_第6张图片
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]:熟练图元绘制,玩转二维图形_第7张图片

3. 绘制几何图形

Triangle2Round.gif
OpenGL ES 2.0 (iOS)[03]:熟练图元绘制,玩转二维图形_第8张图片
LINE LOOP
  • 图形分析
    多段线首尾相接组成的几何形状,GL_LINES_LOOP 模式;

nPoints = mLines

  • 开始写代码
    • 数据源(从左至右),其中五角星这个数据,可以利用内五边形与外五边形相结合的方法(当然内五边形的点要做一个角度旋转),生成相应的点;

      所有的点,都通过程序动态生成,如下:

这个类的计算原理是,建立极坐标系,确定起始点,再循环增加旋转角度,就可以得到所有的点,包括圆的点(圆即正多边形,不过它的边数已经多到细到人眼无法识别,而出现曲线的效果,就像这一小节开始的动态图一样的原理,当然椭圆的点集也可以通过这种方式得到)

这两个类在另外的工程里面, Github: 动态计算点

它的小应用,你可以按照自己的想法尽情改写......


OpenGL ES 2.0 (iOS)[03]:熟练图元绘制,玩转二维图形_第9张图片

红框处的,就是点的生成方法;箭头所指的函数是把生成的点数据按照一定的格式写入文件的方法(文件会自动创建);

下面是具体的数据:

// 三角形
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 行;

  • 程序运行结果


    OpenGL ES 2.0 (iOS)[03]:熟练图元绘制,玩转二维图形_第10张图片
    TRI-ROUND

4. 绘制三角化的几何图形(Low Poly)

OpenGL ES 2.0 (iOS)[03]:熟练图元绘制,玩转二维图形_第11张图片
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);
}
  • 程序运行结果
OpenGL ES 2.0 (iOS)[03]:熟练图元绘制,玩转二维图形_第12张图片
Rect-Star

5. 绘制曲线、圆形

OpenGL ES 2.0 (iOS)[03]:熟练图元绘制,玩转二维图形_第13张图片
BAISER
  • 图形分析

    • 首先,它们都是曲线,它们都可以通过 GL_LINE_STRIP 条带来进行绘制,而且后者也可能通过 GL_LINE_LOOP 进行绘制;
    • 根据上一节的圆可以知道,只要线足够短,以致人眼无法分辨,那么折线就可以形成曲线,但是有个问题?左边的,折线怎么控制它的方向呢,第一个点与第二个点之间的折线弯曲程度,要怎么才能生成它的点集呢?
    • OpenGL 是以点为基础进行图元的绘制的,那么只要有一个方法动态地根据固定点去控制之间曲线点的生成,问题就解决了。坐标与点,那么肯定是函数,要生成曲线,贝塞尔曲线函数就可以了(如果想不到,回忆你所见过的任一个图形绘制软件,就秒懂了,如:PS 的钢笔工具, skecth 的钢笔工具......)。
  • 知识补充( 贝塞尔曲线 )
    请看下面的 word/pdf 文档《贝塞尔曲线推导》
    书写贝塞尔曲线函数如下,具体实现也在Github: 动态计算点 这里

    文件

    OpenGL ES 2.0 (iOS)[03]:熟练图元绘制,玩转二维图形_第14张图片
    应用

  • 开始写代码

    • 数据源都在 文件中,红框处
OpenGL ES 2.0 (iOS)[03]:熟练图元绘制,玩转二维图形_第15张图片
  • 增加 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;
        }
  • 当然图形显示类,也要改咯!

  • 程序运行结果

OpenGL ES 2.0 (iOS)[03]:熟练图元绘制,玩转二维图形_第16张图片
Bezier
  • 完整的线元工程, Github:DrawGeometries_Lines

二、图元绘制之三角形

Triangles,就是多个三角形;

Triangle Strip, 指条带,相互连接的三角形;

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

OpenGL ES 2.0 (iOS)[03]:熟练图元绘制,玩转二维图形_第17张图片
图1:三角形模式
图2:STRIP
OpenGL ES 2.0 (iOS)[03]:熟练图元绘制,玩转二维图形_第18张图片
图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. 工程目录

OpenGL ES 2.0 (iOS)[03]:熟练图元绘制,玩转二维图形_第19张图片
工程目录

这里没有什么太大的变化,只是数据的集合发生了一些变化而已;

1. 绘制基本几何图形

OpenGL ES 2.0 (iOS)[03]:熟练图元绘制,玩转二维图形_第20张图片
TRIANGLE STRIP FAN
  • 图形分析
    • 首先,第一张图片每一个图形都是一个面,但是 OpenGL 只能直接绘制三角面,所以必须把图形分解成三角面才能进行绘制;
    • 以下就是分解成三角面之后的图形:
OpenGL ES 2.0 (iOS)[03]:熟练图元绘制,玩转二维图形_第21张图片
TRIANGLE LINESON

当然你也可以按照自己的方式进行分解,一定要遵守这里的点、三角形关系

OpenGL ES 2.0 (iOS)[03]:熟练图元绘制,玩转二维图形_第22张图片

不然图形是不能正确地绘制出来的;

  • 这里容易出问题的是最后一个图形(五角星形),三角形与点的关系: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 行;

  • 程序运行结果

OpenGL ES 2.0 (iOS)[03]:熟练图元绘制,玩转二维图形_第23张图片
TRI-ROUND Triangle

完整的程序代码: Github DrawGeometries_Triangles


三、图元绘制之点精灵

这里不进行详细讲解,个人感觉在这里讲没什么意思,还是放在 Texture 纹理部分进行详细讲解会比较有用,而且好玩;

如果只是学习 gl_PointSize 的话没意思,结合 gl_PointCoord 去学习反而更有趣,不过这里要有纹理的知识,所以先行不讲了;


四、练练手

OpenGL ES 2.0 (iOS)[03]:熟练图元绘制,玩转二维图形_第24张图片
Challenges

这里的目的不是为了绘制它们而进行绘制,而是针对图元绘制做一个深入的学习,要学习分析图形和寻找合适有效的绘制方式,而且还要做到判断数据的大致生成方法方式是什么,不然你永远都只是一个只会搞代码的搬运工而已;

编程可不仅仅是搞代码;

0. 工程目录

OpenGL ES 2.0 (iOS)[03]:熟练图元绘制,玩转二维图形_第25张图片

取消了采用结构体存取数据的方式,改用 Model 类,方便 OC 处理和传输;

1. 绘制一棵卡通树

OpenGL ES 2.0 (iOS)[03]:熟练图元绘制,玩转二维图形_第26张图片
Tree

提示:进行两次的 glDraw* 调用,分别绘制外边的线和内部的填充图

2. 绘制一张卡片

OpenGL ES 2.0 (iOS)[03]:熟练图元绘制,玩转二维图形_第27张图片
Card

提示:把数据分成左、右、右中线,三种,原因是左边的数据是用贝塞尔曲线生成数据量非常大;主要是利用 glBufferSubData 与 glBufferData 的结合,以及 glVertexAttribPointer 的配合;

3. 绘制一棵草

OpenGL ES 2.0 (iOS)[03]:熟练图元绘制,玩转二维图形_第28张图片
Grass

注意:尽可以地用肉眼去判断线的走向,用 动态计算点 的类做实验,不断成长起来吧。

完整的挑战项目:Github DrawGeometries_Challenge

你可能感兴趣的:(OpenGL ES 2.0 (iOS)[03]:熟练图元绘制,玩转二维图形)