OpenGL ES 畫板案例實(shí)現(xiàn)

思維導(dǎo)圖

image.png

初始化View

-(id)initWithCoder:(NSCoder *)aDecoder
{
    if ((self = [super initWithCoder:aDecoder])) {
        
        //初始化CAEAGLLayer
        CAEAGLLayer *eaglLayer = (CAEAGLLayer *)self.layer;
        
        //設(shè)置透明度
        eaglLayer.opaque = YES;
        
        //設(shè)置eaglLayer描述屬性
        /*
         1.kEAGLDrawablePropertyRetainedBacking
           表示繪圖表面顯示后,是否保留其內(nèi)容史简,通過一個(gè)NSNumber 包裝一個(gè)bool值叔营。如果是NO,表示
         顯示內(nèi)容后埃儿,不能依賴于相同的內(nèi)容;如果是YES挂谍,表示顯示內(nèi)容后不變,一般只有在需要內(nèi)容保存不變的情況下才使用YES,設(shè)置為YES,會(huì)導(dǎo)致性能降低焕梅,內(nèi)存使用量降低。一般設(shè)置為NO卦洽。
         
         2.kEAGLDrawablePropertyColorFormat
            表示繪制表面的內(nèi)部顏色緩存區(qū)格式
         */
        eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:
                                        [NSNumber numberWithBool:YES], kEAGLDrawablePropertyRetainedBacking, kEAGLColorFormatRGBA8, kEAGLDrawablePropertyColorFormat, nil];
        
        //初始化上下文
        context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
        
        //判斷是否開辟成功以及設(shè)置到當(dāng)前的Context
        if (!context || ![EAGLContext setCurrentContext:context]) {
            return nil;
        }
        
        //設(shè)置視圖的比例因子
        /*
         比例因子決定視圖中的內(nèi)容如何從邏輯坐標(biāo)空間(以點(diǎn)測量)映射到設(shè)備坐標(biāo)空間(以像素為單位)贞言。此值通常為1或2。更高比例的因素表明視圖中的每一個(gè)點(diǎn)由底層的多個(gè)像素表示阀蒂。例如该窗,如果縮放因子為2,并且視圖框大小為50×50點(diǎn)蚤霞,則用于顯示內(nèi)容的位圖的大小為100×100像素酗失。
         */
        self.contentScaleFactor = [[UIScreen mainScreen] scale];
        
        //是否需要清屏,默認(rèn)等于YES
        needsErase = YES;
    }
    return self;
}

layoutSubviews

-(void)layoutSubviews
{
    
    [EAGLContext setCurrentContext:context];
    
    //判斷是否初始化
    if (!initialized) {
        //如果沒有初始化則對OpenGL初始化
        initialized = [self initGL];
    }
    else {
        //如果已經(jīng)初始化則調(diào)整layer
        [self resizeFromLayer:(CAEAGLLayer*)self.layer];
    }
    
    // 清除幀第一次分配
    if (needsErase) {
        [self erase];
        needsErase = NO;
    }
    
}

若沒有進(jìn)行過初始化

initialized 繪制"加油!"

initGL
-(BOOL)initGL
{
 
    //生成標(biāo)識一個(gè)幀緩存對象和顏色渲染
    glGenFramebuffers(1, &viewFrameBuffer);
    glGenRenderbuffers(1, &viewRenderBuffer);
    
    //綁定viewFrameBuffer 和 viewRenderBuffer
    glBindFramebuffer(GL_FRAMEBUFFER, viewFrameBuffer);
    glBindRenderbuffer(GL_RENDERBUFFER, viewRenderBuffer);
    
    //綁定一個(gè)Drawable對象存儲(chǔ)到一個(gè)OpenGL ES渲染緩存對象昧绣。
    /*
     創(chuàng)建一個(gè)渲染规肴,可以呈現(xiàn)到屏幕上,你將渲染然后分配共享存儲(chǔ)通過調(diào)用此方法夜畴。這個(gè)方法的調(diào)用替換通常給glrenderbufferstorage拖刃。緩存的存儲(chǔ)分配了這個(gè)方法以后可以顯示一個(gè)回調(diào)presentrenderbuffer:
      為繪制緩沖區(qū)分配存儲(chǔ)區(qū),此處將CAEAGLLayer的繪制存儲(chǔ)區(qū)作為繪制緩沖區(qū)的存儲(chǔ)區(qū)
     參數(shù)1:OpenGL ES的結(jié)合點(diǎn)為當(dāng)前綁定的渲染贪绘。這個(gè)參數(shù)的值必須gl_renderbuffer(或gl_renderbuffer_oes在OpenGL ES 1.1語境)
     參數(shù)2:對象管理數(shù)據(jù)存儲(chǔ)區(qū)中的渲染兑牡。在iOS中,這個(gè)參數(shù)的值必須是一個(gè)CAEAGLLayer對象
     
     */
    [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:(id<EAGLDrawable>)self.layer];
    
    //將viewRenderBuffer 綁定到GL_COLOR_ATTACHMENT0
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, viewRenderBuffer);
    
    //獲取繪制緩存區(qū)的像素寬度 --將繪制緩存區(qū)像素寬度存儲(chǔ)在backingWidth
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth);
    //獲取繪制緩存區(qū)的像素高度--將繪制緩存區(qū)像素高度存儲(chǔ)在backingHeight
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight);
    
    //檢查GL_FRAMEBUFFER緩存區(qū)狀態(tài)
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
        NSLog(@"Make complete framebuffer Object failed! %x",glCheckFramebufferStatus(GL_FRAMEBUFFER));
        return NO;
    }
    
    //設(shè)置視口
    glViewport(0, 0, backingWidth, backingHeight);
    
    //創(chuàng)建頂點(diǎn)緩沖對象來保存我們的數(shù)據(jù)
    glGenBuffers(1, &vboId);
    
    //加載畫筆紋理
    brushTexture = [self textureFromName:@"Particle.png"];
    
    //加載shade
    [self setupShaders];
    
    
    //點(diǎn)模糊效果通過開啟混合模式税灌,并設(shè)置混合函數(shù)
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);
    
    //回放錄制的路徑均函,這是“加油亿虽!”
    NSString *path = [[NSBundle mainBundle]pathForResource:@"abc" ofType:@"string"];
    //將path 使用NSUTF8StringEncoding 編碼
    NSString *str = [NSString stringWithContentsOfFile:path encoding:NSUTF8StringEncoding error:nil];
    
    //開辟數(shù)組空間-可變的
    CCArr = [NSMutableArray array];
    
    //根據(jù)abc.string文件,將繪制點(diǎn)的數(shù)據(jù)边酒,json解析到數(shù)組
    NSArray *jsonArr = [NSJSONSerialization JSONObjectWithData:[str dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingAllowFragments error:nil];
    
    //遍歷jsonArr數(shù)組经柴,將數(shù)據(jù)轉(zhuǎn)為CCPoint類型數(shù)據(jù)
    for (NSDictionary *dict in jsonArr) {
        CCPoint *point = [CCPoint new];
        point.mX = [dict objectForKey:@"mX"];
        point.mY = [dict objectForKey:@"mY"];
        
        //將CCPoint 對象添加到CCArr數(shù)組
        [CCArr addObject:point];
    }
    
    //調(diào)用繪制方法:繪制abc.string 繪制的加油字樣,延時(shí)5秒繪制墩朦!
    [self performSelector:@selector(paint) withObject:nil afterDelay:0.5];

    
    return YES;
   
}
手寫著色程序 vsh&fsh
  • vsh : 頂點(diǎn)著色器
//頂點(diǎn)
attribute vec4 inVertex;

//矩陣
uniform mat4 MVP;

//點(diǎn)的大小
uniform float pointSize;

//點(diǎn)的顏色
uniform lowp vec4 vertexColor;

//輸出顏色
varying lowp vec4 color;

void main()
{
    //頂點(diǎn)計(jì)算 = 矩陣 * 頂點(diǎn)
    gl_Position = MVP * inVertex;
    
    //修改頂點(diǎn)大小
    gl_PointSize = pointSize;
    //    1 * 3.0;
    
    //將通過uniform 傳遞進(jìn)來的顏色,從頂點(diǎn)著色器程序傳遞到片元著色器
    color = vertexColor;
}
  • fsh : 片元著色器
//獲取紋理
uniform sampler2D texture;
/*
 sampler2D,中的2D,表示這是一個(gè)2D紋理坯认。我們也可以使用1D\3D或者其他類型的采樣器。我們總是
 把這個(gè)值設(shè)置為0氓涣。來指示紋理單元0.
 */

//獲取從頂點(diǎn)程序傳遞過來的顏色
//lowp,精度
varying lowp vec4 color;

void main()
{
    //將顏色和紋理組合 是相乘E2浮!@头汀引润!
    gl_FragColor = color * texture2D(texture, gl_PointCoord);
}
加載shader

即加載vsh和fsh

- (void)setupShaders
{
    
    for (int i = 0; i < NUM_PROGRAMS; i++) {
        
        //讀取頂點(diǎn)著色程序
        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;
        //創(chuàng)建字符串?dāng)?shù)組【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) 函數(shù)用于判斷字符串str2是否是str1的子串。如果是痒玩,則該函數(shù)返回str2在str1中首次出現(xiàn)的地址淳附;否則,返回NULL蠢古。
            //判斷奴曙,attribute 變量,是否存在頂點(diǎn)著色器程序中草讶。point.vsh
            if (strstr(vsrc, attribName[j]))
            {
                //attribute個(gè)數(shù)
                attrib[attribCt] = j;
                //使用的attribute的名稱
                attribUsed[attribCt++] = attribName[j];
            }
        }
        
        //利用shaderUtil.c封裝好的方法對programe 進(jìn)行創(chuàng)建洽糟、鏈接、生成Programe
        /*
         參數(shù)1:vsrc,頂點(diǎn)著色器程序
         參數(shù)2:fsrc,片元著色器程序
         參數(shù)3:attribute變量個(gè)數(shù)
         參數(shù)4:attribute變量名稱
         參數(shù)5:當(dāng)前attribute位置
         參數(shù)6:uniform名字
         參數(shù)7:program的uniform地址
         參數(shù)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);
        
        // 設(shè)置常數(shù)堕战、初始化Uniform
        //當(dāng)前的i == 0
        if (i == PROGRAM_POINT)
        {
            //使用proram program[0].id 等價(jià)坤溃,以往課程例子中的GLuint program;
            glUseProgram(program[PROGRAM_POINT].id);
            
            
            //為當(dāng)前程序?qū)ο笾付╱niform變量值
            /*
             為當(dāng)前程序?qū)ο笾付╱niform變量MVP賦值
             
             void glUniform1f(GLint location,  GLfloat v0);
             參數(shù)1:location,指明要更改的uniform變量的位置 MVP
             參數(shù)2:v0,指明在指定的uniform變量中要使用的新值
             
             program[0].uniform[3] = 0
             等價(jià)于嘱丢,vsh頂點(diǎn)著色器程序中的uniform變量薪介,MVP = 0;
             其實(shí)簡單理解就是做了一次初始化,清空這個(gè)mat4矩陣
             */
            glUniform1i(program[PROGRAM_POINT].uniform[UNIFORM_TEXTURE], 0);
            
            // 投影矩陣
            /*
             投影分為正射投影和透視投影越驻,我們可以通過它來設(shè)置投影矩陣來設(shè)置視域昭灵,在OpenGL中,默認(rèn)的投影矩陣是一個(gè)立方體伐谈,即x y z 分別是-1.0~1.0的距離,如果超出該區(qū)域试疙,將不會(huì)被顯示
             
             正射投影(orthographic projection):GLKMatrix4MakeOrtho(float left, float righ, float bottom, float top, float nearZ, float farZ)诵棵,該函數(shù)返回一個(gè)正射投影的矩陣,它定義了一個(gè)由 left祝旷、right履澳、bottom嘶窄、top、near距贷、far 所界定的一個(gè)矩形視域柄冲。此時(shí),視點(diǎn)與每個(gè)位置之間的距離對于投影將毫無影響忠蝗。
             
             透視投影(perspective projection):GLKMatrix4MakeFrustum(float left, float right,float bottom, float top, float nearZ, float farZ)现横,該函數(shù)返回一個(gè)透視投影的矩陣,它定義了一個(gè)由 left阁最、right戒祠、bottom、top速种、near姜盈、far 所界定的一個(gè)平截頭體(椎體切去頂端之后的形狀)視域。此時(shí)配阵,視點(diǎn)與每個(gè)位置之間的距離越遠(yuǎn)馏颂,對象越小。
             
             在平面上繪制棋傍,只需要使正投影就可以了>壤!
             */
            GLKMatrix4 projectionMatrix = GLKMatrix4MakeOrtho(0, backingWidth, 0, backingHeight, -1, 1);
           
            //模型矩陣舍沙,比如你要平移近上、旋轉(zhuǎn)、縮放拂铡,就可以設(shè)置在模型矩陣上
            //這里不需要這些變換壹无,則使用單元矩陣即可,相當(dāng)于1 * 感帅? = 斗锭?
            GLKMatrix4 modelViewMatrix = GLKMatrix4Identity;
            
            //矩陣相乘让禀,就2個(gè)矩陣的結(jié)果交給MVPMatrix
            GLKMatrix4 MVPMatrix = GLKMatrix4Multiply(projectionMatrix, modelViewMatrix);
            /*
              void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
             功能:為當(dāng)前程序?qū)ο笾付╱niform變量值
             參數(shù)1:location 指明要更改的uniform變量的位置 MVP
             參數(shù)2:count 指定將要被修改的矩陣的數(shù)量
             參數(shù)3:transpose 矩陣的值被載入變量時(shí)免姿,是否要對矩陣進(jìn)行變換,比如轉(zhuǎn)置溜宽!
             參數(shù)4:value 实苞,指向?qū)⒁糜诟聈niform變量MVP的數(shù)組指針
             */
            glUniformMatrix4fv(program[PROGRAM_POINT].uniform[UNIFORM_MVP], 1, GL_FALSE, MVPMatrix.m);
            
            //點(diǎn)的大小 pointSize
            /*
              為當(dāng)前程序?qū)ο笾付╱niform變量pointSize賦值
               program[0].uniform[pointSize] = 紋理寬度/畫筆比例
             */
             glUniform1f(program[PROGRAM_POINT].uniform[UNIFORM_POINT_SIZE], brushTexture.width / kBrushScale);
            
            
            //筆刷顏色
            /*
             為當(dāng)前程序?qū)ο笾付╱niform變量vertexColor賦值
             program[0].uniform[vertexColor] = 畫筆顏色
             
             void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
             功能:為當(dāng)前程序?qū)ο笾付╱niform變量值
             參數(shù)1:location 指明要更改的uniform變量的位置 vertexColor
             參數(shù)2:count 指定將要被修改的4分量的數(shù)量
             參數(shù)3:value 豺撑,指向?qū)⒁糜诟聈niform變量vertexColor的值

             */
            glUniform4fv(program[PROGRAM_POINT].uniform[UNIFORM_VERTEX_COLOR], 1, brushColor);
            
        }
    }
    
    glError();
    
}
加載畫筆紋理
// 創(chuàng)建一個(gè)紋理圖片
- (textureInfo_t)textureFromName:(NSString *)name
{
    CGImageRef brushImage;
    CGContextRef brushContext;
    GLubyte *brushData;
    size_t width,height;
    GLuint texId;
    textureInfo_t texture;
    
    //首先建立在圖像文件的數(shù)據(jù)一個(gè)UIImage對象,然后提取核心圖形圖像
    brushImage = [UIImage imageNamed:name].CGImage;
    
    //獲取圖片的寬和高
    width = CGImageGetWidth(brushImage);
    height = CGImageGetHeight(brushImage);
    
    //分配位圖上下文所需的內(nèi)存
    brushData = (GLubyte *)calloc(width * height * 4, sizeof(GLubyte));
    
    //使用Core Graphics框架提供的bitmatp創(chuàng)造功能黔牵。
    /*
     CGContextRef CGBitmapContextCreate(
     void * data,
     size_t width,
     size_t height,
     size_t bitsPerComponent,
     size_t bytesPerRow,
     CGColorSpaceRef cg_nullable space,
     uint32_t bitmapInfo);
     
     Quartz創(chuàng)建一個(gè)位圖繪制環(huán)境聪轿,也就是位圖上下文。
     參數(shù)1:data,要渲染的繪制內(nèi)容的地址
     參數(shù)2:位圖的寬
     參數(shù)3:位圖的高
     參數(shù)4:內(nèi)存中像素的每個(gè)組件的位數(shù)猾浦,比如32位像素格式和RGB顏色空間陆错。一般設(shè)置為8
     參數(shù)5:位圖每一行占有比特?cái)?shù)
     參數(shù)5:顏色空間灯抛,通過CGImageGetColorSpace(圖片)獲取顏色空間
     參數(shù)6:顏色通道,RGBA = kCGImageAlphaPremultipliedLast
     
     */
    brushContext = CGBitmapContextCreate(brushData, width, height, 8, width * 4, CGImageGetColorSpace(brushImage), kCGImageAlphaPremultipliedLast);
    
    //創(chuàng)建完context之后音瓷,可以在context上繪制圖片
    /*
     void CGContextDrawImage(CGContextRef c, CGRect rect,
     CGImageRef image);
     參數(shù)1:位圖上下文
     參數(shù)2:繪制的frame
     參數(shù)3:繪制的圖片
     */
    CGContextDrawImage(brushContext, CGRectMake(0.0, 0.0f, (CGFloat)width, (CGFloat)height), brushImage);
    
    //接下來將不需要上下文对嚼,因此需要釋放它以避免內(nèi)存泄漏
    CGContextRelease(brushContext);
    
    //使用OpenGL ES生成紋理
    /*
     生成紋理的函數(shù)
     glGenTextures (GLsizei n, GLuint* textures)
     參數(shù)1:n,生成紋理個(gè)數(shù)
     參數(shù)2:存儲(chǔ)紋理索引的第一個(gè)元素指針
     */
    glGenTextures(1, &texId);
    
    //綁定紋理名稱 允許建立一個(gè)綁定到目標(biāo)紋理的有名稱的紋理。
    glBindTexture(GL_TEXTURE_2D, texId);
    
    //設(shè)置紋理參數(shù)使用縮小濾波器和線性濾波器(加權(quán)平均)--設(shè)置紋理屬性
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
    
    //指定2D紋理圖像绳慎,為內(nèi)存中的圖像數(shù)據(jù)提供一個(gè)指針纵竖。
    /*
     功能:生成2D紋理
     glTexImage2D (GLenum target, GLint level, GLint internalformat, GLsizei width, GLsizei height, GLint border, GLenum format, GLenum type, const GLvoid* pixels);
     參數(shù)1:target,紋理目標(biāo)偷线,因?yàn)槟闶褂玫氖莋lTexImage2D函數(shù)磨确,所以必須設(shè)置為GL_TEXTURE_2D
     參數(shù)2:level,0,基本圖像級別
     參數(shù)3:internalformat声邦,顏色組件乏奥;GL_RGBA,GL_ALPHA亥曹,GL_RGBA
     參數(shù)4:width,紋理圖像的寬度
     參數(shù)5:height,紋理圖像的高度
     參數(shù)6:border,紋理邊框的寬度,必須為0
     參數(shù)7:format,像素?cái)?shù)據(jù)的顏色格式邓了,可不與internalformat一致,可參考internalformat的值
     參數(shù)8:type,像素?cái)?shù)據(jù)類型媳瞪,GL_UNSIGNED_BYTE
     參數(shù)9:pixels骗炉,內(nèi)存中指向圖像數(shù)據(jù)的指針
     
     */
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (int)width, (int)height, 0, GL_RGBA, GL_UNSIGNED_BYTE, brushData);
    
    //生成紋理之后,即可釋放brushData數(shù)據(jù)
    free(brushData);
    
    //補(bǔ)充自己定義的texture結(jié)構(gòu)體中的內(nèi)容
    //紋理
    texture.id = texId;
    //紋理寬度
    texture.width = (int)width;
    //紋理高度
    texture.height = (int)height;
        
    //返回紋理對象數(shù)據(jù)
    return texture;
}
繪制已存儲(chǔ)頂點(diǎn)數(shù)據(jù)的"加油"
-(void)paint
{

    //從0開始遍歷頂點(diǎn)蛇受,步長為2
    /*
        為什么步長等于2?
        p1,p2,開始點(diǎn)句葵,結(jié)束點(diǎn)!
     */
    for (int i = 0; i < CCArr.count - 1; i+= 2) {
        
        //從CCArr數(shù)組中讀取頂點(diǎn) 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];
        
        
    }
    
}
在兩點(diǎn)之間繪制線條
//在用戶觸摸的地方繪制屏幕上的線條
-(void)renderLineFromPoint:(CGPoint)start toPoint:(CGPoint)end
{
    //頂點(diǎn)緩存區(qū)
    static GLfloat *vertexBuffer = NULL;
    
    //頂點(diǎn)Max(暫時(shí))
    static NSUInteger vertexMax = 64;
    
    //頂點(diǎn)個(gè)數(shù)
    NSUInteger vertexCount = 0,count;
    
    
    //從點(diǎn)到像素轉(zhuǎn)換
    //視圖的比例因子
    CGFloat scale = self.contentScaleFactor;
    //將每個(gè)頂點(diǎn)與scale 因子相乘
    start.x *= scale;
    start.y *= scale;
    
    end.x *= scale;
    end.y *= scale;
    
    //開辟數(shù)組緩存區(qū)
    if (vertexBuffer == NULL) {
        //開辟頂點(diǎn)地址空間
        vertexBuffer = malloc(vertexMax * 2 * sizeof(GLfloat));
    }
    
    /*
     通過把起點(diǎn)到終點(diǎn)的軌跡分解成若干個(gè)點(diǎn)兢仰,分別來繪制每個(gè)點(diǎn)乍丈,從而達(dá)到線的效果
     ceilf()向上取整。不是四舍五入把将,而是判斷后面有小數(shù)轻专,去掉小數(shù)部分,整數(shù)部分加1.
     如:123.456 => 124
        123.001 => 124
     
     */
    
    //向緩沖區(qū)添加點(diǎn)察蹲,所以每個(gè)像素都有繪圖點(diǎn)
    //求得start 和 end 2點(diǎn)間的距離
    float seq = sqrtf((end.x - start.x) * (end.x - start.x) + (end.y - start.y) * (end.y - start.y));
    
    /*
     向上取整请垛,求得距離要產(chǎn)生多少個(gè)點(diǎn)? 
     kBrushPixelStep,畫筆像素步長
     修改kBrushPixelStep 的值洽议,越大宗收,筆觸越細(xì);越小亚兄,筆觸越粗混稽!
     */
    NSInteger pointCount = ceilf(seq / kBrushPixelStep);
    
    //比較pointCount 是不是大于1,如果小于1,則count = 1,否則count = pointCount;
    count = MAX(pointCount, 1);
    
    //NSLog(@"Count = %ld",count);
    
    for (int i = 0; i < count; i++) {
        
        //判斷如果頂點(diǎn)數(shù) > 設(shè)置頂點(diǎn)Max
        if (vertexCount == vertexMax) {
           
            //修改vertexMax 2倍增長
            vertexMax = 2 *vertexMax;
            
            //增加空間開辟
            vertexBuffer = realloc(vertexBuffer, vertexMax * 2 *sizeof(GLfloat));
            
        }
        
        //修改vertexBuffer數(shù)組的值
        //將start 和 end 距離之間,計(jì)算出count個(gè)點(diǎn)荚坞,并存儲(chǔ)在vertexBuffer數(shù)組中
        //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) * ((GLfloat)i/(GLfloat)count);
        //vertextBuffer[1]->y
        vertexBuffer[2 * vertexCount + 1] = start.y + (end.y - start.y) * ((GLfloat)i/(GLfloat)count);
        
        /*
        NSLog(@"X:%f",vertexBuffer[2 * vertexCount]);
        NSLog(@"Y:%f",vertexBuffer[2 * vertexCount + 1]);
        */
        
        //vertexCount 自增1
        vertexCount += 1;
        
    }
    
    //加載數(shù)據(jù)到vertex Buffer對象中
    glBindBuffer(GL_ARRAY_BUFFER, vboId);
    //將cpu存儲(chǔ)的頂點(diǎn)數(shù)據(jù)->GPU中 復(fù)制頂點(diǎn)數(shù)組到緩沖中提供給OpenGL使用
    glBufferData(GL_ARRAY_BUFFER, vertexCount * 2 * sizeof(GLfloat), vertexBuffer, GL_DYNAMIC_DRAW);
    
    /*
     鏈接頂點(diǎn)屬性
     glEnableVertexAttribArray啟用指定屬性,才可在頂點(diǎn)著色器中訪問逐頂點(diǎn)的屬性數(shù)據(jù)
     參考課件:二菲盾、鏈接頂點(diǎn)屬性
     */
    glEnableVertexAttribArray(ATTRIB_VERTEX);
    
    glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, GL_FALSE, 2*sizeof(GLfloat), 0);
    
    //繪制
    //使用剛剛創(chuàng)建的program[0].id的program
    glUseProgram(program[PROGRAM_POINT].id);
    /*
     根據(jù)頂點(diǎn)繪制圖形颓影,
     參數(shù)1:繪制模型 連接線段,參考視覺班第一節(jié)課的課件
     參數(shù)2:起始點(diǎn)懒鉴,0
     參數(shù)3:頂點(diǎn)個(gè)數(shù)
     */
    glDrawArrays(GL_POINTS, 0, (int)vertexCount);
    
    //顯示buffer
    glBindRenderbuffer(GL_RENDERBUFFER, viewRenderBuffer);
    [context presentRenderbuffer:GL_RENDERBUFFER];
    
}

