使用两个FBO互相绑定实现PS液化效果

最终效果如下(旋转效果和膨胀放大效果作为例子):

使用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

https://github.com/cjzjolly/learnopengl/blob/main/app/src/main/java/com/cjztest/glShaderEffect/GLFrameBufferEffectPingPongSave.java

其实最重要的还是fragShader的实现和FBO的巧妙利用。

利用OpenGL高效实现类似PhotoShop液化工具的教程比较少,这个也是我在学习OpenGL的过程中突发奇想的成功实验产物,希望能帮到有需要的读者吧:

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值