EGL&OpenGL着色语言及案例

前言:编程

上篇案例:利用GLKit实现旋转立方体中,使用了苹果提供的GLKit框架,它提供的函数和类,包括提供加载纹理、数学计算、经常使用着色器、视图、视图控制器等,大大减小了工做量。可是GLKit也存在必定缺陷,着色器代码没法修改,着色器提供的属性变量有上限等。xcode


EGL(Embedded Graphics Library)

OpenGL ES命令须要渲染上下文绘制表面才能完成图形图像的绘制,可是,OpenGL ES API并无提供如何建立渲染上下文,或者上下文如何链接到原生窗口系统的方法。 EGL是Khronos渲染API和原生窗口系统之间的接口.可是iOS并不支持EGL,咱们在iOS须要使用苹果封装的EAGL。缓存

  • 渲染上下文:存储相关OpenGL ES状态,状态机
  • 绘制表面:用于绘制图元的表现,它指定渲染所须要的缓存区类型,例如颜色缓存区,深度缓存区,模版缓存区

EGL主要功能

  1. 和本地窗口系统进行通信
  2. 查询可用的配置
  3. 建立OpenGL ES可用的绘图表面-CAEAGLayer
  4. 同步不一样类别的API之间的渲染,好比在OpenGL ES和OpenVG之间同步,或者OpenGL和本地窗口的绘图命令
  5. 管理‘渲染资源’,好比纹理映射(rendering map)

OpenGL着色语言-GLSL

图形硬件的趋势是在那些变得极其复杂的领域,用可编程能力来代替固定功能,这样的两个领域就是顶点处理和片元处理。使用GLSL,顶点处理和片元处理的固定功能阶段由于有了可编程阶段而获得补充。markdown

GLSL源自于C语言,一样包含了丰富的类型,包括向量和矩阵app

向量数据类型

经常使用vec2,vec3,vec4浮点向量框架

类型 描述
vec2,vec3,vec4 2份量,3份量,4份量浮点向量
ivec2,ivec3,ivec4 2份量、3份量、4份量整型向量
uvec2,uvec3,uvec4 2份量、3份量、4份量⽆符号整型向量
bvec2,bvec3,bvec4 2份量、3份量、4份量bool型向量

矩阵数据类型

经常使用mt3,mt4函数

类型 描述
mat2,mat2x2 两行两列
mat3,mat3x3 三行三列
mat4,mat4x4 四行四列
mat2x3 三行两列
mat2x4 四行两列
mat3x2 三行两列
mat3x4 三行四列
mat4x2 两行四列
mat4x3 三行四列

GLSL数据修饰符

修饰符表示,是经过何种方式输入数据。post

1. uniform

从app代码传递到顶点/片元着色器(vertex,fragment)中所用到的变量性能

  • 在vertex,fragment中会将uniform当成常量
  • 使用glUniform..进行传递,通常会经过uniform传递视图矩阵、投影矩阵,投影视图矩阵等
  • 在vertex和fragment中声明同样的类型、变量,就可让vertex和fragment都能接收到。

2.attribute

只能从客户端传递到顶点着色器,而且只能够在顶点着色器中进行使用的数据ui

  • 传递数据包括:顶点、纹理坐标、颜色、法线等一切与坐标颜色有关的数据
  • 使用glVertex..进行传递,例如以前的glVertexAttribPointer
  • 使用attribute传递数据,在iOS必定要记得打开通道开关,iOS出于性能的考虑默认是关闭的

3.varying

当须要将顶点着色器的数据传递到片元着色器时,两个着色器中如出一辙的数据变量就须要它来修饰

着色器代码简读

在iOS中,经过新建一个空白的类来编写着色器源码,一般用.vsh.fsh后缀分别表明顶点着色器和片元着色器,这样的后缀只是开发者用来区分,而Xcode实际并无这样后缀的文件。

顶点着色器

// 经过attribute传递的4份量浮点数据,表示顶点数据
attribute vec4 position;     
// 经过attribute传递的2份量浮点数据,表示纹理坐标
/*
纹理坐标数据,是经过代码传递给vertex后,由vertex桥接给fragment
*/
attribute vec2 textCoordinate;
// 经过varying传递低精度的2份量浮点数据,表示纹理坐标
varying lowp vec2 varyTextCoord;

