现在的位置: 首页 > 综合 > 正文

Xcode创建的默认iOS OpenGL ES 2.0 project代码分析

2013年10月18日 ⁄ 综合 ⁄ 共 4686字 ⁄ 字号 评论关闭

1.使用GLKView简化OpenGL初始化。

在前面的教程里面,介绍了如何从一个UIView来建立OpenGL工程,里面做了很多初始化工作,比如:创建render
buffer 和 frame buffer等。这些重复性的工作,apple提供了一个基础类GLKView给我使用。


在TTAppDelegate.m中初始化了一个TTViewController为rootViewController。

  1. self.viewController = [[[TTViewController alloc]initWithNibName:@"TTViewController" bundle:nil] autorelease];
  2. self.window.rootViewController = self.viewController;

复制代码


点击TTViewController.xib 我们可以看到它的属性是GLKView,而不是通常的UIView。

TTViewController继承于
GLKViewController,在标准viewcontroller函数的基础上添加了OpenGL
ES rendering loop相关的函数。


2. EAGLContext初始化
EAGLContext是OpenGL ES RenderingContext的iOS实现。
每个程序有自己的EAGLContext,这保证了各个OpenGL ES程序互不干扰。
在EAGLContext需要在调用任何OpenGL ES api前创建并初始化。
下面的代码在- (void)viewDidLoad函数的最前面。
创建了一个OpenGL ES 2.0的EAGLContext,并设为view的context。

  1. self.context = [[[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES2] autorelease];

  2. if (!self.context) {
  3.     NSLog(@"Failedto create ES context");
  4. }

  5. GLKView *view = (GLKView *)self.view;
  6. view.context = self.context;
  7. view.drawableDepthFormat =GLKViewDrawableDepthFormat24;

复制代码



3. setupGL初始化
在解析[self setupGL]前,我们来看下这个工程运行的结果。

2个不同颜色的正方体在旋转,但是这里只有一个顶点法线数组gCubeVertexData。
仔细观察代码,会发现这里有2个着色器,
一个是GLKBaseEffect,为了方便OpenGL ES 1.0转移到2.0的通用着色器。
一个是OpenGL ES 2.0新添加的可编程着色器,使用跨平台的着色语言GLSL


为了方便观察,我在原始代码上加了2个宏来控制打开其中某一个还是两个一起。

  1. #define SHADER_1

复制代码


从宏块可以方便的看出哪些代码是属于哪个着色器相关的,哪些代码是共用的。
屏蔽某一个宏可以屏蔽某个正方体的显示输出。

回到setupGL,第一行代码是

  1. [EAGLContext setCurrentContext:self.context];

复制代码


在某个线程调用OpenGL api前,需要设置api作用于哪个context,
这个函数设置当前线程操作的context。
注意:不要多个线程同时操控同一个context。

然后是GLSL着色器初始化,后面再详细分析。

  1. #if defined (SHADER_2)
  2.     [selfloadShaders];
  3. #endif

复制代码


GLKBaseEffect着色器初始化。

  1. #if defined (SHADER_1)
  2.    self.effect= [[[GLKBaseEffect alloc] init] autorelease];
  3.    self.effect.light0.enabled = GL_TRUE;
  4.    self.effect.light0.diffuseColor = GLKVector4Make(1.0f, 0.4f, 0.4f,1.0f);
  5. #endif

复制代码



最后是顶点数组和法线的初始化。

  1. //在涉及到消隐等情况(可能遮挡),都要开启深度测。
  2. //glEnable(GL_DEPTH_TEST),硬件上打开了深度缓存区,当有新的同样XY坐标的片断到来时,
  3. //比较两者的深度。开启这个选项,在绘制每一帧前需要glClear(GL_DEPTH_BUFFER_BIT),后面会看到。   
  4.    glEnable(GL_DEPTH_TEST);

  5. //这里使用VertexArray Objects加载顶点法线数据。
  6.    glGenVertexArraysOES(1, &_vertexArray);
  7.    glBindVertexArrayOES(_vertexArray);

  8.    glGenBuffers(1, &_vertexBuffer);
  9.    glBindBuffer(GL_ARRAY_BUFFER, _vertexBuffer);
  10.    glBufferData(GL_ARRAY_BUFFER, sizeof(gCubeVertexData), gCubeVertexData,GL_STATIC_DRAW);

  11.    glEnableVertexAttribArray(GLKVertexAttribPosition);
  12. // GLKVertexAttribPosition顶点属性指针类型:顶点坐标
  13. // 3 一个顶点坐标由几个值来表示,x,y,z
  14. // GL_FLOAT 每个数值的数据类型
  15. //直接使用24并不优雅,24 = sizoef(GLfloat) * 6; 到下一个顶点坐标数据的步长。
  16.    glVertexAttribPointer(GLKVertexAttribPosition, 3, GL_FLOAT, GL_FALSE,24, BUFFER_OFFSET(0));
  17.    glEnableVertexAttribArray(GLKVertexAttribNormal);
  18. //直接使用12并不优雅,12 = sizoef(GLfloat) * 3;
  19.    glVertexAttribPointer(GLKVertexAttribNormal, 3, GL_FLOAT, GL_FALSE, 24,BUFFER_OFFSET(12));
  20.    
  21.    glBindVertexArrayOES(0);
  22. }

