06-初始OpenGL ES -用GLSL实现画板的功能

效果图
06-初始OpenGL ES -用GLSL实现画板的功能_第1张图片
准备资料
1.下载案例中需要用的资源,地址:https://download.csdn.net/download/lyz0925/12351912;
2.将所需要的资源拖入项目中。如下图:
06-初始OpenGL ES -用GLSL实现画板的功能_第2张图片
06-初始OpenGL ES -用GLSL实现画板的功能_第3张图片
代码实现
1.创建顶点着色器并实现如下的代码

//顶点
attribute vec4 inVertex;

//矩阵
uniform mat4 MVP;

//点的大小
uniform float pointSize;

//点的颜色
uniform lowp vec4 vertexColor;

//输出颜色
varying lowp vec4 color;

void main()
{
    //顶点计算 = 矩阵 * 顶点
    gl_Position = MVP * inVertex;
    
    //修改顶点大小
    gl_PointSize = pointSize;
    //    1 * 3.0;
    
    //将通过uniform 传递进来的颜色,从顶点着色器程序传递到片元着色器
    color = vertexColor;
}

2.创建片元着色器并实现代码:

//获取纹理
uniform sampler2D texture;
/*
 sampler2D,中的2D,表示这是一个2D纹理。我们也可以使用1D\3D或者其他类型的采样器。我们总是
 把这个值设置为0。来指示纹理单元0.
 */

//获取从顶点程序传递过来的颜色
//lowp,精度
varying lowp vec4 color;

void main()
{
    //将颜色和纹理组合 是相乘!!!!
    gl_FragColor = color * texture2D(texture, gl_PointCoord);
}

3.自定义一个视图实现画板的功能:
–.h—的代码如下:

#import 
#import 
#import 
#import 

NS_ASSUME_NONNULL_BEGIN

@interface YZPoint : NSObject

@property (nonatomic, strong) NSNumber *mX;
@property (nonatomic, strong) NSNumber *mY;

@end

@interface YZView : UIView

//最新的点
@property (nonatomic, assign) CGPoint location;
//前一个点
@property (nonatomic, assign) CGPoint previousLocation;

//清屏
- (void)erase;

//设置画笔的颜色
- (void)setBrushColorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue;;

//绘制
- (void)paint;


@end

NS_ASSUME_NONNULL_END

–.m—的代码如下:

#import 
#import 

#import "debug.h"
#import "shaderUtil.h"
#import "fileUtil.h"

//画笔透明度
#define kBrushOpacity  (1.0 / 2.0)
//画笔每一笔 有几个点
#define kBrushPiexlStep 3
//画笔的比例
#define kBrushScale     2

enum {
    PROGRAM_POINT,
    NUM_PROGRAMS
};

enum {
    UNIFORM_MVP,
    UNIFORM_POINT_SIZE,
    UNIFORM_VERTEX_COLOR,
    UNIFORM_TEXTURE,
    NUM_UNIFORMS
};

enum {
    ATTRIB_VERTEX,
    NUM_ATTRIBS
};

//定义一个结构体
typedef struct {
    //vert,frag 指向顶点和片元着色器程序文件
    char *vert, *frag;
    //创建uniform数组,4个元素,数量与着色器程序文件中uniform对象个数一致
    GLint uniform[NUM_UNIFORMS];
    GLuint id;
}programInfo_t;

programInfo_t program[NUM_PROGRAMS] = {
    { "point.vsh",   "point.fsh" },
};

//纹理
typedef struct {
    GLuint id;
    GLsizei width, height;
}texureInfo_t;



@implementation YZPoint

- (instancetype)initWithCGPoint:(CGPoint)point {
    self = [super init];
    if (self) {
        self.mX = [NSNumber numberWithDouble:point.x];
        self.mY = [NSNumber numberWithDouble:point.y];
    }
    return self;
}

@end


@interface YZView() {
    //缓冲区的像素尺寸
    GLint backingWidth;
    GLint backingHeight;
    
    EAGLContext *context;
    
    //缓存区 frameBuffer renderBuffer
    GLuint viewFrameBuffer, viewRenderBuffer;
    
    //画笔纹理 画笔的颜色
    texureInfo_t brushTexture;
    GLfloat brushColor[4];
    
    //是否第一次点击
    Boolean firstTouch;
    
    //是否需要清屏
    Boolean needsErase;
    
    //顶点着色器 片元着色器 Program
    GLuint vertexShader;
    GLuint fragmentShader;
    GLuint shaderProgram;
    
    //顶点buffer
    GLuint vBoId;
    
    //是否初始化
    BOOL initialized;
    
