返回首页

Kotlin DSL 用于 AGSL 着色器 Android

Kotlin DSL 简化 Android 的 AGSL 着色器开发,用类型化结构替换字符串字面量。使用 stdlib 的效果 wave、signal、radar 示例。保留原生性能和与 Compose 的集成。

AGSL 着色器 DSL:从字符串到类型,在 Android 中
Advertisement 728x90

Android AGSL 着色器 Kotlin DSL:类型安全开发之道

使用 RuntimeShader 和 RenderEffect 开发 Android AGSL 着色器时,常遇到字符串字面量、手动 uniform 绑定和 IDE 支持差等问题。Kotlin DSL 通过生成原生 AGSL 代码解决这些痛点,且无性能损失。该库将类型化构造转化为即用着色器,同时通过 agslSource() 保留对原始 AGSL 的访问。

这样,你能专注于效果逻辑而非字符串语法。支持 API 33+,无缝集成 Compose,并内置过程化原语标准库。

原始 AGSL 在 Kotlin 中的痛点

字符串形式的着色器丢失 IDE 自动补全、重构和类型检查等便利。uniform 需要手动声明,名称不匹配就会崩溃应用。使用 smoothstep、fract 和 mix 等构建复杂效果时,代码迅速变得难以阅读。在 Compose 中,还需额外样板代码处理时间和更新。

Google AdInline article slot

DSL 扫清这些障碍:uniform 类型化,中间计算通过 let() 命名,标准函数以声明式调用。

添加库依赖

在 build.gradle 中添加以下依赖:

dependencies {
    implementation("io.github.i-redbyte:redbytefx-core:1.0.0")
    implementation("io.github.i-redbyte:redbytefx-compose:1.0.0")
    implementation("io.github.i-redbyte:redbytefx-stdlib:1.0.0")
}

对比:波浪位移效果

原始 AGSL

uniform shader content;
uniform float wave_amplitude;
uniform float wave_frequency;

half4 main(float2 fragCoord) {
  float2 offset = float2(
    0.0,
    sin(fragCoord.x * wave_frequency) * wave_amplitude
  );
  return content.eval(fragCoord + offset);
}

Kotlin DSL

val effect = redbytefx {
    val amplitudeUniform = uniformFloat(0f, "wave_amplitude")
    val frequencyUniform = uniformFloat(0.08f, "wave_frequency")

    val x = let(fragCoord.x, "x")
    val waveOffset = let(
        float2(0f, sin(x * frequencyUniform) * amplitudeUniform),
        "wave_offset"
    )

    sample(fragCoord + waveOffset)
}

生成的 AGSL 逻辑完全一致,但源代码类型安全且易读。

Google AdInline article slot

按复杂度分类的效果示例

1. 波浪:基础效果

简单 Y 轴偏移,使用 sin 函数。适合学习 fragCoord、sample()、uniform 和 let(),命名步骤无额外复杂度。

2. 信号:函数与蒙版

val effect = redbytefx {
    val densityUniform = uniformFloat(8f, "signal_density")
    val lineWidthUniform = uniformFloat(0.08f, "signal_line_width")
    val amountUniform = uniformFloat(0.85f, "signal_amount")

    val pulseBand = fn(
        name = "pulse_band",
        arg1 = FloatType,
        arg2 = FloatType,
        returns = FloatType
    ) { phase, threshold ->
        step(threshold, smoothstep(0.08f, 0.92f, fract(phase)))
    }

    val base = let(sample(), "base")
    val uv = let(normalizedUv(), "uv")
    val grid = let(gridMask(uv, densityUniform, lineWidthUniform), "grid")
    val scan = let(scanlines(fragCoord.y, 14f, 3f), "scan")
    val pulse = let(pulseBand(uv.y * densityUniform * 0.5f + grid * 0.35f, 0.55f), "pulse")
    val hardMask = let(step(0.45f, scan * pulse), "hard_mask")
    val active = let((grid gt 0.05f) or (hardMask gt 0.5f), "active")
    val accent = let(color(float3(0.05f, 0.95f, 0.82f), base.a), "accent")
    val mixed = let(mix(base, accent, min(grid * 0.85f + hardMask * 0.35f, 1f)), "mixed")

    ifElse(active, mix(base, mixed, amountUniform), base)
}

fn() 提取自定义函数,来自 stdlib 的 gridMask() 和 scanlines() 简化蒙版处理。ifElse() 保持声明式风格。

3. 雷达:极坐标

val effect = redbytefx {
    val time by autoUniformTime()
    val speed by autoUniformFloat(0.72f)
    val radius by autoUniformFloat(0.34f)
    val amount by autoUniformFloat(0.86f)

    val base = let(sample(), "base")
    val uv = let(fragCoord / resolution, "uv")
    val polar = let(polarCoordinates(uv), "polar")
    val sweepAngle = let(fract(time * speed * 0.08f), "sweep_angle")
    val sweep = let(angularSweep(uv = uv, angle = sweepAngle, width = 0.12f, feather = 0.03f), "sweep")
    val arc = let(
        arcMask(
            uv = uv,
            radius = radius,
            ringWidth = 0.09f,
            angle = sweepAngle,
            arcWidth = 0.18f,
            feather = 0.03f
        ),
        "arc"
    )
    val outerRing = let(ringMask(uv, radius = radius, width = 0.016f, feather = 0.012f), "outer_ring")
    val innerRing = let(ringMask(uv, radius = max(radius * 0.58f, 0.08f), width = 0.014f, feather = 0.012f), "inner_ring")
    val beam = let(radialRamp(uv = uv, innerRadius = float(0.06f), outerRadius = radius + 0.18f), "beam")
    val mask = let(max(max(sweep * beam, arc), max(outerRing, innerRing)), "mask")

    val tint = let(
        color(
            mix(0.05f, 0.18f, polar.x * 1.4f),
            mix(0.24f, 1f, sweep + arc * 0.55f),
            mix(0.10f, 0.62f, polar.y * 0.45f + outerRing * 0.35f),
            base.a
        ),
        "tint"
    )

    val screened = let(maskedScreen(base, tint, mask, amount), "screened")
    maskedOverlay(screened, color(float3(0.82f, 1f, 0.72f), base.a), arc, amount * 0.32f)
}

来自 stdlib 的 polarCoordinates()、arcMask() 和 ringMask() 取代手动 smoothstep 链。maskedScreen() 和 maskedOverlay() 简化混合模式。

Google AdInline article slot

4. 变体球:过程化形状

此效果使用平滑 min 构建 SDF 圆形,形成霓虹 blob。DSL 处理完整过程化效果,同时保持复杂几何易读。

核心要点

  • Kotlin DSL 生成原生 AGSL,无额外开销——通过 agslSource() 验证。
  • 类型化 uniform 和 let() 解决字符串代码及重构难题。
  • 标准库(gridMask、polarCoordinates、arcMask)加速原语开发。
  • fn() 和 ifElse() 实现模块化,避免命令式编程。
  • 通过 autoUniformTime() 和参数绑定无缝集成 Compose。

— Editorial Team

Advertisement 728x90

继续阅读