美文网首页
OpenGL ES 案例05:GLSL使用索引绘图

OpenGL ES 案例05:GLSL使用索引绘图

作者: 辉辉岁月 | 来源:发表于2021-04-15 11:12 被阅读0次

    本案例的主要目的是理解GLSL中如何索引绘图

    在介绍本案例之前,首先说说什么是索引绘图
    一个图形中,有许多顶点,例如本案例中的金字塔,有5个面,由6个三角形组成,一共有18个顶点,然而实际肉眼可见的只有5个顶点,如下图所示

    索引绘图技巧就是指将图形中的肉眼可见的顶点,通过索引的方式表示顶点之间的连接,将重复顶点复用进行图形绘制的一种技巧

    案例的整体流程如图所示

    主要包含三部分

    • 准备工作:主要是导入三方数学库以及全局变量的定义
    • 自定义着色器:自定义顶点、片元着色器
    • layoutSubview函数:绘制图形
    • 按钮点击事件:根据点击不同的按钮实现不同方向的旋转

    下面主要说说后面三部分

    自定义着色器

    用GLSL语言自定义顶点、片元着色器,步骤如下

    顶点着色器

    • 由于在案例中图形会发生旋转,涉及矩阵的变换,所以需要定义uniform修饰的投影矩阵模型视图矩阵
    • 在main函数中,需要将顶点数据与矩阵相乘,其相乘也是有顺序的,因为在OpenGL ES中是以列向量为主,position是列向量,即4*1矩阵,而投影矩阵、模型视图矩阵是4*4矩阵,矩阵相乘 本质是 矩阵叉乘,需要满足A x B 时, A的列数 = B的行数,所以需要将position X mvp相乘的顺序 颠倒为 pvm X position
    //顶点坐标
    attribute vec4 position;
    //顶点颜色
    attribute vec4 positionColor;
    //投影矩阵
    uniform mat4 projectionMatrix;
    //模型视图矩阵
    uniform mat4 modelViewMatrix;
    //顶点颜色--用于与片元着色器桥接
    varying lowp vec4 varyColor;
    
    void main(){
        //将顶点颜色赋值给桥接的顶点颜色变量
        varyColor = positionColor;
    
        vec4 vPos;
        //顶点坐标变换: 4*4 x 4*4 x 4*1
        vPos = projectionMatrix * modelViewMatrix * position;
        //将变换后的顶点坐标赋值给内建变量
        gl_Position = vPos;
    }
    
    

    片元着色器

    • 片元着色器中比较简单,只需要将顶点着色器中传过来的顶点颜色赋值给内建变量gl_FragColor
    • 如果顶点颜色少于顶点个数,例如只有【红色,蓝色】,其他部分会进行颜色插值,类似于渐变色,如上面的效果图中所示
    • 如果不想进行插值,就需要指定面颜色,不指定顶点颜色
    //桥接的顶点颜色
    varying lowp vec4 varyColor;
    
    void main(){
        //顶点颜色赋值给内建变量
        gl_FragColor = varyColor;
    }
    

    layoutSubviews

    这部分主要是绘制前的准备工作和图形的绘制

    • 初始化:绘制前的准备工作
    • 绘制:使用索引绘图技巧绘制图形

    初始化

    这部分内容与OpenGL ES 案例04:GLSL加载图片中并没有什么区别

    如图所示,共有5个步骤,这里简要说明下

    • 绘制图层:用于创建挥之内容显示的载体
    • 绘制上下文:用于记录状态
    • 清除缓存区:避免之前的缓存,对本次绘制造成影响
    • 设置renderBuffer:用于实际存储颜色、顶点等的缓存区
    • 设置FrameBuffer:用于管理renderBuffer

    绘制**

    绘制的流程图如图所示,主要分为5部分

    • 初始化

    • GLSL自定义着色器加载

    • 设置顶点数据

    • 构建矩阵 & 传递到顶点着色器

    • 索引绘图

    其中初始化、自定义着色器两部分的代码可参考OpenGL ES 案例04:GLSL加载图片,也可直接在文末的github上查看源代码,在下面的说明中只简述下大致步骤

    初始化

    这部分主要有以下几步:

    • 设置清屏颜色
    • 清理缓存区
    • 设置视口

    GLSL自定义着色器加载

    着色器的加载的流程如图所示

    • 获取自定义着色器文件地址

    • 判断program是否已经存在,如果存在则删除

    • 加载着色器

    加载着色器中主要分为4步

    • 编译着色器
    • 创建最终的程序:shader附着在program上
    • 链接program & 判断是否链接成功
    • 使用program

    到此,GLSL编写的自定义着色器就加载完成了。

    设置顶点数据

    由于图形的绘制是使用的索引绘制,所以除了创建顶点数组外,还需要创建索引数组,大致流程如下

    • 设置顶点数组 & 索引数组

      • 顶点数组中一个顶点包含6个数据,前3位表示顶点值(x,y,z),后3位表示颜色值(RGB)
      • 索引数组中每3个数表示一个三角形,其中的数据为顶点的标识符,表示三角形由这三个顶点构成
        GLfloat attrArr[] =
        {
            -0.5f, 0.5f, 0.0f,      1.0f, 0.0f, 1.0f, //左上0
            0.5f, 0.5f, 0.0f,       1.0f, 0.0f, 1.0f, //右上1
            -0.5f, -0.5f, 0.0f,     1.0f, 1.0f, 1.0f, //左下2
    
            0.5f, -0.5f, 0.0f,      1.0f, 1.0f, 1.0f, //右下3
            0.0f, 0.0f, 1.0f,       0.0f, 1.0f, 0.0f, //顶点4
        };
    
        //(2).索引数组
        GLuint indices[] =
        {
            0, 3, 2,
            0, 1, 3,
            0, 2, 4,
            0, 4, 1,
            2, 3, 4,
            1, 4, 3,
        };
    
    

    后面3步的代码在之前的案例OpenGL ES 案例04:GLSL加载图片中也有涉及,就不再过多说明

    • 开辟缓存区,将顶点数据从CPU拷贝至GPU
    • 处理顶点坐标数据:打开attribute通道,将顶点坐标传入顶点着色器
    • 处理顶点颜色数据:打开attribute通道,将顶点颜色传入顶点着色器

    构建矩阵
    这部分主要是构建mvp矩阵,由于案例图形有旋转操作,还需要构建旋转矩阵

    主要有以下4步

    • 获取顶点着色器中uniform修饰的投影矩阵、模型视图矩阵的入口,方便后续将对应矩阵传入顶点着色器中,入口的获取与attribute入口获取类似,其中的name都需要与着色器中一模一样
    GLuint projectionMatrixSlot = glGetUniformLocation(self.myPrograme, "projectionMatrix");
    GLuint modelViewMatrixSlot = glGetUniformLocation(self.myPrograme, "modelViewMatrix");
    
    
    • 获取屏幕的纵横比,用于设置透视投影
    float width = self.frame.size.width;
    float height = self.frame.size.height;
    //获取纵横比
    float aspect = width / height;
    
    
    • 创建投影矩阵
      • 定义一个4*4的投影矩阵
      • 通过ksMatrixLoadIdentity将投影矩阵初始化为单元矩阵
      • 通过ksPerspective方法,设置透视投影
      • 通过glUniformMatrix4fv方法,将投影矩阵传递到vertex shader

    矩阵涉及的方法均为数学三方库中的封装好的

    KSMatrix4 _projectionMatrix;
        //(1)获取单元矩阵
        ksMatrixLoadIdentity(&_projectionMatrix);
        //(3)获取透视矩阵
        /*
         参数1:矩阵
         参数2:视角,度数为单位
         参数3:纵横比
         参数4:近平面距离
         参数5:远平面距离
         */
        ksPerspective(&_projectionMatrix, 30.0, aspect, 5.0f, 20.0f);
    
        //(4)将投影矩阵传递到顶点着色器
        /*
         参数1-location:指要更改的uniform变量的位置
         参数2-count:更改矩阵的个数
         参数3-transpose:是否要转置矩阵,并将它作为uniform变量的值。必须为GL_FALSE
         参数4-value:执行count个元素的指针,用来更新指定uniform变量
         */
        glUniformMatrix4fv(projectionMatrixSlot, 1, GL_FALSE, (GLfloat*)&_projectionMatrix.m[0][0]);
    
    
    • 创建模型试图矩阵
      • 定义一个4*4的模型视图矩阵
      • 模型视图通过ksMatrixLoadIdentity载入一个单元矩阵
      • 将图形往屏幕里平移,即z轴平移-10,为了方便观察图形
      • 定义一个4*4的旋转矩阵
      • 旋转矩阵通过ksMatrixLoadIdentity载入一个单元矩阵
      • 通过ksRotate分别设置x、y、z相对应的旋转
      • 将旋转矩阵与模型视图矩阵相乘,将结果存储到模型视图矩阵
      • 将mv矩阵传递到vertex shader
    //创建4*4的模型视图矩阵
        KSMatrix4 _modelViewMatrix;
        //初始化
        //(1)获取单元矩阵
        ksMatrixLoadIdentity(&_modelViewMatrix);
        //为了方便观察,围绕z轴往屏幕里平移10个像素点
        //(2)平移,z轴平移-10
        ksTranslate(&_modelViewMatrix, 0.0, 0.0, -10.0);
    
        //创建旋转
        //(3)创建一个4 * 4 矩阵,旋转矩阵
        KSMatrix4 _rotationMatrix;
        //初始化
        //(4)初始化为单元矩阵
        ksMatrixLoadIdentity(&_rotationMatrix);
        //有可能围绕 x / y / z任一轴旋转(为什么不写一行?不确定围绕哪个轴旋转)
        //(5)旋转
        ksRotate(&_rotationMatrix, xDegree, 1, 0, 0);
        ksRotate(&_rotationMatrix, yDegree, 0, 1, 0);
        ksRotate(&_rotationMatrix, zDegree, 0, 0, 1);
    
        //(6)把变换矩阵相乘.将_modelViewMatrix矩阵与_rotationMatrix矩阵相乘,结合到模型视图
        //矩阵相乘 modelview = rotation x modelview
        ksMatrixMultiply(&_modelViewMatrix, &_rotationMatrix, &_modelViewMatrix);
    
        //将mv矩阵传递到顶点着色器
        //(7)将模型视图矩阵传递到顶点着色器
        /*
         void glUniformMatrix4fv(GLint location,  GLsizei count,  GLboolean transpose,  const GLfloat *value);
         参数列表:
         location:指要更改的uniform变量的位置
         count:更改矩阵的个数
         transpose:是否要转置矩阵,并将它作为uniform变量的值。必须为GL_FALSE
         value:执行count个元素的指针,用来更新指定uniform变量
         */
        glUniformMatrix4fv(modelViewMatrixSlot, 1, GL_FALSE, (GLfloat*)&_modelViewMatrix.m[0][0]);
    

    索引绘图

    • 开启正背面剔除glEnable(GL_CULL_FACE);

    • 通过glDrawElements函数,使用索引绘图,方法中3个参数

      • mode:要呈现的画图的模式
      • count:绘图个数,这里不是指顶点的个数,是索引的个数
      • type:类型
      • indices:索引数组
    mode模式 type 类型
    GL_POINTS GL_BYTE
    GL_LINES GL_UNSIGNED_BYTE
    GL_LINE_LOOP GL_SHORT
    GL_LINE_STRIP GL_UNSIGNED_SHORT
    GL_TRIANGLES GL_INT
    GL_TRIANGLE_STRIP GL_UNSIGNED_INT
    GL_TRIANGLE_FAN ----
    glDrawElements(GL_TRIANGLES, sizeof(indices) / sizeof(indices[0]), GL_UNSIGNED_INT, indices);
    
    
    • 要求本地窗口显示渲染的目标
    [self.myContext presentRenderbuffer:GL_RENDERBUFFER];
    

    按钮点击事件

    主要是3个按钮的点击事件,点击事件实现分为以下两步

    • 开启定时器

    • 判断是否旋转

    • 执行定时器方法

      • 更新角度
      • 重新渲染

    完整的代码见github - 11_01_GLSL三角形变换_OC、11_01_GLSL三角形变换_Swift

    相关文章

      网友评论

          本文标题:OpenGL ES 案例05:GLSL使用索引绘图

          本文链接:https://www.haomeiwen.com/subject/mukdlltx.html