2017-02-27 15:02:16 Forever_wj 阅读数 297

一、什么是 OpenGL ES?

iOS上绘制图形的方式很多,UIKit,CoreGraphics,SpriteKit,OpenGL ES,Metal等。OpenGL ES是一套非常底层但使用非常广泛的C语言API,专为移动设备定制,可在不同的手机系统或浏览器上使用,渲染效果很好。

二、iOS的OpenGL ES的使用:

  • 新建一个工程之后,在系统库中需要添加导入OpenGLES.framework和QuartzCore.framework两个库;
  • 新建一个单独实现OpenGL ES的类,命名为OpenGLView,并引入
#import "OpenGLView.h"
#import <QuartzCore/QuartzCore.h>
#import <OpenGLES/ES2/gl.h>
#import <OpenGLES/ES2/glext.h>
  • 在OpenGLView的.m的延展中声明如下实例变量:
@interface OpenGLView () {
    CAEAGLLayer *eaglLayer; //OpenGL context,管理使用openGLES进行绘制的状态,命令及资源
    EAGLContext *eaglContext;
    GLuint      colorRenderBuffer;//渲染缓冲区
    GLuint      frameBuffer;//帧缓冲区
}
  • 继续在.m中实现layerClass方法:
 + (Class)layerClass {
    //为了让 UIView 显示 openGL 内容,必须将默认的layer类型修改为CAEAGLLayer类型
    return [CAEAGLLayer class];
}
  • CAEAGLLayer的配置:默认的 CALayer 是透明的,需要将它设置为 opaque == YES 才能看到在它上面描绘的东西。为此,使用匿名 category 技巧,在 OpenGLView.m的开头(在@interface OpenGLView 的上面)添加匿名 category,并声明私有函数 setupLayer并在implementation中实现:
 + (void)setupLayer {
    eaglLayer = (CAEAGLLayer *)self.layer;
    eaglLayer.opaque = YES;
    // 描绘属性:这里不维持渲染内容
    // kEAGLDrawablePropertyRetainedBacking:若为YES,则使用glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)计算得到的最终结果颜色的透明度会考虑目标颜色的透明度值。
    // 若为NO,则不考虑目标颜色的透明度值,将其当做1来处理。
    // 使用场景:目标颜色为非透明,源颜色有透明度,若设为YES,则使用glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)得到的结果颜色会有一定的透明度(与实际不符)。若未NO则不会(符合实际)。
    eaglLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:NO],kEAGLDrawablePropertyRetainedBacking,kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat, nil];
}
  • EAGLContext渲染上下文
 - (void)setupContext {
    eaglContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES2];
    if (!eaglContext) {
        exit(1);
    }

    if (![EAGLContext setCurrentContext:eaglContext]) {
        exit(1);
    }
}
  • 创建RenderBuffer:用于存储渲染的内容(OpenGL ES 总共有三大不同用途的color buffer,depth buffer 和 stencil buffer,这里创建私有方法setupRenderBuffer来生成 color buffer;
 - (void)setupRenderBuffer {
    if (colorRenderBuffer) {
        glDeleteBuffers(1, &colorRenderBuffer);
        frameBuffer = 0;
    }
    // 生成一个renderBuffer,id是colorRenderBuffer
    glGenBuffers(1, &colorRenderBuffer);
    // 设置为当前renderBuffer,则后面引用GL_RENDERBUFFER,即指的是colorRenderBuffer
    glBindRenderbuffer(GL_RENDERBUFFER, colorRenderBuffer);
    // 为colorRenderBuffer分配存储空间
    [eaglContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:eaglLayer];
}
  • FrameBuffer:renderBuffer对象本身不能直接使用,不能挂载到GPU上直接输出内容,要使用frameBuffer(OpenGlES的FrameBuffer包含:renderBuffer,depthBuffer,stencilBuffer和accumulationBuffer);
- (void)setupFrameBuffer {
    if (frameBuffer) {
        glGenBuffers(1, &frameBuffer);
        frameBuffer = 0;
    }
    // FBO用于管理colorRenderBuffer,离屏渲染
    glGenFramebuffers(1, &frameBuffer);
    // 设置为当前frameBuffer
    glBindRenderbuffer(GL_FRAMEBUFFER, frameBuffer);
    // 将colorRenderBuffer装配到GL_COLOR_ATTACHMENT0这个装配点上
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, colorRenderBuffer);
}
  • 当 UIView 在进行布局变化之后,由于 layer 的宽高变化,导致原来创建的 renderbuffer不再相符,需要销毁既有的 renderbuffer 和 framebuffer;
 - (void)destoryRenderAndFrameBuffer {
    glDeleteFramebuffers(1, &frameBuffer);
    frameBuffer = 0;
    glDeleteRenderbuffers(1, &colorRenderBuffer);
    colorRenderBuffer = 0;
}
  • 声明一个render方法进行真实描绘:
 - (void)render {
    // 设置清屏颜色
    glClearColor(0, 1.0, 0, 1.0);
    // 用来指定要用清屏颜色来清除由mask指定的buffer,此处是color buffer
    glClear(GL_COLOR_BUFFER_BIT);

    [eaglContext presentRenderbuffer:GL_RENDERBUFFER];
}
  • 最后在实现layoutSubviews方法:
- (void)layoutSubviews {
    [self setupLayer];
    [self setupContext];
    [self destoryRenderAndFrameBuffer];
    [self setupRenderBuffer];
    [self setupFrameBuffer];
    [self render];
}
  • 完成了OpenGLView的基本构造之后,利用OpenGLView在界面上进行渲染:在ViewController中导入#import “OpenGLView.h”,并声明一个OpenGLView的实例属性,在viewDidLoad中调用即可:
    self.glView = [[OpenGLView alloc] init];
    self.glView.frame = self.view.frame;
    [self.view addSubview:self.glView];
    self.view.backgroundColor = [UIColor redColor];
2017-02-06 19:34:16 u014604106 阅读数 423

一些OpenGL相关教程

  1. http://www.opengl-tutorial.org/cn/beginners-tutorials/tutorial-1-opening-a-window/这篇文章是基于pc的OpenGL讲解,知识比较基础、详细、准确。

  2. http://www.jianshu.com/p/3b532f6fcedf 这篇文章着重于iPhone中OpenGL进行讲解,讲解时比较有跳跃性,第一次接触OpenGL的话可能看不懂,但是其中的小例子比较好且多,而且都有源码。这是教程源码

iOS导入.obj模型文件

涉及到3D图像处理的相关技术,一般是使用3D制图软件制作模型文件后导出,转换成项目兼容的数据格式后倒入到项目中。这篇博客介绍了一个名为 OBJ2OPENGL 的将模型文件转换成iPhone OpenGL ES兼容的C/C++头文件的脚本。(这是原文链接

我的demo

我做了一个简单的 可拖动立方体展示示例 ,开启了深度缓存,渲染了纹理 设置了法线及光源,只实现了一下基本的操作,但是注释写的比较详细。

一些额外的收获

通过对OpenGL的学习,了解了渲染时CPU与GPU的分工,误打误撞对app界面流畅度优化的一些原理又有了些新的认识。

2015-08-25 21:24:08 sx1989827 阅读数 6725

       上文我们讲解了如何构建一个hello world开发环境,那么这一篇我们就来画一个简单的三角形出来。

       首先,我要向大家介绍下opengl es的渲染流程,在2.0之前,es的渲染采用的是固定管线,何为固定管线,就是一套固定的模板流程,局部坐标变换 -> 世界坐标变换 ->观察坐标变换->背面消除->光照->裁剪->投影->视口计算->光栅化,程序员只需要调用固定的api修改一些配置参数就可以完成整个渲染流程了。而到了2.0,固定管线改成了可编程管线,我们对整个渲染流程可以再编程,没有固定的api给你调用,一切都依靠shader来完成。那么什么是shader呢:

Shader分为Vertex Shader顶点着色器和Pixel Shader像素着色器两种。其中Vertex Shader主要负责顶点的几何关系等的运算,Pixel Shader主要负责片源颜色等的计算。
着色器替代了传统的固定渲染管线,可以实现3D图形学计算中的相关计算,由于其可编辑性,可以实现各种各样的图像效果而不用受显卡的固定渲染管线限制。这极大的提高了图像的画质。
好了,介绍完渲染流程,我们变进入正题,如何在上一篇的基础上完成一个简单三角形的渲染呢?
首先,我们要建立一个三角形的局部坐标系,也就是三角形每个点的顶点坐标。
GLKVector3 vec[3]={
    {0.5,0.5,0.5},
    {-0.5,-0.5,0.5},
    {0.5,-0.5,-0.5}
};
        然后,因为我们介绍的是opengl es3.0的编程,所以我们用的是可编程管线,那么我们就应该建立一个vertex shader文件和一个pixel shader文件,分别命名为shader.vsh和shader.fsh。
shader.vsh:
attribute vec3 position;   //入参,主程序会将数值传入
void main()
{
    gl_Position = vec4(position,1);  //顶点经过投影变换变换后的位置
}
       shader.fsh:
void main()
{
    gl_FragColor = vec4(0.5,0.5,0.5,1);   //顶点的颜色
}
       可以看到,每一个着色器文件都一个类似于c语言的main函数入口,这个就是着色器的入口,所有的代码都从这里开始执行。
编写完着色器后,我们便需要在主程序里加载shader了,加载shader的代码基本上不需要变动什么,直接copy过来就可以了。
- (BOOL)loadShaders
{
    GLuint vertShader, fragShader;
    NSString *vertShaderPathname, *fragShaderPathname;
    
    // Create shader program.
    program = glCreateProgram();
    
    // Create and compile vertex shader.
    vertShaderPathname = [[NSBundle mainBundle] pathForResource:@"Shader" ofType:@"vsh"];
    if (![self compileShader:&vertShader type:GL_VERTEX_SHADER file:vertShaderPathname]) {
        NSLog(@"Failed to compile vertex shader");
        return NO;
    }
    
    // Create and compile fragment shader.
    fragShaderPathname = [[NSBundle mainBundle] pathForResource:@"Shader" ofType:@"fsh"];
    if (![self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:fragShaderPathname]) {
        NSLog(@"Failed to compile fragment shader");
        return NO;
    }
    
    // Attach vertex shader to program.
    glAttachShader(program, vertShader);
    
    // Attach fragment shader to program.
    glAttachShader(program, fragShader);
    
    // Link program.
    if (![self linkProgram:program]) {
        NSLog(@"Failed to link program: %d", program);
        
        if (vertShader) {
            glDeleteShader(vertShader);
            vertShader = 0;
        }
        if (fragShader) {
            glDeleteShader(fragShader);
            fragShader = 0;
        }
        if (program) {
            glDeleteProgram(program);
            program = 0;
        }
        
        return NO;
    }
    // Release vertex and fragment shaders.
    if (vertShader) {
        glDetachShader(program, vertShader);
        glDeleteShader(vertShader);
    }
    if (fragShader) {
        glDetachShader(program, fragShader);
        glDeleteShader(fragShader);
    }
    
    return YES;
}

- (BOOL)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file
{
    GLint status;
    const GLchar *source;
    
    source = (GLchar *)[[NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil] UTF8String];
    if (!source) {
        NSLog(@"Failed to load vertex shader");
        return NO;
    }
    
    *shader = glCreateShader(type);
    glShaderSource(*shader, 1, &source, NULL);
    glCompileShader(*shader);
    
#if defined(DEBUG)
    GLint logLength;
    glGetShaderiv(*shader, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 0) {
        GLchar *log = (GLchar *)malloc(logLength);
        glGetShaderInfoLog(*shader, logLength, &logLength, log);
        NSLog(@"Shader compile log:\n%s", log);
        free(log);
    }
#endif
    
    glGetShaderiv(*shader, GL_COMPILE_STATUS, &status);
    if (status == 0) {
        glDeleteShader(*shader);
        return NO;
    }
    
    return YES;
}

- (BOOL)linkProgram:(GLuint)prog
{
    GLint status;
    glLinkProgram(prog);
    
#if defined(DEBUG)
    GLint logLength;
    glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 0) {
        GLchar *log = (GLchar *)malloc(logLength);
        glGetProgramInfoLog(prog, logLength, &logLength, log);
        NSLog(@"Program link log:\n%s", log);
        free(log);
    }
#endif
    
    glGetProgramiv(prog, GL_LINK_STATUS, &status);
    if (status == 0) {
        return NO;
    }
    
    return YES;
}

