效果图

准备资料
1.下载案例中需要用的资源,地址:https://download.csdn.net/download/lyz0925/12351912;
2.将所需要的资源拖入项目中。如下图:


代码实现
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 <UIKit/UIKit.h>
#import <OpenGLES/EAGL.h>
#import <OpenGLES/ES2/gl.h>
#import <OpenGLES/ES2/glext.h>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;@endNS_ASSUME_NONNULL_END

–.m—的代码如下:

#import <OpenGLES/EAGLDrawable.h>
#import <GLKit/GLKit.h>#import "debug.h"
#import "shaderUtil.h"
#import "fileUtil.h"//画笔透明度
#define kBrushOpacity  (1.0 / 2.0)
//画笔每一笔 有几个点
#define kBrushPiexlStep 3
//画笔的比例
#define kBrushScale     2enum {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 renderBufferGLuint viewFrameBuffer, viewRenderBuffer;//画笔纹理 画笔的颜色texureInfo_t brushTexture;GLfloat brushColor[4];//是否第一次点击Boolean firstTouch;//是否需要清屏Boolean needsErase;//顶点着色器 片元着色器 ProgramGLuint vertexShader;GLuint fragmentShader;GLuint shaderProgram;//顶点bufferGLuint vBoId;//是否初始化BOOL initialized;//点的数组NSMutableArray *pointArray;
}@end@implementation YZView//修改返回类的类型为CAEAGLLayer
+ (Class)layerClass {return [CAEAGLLayer class];
}- (instancetype)initWithCoder:(NSCoder *)coder {self = [super initWithCoder:coder];if (self) {//初始化layerCAEAGLLayer *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];//是否需要清屏,默认为yesneedsErase = 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 和 viewRenderBufferglBindFramebuffer(GL_FRAMEBUFFER, viewFrameBuffer);glBindRenderbuffer(GL_RENDERBUFFER, viewRenderBuffer);//绑定一个Drawable对象,存储到OpenGL渲染缓存对象上[context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(id<EAGLDrawable>)self.layer];//将viewRenderBuffer 绑定到GL_COLOR_ATTACHMENT0glFramebufferRenderbuffer(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开始遍历顶点,步长为2for (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;//顶点maxstatic 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++) {//判断如果顶点式 > 设置顶点maxif (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);//显示bufferglBindRenderbuffer(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);//attributeGLsizei 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",};//遍历attributefor (int j = 0; j < NUM_ATTRIBS; j++){//strstr(str1,str2) 函数用于判断字符串str2是否是str1的子串。如果是,则该函数返回str2在str1中首次出现的地址;否则,返回NULL。//判断,attribute 变量,是否存在顶点着色器程序中。point.vshif (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 == 0if (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个矩阵的结果交给MVPMatrixGLKMatrix4 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、contextif (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
{//获取绘制的boundsCGRect                bounds = [self bounds];//获取当前的点击touchUITouch*            touch = [[event touchesForView:self] anyObject];//设置为firstTouch -> yesfirstTouch = 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状态改为NOfirstTouch = 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 <Foundation/Foundation.h>
#import <AudioToolbox/AudioServices.h>NS_ASSUME_NONNULL_BEGIN@interface SoundEffect : NSObject {SystemSoundID _soundID;
}+ (id)soundEffectWithContentOfFile:(NSString *)path;- (id)initWithContentOfFile:(NSString *)path;- (void)play;@endNS_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.定义SystemSoundidSystemSoundID 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]];
}

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

