OpenGL ES

第十五节—索引绘图

2020-10-10  本文已影响0人  L_Ares

本文为L_Ares个人写作,如需转载请表明原文出处。

直接上代码,里面有很详细的注释。

使用索引绘图的原因主要是因为之前的按照顶点逐个绘图的方式实在是浪费资源(主要是定点缓冲区,索引绘图和顶点绘图一样都是要把顶点绘制的),而且非常多的无用代码,所以索引绘图是一个很好的解决方案,他的大体思路就是把定点设置好编号索引,然后三个定点可以绘制一个三角形,按照这样的思路设置一个索引数组,设置索引数组,使用索引绘图,可以避免顶点多次重绘的问题,也可以提高性能。

//
//  JDView.m
//  05GLSL立体图形
//
//  Created by EasonLi on 2020/9/23.
//  Copyright © 2020 EasonLi. All rights reserved.
//

#import "JDView.h"
#import <OpenGLES/ES3/gl.h>
#import <OpenGLES/ES3/glext.h>
#import "GLESMath.h"
#import "GLESUtils.h"


@interface JDView ()

//定义EAGL图层
@property (nonatomic,strong) CAEAGLLayer* myEAGLLayer;

//定义图形上下文
@property (nonatomic,strong) EAGLContext* myContext;

//定义渲染缓冲区
@property (nonatomic,assign) GLuint myRenderBuffer;

//定义帧缓冲区
@property (nonatomic,assign) GLuint myFrameBuffer;

//定义顶点缓冲区
@property (nonatomic,assign) GLuint myVertexBuffer;

//定义一个程序Program
@property (nonatomic,assign) GLuint myProgram;

@end

@implementation JDView

{
    float xDegree;
    float yDegree;
    float zDegree;
    BOOL bX;
    BOOL bY;
    BOOL bZ;
    NSTimer* myTimer;
    
    
}

#pragma mark - 重写载入子视图方法
- (void)layoutSubviews
{
    
    //设置绘制图层
    [self setUpLayer];
    
    //设置图形上下文
    [self setUpContext];
    
    //清空缓冲区
    [self cleanUpBuffers];
    
    //设置渲染缓冲区
    [self setUpRenderBuffer];
    
    //设置帧缓冲区
    [self setUpFrameBuffer];
    
    //绘制
    [self renderLayer];
    
}

#pragma mark - 设置要绘制的图层
- (void)setUpLayer
{
    //把图层交给EAGL类图层
    self.myEAGLLayer = (CAEAGLLayer *)self.layer;
    
    //设置屏幕的缩放比
    [self setContentScaleFactor:[[UIScreen mainScreen] scale]];
    
    //如果不设置颜色混合的话,这里最好选择layer是不透明的,不然颜色可能会因为立体原因出现问题
    self.myEAGLLayer.opaque = YES;
    
    //设置layer绘制的描述属性,颜色方式和是否保留上次的内容
    self.myEAGLLayer.drawableProperties = @{kEAGLDrawablePropertyColorFormat:kEAGLColorFormatRGBA8,kEAGLDrawablePropertyRetainedBacking:@false};
    
    
}

/**
 想要把自己的图层子类对象设置为当前View的宿主图层,就要在图层创建前重写layerClass,
 返回不同的图层子类作为View的宿主图层,这样UIView在初始化的时候,就会调用layerClass
 用它返回的类型来作为自己的宿主图层,不要在创建完成后再设置,因为一旦创建完,图层就确定了,无法替换
 */
+ (Class)layerClass
{
    return [CAEAGLLayer class];
}

#pragma mark - 设置图形上下文
- (void)setUpContext
{
    //初始化图形上下文,可以选择OpenGL ES2或者OpenGL ES3的api来使用
    EAGLContext *context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
    if (!context) {
        NSLog(@"图形上下文初始化失败");
        return;
    }
    //设置当前上下文
    if (![EAGLContext setCurrentContext:context]) {
        NSLog(@"设置当前上下文失败");
        return;
    }
    //将上下文对象赋值给属性中的上下文,用于全局
    self.myContext = context;
    
}

#pragma mark - 清空缓存区
- (void)cleanUpBuffers
{
    glDeleteBuffers(1, &_myRenderBuffer);
    self.myRenderBuffer = 0;
    
    glDeleteBuffers(1, &_myFrameBuffer);
    self.myRenderBuffer = 0;
}

#pragma mark - 设置渲染缓冲区
- (void)setUpRenderBuffer
{
    //定义存储缓冲区ID的变量
    GLuint renderBuffer;
    
    //申请渲染缓冲区
    glGenRenderbuffers(1, &renderBuffer);
    
    //赋值给属性,作为全局变量
    self.myRenderBuffer = renderBuffer;
    
    //绑定缓冲区属性
    glBindRenderbuffer(GL_RENDERBUFFER, self.myRenderBuffer);
    
    //把renderbuffer存储或者说绑定到CAEAGLLayer对象上
    BOOL result = [self.myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myEAGLLayer];
    if (!result) {
        NSLog(@"renderbuffer绑定到图层失败");
    }
    
}

#pragma mark - 设置帧缓冲区
- (void)setUpFrameBuffer
{
    
    //定义存储帧缓冲区ID的变量
    GLuint frameBuffer;
    
    //申请帧缓冲区
    glGenFramebuffers(1, &frameBuffer);
    
    //将帧缓冲区ID赋值给属性中的全局变量
    self.myFrameBuffer = frameBuffer;
    
    //将帧缓冲区绑定属性
    glBindFramebuffer(GL_FRAMEBUFFER, self.myFrameBuffer);
    
    //将帧缓冲区和渲染缓冲区绑定,设置附着点
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.myRenderBuffer);
    
}

#pragma mark - 开始绘制
- (void)renderLayer
{
    
    //设置清屏颜色,清空缓冲区,设置视口
    [self setUpConfig];
    
    //加载shader源码,获取program,将shader附着到program,
    //链接program,使用program
    [self loadShaderAndLinkProgram];
    
    //设置顶点
    [self setUpVertex];
    
    //设置纹理
    [self setUpTexture];
    
    //设置变换矩阵
    [self setUpMatrixs];
    
    //开启正背面剔除
    glEnable(GL_CULL_FACE);
    
    //定义索引数组
    GLuint indexArr[] = {
        0,2,3,
        0,1,2,
        0,3,4,
        0,4,1,
        1,4,2,
        2,4,3
    };
    
    //利用索引绘制来绘制图形
    //(1).绘制方式 (2).绘图的个数 (3).
    glDrawElements(GL_TRIANGLES, sizeof(indexArr)/sizeof(indexArr[0]), GL_UNSIGNED_INT, indexArr);
    
    //显示OpenGL ES图层
    [self.myContext presentRenderbuffer:GL_RENDERBUFFER];
    
}

#pragma mark - 绘制常规设置
- (void)setUpConfig
{
    //设置清屏颜色
    glClearColor(0.3f, 0.3f, 0.3f, 1.f);
    
    //清空缓冲区
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    
    //拿到屏幕的缩放比
    CGFloat scale = [UIScreen mainScreen].scale;
    
    //定义变量拿到屏幕的size和origin
    CGSize mSize = self.frame.size;
    CGPoint mOrigin = self.frame.origin;
    
    //设置视口
    glViewport(mOrigin.x * scale, mOrigin.y * scale, mSize.width * scale, mSize.height * scale);
}

#pragma mark - 加载shader并链接program
- (void)loadShaderAndLinkProgram
{
    
    //获取顶点着色器和片元着色器源码路径
    NSString *vshFile = [[NSBundle mainBundle] pathForResource:@"shaderv" ofType:@"glsl"];
    NSString *fshFile = [[NSBundle mainBundle] pathForResource:@"shaderf" ofType:@"glsl"];
    
    //判断一下属性中的程序是否已经存在,如果有的话,可以重置一下
    if (self.myProgram) {
        glDeleteProgram(self.myProgram);
        self.myProgram = 0;
    }
    
    //加载着色器并获取Program
    self.myProgram = [self loadVertexShader:vshFile FragmentShader:fshFile];
    
    //链接program
    glLinkProgram(self.myProgram);
    
    //获取链接的状态
    GLint linkStatus;
    glGetProgramiv(self.myProgram, GL_LINK_STATUS, &linkStatus);
    
    //判断是否链接成功
    if (linkStatus == GL_FALSE) {
        //失败了要把失败信息存储起来,所以定义数组存储失败log
        GLchar falseMessage[256];
        //失败就要拿到失败的原因,最后一个参数写数组的首地址,可以直接写数组名
        glGetProgramInfoLog(self.myProgram, sizeof(falseMessage), 0, &falseMessage[0]);
        NSLog(@"program链接失败 : %@",[NSString stringWithUTF8String:falseMessage]);
        return;
    }
    
    //使用program
    glUseProgram(self.myProgram);
    
}

/**
 加载着色器,返回Program
 */
- (GLuint)loadVertexShader:(NSString *)vshFile FragmentShader:(NSString *)fshFile
{
    
    //定义两个空的临时的着色器,分别获取顶点和片元着色器内容用
    GLuint vertexShader,fragmentShader;
    
    //创建一个program
    GLuint program = glCreateProgram();
    
    //编译顶点着色器和片元着色器,使临时变量拿到着色器文件中的内容
    [self compileShader:&vertexShader type:GL_VERTEX_SHADER file:vshFile];
    [self compileShader:&fragmentShader type:GL_FRAGMENT_SHADER file:fshFile];
    
    //着色器附着到program上
    glAttachShader(program, vertexShader);
    glAttachShader(program, fragmentShader);
    
    //使用完成要删除临时着色器,因为都附着到program上了,不再用他们了
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
    
    return program;
    
}

/**
 编译着色器,获取着色器内容
 */
- (void)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file
{
    //读取着色器源码内容
    NSString *shaderContent = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil];
    
    //因为在将着色器源码附着到着色器对象上的时候要使用glShaderSource函数
    //而glShaderSource函数需要的是char类型的指针,所以这里内容要换成char*
    const GLchar *cshContent = (GLchar *)[shaderContent UTF8String];
    
    //创建一个shader,并赋值给传来的着色器
    *shader = glCreateShader(type);
    
    
    //将着色器内容附着到着色器对象上
    //最后一个参数是着色器字符串的长度,不确定的话可以写NULL,意为
    //字符串终止位
    glShaderSource(*shader, 1, &cshContent, NULL);
    
    //将着色器源码编译成目标代码
    glCompileShader(*shader);
    
}

#pragma mark - 设置顶点
- (void)setUpVertex
{
    
    //这里换成索引绘制
    //也就是需要顶点数组和索引数组
    
    //定义顶点数组(前三位xyz,后三位rgb)
    GLfloat vertexArr[] = {
        -0.5f,0.5f,0.f,   1.f,0.f,0.f,   //左上,index:0
        0.5f,0.5f,0.f,    0.f,1.f,0.f,   //右上,index:1
        0.5f,-0.5f,0.f,   0.f,0.f,1.f,   //右下,index:2
        -0.5f,-0.5f,0.f,  1.f,0.f,1.f,   //左下,index:3
        0.f,0.f,1.f,      1.f,1.f,0.f,   //顶点,index:4
    };
    
    //判断顶点缓冲区是否为空,是空的话就分配一个顶点缓冲区
    if (self.myVertexBuffer == 0) {
        glGenBuffers(1, &_myVertexBuffer);
    }
    
    //绑定顶点缓冲区到对应的缓冲区类型上
    glBindBuffer(GL_ARRAY_BUFFER, self.myVertexBuffer);
    
    //把顶点数据从内存copy到顶点缓冲区
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertexArr), vertexArr, GL_DYNAMIC_DRAW);
    
    //获取顶点参数的位置
    GLuint vPosition = glGetAttribLocation(self.myProgram, "position");
    
    //根据位置,打开顶点属性通道
    glEnableVertexAttribArray(vPosition);
    
    //设置顶点的读取方式,传递数据进入着色器
    glVertexAttribPointer(vPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, NULL + 0);
    
}

#pragma mark - 设置纹理(颜色值)
- (void)setUpTexture
{
    //获取颜色的位置
    GLuint cPosition = glGetAttribLocation(self.myProgram, "colorInfo");
    
    //打开颜色属性通道
    glEnableVertexAttribArray(cPosition);
    
    //设置颜色的读取方式,传递进入着色器
    glVertexAttribPointer(cPosition, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 6, (float* )NULL + 3);
    
}

#pragma mark - 设置变换矩阵
- (void)setUpMatrixs
{
    
    //还是先获取mvMatrix和pMatrix的位置
    GLuint pmPosition = glGetUniformLocation(self.myProgram, "projectionMatrix");
    GLuint mvmPosition = glGetUniformLocation(self.myProgram, "modelViewMatrix");
    
/***************************设置投影视图矩阵***************************/
    
    //创建一个4*4的投影矩阵
    KSMatrix4 nProjectionMatrix;
    
    //给投影矩阵载入一个单元矩阵,实在不理解就理解成对象初始化吧
    ksMatrixLoadIdentity(&nProjectionMatrix);
    
    //获取纵横比
    float w = self.frame.size.width;
    float h = self.frame.size.height;
    float whAspect = w / h;
    
    //得到投影矩阵,这个函数就和OpenGL里面的GLFrustum设置SetPerspective一样
    ksPerspective(&nProjectionMatrix, 30.f, whAspect, 5.f, 20.f);
    
    //传输到着色器参数中
    /**
     参数:
     (1).矩阵的位置 (2).传输了几个矩阵 (3).是否转置 (4).矩阵的首地址的内容
     */
    glUniformMatrix4fv(pmPosition, 1, GL_FALSE, (GLfloat *)&nProjectionMatrix.m[0][0]);
    
/*******************************************************************/
    

/***************************设置模型视图矩阵***************************/
    
    //创建一个4*4的模型视图矩阵
    KSMatrix4 nModelViewMatrix;
    
    //给模型视图矩阵一个单元矩阵
    ksMatrixLoadIdentity(&nModelViewMatrix);
    
    //把图形向外移动一些,免的和原点重合
    ksTranslate(&nModelViewMatrix, 0.f, 0.f, -10.f);
    
    //定义旋转矩阵
    KSMatrix4 nRotateMatrix;
    
    //给旋转矩阵加载一个单元矩阵
    ksMatrixLoadIdentity(&nRotateMatrix);
    
    //做旋转
    ksRotate(&nRotateMatrix, xDegree, 1.f, 0.f, 0.f);
    ksRotate(&nRotateMatrix, yDegree, 0.f, 1.f, 0.f);
    ksRotate(&nRotateMatrix, zDegree, 0.f, 0.f, 1.f);
    
    //把平移矩阵和旋转矩阵做融合,结果放到模型视图矩阵中
    ksMatrixMultiply(&nModelViewMatrix, &nRotateMatrix, &nModelViewMatrix);
    
    //把模型视图矩阵传进顶点着色器
    glUniformMatrix4fv(mvmPosition, 1, GL_FALSE, (GLfloat *)&nModelViewMatrix.m[0][0]);
    
/*******************************************************************/
    
    
}

#pragma mark - 点击方法
- (IBAction)clickX:(UIButton *)sender {
    if (!myTimer) {
        NSLog(@"没有timer");
        myTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(reDegree) userInfo:nil repeats:YES];
    }
    bX = !bX;
}

- (IBAction)clickY:(UIButton *)sender {
    if (!myTimer) {
        NSLog(@"没有timer");
        myTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(reDegree) userInfo:nil repeats:YES];
    }
    bY = !bY;
}

- (IBAction)clickZ:(UIButton *)sender {
    if (!myTimer) {
        NSLog(@"没有timer");
        myTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:@selector(reDegree) userInfo:nil repeats:YES];
    }
    bZ = !bZ;
}

#pragma mark - 旋转方法
- (void)reDegree
{
    
    xDegree += bX * 5;
    yDegree += bY * 5;
    zDegree += bZ * 5;
    
    //重新渲染
    [self renderLayer];
    
}

#pragma mark - 释放
- (void)dealloc
{
    
    if ([EAGLContext currentContext] == self.myContext) {
        [EAGLContext setCurrentContext:nil];
    }
    
    if (_myRenderBuffer) {
        glDeleteBuffers(1, &_myRenderBuffer);
        _myRenderBuffer = 0;
    }
    
    if (_myFrameBuffer) {
        glDeleteBuffers(1, &_myFrameBuffer);
        _myFrameBuffer = 0;
    }
    
    if (_myVertexBuffer) {
        glDeleteBuffers(1, &_myVertexBuffer);
        _myVertexBuffer = 0;
    }
    
    if (_myProgram) {
        glDeleteProgram(_myProgram);
        _myProgram = 0;
    }
    
    if ([myTimer isValid]) {
        [myTimer invalidate];
        myTimer = nil;
    }
    
}

/*
// Only override drawRect: if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
- (void)drawRect:(CGRect)rect {
    // Drawing code
}
*/

@end

效果图如下图1.1所示:

另外,button都是自己在sb上面自己加的,这个就不用多说了,记得在viewcontroller里面把上面的代码所在的自定义view设置成viewcontroller的view,不然没有办法显示的。

1.1.png
上一篇 下一篇

猜你喜欢

热点阅读