OpenGL学习- 17. 动态周期滤镜

608 阅读4分钟

17.动态周期滤镜

动态周期滤镜其实就是根据传入时间和时间周期来控制着色器处理来实现的。
基于上一节的马赛克滤镜框架添加定时器来获取滤镜运行时间

- (void)setShader:(NSString *)shader image:(UIImage *)image {
    if (self.displayLink) {
        [self.displayLink invalidate];
        self.displayLink = nil;
    }
    self.startTimeInterval = 0;
    /// 载入纹理
    if ([self loadTextureWithImage:image]) {
        /// link program
        if ([self linkProgramWithShaderName:shader]) {
            glUseProgram(_program);
            /// 运行过程中未改变的数据,仅开始传入一次就好了
            [self staticDataPrepare];
            /// 一直在改变的时间在定时器方法里传入,并渲染
            self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(timeAction)];
            [self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];
        }
    }
}
- (void)timeAction {
    /// displayLink.timestamp 与显示的最后一帧相关联的时间值
    if (self.startTimeInterval == 0) {
        self.startTimeInterval = self.displayLink.timestamp;
    }
    CGFloat currentTime = self.displayLink.timestamp - self.startTimeInterval;
    /// 传入时间
    GLuint time = glGetUniformLocation(_program, "u_time");
    glUniform1f(time, currentTime);

    glClear(GL_COLOR_BUFFER_BIT);
    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
    [self.context presentRenderbuffer:GL_RENDERBUFFER];
}
/// 渲染过程中未改变的数据
- (void)staticDataPrepare {
    GLfloat vertices[20] = {
        -1.0, 1.0, 0.0,   0.0, 1.0,
        -1.0, -1.0, 0.0,  0.0, 0.0,
        1.0, 1.0, 0.0,    1.0, 1.0,
        1.0, -1.0, 0.0,   1.0, 0.0,
    };
    glGenBuffers(1, &_buffer);
    glBindBuffer(GL_ARRAY_BUFFER, _buffer);
    glBufferData(GL_ARRAY_BUFFER, sizeof(vertices), vertices, GL_DYNAMIC_DRAW);

    GLuint position = glGetAttribLocation(_program, "i_position");
    glEnableVertexAttribArray(position);
    glVertexAttribPointer(position, 3, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, NULL);

    GLuint textureCoord = glGetAttribLocation(_program, "i_textureCoord");
    glEnableVertexAttribArray(textureCoord);
    glVertexAttribPointer(textureCoord, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, NULL + 3);
    glVertexAttribPointer(textureCoord, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, NULL + sizeof(GLfloat)*3);
    glVertexAttribPointer(textureCoord, 2, GL_FLOAT, GL_FALSE, sizeof(GLfloat)*5, (GLfloat*)NULL + 3);

    GLuint sampler = glGetUniformLocation(_program, "u_sampler");
    glUniform1i(sampler, 0);

    GLuint aspectRatio = glGetUniformLocation(_program, "u_aspectRatio");
    glUniform1f(aspectRatio, self.aspectRatio);
}

缩放滤镜

简单的缩放滤镜可以直接通过改变顶点着色器的顶点位置来实现
顶点着色器

attribute vec4 i_position;
attribute vec2 i_textureCoord;
varying lowp vec2 o_textureCoord;
uniform float u_time;
/// 缩放周期
const float cycle_time = 3.0;
/// 最大缩放比
const float max_scale = 1.3;
/// 最小缩放比
const float min_scale = 0.8;

void main()
{
    /// 根据周期取余,得到对应[0,cycle_time]区间内的进度比例
    float progress = mod(u_time, cycle_time)/cycle_time;
    /// 结合三角函数的绝对值,优化缩放模式
    float scale = min_scale + (max_scale - min_scale) * abs(sin(radians(progress*360.0)));
    /// x,y进行缩放;z,w不变
    gl_Position = vec4(i_position.x * scale, i_position.y * scale, i_position.zw);
    o_textureCoord = i_textureCoord;
}

片元着色器

varying lowp vec2 o_textureCoord;
uniform sampler2D u_sampler;

void main()
{
    gl_FragColor = texture2D(u_sampler, o_textureCoord);
}

scale.gif

灵魂出窍

在片元着色器中取当前点纹素,和放大后当前位置对应点的对应纹素,然后进行颜色混合。根据传入时间,调整放大部分的放大程度和混合时的透明度来实现
顶点着色器

attribute vec4 i_position;
attribute vec2 i_textureCoord;
varying lowp vec2 o_textureCoord;

void main()
{
    gl_Position = i_position;
    o_textureCoord = i_textureCoord;
}

片元着色器

precision highp float;
varying lowp vec2 o_textureCoord;
uniform sampler2D u_sampler;
uniform float u_time;
/// 周期
const float cycle_time = 1.0;
/// 放大层最大透明度
const float max_alpha = 0.4;
/// 放大层最大放大比例
const float max_scale = 1.4;

void main()
{
    /// 根据周期取余,得到对应[0,cycle_time]区间内的进度比例
    float progress = mod(u_time, cycle_time)/cycle_time;
    /// 当前时间对应的放大层透明度
    float alpha = max_alpha * (1.0 - progress);
    /// 当前时间对应的放大层放大比例
    float scale = 1.0 + (max_scale - 1.0) * progress;
    /// 当前时间对应的放大层的纹理坐标,以中心进行缩放,所以-0.5先把坐标系移动到中心,进行完缩放计算后再+0.5还原成着色器使用的坐标系
    vec2 scale_textureCoord = vec2(0.5 + (o_textureCoord.x - 0.5)/scale, 0.5 + (o_textureCoord.y - 0.5)/scale);
    /// 原始纹素
    vec4 original_FragColor = texture2D(u_sampler, o_textureCoord);
    /// 放大纹素
    vec4 scale_FragColor = texture2D(u_sampler, scale_textureCoord);
    /// 进行颜色混合,得到实际渲染
    gl_FragColor = (1.0 - alpha)*original_FragColor + alpha*scale_FragColor;
}

soul.gif

颜色偏移

片元着色器中,在进行放大的基础上进行多层颜色偏移的颜色混合
顶点着色器

attribute vec4 i_position;
attribute vec2 i_textureCoord;
varying lowp vec2 o_textureCoord;

void main()
{
    gl_Position = i_position;
    o_textureCoord = i_textureCoord;
}

片元着色器

precision highp float;
varying lowp vec2 o_textureCoord;
uniform sampler2D u_sampler;
uniform float u_time;
/// 周期
const float cycle_time = 1.0;
/// 最大放大比例
const float max_scale = 1.02;
/// 颜色偏移向量(x,y方向偏移值)
const vec2 offset = vec2(0.04, 0.02);

void main()
{
    /// 进度
    float progress = mod(u_time, cycle_time)/cycle_time;
    /// 当前放大比例
    float scale = 1.0 + (max_scale - 1.0) * progress;
    /// 放大后对应的纹理坐标
    vec2 scale_textureCoord = vec2(0.5,0.5) + (o_textureCoord - vec2(0.5, 0.5))/scale;
    /// 左下偏移纹素
    vec4 left_fragColor = texture2D(u_sampler, scale_textureCoord - offset*progress);
    /// 当前位置纹素
    vec4 c_fragColor = texture2D(u_sampler, scale_textureCoord);
    /// 右上偏移纹素
    vec4 right_fragColor = texture2D(u_sampler, scale_textureCoord + offset*progress);
    /// 取左下偏移纹素的r,右上偏移的g,当前纹素的b、a组成新的颜色
    gl_FragColor = vec4(left_fragColor.r, right_fragColor.g, c_fragColor.b, c_fragColor.a);
}

dither.gif

闪白

根据时间调整当前纹素和白色的颜色混合因子来实现
顶点着色器

attribute vec4 i_position;
attribute vec2 i_textureCoord;
varying lowp vec2 o_textureCoord;

void main()
{
    gl_Position = i_position;
    o_textureCoord = i_textureCoord;
}

片元着色器

precision highp float;
varying lowp vec2 o_textureCoord;
uniform sampler2D u_sampler;
uniform float u_time;
/// 周期
const float cycle_time = 2.0;

void main()
{
    /// 进度
    float progress = mod(u_time, cycle_time)/cycle_time;
    /// 纹素在颜色混合中所占比例(取1/4函数周期,[0,1])
    float alpha = sin(radians(progress*90.0));
    /// 当前点纹素
    vec4 fragColor = texture2D(u_sampler, o_textureCoord);
    /// 颜色混合 当前纹素和白色混合
    gl_FragColor = alpha * fragColor + (1.0 - alpha) * vec4(1.0);
}

white.gif

毛刺

在x轴方向上,每一行都进行一个随机的偏移,绝大多数行进行小范围随机偏移,小部分行进行大范围偏移,可制作出毛刺效果。再配合色度偏移,可以效果更好。
顶点着色器

attribute vec4 i_position;
attribute vec2 i_textureCoord;
varying lowp vec2 o_textureCoord;

void main()
{
    gl_Position = i_position;
    o_textureCoord = i_textureCoord;
}

片元着色器

precision highp float;
varying lowp vec2 o_textureCoord;
uniform sampler2D u_sampler;
uniform float u_time;
/// 周期
const float cycle_time = 0.5;
/// 小范围偏移的与大范围偏移的界限
const float min_limit = 0.96;
/// 大范围偏移值
const float max_offset = 0.07;
/// 小范围偏移值
const float min_offset = 0.01;
/// 颜色r分量偏移
const float r_offset = -0.01;
/// 颜色g分量偏移
const float g_offset = 0.003;

/// 一个伪随机函数
float random(float n)
{
    /// floor(n*1000.0)是为了让n的取值离散
    return fract(sin(floor(n*1000.0)) * 43758.5453123);
}

void main()
{
    /// 进度
    float progress = mod(u_time, cycle_time)/cycle_time;
    /// x轴随机偏移(-1.0, 1.0)之间
    float x_offset = random(o_textureCoord.y) * 2.0 - 1.0;
    /// 获取x再当前进度下的偏移
    float offsetX = (abs(x_offset) > min_limit) ? x_offset * max_offset * progress : x_offset * min_offset * progress;
    /// 偏移后的纹理坐标
    vec2 new_textureCoord = o_textureCoord + vec2(offsetX, 0.0);
    /// 当前点纹素
    vec4 o_FragColor = texture2D(u_sampler, new_textureCoord);
    /// r偏移纹素
    vec4 r_FragColor = texture2D(u_sampler, new_textureCoord + vec2(r_offset * progress, 0.0));
    /// g偏移纹素
    vec4 g_FragColor = texture2D(u_sampler, new_textureCoord + vec2(g_offset * progress, 0.0));
    gl_FragColor = vec4(r_FragColor.r, g_FragColor.g, o_FragColor.b, o_FragColor.a);
}

burr.gif

代码示例见:Github