- (BOOL)validateProgram:(GLuint)prog
{
    GLint logLength, status;
    
    glValidateProgram(prog);
    glGetProgramiv(prog, GL_INFO_LOG_LENGTH, &logLength);
    if (logLength > 0) {
        GLchar *log = (GLchar *)malloc(logLength);
        glGetProgramInfoLog(prog, logLength, &logLength, log);
        NSLog(@"Program validate log:\n%s", log);
        free(log);
    }
    
    glGetProgramiv(prog, GL_VALIDATE_STATUS, &status);
    if (status == 0) {
        return NO;
    }
    
    return YES;
}
      然后再viewdidload里面添加如下代码:
[self loadShaders];
    glEnable(GL_DEPTH_TEST);
    glClearColor(0.1, 0.2, 0.3, 1);
    glGenVertexArrays(1, &vertexID);//生成一个vao对象
    glBindVertexArray(vertexID); //绑定vao
    GLuint bufferID;
    glGenBuffers(1, &bufferID);  //生成vbo
glBindBuffer(GL_ARRAY_BUFFER, bufferID);  //绑定
    glBufferData(GL_ARRAY_BUFFER, sizeof(vec), vec, GL_STATIC_DRAW); //填充缓冲对象
    GLuint loc=glGetAttribLocation(program, "position");   //获得shader里position变量的索引
    glEnableVertexAttribArray(loc);     //启用这个索引
    glVertexAttribPointer(loc, 3, GL_FLOAT, GL_FALSE, sizeof(GLKVector3), 0);  //设置这个索引需要填充的内容
    glBindVertexArray(0);   //释放vao
    glBindBuffer(GL_ARRAY_BUFFER, 0);  //释放vbo
       这里我们用到了es 3.0里面的新技术vao(vertex array object)以及2.0里面的vbo。关于vao和vbo,我们会专门展开一章来探讨,现在大家知道就行啦。
       接下来便到了渲染阶段,这里的代码需要写在-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect方法里。
