IOS开发入门之OpenGL ES2.0 – Iphone开发指引
白羽 2019-01-23 来源 :网络 阅读 433 评论 0

摘要:本文将带你了解IOS开发入门OpenGL ES2.0 – Iphone开发指引,希望本文对大家学IOS有所帮助。

    本文将带你了解IOS开发入门OpenGL ES2.0 – Iphone开发指引,希望本文对大家学IOS有所帮助。


OpenGL  ES2.0 – Iphone开发指引,
     OpenGL ES是可以在iphone上实现2D和3D图形编程的低级API。
     如果你之前接触过cocos2d,sparrow,corona,unity这些框架,你会发现其实它们都是基于OpenGL上创建的。
     多数程序员选择使用这些框架,而不是直接调用OpenGL,因为OpenGL实在是太难用了。
     而这篇教程,就是为了让大家更好地入门而写的。
     在这个系列的文章中,你可以通过一些实用又容易上手的实验,创建类似hello world的APP。例如显示一些简单的立体图形。
     流程大致如下:
       ·创建一个简单的OpenGL app
       ·编译并运行vertex & fragment shaders
       ·通过vertex buffer,在屏幕上渲染一个简单矩形
       ·使用投影和model-view变形。
       ·渲染一个可以depth testing的3D对象。
     说明:
       我并非OpenGL的专家,这些完全是通过自学得来的。如果大家发现哪些不对的地方,欢迎指出。
   OpenGL ES1.0和OpenGL ES2.0
     第一件你需要搞清楚的事,是OpenGL ES 1.0和2.0的区别。
     他们有多不一样?我只能说他们很不一样。
   
   OpenGL ES1.0:
     针对固定管线硬件(fixed pipeline),通过它内建的functions来设置诸如灯光、,vertexes(图形的顶点数),颜色、camera等等的东西。
   OpenGL ES2.0:
     针对可编程管线硬件(programmable  pipeline),基于这个设计可以让内建函数见鬼去吧,但同时,你得自己动手编写任何功能。
     “TMD”,你可能会这么想。这样子我还可能想用2.0么?
     但2.0确实能做一些很cool而1.0不能做的事情,譬如:toon shader(贴材质).
   
     利用opengles2.0,甚至还能创建下面的这种很酷的灯光和阴影效果:
   
     OpenGL ES2.0只能够在iphone 3GS+、iPod Touch  3G+和所有版本的ipad上运行。庆幸现在大多数用户都在这个范围。
   开始吧
     尽管Xcode自带了OpenGL ES的项目模板,但这个模板自行创建了大量的代码,这样会让初学者感到迷惘。
     因此我们通过自行编写的方式来进行,通过一步一步编写,你能更清楚它的工作机制。
     启动Xcode,新建项目-选择Window-based Application,让我们从零开始。
     点击下一步,把这个项目命名为HelloOpenGL,点击下一步,选择存放目录,点击“创建”。
     CMD+R,build and run。你会看到一个空白的屏幕。
   
     如你所见的,Window-based模板创建了一个没有view、没有view  controller或者其它东西的项目。它只包含了一个必须的UIWindow。
     File/New File,新建文件:选择iOS\Cocoa Touch\Objective-c Class,点击下一步。
     选择subclass UIView,点击下一步,命名为OpenGLView.m.,点击保存。
     接下来,你要在这个OpenGLView.m文件下加入很多代码。
   1)添加必须的framework(框架)
   
     加入:OpenGLES.frameworks和QuartzCore.framework
     在项目的Groups&Files目录下,选择target“HelloOpenGL”,展开Link Binary with  Libraries部分。这里是项目用到的框架。
     “+”添加,选择OpenGLES.framework,重复一次把QuartzCore.framework也添加进来。
   2)修改OpenGLView.h
     如下:引入OpenGL的Header,创建一些后面会用到的实例变量。
   
  #import#import  #include#include  @interface OpenGLView : UIView  {    CAEAGLLayer*  _eaglLayer;    EAGLContext*  _context;    GLuint  _colorRenderBuffer;}  @end
   
   3)设置layer class为CAEAGLLayer
   
   + (Class)layerClass {    return [CAEAGLLayer  class];}
   
     想要显示OpenGL的内容,你需要把它缺省的layer设置为一个特殊的layer。(CAEAGLLayer)。这里通过直接复写layerClass的方法。
   4)设置layer为不透明(Opaque)
   
   - (void)setupLayer {    _eaglLayer =  (CAEAGLLayer*) self.layer;    _eaglLayer.opaque =  YES;}
   
     因为缺省的话,CALayer是透明的。而透明的层对性能负荷很大,特别是OpenGL的层。
     (如果可能,尽量都把层设置为不透明。另一个比较明显的例子是自定义tableview cell)
   5)创建OpenGL context
   
  - (void)setupContext {        EAGLRenderingAPI api =  kEAGLRenderingAPIOpenGLES2;    _context = [[EAGLContext  alloc] initWithAPI:api];    if (!_context)  {        NSLog(@"Failed to  initialize OpenGLES 2.0  context");        exit(1);    }      if  (![EAGLContext setCurrentContext:_context])  {        NSLog(@"Failed to set  current OpenGL  context");        exit(1);    }}
   
     无论你要OpenGL帮你实现什么,总需要这个EAGLContext。
     EAGLContext管理所有通过OpenGL进行draw的信息。这个与Core Graphics context类似。
     当你创建一个context,你要声明你要用哪个version的API。这里,我们选择OpenGL ES 2.0.
     (容错处理,如果创建失败了,我们的程序会退出)
   6)创建render buffer(渲染缓冲区)
   
   - (void)setupRenderBuffer  {    glGenRenderbuffers(1,  &_colorRenderBuffer);    glBindRenderbuffer(GL_RENDERBUFFER,  _colorRenderBuffer);             [_context renderbufferStorage:GL_RENDERBUFFER  fromDrawable:_eaglLayer];    }
   
     Render buffer是OpenGL的一个对象,用于存放渲染过的图像。
     有时候你会发现render buffer会作为一个color buffer被引用,因为本质上它就是存放用于显示的颜色。
     创建render buffer的三步:
    1.调用glGenRenderbuffers来创建一个新的render buffer  object。这里返回一个唯一的integer来标记render  buffer(这里把这个唯一值赋值到_colorRenderBuffer)。有时候你会发现这个唯一值被用来作为程序内的一个OpenGL的名称。(反正它唯一嘛)
    2.调用glBindRenderbuffer,告诉这个OpenGL:我在后面引用GL_RENDERBUFFER的地方,其实是想用_colorRenderBuffer。其实就是告诉OpenGL,我们定义的buffer对象是属于哪一种OpenGL对象
     3.最后,为render buffer分配空间。renderbufferStorage
   7)创建一个frame buffer(帧缓冲区)
   
  - (void)setupFrameBuffer {         GLuint framebuffer;    glGenFramebuffers(1,  &framebuffer);    glBindFramebuffer(GL_FRAMEBUFFER,  framebuffer);    glFramebufferRenderbuffer(GL_FRAMEBUFFER,  GL_COLOR_ATTACHMENT0,         GL_RENDERBUFFER,  _colorRenderBuffer); }
   
     Frame buffer也是OpenGL的对象,它包含了前面提到的render buffer,以及其它后面会讲到的诸如:depth  buffer、stencil buffer和accumulation buffer。
     前两步创建frame buffer的动作跟创建render buffer的动作很类似。(反正也是用一个glBind什么的)
     而最后一步glFramebufferRenderbuffer这个才有点新意。它让你把前面创建的buffer render依附在frame  buffer的GL_COLOR_ATTACHMENT0位置上。
   8)清理屏幕
   
   - (void)render {    glClearColor(0, 104.0/255.0,  55.0/255.0, 1.0);    glClear(GL_COLOR_BUFFER_BIT);    [_context  presentRenderbuffer:GL_RENDERBUFFER];}
   
     为了尽快在屏幕上显示一些什么,在我们和那些vertexes、shaders打交道之前,把屏幕清理一下,显示另一个颜色吧。(RGB 0, 104,  55,绿色吧)
     这里每个RGB色的范围是0~1,所以每个要除一下255.
     下面解析一下每一步动作:
     1.调用glClearColor,设置一个RGB颜色和透明度,接下来会用这个颜色涂满全屏。
     2.调用glClear来进行这个“填色”的动作(大概就是photoshop那个油桶嘛)。还记得前面说过有很多buffer的话,这里我们要用到GL_COLOR_BUFFER_BIT来声明要清理哪一个缓冲区。
     3.调用OpenGL context的presentRenderbuffer方法,把缓冲区(render buffer和color  buffer)的颜色呈现到UIView上。
   9)把前面的动作串起来修改一下OpenGLView.m
   
   // Replace initWithFrame with this-  (id)initWithFrame:(CGRect)frame{    self = [super  initWithFrame:frame];    if (self)  {                 [self  setupLayer];                 [self setupContext];                         [self  setupRenderBuffer];                 [self  setupFrameBuffer];                         [self  render];            }    return  self;}  // Replace dealloc method with this-  (void)dealloc{    [_context  release];    _context =  nil;    [super dealloc];}
   
   10)把App Delegate和OpenGLView连接起来
     在HelloOpenGLAppDelegate.h中修改一下:
   
   // At top of file#import "OpenGLView.h"  //  Inside @interfaceOpenGLView* _glView;  // After @interface@property  (nonatomic, retain) IBOutlet OpenGLView *glView;
   
     接下来修改.m文件:
   
  // At top of file@synthesize glView=_glView;  // At  top of application:didFinishLaunchingWithOptionsCGRect screenBounds =  [[UIScreen mainScreen] bounds];    self.glView = [[[OpenGLView  alloc] initWithFrame:screenBounds] autorelease];[self.window  addSubview:_glView];  // In dealloc[_glView release];
   
     一切顺利的话,你就能看到一个新的view在屏幕上显示。
     这里是OpenGL的世界。
   
   添加shaders:顶点着色器和片段着色器
     在OpenGL ES2.0的世界,在场景中渲染任何一种几何图形,你都需要创建两个称之为“着色器”的小程序。
     着色器由一个类似C的语言编写- GLSL。知道就好了,我们不深究。
     这个世界有两种着色器(Shader):
     ·Vertex  shaders–在你的场景中,每个顶点都需要调用的程序,称为“顶点着色器”。假如你在渲染一个简单的场景:一个长方形,每个角只有一个顶点。于是vertex  shader会被调用四次。它负责执行:诸如灯光、几何变换等等的计算。得出最终的顶点位置后,为下面的片段着色器提供必须的数据。
     ·Fragment  shaders–在你的场景中,大概每个像素都会调用的程序,称为“片段着色器”。在一个简单的场景,也是刚刚说到的长方形。这个长方形所覆盖到的每一个像素,都会调用一次fragment  shader。片段着色器的责任是计算灯光,以及更重要的是计算出每个像素的最终颜色。
     下面我们通过简单的例子来说明。
     打开你的xcode,File\New\New File…选择iOS\Other\Empty,点击下一步。命名为:
     SimpleVertex.glsl点击保存。
     打开这个文件,加入下面的代码:
   
   attribute vec4 Position; // 1attribute vec4 SourceColor; //  2  varying vec4 DestinationColor; // 3  void main(void) {  // 4    DestinationColor = SourceColor; //  5    gl_Position = Position; // 6}
   
     我们一行一行解析:
     1“attribute”声明了这个shader会接受一个传入变量,这个变量名为“Position”。在后面的代码中,你会用它来传入顶点的位置数据。这个变量的类型是“vec4”,表示这是一个由4部分组成的矢量。
     2与上面同理,这里是传入顶点的颜色变量。
     3这个变量没有“attribute”的关键字。表明它是一个传出变量,它就是会传入片段着色器的参数。“varying”关键字表示,依据顶点的颜色,平滑计算出顶点之间每个像素的颜色。
   文字比较难懂,我们一图胜千言:
     图中的一个像素,它位于红色和绿色的顶点之间,准确地说,这是一个距离上面顶点55/100,距离下面顶点45/100的点。所以通过过渡,能确定这个像素的颜色。
   
     4每个shader都从main开始–跟C一样嘛。
     5设置目标颜色=传入变量:SourceColor
     6gl_Position是一个内建的传出变量。这是一个在vertex shader中必须设置的变量。这里我们直接把gl_Position =  Position;没有做任何逻辑运算。
     一个简单的vertex shader就是这样了,接下来我们再创建一个简单的fragment shader。
     新建一个空白文件:
     File\New\New File…选择iOS\Other\Empty
     命名为:SimpleFragment.glsl保存。
     打开这个文件,加入以下代码:
   
