前言:编程
上篇案例:利用GLKit实现旋转立方体中,使用了苹果提供的GLKit框架,它提供的函数和类,包括提供加载纹理、数学计算、经常使用着色器、视图、视图控制器等,大大减小了工做量。可是GLKit也存在必定缺陷,着色器代码没法修改,着色器提供的属性变量有上限等。xcode
OpenGL ES命令须要
渲染上下文
、绘制表面
才能完成图形图像的绘制,可是,OpenGL ES API并无提供如何建立渲染上下文,或者上下文如何链接到原生窗口系统的方法。 EGL是Khronos渲染API和原生窗口系统之间的接口.可是iOS并不支持EGL,咱们在iOS须要使用苹果封装的EAGL。缓存
状态机
图形硬件的趋势是在那些变得极其复杂的领域,用可编程能力来代替固定功能,这样的两个领域就是顶点处理和片元处理。使用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 | 三行四列 |
修饰符表示,是经过何种方式输入数据。post
从app代码传递到顶点/片元着色器(vertex,fragment)中所用到的变量性能
glUniform..
进行传递,通常会经过uniform传递视图矩阵、投影矩阵,投影视图矩阵等只能从客户端传递到顶点着色器,而且只能够在顶点着色器中进行使用的数据ui
glVertex..
进行传递,例如以前的glVertexAttribPointer
当须要将顶点着色器的数据传递到片元着色器时,两个着色器中如出一辙的数据变量就须要它来修饰
在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(纹理,纹理坐标)
计算// 要建立的类型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);
复制代码
GLUint glCreateProgram()
复制代码
void glAttachShader(GLuint program,GLuint shader);
复制代码
glLinkProgram(GLuint program)
复制代码
glUseProgram(GLuint program)
复制代码
以上API,基本表现出了,获取连接后着色器对象的通常过程:
大体能够分为六个步骤
CAEAGLLayer
,继承CALayer以上步骤为渲染作的准备工做,重点在于渲染的步骤,其中也能够继续分四步
+(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.设置图形上下文
- (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.清空缓冲区
- (void)deleteRenderAndFrameBuffer{
// 帧缓冲区 是 渲染缓冲区的管理者 FBO:frame buffer object
// 先清空渲染缓冲区 , 而后帧缓冲区,未免有遗留脏数据,先作清空,再使用
glDeleteRenderbuffers(1, &_myColorRenderBuffer);
self.myColorRenderBuffer = 0;
glDeleteFramebuffers(1, &_myColorFrameBuffer);
self.myColorFrameBuffer = 0;
}
复制代码
备注:如下会对帧缓冲区,渲染缓冲区作扩展
由于帧缓冲区管理着渲染缓冲区,因此要先建立设置渲染缓冲区,而后在建立设置帧缓冲区时,创建二者间的管理关系
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.帧缓冲区
- (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);
}
复制代码
到此为止,准备工做告一段落,正式渲染工做中,也须要对开发者本身利用GLSL语言编写的着色器源码进行处理、以及顶点纹理数据的处理等.
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);
复制代码
利用上边的顶点着色器源码和片元着色器源码,在编写GLSL着色器源码时除了注意不一样的修饰符表明的传输方式,也要注意不要在源码中留下注释,以避免出现问题定位不许,毕竟它们只是个字符串
如上记录的:着色器与程序的建立、编译、连接API
,咱们须要对编写的着色器源码进行一样的处理,最终获得的程序来使用.
其中用到的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,源码位置,最终获得一个附着有着色源码的对象
- (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;
}
复制代码
GLint linkStatus;
glGetProgramiv(self.myProgram, GL_LINK_STATUS, &linkStatus);
复制代码
linkStatus
成功的话,咱们就能够调用glUseProgram
来使用这个着色器程序
一样的,咱们须要将顶点、纹理数据从内容中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);
复制代码
//绘图
glDrawArrays(GL_TRIANGLES, 0, 6);
//从渲染缓存区显示到屏幕上
[self.myContext presentRenderbuffer:GL_RENDERBUFFER];
复制代码
ps:为何图片是反的?