已經(jīng)進(jìn)行過初始化

調(diào)整圖層

//調(diào)整圖層
- (BOOL)resizeFromLayer:(CAEAGLLayer *)layer
{
    
    //根據(jù)當(dāng)前圖層大小分配顏色緩沖區(qū)
    glBindRenderbuffer(GL_RENDERBUFFER, viewRenderBuffer);
    
    //綁定一個(gè)Drawable對象存儲(chǔ)到一個(gè)OpenGL ES渲染緩存對象诡挂。
    /*
     創(chuàng)建一個(gè)渲染,可以呈現(xiàn)到屏幕上临谱,你將渲染然后分配共享存儲(chǔ)通過調(diào)用此方法璃俗。這個(gè)方法的調(diào)用替換通常給glrenderbufferstorage。緩存的存儲(chǔ)分配了這個(gè)方法以后可以顯示一個(gè)回調(diào)presentrenderbuffer:
     - (BOOL)renderbufferStorage:(NSUInteger)target fromDrawable:(id<EAGLDrawable>)drawable;
     為繪制緩沖區(qū)分配存儲(chǔ)區(qū)悉默,此處將CAEAGLLayer的繪制存儲(chǔ)區(qū)作為繪制緩沖區(qū)的存儲(chǔ)區(qū)
     參數(shù)1:OpenGL ES的結(jié)合點(diǎn)為當(dāng)前綁定的渲染城豁。這個(gè)參數(shù)的值必須gl_renderbuffer(或gl_renderbuffer_oes在OpenGL ES 1.1語境)
     參數(shù)2:對象管理數(shù)據(jù)存儲(chǔ)區(qū)中的渲染。在iOS中抄课,這個(gè)參數(shù)的值必須是一個(gè)CAEAGLLayer對象
     
     */

    [context renderbufferStorage:GL_RENDERBUFFER fromDrawable:layer];
    
    ////獲取繪制緩存區(qū)的像素寬度 --將繪制緩存區(qū)像素寬度存儲(chǔ)在backingWidth
    glGetRenderbufferParameteriv(GL_RENDERBUFFER,GL_RENDERBUFFER_WIDTH, &backingWidth);
    
    ////獲取繪制緩存區(qū)的像素高度--將繪制緩存區(qū)像素高度存儲(chǔ)在backingHeight
    glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight);
    
    //檢查GL_FRAMEBUFFER緩存區(qū)狀態(tài)
    if (glCheckFramebufferStatus(GL_FRAMEBUFFER) != GL_FRAMEBUFFER_COMPLETE) {
        
        NSLog(@"Make compelete framebuffer object failed!%x",glCheckFramebufferStatus(GL_FRAMEBUFFER));
        return NO;
    }
    
    //更新投影矩陣唱星、模型視圖矩陣
    // 投影矩陣
    /*
     投影分為正射投影和透視投影,我們可以通過它來設(shè)置投影矩陣來設(shè)置視域跟磨,在OpenGL中间聊,默認(rèn)的投影矩陣是一個(gè)立方體,即x y z 分別是-1.0~1.0的距離抵拘,如果超出該區(qū)域哎榴,將不會(huì)被顯示
     
     正射投影(orthographic projection):GLKMatrix4MakeOrtho(float left, float righ, float bottom, float top, float nearZ, float farZ),該函數(shù)返回一個(gè)正射投影的矩陣僵蛛,它定義了一個(gè)由 left尚蝌、right、bottom墩瞳、top驼壶、near、far 所界定的一個(gè)矩形視域喉酌。此時(shí)热凹,視點(diǎn)與每個(gè)位置之間的距離對于投影將毫無影響。
     
     透視投影(perspective projection):GLKMatrix4MakeFrustum(float left, float right,float bottom, float top, float nearZ, float farZ)泪电,該函數(shù)返回一個(gè)透視投影的矩陣般妙,它定義了一個(gè)由 left、right相速、bottom碟渺、top、near突诬、far 所界定的一個(gè)平截頭體(椎體切去頂端之后的形狀)視域苫拍。此時(shí)芜繁,視點(diǎn)與每個(gè)位置之間的距離越遠(yuǎn),對象越小绒极。
     
     在平面上繪制骏令,只需要使正投影就可以了!垄提!
     */
    GLKMatrix4 projectionMatrix = GLKMatrix4MakeOrtho(0, backingWidth, 0, backingHeight, -1, 1);
    
    // //模型矩陣榔袋,比如你要平移、旋轉(zhuǎn)铡俐、縮放凰兑,就可以設(shè)置在模型矩陣上
    //這里不需要這些變換,則使用單元矩陣即可审丘,相當(dāng)于1 * 吏够? = ?
    GLKMatrix4 modelViewMatrix = GLKMatrix4Identity;
    
    //矩陣相乘备恤,就2個(gè)矩陣的結(jié)果交給MVPMatrix
    GLKMatrix4 MVPMatrix = GLKMatrix4Multiply(projectionMatrix, modelViewMatrix);
    

    /*
     void glUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat* value);
     功能:為當(dāng)前程序?qū)ο笾付╱niform變量值
     參數(shù)1:location 指明要更改的uniform變量的位置 MVP
     參數(shù)2:count 指定將要被修改的矩陣的數(shù)量
     參數(shù)3:transpose 矩陣的值被載入變量時(shí)稿饰,是否要對矩陣進(jìn)行變換,比如轉(zhuǎn)置露泊!
     參數(shù)4:value 喉镰,指向?qū)⒁糜诟聈niform變量MVP的數(shù)組指針
     */
    glUniformMatrix4fv(program[PROGRAM_POINT].uniform[UNIFORM_MVP], 1, GL_FALSE, MVPMatrix.m);
    
    //更新視口
    glViewport(0, 0, backingWidth, backingHeight);
    
    return YES;
    
}