    //点的数组
    NSMutableArray *pointArray;
}

@end

@implementation YZView

//修改返回类的类型为CAEAGLLayer
+ (Class)layerClass {
    return [CAEAGLLayer class];
}

- (instancetype)initWithCoder:(NSCoder *)coder {
    self = [super initWithCoder:coder];
    if (self) {
         //初始化layer
       CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
       
       //设置透明度
       eaglLayer.opaque = YES;
       
       //设置layer的描述属性
       /*
       1.kEAGLDrawablePropertyRetainedBacking
         表示绘图表面显示后,是否保留其内容,通过一个NSNumber 包装一个bool值。
        如果是NO,表示显示内容后,不能依赖于相同的内容;如果是YES,表示显示内容后不变;
        一般只有在需要内容保存不变的情况下才使用YES,设置为YES,会导致性能降低,内存使用量降低。
        一般设置为NO。
       
       2.kEAGLDrawablePropertyColorFormat
          表示绘制表面的内部颜色缓存区格式
       */
       eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
       [NSNumber numberWithBool:NO], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
       
       //初始化上下文
       context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
       if (!context || ![EAGLContext setCurrentContext:context]) {
           NSLog(@"Error:context is nil or setCurrentContext error");
           return nil;
       }
       
       //设置视图的比例因子
       /*
       比例因子决定视图中的内容如何从逻辑坐标空间(以点测量)映射到设备坐标空间(以像素为单位)。
       此值通常为1或2。更高比例的因素表明视图中的每一个点由底层的多个像素表示。
       例如,如果缩放因子为2,并且视图框大小为50×50点,则用于显示内容的位图的大小为100×100像素。
       */
       self.contentScaleFactor = [[UIScreen mainScreen] scale];
       
       //是否需要清屏,默认为yes
       needsErase = YES;
    }
    return self;
}


- (void)layoutSubviews {
    [EAGLContext setCurrentContext:context];
    
    //判断是否初始化
    if (!initialized) {
        initialized = [self initGL];
    }else {
        //如果已经初始化,则调整layer
        [self resizeFromLayer:(CAEAGLLayer *)self.layer];
    }
    //清除帧第一次分配
    if (needsErase) {
        [self erase];
        needsErase = NO;
    }
}

- (void)erase {
    glBindFramebuffer(GL_FRAMEBUFFER, viewFrameBuffer);
    glClearColor(0.0f, 0.0, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    
    //显示缓存区
    glBindRenderbuffer(GL_RENDERBUFFER, viewRenderBuffer);
    [context presentRenderbuffer:GL_RENDERBUFFER];
}

- (BOOL)resizeFromLayer:(CAEAGLLayer *)layer {
    //根据当前图层大小分配颜色缓存区
    glBindRenderbuffer(GL_RENDERBUFFER, viewRenderBuffer);
    
    //绑定一个Drawble对象存储到Opgen GL ES 渲染缓存对象
    [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:layer];
    
    //获取绘制缓存区的像素宽度
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth);
    //获取绘制缓存的像素高度
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight);
    
    //检查GL_FRAMEBUFFER缓存区状态
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
        NSLog(@"make complete framebuffer object failed");
        return NO;
    }
    
    //更新投影矩阵、模型视图矩阵
    /*
     投影分为正射投影和透视投影,我们可以通过它来设置投影矩阵来设置视域,
     在OpenGL中,默认的投影矩阵是一个立方体,即x y z 分别是-1.0~1.0的距离,如果超出该区域,将不会被显示
     
     正射投影(orthographic projection):GLKMatrix4MakeOrtho(float left, float righ, float bottom, float top, float nearZ, float farZ),
     该函数返回一个正射投影的矩阵,它定义了一个由 left、right、bottom、top、near、far 所界定的一个矩形视域。
     此时,视点与每个位置之间的距离对于投影将毫无影响。
     
     透视投影(perspective projection):GLKMatrix4MakeFrustum(float left, float right,float bottom, float top, float nearZ, float farZ),
     该函数返回一个透视投影的矩阵,它定义了一个由 left、right、bottom、top、near、far 所界定的一个平截头体(椎体切去顶端之后的形状)视域。
     此时,视点与每个位置之间的距离越远,对象越小。
     */
    GLKMatrix4 projectionMatrix = GLKMatrix4MakeOrtho(0, backingWidth, 0, backingHeight, -1, 1);
    //模型视图矩阵,用于平移,旋转,缩放
    GLKMatrix4 modelViewMatrix = GLKMatrix4Identity;
    //矩阵相乘
    GLKMatrix4 MVPMatrix = GLKMatrix4Multiply(projectionMatrix, modelViewMatrix);
    
    /*
    void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
    功能:为当前程序对象指定uniform变量值
    参数1:location 指明要更改的uniform变量的位置 MVP
    参数2:count 指定将要被修改的矩阵的数量
    参数3:transpose 矩阵的值被载入变量时,是否要对矩阵进行变换,比如转置!
    参数4:value ,指向将要用于更新uniform变量MVP的数组指针
    */
    glUniformMatrix4fv(program[PROGRAM_POINT].uniform[UNIFORM_MVP], 1, GL_FALSE, MVPMatrix.m);
    
    //更新视口
    glViewport(0, 0, backingWidth, backingHeight);
    
    return YES;
}

