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

文章介绍了如何通过GLSL在Flutter中使用shader控制图形绘制,包括利用circle函数和step函数创建圆形、坐标系转换、多个圆形叠加以及区域控制和贴图应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


theme: cyanosis

Flutter & GLSL 系列文章:

案例代码开源地址 【skeleton】

shader5.png


1、从圆形与 step 函数

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

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

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

image.png

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

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

```c

version 460 core

include

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] 呢?

image.png

其实很简单,左侧坐标系值放大两倍,即 坐标 *2 可以得到 x,y 的取值范围在 [0,2]的坐标系;然后坐标轴右移 1 个单位,即可得到 x,y 的取值范围在 [-1,1]的目标坐标系。
此时距离原点小于 0.5 的点被着为黑色,就可以得到如下的圆形:

image.png

```c ---->[shaders/base01circle_step2.frag]----

version 460 core

include

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)

image.png

```c ---->[shaders/base01circle_step3.frag]----

version 460 core

include

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


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

image.png

代码中有两个半径为 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/base01circle_step4.frag]----

version 460 core

include

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) 为白色。

image.png

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

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 来控制纹理贴图对应坐标的像素颜色。这样就很容易实现对贴图 区域控制 的效果, 如下所示,当像素点位于白色圆区域时展示图片颜色。以此可以实现类似图片裁剪的效果:

image.png

```c

version 460 core

include

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;

} ```

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

image.png

```c

version 460 core

include

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;

} ```

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值