技术要点:
1.实现CAEAGLLayer添加特殊图层到VC;
2.设置并加载纹理;
3.绑定顶点、帧缓冲区;
4.初始化着色器程序并链接program;
5.编译着色器程序;
分屏原理
先走一波「死亡」凝视
- 1. 二分屏
这是纹理坐标映射到屏幕顶点,且代码中实现了翻转,所以从左上(0,0)开始。
为了截取图片中心有效内容实现二分屏,故从纹理图片中截取Y轴下 [0.25, 0.75] 范围的内容,对应到实际屏幕(0,0)坐标实际取值为纹理的(0,0.25);在Y值小于0.5时,换算到Y轴表达式:newY = textureY + 0.25;Y轴大于0.5时,换算Y轴表达式:newY = textureY - 0.25 ;
片元着色器程序文件
precision highp float;
uniform sampler2D un_texture;
varying vec2 var_textureCoords;
void main()
{
vec2 var_xy = var_textureCoords.xy;
if (var_xy.y > 0.0 && var_xy.y < 0.5) {
var_xy.y = var_xy.y + 0.25;
}else{
var_xy.y = var_xy.y - 0.25;
}
gl_FragColor = texture2D(un_texture,vec2( var_xy.x, var_xy.y));
}
三分屏、六分屏和二分屏是相同原理,改变对应的映射值即可,具体见Demo 中的着色器程序;
- 2. 四分屏
四分屏和二分屏不一样,四分屏是在固定的屏幕区域,将纹理的原尺寸缩小一倍,这样实现了原画均匀输出在一分为四的显示区域,不会有拉伸or放大显示了图片的某个区域,且不失真,以达到更好的体验。x, y
在 0 -- 0.5 范围内,要想获取整个纹理全输出,就得将传进的纹理坐标 乘2,当x, y
在 0.5 -- 1.0范围时,原纹理坐标就得 减0.5 再 乘2 ;
precision highp float;
uniform sampler2D un_texture;
varying vec2 var_textureCoords;
void main()
{
vec2 var_xy = var_textureCoords.xy;
if (var_xy.x <= 0.5) {
var_xy.x = var_xy.x * 2.0;
}else{
var_xy.x = (var_xy.x - 0.5) * 2.0;
}
if (var_xy.y <= 0.5) {
var_xy.y = var_xy.y * 2.0;
}else{
var_xy.y = (var_xy.y - 0.5) * 2.0;
}
gl_FragColor = texture2D(un_texture,vec2(var_xy.x, var_xy.y));
}
九分屏原理和四分屏一样,出现多的一个判断是在 1/3 至 2/3 范围时,x,y轴的取值。
precision highp float;
uniform sampler2D un_texture;
varying vec2 var_textureCoords;
void main()
{
vec2 var_xy = var_textureCoords.xy;
if (var_xy.x <= 1.0/3.0) {
var_xy.x = var_xy.x * 3.0;
}else if (var_xy.x > 1.0/3.0 && var_xy.x < 2.0/3.0){
var_xy.x = (var_xy.x - 1.0/3.0) * 3.0;
}else if (var_xy.x >= 2.0/3.0){
var_xy.x = (var_xy.x - 2.0/3.0) * 3.0;
}
if (var_xy.y <= 1.0/3.0) {
var_xy.y = var_xy.y * 3.0;
}else if (var_xy.y > 1.0/3.0 && var_xy.y < 2.0/3.0){
var_xy.y = (var_xy.y - 1.0/3.0) * 3.0;
}else if (var_xy.y >= 2.0/3.0){
var_xy.y = (var_xy.y - 2.0/3.0) * 3.0;
}
gl_FragColor = texture2D(un_texture,vec2( var_xy.x, var_xy.y));
}
主要实现
初始化Something
- 初始化 EAGAContext & CAEAGLLayer
- 准备数组
- 绑定渲染 & 帧 缓冲区
#pragma mark - 初始化something
-(void)initContextAndCALayer
{
self.mContext = [[EAGLContext alloc] initWithAPI:kEAGLRenderingAPIOpenGLES3];
if (![EAGLContext setCurrentContext:self.mContext]) {
NSLog(@"setCurrentContext failed");
return;
}
//2.开辟顶点数组内存空间
self.mVertices = malloc(sizeof(SenceVertex) * 4);
//3.初始化顶点(0,1,2,3)的顶点坐标以及纹理坐标
self.mVertices[0] = (SenceVertex){{-1, 1, 0}, {0, 1}};
self.mVertices[1] = (SenceVertex){{-1, -1, 0}, {0, 0}};
self.mVertices[2] = (SenceVertex){{1, 1, 0}, {1, 1}};
self.mVertices[3] = (SenceVertex){{1, -1, 0}, {1, 0}};
// 创建CAEAGLayer图层
self.mEagaLayer = [[CAEAGLLayer alloc] init];
self.mEagaLayer.frame = CGRectMake(0, 150, self.view.frame.size.width, self.view.frame.size.width);
self.mEagaLayer.contentsScale = [[UIScreen mainScreen] scale];
[self.view.layer addSublayer:self.mEagaLayer];
//=====绑定渲染缓冲区 & 帧缓冲区======
// 渲染缓存区,帧缓存区对象
GLuint renderBuffer,frameBuffer;
// 获取帧渲染缓存区名称,绑定渲染缓存区以及将渲染缓存区与layer建立连接
glGenBuffers(1, &renderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
[self.mContext renderbufferStorage:GL_RENDERBUFFER fromDrawable:self.mEagaLayer];
// 获取帧缓存区名称,绑定帧缓存区以及将渲染缓存区附着到帧缓存区上
glGenBuffers(1, &frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer);
//=====加载纹理======
//
self.textureID = [self createTextureIDWithImage];
// 设置视口
glViewport(0, 0, self.drawableWidth, self.drawableHeight);
// 设置顶点缓冲区
GLuint vertexBuffer;
glGenBuffers(1, &vertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, vertexBuffer);
GLsizeiptr bufferSizeBytes = sizeof(SenceVertex) * 4;
glBufferData(GL_ARRAY_BUFFER, bufferSizeBytes, self.mVertices, GL_STATIC_DRAW);
// 设置默认着色器
// [self setupNormalShaderProgram]; // 一开始选用默认的着色器
[self setupShaderProgramWithName:@"normalShader"];
//10.将顶点缓存保存,退出时才释放
self.vertexBuffer = vertexBuffer;
}
纹理处理
- 加载纹理
/**
加载纹理,
@return 返回的纹理ID
*/
- (GLuint)createTextureIDWithImage
{
NSString * imgPath = [[NSBundle mainBundle] pathForResource:@"miao" ofType:@"jpg"];
UIImage * image = [UIImage imageWithContentsOfFile:imgPath];
// 将UIImage => CGImageRef
CGImageRef imageRef = image.CGImage;
if (!imageRef) {
NSLog(@"can not get imageRef");
return 0;
}
// 获取图片的数据 大小、宽高、字节数
GLuint imgWidth = (GLuint)CGImageGetWidth(imageRef);
GLuint imgHeight = (GLuint)CGImageGetHeight(imageRef);
GLubyte * imageData = (GLubyte *)calloc(imgWidth * imgHeight * 4, sizeof(GLubyte));
//获取图片的颜色空间
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
/** 创建上下文
para1: data,指向要渲染的绘制图像的内存地址
para2: width,bitmap的宽度,单位为像素
para3: height,bitmap的高度,单位为像素
para4: bitPerComponent,内存中像素的每个组件的位数,比如32位RGBA,就设置为8
para5: bytesPerRow,bitmap的没一行的内存所占的比特数
para6: colorSpace,bitmap上使用的颜色空间 kCGImageAlphaPremultipliedLast:RGBA
*/
CGContextRef imgContext = CGBitmapContextCreate(imageData, imgWidth, imgHeight, 8, imgWidth * 4, CGImageGetColorSpace(imageRef), kCGImageAlphaPremultipliedLast);
CGRect imgRect = CGRectMake(0, 0, imgWidth, imgHeight);
// 翻转图片
CGContextTranslateCTM(imgContext, 0, imgHeight);
CGContextScaleCTM(imgContext, 1.0f, -1.0f);
CGColorSpaceRelease(colorSpace);
CGContextClearRect(imgContext, imgRect);
// 重新绘制图——解压缩的位图
CGContextDrawImage(imgContext, imgRect, imageRef);
// 设置图片纹理属性
GLuint textureID;
glGenTextures(1, &textureID);
glBindTexture(GL_TEXTURE_2D, textureID);
// 载入纹理
/*
参数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, imgWidth, imgHeight, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);
// 设置纹理属性 过滤方式 + 环绕方式
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);
// 绑定纹理
glBindTexture(GL_TEXTURE_2D, 0);
CGContextRelease(imgContext);
free(imageData);
// 返回纹理id
return textureID;
}
- 设置默认着色器
/**
设置着色器programID
@param nameStr 着色程序文件名
*/
-(void)setupShaderProgramWithName:(NSString *)nameStr
{
// 获取着色器program
GLuint program = [self backProgramWithShaderName:nameStr];
// 2.使用program
glUseProgram(program);
// 3.获取 att_position att_textuteCoords un_texture 的索引位置
GLuint positionSlot = glGetAttribLocation(program, "att_position");
GLuint textureCoordSlot = glGetAttribLocation(program, "att_textuteCoords");
GLuint textureSlot = glGetAttribLocation(program, "un_texture");
// 4.激活纹理,绑定纹理id
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, self.textureID);
// 5.采样纹理
glUniform1i(textureSlot , 0);
// 6.打开positionSlolt属性,并将数据传递到 att_position 中
glEnableVertexAttribArray(positionSlot);
glVertexAttribPointer(positionSlot, 3, GL_FLOAT, GL_FALSE, sizeof(SenceVertex), NULL + offsetof(SenceVertex, positionCoord));
// 7.打开positionSlolt属性,并将数据传递到 att_position 中
glEnableVertexAttribArray(textureCoordSlot);
glVertexAttribPointer(textureCoordSlot, 2, GL_FLOAT, GL_FALSE, sizeof(SenceVertex), NULL + offsetof(SenceVertex, textureCoord));
// 8.保存program
self.programID = program;
}
/**
返回program
@param nameStr shaderName
@return 返回对应的program
*/
- (GLuint)backProgramWithShaderName:(NSString *)nameStr
{
// 1.编译顶点、片元着色器
GLuint vertexShader = [self compileShaderWithName:nameStr WithType:GL_VERTEX_SHADER];
GLuint fragmentShader = [self compileShaderWithName:nameStr WithType:GL_FRAGMENT_SHADER];
// 2.将顶点、片元着色程序着到program
GLuint program = glCreateProgram();
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
// 3.链接program
glLinkProgram(program);
// 4.获取链接program的状态
GLint linkStatus;
glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
if (linkStatus == GL_FALSE) {
GLchar messages[256];
glGetProgramInfoLog(program, sizeof(messages), 0, &messages[0]);
NSString *messageString = [NSString stringWithUTF8String:messages];
NSAssert(NO, @"program link err:%@", messageString);
exit(1);
}
// 5.返回program
return program;
}
- 编译着色程序
喵之偷懒:多分屏时,其顶点数据和无分屏时是一样的,在屏幕上的顶点坐标就可以共用一个顶点着色程序;多分屏处理的是纹理的映射变化,通过对图片的纹理操作,改变映射到屏幕的坐标来实现分屏效果。
/**
编译shader
@param shaderName 着色器文件名
@param shaderType 着色器类型
@return 返回对应的着色器
*/
- (GLuint)compileShaderWithName:(NSString *)shaderName WithType:(GLenum)shaderType
{
// 1.获取shader的path
// 使用2、3、4、6、9分屏,其顶点着色程序是不变的,故可使用用一个normalShader.vsh 文件
shaderName = shaderType == GL_VERTEX_SHADER ? @"normalShader" : shaderName;
NSString * shaderPath = [[NSBundle mainBundle] pathForResource:shaderName ofType:shaderType == GL_VERTEX_SHADER ? @"vsh" : @"fsh"];
NSString * shaderString = [NSString stringWithContentsOfFile:shaderPath encoding:NSUTF8StringEncoding error:nil];
if (!shaderString) {
NSAssert(NO, @"shader read failed");
return 0;
}
// 2.根据shaderType 创建对应的shader
GLuint shader = glCreateShader(shaderType);
// 3.获取shader Source
const char * shadertStrUtf8 = shaderString.UTF8String;
int shaderStringLength = (int)[shaderString length];
glShaderSource(shader, 1, &shadertStrUtf8, &shaderStringLength);
// 4.编译shader
glCompileShader(shader);
// 5.获取编译状态
GLint complieStatus;
glGetShaderiv(shader, GL_COMPILE_STATUS, &complieStatus);
if (complieStatus == GL_FALSE) {
GLchar message[256];
glGetShaderInfoLog(shader, sizeof(message), 0, &message[0]);
NSString * messageStr = [NSString stringWithUTF8String:message];
NSAssert(NO, @"shader compile error : %@",messageStr);
return 0;
}
// 6.返回shader
return shader;
}
github demo 地址