varying lowp vec4 DestinationColor; // 1  void main(void) {  // 2    gl_FragColor = DestinationColor; // 3}
   
     下面解析:
     1这是从vertex shader中传入的变量,这里和vertex shader定义的一致。而额外加了一个关键字:lowp。在fragment  shader中,必须给出一个计算的精度。出于性能考虑,总使用最低精度是一个好习惯。这里就是设置成最低的精度。如果你需要,也可以设置成medp或者highp.
     2也是从main开始嘛
     3正如你在vertex shader中必须设置gl_Position,在fragment  shader中必须设置gl_FragColor.
     这里也是直接从vertex shader中取值,先不做任何改变。
     还可以吧?接下来我们开始运用这些shader来创建我们的app。
   编译Vertex shader和Fragment shader
     目前为止,xcode仅仅会把这两个文件copy到application bundle中。我们还需要在运行时编译和运行这些shader。
     你可能会感到诧异。为什么要在app运行时编译代码?
     这样做的好处是,我们的着色器不用依赖于某种图形芯片。(这样才可以跨平台嘛)
     下面开始加入动态编译的代码,打开OpenGLView.m
     在initWithFrame:方法上方加入:
   
-  (GLuint)compileShader:(NSString*)shaderName withType:(GLenum)shaderType  {      // 1    NSString*  shaderPath = [[NSBundle mainBundle] pathForResource:shaderName          ofType:@"glsl"];    NSError*  error;    NSString* shaderString = [NSString  stringWithContentsOfFile:shaderPath          encoding:NSUTF8StringEncoding  error:&error];    if (!shaderString)  {        NSLog(@"Error loading  shader: %@",  error.localizedDescription);        exit(1);    }      //  2    GLuint shaderHandle =  glCreateShader(shaderType);           // 3constchar* shaderStringUTF8 =  [shaderString UTF8String];        int  shaderStringLength = [shaderString  length];    glShaderSource(shaderHandle, 1,  &shaderStringUTF8,  &shaderStringLength);      //  4    glCompileShader(shaderHandle);      //  5    GLint  compileSuccess;    glGetShaderiv(shaderHandle,  GL_COMPILE_STATUS, &compileSuccess);    if  (compileSuccess == GL_FALSE) {        GLchar  messages[256];        glGetShaderInfoLog(shaderHandle,  sizeof(messages), 0,  &messages[0]);        NSString  *messageString = [NSString stringWithUTF8String:messages];        NSLog(@"%@",  messageString);        exit(1);    }      return  shaderHandle;  }
   
     下面解析:
     1这是一个UIKit编程的标准用法,就是在NSBundle中查找某个文件。大家应该熟悉了吧。
     2调用glCreateShader来创建一个代表shader的OpenGL对象。这时你必须告诉OpenGL,你想创建fragment  shader还是vertex shader。所以便有了这个参数:shaderType
     3调用glShaderSource,让OpenGL获取到这个shader的源代码。(就是我们写的那个)这里我们还把NSString转换成C-string
     4最后,调用glCompileShader在运行时编译shader
     5大家都是程序员,有程序的地方就会有fail。有程序员的地方必然会有debug。如果编译失败了,我们必须一些信息来找出问题原因。glGetShaderiv和glGetShaderInfoLog会把error信息输出到屏幕。(然后退出)
     我们还需要一些步骤来编译vertex shader和frament shader。
   -把它们俩关联起来
   -告诉OpenGL来调用这个程序,还需要一些指针什么的。
     在compileShader:方法下方,加入这些代码
   
   -  (void)compileShaders {      //  1    GLuint vertexShader = [self  compileShader:@"SimpleVertex"        withType:GL_VERTEX_SHADER];    GLuint  fragmentShader = [self  compileShader:@"SimpleFragment"        withType:GL_FRAGMENT_SHADER];      //  2    GLuint programHandle =  glCreateProgram();    glAttachShader(programHandle,  vertexShader);    glAttachShader(programHandle,  fragmentShader);    glLinkProgram(programHandle);      //  3    GLint  linkSuccess;    glGetProgramiv(programHandle,  GL_LINK_STATUS, &linkSuccess);    if (linkSuccess ==  GL_FALSE) {        GLchar  messages[256];        glGetProgramInfoLog(programHandle,  sizeof(messages), 0, &messages[0]);        NSString  *messageString = [NSString  stringWithUTF8String:messages];        NSLog(@"%@",  messageString);        exit(1);    }      //  4    glUseProgram(programHandle);      //  5    _positionSlot = glGetAttribLocation(programHandle, "Position");    _colorSlot  = glGetAttribLocation(programHandle,  "SourceColor");    glEnableVertexAttribArray(_positionSlot);    glEnableVertexAttribArray(_colorSlot);}
   
     下面是解析:
     1用来调用你刚刚写的动态编译方法,分别编译了vertex shader和fragment shader
     2调用了glCreateProgramglAttachShaderglLinkProgram连接vertex和fragment  shader成一个完整的program。
     3调用glGetProgramivlglGetProgramInfoLog来检查是否有error,并输出信息。
     4调用glUseProgram让OpenGL真正执行你的program
     5最后,调用glGetAttribLocation来获取指向vertex  shader传入变量的指针。以后就可以通过这写指针来使用了。还有调用glEnableVertexAttribArray来启用这些数据。(因为默认是disabled的。)
     最后还有两步:
     1在initWithFrame方法里,在调用render之前要加入这个:
     2在@interface in OpenGLView.h中添加两个变量:
   
   GLuint _positionSlot;GLuint _colorSlot;
   
     编译!运行!
     如果你仍能正常地看到之前那个绿色的屏幕,就证明你前面写的代码都很好地工作了。
   为这个简单的长方形创建Vertex Data!
     在这里,我们打算在屏幕上渲染一个正方形,如下图:
   
     在你用OpenGL渲染图形的时候,时刻要记住一点,你只能直接渲染三角形,而不是其它诸如矩形的图形。所以,一个正方形需要分开成两个三角形来渲染。
     图中分别是顶点(0,1,2)和顶点(0,2,3)构成的三角形。
     OpenGL ES2.0的一个好处是,你可以按你的风格来管理顶点。
     打开OpenGLView.m文件,创建一个纯粹的C结构以及一些array来跟踪我们的矩形信息,如下:
   
   typedef struct {    float  Position[3];    float Color[4];} Vertex;  const  Vertex Vertices[] = {    {{1, -1, 0}, {1, 0, 0,  1}},    {{1, 1, 0}, {0, 1, 0,  1}},    {{-1, 1, 0}, {0, 0, 1,  1}},    {{-1, -1, 0}, {0, 0, 0, 1}}};  const  GLubyte Indices[] = {     0, 1,  2,     2, 3, 0};
   
     这段代码的作用是:
     1一个用于跟踪所有顶点信息的结构Vertex(目前只包含位置和颜色。)
     2定义了以上面这个Vertex结构为类型的array。
     3一个用于表示三角形顶点的数组。   


本文由职坐标整理并发布,希望对同学们有所帮助。了解更多详情请关注职坐标移动开发之IOS频道!


本文由 @白羽 发布于职坐标。未经许可,禁止转载。
喜欢 | 0 不喜欢 | 0
看完这篇文章有何感觉?已经有0人表态,0%的人喜欢 快给朋友分享吧~
评论(0)
后参与评论

您输入的评论内容中包含违禁敏感词

我知道了

助您圆梦职场 匹配合适岗位
验证码手机号,获得海同独家IT培训资料
选择就业方向:
人工智能物联网
大数据开发/分析
人工智能Python
Java全栈开发
WEB前端+H5

请输入正确的手机号码

请输入正确的验证码

获取验证码

您今天的短信下发次数太多了,明天再试试吧!

提交

我们会在第一时间安排职业规划师联系您!

您也可以联系我们的职业规划师咨询:

小职老师的微信号:z_zhizuobiao
小职老师的微信号:z_zhizuobiao

版权所有 职坐标-一站式IT培训就业服务领导者 沪ICP备13042190号-4
上海海同信息科技有限公司 Copyright ©2015 www.zhizuobiao.com,All Rights Reserved.
 沪公网安备 31011502005948号    

©2015 www.zhizuobiao.com All Rights Reserved

208小时内训课程