最终效果如下(旋转效果和膨胀放大效果作为例子):
使用OpenGL双FBO加FragShader模仿PS的液化工具
FragShader文件:
#version 300 es
precision highp float;
uniform sampler2D sTexture;//纹理输入
uniform int frame;//第几帧
uniform int funChoice; //功能代码块选择
uniform float effectR; //作用半径
uniform vec2 targetXY; //作用位置,使用纹理分辨率坐标
uniform vec2 prevTargetXY; //上一次的作用位置。
uniform vec2 resolution;//纹理分辨率
in vec4 fragObjectColor;//接收vertShader处理后的颜色值给片元程序
in vec2 fragVTexCoord;//接收vertShader处理后的纹理内坐标给片元程序
out vec4 fragColor;//输出到的片元颜色
#define PINCH_VECTOR vec2( sin(10. * frame / 10.0), cos(20. * frame / 10.0)) * .03 // 挤压向量
mat2 rotate(float a) // 旋转矩阵
{
float s = sin(a);
float c = cos(a);
return mat2(c,-s,s,c);
}
vec2 twirl(vec2 uv, vec2 center, float range, float angle, bool cw) {
float d = distance(uv, center);
uv -=center;
// d = clamp(-angle/range * d + angle,0.,angle); // 线性方程
d = smoothstep(0., range, range-d) * angle;
if (cw) {
uv *= rotate(d);
} else {
uv *= rotate(-d);
}
uv+=center;
return uv;
}
vec2 inflate(vec2 uv, vec2 center, float range, float strength) {
float dist = distance(uv , center);
vec2 dir = normalize(uv - center);
float scale = 1.-strength + strength * smoothstep(0., 1. ,dist / range);
float newDist = dist * scale;
return center + newDist * dir;
}
vec2 pinch(vec2 uv, vec2 targetPoint, vec2 prevTargetXY, float range)
{
float dist = distance(uv, prevTargetXY);
vec2 point = prevTargetXY + smoothstep(0., 1., dist / range) * (targetPoint - prevTargetXY); //(targetPoint - prevTargetXY)前后两次触摸点之间的x,y距离
return uv - targetPoint + point;
}
//vec2 pinch(vec2 uv, vec2 targetPoint, vec2 prevTargetXY, float range)
//{
// float dist = distance(uv, targetPoint); //目标点和当前采样点的距离
// float distBetweenTwoPoint = distance(targetPoint, prevTargetXY);
// float dist3 = distance(uv, prevTargetXY);
// vec2 point = targetPoint + smoothstep(0.0, 1.0, dist / range) * prevTargetXY;
// return uv - distBetweenTwoPoint * (dist / dist3) * smoothstep(0., 1., dist / range);
// //1、算采样点uv和tagetPoint、prevTargetXy构成的线段的夹角。用于确定挤压的方向和挤压的强大
// //2
//}
void main() {
vec2 targetXYToOne = targetXY / resolution; //归1化处理
vec2 prevTargetXYToOne = prevTargetXY / resolution; //归1化处理
targetXYToOne.y = 1.0 - targetXYToOne.y;
prevTargetXYToOne.y = 1.0 - prevTargetXYToOne.y;
// vec2 texCoord = vec2(fragVTexCoord.y, fragVTexCoord.x);
vec2 texCoord = fragVTexCoord;
switch (funChoice) {
case 0: //采集纹理图片像素
vec4 color = texture(sTexture, texCoord);//采样纹理中对应坐标颜色,进行纹理渲染
color.a = color.a * fragObjectColor.a;//利用顶点透明度信息控制纹理透明度
fragColor = color;
break;
//已经采集了纹理图片像素并留下残迹在Framebuffer了,可以进行图像处理了:
case 1: //绘制点
float d = distance(texCoord, targetXY);
if (distance(targetXYToOne, texCoord) < effectR) {
fragColor = vec4(1.0, 0.0, 0.0, 1.0);
}
break;
case 2: //旋转形变1
vec2 newST = twirl(texCoord, targetXYToOne, effectR, 0.05, true);
color = texture(sTexture, newST);//采样纹理中对应坐标颜色,进行纹理渲染
color.a = color.a * fragObjectColor.a;//利用顶点透明度信息控制纹理透明度
fragColor = color;
break;
case 3: //旋转形变2
newST = twirl(texCoord, targetXYToOne, effectR, 0.05, false);
color = texture(sTexture, newST);//采样纹理中对应坐标颜色,进行纹理渲染
color.a = color.a * fragObjectColor.a;//利用顶点透明度信息控制纹理透明度
fragColor = color;
break;
case 4: //膨胀:
newST = inflate(texCoord, targetXYToOne, effectR, 0.02);
color = texture(sTexture, newST);//采样纹理中对应坐标颜色,进行纹理渲染
color.a = color.a * fragObjectColor.a;//利用顶点透明度信息控制纹理透明度
fragColor = color;
break;
case 5: //收缩:
newST = inflate(texCoord, targetXYToOne, effectR, -0.02);
color = texture(sTexture, newST);//采样纹理中对应坐标颜色,进行纹理渲染
color.a = color.a * fragObjectColor.a;//利用顶点透明度信息控制纹理透明度
fragColor = color;
break;
case 6: //挤压
// newST = pinch(texCoord, targetXYToOne, vec2(0.1, 0.2), effectR);
newST = pinch(texCoord, targetXYToOne, prevTargetXYToOne, effectR);
color = texture(sTexture, newST);//采样纹理中对应坐标颜色,进行纹理渲染
color.a = color.a * fragObjectColor.a;//利用顶点透明度信息控制纹理透明度
fragColor = color;
break;
default:
case -1: //鼠标抬起时要切换到这里,避免持续刷新
break;
}
}
直接靠本fragShader的纹理采样圈算法可以实现各种图片扭曲效果,而且是完全基于GPU不需要一丁点GPU的,性能非常高,但如果每次都用图片纹理绑定上去,那么每帧只有目标值会产生效果,无法使效果暂留和叠加。所以我使用了两个FBO对象开辟两个可以装载图像纹理的空间,使用swapBuffer的思路互为纹理绑定,也就是:
第一帧渲染图片到fbo_0,然后fbo_1的texture绑为fbo_0,然后做纹理采样圈处理;再下帧切换为fbo_0绑定的texture为fbo_1。除了第一帧,两个fbo互相绑定对方为纹理,即可使得每一步的片元特效都可以叠加,做出类似PS中液化的效果。
即可把每帧的处理效果暂留和叠加:
具体代码比较长就不贴出来了,可以访问我的GitHub直接看这几个文件:
learnopengl/app/src/main/java/com/cjz/littleps at main · cjzjolly/learnopengl · GitHub
其实最重要的还是fragShader的实现和FBO的巧妙利用。
利用OpenGL高效实现类似PhotoShop液化工具的教程比较少,这个也是我在学习OpenGL的过程中突发奇想的成功实验产物,希望能帮到有需要的读者吧: