Flutter & GLSL 系列文章:
- 《Flutter & GLSL - 壹 | Shader 让绘制无限强大》
- 《Flutter & GLSL - 贰 | 从坐标到颜色》
- 《Flutter & GLSL - 叁 | 变量传参》
- 《Flutter & GLSL - 肆 | 从条纹到马赛克》
- 《Flutter & GLSL - 伍 | 图形区域控制》
- 《Flutter & GLSL - 陆 | 平滑过渡 smoothstep》
- 《Flutter & GLSL - 柒 | 减法与线》
案例代码开源地址 【skeleton】
前面我们通过圆形的区域和平滑过渡,认识了两个非常重要的内置函数 step
和 smoothstep
。其中这两个方法本质上是非常简单的,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 < 0
和 v < 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 探索的文章,敬请期待 ~