OpenGL ES-12-案例07-灰度&马赛克滤镜

1,060 阅读12分钟

⻢赛克效果就是把图⽚的⼀个相当⼤⼩的区域⽤同⼀个点的颜⾊来表示.可以认为是⼤规模的降低图像的分辨率,⽽让图像的⼀些细节隐藏起来。

其实整份代码与前一篇《分屏滤镜》相比,也就片元着色器部分发生了改变。
说白了,滤镜就是片元着色器像素值的处理。

一、效果图

二、流程图

三、着色器中的主要区别

顶点着色器中不需要改变,只在片元着色器改变像素绘制的点就ok了

1、顶点着色器

attribute vec4 Position;
attribute vec2 TextureCoords;
varying vec2 TextureCoordsVarying;

void main (void) {
    
    TextureCoordsVarying = TextureCoords;
    gl_Position = Position;
    
}

以下是不同滤镜的片元着色器代码

2、正常

precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;

void main (void) {
    vec4 mask = texture2D(Texture, TextureCoordsVarying);
    gl_FragColor = vec4(mask.rgb, 1.0);
}


3、灰度

灰度滤镜,说白了就是调整了每个像素显示颜色的亮度,使得整张图片显示一个颜色。

灰度滤镜的实现原理是让RGB值保持一个平衡并填充,或者只保留一个亮度值,即绿色,在人眼中,绿色的亮度是最显眼的,绿色值越深,在肉眼观察中图片越暗淡,这是眼睛的一种生理现象。

有5种方法可以通过调整RGB值来实现:

  • 前三种属于权值法:处理后的图片比较逼真
  1. 浮点算法:Gray = R * 0.3 + G * 0.59 + B * 0.11 【RGB的权重总和为1】
  2. 整数⽅法:Gray = ( R * 30 + G * 59 + B * 11) / 100 【RGB的权重总和为100】
  3. 移位⽅法:Gray = ( R * 76 + G * 151 + B* 28) >> 8
  4. 平均值法:Gray = ( R + G + B ) / 3 【处理后的图片比较柔和】
  5. 仅取绿⾊:Gray = G 【这种方式最方便简单,且易用】
precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;

//变换因子:RGB的权重值,绿色占比最高
const highp vec3 W = vec3(0.2125, 0.7154, 0.0721);

void main(){
    //获取每个像素在对应纹理坐标下的颜色值
    vec4 mask = texture2D(Texture, TextureCoordsVarying);
    
    //将 像素颜色 与 变换因子 相乘 得到灰度值(向量和向量 点乘 得到标量)
    float luminance = dot(mask.rgb, W);
    
    //将原本的r,g,b,a 最终转换成 灰度值 填充到像素里。即(luminance,luminance,luminance,1.0)
    gl_FragColor = vec4(vec3(luminance), 1.0);
}



4、颠倒

通过上一个案例,我们可以简单地来实现了,直接Y值取反就实现了~

precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;

void main (void) {
    
    vec2 uv = TextureCoordsVarying.xy;
    vec4 mask = texture2D(Texture, vec2(uv.x,1.0-uv.y));
    gl_FragColor = vec4(mask.rgb, 1.0);
}



5、正方形马赛克

马赛克的原理:把图片的一个相当大小的区域用同一个颜色值来表示,可以认为是大规模的降低图像的分辨率,从而让图像的一些细节隐藏起来。

举例说明:100x100的图片,每10x10的区域本里可能有100个不同颜色,现在只用一个颜色填充,整张图片看下来就模糊了。

步骤:

  1. 先把原图按我们设置的比例放大
  2. 根据比例计算每一个马赛克的大小,获得一个区域
  3. 把马赛克区域根据比例缩小,就拿到了原纹理图片中的位置
  4. 给这一个区域填充颜色

precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;
//TextureCoordsVarying原本传进来
//假定纹理图片有这么大
const vec2 TexSize = vec2(400.0, 400.0);
//然后一个马赛克占得位置这么大,其实是算的一个比例
const vec2 mosaicSize = vec2(16.0, 16.0);