//初始化OpenGl
- (BOOL)initGL {
    //生成一个帧缓存对象和颜色渲染缓存的标识
    glGenFramebuffers(1, &viewFrameBuffer);
    glGenRenderbuffers(1, &viewRenderBuffer);
    
    //绑定viewFrambuffer 和 viewRenderBuffer
    glBindFramebuffer(GL_FRAMEBUFFER, viewFrameBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, viewRenderBuffer);
    
    //绑定一个Drawable对象,存储到OpenGL渲染缓存对象上
    [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(id<EAGLDrawable>)self.layer];
    
    //将viewRenderBuffer 绑定到GL_COLOR_ATTACHMENT0
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, viewRenderBuffer);
    
    //获取绘制缓冲区的像素宽度---将绘制缓冲区像素宽度存储在backingwidth中
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth);
    //获取绘制缓存区的像素高度--将绘制缓冲区像素高度存储在backingHeight中
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight);
    
    //检查GL_FRAMEBUFFER缓存区状态
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
        NSLog(@"Make complete frameBuffer object failed");
        return NO;
    }
    
    //设置视口
    glViewport(0, 0, backingWidth, backingHeight);
    
    //创建顶点缓冲对象来保存顶点数据
    glGenBuffers(1, &vBoId);
    
    //加载画笔纹理
    brushTexture = [self texureForName:@"Particle.png"];
    
    //加载shader
    [self setupShaders];
    
    //点模糊效果通过开启混合模式,并设置混合函数
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    
    //回放录制的加油路径
    [self setupComeOnPath];
    
    
    return YES;
}

