iOS開發(fā)-OpenGL ES畫圖應(yīng)用

這是一篇OpenGL ES的實(shí)戰(zhàn)吝梅,緊接 入門教程3
學(xué)了OpenGL ES一段時(shí)間,用這個(gè)應(yīng)用來練練手。

OpenGL ES系列教程在這里舵抹。
OpenGL ES系列教程的代碼地址 - 你的star和fork是我的源動(dòng)力,你的意見能讓我走得更遠(yuǎn)劣砍。

效果展示

實(shí)戰(zhàn).gif

demo來自蘋果官方惧蛹,可以學(xué)習(xí)蘋果的工程師如何應(yīng)用OpenGL ES。
這次的內(nèi)容包括刑枝,shader香嗓,CoreGraphics手勢識(shí)別装畅、運(yùn)動(dòng)軌跡靠娱、模糊點(diǎn)效果

shader

自定義enum洁灵,方便OC與shader之間的賦值饱岸,配合下面的自動(dòng)assign功能,非常便捷徽千。

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 {
 char *vert, *frag;
 GLint uniform[NUM_UNIFORMS];
 GLuint id;
} programInfo_t;

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

創(chuàng)建program的過程苫费,用glBindAttribLocation()glueGetUniformLocation ()來綁定attribute和uniform變量。

/* Convenience wrapper that compiles, links, enumerates uniforms and attribs */
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)
{
 GLuint vertShader = 0, fragShader = 0, prog = 0, status = 1, i;
 
 prog = glCreateProgram();

 status *= glueCompileShader(GL_VERTEX_SHADER, 1, &vertSource, &vertShader);
 status *= glueCompileShader(GL_FRAGMENT_SHADER, 1, &fragSource, &fragShader);
 glAttachShader(prog, vertShader);
 glAttachShader(prog, fragShader);
 
 for (i = 0; i < attribNameCt; i++)
 {
  if(strlen(attribNames[i]))
   glBindAttribLocation(prog, attribLocations[i], attribNames[i]);
 }
 
 status *= glueLinkProgram(prog);
 status *= glueValidateProgram(prog);

 if (status)
 { 
        for(i = 0; i < uniformNameCt; i++)
  {
            if(strlen(uniformNames[i]))
       uniformLocations[i] = glueGetUniformLocation(prog, uniformNames[i]);
  }
  *program = prog;
 }
 if (vertShader)
  glDeleteShader(vertShader);
 if (fragShader)
  glDeleteShader(fragShader);
 glError();
  
 return status;
}

shader的編譯之前困擾過我很久双抽,這個(gè)demo介紹了一種方法可以獲取編譯錯(cuò)誤信息百框,非常的nice。

#define glError() { \
 GLenum err = glGetError(); \
 if (err != GL_NO_ERROR) { \
  printf("glError: %04x caught at %s:%u\n", err, __FILE__, __LINE__); \
 } \
}

CoreGraphics

自定義textureInfo_t結(jié)構(gòu)體牍汹,在textureFromName()用CoreGraphics把url對(duì)應(yīng)的image data緩存到OpenGLES铐维,并通過textureInfo_t返回信息柬泽。

// Texture
typedef struct {
    GLuint id;
    GLsizei width, height;
} textureInfo_t;

// Create a texture from an image
- (textureInfo_t)textureFromName:(NSString *)name
{
    CGImageRef  brushImage;
 CGContextRef brushContext;
 GLubyte   *brushData;
 size_t   width, height;
    GLuint          texId;
    textureInfo_t   texture;
    
    // First create a UIImage object from the data in a image file, and then extract the Core Graphics image
    brushImage = [UIImage imageNamed:name].CGImage;
    
    // Get the width and height of the image
    width = CGImageGetWidth(brushImage);
    height = CGImageGetHeight(brushImage);
    
    // Make sure the image exists
    if(brushImage) {
        // Allocate  memory needed for the bitmap context
        brushData = (GLubyte *) calloc(width * height * 4, sizeof(GLubyte));
        // Use  the bitmatp creation function provided by the Core Graphics framework.
        brushContext = CGBitmapContextCreate(brushData, width, height, 8, width * 4, CGImageGetColorSpace(brushImage), kCGImageAlphaPremultipliedLast);
        // After you create the context, you can draw the  image to the context.
        CGContextDrawImage(brushContext, CGRectMake(0.0, 0.0, (CGFloat)width, (CGFloat)height), brushImage);
        // You don't need the context at this point, so you need to release it to avoid memory leaks.
        CGContextRelease(brushContext);
        // Use OpenGL ES to generate a name for the texture.
        glGenTextures(1, &texId);
        // Bind the texture name.
        glBindTexture(GL_TEXTURE_2D, texId);
        // Set the texture parameters to use a minifying filter and a linear filer (weighted average)
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
        // Specify a 2D texture image, providing the a pointer to the image data in memory
        glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, (int)width, (int)height, 0, GL_RGBA, GL_UNSIGNED_BYTE, brushData);
        // Release  the image data; it's no longer needed
        free(brushData);
        
        texture.id = texId;
        texture.width = (int)width;
        texture.height = (int)height;
    }
    
    return texture;
}