-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);   //清除颜色缓冲和深度缓冲
    glBindVertexArray(vertexID);    
    glUseProgram(program);      //使用shader
    glDrawArrays(GL_TRIANGLES, 0, 3);     //绘制三角形
    glBindVertexArray(0);
    glBindBuffer(GL_ARRAY_BUFFER, 0);
    glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0);
}
      代码阶段便编写完成啦,保存运行,出现如下界面便大功告成啦。







2016-11-01 14:55:41 a1252425 阅读数 193

GPUImage是当前做滤镜最主流的框架,没有之一。其中,OpenGL ES是框架的基石,因此,我们先从iOS的OpenGL ES学起。
本文的翻译地址:OpenGL ES 2.0 for iOS
很多人都听说过OpenGL ES 渲染管线,但都不太清楚这个术语确切的意思,那么,让我们一起来搞清楚吧。“管道”一词是指整个事件的顺序,从你告诉OpenGL ES开始做一些事情(通常叫做打底),到所有被提交的对象被绘制完成。通常,当一个OpenGL ES程序运行时,反复绘制,每个完成的图像为一帧。
OpenGL ES 2.0 之前(包含1.1,支持所有iOS设备)的使用被称为固定打底管道,也就是说OpenGL ES最后生成的图片没有让你处理的机会。一个更好的术语“闭合管道”更符合它,因为你从一段塞入东西,它在另一段输出另外一个东西,当它开始进入管道时,你没有能力去影响它。
在固定管道中,整个图片是根据你通过程序预先定义好的接口调用时提交给OpenGL ES的参数来打底的。每一次OpenGL ES 1.x打底一些东西时,用的是一样的算法和计算。如果你想要一个道光线,举个例子,你需要在应用代码中调用一大把OpenGL ES的函数来定义你需要的这道光线的类型,光线的位置,光线的强度,也可能一些其他的属性。然后,OpenGL ES1.1会带着你提供的信息,做出将光线加入到你的场景所需的全部运算。它会计算如何遮挡你的物体,以便于它们看起来像是光线照射到它们上面,并相应的绘制它们。固定管道是你和很多事物隔绝。它从根本上来讲,“噢,亲爱的 … 把你的场景信息给我,不要担心你的漂亮的小脑袋关于所有的数学。”
固定管道编程的好处是它在概念上很简单容易。当你学起来时会感觉不容易,但是,相较于可编程管道,最基本的是更容易掌控。是不是想建立一个页面来验证观点?如果你使用一对接口来提交一大把输入,OpenGL ES基本上会为你完成。想要移动,旋转,或者缩放一个物体?这也有很多函数提供给你去做这些。想要给你的场景添加一道或者六道光线?仅需要在每道光线绘制前,做很少的调用,然后就没你事了。
固定管道编程的缺点是限制你做想做的事。很多你想要的光线和你能在现代化的3D游戏中见到的有质感的效果,举个例子,用固定管道很难(或者不能)实现。用固定管道,你只能做绘制库作者认为你可能需要,你可能需要做的方法。想要一个透明的火光或者有立体感的土地?好吧,你通过固定管道又可能找到一些方法实现这类事情,但那不会很容易。人们已经想出了一些很巧妙的方法来打破固定管道的限制。但是尽管您通过绕开管道限制确实实现了一些效果,你的代码看起来像一个hack,更重要的是,相对于最好的程序,一些你用来实现的功能代码很可能运行在错误的地方。因为一旦我们开始使用可编程管道,它是一个关键的信息片段。