void main()
{
    // 数据桥接
    varyTextCoord = textCoordinate;
    // vertex计算以后的顶点数据须要赋值给GLSL的内建变量`gl_Position`
    gl_Position = position;
}
复制代码

片元着色器

//定义精度,不然可能会报错
precsion highp float;
//纹理坐标 必须与顶点着色器中如出一辙,经过这个参数获取传递过来的值
varying lowp vec2 varyTextCoord;
//纹理  sampler采样器, 
uniform sampler2D colorMap;   

void main(){
    //拿到纹理对应坐标下的纹素。纹素是纹理对应像素点的颜色值
    lowp vec4 temp = texture2D(colorMap, varyTextCoord);
    //纹理结果赋值给内建变量:gl_FragColor
    gl_FragColor = temp;
} 
复制代码
  • 片元着色器中最终的颜色,即纹理坐标对应的纹素,须要经过内建函数textrue2D(纹理,纹理坐标)计算
  • 经过sampler采样器这样的数据类型,能够将纹理对象传递给片元着色器,1D、2D、3D表明不一样维度的纹理类型,传递的实际上是纹理id

着色器与程序的建立、编译、连接API

1.自定义着色器

  • 建立着色器
// 要建立的类型type:GL_VERTEX_SHADER / GL_FRAGMENT_SHADER
GLuint glCreateShader(GLenum type)
复制代码
  • 将着色器源码附加到着色器对象上
/*
1.shader:要编译的着色器对象
2.count:源码字符串数量
3.string:源码内容
4.length:通常NULL,意味字符串是NULL终止的
*/
glShaderSource(GLuint shader, GLsizei count, const GLchar *const *string, const GLint *length)
复制代码

在xcode建立空文件,自定义编写着色器代码,本质是一个字符串。

  • 编译着色器源码
void glCompileShader(GLuint shader);
复制代码

2.program自定义程序

  • 建立
GLUint glCreateProgram()
复制代码
  • 着色器与程序链接/附着
void glAttachShader(GLuint program,GLuint shader);
复制代码
  • 连接程序
glLinkProgram(GLuint program)
复制代码
  • 使用程序
glUseProgram(GLuint program)
复制代码

以上API,基本表现出了,获取连接后着色器对象的通常过程:

  1. 建立一个顶点着色器对象和片元着色器对象
  2. 将着色器源码链接到每一个着色器对象
  3. 编译着色器对象
  4. 建立一个程序对象
  5. 将编译后的着色器对象连接到程序对象
  6. 连接程序对象

案例:渲染一张图片

大体能够分为六个步骤

  • 设置图层:在iOS上用来绘制OpenGL ES内容CAEAGLLayer,继承CALayer
  • 建立上下文:EAGLContext用来保存状态
  • 清空缓冲区:以防有遗留数据形成干扰
  • 设置RenderBuffer:
  • 设置FrameBuffer

以上步骤为渲染作的准备工做,重点在于渲染的步骤,其中也能够继续分四步

  • 开始渲染
    • 清空Buffer,设置视口、背景颜色
    • 建立、编译、连接着色器
    • 处理顶点、纹理数据
    • 开始绘制,渲染屏幕

1.设置图层

+(Class)layerClass
{
    return [CAEAGLLayer class];
}
//1.设置图层
- (void)setupLayer{
    // 1 建立特殊图层
    self.myEagLayer = (CAEAGLLayer *)self.layer;
    
    // 2.设置scale ,由屏幕的scale来决定
    [self setContentScaleFactor: [[UIScreen mainScreen]scale]];
    
    //3.设置描述属性,这里设置不维持渲染内容以及颜色格式为RGBA8
    /*
     kEAGLDrawablePropertyRetainedBacking  表示绘图表面显示后,是否保留其内容。
     kEAGLDrawablePropertyColorFormat 可绘制表面的内部颜色缓存区格式,这个key对应的值是一个NSString指定特定颜色缓存区对象。默认是kEAGLColorFormatRGBA8;
     
         kEAGLColorFormatRGBA8:32位RGBA的颜色,4*8=32位
         kEAGLColorFormatRGB565:16位RGB的颜色,
     kEAGLColorFormatSRGBA8:sRGB表明了标准的红、绿、蓝,即CRT显示器、LCD显示器、投影机、打印机以及其余设备中色彩再现所使用的三个基本色素。sRGB的色彩空间基于独立的色彩坐标,可使色彩在不一样的设备使用传输中对应于同一个色彩坐标体系,而不受这些设备各自具备的不一样色彩坐标的影响。
     */
    self.myEagLayer.drawableProperties = [NSDictionary dictionaryWithObjectsAndKeys:@YES,kEAGLDrawablePropertyRetainedBacking,kEAGLColorFormatRGBA8,kEAGLDrawablePropertyColorFormat, nil];
}
复制代码