//回放录制的加油路径
- (void)setupComeOnPath {
    //回放录制的路径,这是“加油!”
    NSString *path = [[NSBundle mainBundle]pathForResource:@"abc" ofType:@"string"];
    //将path 使用NSUTF8StringEncoding 编码
    NSString *str = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
    
    //开辟数组空间-可变的
    pointArray = [NSMutableArray array];
    
    //根据abc.string文件,将绘制点的数据,json解析到数组
    NSArray *jsonArr = [NSJSONSerialization JSONObjectWithData:[str dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingAllowFragments error:nil];
    
    //遍历jsonArr数组,将数据转为CCPoint类型数据
    for (NSDictionary *dict in jsonArr) {
        YZPoint *point = [YZPoint new];
        point.mX = [dict objectForKey:@"mX"];
        point.mY = [dict objectForKey:@"mY"];
        
        //将CCPoint 对象添加到CCArr数组
        [pointArray addObject:point];
    }
    
    //调用绘制方法:绘制abc.string 绘制的加油字样,延时5秒绘制!
    [self performSelector:@selector(paint) withObject:nil afterDelay:0.5];
    
}

- (void)paint {
    //从0开始遍历顶点,步长为2
    for (int i = 0; i < pointArray.count - 1; i += 2) {
        YZPoint *yPoint1 = pointArray[i];
        YZPoint *yPoint2 = pointArray[i+1];
        CGPoint p1, p2;
        p1.x = yPoint1.mX.floatValue;
        p1.y = yPoint1.mY.floatValue;
        
        p2.x = yPoint2.mX.floatValue;
        p2.y = yPoint2.mY.floatValue;
        
        //将两个点绘制成线条
        [self renderLineFormPoint:p1 toPoind:p2];
        
    }
}

- (void)renderLineFormPoint:(CGPoint)start toPoind:(CGPoint)end {
    //顶点缓冲区
    static GLfloat *vertexBuffer = NULL;
    //顶点max
    static NSUInteger vertexMax = 64;
    //顶点个数
    NSUInteger vertexCount = 0, count;
    
    //从点到像素转换
    CGFloat scale = self.contentScaleFactor;
    //将每个顶点与scale因子相乘
    start.x *= scale;
    start.y *= scale;
    
    end.x *= scale;
    end.y *= scale;
    
    //开启数据缓存区
    if (vertexBuffer == NULL) {
        //开启顶点地址空间
        vertexBuffer = malloc(vertexMax * 2 * sizeof(GLfloat));
    }
    
    //向缓冲区添加点 求得start 和 end 2点间的距离
    float seq = sqrtf((end.x - start.x) * (end.x - start.x) + (end.y - start.y) * (end.y - start.y));
    
    /*
    向上取整,求得距离要产生多少个点?
    kBrushPixelStep,画笔像素步长
    修改kBrushPixelStep 的值,越大,笔触越细;越小,笔触越粗!
    */
    NSInteger pointCount = ceil(seq / kBrushPiexlStep);
    
    count = MAX(pointCount, 1);
    
    for (int i = 0; i < count; i++) {
        //判断如果顶点式 > 设置顶点max
        if (vertexCount >= vertexMax) {
            //修改vertexMax 的长度
            vertexMax = 2 * vertexMax;
            //增加空间开辟
            vertexBuffer = realloc(vertexBuffer, vertexMax * 2 * sizeof(GLfloat));
        }
        //修改vertexBuffer数组的值
        vertexBuffer[2 * vertexCount + 0] = start.x + (end.x - start.x) * ((GLfloat)i / (GLfloat)count);
        vertexBuffer[2 * vertexCount + 1] = start.y + (end.y - start.y) * ((GLfloat)i / (GLfloat)count);
        vertexCount += 1;
    }
    
    //加载数据到vertexBuffer对象中
    glBindBuffer(GL_ARRAY_BUFFER, vBoId);
    //将cpu存储的顶点数据复制到GPu中, 复制顶点数据到缓冲区中提供给OpenGl使用
    glBufferData(GL_ARRAY_BUFFER, vertexCount * 2 * sizeof(GLfloat), vertexBuffer, GL_DYNAMIC_DRAW);
    
    //链接顶点属性
    glEnableVertexAttribArray(ATTRIB_VERTEX);
    glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, GL_FALSE, 2 * sizeof(GLfloat), 0);
    
    //用刚刚创建的program[0].id的program绘制
    glUseProgram(program[PROGRAM_POINT].id);
    /*
    根据顶点绘制图形,
    参数1:绘制模型 连接线段,参考视觉班第一节课的课件
    参数2:起始点,0
    参数3:顶点个数
    */
    glDrawArrays(GL_POINTS, 0, (int)vertexCount);
    
    //显示buffer
    glBindRenderbuffer(GL_RENDERER, viewRenderBuffer);
    [context presentRenderbuffer:GL_RENDERBUFFER];
}


