iOS视觉(十二) -- 初用GLSL(二):索引绘制立方体

633 阅读3分钟

一、索引绘制

1.1、索引绘制概念

前文中进行打开顶点数据通道的时候,我们设置的是一个6个顶点绘制成两个三角形。但是会发现其中有两个三角形的顶点是重复的存在,当图形过于复杂的时候进行这样的绘制会浪费很多性能,所以在OpenGL ES也提供了另外一个方法 -- 索引绘制。

例如,我们需要在界面上绘制一个四棱锥:

一共需要5个顶点,如果按照前文的方法来写入顶点数据的话,是需要 6 * 3 = 18个顶点数据。使用索引绘制的话,我们只需要5个顶点以及一个索引数组。

数据如下:

    //顶点数据 纹理坐标
    GLfloat attrArr[] =
    {
        -0.5f, 0.5f, 0.0f,      0.0f, 1.0f,//1
        0.5f, 0.5f, 0.0f,       1.0f, 1.0f,//2
        -0.5f, -0.5f, 0.0f,     0.0f, 0.0f,//3
        0.5f, -0.5f, 0.0f,      1.0f, 1.0f,//4
        0.0f, 0.0f, 1.0f,       1.0f, 0.0f//5
    };
    //索引数组
    GLuint indices[] =
    {
        0, 3, 2,
        0, 1, 3,
        0, 2, 4,
        0, 4, 1,
        2, 3, 4,
        1, 4, 3,
    };

通过线索数据来找到对应的顶点数据再进行绘制,这样就减少定义了许多重复的顶点数据。

1.2、索引绘制的使用

OpenGL ES为我们提供了一个函数用做索引绘制:

/*
参数1:图元装配方式
参数2:索引的个数
参数3:数据类型
参数4:索引数组
*/
void glDrawElements(GLenum mode, GLsizei count, GLenum type, const GLvoid* indices);

所以我们在绘制的时候就需要将 glDrawArrays() 改为 glDrawElements()

二、使用索引绘制一个四棱锥

由于四棱锥是一个立体图形,为了便于观察,我们为这个图元进行矩阵变换。所以在GLSL中,我们也需要添加两个通道:投影矩阵、视图模型矩阵:

顶点着色器:

//VSH
attribute vec4 position;//顶点坐标
attribute vec2 textCoordinate;//纹理坐标

varying lowp vec2 varyTextCoord;//纹理坐标传递给片元

uniform mat4 projectionMatrix;//投影矩阵
uniform mat4 modelViewMatrix;//视图模型矩阵

void main()
{
    varyTextCoord = textCoordinate;
    
    vec4 vPos;
    vPos = projectionMatrix * modelViewMatrix * position;
    gl_Position = vPos;
}

片元着色器:

varying lowp vec2 varyTextCoord;
uniform sampler2D colorMap;
void main()
{
    gl_FragColor = texture2D(colorMap, varyTextCoord);
}

接下来在绘制的方法里,打开顶点数据通道之后就需要进行投影矩阵与视图模型矩阵的设置.

这里矩阵的操作使用了两个第三方的类:

  • #import "GLESMath.h"
  • #import "GLESUtils.h"

设置投影矩阵与视图模型矩阵的方法与最前面的OpenGL篇章的相似度很高:

- (void)renderLayer {
	
    //...省略
    
    //拿到mvp
    GLuint projectionMatrixSlot = glGetUniformLocation(self.myPrograme, "projectionMatrix");//投影矩阵
    GLuint modelViewMatrixSlot = glGetUniformLocation(self.myPrograme, "modelViewMatrix");//视图模型矩阵

    float width = self.frame.size.width;
    float height = self.frame.size.height;
    
    //设置投影矩阵
    KSMatrix4 _projectionMatrix;//声明矩阵
    ksMatrixLoadIdentity(&_projectionMatrix);//加载为单元矩阵
    float aspect = width / height;//宽高比
    ksPerspective(&_projectionMatrix, 30.0f, aspect, 5.0f, 20.0f);//设置视角30°
    glUniformMatrix4fv(projectionMatrixSlot, 1, GL_FALSE, (GLfloat *)&_projectionMatrix.m[0][0]);
    
    //设置视图模型矩阵
    KSMatrix4 _modelViewMatrix;//声明矩阵
    ksMatrixLoadIdentity(&_modelViewMatrix);//加载为单元矩阵
    ksTranslate(&_modelViewMatrix, 0, 0, -10.0f);//平移z轴(方便观察)

    //旋转矩阵
    KSMatrix4 _rotationMatrix;//声明矩阵
    ksMatrixLoadIdentity(&_rotationMatrix);//加载为单元矩阵
    ksRotate(&_rotationMatrix, xDegree, 1, 0, 0);//围绕哪个轴旋转
    ksRotate(&_rotationMatrix, yDegree, 0, 1, 0);
    ksRotate(&_rotationMatrix, zDegree, 0, 0, 1);

    //矩阵相乘
    ksMatrixMultiply(&_modelViewMatrix, &_rotationMatrix, &_modelViewMatrix);
    glUniformMatrix4fv(modelViewMatrixSlot, 1, GL_FALSE, (GLfloat *)&_modelViewMatrix.m[0][0]);
    
    glEnable(GL_DEPTH_TEST);
    glEnable(GL_CULL_FACE);

	//...省略纹理加载
    
    

    //索引绘制
    glDrawElements(GL_TRIANGLES, sizeof(indices)/sizeof(indices[0]), GL_UNSIGNED_INT, indices);
    
    //要求本地窗口系统显示OpenGL ES渲染
    [self.myContext presentRenderbuffer:GL_RENDERBUFFER];
}

为了方便观察, 在界面上定义三个按钮分别使视图围绕不同的轴进行旋转:

#pragma mark - XYClick
- (IBAction)XClick:(id)sender {
    //开启定时器
    if (!myTimer) {
        myTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(reDegree) userInfo:nil repeats:YES];
    }
    //更新的是X还是Y
    bX = !bX;
    
}
- (IBAction)YClick:(id)sender {
    
    //开启定时器
    if (!myTimer) {
        myTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(reDegree) userInfo:nil repeats:YES];
    }
    //更新的是X还是Y
    bY = !bY;
}
- (IBAction)ZClick:(id)sender {
    
    //开启定时器
    if (!myTimer) {
        myTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(reDegree) userInfo:nil repeats:YES];
    }
    //更新的是X还是Y
    bZ = !bZ;
}

-(void)reDegree
{
    //如果停止X轴旋转,X = 0则度数就停留在暂停前的度数.
    //更新度数
    xDegree += bX * 5;
    yDegree += bY * 5;
    zDegree += bZ * 5;
    //重新渲染
    [self renderLayer];
}

最终效果如下: