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;
}

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

相关推荐
大白要努力!1 小时前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
天空中的野鸟2 小时前
Android音频采集
android·音视频
小白也想学C3 小时前
Android 功耗分析(底层篇)
android·功耗
曙曙学编程3 小时前
初级数据结构——树
android·java·数据结构
Summer不秃4 小时前
Flutter之使用mqtt进行连接和信息传输的使用案例
前端·flutter
旭日猎鹰4 小时前
Flutter踩坑记录(二)-- GestureDetector+Expanded点击无效果
前端·javascript·flutter
sunly_4 小时前
Flutter:AnimatedSwitcher当子元素改变时,触发动画
flutter
AiFlutter4 小时前
Flutter封装Coap
flutter
闲暇部落6 小时前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
诸神黄昏EX8 小时前
Android 分区相关介绍
android