- (void)setupShaders {
    for (int i = 0; i < NUM_PROGRAMS; i++) {
        
        //读取顶点着色程序
        char *vsrc = readFile(pathForResource(program[i].vert));
        char *fsrc = readFile(pathForResource(program[i].frag));
        
        //将char->NSString 对象
        NSString *vsrcStr = [[NSString alloc]initWithBytes:vsrc length:strlen(vsrc)-1 encoding:NSUTF8StringEncoding];
         NSString *fsrcStr = [[NSString alloc]initWithBytes:fsrc length:strlen(fsrc)-1 encoding:NSUTF8StringEncoding];
        
        //打印着色程序中的代码
        NSLog(@"vsrc:%@",vsrcStr);
        NSLog(@"fsrc:%@",fsrcStr);
        
        
        //attribute
        GLsizei attribCt = 0;
        //创建字符串数组【1】
        GLchar *attribUsed[NUM_ATTRIBS];
        //
        GLint attrib[NUM_ATTRIBS];
        
        //attribute 变量名称-inVertex(point.vsh)
        GLchar *attribName[NUM_ATTRIBS] = {
            "inVertex",
        };
        
        //uniform变量名称 "MVP", "pointSize", "vertexColor", "texture",
        const GLchar *uniformName[NUM_UNIFORMS] = {
            "MVP", "pointSize", "vertexColor", "texture",
        };
        
        //遍历attribute
        for (int j = 0; j < NUM_ATTRIBS; j++)
        {
            //strstr(str1,str2) 函数用于判断字符串str2是否是str1的子串。如果是,则该函数返回str2在str1中首次出现的地址;否则,返回NULL。
            //判断,attribute 变量,是否存在顶点着色器程序中。point.vsh
            if (strstr(vsrc, attribName[j]))
            {
                //attribute个数
                attrib[attribCt] = j;
                //使用的attribute的名称
                attribUsed[attribCt++] = attribName[j];
            }
        }
        
        //利用shaderUtil.c封装好的方法对programe 进行创建、链接、生成Programe
        /*
         参数1:vsrc,顶点着色器程序
         参数2:fsrc,片元着色器程序
         参数3:attribute变量个数
         参数4:attribute变量名称
         参数5:当前attribute位置
         参数6:uniform名字
         参数7:program的uniform地址
         参数8:program程序地址
         */
        glueCreateProgram(vsrc, fsrc,
                          attribCt, (const GLchar **)&attribUsed[0], attrib,
                          NUM_UNIFORMS, &uniformName[0], program[i].uniform,
                          &program[i].id);
        
        //释放vsrc,fsrc指针
        free(vsrc);
        free(fsrc);
        
        // 设置常数、初始化Uniform
        //当前的i == 0
        if (i == PROGRAM_POINT)
        {
            //使用proram program[0].id 等价,以往课程例子中的GLuint program;
            glUseProgram(program[PROGRAM_POINT].id);
            
            
            //为当前程序对象指定uniform变量值
            /*
             为当前程序对象指定uniform变量MVP赋值
             
             void glUniform1f(GLint location,  GLfloat v0);
             参数1:location,指明要更改的uniform变量的位置 MVP
             参数2:v0,指明在指定的uniform变量中要使用的新值
             
             program[0].uniform[3] = 0
             等价于,vsh顶点着色器程序中的uniform变量,MVP = 0;
             其实简单理解就是做了一次初始化,清空这个mat4矩阵
             */
            glUniform1i(program[PROGRAM_POINT].uniform[UNIFORM_TEXTURE], 0);
            
            // 投影矩阵
            /*
             投影分为正射投影和透视投影,我们可以通过它来设置投影矩阵来设置视域,在OpenGL中,默认的投影矩阵是一个立方体,即x y z 分别是-1.0~1.0的距离,如果超出该区域,将不会被显示
             
             正射投影(orthographic projection):GLKMatrix4MakeOrtho(float left, float righ, float bottom, float top, float nearZ, float farZ),该函数返回一个正射投影的矩阵,它定义了一个由 left、right、bottom、top、near、far 所界定的一个矩形视域。此时,视点与每个位置之间的距离对于投影将毫无影响。
             
             透视投影(perspective projection):GLKMatrix4MakeFrustum(float left, float right,float bottom, float top, float nearZ, float farZ),该函数返回一个透视投影的矩阵,它定义了一个由 left、right、bottom、top、near、far 所界定的一个平截头体(椎体切去顶端之后的形状)视域。此时,视点与每个位置之间的距离越远,对象越小。
             
             在平面上绘制,只需要使正投影就可以了!!
             */
            GLKMatrix4 projectionMatrix = GLKMatrix4MakeOrtho(0, backingWidth, 0, backingHeight, -1, 1);
           
            //模型矩阵,比如你要平移、旋转、缩放,就可以设置在模型矩阵上
            //这里不需要这些变换,则使用单元矩阵即可,相当于1 * ? = ?
            GLKMatrix4 modelViewMatrix = GLKMatrix4Identity;
            
            //矩阵相乘,就2个矩阵的结果交给MVPMatrix
            GLKMatrix4 MVPMatrix = GLKMatrix4Multiply(projectionMatrix, modelViewMatrix);
            /*
              void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
             功能:为当前程序对象指定uniform变量值
             参数1:location 指明要更改的uniform变量的位置 MVP
             参数2:count 指定将要被修改的矩阵的数量
             参数3:transpose 矩阵的值被载入变量时,是否要对矩阵进行变换,比如转置!
             参数4:value ,指向将要用于更新uniform变量MVP的数组指针
             */
            glUniformMatrix4fv(program[PROGRAM_POINT].uniform[UNIFORM_MVP], 1, GL_FALSE, MVPMatrix.m);
            
            //点的大小 pointSize
            /*
              为当前程序对象指定uniform变量pointSize赋值
               program[0].uniform[pointSize] = 纹理宽度/画笔比例
             */
//             glUniform1f(program[PROGRAM_POINT].uniform[UNIFORM_POINT_SIZE], brushTexture.width / kBrushScale);
             glUniform1f(program[PROGRAM_POINT].uniform[UNIFORM_POINT_SIZE], brushTexture.width / kBrushScale);
            
            
            //笔刷颜色
            /*
             为当前程序对象指定uniform变量vertexColor赋值
             program[0].uniform[vertexColor] = 画笔颜色
             
             void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
             功能:为当前程序对象指定uniform变量值
             参数1:location 指明要更改的uniform变量的位置 vertexColor
             参数2:count 指定将要被修改的4分量的数量
             参数3:value ,指向将要用于更新uniform变量vertexColor的值

             */
            glUniform4fv(program[PROGRAM_POINT].uniform[UNIFORM_VERTEX_COLOR], 1, brushColor);
            
        }
    }
    
    glError();
}

