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 探索的文章,敬请期待 ~

相关推荐
webmote7 分钟前
做一个FabricJS.cc的中文文档网站——面向markdown编程
canvas·fabric·使用手册·中文·fabricjs
Dnelic-2 小时前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记
Eastsea.Chen4 小时前
MTK Android12 user版本MtkLogger
android·framework
比格丽巴格丽抱11 小时前
flutter项目苹果编译运行打包上线
flutter·ios
长亭外的少年12 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
SoaringHeart12 小时前
Flutter进阶:基于 MLKit 的 OCR 文字识别
前端·flutter
建群新人小猿14 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神15 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri
兰琛15 小时前
20241121 android中树结构列表(使用recyclerView实现)
android·gitee
AiFlutter16 小时前
Flutter通过 Coap发送组播
flutter