OpenGL ES-09-案例05-GLSL索引绘图+颜色纹理混合

我们上一篇中介绍了2D图形的绘制,那么今天来看一下3D图形的绘制。为了使3D效果更加明显,我们增加了旋转功能,因此就需要用到矩阵记录变化。除此之外,还添加了颜色与纹理的混合,并且改变了绘图方式:索引绘图

前言

OpenGL提供了一些绘图函数
到目前为止我们使用的glDrawArrays绘图函数属于顺序绘制。这意味着顶点缓冲区从指定的偏移量开始被扫描,每X(点为1,直线为2等)个顶点构成一个图元。这样使用起来非常方便,缺点是当多个图元共用一个顶点时,这个顶点必须在顶点缓冲区中出现多次。也就是说,这些顶点没有共享的概念。
索引绘制的函数glDrawElements则提供这种共享机制。我们除了一个顶点缓存区外,还有一个索引缓存区用来存放顶点的索引值。索引缓存区的扫描和顶点缓存区类似,以每X个索引对应的顶点构成一个基本图元。共享机制在提高内存使用效率上非常重要,因为计算机中的绝大多数图形对象都是三角形网格构成的,这些三角形有很多都是共用顶点。

例如: 如果我们要绘制一个金字塔。

  • 顺序绘制,需要传入18个顶点的信息
  • 索引绘制,只需要传入5个顶点的信息
image

一、效果图

image

二、流程图

image

三、GLSL绘制3D与2D的主要区别

  • 多了投影矩阵、模型视图矩阵
  • 多了索引数组,并改变了绘制方式
  • 多了一组颜色数据,为了做混合效果
  • 因为是3D可旋转,所以要开启正背面剔除或者深度测试

四、代码部分

1、顶点着色器

//特意加的注释,真是项目中,切记不要写中文,避免不必要的错误
attribute vec4 position;//顶点数据
attribute vec4 positionColor;//顶点颜色
attribute vec2 textCoor;//纹理数据
uniform mat4 projectionMatrix;//投影矩阵
uniform mat4 modelViewMatrix;//模型视图矩阵


varying lowp vec2 vTextCoor;//传递给 片元着色器的  顶点颜色
varying lowp vec4 varyColor;//传递给 片元着色器的  纹理数据

void main()
{
    vTextCoor = textCoor;
    varyColor = positionColor;
    
    vec4 vPos;
    vPos = projectionMatrix * modelViewMatrix * position;
    gl_Position = vPos;
}


2、片元着色器

//特意加的注释,真是项目中,切记不要写中文,避免不必要的错误
precision highp float;![](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/d7d6df3f338a45bb931d655c45fc6390~tplv-k3u1fbpfcp-zoom-1.image)//默认高精度,一定要写

varying lowp vec2 vTextCoor; //纹理坐标
varying lowp vec4 varyColor; //顶点颜色数据
uniform sampler2D colorMap; //纹理数据

void main()
{
     
    //拿到每个像素的纹素
    vec4 weakMask = texture2D(colorMap, vTextCoor);
    //拿到颜色数据
    vec4 mask = varyColor;
    float alpha = 0.3;
    //要想进行混合,需要半透明才可以,所以调整他们各自的透明度
    vec4 tempColor = mask * (1.0 - alpha) + weakMask * alpha;
    gl_FragColor = tempColor;
}

3、无区别部分

前面的步骤代码基本都一样,(只是声明多了点东西)
为了方便看出区别,从第6步分开了,不一样的代码放到第5部分


#import "MyView.h"
#import 
//导入工具类
#import "GLESMath.h"
#import "GLESUtils.h"
@interface MyView()
{
    //这里的东西是用来控制旋转操作的参数
    float xDegree;
    float yDegree;
    float zDegree;
    BOOL bX;
    BOOL bY;
    BOOL bZ;
    NSTimer* myTimer;
    
    BOOL isHybrid;
}
//EAGL提供的绘制表面
@property (nonatomic,strong) CAEAGLLayer *myEaglLayer;
//上下文
@property (nonatomic,strong) EAGLContext *myContext;
//渲染缓冲区
@property (nonatomic,assign) GLuint myColorRenderBuffer;
//帧缓冲区
@property (nonatomic,assign) GLuint myColorFrameBuffer;
//程序对象的id
@property (nonatomic,assign) GLuint myPrograme;
//顶点缓冲区id
@property (nonatomic , assign) GLuint  myVertices;