- (texureInfo_t)texureForName:(NSString *)name {
    CGImageRef brushImage;
    CGContextRef brushContext;
    GLubyte *brushData;
    size_t width, height;
    GLuint textID;
    texureInfo_t texture;
    
    //首先建立一个图像文件的数据:一个UIImage对象,然后提取核心图形图像
    brushImage = [UIImage imageNamed:name].CGImage;
    
    //获取图片的宽和高
    width = CGImageGetWidth(brushImage);
    height = CGImageGetHeight(brushImage);
    
    //分配位图上下文所需的内存
    brushData = (GLubyte *)calloc(width * height * 4, sizeof(GLubyte));
    
    //使用core grahics 框架提供的bitMaph创造功能
    /*
     CGContextRef CGBitmapContextCreate(
     void * data,
     size_t width,
     size_t height,
     size_t bitsPerComponent,
     size_t bytesPerRow,
     CGColorSpaceRef cg_nullable space,
     uint32_t bitmapInfo);
     
     Quartz创建一个位图绘制环境,也就是位图上下文。
     参数1:data,要渲染的绘制内容的地址
     参数2:位图的宽
     参数3:位图的高
     参数4:内存中像素的每个组件的位数,比如32位像素格式和RGB颜色空间。一般设置为8
     参数5:位图每一行占有比特数
     参数5:颜色空间,通过CGImageGetColorSpace(图片)获取颜色空间
     参数6:颜色通道,RGBA = kCGImageAlphaPremultipliedLast
     */
    brushContext = CGBitmapContextCreate(brushData, width, height, 8, width * 4, CGImageGetColorSpace(brushImage), kCGImageAlphaPremultipliedLast);
    
    //创建context之后,可以再context上绘制图片
    CGContextDrawImage(brushContext, CGRectMake(0.0, 0.0, (CGFloat)width, (CGFloat)height), brushImage);
    
    //释放不需要的上下文,避免内存泄漏
    CGContextRelease(brushContext);
    
    //使用OpenGl ES 生成纹理
    /*
     生成纹理的函数
     glGenTextures (GLsizei n, GLuint* textures)
     参数1:n,生成纹理个数
     参数2:存储纹理索引的第一个元素指针
     */
    glGenTextures(1, &textID);
    
    //b绑定纹理名称,允许建立一个绑定到目标纹理的有名称的纹理
    glBindTexture(GL_TEXTURE_2D, textID);
    
    //设置纹理参数使用缩小滤波器和线性滤波器 --设置纹理属性
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    
    //绑定纹理名称,为内存中的图形数据提供一个指针
    /*
     功能:生成2D纹理
     glTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid* pixels);
     参数1:target,纹理目标,因为你使用的是glTexImage2D函数,所以必须设置为GL_TEXTURE_2D
     参数2:level,0,基本图像级别
     参数3:internalformat,颜色组件;GL_RGBA,GL_ALPHA,GL_RGBA
     参数4:width,纹理图像的宽度
     参数5:height,纹理图像的高度
     参数6:border,纹理边框的宽度,必须为0
     参数7:format,像素数据的颜色格式,可不与internalformat一致,可参考internalformat的值
     参数8:type,像素数据类型,GL_UNSIGNED_BYTE
     参数9:pixels,内存中指向图像数据的指针
     */
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (int)width, (int)height, 0, GL_RGBA, GL_UNSIGNED_BYTE, brushData);
    
    //生成纹理之后,即可释放brushData数据
    free(brushData);
    
    //补充自己定义的texure结构体中的内容
    texture.id = textID;
    //纹理宽度
    texture.width = (int)width;
    texture.height = (int)height;
    //返回纹理对象数据
    return texture;
    
    
    return texture;
}

- (void)setBrushColorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue {
    //更新画笔颜色 颜色 * 透明度
    brushColor[0] = red * kBrushOpacity;
    brushColor[1] = green * kBrushOpacity;
    brushColor[2] = green * kBrushOpacity;
    brushColor[3] = kBrushOpacity;
    
    if (initialized) {
        glUseProgram(program[PROGRAM_POINT].id);
        glUniform4fv(program[PROGRAM_POINT].uniform[UNIFORM_VERTEX_COLOR], 1, brushColor);
    }
}