硬件结构
OpenGL ES将你与特殊硬件的代码隔离,但是,了解它还是很重要的,至少在一个很高的水平,iOS设备是怎样计算和展示图像内容的。以往每台iOS设备都有两个处理器。它们都有一个通用用途的处理器叫做CPU,也有一个第二处理器GPU,代表了绘制程序单元。CPU可以做所有你需要做的事情,并且它是你的应用代码主要运行的地方。CPU在处理整数运算是很快,但是在做浮点数运算是就没那么快了。另一方面,GPU更专业,它更擅长浮点数等大数字的计算非常快。它被设计来辅助CPU来处理那些CPU不是很擅长的任务,而不是作为一个独立的处理器。CPU,本质上,着手于确定的任务,GPU更擅长运行中的。随着两个处理器平行的工作,设备每次能做更多的工作。但是这种“相互帮助”在程序中并不会自动发生。
当你用Xcode写C,Objectice C,或者c++代码时,编译后的二进制代码在CPU上执行。有很多库,像CoreAnimation,会为你考虑,将任务交个GPU处理。但是通常来讲,你需要特殊的库来获取进入GPU的方式。非常幸运的是,OpenGL ES就是这样一种库,固定通道和可编程通道的打底工作都是在GPU中进行。这样是有道理的,如果你想到她:图形的计算是GPU被设计来做的。很多OpenGL ES 2.0的管道,像所有的固定管道,是在你的控制之外的。然而,有两个特殊的地方你能编写代码。你写的代码在GPU上运行,不能使用C,OC和C++编写。它必须用为特殊目的设计的特殊语言来编写。你为可编程管道写的程序在GPU上运行,并被称为着色器。你写入着色器的语言叫GLSL,是GL Shading Language的缩写。
着色器这个术语是OpenGL ES中非直观命名的另一个例子。着色器无非是替代CPU运行在GPU上的一段可执行的代码片段。在任务中,它们的角色是计算代表虚拟物体的每个像素的阴影,但是它们能做的还有更多。着色器在图灵编程语言中是一个非常完善的程序。

OpenGL ES Shaders

Vertex Shaders

Fragment Shaders

Sending Data to the Shaders

Attributes

Uniforms

Varyings

