由于着色器文件中加入中文注释后会产生不明问题,为了以后复习时能更好的理解如何实现动态滤镜效果,本文将逐步描述着色器文件中实现动态滤镜效果,其它的openGL绘制图像等操作可查看文末的Demo源码。
缩放滤镜
动态缩放效果图

实现原理:
- 将当前的
顶点坐标
乘以缩放因子
能实现放大或缩小的效果。 - 当
缩放因子
随着正弦曲线
进行变化时,即可实现动态效果。
实现步骤
1. 实现缩放效果
这个步骤比较简单,拿到顶点坐标之后,将其乘以一个缩放因子
就可以。注意点:缩放因子最好是float类型,否则可能缩放不成功
。
放大效果:将顶点坐标乘以一个大于1.0的数。
缩小效果:将顶点坐标乘以一个大于0.0,小于1.0的数
放大效果示例:
gl_Position = vec4(Position.x * 1.5, Position.y * 1.5, Position.zw);

缩小效果示例:
gl_Position = vec4(Position.x * 0.5, Position.y * 0.5, Position.zw);

2. 实现动态效果
正弦曲线是一个从0 -> 1 -> 0 -> -1 ->0
循环变化的曲线。这符合我们的需求,即让缩放因子
从0 -> 1 -> 0
循环变化,即可形成一个动态的效果。由于由于缩放因子
不能为负数,所以我们只需要正弦曲线的[0,π]
。下图为[0,π]
的正弦曲线。

如何形成动态效果的方法有了。现在只剩下如何产生一个正弦变化的
缩放因子
。一提到变化,整个项目中随时都在变化的就是
时间
了。我们可以将时间通过取模运算
,让其它在指定区间内变化 。有了以上的理论基础,可以修改着色器文件了。以下着色器代码以动态放大效果为目的。
//定义一次动态变化的时长,即完成一次 0->1->0 所需要的时间
float duration = 0.6;
//其中Time为当前时间,mod为取模运算,经过mod运算后,time的取值范围为[0, duration];
float time = mod(Time, duration);
//定义一个最大放大倍数
float maxAmplitude = 0.3;
//amplitude:表示振幅,通过正弦函数计算的振幅范围为[0,0.3],加上原本图像缩放比例为1(即不缩放),即振幅的变化范围为[1,1.3]。
float amplitude = 1.0 + maxAmplitude * abs(sin(time * PI/duration));
//最后将顶点坐标乘以当前计算的振幅,就可达到缩放效果
gl_Position = vec4(Position.x * amplitude, Position.y * amplitude, Position.zw);
灵魂出窍滤镜
效果图

实现原理
从效果图中看到灵魂出窍的效果其实就是两个图层的叠加
,将一个放大且透明度在慢慢变小的图层叠加到原始图层中。
将纹理坐标均匀放大。
将放大后的纹素与原始纹素进行混合。
实现步骤
由于需要将两个纹素进行颜色混合,所以必须修改片元着色器
,之前所操作的放大效果是在顶点着色器
完成,这里引入一个方法实现新的放大效果,即在片元着色器中修改纹理坐标
。
实现纹理均匀放大
获取一个均匀变化的常量在之前的缩放滤镜效果
中已经描述过,这里采用相似的方法。不同的是,透明度的变化是一个[1,0]的过程,所以需要根据时间变化设定一个[0,1]的范围
//动画持续时长
float duration = 0.7;
//将当前时间限制在[0,duration]变化范围
float time = mod(Time, duration);
//定义一个百分比,即time/duration,此时就可以将这个百分比限定在[0,1]范围
float process = time/duration;
由于最终效果是两个图层混合,而位于上层(即放大效果的图层)的透明度如果为1的话,则下层将会被完全遮挡
,因此,上层的透明度必须小于1.0。
//设定上层透明度的最大值
float maxAlpha = 0.4;
//根据百分比,计算一个[0,maxAlpha]的透明度变量
float alpha = maxAlpha * (1.0 - process);
根据百分比,计算均匀变化的放大因子
变量
//设定上层放大因子的最大值
float maxScale = 0.4;
//根据百分比,计算一个[0, maxScale]的放大因子变量
float scale = 1.0 + maxScale * process;
在实现纹理放大之前,先来了解一下纹理放大的原理。
下图为纹理未放大前,为了便于观察,绿色透明区域为顶点坐标对应的范围,顶点坐标与纹理坐标呈一一对应的关系
,即:
顶点的(0,0)与纹理的(0,0)对应
顶点的(0,1)与纹理的(0,1)对应
顶点的(1,0)与纹理的(1,0)对应
顶点的(1,1)与纹理的(1,1)对应

假设将当前纹理放大1.3倍。则顶点坐标与纹理坐标对应关系如下:
顶点的(0,0)与纹理的(0,0)对应
顶点的(0,1)与纹理的(0,0.77)对应
顶点的(1,0)与纹理的(0.77,0)对应
顶点的(1,1)与纹理的(0.77,0.77)对应
注意:0.77 = 1.0/1.3

根据这个原理,来尝试一下纹理放大后。此处暂时不加入混合,只实现一下放大效果。
//放大纹理的X坐标
float weakX = (TextureCoordsVarying.x)/scale;
//放大纹理的Y坐标
float weakY = (TextureCoordsVarying.y)/scale;
//得到放大后的纹理坐标
vec2 weakTextureCoords = vec2(weakX, weakY);
//将放大后的纹理坐标赋值给内建变量
gl_FragColor = weakMask;
纹理放大效果图如下:

从效果图上看,确实实现了放大效果,但这与我们最终想要的存在差异。由于中心点的位置是不变的
,那可以根据中心点操作完成纹理放大。
- 实现中心点的左边放大
float weakX, weakY;
if(TextureCoordsVarying.x <= 0.5){
weakX = (0.5 - TextureCoordsVarying.x)/scale;
}
else{
// weakX = (TextureCoordsVarying.x - 0.5)/scale;
}

- 实现中心点的右边放大
if(TextureCoordsVarying.x <= 0.5){
// weakX = (0.5 - TextureCoordsVarying.x)/scale;
}
else{
weakX = (TextureCoordsVarying.x - 0.5)/scale;
}

- 合并左右两边放大的效果
float weakX, weakY;
if(TextureCoordsVarying.x <= 0.5){
weakX = 0.5 - (0.5 - TextureCoordsVarying.x)/scale;
}
else{
weakX = (TextureCoordsVarying.x - 0.5)/scale + 0.5;
}
//上面整个if判断的代码等价于下面代码
float weakX = 0.5 + (TextureCoordsVarying.x - 0.5)/scale;
//上面的代码与下面的代码是等价的
vec2 weakTextureCoords = vec2(0.5, 0.5) + (TextureCoordsVarying - vec2(0.5, 0.5))/scale;

同理,Y方向放大也是一样的实现方法。
float weakY = 0.5 + (TextureCoordsVarying.y - 0.5)/scale;

实现纹理颜色混合
最终的效果图中,上面图层是一个逐渐放大且透明度变低的过程。下面图层是一个原始图片。此时需要将上面图层与下面图层进行颜色混合。
混合的方式有两种:
- mix函数方法
gl_FragColor = mix(mask, weakMask, alpha);
- 调用混合方程式
//这里使用默认方程式
vec4 weakMask = texture2D(Texture, weakTextureCoords);
vec4 mask = texture2D(Texture, TextureCoordsVarying);
gl_FragColor = weakMask * alpha + mask * (1.0 - alpha);
Demo源码详见:Demo地址中的缩放滤镜和灵魂出窍滤镜着色器文件。
网友评论