@end

@implementation MyView
 
- (void)layoutSubviews
{
    
    isHybrid = NO;
    
    //1、设置图层
    [self setUpLayer];
    
    //2、设置上下文
    [self setUpContext];
    
    //3、清空缓冲区
    [self deleteBuffers];
    
    //4、设置renderBuffer
    [self setUpRenderBuffer];
    
    //5、设置frameBuffer
    [self setUpframeBuffer];
    
    //6、开始绘制
    [self renderDraw];
    
    //7、添加底部按钮,控制旋转、切换
    [self setUpBottomButtons];
    
}
#pragma mark - 1、设置图层
-(void)setUpLayer{
    
    //1、创建图层
    self.myEaglLayer = (CAEAGLLayer *)self.layer;
    self.myEaglLayer.opaque = YES;
    //2、设置scale
    [self setContentScaleFactor:[[UIScreen mainScreen] scale]];
    
    //3、设置描述属性
    self.myEaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                           @false,kEAGLDrawablePropertyRetainedBacking,
                                           kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat,
                                           nil];

}
+(Class)layerClass{
    return [CAEAGLLayer class];
}

#pragma mark - 2、设置上下文
-(void)setUpContext{
    //初始化上下文
    EAGLContext *context = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES2];
    //判断
    if (!context) {
        NSLog(@"上下文创建失败");
        return;
    }
    //设置当前上下文
    [EAGLContext setCurrentContext:context];
    self.myContext = context;
}

#pragma mark - 3、清空缓冲区
-(void)deleteBuffers{
 
    //清空渲染缓冲区
    glDeleteBuffers(1, &_myColorRenderBuffer);
    self.myColorRenderBuffer = 0;
    
    //清空帧缓冲区
    glDeleteBuffers(1, &_myColorFrameBuffer);
    self.myColorFrameBuffer = 0;
    
}
#pragma mark - 4、设置renderBuffer
-(void)setUpRenderBuffer{
    
    //定义一个缓冲区id
    GLuint rBuffer;
    //申请一个缓冲区
    glGenRenderbuffers(1, &rBuffer);
    self.myColorRenderBuffer = rBuffer;
    //绑定渲染缓冲区
    glBindRenderbuffer(GL_RENDERBUFFER, self.myColorRenderBuffer);
    //把layer的存储 绑定到 渲染缓冲区。此处也把context与layer绑定在一起
    [self.myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myEaglLayer];
    
}

