效果
案例思路
核心代码
1.创建图层
-(id)initWithCoder:(NSCoder *)aDecoder
{
if (self = [super initWithCoder:aDecoder]) {
//初始化CAEAGLLayer
CAEAGLLayer *eagLayer = (CAEAGLLayer *)self.layer;
//设置透明度
eagLayer.opaque = YES;
//设置eaglLayer描述属性
/*
1.kEAGLDrawablePropertyRetainedBacking
表示绘图表面显示后,是否保留其内容,通过一个NSNumber 包装一个bool值。如果是NO,表示
显示内容后,不能依赖于相同的内容;如果是YES,表示显示内容后不变,一般只有在需要内容保存不变的情况下才使用YES,设置为YES,会导致性能降低,内存使用量降低。一般设置为NO。
2.kEAGLDrawablePropertyColorFormat
表示绘制表面的内部颜色缓存区格式
*/
eagLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO],kEAGLDrawablePropertyRetainedBacking,kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat, nil];
//初始化上下文
context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
//判断是否开辟成功以及设置到当前的context
if (!context ||![EAGLContext setCurrentContext:context]) {
return nil;
}
//设置视图比例因子
/*
比例因子决定视图中的内容如何从逻辑坐标空间(以点测量)映射到设备坐标空间(以像素为单位)。此值通常为1或2。更高比例的因素表明视图中的每一个点由底层的多个像素表示。例如,如果缩放因子为2,并且视图框大小为50×50点,则用于显示内容的位图的大小为100×100像素。
*/
self.contentScaleFactor = [UIScreen mainScreen].scale;
//是否需要清屏,默认等于YES
needsErase = YES;
}
return self;
}
2.判断是否要初始化
//CCView layOut
-(void)layoutSubviews
{
[EAGLContext setCurrentContext:context];
//判断是否初始化
if (!initialized) {
//如果没有初始化则对OpenGL初始化
initialized = [self initGL];
}else
{
//如果已经初始化则调整layer
[self resizeFromLayer:(CAEAGLLayer *)self.layer];
}
//清除第一次分配
if (needsErase) {
[self erase];
needsErase = NO;
}
}
3.初始化并绘制加油字样
-(BOOL)initGL
{
//生成表示一个帧缓存对象和颜色渲染
glGenFramebuffers(1, &viewFrameBuffer);
glGenRenderbuffers(1, &viewRenderBuffer);
//绑定viewFrameBuffer 和viewRenderBuffer
glBindFramebuffer(GL_FRAMEBUFFER, viewFrameBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, viewRenderBuffer);
//绑定一个Drawable对象储存到一个OpenGL ES渲染缓存对象
/*
创建一个渲染,可以呈现到屏幕上,你将渲染然后分配共享存储通过调用此方法。这个方法的调用替换通常给glrenderbufferstorage。缓存的存储分配了这个方法以后可以显示一个回调presentrenderbuffer:
为绘制缓冲区分配存储区,此处将CAEAGLLayer的绘制存储区作为绘制缓冲区的存储区
参数1:OpenGL ES的结合点为当前绑定的渲染。这个参数的值必须gl_renderbuffer(或gl_renderbuffer_oes在OpenGL ES 1.1语境)
参数2:对象管理数据存储区中的渲染。在iOS中,这个参数的值必须是一个CAEAGLLayer对象
*/
[context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(id)self.layer];
//将viewRenderBuffer 绑定到GL_COLOR_ATTACHMENT0, GL_FRAMEBUFFER 附着在GL_RENDERBUFFER的颜色附着点0点上,放在viewRenderBuffer
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 comlete frameBuffer Object faild %X !", glCheckFramebufferStatus(GL_FRAMEBUFFER));
return NO;
}
//设置视口
glViewport(0, 0, backingWidth, backingHeight);
//创建顶点缓冲对象来保存我们的对象
glGenBuffers(1, &vboId);
//加载画笔纹理
brushTexture = [self textureFromName:@"Particle.png"];
//加载着色器
[self setupShaders];
//点模糊效果通过开启混合模式,并设置混合函数
glEnable(GL_BLEND);
glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); //参考之前的混合函数表文章
//回放录制的路径,这是“加油”
NSString *path = [[NSBundle mainBundle]pathForResource:@"abc" ofType:@"string"];
NSString *str = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
//开辟数据空间-可变的
CCArr = [NSMutableArray array];
//根据abc.string文件,将绘制点的数据。json解析到数组
NSArray *jsonArray = [NSJSONSerialization JSONObjectWithData:[str dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingAllowFragments error:nil];
for (NSDictionary *dict in jsonArray) {
CCPoint *point = [CCPoint new];
point.mX = [dict objectForKey:@"mX"];
point.mY = [dict objectForKey:@"mY"];
//将CCPoint 对象添加到CCArr数组
[CCArr addObject:point];
}
//调用绘制方法:绘制abc.string 绘制的加油字样,延时5秒绘制
[self performSelector:@selector(paint) withObject:nil afterDelay:0.5];
return YES;
}
//绘制加油字样
-(void)paint
{
//从0开始遍历顶点,步长为2
//p1,开始点,p2,结束点
for (int i = 0; i < CCArr.count - 1; i+=2) {
//从CCArr数组中读取顶点cp1,cp2
CCPoint *cp1 = CCArr[i];
CCPoint *cp2 = CCArr[i + 1];
//将CCPoint对象 -> CGPoint对象
CGPoint p1,p2;
p1.x = cp1.mX.floatValue;
p2.x = cp2.mX.floatValue;
p1.y = cp1.mY.floatValue;
p2.y = cp2.mY.floatValue;
//在用户触摸的地方绘制上的线条
[self renderLineFromPoint:p1 toPoint:p2];
}
}
4.手写顶点着色器程序point.vsh
//顶点
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;
}
5.手写片元着色器程序point.fsh
// 获取纹理
/**
sampler2D中的2D,表示这是一个2D纹理。我们也可以使用1D/3D或者其他类型的采样器。我们总是
把这个值设置为0。来指示纹理单元0.
*/
uniform sampler2D texture;
//获取从顶点程序传递过来的颜色
//lowp.精度
//输出颜色
varying lowp vec4 color;
void main()
{
//将颜色和纹理组合是相乘
gl_FragColor = color * texture2D(texture, gl_PointCoord);
}
6.加载shader
- (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
/**
GLint glueCreateProgram(const GLchar *vertSource, const GLchar *fragSource,
GLsizei attribNameCt, const GLchar **attribNames,
const GLint *attribLocations,
GLsizei uniformNameCt, const GLchar **uniformNames,
GLint *uniformLocations,
GLuint *program)
*/
/*
参数1:vsrc,顶点着色器程序
参数2:fsrc,片元着色器程序
参数3:attribute变量个数
参数4:attribute变量名称
参数5:当前attribute位置
参数6: uniform个数
参数7:uniform名字
参数8:program的uniform地址
参数9: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);
//设置常数
//当前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);
//笔刷颜色
/*
为当前程序对象指定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();
}
7.通过两点绘制线条
//在用户触摸的地方绘制屏幕上的线条
-(void)renderLineFromPoint:(CGPoint)start toPoint:(CGPoint)end
{
//顶点缓存区
static GLfloat *vertexBuffer = NULL;
//顶点Max(暂时)
static NSUInteger vertextMax = 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(vertextMax * 2 * sizeof(GLfloat));
}
/*
通过把起点到终点的轨迹分解成若干个点,分别来绘制每个点,从而达到线的效果
ceilf()向上取整。不是四舍五入,而是判断后面有小数,去掉小数部分,整数部分加1.
如:123.456 => 124
123.001 => 124
*/
//向缓冲区添加点,所以每个像素都有绘图点
//求得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 = ceilf(seq / kBrushPixelStep);
//比较pointCount 是不是大于1,如果小于1,则count = 1,否则count = pointCount;
count = MAX(pointCount, 1);
for (int i = 0; i < count; i++) {
//判断如果顶点数 > 设置顶点Max
if (vertexCount == vertextMax) {
//修改vertexMax 2倍增长
vertextMax = 2 * vertextMax;
//增加空间开辟
vertexBuffer = realloc(vertexBuffer, vertextMax * 2 * sizeof(GLfloat));
}
//修改vertexBuffer数组的值
//将start 和 end 距离之间,计算出count个点,并存储在vertexBuffer数组中
//x = start.x + (end.x - start.x) * (i/count);
//y = start.y + (end.y - start.y) * (i/count);
//vertexBuffer[0]->x
vertexBuffer[2 * vertexCount + 0] = start.x + (end.x - start.x) * ((CGFloat)i/(GLfloat)count);
//vertextBuffer[1]->y
vertexBuffer[2 * vertexCount + 1] = start.y + (end.y - start.y) * ((CGFloat)i/(CGFloat)count);
NSLog(@"X:%f",vertexBuffer[2 * vertexCount]);
NSLog(@"Y:%f",vertexBuffer[2 * vertexCount + 1]);
// vertexCount 自增1
vertexCount += 1;
}
//加载数据到vertexBuffer 对象中
glBindBuffer(GL_ARRAY_BUFFER, vboId);
//将cpu存储的顶点数据-cpu中 复制顶点数据到缓冲中提供给OpenGL使用
glBufferData(GL_ARRAY_BUFFER, vertexCount * 2 * sizeof(GLfloat), vertexBuffer, GL_DYNAMIC_DRAW);
/**
连接顶点属性
glEnableVertexAttribArray启用指定属性,才可在顶点着色器中访问逐顶点的属性数据
*/
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_RENDERBUFFER, viewRenderBuffer);
[context presentRenderbuffer:GL_RENDERBUFFER];
}
8.用户手势处理
#pragma mark -- Touch Click
//点击屏幕开始
-(void)touchesBegan:(NSSet *)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 *)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 renderLineFromPoint:_previousLocation toPoint:_location];
}
-(void)touchesEnded:(NSSet *)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 renderLineFromPoint:_previousLocation toPoint:_location];
}
}
-(void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(@"Touch Cancelled");
}
10.加载画笔纹理
// 创建一个纹理图片
- (textureInfo_t)textureFromName:(NSString *)name
{
CGImageRef brushImage;
CGContextRef brushContext;
GLubyte *brushData;
size_t width,height;
GLuint textId;
textureInfo_t texture;
//首先建立在图像文件的额数据一个UIImage对象,然后提取核心图形图像
brushImage = [UIImage imageNamed:name].CGImage;
//获取图片的宽高
width = CGImageGetWidth(brushImage);
height = CGImageGetHeight(brushImage);
//分配位图上下文所需的内存
/**
参数一:图标的内存大小,宽*高*4,4为rbga
参数二:存储类型,无符号
*/
brushData = (GLubyte *)calloc(width * height * 4 , sizeof(GLubyte));
//使用Core Graphics框架提供的bitmatp创造功能。
/*
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上绘制图片
/*
void CGContextDrawImage(CGContextRef c, CGRect rect,
CGImageRef image);
参数1:位图上下文
参数2:绘制的frame
参数3:绘制的图片
*/
CGContextDrawImage(brushContext, CGRectMake(0.0, 0.0f, (CGFloat)width, (CGFloat)height), brushImage);
//接下来将不需要上下文,因此需要释放它以避免内存泄漏
CGContextRelease(brushContext);
//使用OpenGL ES生成纹理
/*
生成纹理的函数
glGenTextures (GLsizei n, GLuint* textures)
参数1:n,生成纹理个数
参数2:存储纹理索引的第一个元素指针
*/
glGenTextures(1, &textId);
//绑定纹理名称,允许建立一个绑定到目标纹理的有名称的纹理
glBindTexture(GL_TEXTURE_2D, textId);
//设置纹理参数使用缩小滤波器和线性滤波器(加权平均)--设置纹理属性
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
//指定2D纹理图像,为内存中的图像数据提供一个指针。
/*
功能:生成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);
//补充自己定义的texture结构体中的内容
//纹理
texture.id = textId;
//纹理宽度
texture.width = (int)width;
//纹理高度
texture.height = (int)height;
//返回纹理对象数据
return texture;
}
11.设置画笔颜色
- (void)setBrushColorWithRed:(CGFloat)red green:(CGFloat)green blue:(CGFloat)blue
{
//更新画笔颜色 颜色 * 透明度
brushColor[0] = red * kBrushOpacity;
brushColor[1] = green * kBrushOpacity;
brushColor[2] = blue * kBrushOpacity;
brushColor[3] = kBrushOpacity;
NSLog(@"%f,%f,%f,%f",brushColor[0],brushColor[1],brushColor[2],brushColor[3]);
NSLog(@"%f,%f,%f",red,green,blue);
//释放初始化
if (initialized) {
//使用program[0].id
glUseProgram(program[PROGRAM_POINT].id);
//将颜色值brushColor 传递到 vertexColor中
glUniform4fv(program[PROGRAM_POINT].uniform[UNIFORM_VERTEX_COLOR], 1, brushColor);
}
}