OPenGLES 在iOS中的开发流程记录

560 阅读4分钟

不采用GLKBaseEffect,使用编译连接自定义的着色器(shader)。用简单的glsl语言来实现顶点、片元着色器,并图形进行简单的变换。

主要思路

  1. 创建图层
  2. 创建上下文
  3. 清空缓存区
  4. 设置RenderBuffer
  5. 设置FrameBuffer
  6. 开始绘制

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;
}

frameBufferRenderBuffer关系

frameBuffer与RenderBuffer


总结

此篇文章主要是为了熟悉OpenGL ES的基本开发流程,及着色器的基本使用方法。