#pragma mark - 5、设置frameBuffer
-(void)setUpframeBuffer{
    
    GLuint *fBuffer;
    glGenFramebuffers(1, &fBuffer);
    self.myColorFrameBuffer = fBuffer;
    glBindFramebuffer(GL_FRAMEBUFFER, self.myColorFrameBuffer);
    
    //把renderBuffer和frameBuffer绑定在一起.把renderBuffer绑定到GL_COLOR_ATTACHMENT0上
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.myColorRenderBuffer);
    
    
}
#pragma mark - 6、开始绘制
-(void)renderDraw{
    
    //1、设置背景色&清空颜色缓冲区
    glClearColor(0, 0, 0, 1);
    glClear(GL_COLOR_BUFFER_BIT);
    
    //2、设置视口
    CGFloat scale = [[UIScreen mainScreen] scale];
    glViewport(self.frame.origin.x * scale, self.frame.origin.y * scale, self.frame.size.width * scale, self.frame.size.height * scale);
    
    //3、读取2个着色器地址
    NSString *vertexFile = [[NSBundle mainBundle] pathForResource:@"shader" ofType:@"vsh"];
    NSString *fragmentFile = [[NSBundle mainBundle] pathForResource:@"shader" ofType:@"fsh"];
    
    //4、编译加载shader,并附着到程序上,拿到程序id
    //**注意** 有可能一个项目中有多个程序。严谨一点 先判断
    if (self.myPrograme) {
        glDeleteProgram(self.myPrograme);
        self.myPrograme = 0;
    }
    self.myPrograme = [self loadShaderWithVertexFile:vertexFile andFragmentFile:fragmentFile];
    
    //5、连接link
    glLinkProgram(self.myPrograme);
    
    //6、检验link
    GLint linkStatus;
    glGetProgramiv(self.myPrograme, GL_LINK_STATUS, &linkStatus);
    
    if (linkStatus == GL_FALSE) {
        //定义一个c语言字符串接受失败信息,不知道信息有多大,尽量写大一点,这里写了512
        GLchar message[512];
        glGetProgramInfoLog(self.myPrograme, sizeof(message), 0, &message[0]);
        //转成oc字符串并打印信息
        NSString *messageString = [NSString stringWithUTF8String:message];
        NSLog(@"link失败信息:%@",messageString);
        return;
    }
    
    
    //7、使用program
    glUseProgram(self.myPrograme);
    

5、有区别的部分

与上面部分 无缝衔接的看


    //========================================================================================================
    //**注意**| 这里开始就与之前加载2D图片有所区别了 |**注意**
    //========================================================================================================
    //8.创建顶点数组 & 索引数组
    //(1)顶点数组 前3顶点值(x,y,z),中间3位颜色值(RGB),后面2位是纹理(s,t)
    GLfloat attrArr[] =
    {
        -0.5f, 0.5f, 0.0f,      0.0f, 0.0f, 0.5f,       0.0f, 1.0f,//左上
        0.5f, 0.5f, 0.0f,       0.0f, 0.5f, 0.0f,       1.0f, 1.0f,//右上
        -0.5f, -0.5f, 0.0f,     0.5f, 0.0f, 1.0f,       0.0f, 0.0f,//左下
        0.5f, -0.5f, 0.0f,      0.0f, 0.0f, 0.5f,       1.0f, 0.0f,//右下
        0.0f, 0.0f, 1.0f,       1.0f, 1.0f, 1.0f,       0.5f, 0.5f,//顶点
    };
    
    //(2).索引数组
    GLuint indices[] =
    {
        0, 3, 2,
        0, 1, 3,
        0, 2, 4,
        0, 4, 1,
        2, 3, 4,
        1, 4, 3,
    };
    
    
    //9、从数组copy到缓冲区
    //1)因为用了定时器会不断刷新,先判断缓冲区是否为空,为空再申请id
    if (self.myVertices == 0) {
        glGenBuffers(1, &_myVertices);
    }
    //2)绑定到顶点缓冲区
    glBindBuffer(GL_ARRAY_BUFFER, _myVertices);
    //3)从内存copy到显存
    glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);
    
    //10、打开通道,传递顶点数据
    GLint position = glGetAttribLocation(self.myPrograme, "position");
    glEnableVertexAttribArray(position);
    glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*8, (float *)NULL + 0);
    
    //11、打开通道,传递颜色数据
    GLint positionColor = glGetAttribLocation(self.myPrograme, "positionColor");
    glEnableVertexAttribArray(positionColor);
    glVertexAttribPointer(positionColor, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*8, (float *)NULL + 3);
    
    //开关控制是否混合纹理和颜色
    if (isHybrid == YES) {
        //12、打开通道,传递纹理数据
        GLint textCoor = glGetAttribLocation(self.myPrograme, "textCoor");
        glEnableVertexAttribArray(textCoor);
        glVertexAttribPointer(textCoor, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*8, (float *)NULL + 6);
        
        //13、加载纹理
        [self setUpTexture:@"mark.jpeg"];
        
        //14、设置纹理采样器 sampler2D   整个OpenGL中能有16个纹理id,默认0是第一个
        glUniform1i(glGetUniformLocation(self.myPrograme, "colorMap"), 0);
        
    }
    
    //15、先找到program中投影矩阵、视图模型矩阵的地址。如果找不到返回-1 表示没有找到这2个对象
    GLuint projectionMatrixSlot = glGetUniformLocation(self.myPrograme, "projectionMatrix");
    GLuint modelViewMatrixSlot = glGetUniformLocation(self.myPrograme, "modelViewMatrix");
    
    
    //16、投影矩阵 :决定了是正投影还是透视投影
    //1)创建一个4*4的投影矩阵
    KSMatrix4 _projectionMatrix;
    //2)获取单元矩阵
    ksMatrixLoadIdentity(&_projectionMatrix);
    //3)计算纵横比
    float width = self.frame.size.width;
    float height = self.frame.size.height;
    float aspect = width / height;
    //4)获取投影矩阵
    ksPerspective(&_projectionMatrix, 30.0, aspect, 5.0f, 30.0f);
    //5)把投影矩阵传递到顶点着色器
    /*
     参数列表:
     location:指要更改的uniform变量的位置
     count:更改矩阵的个数
     transpose:是否要转置矩阵,并将它作为uniform变量的值。必须为GL_FALSE
     value:执行count个元素的指针,用来更新指定uniform变量
     */
    glUniformMatrix4fv(projectionMatrixSlot, 1, GL_FALSE, (GLfloat *)&_projectionMatrix.m[0][0]);
    
    
    
    
    
    
    //17、视图模型矩阵 : 决定了 金字塔是怎么移动旋转的
    //1)创建一个4*4的矩阵
    KSMatrix4 _modelViewMatrix;
    //2)获取单元矩阵
    ksMatrixLoadIdentity(&_modelViewMatrix);
    //3)平移,z轴移动-10    相当于OpenGL中setUpRC()中,设置物体位置一样
    ksTranslate(&_modelViewMatrix, 0.0, 0.0, -10.0);
    //4)创建一个旋转矩阵     这里相当于OpenGL中RenderSence()中,记录模型变化的地方一样
    KSMatrix4 _rotationMatrix;
    //5)加载一个单元矩阵
    ksMatrixLoadIdentity(&_rotationMatrix);
    //6)旋转
    ksRotate(&_rotationMatrix, xDegree, 1.0, 0.0, 0.0);
    ksRotate(&_rotationMatrix, yDegree, 0.0, 1.0, 0.0);
    ksRotate(&_rotationMatrix, zDegree, 0.0, 0.0, 1.0);
    //7)把最终的 旋转矩阵 和 模型视图矩阵 相乘,结果放到模型视图矩阵中
    ksMatrixMultiply(&_modelViewMatrix, &_rotationMatrix, &_modelViewMatrix);
    //8)把模型视图矩阵传递到顶点着色器中
    glUniformMatrix4fv(modelViewMatrixSlot, 1, GL_FALSE, &_modelViewMatrix.m[0][0]);
    
    
    
    
    
    //18、开启正背面剔除
    glEnable(GL_CULL_FACE);
    
    //18、索引绘图
    /*
    void glDrawElements(GLenum mode,GLsizei count,GLenum type,const GLvoid * indices);
    参数列表:
    mode:要呈现的画图的模型
               GL_POINTS
               GL_LINES
               GL_LINE_LOOP
               GL_LINE_STRIP
               GL_TRIANGLES
               GL_TRIANGLE_STRIP
               GL_TRIANGLE_FAN
    count:绘图个数
    type:类型
            GL_BYTE
            GL_UNSIGNED_BYTE
            GL_SHORT
            GL_UNSIGNED_SHORT
            GL_INT
            GL_UNSIGNED_INT
    indices:绘制索引数组

    */
    glDrawElements(GL_TRIANGLES, sizeof(indices) / sizeof(indices[0]), GL_UNSIGNED_INT, indices);
    
    
    //19、渲染到屏幕上
    [self.myContext presentRenderbuffer:GL_RENDERBUFFER];
    
}

 

