一、渲染结构
OpenGL的渲染结构
Client:是指常见的iOS代码和OpenGL API方法,这部分是在CPU中运行
Server:是指OpenGL底层的渲染等处理,是运行在GPU中的
attribute属性, 直接用于顶点着色器, 不能直接传递到片元着色器, 通过GLSL代码间接传递到片元着色器。
Uniform 值(只是一个通道, 可传矩阵,可传单个值), 可直接传递到片元着色器和顶点着色器。
Vertex Shader: 顶点着色器 (可编程)
**Primitive Assembly **(图元装配 也叫光栅化): 顶点着色器 -> 光栅化(过程开发者是不可干预的) -> Fragment Shader, 只能编程Vertex Shader 和 Fragment Shader。
Fragment Shader:片元着色器(可编程)
TexTure Data:纹理数据 -> 顶点着色器,片元着色器
渲染一个图形: 比如渲染一个正方形 如果是渲染为蓝色 那么可以直接使用颜色去处理, 不用使用纹理, 要填充图片的话, 才需要使用到纹理。
OpenGL 为什么能够跨平台有一个很重要的原因是因为OpenGL是没有自己的窗口系统的, 每个平台需要使用OpenGL需要配合OpenGL集成一个自己的窗口系统。
iOS下的渲染架构
Core Animation: 本质上可以理解为一个复合引擎,主要职责包含:渲染,构建和实现动画。
二、离屏渲染
老生常谈的离屏渲染,无非就是cornerRadius,masksToBounds = YES, 我们来探究一下引起离屏渲染的真正因素。
先来一段简单的代码。
UIButton *btn1 = [[UIButton alloc] initWithFrame:CGRectMake(100, 50, 100, 100)];
btn1.layer.cornerRadius = 50;
[btn1 setImage:[UIImage imageNamed:@"Popup_new1"] forState:UIControlStateNormal];
btn1.clipsToBounds = YES;
[self.view addSubview:btn1];
UIButton *btn2 = [[UIButton alloc] initWithFrame:CGRectMake(100, 200, 100, 100)];
btn2.layer.cornerRadius = 50;
[btn2 setBackgroundColor:UIColor.redColor];
btn2.clipsToBounds = YES;
[self.view addSubview:btn2];
UIImageView *imgView1 = [[UIImageView alloc] initWithFrame:CGRectMake(100, 400, 100, 100)];
imgView1.image = [UIImage imageNamed: @"Popup_new1"];
imgView1.layer.cornerRadius = 50;
imgView1.layer.masksToBounds = YES;
imgView1.backgroundColor = [UIColor redColor];
[self.view addSubview:imgView1];
UIImageView *imgView3 = [[UIImageView alloc] initWithFrame:CGRectMake(100, 550 , 100, 100)];
imgView3.image = [UIImage imageNamed: @"Popup_new1"];
imgView3.layer.cornerRadius = 50;
imgView3.layer.masksToBounds = YES;
[self.view addSubview:imgView3];
运行结果
以上现象说明了什么。
设置了cornerRadius和masksToBounds = YES 不一定会造成离屏渲染.
图片正常渲染,没有离屏的时候 流程是 APP -> 帧缓存区 -> 显示到界面上
离屏渲染: APP -> 离屏缓存区 -> 帧缓存区 -> 显示
为什么单独只设置了背景色或者图片的时候不会离屏,一般情况下 GPU渲染都会遵循画家算法,由远至近,一层一层的渲染,渲染完一个张后帧缓存区的数据会清空。
我们在切圆角的时候, 此时是只有一个图层,渲染好这张图片直接切了显示到界面上就可以了。
如果有多张图层的时候,先渲染好的图层需要等待其他的图层都渲染完成 ,如果不另外保存起来,那么之前的数据都清空了,那么久没有办法去处理圆角了。这个就叫离屏渲染,需要多开辟一个离屏缓存区,用于存放渲染后的数据再统一处理。
比较UIButton 和UIImageView
iOS的button setImage 是添加了一个imgaeView的,所以就有两个图层,所以在切button的圆角时 会造成离屏渲染,而如果只切button中的imageView圆角,是不会的,大家可以自己试一试。
cornerRadius+masksToBounds 只有在设置了content且背景不是透明时,才会出现离屏渲染。
三、iOS下常用圆角处理方案
方案一
imageView.clipsToBounds = YES;
imageVieiw.layer.cornerRadius = 4.0;
方案二
- (UIImage *)roundedCornerImageWithCornerRadius: (CGFloat)cornerRadius {
CGFloat w = self.size.width;
CGFloat h = self.size.height;
CGFloat scale = [UIScreen mainScreen].scale;
if (cornerRadius < 0) {
cornerRadius = 0;
} else if (cornerRadius > MIN(w, h)) {
cornerRadius = MIN(w, h) / 2.;
}
UIImage *image = nil;
CGRect imageFrame = CGRectMake(0.0, 0.0, w, h);
UIGraphicsBeginImageContextWithOptions(self.size, NO, scale);
[[UIBezierPath bezierPathWithRoundedRect:imageFrame cornerRadius:cornerRadius] addClip];
[self drawInRect:imageFrame];
image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return image;
}
方案三
- (void)addMaskToBounds: (CGRect)maskBounds cornerRadius:(CGFloat)cornerRadius {
CGFloat w = maskBounds.size.width;
CGFloat h = maskBounds.size.height;
CGSize size = maskBounds.size;
CGFloat scale = [UIScreen mainScreen].scale;
CGRect imageRect = CGRectMake(0, 0, w, h);
if (cornerRadius < 0) {
cornerRadius = 0;
} else if (cornerRadius > MIN(w, h)) {
cornerRadius = MIN(w, h) / 2.;
}
UIImage *image = nil;
UIGraphicsBeginImageContextWithOptions(self.size, NO, scale);
[[UIBezierPath bezierPathWithRoundedRect:imageRect cornerRadius:cornerRadius] addClip];
[image drawInRect:imageRect];
self.roundImage = UIGraphicsGetImageFromCurrentImageContext();
self.image = self.roundImage;
UIGraphicsEndImageContext();
}
常见的触发离屏渲染的几种情况
- 使⽤了 mask 的 layer (layer.mask)
- 需要进⾏裁剪的 layer (layer.masksToBounds / view.clipsToBounds)
- 设置了组透明度为 YES,并且透明度不为 1 的 layer (layer.allowsGroupOpacity/layer.opacity)
- 添加了投影的 layer (layer.shadow*)
- 采⽤了光栅化的 layer (layer.shouldRasterize)
- 绘制了⽂字的 layer (UILabel, CATextLayer, Core Text 等)