Flutter & GLSL - 伍 | 图形区域控制

Flutter & GLSL 系列文章:

案例代码开源地址 【skeleton】


1、从圆形与 step 函数

有时我们需要通过着色器来表现图形,那如何通过坐标控制颜色值的输出,得到基本图形呢?之前一直强调:

shader 的奥义在于 通过坐标控制像素的颜色信息

想要展示一个半径为 r 的黑色圆形,只需要计算 像素点 距原点距离 len , 对于所有 len <= r 的像素点着为黑色;反之着为白色:

这个逻辑由下面的 circle 方法进行处理:当 len <= r 时返回 0 ,着色为 vec4(0, 0, 0, 1) 即黑色;反之返回 1 , 着色为 vec4(1, 1, 1, 1) 即白色。这样就通过圆的性质,通过对坐标点,控制像素的表现,形成图形。

内置函数 length(vec2) : 用于计算 vec2 坐标到原点的距离。

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

out vec4 fragColor;
uniform vec2 uSize;

float circle(vec2 coo, float r) {
    float len = length(coo);
    if (len <= r) {
        return 0;
    } else {
        return 1;
    }
}

void main() {
    vec2 coo = FlutterFragCoord() / uSize;
    float radius = 0.5;
    float ret = circle(coo, radius);
    fragColor = vec4(ret, ret, ret, 1);
}

在 GLSL 中内置了一个用于生成阶梯的 step 函数:

内置函数 step(a,b) : 比较两个值 a, b ; 如果 a < b,则返回 0.0、否则返回 1.0。

也就是说,上面 circle 函数的比较逻辑可以简化成如下形式,效果是完全等价的:

c 复制代码
float circle(vec2 coo, float r) {
    float len = length(coo);
    return step(r, len);
}

2、坐标系的转变

目前坐标系的原点在左上角(下图左),x,y 的取值范围在 [0,1]。所以上面画的圆形只显示了四分之一。如何变化,可以使坐标系的原点在画板中心(下图右),并且横纵坐标取值范围在 [-1,1] 呢?

其实很简单,左侧坐标系值放大两倍,即 坐标 *2 可以得到 x,y 的取值范围在 [0,2]的坐标系;然后坐标轴右移 1 个单位,即可得到 x,y 的取值范围在 [-1,1]的目标坐标系。

此时距离原点小于 0.5 的点被着为黑色,就可以得到如下的圆形:

c 复制代码
---->[shaders/base_01_circle_step2.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 len = length(coo);
    return step(r, len);
}

void main() {
    vec2 coo = FlutterFragCoord() / uSize;
    /// 变换坐标系,以中心为原点
    coo = coo * 2 - 1;
    float ret = circle(coo, 0.5);
    fragColor = vec4(ret, ret, ret, 1);
}

3. 多个圆形联合

现在想一个小问题:如何将圆形呈白色,周围是黑色呢?很简单,用 1 - step(r, len) 即可,这样原来的黑色 1 就会变为白色 1-1 = 0 ; 原来的白色 0 就会变为白色 1-0 = 1

根据 step 的作用,不难推出: 1 - step(r, len) = step(len, r)

c 复制代码
---->[shaders/base_01_circle_step3.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 len = length(coo);
    return step(len, r);
}

void main() {
    vec2 coo = FlutterFragCoord() / uSize;
    coo = coo * 2 - 1;
    float ret = circle(coo, 0.5);
    fragColor = vec4(ret, ret, ret, 1);
}

现在再想一想,如何在界面上显示多个圆呢?如下所示:

代码中有两个半径为 0.2 的小圆 c1 和 c2 ,可以自己思考一下 ret = c0 + c1 + c2 为什么可以把小圆展示出来?

  • 对每个像素操作 的视角来看,返回 1 表示该像素点是白色,返回 0 表示黑色;
  • c0 + c1 表示每个像素点的值是两个圆的结果累加值。如果每个坐标计算的结果啊: c0 是 0 (黑色); c1 是 1 (白色),两张相加 0+1 = 1 。就表示当前像素为白色。这样 c1 的白色就会出现在屏幕上。以此类推。
  • 当白色重叠时,即两个圆相交的地方,累加值是 1+1=2; 最后让 ret = min(ret, 1.0),就可以使结果中大于 1 时取 1 值:
c 复制代码
---->[shaders/base_01_circle_step4.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 len = length(coo);
    return step(len, r);
}