手勢識(shí)別

這里的手勢只有點(diǎn)擊和滑動(dòng),通過記錄touchesBegan嫁蛇,獲取第一個(gè)點(diǎn)的位置锨并,之后滑動(dòng)的過程中touchesMoved獲取到這次的位置和上次的位置,可以畫出一道手指滑動(dòng)的軌跡睬棚,通過renderLineFromPoint()繪制第煮。

// Handles the start of a touch
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{   
 CGRect    bounds = [self bounds];
 UITouch*   touch = [[event touchesForView:self] anyObject];
 firstTouch = YES;
 // Convert touch point from UIView referential to OpenGL one (upside-down flip)
 location = [touch locationInView:self];
 location.y = bounds.size.height - location.y;
}

// Handles the continuation of a touch.
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event
{   
 CGRect    bounds = [self bounds];
 UITouch*   touch = [[event touchesForView:self] anyObject];
  
 // Convert touch point from UIView referential to OpenGL one (upside-down flip)
 if (firstTouch) {
  firstTouch = NO;
  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;
 }
  
 // Render the stroke
    if (!lyArr) {
        lyArr = [NSMutableArray array];
    }
    [lyArr addObject:[[LYPoint alloc] initWithCGPoint:previousLocation]];
    [lyArr addObject:[[LYPoint alloc] initWithCGPoint:location]];

    
 [self renderLineFromPoint:previousLocation toPoint:location];
}

// Handles the end of a touch event when the touch is a tap.
- (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];
 }
}

// Handles the end of a touch event.
- (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event
{
 // If appropriate, add code necessary to save the state of the application.
 // This application is not saving state.
    NSLog(@"cancell");
}

運(yùn)動(dòng)軌跡

通過把起點(diǎn)到終點(diǎn)的軌跡分解成若干個(gè)點(diǎn),分別來繪制每個(gè)點(diǎn)抑党,從而達(dá)到線的效果包警。
count = MAX(ceilf(sqrtf((end.x - start.x) * (end.x - start.x) + (end.y - start.y) * (end.y - start.y)) / kBrushPixelStep), 1);
這行代碼是核心思想,分出count個(gè)點(diǎn)底靠。
然后通過glDrawArrays(GL_POINTS, 0, (int)vertexCount);繪制害晦。

// Drawings a line onscreen based on where the user touches
- (void)renderLineFromPoint:(CGPoint)start toPoint:(CGPoint)end
{
 static GLfloat*  vertexBuffer = NULL;
 static NSUInteger vertexMax = 64;
 NSUInteger   vertexCount = 0,
      count,
      i;
 
 [EAGLContext setCurrentContext:context];
 glBindFramebuffer(GL_FRAMEBUFFER, viewFramebuffer);
 
 // Convert locations from Points to Pixels
 CGFloat scale = self.contentScaleFactor;
 start.x *= scale;
 start.y *= scale;
 end.x *= scale;
 end.y *= scale;
 
 // Allocate vertex array buffer
 if(vertexBuffer == NULL)
  vertexBuffer = malloc(vertexMax * 2 * sizeof(GLfloat));
 
 // Add points to the buffer so there are drawing points every X pixels
 count = MAX(ceilf(sqrtf((end.x - start.x) * (end.x - start.x) + (end.y - start.y) * (end.y - start.y)) / kBrushPixelStep), 1);
 for(i = 0; i < count; ++i) {
  if(vertexCount == vertexMax) {
   vertexMax = 2 * vertexMax;
   vertexBuffer = realloc(vertexBuffer, vertexMax * 2 * sizeof(GLfloat));
  }
  
  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;
 }
    
 // Load data to the Vertex Buffer Object
 glBindBuffer(GL_ARRAY_BUFFER, vboId);
 glBufferData(GL_ARRAY_BUFFER, vertexCount*2*sizeof(GLfloat), vertexBuffer, GL_DYNAMIC_DRAW);
 
    glEnableVertexAttribArray(ATTRIB_VERTEX);
    glVertexAttribPointer(ATTRIB_VERTEX, 2, GL_FLOAT, GL_FALSE, 0, 0);
 
 // Draw
    glUseProgram(program[PROGRAM_POINT].id);
 glDrawArrays(GL_POINTS, 0, (int)vertexCount);
 
 // Display the buffer
 glBindRenderbuffer(GL_RENDERBUFFER, viewRenderbuffer);
 [context presentRenderbuffer:GL_RENDERBUFFER];
}

模糊點(diǎn)的效果

點(diǎn)模糊的效果通過開啟混合模式,并設(shè)置混合函數(shù)

// Enable blending and set a blending function appropriate for premultiplied alpha pixel data
    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA);

