Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

第19章:Sdf2d Shader 编程

为什么这很重要

上一章讲解了 Draw 管线,管线的最后一步是 pixel 函数。Makepad 提供了 Sdf2d(Signed Distance Field 2D)API,让你在 GPU 上用类似绘图 API 的方式描述形状。RoundedView 的圆角、Button 的按下效果、Slider 的轨道——所有内置 Widget 的视觉外观都由 Sdf2d 实现。

flowchart LR
    A["pixel: fn()"] --> B["Sdf2d::viewport()"]
    B --> C["形状原语<br>circle / box / rect"]
    C --> D["布尔运算<br>union / intersect / subtract"]
    D --> E["着色操作<br>fill / stroke / glow"]
    E --> F["vec4 颜色输出"]

Shader 结构与变量类型

DrawQuadfragment 调用 pixel()pixel() 返回 vec4 颜色。Shader 中有四种变量类型:

类型声明说明
instanceinstance(default)每个实例不同(rect_pos、color),Splash 可直接设置
uniformuniform(type)同一 draw call 共享,系统自动填充
varyingvarying(type)vertex → fragment 插值传递
texturetexture_2d(type)GPU 纹理采样

Sdf2d API

Sdf2d 通过距离场描述形状——对每个像素计算"到最近形状边界的有符号距离",据此决定填充或描边。

初始化与基本流程

pixel: fn(){
    let sdf = Sdf2d::viewport(self.pos * self.rect_size);
    sdf.circle(50., 50., 30.);   // 定义形状
    sdf.fill(#f80);              // 着色
    return sdf.result;           // 输出
}

形状原语

来源:draw/src/shader/sdf.rs:150-500

函数说明
sdf.circle(x, y, r)圆形
sdf.box(x, y, w, h, radius)圆角矩形(最常用)
sdf.box_all(x, y, w, h, r_lt, r_rt, r_rb, r_lb)独立四角圆角
sdf.rect(x, y, w, h)无圆角矩形
sdf.hexagon(x, y, r)六边形
sdf.hline(y, h)水平线
sdf.arc_round_caps(x, y, r, start, end, thickness)圆端弧线
sdf.arc_flat_caps(x, y, r, start, end, thickness)平端弧线

路径绘制:sdf.move_to(x, y)sdf.line_to(x, y)sdf.close_path()

着色操作

函数说明
sdf.fill(color)填充并清除形状
sdf.fill_keep(color)填充但保留形状(可继续 stroke)
sdf.stroke(color, width)描边并清除形状
sdf.stroke_keep(color, width)描边但保留形状
sdf.glow(color, width)外发光效果

来源:draw/src/shader/sdf.rs:215-274

布尔运算

sdf.circle(50., 50., 30.);
sdf.circle(70., 50., 30.);
sdf.union();               // 并集
sdf.fill(#f00);
运算效果
union()并集——形状合并
intersect()交集——只保留重叠区域
subtract()差集——从旧形状中减去新形状
gloop(k)平滑并集——圆滑过渡
blend(k)混合——在两个形状间插值(适合动画)

来源:draw/src/shader/sdf.rs:276-300

变换

sdf.translate(x, y)        // 平移
sdf.rotate(angle, cx, cy)  // 绕点旋转
sdf.scale(factor, cx, cy)  // 绕点缩放

辅助工具

Pal(调色板)Pal.premul(color) 预乘 alpha、Pal.hsv2rgb(hsva) 色彩空间转换、Pal.iq0..iq7(t) 程序化渐变色。

GaussShadowGaussShadow.rounded_box_shadow(lower, upper, point, sigma, corner)box_shadow 实现 GPU 高斯模糊阴影。

来源:draw/src/shader/sdf.rs:11-148


完整示例:RoundedView 的 draw_bg

draw_bg: {
    pixel: fn(){
        let sdf = Sdf2d::viewport(self.pos * self.rect_size);
        sdf.box(
            self.border_width,
            self.border_width,
            self.rect_size.x - self.border_width * 2.0,
            self.rect_size.y - self.border_width * 2.0,
            max(1.0, self.border_radius)
        );
        sdf.fill_keep(self.color);
        if self.border_width > 0.0 {
            sdf.stroke(self.border_color, self.border_width);
        }
        return sdf.result;
    }
}

每个属性(border_widthborder_radiuscolor)都是 instance 变量,可在 Splash 中设置或通过 Animator 动画化(详见第10章)。


模式提炼

模式:viewport + shape + fill 三步法

let sdf = Sdf2d::viewport(self.pos * self.rect_size);
sdf.circle(x, y, r);    // 1. 定义形状
sdf.fill(color);         // 2. 着色
return sdf.result;       // 3. 输出

几乎所有 Makepad shader 都遵循这个三步模式。多个形状按顺序叠加——后绘制的在上面。

模式:fill_keep + stroke 组合

sdf.box(x, y, w, h, r);
sdf.fill_keep(bg_color);              // 填充但不清除形状
sdf.stroke(border_color, border_width);// 同一形状上描边

fill_keep 保留距离场,后续 stroke 可在同一形状上描边。用 fill(不带 keep)则会重置距离场。

模式:instance 变量驱动外观

Button{ draw_bg: { color: #f00, border_radius: 8.0 } }

Shader 中的 instance 变量可从 Splash DSL、Rust 代码和 Animator 三个入口驱动,比传统 CSS 更灵活。


本章小结

概念说明
Sdf2d::viewport初始化距离场,传入像素坐标
circle / box / rect基础形状原语
fill / stroke / glow着色操作
union / intersect / subtract布尔运算组合形状
blend(k)两形状间插值,适合动画
Pal / GaussShadow调色板和阴影辅助工具
instance 变量可从 DSL/Rust/Animator 设置的 shader 参数

SDF shader 解决了"形状怎么画"。下一章讲解矢量图形(Vector Widget),用三角化方式绘制复杂的 SVG 路径和动画。