其中作layer强转时,须要重写类方法layerClass

2.设置context

// 2.设置图形上下文
- (void)setupContext{
    // 初始化2.0
    EAGLContext *context = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES2];
    if (context == nil) {
        NSLog(@"建立上下文失败");
        return;
    }
    
    if (![EAGLContext setCurrentContext:context]) {
        NSLog(@"设置当前上下文失败");
        return;
    }
    self.myContext = context;
}
复制代码

3.清空缓冲区

// 3.清空缓冲区
- (void)deleteRenderAndFrameBuffer{
    
    // 帧缓冲区 是 渲染缓冲区的管理者  FBO:frame buffer object
    // 先清空渲染缓冲区 , 而后帧缓冲区,未免有遗留脏数据,先作清空,再使用
    glDeleteRenderbuffers(1, &_myColorRenderBuffer);
    self.myColorRenderBuffer = 0;
    glDeleteFramebuffers(1, &_myColorFrameBuffer);
    self.myColorFrameBuffer = 0;
    
}
复制代码

备注:如下会对帧缓冲区,渲染缓冲区作扩展

4.设置渲染缓冲区

由于帧缓冲区管理着渲染缓冲区,因此要先建立设置渲染缓冲区,而后在建立设置帧缓冲区时,创建二者间的管理关系

gen 申请分配缓冲区->bind 绑定缓冲区类型GL_RENDERBUFFER->renderbufferStorage 将缓冲区绑定到上下文

// 4.开辟渲染缓冲区
- (void)setupRenderBuffer{
    
    //1.定义bufferid
    GLuint buffer;
    // 申请缓冲区,获得bufferid存在buffer
    glGenRenderbuffers(1, &buffer);
    self.myColorRenderBuffer = buffer;
    // 绑定缓冲区
    glBindRenderbuffer(GL_RENDERBUFFER, self.myColorRenderBuffer);
    // 缓冲区绑定到上下文
    [self.myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myEagLayer];
    
}
复制代码

5.设置帧缓冲区

设置帧缓冲区时,须要和渲染缓冲区进行绑定

// 5.帧缓冲区
- (void)setupFrameBuffer{
    GLuint buffer;
    glGenFramebuffers(1, &buffer);
    self.myColorFrameBuffer = buffer;
    glBindFramebuffer(GL_FRAMEBUFFER, self.myColorFrameBuffer);
    // !!! 渲染缓冲区和 帧缓冲区 绑定一块儿
    /*
     glFramebufferRenderbuffer(GLenum target, GLenum attachment, GLenum renderbuffertarget, GLuint renderbuffer)
     target:GL_FRAMEBUFFER
     attachment:渲染缓冲区附着到帧缓冲区的哪里,GL_COLOR_ATTACHMENT0
     renderbuffertarget:
     */
    glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.myColorFrameBuffer);
}
复制代码

6.开始渲染

到此为止,准备工做告一段落,正式渲染工做中,也须要对开发者本身利用GLSL语言编写的着色器源码进行处理、以及顶点纹理数据的处理等.

6.1 基础设置

glClearColor(0.3, 0.45, 0.5, 1);
glClear(GL_COLOR_BUFFER_BIT);
    
// 视口
CGFloat scale = [[UIScreen mainScreen]scale];
glViewport(self.frame.origin.x * scale, self.frame.origin.y * scale, self.frame.size.width * scale, self.frame.size.height * scale);
复制代码

6.2 自定义着色器

利用上边的顶点着色器源码和片元着色器源码,在编写GLSL着色器源码时除了注意不一样的修饰符表明的传输方式,也要注意不要在源码中留下注释,以避免出现问题定位不许,毕竟它们只是个字符串

如上记录的:着色器与程序的建立、编译、连接API,咱们须要对编写的着色器源码进行一样的处理,最终获得的程序来使用.

6.2.1 编译着色器源码获得着色器对象

其中用到的API都在上边作过了详细的介绍