void main (void) {
    //1、计算实际纹理的像素点位置。
    //其实也是个相对的值,因为TextureCoordsVarying原本的值并不能确定每一个像素点的位置,放大后更好找
    vec2 intXY = vec2(TextureCoordsVarying.x*TexSize.x, TextureCoordsVarying.y*TexSize.y);
    //2、计算一个小马赛克的大小,即一个色块的位置
    /*
    floor(x)函数:返回一个 小于或者等于x的最大的整数,也就是向下取整
    floor(intXY.x/mosaicSize.x)*mosaicSize.x 拿到小马赛克的X
    floor(intXY.y/mosaicSize.y)*mosaicSize.y 拿到小马赛克的Y
     */
    vec2 XYMosaic = vec2(floor(intXY.x/mosaicSize.x)*mosaicSize.x, floor(intXY.y/mosaicSize.y)*mosaicSize.y);
    //3、换算成在纹理坐标中真正的位置
    vec2 UVMosaic = vec2(XYMosaic.x/TexSize.x, XYMosaic.y/TexSize. y);
    //4、拿到⻢赛克后的纹理坐标中的颜⾊值,然后进行填充
    vec4 mask = texture2D(Texture, UVMosaic);
    gl_FragColor = vec4(mask.rgb, 1.0);
} 

6、六边形马赛克

六边形马赛克原理:将一张图片分割成由六边形组成,再取每个六边形的中心点画出一个个的矩形,根据矩形的奇偶排列情况求出对应的2个中心点,并计算纹理坐标与两个中心点的距离,根据距离判断,采取就近原则,当前的六边形就采用近的中心点的颜色值。

说直白点,一个图片分成N个六边形,然后找出所有六边形中心点连线能组个一个个矩阵,这样的矩形根据中心点排布,可分为4种,然后看每一个像素点的位置, 距离哪个中心点更近,这样像素就取那个中心点的颜色,最终,一个六边形同色块的马赛克就形成了。

看几行几列,是为了计算对应的两个中心点,方便后面取值。

如何计算某一个像素点在这四种矩形中的具体坐标呢?

然后,计算这个点到两个中心点v1和v2的距离,距离谁比较近,取谁的颜色值就完成了。

  • s1 = √((v1.x-x)² + (v1.y-y)²)
  • s2 = √((v2.x-x)² + (v2.y-y)²)
precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;
//定义一个马赛克边长
const float mosaicSize = 0.03;
void main (void) {
    
    float length = mosaicSize;
    //根据3:√3设定宽高
    //定义矩形的宽
    float TB = 1.5;
    //定义矩形的高
    float TR = 0.866025;
    
    //拿到当前纹理坐标
    float x = TextureCoordsVarying.x;
    float y = TextureCoordsVarying.y;
    
    //换算成在矩形中的坐标
    int wx = int(x / TB / length);
    int wy = int(y / TR / length);
    
    //定义中心点v1  v2 和最终选的的点
    vec2 v1, v2, vn;
    
    //分4种情况判断取这个点的颜色值。就要先拿到对应2个中心点坐标
    //wx/2 * 2 == wx 就是偶数行   wy/2 * 2 == wy就是偶数列
    if (wx/2 * 2 == wx) {
       
        if (wy/2 * 2 == wy) {
            //(0,0),(1,1)
            v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy));
            v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy + 1));
        } else {
            //(0,1),(1,0)
            v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy + 1));
            v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy));
        }
    }else {
        if (wy/2 * 2 == wy) {
            //(0,1),(1,0)
            v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy + 1));
            v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy));
        } else {
            //(0,0),(1,1)
            v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy));
            v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy + 1));
        }
    }
    
    
    //计算现在这个像素点到两个中心点之间的距离
    float s1 = sqrt(pow(v1.x - x, 2.0) + pow(v1.y - y, 2.0));
    float s2 = sqrt(pow(v2.x - x, 2.0) + pow(v2.y - y, 2.0));
    
    //比较,哪个距离小,这个像素点的颜色就取哪个中心点颜色
    if (s1 < s2) {
        vn = v1;
    } else {
        vn = v2;
    }
    vec4 color = texture2D(Texture, vn);
    
    gl_FragColor = color;
}



7、三角形马赛克

三角形马赛克是在六边形马赛克的基础上得到的,是把正六边形马赛克进行6等分,得到6个正三角形。

  1. 计算某一个像素点和中心点直接的夹角
  2. 求出6个正三角形的中心点
  3. 判断这个夹角属于哪个正三角形,就取哪个正三角形的颜色值