修改畫筆顏色

- (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;
    
    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);
        
    }
    
}

清理屏幕

//清空屏幕
-(void)erase
{
    //clear frameBuffer
    glBindFramebuffer(GL_FRAMEBUFFER, viewFrameBuffer);
    glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
    glClear(GL_COLOR_BUFFER_BIT);
    
    //顯示緩存區(qū)
    glBindRenderbuffer(GL_RENDERBUFFER, viewRenderBuffer);
    [context presentRenderbuffer:GL_RENDERBUFFER];
    

}

開始繪制

繪制任意圖形,線條

#pragma mark -- Touch Click
//點(diǎn)擊屏幕開始
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
    //獲取繪制的bounds
    CGRect              bounds = [self bounds];
    //獲取當(dāng)前的點(diǎn)擊touch
    UITouch*            touch = [[event touchesForView:self] anyObject];
    //設(shè)置為firstTouch -> yes
    firstTouch = YES;
    
    //獲取當(dāng)前點(diǎn)擊的位置信息,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];
    
    //第一次點(diǎn)擊
    if (firstTouch) {
        //將firstTouch狀態(tài)改為NO
        firstTouch = NO;
        //_previousLocation = 獲取上一個(gè)頂點(diǎn)
        _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個(gè)頂點(diǎn)惭笑,繪制成線條
    [self renderLineFromPoint:_previousLocation toPoint:_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 renderLineFromPoint:_previousLocation toPoint:_location];
    }    
}

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

