不采用
GLKBaseEffect
,使用编译连接自定义的着色器(shader
)。用简单的glsl
语言来实现顶点、片元着色器,并图形进行简单的变换。
主要思路
- 创建图层
- 创建上下文
- 清空缓存区
- 设置
RenderBuffer
- 设置
FrameBuffer
- 开始绘制
0x01 创建图层
在
iOS``和tvOS
上绘OpenGL ES
内容的图层,使用CAEAGLLayer,继承自
CALayer`
- (void)setupLayer {
// 获取特殊图层
self.myEagLayer = (CAEAGLLayer *)self.layer;
// 设置scale
[self setContentScaleFactor:[UIScreen mainScreen].scale];
// 设置绘制属性
self.myEagLayer.drawableProperties = @{
kEAGLDrawablePropertyColorFormat:kEAGLColorFormatRGBA8,
kEAGLDrawablePropertyRetainedBacking:@(false)
};
}
+ (Class)layerClass {
return [CAEAGLLayer class];
}
0x02 创建上下文
- (void)setUpContext {
// 指定API
EAGLRenderingAPI api = kEAGLRenderingAPIOpenGLES2;
// 创建图形上下文
EAGLContext *context = [[EAGLContext alloc] initWithAPI:api];
// 判断上下文是否创建成功
if (!context) {
NSLog(@"create context failuer");
return;
}
// 保存context
self.myContext = context;
}
0x03 清空缓存区
- (void)clearRenderFrameBuffer {
/* buffer分为两类
* frame buffer: 相当于render buffer的管理者
* render buffer: 又可以分为 colorbuffer, depthbuffer stencilbuffer
*/
glDeleteBuffers(1, &_renderBuffer);
self.renderBuffer = 0;
glDeleteBuffers(1, &_frameBuffer);
self.frameBuffer = 0;
}
--
0x04 设置RenderBuffer
- (void)setupRenderBuffer {
// 1. 定义一个缓冲区
GLuint buffer;
// 2. 创建一个buffer
glGenRenderbuffers(1, &buffer);
// 3. 赋值保存
self.renderBuffer = buffer;
// 4. 绑定buffer
glBindRenderbuffer(GL_RENDERBUFFER, self.renderBuffer);
// 5. 将可绘制对象的layer存储绑定到OPenGL的renderBuffer中
[self.myContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.myEagLayer];
}
0x05 设置FrameBuffer
- (void)setupFrameBuffer {
// 1. 定义一个缓冲区
GLuint buffer;
// 2. 创建一个buffer
glGenRenderbuffers(1, &buffer);
// 3. 保存值
self.frameBuffer = buffer;
// 4. 绑定frame buffer
glBindFramebuffer(GL_FRAMEBUFFER, self.frameBuffer);
// 5. 将renderbuffer 与 fame buffer进行绑定
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, self.renderBuffer);
}
0x06 开始绘制
- (void)renderLayer {
// 清屏颜色
glClearColor(0.3, 0.3, 0.3, 0.8);
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);
// 读取顶点着色器,片元着色器
NSString *vertFile = [[NSBundle mainBundle] pathForResource:@"shader" ofType:@"vsh"];
NSString *fragFile = [[NSBundle mainBundle] pathForResource:@"shader" ofType:@"fsh"];
NSLog(@"vertFile:%@",vertFile);
NSLog(@"fragFile:%@",fragFile);
// 3. 加载shader
self.myProgram = [self loadShaders:vertFile withFrag:fragFile];
// 4. 链接
glLinkProgram(self.myProgram);
// 获取链接状态
GLint linkStatus;
glGetProgramiv(self.myProgram, GL_LINK_STATUS, &linkStatus);
if (linkStatus == GL_FALSE) {
GLchar message[512];
glGetProgramInfoLog(self.myProgram, sizeof(message), 0, &message[0]);
NSString *messageString = [NSString stringWithUTF8String:message];
NSLog(@"Program Link Error:%@",messageString);
return;
}
NSLog(@"Program Link Success!");
// 5. 使用program
glUseProgram(self.myProgram);
// 6. 设置顶点,纹理坐标
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,
};
// 7. 处理顶点数据
// 7.1 顶点缓存区
GLuint attrBuffer;
// 7.2 申请一个缓存区标识符
glGenBuffers(1, &attrBuffer);
// 7.3 将attrBuffer绑定到GL_ARRAY_BUFFER标识符上
glBindBuffer(GL_ARRAY_BUFFER, attrBuffer);
// 7.4 把顶点数据从CPU内存赋值到GPU上
glBufferData(GL_ARRAY_BUFFER, sizeof(attrArr), attrArr, GL_DYNAMIC_DRAW);
// 8. 将顶点数据通过myPrograme 传递到顶点着色器程序的position
// 8.1 glgetAttribLocation,用来获取vertex attribute的入口的
// 8.2 告诉OPenGL ES,通过glEnableVertextAttribArray
// 8.3 最后数据是通过glVertexAttribPointer传递过去的。
// 注意第二个字符串必须和shader.vsh中的输入变量保持一致
GLuint position = glGetAttribLocation(self.myProgram, "position");
// 设置何时的格式从buffer里读取数据
glEnableVertexAttribArray(position);
// 设置读取方式
glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat) * 5, NULL);
// 处理纹理数据
GLuint textCoor = glGetAttribLocation(self.myProgram, "textCoordinate");
// 设置何时的格式从buffer里面读取数据
glEnableVertexAttribArray(textCoor);
glVertexAttribPointer(textCoor, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, (float *)NULL + 3);
// 10. 加载纹理
[self setupTexture:@"kunkun"];
// 11. 设置纹理采样器 sampler2D
glUniform1i(glGetUniformLocation(self.myProgram, "colorMap"), 0);
// 12. 绘图
glDrawArrays(GL_TRIANGLES, 0, 6);
// 13. 从渲染缓存区显示到屏幕上
[self.myContext presentRenderbuffer:GL_RENDERBUFFER];
}
0x07 其他辅助逻辑
加载着色器文件
- (GLuint)loadShaders:(NSString *)verFile withFrag:(NSString *)fragFile {
// 1. 定义两个临时着色器对象
GLuint verShader, fragShader;
// 2. 创建program
GLint program = glCreateProgram();
[self compileShader:&verShader type:GL_VERTEX_SHADER file:verFile];
[self compileShader:&fragShader type:GL_FRAGMENT_SHADER file:fragFile];
// 3. 创建最终的程序
glAttachShader(program, verShader);
glAttachShader(program, fragShader);
// 4. 释放不需要的shader
glDeleteShader(verShader);
glDeleteShader(fragShader);
return program;
}
** 编译着色器**
- (void)compileShader:(GLuint *)shader type:(GLenum)type file:(NSString *)file {
// 1. 读取文件路径字符串
NSString *content = [NSString stringWithContentsOfFile:file encoding:NSUTF8StringEncoding error:nil];
const GLchar * source = (GLchar *)[content UTF8String];
// 2. 创建一个shader(根据type)
*shader = glCreateShader(type);
// 3. 将着色器源码附加到着色器对象上
/* 1. shader, 要编译的着色器对象
* 2. numOfStrings, 传递的源码字符串数量 1个
* 3. strings, 着色器程序的源码(真正的程序源码)
* 4. lenOfStrings, 长度,具有每个字符串长度的数组,或NULL,这意味着字符串是NULL终止的
*/
glShaderSource(*shader, 1, &source, NULL);
// 4. 把着色器源代码编译成目标代码
glCompileShader(*shader);
}
加载纹理
- (GLuint)setupTexture:(NSString *)textureName {
// 1. 将UIImage 转换为CGImageRef
CGImageRef spriteImage = [UIImage imageNamed:textureName].CGImage;
// 2. 判断图片是否获取成功
if (!spriteImage) {
NSLog(@"Failed to load image %@");
exit(1);
}
// 2. 读取图片大小,宽和高
size_t width = CGImageGetWidth(spriteImage);
size_t height = CGImageGetHeight(spriteImage);
// 3. 获取图片字节数 宽*高*4 (RGBA)
GLbyte *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上使用的颜色空间 KCGImageAlphaPremulatipliedLast: RGBA
*/
CGContextRef spriteContext = CGBitmapContextCreate(spriteData, width, height, 8, width * 4, CGImageGetColorSpace(spriteImage), kCGImageAlphaPremultipliedLast);
// 将图片绘制出来
CGRect rect = CGRectMake(0, 0, width, height);
// 6. 使用默认方式绘制
CGContextDrawImage(spriteContext, rect, spriteImage);
// 7. 画图完毕就释放上下文
CGContextRelease(spriteContext);
// 8. 绑定纹理到默认的纹理ID
glBindTexture(GL_TEXTURE_2D, 0);
// 9. 设置纹理属性
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数据
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, fw, fh, 0, GL_RGBA, GL_UNSIGNED_BYTE, spriteData);
// 11. 释放spriteData
free(spriteData);
return 0;
}
frameBuffer
与 RenderBuffer关系
总结
此篇文章主要是为了熟悉OpenGL ES
的基本开发流程,及着色器的基本使用方法。