precision highp float;
uniform sampler2D Texture;
varying vec2 TextureCoordsVarying;
//定义一个马赛克边长
const float mosaicSize = 0.03;
void main (void) {
    
    float length = mosaicSize;
    //根据3:√3设定宽高
    //定义矩形的宽
    float TB = 1.5;
    //定义矩形的高
    float TR = 0.866025;
    //π/6的值
    const float PI6 = 0.523599;
    //拿到当前纹理坐标
    float x = TextureCoordsVarying.x;
    float y = TextureCoordsVarying.y;
    
    //换算成在矩形中的坐标
    int wx = int(x / TB / length);
    int wy = int(y / TR / length);
    
    //定义中心点v1  v2 和最终选的的点
    vec2 v1, v2, vn;
    
    //分4种情况判断取这个点的颜色值。就要先拿到对应2个中心点坐标
    //wx/2 * 2 == wx 就是偶数行   wy/2 * 2 == wy就是偶数列
    if (wx/2 * 2 == wx) {
       
        if (wy/2 * 2 == wy) {
            //(0,0),(1,1)
            v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy));
            v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy + 1));
        } else {
            //(0,1),(1,0)
            v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy + 1));
            v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy));
        }
    }else {
        if (wy/2 * 2 == wy) {
            //(0,1),(1,0)
            v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy + 1));
            v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy));
        } else {
            //(0,0),(1,1)
            v1 = vec2(length * 1.5 * float(wx), length * TR * float(wy));
            v2 = vec2(length * 1.5 * float(wx + 1), length * TR * float(wy + 1));
        }
    }
    
    
    //计算现在这个像素点到两个中心点之间的距离
    float s1 = sqrt(pow(v1.x - x, 2.0) + pow(v1.y - y, 2.0));
    float s2 = sqrt(pow(v2.x - x, 2.0) + pow(v2.y - y, 2.0));
    
    //比较,哪个距离小,这个像素点的颜色就取哪个中心点颜色
    if (s1 < s2) {
        vn = v1;
    } else {
        vn = v2;
    }
    
    
    //计算像素和中心点vn的夹角
    float a = atan((x - vn.x)/(y - vn.y));
    
    //计算6个正三角形的中心点
    vec2 area1 = vec2(vn.x, vn.y - mosaicSize * TR / 2.0);
    vec2 area2 = vec2(vn.x + mosaicSize / 2.0, vn.y - mosaicSize * TR / 2.0);
    vec2 area3 = vec2(vn.x + mosaicSize / 2.0, vn.y + mosaicSize * TR / 2.0);
    vec2 area4 = vec2(vn.x, vn.y + mosaicSize * TR / 2.0);
    vec2 area5 = vec2(vn.x - mosaicSize / 2.0, vn.y + mosaicSize * TR / 2.0);
    vec2 area6 = vec2(vn.x - mosaicSize / 2.0, vn.y - mosaicSize * TR / 2.0);
    
    //判断在哪个区域
    if (a >= PI6 && a < PI6 * 3.0) {
        vn = area1;
    } else if (a >= PI6 * 3.0 && a < PI6 * 5.0) {
        vn = area2;
    } else if ((a >= PI6 * 5.0 && a <= PI6 * 6.0)|| (a<-PI6 * 5.0 && a>-PI6*6.0)) {
        vn = area3;
    } else if (a < -PI6 * 3.0 && a >= -PI6 * 5.0) {
        vn = area4;
    } else if(a <= -PI6 && a> -PI6 * 3.0) {
        vn = area5;
    } else if (a > -PI6 && a < PI6)
    {
        vn = area6;
    }
    
    //拿的最终所在区域的三角形中心点颜色
    vec4 color = texture2D(Texture, vn);
    gl_FragColor = color;
     
}