-(void)dealloc
{
    //安全释放viewFrameBuffer、viewRenderBuffer、brushTexture、vboId、context
    if (viewFrameBuffer) {
        glDeleteFramebuffers(1, &viewFrameBuffer);
        viewFrameBuffer = 0;
    }
    
    if (viewRenderBuffer) {
        glDeleteRenderbuffers(1, &viewRenderBuffer);
        viewRenderBuffer = 0;
    }
    
    
    if (brushTexture.id) {
        glDeleteTextures(1, &brushTexture.id);
        brushTexture.id = 0;
    }
    if (vBoId) {
        glDeleteBuffers(1, &vBoId);
        vBoId = 0;
    }
    
    if ([EAGLContext currentContext] == context) {
        [EAGLContext setCurrentContext:nil];
    }
}

#pragma mark -- Touch Click
//点击屏幕开始
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    //获取绘制的bounds
    CGRect                bounds = [self bounds];
    //获取当前的点击touch
    UITouch*            touch = [[event touchesForView:self] anyObject];
    //设置为firstTouch -> yes
    firstTouch = YES;
    
    //获取当前点击的位置信息,x,y
    _location = [touch locationInView:self];
    
    //y = height - y
    _location.y = bounds.size.height - _location.y;
    
}

-(void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    CGRect bounds =  [self bounds];
    UITouch *touch = [[event touchesForView:self]anyObject];
    
    //第一次点击
    if (firstTouch) {
        //将firstTouch状态改为NO
        firstTouch = NO;
        //_previousLocation = 获取上一个顶点
        _previousLocation = [touch previousLocationInView:self];
        _previousLocation.y = bounds.size.height - _previousLocation.y;
    
    }else
    {
        _location = [touch locationInView:self];
        _location.y = bounds.size.height - _location.y;
        _previousLocation = [touch previousLocationInView:self];
        _previousLocation.y = bounds.size.height - _previousLocation.y;
    }
    
    //获取_previousLocation 和 _location 2个顶点,绘制成线条
    [self renderLineFormPoint:_previousLocation toPoind:_location];
}

-(void)touchesEnded:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{

    CGRect bounds = [self bounds];
    UITouch *touch = [[event touchesForView:self]anyObject];
    
    //判断是否为第一次触碰
    if (firstTouch) {
        firstTouch = NO;
        _previousLocation = [touch previousLocationInView:self];
        _previousLocation.y = bounds.size.height - _previousLocation.y;
        [self renderLineFormPoint:_previousLocation toPoind:_location];
    }
}

-(void)touchesCancelled:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    NSLog(@"Touch Cancelled");
    
}

-(BOOL)canBecomeFirstResponder
{
    return YES;
}

@end

4.封装系统音效播放的工具类,代码如下:

#import 
#import 

NS_ASSUME_NONNULL_BEGIN

@interface SoundEffect : NSObject {
    SystemSoundID _soundID;
}

+ (id)soundEffectWithContentOfFile:(NSString *)path;

- (id)initWithContentOfFile:(NSString *)path;

- (void)play;


@end

NS_ASSUME_NONNULL_END
#import "SoundEffect.h"

@implementation SoundEffect

//根据指定的声音文件中创建一个系统音效对象
+ (id)soundEffectWithContentOfFile:(NSString *)path {
    if (path) {
        return [[SoundEffect alloc] initWithContentOfFile:path];
    }
    return nil;
}

- (id)initWithContentOfFile:(NSString *)path {
    self = [super init];
    if (self != nil) {
        //1. 获取声音文件路径
        NSURL *fileURL= [NSURL fileURLWithPath:path];
        
        //2.判断声音文件是否存在
        if (fileURL == nil) {
            NSLog(@"URL is nil for path %@",path);
            return nil;
        }
        
        //3.定义SystemSoundid
        SystemSoundID aSoundID;
        
        //允许应用程序指定由系统声音服务播放其播放的音效
        OSStatus error = AudioServicesCreateSystemSoundID((__bridge CFURLRef)fileURL, &aSoundID);
        if (error == kAudioServicesNoError) {
            //赋值
            _soundID = aSoundID;
        }else {
            NSLog(@"Error:loading soud path = %@", path);
            self = nil;
        }
    }
    return self;
}

- (void)play {
    //播放设置的系统音频
    AudioServicesPlaySystemSound(_soundID);
}


- (void)dealloc {
    //清除系统音效
    AudioServicesDisposeSystemSoundID(_soundID);
}
@end

5.在storyboard中修改view的父类为“YZView”,并在控制器中实现如下代码:

#import "ViewController.h"
#import "SoundEffect.h"
#import "YZView.h"

