OpenGL ES 常见滤镜处理

    科技2026-01-20  12

    图片滤镜实现思路

    前提条件:能够用GLSL显示普通图片

    思路

    初始化(上下文,顶点数组,顶点数据,顶点缓存区,CAEAGLayer,绑定渲染缓存区/帧缓存区,获取图片路径并将图片->纹理,设置视口,link默认着色器)

    创建CADisplayLink刷新图片

    图片缩放滤镜实现思路

    缩放滤镜实际上基本的原理:可以通过修改顶点坐标和纹理坐标的对应关系来实现

    缩放滤镜.vsh

    //顶点坐标 

    attribute vec4 Position; 

    //纹理坐标 

    attribute vec2 TextureCoords; 

    //纹理坐标 

    varying vec2 TextureCoordsVarying; 

    //时间撮(及时更新) 

    uniform float Time; 

    //PI 

    const float PI = 3.1415926; 

    void main (void) { 

    //⼀次缩放效果时⻓ = 0.6ms 

    float duration = 0.6; 

    //最⼤缩放幅度 

    float maxAmplitude = 0.3; 

    //表示传⼊的时间周期.即time的范围被控制在[0.0~0.6]; 

    //mod(a,b),求模运算. a%b 

    float time = mod(Time, duration); 

    //amplitude 表示振幅,引⼊ PI 的⽬的是为了使⽤ sin 函数,将 amplitude 的范围控制在 1.0 ~ 1.3 

    之间,并随着时间变化 

    float amplitude = 1.0 + maxAmplitude * abs(sin(time * (PI / duration))); 

    //放⼤关键: 将顶点坐标的 x 和 y 分别乘上⼀个放⼤系数,在纹理坐标不变的情况下,就达到了拉伸的 

    效果。

    //x,y 放⼤; z和w保存不变 

    gl_Position = vec4(Position.x * amplitude, Position.y * amplitude, Position.zw); 

    纹理坐标传递给TextureCoordsVarying 

    TextureCoordsVarying = TextureCoords; 

    }

    灵魂出窍滤镜实现思路

    灵魂出窍滤镜:是两个层的叠加,并且上面的那层随着时间的推移,会逐渐放大且不透明度逐渐降低,这里也用到了放大的效果,我们用片段着色器实现。

    灵魂出窍滤镜. fsh 

    //「灵魂出窍」看上去是两个层的叠加,并且上⾯的那层随着时间的推移,会逐渐放⼤且不透明度逐渐降 

    低。这⾥也⽤到了放⼤的效果,我们这次⽤⽚段着⾊器来实现 

    precision highp float; 

    //纹理采样器 

    uniform sampler2D Texture; 

    //纹理坐标 

    varying vec2 TextureCoordsVarying; 

    //时间撮 

    uniform float Time; 

    void main (void) { 

    //⼀次灵魂出窍的时⻓ 

    float duration = 0.7; 

    //透明度上限 

    float maxAlpha = 0.4; 

    //放⼤图⽚上限 

    float maxScale = 1.8; 

    //进度(0~1) 

    float progress = mod(Time, duration) / duration; // 0~1 

    //透明度(0-0.4) 

    float alpha = maxAlpha * (1.0 - progress); 

    //缩放⽐例(1.0 - 1.8) 

    float scale = 1.0 + (maxScale - 1.0) * progress; 

    //放⼤纹理坐标 

    //将顶点坐标对应的纹理坐标的 x 值到纹理中点的距离,缩⼩⼀定的⽐例。这次我们是改变了纹理坐 

    标,⽽保持顶点坐标不变,同样达到了拉伸的效果 

    float weakX = 0.5 + (TextureCoordsVarying.x - 0.5) / scale; 

    float weakY = 0.5 + (TextureCoordsVarying.y - 0.5) / scale; 

    //获取放⼤的纹理坐标 

    vec2 weakTextureCoords = vec2(weakX, weakY); 

    //通过上⾯的计算,我们得到了两个纹理颜⾊值 weakMask 和 mask, weakMask 是在 mask 的基 

    础上做了放⼤处理 

    4//读取到放⼤后的纹理坐标的纹素的颜⾊值 

    vec4 weakMask = texture2D(Texture, weakTextureCoords); 

    //读取原始的纹理坐标对应的纹素的颜⾊值 

    vec4 mask = texture2D(Texture, TextureCoordsVarying); 

    //在GLSL 实现颜⾊混合⽅程式.默认颜⾊混合⽅程式 = mask * (1.0 - alpha) + weakMask * alpha 

    //参考OpenGL 第⼆节课中的颜⾊混合 

    //计算最终的颜⾊赋值给⽚元着⾊器的内置变量: gl_FragColor 

    gl_FragColor = mask * (1.0 - alpha) + weakMask * alpha; 

    }

    抖动滤镜实现思路

    抖动滤镜:颜色偏移+微弱的放大效果

    抖动滤镜.fsh

    precision highp float;

    uniform sampler2D Texture;

    varying vec2 TextureCoordsVarying;

    uniform float Time;

    void main (void) {

        float duration = 0.7;

        float maxScale = 1.1;

        float offset = 0.02;

     

        float progress = mod(Time, duration) / duration; // 0~1

        vec2 offsetCoords = vec2(offset, offset) * progress;

        float scale = 1.0 + (maxScale - 1.0) * progress;

     

        vec2 ScaleTextureCoords = vec2(0.5, 0.5) + (TextureCoordsVarying - vec2(0.5, 0.5)) / scale;

     

        vec4 maskR = texture2D(Texture, ScaleTextureCoords + offsetCoords);

        vec4 maskB = texture2D(Texture, ScaleTextureCoords - offsetCoords);

        vec4 mask = texture2D(Texture, ScaleTextureCoords);

     

        gl_FragColor = vec4(maskR.r, mask.g, maskB.b, mask.a);

    }

    闪白滤镜实现思路

    闪白滤镜:添加白色图层,白色图层的透明度随着时间变化

    闪⽩滤镜.fsh 

    precision highp float; 

    //纹理采样器 

    uniform sampler2D Texture; 

    //纹理坐标 

    varying vec2 TextureCoordsVarying; 

    //时间撮 

    uniform float Time; 

    //PI 常量 

    const float PI = 3.1415926; 

    void main (void) { 

    //⼀次闪⽩滤镜的时⻓ 

    float duration = 0.6; 

    //表示将传⼊的时间转换到⼀个周期内,即 time 的范围是 0 ~ 0.6 

    float time = mod(Time, duration); 

    //⽩⾊颜⾊遮罩 

    vec4 whiteMask = vec4(1.0, 1.0, 1.0, 1.0); 

    //amplitude 表示振幅,引⼊ PI 的⽬的是为了使⽤ sin 函数,将 amplitude 的范围控制在 0.0 ~ 1.0 

    之间,并随着时间变化 

    float amplitude = abs(sin(time * (PI / duration))); 

    //获取纹理坐标对应的纹素颜⾊值 

    vec4 mask = texture2D(Texture, TextureCoordsVarying); 

    //利⽤混合⽅程式: 将纹理颜⾊与⽩⾊遮罩融合. 

    5//注意: ⽩⾊遮罩的透明度会随着时间变化做调整 

    //我们已经知道了如何实现两个层的叠加.当前的透明度来计算最终的颜⾊值即可。 

    gl_FragColor = mask * (1.0 - amplitude) + whiteMask * amplitude; 

    }

    毛刺滤镜实现思路

    毛刺滤镜:撕裂+微弱的颜色偏移

    具体的思路是,我们让每⼀⾏像素随机偏移 -1 ~ 1 的距离(这⾥的 -1 ~ 1 是对于纹理坐标来说的),但是如果整个画⾯都偏移⽐较⼤的值,那我们可能都看不出原来图像的样⼦。所以我们的逻辑是,设定⼀个阈值,⼩于这个阈值才进⾏偏移,超过这个阈值则乘上⼀个缩⼩系数。

    则最终呈现的效果是:绝⼤部分的⾏都会进⾏微⼩的偏移,只有少量的⾏会进⾏较⼤偏移 

    ⽑刺滤镜.fsh 

    precision highp float; 

    //纹理采样器 

    uniform sampler2D Texture; 

    //纹理坐标 

    varying vec2 TextureCoordsVarying; 

    //时间撮 

    uniform float Time; 

    //PI 常量 

    const float PI = 3.1415926; 

    //随机数 

    float rand(float n) { 

    //fract(x),返回x的⼩数部分 

    //返回: sin(n) * 43758.5453123 

    return fract(sin(n) * 43758.5453123); 

    }

    void main (void) { 

    //最⼤抖动 

    float maxJitter = 0.06; 

    //⼀次⽑刺滤镜的时⻓ 

    float duration = 0.3; 

    //红⾊颜⾊偏移 

    float colorROffset = 0.01; 

    //绿⾊颜⾊偏移 

    float colorBOffset = -0.025; 

    //表示将传⼊的时间转换到⼀个周期内,即 time 的范围是 0 ~ 0.6 

    float time = mod(Time, duration * 2.0); 

    //amplitude 表示振幅,引⼊ PI 的⽬的是为了使⽤ sin 函数,将 amplitude 的范围控制在 1.0 ~ 1.3 

    之间,并随着时间变化 

    float amplitude = max(sin(time * (PI / duration)), 0.0); 

    6// -1~1 像素随机偏移范围(-1,1) 

    float jitter = rand(TextureCoordsVarying.y) * 2.0 - 1.0; // -1~1 

    //判断是否需要偏移,如果jtter范围< 最⼤抖动*振幅 

    bool needOffset = abs(jitter) < maxJitter * amplitude; 

    //获取纹理x 坐标,根据needOffset,来计算它的X撕裂,如果是needOffset = yes 则撕裂⼤;如果 

    needOffset = no 则撕裂⼩; 

    float textureX = TextureCoordsVarying.x + (needOffset ? jitter : (jitter * amplitude * 0.006)); 

    //获取纹理撕裂后的x,y坐标 

    vec2 textureCoords = vec2(textureX, TextureCoordsVarying.y); 

    //颜⾊偏移 

    //获取3组颜⾊: 根据撕裂计算后的纹理坐标,获取纹素的颜⾊ 

    vec4 mask = texture2D(Texture, textureCoords); 

    //获取3组颜⾊: 根据撕裂计算后的纹理坐标,获取纹素的颜⾊ 

    vec4 maskR = texture2D(Texture, textureCoords + vec2(colorROffset * amplitude, 0.0)); 

    //获取3组颜⾊: 根据撕裂技术后的纹理坐标,获取纹素颜⾊ 

    vec4 maskB = texture2D(Texture, textureCoords + vec2(colorBOffset * amplitude, 0.0)); 

    //颜⾊主要撕裂: 红⾊和蓝⾊部分.所以只调整红⾊ 

    gl_FragColor = vec4(maskR.r, mask.g, maskB.b, mask.a); 

    }

    幻觉滤镜实现思路

    幻觉滤镜:残影和颜色偏移的叠加

    残影的效果: 是在移动的过程中,每经过⼀段时间间隔,根据当前的位置去创建⼀个新层,并且新层的不透明度随着时间逐渐减弱。于是在⼀个移动周期内,可以看到很多透明度不同的层叠加在⼀起,从⽽形成残影的效果。残影,让图⽚随着时间做圆周运动

    颜⾊偏移: 物体移动的过程是蓝⾊在前⾯,红⾊在后⾯。所以整个过程可以理解成:在移动的过程中,每间隔⼀段时间,遗失了⼀部分红⾊通道的值在原来的位置,并且这部分红⾊通道的值,随着时间偏移,会逐渐恢复.

     

    幻觉滤镜.fsh 

    precision highp float; 

    //纹理采样器 

    uniform sampler2D Texture; 

    //纹理坐标 

    varying vec2 TextureCoordsVarying; 

    //时间撮 

    uniform float Time; 

    //PI 常量 

    const float PI = 3.1415926; 

    //⼀次幻觉滤镜的时⻓ 

    const float duration = 2.0; 

    7//这个函数可以计算出,在某个时刻图⽚的具体位置。通过它我们可以每经过⼀段时间,去⽣成⼀个新的 

    vec4 getMask(float time, vec2 textureCoords, float padding) { 

    //圆周坐标 

    vec2 translation = vec2(sin(time * (PI * 2.0 / duration)), 

    cos(time * (PI * 2.0 / duration))); 

    //纹理坐标 = 纹理坐标+偏离量 * 圆周坐标 

    vec2 translationTextureCoords = textureCoords + padding * translation; 

    //根据这个坐标获取新图层的坐标 

    vec4 mask = texture2D(Texture, translationTextureCoords); 

    return mask; 

    }

    //这个函数可以计算出,某个时刻创建的层,在当前时刻的透明度。 

    float maskAlphaProgress(float currentTime, float hideTime, float startTime) { 

    //duration+currentTime-startTime % duration 

    float time = mod(duration + currentTime - startTime, duration); 

    return min(time, hideTime); 

    }

    void main (void) { 

    //表示将传⼊的时间转换到⼀个周期内,即 time 的范围是 0 ~ 2.0 

    float time = mod(Time, duration); 

    //放⼤倍数 

    float scale = 1.2; 

    //偏移量 

    float padding = 0.5 * (1.0 - 1.0 / scale); 

    //放⼤后的纹理坐标 

    vec2 textureCoords = vec2(0.5, 0.5) + (TextureCoordsVarying - vec2(0.5, 0.5)) / scale; 

    //隐藏时间 

    float hideTime = 0.9; 

    //时间间隔 

    8float timeGap = 0.2; 

    //注意: 只保留了红⾊的透明的通道值,因为幻觉效果残留红⾊. 

    //新图层的-R⾊透明度 0.5 

    float maxAlphaR = 0.5; // max R 

    //新图层的-G⾊透明度 0.05 

    float maxAlphaG = 0.05; // max G 

    //新图层的-B⾊透明度 0.05 

    float maxAlphaB = 0.05; // max B 

    //获得新的图层坐标! 

    vec4 mask = getMask(time, textureCoords, padding); 

    float alphaR = 1.0; // R 

    float alphaG = 1.0; // G 

    float alphaB = 1.0; // B 

    //最终图层颜⾊ 

    vec4 resultMask = vec4(0, 0, 0, 0); 

    //循环 

    for (float f = 0.0; f < duration; f += timeGap) { 

    float tmpTime = f; 

    //获取到0-2.0秒内所获取的运动后的纹理坐标 

    vec4 tmpMask = getMask(tmpTime, textureCoords, padding); 

    //某个时刻创建的层,在当前时刻的红绿蓝的透明度 

    float tmpAlphaR = maxAlphaR - maxAlphaR * maskAlphaProgress(time, hideTime, 

    tmpTime) / hideTime; 

    float tmpAlphaG = maxAlphaG - maxAlphaG * maskAlphaProgress(time, hideTime, 

    tmpTime) / hideTime; 

    float tmpAlphaB = maxAlphaB - maxAlphaB * maskAlphaProgress(time, hideTime, 

    tmpTime) / hideTime; 

    //累积每⼀层每个通道乘以透明度颜⾊通道 

    resultMask += vec4(tmpMask.r * tmpAlphaR, 

    tmpMask.g * tmpAlphaG, 

    tmpMask.b * tmpAlphaB, 

    1.0); 

    //透明度递减 

    9alphaR -= tmpAlphaR; 

    alphaG -= tmpAlphaG; 

    alphaB -= tmpAlphaB; 

    }

    //最终颜⾊ += 红绿蓝 * 透明度 

    resultMask += vec4(mask.r * alphaR, mask.g * alphaG, mask.b * alphaB, 1.0); 

    //将最终颜⾊填充到像素点⾥. 

    gl_FragColor = resultMask; 

    }

    Processed: 0.021, SQL: 9