OpenGL ES Programs

Sending Attributes and Uniforms to the Shaders

Submitting Uniforms

Submitting Attributes

Drawing to a Close

未完,待补充

2015-08-05 22:52:38 sx1989827 阅读数 17146

OpenGL ES简介:

     OpenGL ES (OpenGL for Embedded Systems) 是 OpenGL三维图形 API 的子集,针对手机、PDA和游戏主机等嵌入式设备而设计。该API由Khronos集团定义推广,Khronos是一个图形软硬件行业协会,该协会主要关注图形和多媒体方面的开放标准。

OpenGL ES 3.0主要新功能有:
1、渲染管线多重增强,实现先进视觉效果的加速,包括遮挡查询(Occlusion Query)、变缓反馈(Transform Feedback)、实例渲染(Instanced Rendering)、四个或更多渲染目标支持。
2、高质量ETC2/EAC纹理压缩格式成为一项标准功能,不同平台上不再需要需要不同的纹理集。
3、新版GLSL ES 3.0着色语言,全面支持整数和32位浮点操作。
4、纹理功能大幅增强,支持浮点纹理、3D纹理、深度纹理、顶点纹理、NPOT纹理、R/RG单双通道纹理、不可变纹理、2D阵列纹理、无二次幂限制纹理、阴影对比、调配(swizzle)、LOD与mip level clamps、无缝立方体贴图、采样对象、纹理MSAA抗锯齿渲染器。
5、一系列广泛的精确尺寸纹理和渲染缓冲格式,便携移动应用更简单。

     而在iOS上,可以支持opengles3.0的最低环境是iphone5s ios7.0.

iOS下环境搭建:

     关于ios下环境搭建有好几种,最简单的是在新建工程的时候选择game模板,然后如下图所示:


     不过,用这种方式,创建出来的工程有很多用不到的代码,不利于我们one by one的学习,所以,我们就从最简单的single view application工程说起,怎么一步步去搭建opengl es环境。

     首先,创建一个single view application工程,然后,引入GlKit和OpenGLES。



     紧接着,在ViewController.h文件里引入GLKit/GLKit.h,将ViewController类的父类改成GLKViewController。

#import <UIKit/UIKit.h>
#import <GLKit/GLKit.h>
@interface ViewController : GLKViewController


@end

     然后将storyboard中ViewController所对应的view的Custom Class改成GLKView。



     修改ViewController.m文件,代码如下:

#import "ViewController.h"
#import <OpenGLES/ES3/gl.h>
#import <OpenGLES/ES3/glext.h>
@interface ViewController ()<GLKViewDelegate>
{
    EAGLContext *context; //EAGLContent是苹果在ios平台下实现的opengles渲染层,用于渲染结果在目标surface上的更新。

}
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    context = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];//这里用的是opengles3.
    
    if (!context) {
        NSLog(@"Failed to create ES context");
    }
    
    GLKView *view = (GLKView *)self.view;
    view.context = context;
    view.drawableDepthFormat = GLKViewDrawableDepthFormat24;
    [EAGLContext setCurrentContext:context];
    glEnable(GL_DEPTH_TEST); //开启深度测试,就是让离你近的物体可以遮挡离你远的物体。
    glClearColor(0.1, 0.2, 0.3, 1); //设置surface的清除颜色,也就是渲染到屏幕上的背景色。
}

- (BOOL)prefersStatusBarHidden {
    return YES;
}

-(void)glkView:(GLKView *)view drawInRect:(CGRect)rect
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);  //清除surface内容,恢复至初始状态。
}
@end

     再次构建运行,如下图所示:



     至此,一个初步的opengles3的开发环境已经搭建完毕,因为ios平台的特殊性,所以我们很多时候不得不借助ios下glkit库去实现和简化我们的操作,不过,对于大部分opengles的内容,我还是会脱离glkit,原汁原味的道出,这样,对于以后的跨平台是大有裨益的。

     本文有不足之处,还望各位多多指正。







iOS OpenGL ES Guide

阅读数 3502

IOS metal OpenGL ES

阅读数 679

没有更多推荐了,返回首页