Flutter & GLSL - 柒 | 减法与线

Flutter & GLSL 系列文章:

案例代码开源地址 【skeleton】


前面我们通过圆形的区域和平滑过渡,认识了两个非常重要的内置函数 stepsmoothstep。其中这两个方法本质上是非常简单的,GLSL 中内置它们是因为非常通用,GPU 对其有特殊的优化,从而可以被硬件加速。

c 复制代码
float step(float a, float b) {
    return a < b ? 0 : 1;
}

float smoothstep(float e0, float e1, float x) {
    x = clamp((x - e0) / (e1 - e0), 0.0, 1.0); 
    return x * x * (3 - 2 * x); }
}

1. 图形的减法

现在思考一下,如果想要实现圆形边线 的图形,该怎么办呢?思路其实很简单,如下左图是一个 r=0.6 的圆;右图将该圆减去 r=0.5 的圆,就可以得到圆环;当圆环的宽度变小,就可以得到 圆形线

r=0.6 的圆 r=0.6 圆 减 r=0.5 圆 r=0.6 圆 减 r=0.59 圆

现在问题关键在于如何对两个图形进行 减法操作 。 上一篇中将实心圆形封装为如下的 circle 方法:

  • coo 表示坐标;
  • r 表示圆的半径,
  • t 表示过渡的阈值宽度:
c 复制代码
float circle(vec2 coo, float r, float t) {
    float len = length(coo);
    return 1 - smoothstep(r, r + t, len);
}

函数返回值是颜色通道的浮点型数字。黑色返回 0 、白色返回 1 、过渡区域在 0~1间渐变。仔细想一想,如果两个形状像素点重合,如果都是白色 1-1 = 0 就变成了黑色;如果都是黑色 0-0 = 0 保持黑色。

所以 circle 函数返回值的加减法在视觉上可以增加和减去图形。

c 复制代码
---->[shaders/base_03_line_step1.frag]----
#version 460 core
#include <flutter/runtime_effect.glsl>
precision mediump float;

out vec4 fragColor;
uniform vec2 uSize;

float circle(vec2 coo, float r, float t) {
    float len = length(coo);
    return 1 - smoothstep(r, r + t, len);
}

void main() {
    vec2 coo = FlutterFragCoord() / uSize;
    coo = coo * 2 - 1;
    float ret = circle(coo, 0.6, 0.01);
    // 减去 r = 0.5 的圆
    ret -= circle(coo, 0.5, 0.01);
    fragColor = vec4(ret, ret, ret, 1);
}

2. 圆形线的封装

上面我们通过两个圆相减实现了圆形线,现在来推演一下如何封装一个 圆形线方法 circle_line。 如下所示增加 w 参数表示线的宽度:

  • 演绎第一阶段:将圆的相减逻辑封装在 circle_line 内部
c 复制代码
// coo  :  像素坐标
// r    :  圆半径
// w    :  边线宽度
// t    :  过渡阈值
float circle_line(vec2 coo, float r, float w, float t) {
    float len = length(coo);
    float c1 = 1 - smoothstep(r + w, r + w + t, len);
    float c2 = 1 - smoothstep(r, r + t, len);
    return c1 - c2;
}

对于边线来说,当线宽较大不可忽略时,想要考虑线宽在图形外、中、内,上面的方法边线在圆形之外。下面用一个

r=0.6 , 边线 w = 0.4 的圆说明一下

边线在外侧 边线在中间侧 边线在内存侧
  • 演绎第二阶段:添加边线溢出控制 boder_out ,为 0 时表示不溢出,也就是边线在圆内;1 全部溢出,边线在圆外;0.4 表示 40% 的变现露出,60%的边线在园内。
c 复制代码
// coo        :  像素坐标
// r          :  圆半径
// w          :  边线宽度
// t          :  过渡阈值
// boder_out  :  边线溢出控制 0:内侧 1:外侧 0.5: 增加
float circle_line(vec2 coo, float r, float w, float t, float boder_out) {
    float len = length(coo);
    float outW = w * boder_out;
    float inW = w - outW;
    float c1 = 1 - smoothstep(r + outW, r + outW + t, len);
    float c2 = 1 - smoothstep(r - inW, r -inW + t, len);
    return c1 - c2;
}

3. 再认知 smoothstep

上一节我们介绍过 smoothstep 的作用,以及三个参数的含义:

内置函数 smoothstep(e0,e1,v) :
v < e0 时, 返回 0;
v > e1 时, 返回 1;
v 在 [e0,e1] 之间 时,通过曲线函数在 0~1 间过渡插值

现在思考一个问题, smoothstep(e0,e1,v)smoothstep(0,e1-e0,v-e0) 两者是否等价?根据定义不难分析出, 后者表示:

v-e0 < 0 时, 返回 0;
v-e0 > e1-e0 时, 返回 1;
v-e0 在 [0,e1-e0] 之间 时,通过曲线函数在 0~1 间过渡插值

可以看出两者只是参考物的不同, v-e0 < 0v < e0 在逻辑上是一致的。所以 smoothstep 中的三个参数同时加减数字,返回的结果保持不变。所以圆形函数也可以是如下逻辑:

c 复制代码
float circle(vec2 coo, float r, float t) {
    float len = length(coo);
    return 1 - smoothstep(0,  t, len-r);
}

float circle_line(vec2 coo, float r, float w, float t, float boder_out) {
    float len = length(coo);
    float outW = w * boder_out;
    float inW = w - outW;
    return smoothstep(-inW, -inW + t, len - r)
    - smoothstep(outW, outW + t, len - r);
}

4. 循环遍历

glsl 中,可以使用 for 来执行循环逻辑,比如下面遍历生成很多条线圆形线,在循环体中可以根据次数 i 控制圆的半径、线宽、过渡阈值参数:

效果1 效果2 效果3
c 复制代码
void main() {
    vec2 coo = FlutterFragCoord() / uSize;
    coo = coo * 2 - 1;
    float ret = 0;
    for (int i = 0;i < 40; i++) {
        float radius = 0.05 * i;
        //ret += circle_line(coo, radius, 0.01, 0.01, 0.5);// 效果1 
        ret += circle_line(coo, radius, 0.01 * (i / 8), 0.01, 0.5);// 效果2 
        //ret += circle_line(coo, radius, 0.01*(i/8), 0.01*i, 0.5);// 效果3 
    }
    fragColor = vec4(ret, ret, ret, 1);
}

颜色和纹理图片相结合,白色是图片展示区域,黑色不显示图片,过渡区域展示对应的透明色。所以将上面的圆线条纹施加到纹理上既可以得到如下效果:

代码中分为 40 条圆线,半径从内到外依次增加 0.025,将所有的圆线通过 + 号进行合并;最后将结果施加到图片纹理中:

c 复制代码
#version 460 core
#include <flutter/runtime_effect.glsl>
precision mediump float;

out vec4 fragColor;
uniform sampler2D uTexture;
uniform vec2 uSize;

float circle_line(vec2 coo, float r, float w, float t, float boder_out) {
    float len = length(coo);
    float outW = w * boder_out;
    float inW = w - outW;
    return smoothstep(-inW, -inW + t, len - r)- smoothstep(outW, outW + t, len - r);
}

void main() {
    vec2 coo = FlutterFragCoord() / uSize;
    coo = coo * 2 - 1;
    float ret = 0;
    for(int i=0;i<40;i++){
        float radius = 0.025*i;
        float t = 0.01;
        float w = 0.05;
        ret += circle_line(coo, radius, w, t, 0.5);
    }
    fragColor = vec4(ret, ret, ret, 1);
    vec2 picCoo = (coo + 1) / 2;
    vec4 color = texture(uTexture, picCoo);
    fragColor = color*ret;
}

这里如果改变宽度,比如 0.03 ,那么临近的两个圆值将会叠加,此时纹理对应的颜色将被 "增强"。w= 0.05 时,输出值会大于 1 ,可以看到图片被明显提亮。

w = 0.03 w= 0.05

本文通过 减法 认识了如何将两个形状进行裁剪,从而得到圆环和圆线。那本篇就到这里,后续还会带来更多 Flutter & GLSL 探索的文章,敬请期待 ~

相关推荐
太空漫步112 小时前
android社畜模拟器
android
神秘_博士2 小时前
自制AirTag,支持安卓/鸿蒙/PC/Home Assistant,无需拥有iPhone
arm开发·python·物联网·flutter·docker·gitee
陈皮话梅糖@4 小时前
Flutter 网络请求与数据处理:从基础到单例封装
flutter·网络请求
海绵宝宝_4 小时前
【HarmonyOS NEXT】获取正式应用签名证书的签名信息
android·前端·华为·harmonyos·鸿蒙·鸿蒙应用开发
凯文的内存6 小时前
android 定制mtp连接外设的设备名称
android·media·mtp·mtpserver
天若子6 小时前
Android今日头条的屏幕适配方案
android
林的快手8 小时前
伪类选择器
android·前端·css·chrome·ajax·html·json
望佑8 小时前
Tmp detached view should be removed from RecyclerView before it can be recycled
android
xvch10 小时前
Kotlin 2.1.0 入门教程(二十四)泛型、泛型约束、绝对非空类型、下划线运算符
android·kotlin
人民的石头14 小时前
Android系统开发 给system/app传包报错
android