#pragma mark -第6步需要的  封装方法
#pragma mark - 编译加载着色器shader,并且与程序附着,拿到最后的程序id
-(GLuint)loadShaderWithVertexFile:(NSString *)vertexFile andFragmentFile:(NSString *)fragmentFile
{

    //定义着色器对象
    GLuint verShader,fragShader;
    //创建程序对象
    GLuint program = glCreateProgram();
    
    //编译着色器
    [self compileShader:&verShader type:GL_VERTEX_SHADER filePath:vertexFile];
    [self compileShader:&fragShader type:GL_FRAGMENT_SHADER filePath:fragmentFile];
    
    //拿到了着色器对象,附着到程序上
    glAttachShader(program, verShader);
    glAttachShader(program, fragShader);
    
    //附着完成,拿到了程序id,着色器就可以释放了
    glDeleteShader(verShader);
    glDeleteShader(fragShader);
    
    return program;
}

#pragma mark - 编译着色器
-(void)compileShader:(GLuint *)shader type:(GLenum)type filePath:(NSString *)filePath{
 
    //1 读取着色器的路径,转换成c语言字符串
    NSString *pathString = [NSString stringWithContentsOfFile:filePath encoding:NSUTF8StringEncoding error:nil];
    
    const GLchar *source = (GLchar *)[pathString UTF8String];
    
    //2、根据类型创建着色器
    *shader = glCreateShader(type);
    
    //3、把着色器字符串的源码,放到着色器对象里面
    glShaderSource(*shader, 1, &source, NULL);
    
    //4、编译(把我们写的着色器源代码 编译成 目标代码,也就是shader就完成了)
    glCompileShader(*shader);
}