- (void)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file{
    
    // 源码读取路径,转换为NSString
    NSString *content = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil];
    // EAGL使用的是C语言函数,将OC的content字符串 转换为 C语言的字符串
    const GLchar *source = (GLchar *)[content UTF8String];
    
    // 1.建立对应类型的着色器
    *shader = glCreateShader(type);
    
    // 2.将着色器的源码 附着在着色器对象
    glShaderSource(*shader, 1, &source, NULL);
    
    // 3.编译
    glCompileShader(*shader);
    
}

复制代码
  • 参数shader:表示传入的着色器对象,最终源码编译的结果就附着在着色器对象上
  • 参数type:表示建立的是什么类型的着色器
  • file:表示传入的着色器源码文件的位置

这个函数方法,经过咱们传入的shader对象、着色器type,源码位置,最终获得一个附着有着色源码的对象

6.2.2 将着色器对象附着program程序来使用
- (GLuint)loaderShaders:(NSString *)vert withFrag:(NSString *)frag{
    
    // 1.定义顶点/片元着色器
    GLuint verShader,fragShader;
    
    // 2.定义一个program程序
    GLuint program = glCreateProgram();
    
    
    // 3.编译着色器
    [self compileShader:&verShader type:GL_VERTEX_SHADER file:vert];
    [self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:frag];
    
    
    // 4.编译好的着色器对象 附着到程序
    glAttachShader(program, verShader);
    glAttachShader(program, fragShader);
    
    // 5.已经附着的shader对象能够删掉
    glDeleteShader(verShader);
    glDeleteShader(fragShader);
    
    return program;
}
复制代码
  • 参数vert,frag就是着色器源码的位置
  • 该函数调用成功后,咱们还须要作glLinkProgra进行连接,连接结果能够经过glGetProgramiv获取
GLint linkStatus;
glGetProgramiv(self.myProgram, GL_LINK_STATUS, &linkStatus);
复制代码

linkStatus成功的话,咱们就能够调用glUseProgram来使用这个着色器程序

6.3 顶点、纹理数据

一样的,咱们须要将顶点、纹理数据从内容中copy到缓冲区里

// 准备顶点/纹理数据
//前3个是顶点坐标,后2个是纹理坐标
GLfloat attrArr[] =
{
    0.5f, -0.5f, -1.0f,     1.0f, 0.0f,
    -0.5f, 0.5f, -1.0f,     0.0f, 1.0f,
    -0.5f, -0.5f, -1.0f,    0.0f, 0.0f,
    
    0.5f, 0.5f, -1.0f,      1.0f, 1.0f,
    -0.5f, 0.5f, -1.0f,     0.0f, 1.0f,
    0.5f, -0.5f, -1.0f,     1.0f, 0.0f,
};

// 将内存中的数据 copy 到缓冲区
// 顶点数据处理
GLuint attrBuffer;
glGenBuffers(1, &attrBuffer);
glBindBuffer(GL_ARRAY_BUFFER, attrBuffer);
glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);
// 打开顶点数据通道
// 从program得到顶点数据通道id position
GLuint position = glGetAttribLocation(self.myProgram, "position");
glEnableVertexAttribArray(position);
glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, NULL);

// 打开纹理数据通道,传输纹理坐标数据
GLuint textCoordinate = glGetAttribLocation(self.myProgram, "textCoordinate");
glEnableVertexAttribArray(textCoordinate);
glVertexAttribPointer(textCoordinate, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, (float *)NULL+3);

复制代码
  • 其中有一点和GLKit有所不一样,在GLKit中,苹果已经写好了着色器源码,当咱们须要打开Attrib属性通道时,能够直接调用glEnableVertexAttribArray(GLKVertexAttribPosition);其中的GLKVertexAttribPosition,其实就是上面代码中GLuint position = glGetAttribLocation(self.myProgram, "position");的结果

  • 接下来还有一处与GLKit不一样的地方:纹理加载.在GLKit中咱们是使用GLKTextureLoader加载的图片纹理,那咱们如今自定义的话须要另外本身写纹理加载的部分,这部分其实和OpenGL没有关系,它使用的是iOS中Core Graphics