注意color和texture2D的操作符是*暑中,不是+壹瘟。

uniform sampler2D texture;
varying lowp vec4 color;

void main()
{
 gl_FragColor = color * texture2D(texture, gl_PointCoord);
}

最后

送上一張圖

畫圖.gif

附上源碼

思考題

  • 如何改動(dòng)開頭的加油?
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末痒芝,一起剝皮案震驚了整個(gè)濱河市俐筋,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌严衬,老刑警劉巖澄者,帶你破解...
    沈念sama閱讀 222,183評(píng)論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異请琳,居然都是意外死亡粱挡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,850評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門俄精,熙熙樓的掌柜王于貴愁眉苦臉地迎上來询筏,“玉大人,你說我怎么就攤上這事竖慧∠犹祝” “怎么了?”我有些...
    開封第一講書人閱讀 168,766評(píng)論 0 361
  • 文/不壞的土叔 我叫張陵圾旨,是天一觀的道長踱讨。 經(jīng)常有香客問我,道長砍的,這世上最難降的妖魔是什么痹筛? 我笑而不...
    開封第一講書人閱讀 59,854評(píng)論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上帚稠,老公的妹妹穿的比我還像新娘谣旁。我一直安慰自己,他們只是感情好滋早,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,871評(píng)論 6 398
  • 文/花漫 我一把揭開白布榄审。 她就那樣靜靜地躺著,像睡著了一般馆衔。 火紅的嫁衣襯著肌膚如雪瘟判。 梳的紋絲不亂的頭發(fā)上怨绣,一...
    開封第一講書人閱讀 52,457評(píng)論 1 311
  • 那天角溃,我揣著相機(jī)與錄音,去河邊找鬼篮撑。 笑死减细,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的赢笨。 我是一名探鬼主播未蝌,決...
    沈念sama閱讀 40,999評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼茧妒!你這毒婦竟也來了萧吠?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,914評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤桐筏,失蹤者是張志新(化名)和其女友劉穎纸型,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體梅忌,經(jīng)...
    沈念sama閱讀 46,465評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡狰腌,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,543評(píng)論 3 342
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了牧氮。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片琼腔。...
    茶點(diǎn)故事閱讀 40,675評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖踱葛,靈堂內(nèi)的尸體忽然破棺而出丹莲,到底是詐尸還是另有隱情,我是刑警寧澤尸诽,帶...
    沈念sama閱讀 36,354評(píng)論 5 351
  • 正文 年R本政府宣布甥材,位于F島的核電站,受9級(jí)特大地震影響逊谋,放射性物質(zhì)發(fā)生泄漏擂达。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,029評(píng)論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望板鬓。 院中可真熱鬧悲敷,春花似錦、人聲如沸俭令。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,514評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽抄腔。三九已至瓢湃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間赫蛇,已是汗流浹背绵患。 一陣腳步聲響...
    開封第一講書人閱讀 33,616評(píng)論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留悟耘,地道東北人落蝙。 一個(gè)月前我還...
    沈念sama閱讀 49,091評(píng)論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像暂幼,于是被迫代替她去往敵國和親筏勒。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,685評(píng)論 2 360

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