#pragma mark - 加载纹理,解压图片 === 这里也是图片解压缩的原理
-(GLuint)setUpTexture:(NSString *)imageName{
 
    //1、纹理解压缩
    CGImageRef spriImage = [UIImage imageNamed:imageName].CGImage;
    //2、判断图片有没有拿到
    if (!spriImage) {
        NSLog(@"图片没有拿到");
        exit(1);
    }
    //3、创建一个上下文
    /*
    CGBitmapContextCreate
    参数1:data,指向要渲染的绘制图像的内存地址
    参数2:width,bitmap的宽度,单位为像素
    参数3:height,bitmap的高度,单位为像素
    参数4:bitPerComponent,内存中像素的每个组件的位数,比如32位RGBA,就设置为8
    参数5:bytesPerRow,bitmap的没一行的内存所占的比特数
    参数6:colorSpace,bitmap上使用的颜色空间  kCGImageAlphaPremultipliedLast:RGBA
    */
    //1)拿到图片的宽高
    size_t width = CGImageGetWidth(spriImage);
    size_t height = CGImageGetHeight(spriImage);
    
    //2)拿到图片的大小,也就是纹理数据
    GLubyte *spriData = (GLubyte *)calloc(width * height * 4, sizeof(GLubyte));
    
    //3)创建上下文
    CGContextRef spriContext = CGBitmapContextCreate(spriData, width, height, 8, width*4, CGImageGetColorSpace(spriImage), kCGImageAlphaPremultipliedLast);
    
    //4、将图片绘制出来
    //1)拿到坐标
    CGRect rect = CGRectMake(0, 0, width, height);
    //2)使用默认方式绘制
    CGContextDrawImage(spriContext, rect, spriImage);
    //3)翻转策略
    CGContextTranslateCTM(spriContext, rect.origin.x, rect.origin.y);
    CGContextTranslateCTM(spriContext, 0, rect.size.height);
    CGContextScaleCTM(spriContext, 1.0, -1.0);
    CGContextTranslateCTM(spriContext, -rect.origin.x, -rect.origin.y);
    CGContextDrawImage(spriContext, rect, spriImage);
    
    //5、绘制完成,释放上下文
    CGContextRelease(spriContext);
    
    //6、绑定纹理id(如果只有一个纹理,直接使用0就行了)
    glBindTexture(GL_TEXTURE_2D, 0);
    
    //7、设置纹理的缩放和环绕方式
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    
    //8、载入2D纹理
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (float)width, (float)height, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriData);
    
    //9、释放纹理数据
    free(spriData);
    
    return 0;
}

#pragma mark - 7、添加底部按钮,控制旋转、切换
-(void)setUpBottomButtons{
    
    NSArray *array = @[@"x",@"y",@"z",@"混合"];
    
  
    
    
    
    
#define AppViewW 50
#define AppViewH 50
#define KColCount 4 //每行的个数
    //每个Button的起始位置
#define KStartX 30
#define KStartY self.frame.size.height - 80
    //两个Button之间的间距
#define SpaceX 30
#define SpaceY 0
        
        
    for (int i=0; i

你可能感兴趣的:(OpenGL ES-09-案例05-GLSL索引绘图+颜色纹理混合)