06-初始OpenGL ES -用GLSL实现画板的功能相关推荐

  1. OpenGL ES之GLSL实现仿抖音“缩放”“灵魂出窍”“抖动”“闪白”“毛刺”“幻觉”等动态滤镜效果

    无滤镜效果 "动态滤镜"效果的实现准备工作的代码与"无分屏滤镜"中的实现逻辑和流程一致,只需要修改相应的底部item数组及对应的着色器名称等,这里不再说明这部分 ...

  2. OpenGL ES之GLSL实现仿抖音“灰度滤镜”和“颠倒滤镜”效果

    无滤镜 "无滤镜"效果的实现准备工作的代码与"无分屏滤镜"中的实现逻辑和流程一致,只需要修改相应的底部item数组及对应的着色器名称等,这里不再说明这部分内容, ...

  3. OpenGL ES之GLSL实现多种“马赛克滤镜”效果

    ⻢赛克效果 "⻢赛克效果"就是把图⽚的⼀个相当⼤⼩的区域⽤同⼀个点的颜⾊来表示,可以认为是⼤规模的降低图像的分辨率,⽽让图像的⼀些细节隐藏起来. 无马赛克滤镜 "无滤镜& ...

  4. OpenGL ES之GLSL渲染图片显示的整体流程

    整体思路 本文不采用UIKit的GLKBaseEffect渲染一张图片的显示,而是使用编译链接自定义的着色器(shader).用GLSL语言来实现自定义顶点/片元着色器,并将图形进行简单的渲染显示. ...

  5. OpenGL ES之GLSL实现仿抖音“分屏滤镜”效果

    无分屏滤镜 一.GLSL自定义着色器 Normal.vsh:顶点着色器 attribute vec4 Position; attribute vec2 TextureCoords; varying v ...

  6. OpenGL ES之GLSL实现索引绘制及渲染纹理和颜色混合

    渲染流程简介 一.基本图形硬件流水线设计 应用程序层:游戏和应用层软件开发人员为主体,通过调用API进行上层开发,不需要考虑移植性问题: 硬件抽象层:抽象出硬件的加速功能,进行有利于应用层开发的封装, ...

  7. OpenGL ES之GLSL常用内建函数

    dot 点乘 返回两个单位向量之间夹角的cos值 cross 叉乘 texture2D 用于纹理采样 normalize :对⼀个向量量规格化 clamp 将⼀个向量固定在一个最小值和最大值之间 po ...

  8. OpenGL ES之GLSL自定义着色器编程实现粒子效果

    效果展示 实现流程 一.自定义着色器 顶点着色器:YDWPointParticleShader.vsh // 位置 attribute vec3 a_emissionPosition; // 速度 a ...

  9. OpenGL ES之GLSL实现“瘦身大长腿”美颜滤镜效果

    一."大长腿"原理与流程 ① 第一次加载图片 第一次图片的加载是使用 GLKit 加载,利用自定义的 GLKView 视图,通过计算图片的顶点数据,绘制图片并显示到屏幕上,整体的流 ...

最新文章

  1. python零基础有用吗-零基础参加软件测试有用吗?老男孩Python培训班
  2. 仔细学习CSS(二)
  3. jQuery学习笔记之DOM操作、事件绑定(2)
  4. C#的winform的中委托显示图片
  5. 第十三期:消灭 Java 代码的“坏味道”
  6. 休眠后gpio状态_STM32中GPIO的8种工作模式总结
  7. 架构设计 | 高并发流量削峰,共享资源加锁机制
  8. 中国开放教育资源协会_教育中的开放数据开始显示出真正的吸引力
  9. 2017.5.9 寻找道路 思考记录
  10. linux下浏览器如何登录微信,js判断手机浏览器操作系统和微信浏览器的方法
  11. 转:关于数据库压缩技术的Survey
  12. ubuntu下mysql数据库存储路径修改
  13. libcurl官方手册
  14. vscode编写C++代码出现collect2.exe: error: ld returned 1 exit status问题的解决方案
  15. (python热门库之)PyQt5常用代码
  16. CVE-2020-25540:ThinkAdmin未授权列目录/任意文件读取漏洞复现
  17. HTML(常用标签与超链接)的案例
  18. 华容道源代码android,基于Android的华容道游戏的源代码
  19. vue中利用particlesJS实现鼠标动画粒子连线效果
  20. ffmpeg下载m3u8工具大全

热门文章

  1. 赋能新零售,Stratifyd亮相Smart Retail智慧零售年度峰会
  2. android 开源库osmdroid绘制点线面(比例尺,缩小放大,导航图标等)
  3. linux下刻录光盘读取不了_Linux操作系统下光盘刻录实战
  4. CF-940-F. Machine Learning【带修莫队】
  5. 百度前端实战训练营第二弹
  6. 31、什么是 BIO?
  7. unity 音乐节奏游戏_使用您当地音乐收藏的最佳节奏游戏
  8. 水滴公司:左手口碑,右手商业
  9. VS2005 制作安装程序
  10. 网页版式设计与平面构图