OpenGL ES案例 多分屏滤镜
这个案例我们将使用自定义的顶点着色器和片元着色器来实现图片的多屏显示,最终效果如下图所示:
Simulator Screen Shot - iPhone SE -2nd generation- - 2020-08-08 at 18.43.21.png Simulator Screen Shot - iPhone SE -2nd generation- - 2020-08-08 at 18.43.26.png上面两图是一个一屏和九屏的效果图,还有4种效果,这边就不再贴图显示了。
首先我们来实现一屏的效果(下面的代码只贴出核心代码)。
图片的现实,需要设置顶点坐标和纹理坐标,这边利用一个结构体来实现坐标的存储
typedef struct {
GLKVector3 positionCoord;//(x,y,z)
GLKVector2 textureCoord; //(U,V)
}SenceVertex;
//坐标数据
self.vertices[0] = (SenceVertex){{-1,1,0},{0,1}};
self.vertices[1] = (SenceVertex){{-1,-1,0},{0,0}};
self.vertices[2] = (SenceVertex){{1,1,0},{1,1}};
self.vertices[3] = (SenceVertex){{1,-1,0},{1,0}};
在解决顶点坐标和纹理坐标之后,我们来开始实现绘制贴图所需要的属性和着色器。
1.首先初始化上下文并设置为当前上下文
self.context = [[EAGLContext alloc]initWithAPI:kEAGLRenderingAPIOpenGLES2];
[EAGLContext setCurrentContext:self.context];
2.创建图层,并设置位置和scale
CAEAGLLayer *layer = [[CAEAGLLayer alloc]init];
layer.frame = CGRectMake(0, 100,self.view.frame.size.width, self.view.frame.size.width);
layer.contentsScale = [[UIScreen mainScreen]scale];
3.绑定渲染缓冲区,这边封装成一个方法去使用
-(void)bindRenderLayer:(CALayer <EAGLDrawable> *)layer{
//1.渲染缓冲区,帧缓冲区
GLuint renderBuffer;
GLuint frameBuffer;
//2.获取帧渲染缓存区名称,绑定渲染缓冲区以及将渲染缓存区与layer建立连接
glGenRenderbuffers(1, &renderBuffer);
glBindRenderbuffer(GL_RENDERBUFFER, renderBuffer);
[self.context renderbufferStorage:GL_RENDERBUFFER fromDrawable:layer];
//3.获取帧缓存区名称,绑定帧缓存区以及将渲染缓存区附着到帧缓存区上
glGenFramebuffers(1, &frameBuffer);
glBindFramebuffer(GL_FRAMEBUFFER, frameBuffer);
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, renderBuffer);
}
4.处理图片数据和将图片转成纹理图片
GLuint texture2D = [self createTextureWithImage:image];
self.textureID = texture2D;
5.设置视口和顶点缓冲区
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.vertices, GL_STATIC_DRAW);
6.接下来就是设置着色器。
1、这边先实现顶点着色器和片元着色器的自定义
```
//顶点着色器
attribute vec4 Position;
attribute vec2 TextureCoords;
varying vec2 TextureCoordsVarying;
void main(void){
gl_Position = Position;
TextureCoordsVarying = TextureCoords;
}
片元着色器
precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;
void main(void){
vec4 mask = texture2D(Texture,TextureCoordsVarying);
gl_FragColor = vec4(mask.rgb,1.0);
}
```
2、编译和link着色器
-(GLuint)programWithShaderName:(NSString *)shaderName{
//1.编译顶点着色器/片源着色器
GLuint vertexShader = [self compileShaderWithName:shaderName type:GL_VERTEX_SHADER];
GLuint fragmentShader = [self compileShaderWithName:shaderName type:GL_FRAGMENT_SHADER];
//2.将顶点/片元附着到program
GLuint program = glCreateProgram();
glAttachShader(program, vertexShader);
glAttachShader(program, fragmentShader);
//3.link
glLinkProgram(program);
//4.检查link是否成功
GLint success;
glGetProgramiv(program, GL_LINK_STATUS, &success);
if(success == GL_FALSE){
GLchar message[256];
glGetProgramInfoLog(program, sizeof(message), 0, &message[0]);
NSString *messageString = [NSString stringWithUTF8String:message];
NSAssert(NO, @"program链接失败:%@",messageString);
return 0;
}
//5.返回program
return program;
}
-(GLuint)compileShaderWithName:(NSString *)name type:(GLenum)shaderType{
//1.获取shader路径
NSString *shaderPath = [[NSBundle mainBundle]pathForResource:name ofType:shaderType == GL_VERTEX_SHADER ? @"vsh" : @"fsh"];
NSError *error;
NSString *shaderString = [NSString stringWithContentsOfFile:shaderPath encoding:NSUTF8StringEncoding error:&error];
if(!shaderString){
NSAssert(NO, @"读取失败");
return 0;
}
//2.创建shader
GLuint shader = glCreateShader(shaderType);
//3.获取shader source
const char *shaderStringUTF8 = [shaderString UTF8String];
int shaderStringLength = (int)[shaderString length];
glShaderSource(shader, 1, &shaderStringUTF8, &shaderStringLength);
//4.编译shader
glCompileShader(shader);
//5.查看编译是否成功
GLint success;
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if(success == GL_FALSE){
GLchar message[256];
glGetShaderInfoLog(shader, sizeof(message), 0, &message[0]);
NSString *messageString = [NSString stringWithUTF8String:message];
NSAssert(NO, @"shader编译失败:%@",messageString);
return 0;
}
//6.返回shader
return shader;
}
3、初始化着色器程序
-(void)setupShaderProgramWithName:(NSString *)name{
//1.获取着色器program
GLuint program = [self programWithShaderName:name];
//2.使用program
glUseProgram(program);
//3.获取position,texture,textureCoords的索引位置
GLuint positionSlot = glGetAttribLocation(program, "Position");
GLuint textureSlot = glGetUniformLocation(program, "Texture");
GLuint textureCoordSlot = glGetAttribLocation(program, "TextureCoords");
//4.激活纹理,绑定纹理ID
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, self.textureID);
//5.纹理sample
glUniform1i(textureSlot, 0);
//6.打开positionSlot属性并传递数据到positionSlot(顶点坐标)
glEnableVertexAttribArray(positionSlot);
glVertexAttribPointer(positionSlot, 3, GL_FLOAT, GL_FALSE, sizeof(SenceVertex), NULL+offsetof(SenceVertex, positionCoord));
//7.打开textureCoordSlot属性并传递数据到textureCoordslot(纹理坐标)
glEnableVertexAttribArray(textureCoordSlot);
glVertexAttribPointer(textureCoordSlot, 2, GL_FLOAT, GL_FALSE, sizeof(SenceVertex), NULL+offsetof(SenceVertex, textureCoord));
//8.保存program ,界面销毁则释放
self.program = program;
}
7.绘制
-(void)render{
//清除画布
glClear(GL_COLOR_BUFFER_BIT);
glClearColor(1, 1, 1, 1);
glUseProgram(self.program);
glBindBuffer(GL_ARRAY_BUFFER, self.vertexBuffer);
glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
[self.context presentRenderbuffer:GL_RENDERBUFFER];
}
在写完上面的代码之后(贴出来的代码不完整,完整代码请下载下面的demo),图片就能够正常显示。
在实现一屏滤镜现实后,我们来实现一下多屏滤镜的实现;那我们修改哪一块内容去实现效果呢?
我们知道,纹理图片的贴图是由着色器去实现的,因此我们只需要修改着色器代码和调用不同的着色器源码来实现不同屏的效果。
其实我们只需要修改片元着色器的代码,就可以实现效果。
在我们实际开发当中,我们需要分屏的内容或许并不一定是完整的一张图片,有可能是图片当中的一部分,因此我们需要根据图片的纹理坐标去绘制整张图片的一部分画面。
接下来我们通过几张图片来观察一下2屏,3屏,4屏等纹理坐标的变换。
15968851413980.png看上图中间的方框,就是我们需要现实的一半的画面,从坐标的看出,我们只需要修改y轴坐标。
片元着色器代码
precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;
void main(void){
vec2 uv = TextureCoordsVarying.xy;
float y;
if(uv.y>=0.0&&uv.y<=0.5){
y = uv.y+0.25;
}else{
y = uv.y-0.25;
}
// vec4 mask = texture2D(Texture,TextureCoordsVarying);
gl_FragColor = texture2D(Texture,vec2(uv.x,y));
}
首先获取纹理坐标的x,y轴,通过对y轴进行修改,来实现。
那么三屏其实也是一样的,图片就不现实了,直接贴代码
precision highp float;
uniform sampler2D Texture;
varying highp vec2 TextureCoordsVarying;
void main() {
vec2 uv = TextureCoordsVarying.xy;
if (uv.y < 1.0/3.0) {
uv.y = uv.y + 1.0/3.0;
} else if (uv.y > 2.0/3.0){
uv.y = uv.y - 1.0/3.0;
}
gl_FragColor = texture2D(Texture, uv);
}
4屏的现实就是4张完整的图片组合在一起
15968854119218.png那我们就需要同时修改x轴和y轴的坐标
precision highp float;
uniform sampler2D Texture;
varying highp vec2 TextureCoordsVarying;
void main() {
vec2 uv = TextureCoordsVarying.xy;
if(uv.x<= 0.5){
uv.x = uv.x * 2.0;
}else{
uv.x = (uv.x - 0.5) * 2.0;
}
if (uv.y <= 0.5) {
uv.y = uv.y * 2.0;
}else{
uv.y = (uv.y - 0.5) * 2.0;
}
gl_FragColor = texture2D(Texture, uv);
}
通过对x,y前半轴的一部分乘2和后半部分减去一半乘二来实现4屏效果
那么6屏效果原理也差不多
precision highp float;
uniform sampler2D Texture;
varying highp vec2 TextureCoordsVarying;
void main() {
vec2 uv = TextureCoordsVarying.xy;
if(uv.x<= 1.0/3.0){
uv.x = uv.x + 1.0/3.0;
}else if(uv.x >= 2.0/3.0){
uv.x = uv.x - 1.0/3.0;
}
if (uv.y <= 0.5) {
uv.y = uv.y +0.25;
}else{
uv.y = uv.y - 0.25;
}
gl_FragColor = texture2D(Texture, uv);
}
9屏:
precision highp float;
uniform sampler2D Texture;
varying highp vec2 TextureCoordsVarying;
void main() {
vec2 uv = TextureCoordsVarying.xy;
if(uv.x<= 1.0/3.0){
uv.x = uv.x * 3.0;
}else if(uv.x<= 2.0/3.0){
uv.x = (uv.x - 1.0/3.0)*3.0;
}else{
uv.x = (uv.x-2.0/3.0)*3.0;
}
if(uv.y<= 1.0/3.0){
uv.y = uv.y * 3.0;
}else if(uv.y<= 2.0/3.0){
uv.y = (uv.y - 1.0/3.0)*3.0;
}else{
uv.y = (uv.y - 2.0/3.0)*3.0;
}
gl_FragColor = texture2D(Texture, uv);
}
那么整个案例的核心代码都已经贴出来了,完整代码demo