复制代码



关于VertexArray
Objects
请猛击。
这里我简单画了个关系图,可能更好理解。

[/url]



4. loadShaders初始化GLSL着色器
与可编程着色器相关的几个函数如下:

  1. - (BOOL)loadShaders;
  2. - (BOOL)compileShader:(GLuint *)shadertype:(GLenum)type file:(NSString *)file;
  3. - (BOOL)linkProgram:(GLuint)prog;
  4. - (BOOL)validateProgram:(GLuint)prog;

复制代码


validateProgram没有使用,compileShader和linkProgram可以作为公共库函数,不需要修改。
唯一需要根据需求修改的是loadShaders函数。

loadShaders大部分步骤都有英文注释,标准的加载流程:
1.创建程序。
2.创建并编译 顶点着色器和片段着色器。
3.把 顶点着色器和片段着色器 与 程序连接起来。
4.设置 顶点着色器和片段着色器 的输入参数。
5.链接程序。
6.获取 uniform 指针。
注意:这步只能在5成功后才能调用,在linkProgrom前,uniform位置是不确定的。
7.断开 顶点着色器和片段着色器 ,并释放它们。
注意:程序并没释放。

第4步是会变化的部分,第6步为可选。

先来看看Shader.vsh顶点着色器的代码。

  1. - (BOOL)loadShaders;
  2. - (BOOL)compileShader:(GLuint *)shadertype:(GLenum)type file:(NSString *)file;
  3. - (BOOL)linkProgram:(GLuint)prog;
  4. - (BOOL)validateProgram:(GLuint)prog;

复制代码



再来看片段着色器代码。

  1. // varying在片段着色器里面表示从顶点着色器传过来的输入参数
  2. //片段着色器不能直接传如参数,只能接收顶点着色器的输出。
  3. varying lowp vec4 colorVarying;

  4. void main()
  5. {
  6.    gl_FragColor = colorVarying;
  7. }

复制代码


我们现在回头再看看loadShaders函数里面的第4步。

  1. glBindAttribLocation(_program, ATTRIB_VERTEX,"position");
  2. glBindAttribLocation(_program, ATTRIB_NORMAL, "normal");

复制代码


"position"和"normal"与顶点着色器代码里面的两个attribute对应,
分别与setupGL加载的顶点数组里面的顶点和法线数据对应起来。

5.update更新数据
update是一个delegate 方法用来更新数据,不做UI更新。
好吧,这部分是很头痛的矩阵变换,暂时不去分析他的算法(其实是我不懂=,=)。

对于GLKBaseEffect着色器,下面的代码用来更新矩阵。

  1. self.effect.transform.projectionMatrix = projectionMatrix;
  2. self.effect.transform.modelviewMatrix = modelViewMatrix;

复制代码


而可编程着色器,先保存在下面2个变量中,然后再draw的时候作为输入参数传递给着色器。

  1. _normalMatrix =GLKMatrix3InvertAndTranspose(GLKMatrix4GetMatrix3(modelViewMatrix), NULL);
  2. _modelViewProjectionMatrix =GLKMatrix4Multiply(projectionMatrix, modelViewMatrix);

复制代码


6.drawInRect输出到屏幕

  1. - (void)glkView:(GLKView *)viewdrawInRect:(CGRect)rect
  2. {
  3.    glClearColor(0.65f, 0.65f, 0.65f, 1.0f);
  4.    //GL_DEPTH_BUFFER_BIT与前面的glEnable(GL_DEPTH_TEST)对应。
  5.    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

  6.     //指定Draw使用的顶点数组。
  7.    glBindVertexArrayOES(_vertexArray);
  8.     
  9. #if defined (SHADER_1)
  10.     // Renderthe object with GLKit
  11.     // prepareToDraw绑定着色器到当前的OpenGL ES context。
  12.    [self.effect prepareToDraw];
  13.     //画出第一个正方体
  14.    glDrawArrays(GL_TRIANGLES, 0, 36);
  15. #endif
  16.     
  17. #if defined (SHADER_2)
  18.     // Renderthe object again with ES2

抱歉!评论已关闭.