//亮度
#define kBrightness             1.0
//饱和度
#define kSaturation             0.45
//调色板高度
#define kPaletteHeight            30
//调色板大小
#define kPaletteSize            5
//最小擦除区间
#define kMinEraseInterval        0.5

//填充率
#define kLeftMargin                10.0
#define kTopMargin                10.0
#define kRightMargin            10.0


@interface ViewController () {
    SoundEffect *eraseSound;
    SoundEffect *selecColorSound;
    CFTimeInterval lastTime;
}

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    [self setUpUI];
}


- (void)setUpUI {
    //数据存储颜色选择的图片
    UIImage *redImage = [[UIImage imageNamed:@"Red"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
    UIImage *yeallowImage = [[UIImage imageNamed:@"Yellow"] imageWithRenderingMode:(UIImageRenderingMode)UIImageRenderingModeAlwaysOriginal];
    UIImage *greenImage = [[UIImage imageNamed:@"Green"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
    UIImage *blueImage = [[UIImage imageNamed:@"Blue"] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
    
    NSArray *selectorImageArr = @[redImage, yeallowImage, greenImage, blueImage];
    
    //2.创建一个分段控件 让用户可以选择画笔颜色
    UISegmentedControl *segmentControl = [[UISegmentedControl alloc] initWithItems:selectorImageArr];
    
    //3.设置控件的位置
    CGRect rect = [[UIScreen mainScreen] bounds];
    CGRect frame = CGRectMake(rect.origin.x + kLeftMargin, rect.size.height - kPaletteHeight - kTopMargin , rect.size.width - (kLeftMargin + kRightMargin), kPaletteHeight);
    segmentControl.frame = frame;
    
    //4.segmentcontoller添加按钮事件-- 改变画笔颜色
    [segmentControl addTarget:self action:@selector(changeBrushColor:) forControlEvents:UIControlEventValueChanged];
    
    //5. 设置tintColor 和 默认选择
    segmentControl.selectedSegmentIndex = 2;
    
    [self.view addSubview:segmentControl];
    
    //6.加载声音--选择颜色和清空屏幕颜色的声音
    NSString *ereasePath = [[NSBundle mainBundle] pathForResource:@"Erase" ofType:@"caf"];
    NSString *selectPath = [[NSBundle mainBundle] pathForResource:@"Select" ofType:@"caf"];
    
    eraseSound = [SoundEffect soundEffectWithContentOfFile:ereasePath];
    selecColorSound = [SoundEffect soundEffectWithContentOfFile:selectPath];
    
    //7.定义起始颜色
       //创建并返回一个颜色对象使用指定的不透明的HSB颜色空间的分量值
       /*
        参数列表:
        Hue:色调 = 选择的index/颜色选择总数
        saturation:饱和度
        brightness:亮度
        */
       CGColorRef color = [UIColor colorWithHue:(CGFloat)2.0/(CGFloat)kPaletteSize saturation:kSaturation brightness:kBrightness alpha:1.0].CGColor;
       
       //根据颜色值,返回颜色相关的颜色组件
       const CGFloat *components = CGColorGetComponents(color);
       
       //根据OpenGL视图设置画笔颜色
       [(YZView *)self.view setBrushColorWithRed:components[0] green:components[1] blue:components[2]];
}

- (IBAction)eraseAction:(id)sender {
    
    
    //防止一直不停点击清除屏幕
    if (CFAbsoluteTimeGetCurrent() > lastTime + kMinEraseInterval) {
        NSLog(@"清除屏幕");
        
        //播放系统音效
        [eraseSound play];
        
        //清理屏幕
        [(YZView *)self.view erase];
        
        //保存这次时间到lastTime中
        lastTime = CFAbsoluteTimeGetCurrent();
    }
}

- (void)changeBrushColor:(id)sender {
    NSLog(@"改变颜色");
    [selecColorSound play];
    // 定义新的画笔颜色 创建并返回一个颜色对象使用指定的不透明的HSB颜色空间的分量值
    CGColorRef color = [UIColor colorWithHue:(CGFloat)[sender selectedSegmentIndex] / (CGFloat)kPaletteSize saturation:kSaturation brightness:kBrightness alpha:1.0].CGColor;
    
    //返回颜色的组件,红、绿、蓝、alpha颜色值
    const CGFloat *components = CGColorGetComponents(color);
    
    // 根据OpenGL视图设置画笔颜色
    [(YZView *)self.view setBrushColorWithRed:components[0] green:components[1] blue:components[2]];
}

按照以上的代码,便可实现电子画板的功能。

你可能感兴趣的:(OpenGL与OpenGL,SE)