void main() {
    vec2 coo = FlutterFragCoord() / uSize;
    coo = coo * 2 - 1;
    float ret = 0;
    float c0 = circle(coo, 0.5);
    vec2 offset = vec2(-0.6, -0.6);
    float c1 = circle(coo + offset, 0.2);
    float c2 = circle(coo - offset*0.7, 0.2);
    ret = c0 + c1 + c2;
    ret = min(ret, 1.0);
    fragColor = vec4(ret, ret, ret, 1);
}

如下,我们将累加后的 ret 值减半,就更能说明问题。这样:

  • 未相交的圆 ret = (1+0)*0.5 = 0.5 ;颜色就是 vec4(0.5, 0.5, 0.5, 1) 为灰色。
  • 相交时 ret = (1+1) * 0.5 = 1 ;颜色就是 vec4(1, 1, 1, 1) 为白色。

于是,界面上可以呈现出叠合处更亮的效果:

c 复制代码
---->[shaders/base_01_circle_step5.frag]----
void main() {
    /// 略同...
    ret = c0 + c1 + c2;
    ret *=0.5;
    ret = min(ret, 1.0);
    fragColor = vec4(ret, ret, ret, 1);
}

4. 区域控制与贴图

我们可以根据 circle 计算的结果是 0 还是 1 来控制纹理贴图对应坐标的像素颜色。这样就很容易实现对贴图 区域控制 的效果, 如下所示,当像素点位于白色圆区域时展示图片颜色。以此可以实现类似图片裁剪的效果:

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

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

float circle(vec2 coo, float r) {
    float len = length(coo);
    return step(len, r);
}

void main() {
    vec2 coo = FlutterFragCoord() / uSize;
    coo = coo * 2 - 1;
    float ret = 0;
    float c0 = circle(coo, 0.5);
    vec2 offset = vec2(-0.6, -0.6);
    float c1 = circle(coo + offset, 0.2);
    float c2 = circle(coo - offset * .7, 0.2);
    ret = c0 + c1 + c2;
    ret = min(ret, 1.0);

    vec2 picCoo = (coo + 1) / 2;
    vec4 color = texture(uTexture, picCoo);
    /// 白色圆区域展示图片颜色
    color = ret == 1 ? color : vec4(0, 1, 1, 1);
    fragColor = color;
}

有了区域控制的手段,也可以很轻松地实现 区域贴图特效,如下所示,将原本黑色的区域施加马赛克的效果,就可以实现区域外的局部马赛克:

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

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

float circle(vec2 coo, float r) {
    float len = length(coo);
    return step(len, r);
}

void main() {
    vec2 coo = FlutterFragCoord() / uSize;
    coo = coo * 2 - 1;
    float ret = 0;
    float c0 = circle(coo, 0.5);
    vec2 offset = vec2(-0.6, -0.6);
    float c1 = circle(coo + offset, 0.2);
    float c2 = circle(coo - offset * .7, 0.2);
    ret = c0 + c1 + c2;
    ret = min(ret, 1.0);
    vec2 picCoo = (coo + 1) / 2;
    vec4 color = texture(uTexture, picCoo);

    if (ret == 0) {
        /// 局部马赛克
        float rowCount = 40.0;
        float x = floor(picCoo.x * rowCount) / rowCount;
        float y = floor(picCoo.y * rowCount) / rowCount;
        color = texture(uTexture, vec2(x, y));
    }
    fragColor = color;
}

本篇通过图形区域,让我们又多了一种对像素坐标控制颜色输出的手段。除了圆形之外,还有其他很多的基础图形区域,将在后面继续介绍。那本文就到这里,谢谢观看~

相关推荐
似霰3 小时前
安卓adb shell串口基础指令
android·adb
fatiaozhang95275 小时前
中兴云电脑W102D_晶晨S905X2_2+16G_mt7661无线_安卓9.0_线刷固件包
android·adb·电视盒子·魔百盒刷机·魔百盒固件
CYRUS_STUDIO6 小时前
Android APP 热修复原理
android·app·hotfix
鸿蒙布道师6 小时前
鸿蒙NEXT开发通知工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
鸿蒙布道师6 小时前
鸿蒙NEXT开发网络相关工具类(ArkTs)
android·ios·华为·harmonyos·arkts·鸿蒙系统·huawei
大耳猫7 小时前
【解决】Android Gradle Sync 报错 Could not read workspace metadata
android·gradle·android studio
ta叫我小白7 小时前
实现 Android 图片信息获取和 EXIF 坐标解析
android·exif·经纬度
bst@微胖子7 小时前
Flutter之路由和导航
flutter
dpxiaolong8 小时前
RK3588平台用v4l工具调试USB摄像头实践(亮度,饱和度,对比度,色相等)
android·windows
亚洲小炫风8 小时前
flutter 中各种日志
前端·flutter·日志·log