-(BOOL)canBecomeFirstResponder
{
    return YES;
}

效果

初始化
image.png
自行隨意繪制
image.png
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末侣姆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子沉噩,更是在濱河造成了極大的恐慌捺宗,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件川蒙,死亡現(xiàn)場離奇詭異蚜厉,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)畜眨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進(jìn)店門昼牛,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人康聂,你說我怎么就攤上這事贰健。” “怎么了恬汁?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵伶椿,是天一觀的道長。 經(jīng)常有香客問我,道長脊另,這世上最難降的妖魔是什么导狡? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮偎痛,結(jié)果婚禮上烘豌,老公的妹妹穿的比我還像新娘。我一直安慰自己看彼,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布囚聚。 她就那樣靜靜地躺著靖榕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪顽铸。 梳的紋絲不亂的頭發(fā)上茁计,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天,我揣著相機(jī)與錄音谓松,去河邊找鬼星压。 笑死,一個(gè)胖子當(dāng)著我的面吹牛鬼譬,可吹牛的內(nèi)容都是我干的娜膘。 我是一名探鬼主播,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼优质,長吁一口氣:“原來是場噩夢啊……” “哼竣贪!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起巩螃,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤演怎,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后避乏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體爷耀,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年拍皮,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了歹叮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,137評論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡春缕,死狀恐怖盗胀,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情锄贼,我是刑警寧澤票灰,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響屑迂,放射性物質(zhì)發(fā)生泄漏浸策。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一惹盼、第九天 我趴在偏房一處隱蔽的房頂上張望庸汗。 院中可真熱鬧,春花似錦手报、人聲如沸蚯舱。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽枉昏。三九已至,卻和暖如春揍鸟,著一層夾襖步出監(jiān)牢的瞬間兄裂,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工阳藻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留晰奖,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓腥泥,卻偏偏與公主長得像匾南,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子蛔外,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,901評論 2 345

推薦閱讀更多精彩內(nèi)容