四、代码部分


 #import "ViewController.h"
 #import <GLKit/GLKit.h>
 #import "FilterBar.h"

 //之前也提到过,c语言结构体,存放顶点数据
 typedef struct {
     GLKVector3 positionCoord; // (X, Y, Z)
     GLKVector2 textureCoord; // (U, V)
 } SenceVertex;


 @interface ViewController ()<FilterBarDelegate>
 // 顶点数组
 @property (nonatomic, assign) SenceVertex *vertices;
 // 上下文
 @property (nonatomic, strong) EAGLContext *context;
 // 用于刷新屏幕的专属定时器(相比timer,它可以和屏幕刷新同频)
 @property (nonatomic, strong) CADisplayLink *displayLink;
 // 着色器程序
 @property (nonatomic, assign) GLuint program;
 // 顶点缓冲区id
 @property (nonatomic, assign) GLuint vertexBuffer;
 // 纹理的id
 @property (nonatomic, assign) GLuint textureID;

 @end

 @implementation ViewController

 //释放部分
 - (void)dealloc {
     //上下文释放
     if ([EAGLContext currentContext] == self.context) {
         [EAGLContext setCurrentContext:nil];
     }
     //顶点缓存区释放
     if (_vertexBuffer) {
         glDeleteBuffers(1, &_vertexBuffer);
         _vertexBuffer = 0;
     }
     //顶点数组释放
     if (_vertices) {
         free(_vertices);
         _vertices = nil;
     }
 }

 - (void)viewWillDisappear:(BOOL)animated {
     [super viewWillDisappear:animated];
     
     // 移除 displayLink
     if (self.displayLink) {
         [self.displayLink invalidate];
         self.displayLink = nil;
     }
 }

 - (void)viewDidLoad {
     [super viewDidLoad];
     
     self.view.backgroundColor = [UIColor blackColor];
     
     /*
      整体思路:
      和GLSL加载图片的流程一样
      不同分屏滤镜效果,主要是在着色器里去计算的
      这里加一个计时器的关键点在于,让屏幕保持一直刷新渲染,方便切换滤镜及时刷新。更多是用于有动效的滤镜
      */
     //1、创建底部切换bar
     [self setupFilterBar];
     
     //2、GLSL加载图片流程
     [self loaderImage];

     //3、启动定时器,刷新屏幕
     [self startRender];
 }

 #pragma mark - 1
 - (void)setupFilterBar {
     
     CGFloat filterBarWidth = [UIScreen mainScreen].bounds.size.width;
     CGFloat filterBarHeight = 100;
     CGFloat filterBarY = [UIScreen mainScreen].bounds.size.height - filterBarHeight;
     NSArray *dataSource = @[@"无",@"灰度",@"颠倒",@"正方形\n马赛克",@"六边形\n马赛克",@"三角形\n马赛克"];
     
     FilterBar *filerBar = [[FilterBar alloc] initWithFrame:CGRectMake(0, filterBarY, filterBarWidth, filterBarHeight)];
     filerBar.itemList = dataSource;
     filerBar.delegate = self;
     [self.view addSubview:filerBar];
      
 }


 - (void)filterBar:(FilterBar *)filterBar didScrollToIndex:(NSUInteger)index {
     //1. 选择默认shader
     if (index == 0) {
         [self setUpDrawShaderWith:@"Normal"];
     }else if(index == 1)
     {
         [self setUpDrawShaderWith:@"Gray"];
     }else if(index == 2)
     {
         [self setUpDrawShaderWith:@"Reversal"];
     }else if(index == 3)
     {
         [self setUpDrawShaderWith:@"square"];
     }else if(index == 4)
     {
         [self setUpDrawShaderWith:@"hexagon"];
     }else if(index == 5)
     {
         [self setUpDrawShaderWith:@"triangle"];
     }
     // 重新开始滤镜动画
     [self startRender];
 }

 #pragma mark - 2
 - (void)loaderImage {

     //1、准备工作
     /*
      把一些会重复用到的地方,封装起来,然后剩下的不变的,放在这个方法里面。
      上下文&设置当前
      设置图层
      设置缓冲区
      设置视口
      设置顶点数据
      设置顶点缓冲区
      解压图片,拿到纹理id(因为这里面只有一个纹理,如果有多个也要拆分出去,方便复用)
      */
     [self setUpConfig];
     
     //2、绘制每一个着色器都需要调用的方法。第一次加载,肯定使用默认着色器
     /*
      1、加载、编译shader
         1)拿到shader路径,转成c字符串
         2)创建shader对象
         3)把着色器字符串 附着到shader对象上
         4)编译shader对象&检验
      2、附着、连接program
         1)创建一个program对象
         2)把顶点、片元shader 附着上
         3)链接program&检验
      3、use program
      4、传递数据
         1)顶点坐标数据
         2)纹理坐标数据
         3)采样器传递纹理id(纹理id在准备工作就拿到了,不放这里是防止重复操作)
      */
     [self setUpDrawShaderWith:@"Normal"];
     
      
 }
 #pragma mark - 2.1
 - (void)setUpConfig {
     
     //1.上下文
     self.context = [[EAGLContext alloc] initWithAPI: kEAGLRenderingAPIOpenGLES2];
     [EAGLContext setCurrentContext:self.context];
     
     
     //2、图层-设置一个正方形
     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];
     [self.view.layer addSublayer:layer];
     
     
     //3、缓冲区
     //1)渲染缓冲区
     GLuint rBuffer,fBuffer;
     glGenRenderbuffers(1, &rBuffer);
     glBindRenderbuffer(GL_RENDERBUFFER, rBuffer);
     //把layer的存储绑定到渲染缓冲区
     [self.context renderbufferStorage:GL_RENDERBUFFER fromDrawable:layer];
     //2)帧缓冲区
     glGenFramebuffers(1, &fBuffer);
     glBindFramebuffer(GL_FRAMEBUFFER, fBuffer);
     //把renderBuffer绑定到ATTACHMENT0上
     glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_RENDERBUFFER, rBuffer);

     
     //4、视口
     glViewport(0, 0, self.drawableWidth, self.drawableHeight);


     //5、顶点数据
     //1)开辟顶点数组内存空间
     self.vertices = malloc(sizeof(SenceVertex) * 4);
     //2)
     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}};


     //6、顶点缓冲区
     GLuint vBuffer;
     glGenBuffers(1, &vBuffer);
     glBindBuffer(GL_ARRAY_BUFFER, vBuffer);
     glBufferData(GL_ARRAY_BUFFER, sizeof(SenceVertex) * 4, self.vertices, GL_STATIC_DRAW);
     //保存,退出的时候才释放
     self.vertexBuffer = vBuffer;


     //7、解压图片,获取纹理id
     
     GLuint textureID = [self createTextureWithImageName:@"mark.jpeg"];
     //设置纹理ID
     self.textureID = textureID;
     
     
     
 }
 - (GLuint)createTextureWithImageName:(NSString *)imageName{
     
     
     //1、拿到图片路径
     //这么写的好处是,图片不做缓存处理
     NSString *imagePath = [[[NSBundle mainBundle] resourcePath] stringByAppendingPathComponent:imageName];
    
     UIImage *image = [UIImage imageWithContentsOfFile:imagePath];
     
     
     //2、解压图片
     CGImageRef imageRef = [image CGImage];
     
     //3、判断图片有没有拿到
     if (!imageRef) {
         NSLog(@"load image faile");
         exit(1);
     }
     
     //4、创建上下文
     //1)获取宽高
     GLuint width = (GLuint)CGImageGetWidth(imageRef);
     GLuint height = (GLuint)CGImageGetHeight(imageRef);
     //2)拿到图片大小
     void *imageData = malloc(width * height * 4);
     //3)拿到图片的颜色
     CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
     //3)上下文
     /*
     参数1:data,指向要渲染的绘制图像的内存地址
     参数2:width,bitmap的宽度,单位为像素
     参数3:height,bitmap的高度,单位为像素
     参数4:bitPerComponent,内存中像素的每个组件的位数,比如32位RGBA,就设置为8
     参数5:bytesPerRow,bitmap的没一行的内存所占的比特数
     参数6:colorSpace,bitmap上使用的颜色空间  kCGImageAlphaPremultipliedLast:RGBA
     */
     CGContextRef imageContext = CGBitmapContextCreate(imageData, width, height, 8, width * 4, colorSpace, kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big);

     //5、重新绘制
     CGRect rect = CGRectMake(0, 0, width, height);
     //1)翻转策略
     CGContextTranslateCTM(imageContext, 0, height);
     CGContextScaleCTM(imageContext, 1.0f, -1.0f);
     
     //2)对图片重新绘制,得到一张新的解压后的位图
     CGContextDrawImage(imageContext, rect, imageRef);
     
     //3)用完之后释放
     CGColorSpaceRelease(colorSpace);
     CGContextRelease(imageContext);
     
     //6、设置纹理 (因为这个方法需要我们返回一个id,就不穿默认0了,还是写一遍代码吧)
     GLuint textureId;
     glGenTextures(1, &textureId);
     glBindTexture(GL_TEXTURE_2D, textureId);
     
     //7、载入纹理数据
     /*
     参数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, width, height, 0, GL_RGBA, GL_UNSIGNED_BYTE, imageData);

     
     //8、设置纹理属性
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
     glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
     
     //9、重新绑定一下(用的时候就绑定准没错)
     glBindTexture(GL_TEXTURE_2D, textureId);
     //10、释放
     free(imageData);
         
     return textureId;
 }

 #pragma mark - 2.2
 - (void)setUpDrawShaderWith:(NSString *)shaderName{
     
     //1. 编译顶点着色器/片元着色器
     GLuint vertexShader = [self compileShaderWithName:shaderName type:GL_VERTEX_SHADER];
     GLuint fragmentShader = [self compileShaderWithName:shaderName type:GL_FRAGMENT_SHADER];
     
     
     //2、
     //1)创建一个program
     GLuint program = glCreateProgram();
     
     //2)附着
     glAttachShader(program, vertexShader);
     glAttachShader(program, fragmentShader);
     
 //    glDeleteShader(vertexShader);
 //    glDeleteShader(fragmentShader);
     
     //3)link
     glLinkProgram(program);
     
     //4)检查
     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];
         NSLog(@"program链接失败:%@", messageString);
         exit(1);
     }
  
     
     //3、use
     glUseProgram(program);
     
     //4、传递数据
     //1)先拿到通道名
     //顶点坐标
     GLuint positionSlot = glGetAttribLocation(program, "Position");
     //纹理坐标
     GLuint textureCoordsSlot = glGetAttribLocation(program, "TextureCoords");
     //纹理
     GLuint textureSlot = glGetUniformLocation(program, "Texture");
  
     
     //2)传顶点坐标
     glEnableVertexAttribArray(positionSlot);
     glVertexAttribPointer(positionSlot, 3, GL_FLOAT, GL_FALSE, sizeof(SenceVertex), NULL + offsetof(SenceVertex, positionCoord));
     
     //3)传纹理坐标
     glEnableVertexAttribArray(textureCoordsSlot);
     glVertexAttribPointer(textureCoordsSlot, 2, GL_FLOAT, GL_FALSE, sizeof(SenceVertex), NULL + offsetof(SenceVertex, textureCoord));
     
     //4) 传纹理
     glActiveTexture(GL_TEXTURE0);
     glBindTexture(GL_TEXTURE_2D, self.textureID);
     
     glUniform1i(textureSlot, 0);
     
     
     //5.保存program,界面销毁则释放
     self.program = program;
 }

 //编译shader代码
 - (GLuint)compileShaderWithName:(NSString *)name type:(GLenum)shaderType {
     
     //1、获得shader路径
     NSString *shaderPath = [[NSBundle mainBundle] pathForResource:name ofType:shaderType == GL_VERTEX_SHADER ? @"vsh" : @"fsh"];
     
     //2、转换成c语言字符串
     NSError *error;
     NSString *shaderString = [NSString stringWithContentsOfFile:shaderPath encoding:NSUTF8StringEncoding error:&error];
     if (!shaderString) {
         NSLog( @"读取shader失败");
         exit(1);
     }
     
 //    const GLchar* source = (GLchar*)[pathString UTF8String];
     
     const char *shaderStringUTF8 = [shaderString UTF8String];
     int shaderStringLength = (int)[shaderString length];
     
     
     //3、创建shader对象
     GLuint shader = glCreateShader(shaderType);
     
     //4、附着
     glShaderSource(shader, 1, &shaderStringUTF8, &shaderStringLength);
     
     //5、编译
     glCompileShader(shader);
     
     //6、检查编译
     GLint compileStatus;
     glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus);
     
     if (compileStatus == GL_FALSE) {
         GLchar messages[256];
         glGetShaderInfoLog(shader, sizeof(messages), 0, &messages[0]);
         NSString *messageString = [NSString stringWithUTF8String:messages];
         NSLog(@"shader编译失败:%@", messageString);
         exit(1);
     }
     
     return shader;
 }
 #pragma mark - 3
 - (void)startRender {

     //1.因为会重复调用,严谨一点,先判断一下
     if (self.displayLink) {
         
         [self.displayLink invalidate];
         self.displayLink = nil;
     }
     //2. 设置displayLink 的方法
     self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(timeAction)];
     
     //3.将displayLink 添加到runloop 运行循环
     [self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
 }

  
 - (void)timeAction{
     
    
     //使用program
     glUseProgram(self.program);
     //绑定buffer
     glBindBuffer(GL_ARRAY_BUFFER, self.vertexBuffer);
      
     // 清除画布
     glClear(GL_COLOR_BUFFER_BIT);
     glClearColor(1, 1, 1, 1);
     
     // 重绘
     glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
     //渲染到屏幕上
     [self.context presentRenderbuffer:GL_RENDERBUFFER];
     
 }


 //获取渲染缓存区的宽
 - (GLint)drawableWidth {
     GLint backingWidth;
     glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_WIDTH, &backingWidth);
     return backingWidth;
 }
 //获取渲染缓存区的高
 - (GLint)drawableHeight {
     GLint backingHeight;
     glGetRenderbufferParameteriv(GL_RENDERBUFFER, GL_RENDERBUFFER_HEIGHT, &backingHeight);
     return backingHeight;
 }
 @end



源码链接:

链接:pan.baidu.com/s/13G0wUQKy… 密码:sbfd