//从图片中加载纹理
- (GLuint)setupTexture:(NSString *)fileName {
    
    //一、将 UIImage 转换为 CGImageRef
    CGImageRef spriteImage = [UIImage imageNamed:fileName].CGImage;
    
    //判断图片是否获取成功
    if (!spriteImage) {
        NSLog(@"Failed to load image %@", fileName);
        exit(1);
    }
    
    //二、读取图片的大小,宽和高
    size_t width = CGImageGetWidth(spriteImage);
    size_t height = CGImageGetHeight(spriteImage);
    
    //3.获取图片字节数 宽*高*4(RGBA)
    GLubyte * spriteData = (GLubyte *) calloc(width * height * 4, sizeof(GLubyte));
    
    //4.建立上下文
    /*
     参数1:data,指向要渲染的绘制图像的内存地址
     参数2:width,bitmap的宽度,单位为像素
     参数3:height,bitmap的高度,单位为像素
     参数4:bitPerComponent,内存中像素的每一个组件的位数,好比32位RGBA,就设置为8
     参数5:bytesPerRow,bitmap的没一行的内存所占的比特数
     参数6:colorSpace,bitmap上使用的颜色空间  kCGImageAlphaPremultipliedLast:RGBA
     */
    CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width*4,CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
    

    //五、在CGContextRef上--> 将图片绘制出来
    /*
     CGContextDrawImage 使用的是Core Graphics框架,坐标系与UIKit 不同。UIKit框架的原点在屏幕的左上角,Core Graphics框架的原点在屏幕的左下角。
     CGContextDrawImage 
     参数1:绘图上下文
     参数2:rect坐标
     参数3:绘制的图片
     */
    CGRect rect = CGRectMake(0, 0, width, height);
   
    //6.使用默认方式绘制
    CGContextDrawImage(spriteContext, rect, spriteImage);
   
    //七、画图完毕就释放上下文
    CGContextRelease(spriteContext);
    
    //八、绑定纹理到默认的纹理ID(
    glBindTexture(GL_TEXTURE_2D, 0);
    
    //9.设置纹理属性
    /*
     参数1:纹理维度
     参数2:线性过滤、为s,t坐标设置模式
     参数3:wrapMode,环绕模式
     */
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR );
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
    glTexParameteri( GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
    
    float fw = width, fh = height;
    
    //10.载入纹理2D数据
    /*
     参数1:纹理模式,GL_TEXTURE_1D、GL_TEXTURE_2D、GL_TEXTURE_3D
     参数2:加载的层次,通常设置为0
     参数3:纹理的颜色值GL_RGBA
     参数4:宽
     参数5:高
     参数6:border,边界宽度
     参数7:format
     参数8:type
     参数9:纹理数据
     */
    glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
    
    //11.释放spriteData
    free(spriteData);   
    return 0;
}
复制代码

纹理数据加载成功后,咱们还须要将纹理数据,经过uniform传递给片元着色器,uniform修饰的数据,不须要打开通道开关

glUniform1i(glGetUniformLocation(self.myProgram,"colorMap"), 0);
复制代码

6.4 开始绘制,显示

//绘图
glDrawArrays(GL_TRIANGLES, 0, 6);    
//从渲染缓存区显示到屏幕上
[self.myContext presentRenderbuffer:GL_RENDERBUFFER];
复制代码


FrameBuffer、RenderBuffer

  • ⼀个renderbuffer 对象是经过应⽤分配的⼀个2D图像缓存区。renderbuffer 可以被⽤来分配和存储颜⾊、深度或者模板值。也可以在⼀个framebuffer被⽤做颜⾊、深度、模板的附件。⼀个renderbuffer是⼀个相似于屏幕窗⼝系统提供可绘制的表⾯。⽐如pBuffer。⼀个renderbuffer,而后它并不能直接的使⽤像⼀个GL 纹理。
  • ⼀个 frameBuffer 对象(一般被称为⼀个FBO)。是⼀个收集颜⾊、深度和模板缓存区的附着点。描述属性的状态,例如颜⾊、深度和模板缓存区的⼤⼩和格式,都关联到FBO(Frame Buffer Object)。而且纹理的名字和renderBuffer 对象也都是关联于FBO。各类各样的2D图形可以被附着framebuffer对象的颜⾊附着点。它们包含了renderbuffer对象存储的颜⾊值、⼀个2D纹理或⽴⽅体贴图。或者⼀个mip-level的⼆维切⾯在3D纹理。一样,各类各样的2D图形包含了当时的深度值能够附加到⼀个FBO的深度附着点钟去。惟⼀的⼆维图像,可以附着在FBO的模板附着点,是⼀个renderbuffer对象存储模板值。


ps:为何图片是反的?

相关文章
相关标签/搜索