Retour à l'accueil

Kotlin DSL pour les shaders AGSL Android

Kotlin DSL simplifie le développement de shaders AGSL pour Android, en remplaçant les littéraux de chaînes par des constructions typées. Exemples d'effets wave, signal, radar utilisant stdlib. Performances natives et intégration avec Compose préservées.

DSL pour les shaders AGSL : des chaînes aux types sur Android
Advertisement 728x90

# DSL Kotlin pour les shaders AGSL sur Android : Approche typée

Le développement de shaders AGSL pour Android avec RuntimeShader et RenderEffect bute souvent sur des obstacles comme les littéraux en chaîne, le bind manuel des uniforms et un faible support IDE. Le DSL Kotlin résout ces problèmes en générant du code AGSL natif sans perte de performance. La bibliothèque transforme des constructions typées en shaders prêts à l'emploi tout en conservant l'accès au AGSL brut via agslSource().

Cela vous permet de vous concentrer sur la logique des effets plutôt que sur la syntaxe des chaînes. Compatible API 33+, intégration avec Compose, et bibliothèque standard de primitives procédurales.

Points douloureux du AGSL brut en Kotlin

Les shaders sous forme de chaînes perdent les avantages de l'IDE comme l'autocomplétion, le refactoring et la vérification de types. Les uniforms sont déclarés manuellement — une incohérence de nom fait planter l'app. Les effets complexes avec smoothstep, fract et mix deviennent des monstres illisibles. En Compose, vous ajoutez de la boilerplate pour le temps et les mises à jour.

Google AdInline article slot

Le DSL supprime ces obstacles : les uniforms sont typés, les calculs intermédiaires nommés via let(), et les fonctions standard appelées de manière déclarative.

Ajout de la bibliothèque

Ajoutez ces dépendances à votre 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")
}

Comparaison : Déplacement ondulatoire

AGSL brut

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

DSL Kotlin

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

Le AGSL généré correspond exactement à la logique, mais votre code source est typé et lisible.

Google AdInline article slot

Exemples d'effets par complexité

1. Onde : Effet de base

Simple décalage Y avec sin. Idéal pour apprendre fragCoord, sample(), uniforms et let() pour nommer les étapes sans complexité superflue.

2. Signal : Fonctions et masques

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() extrait des fonctions personnalisées, gridMask() et scanlines() de la stdlib simplifient les masques. ifElse() garde un style déclaratif.

3. Radar : Coordonnées polaires

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

polarCoordinates(), arcMask() et ringMask() de la stdlib remplacent les chaînes de smoothstep manuelles. maskedScreen() et maskedOverlay() simplifient le blending.

Google AdInline article slot

4. Metaballs : Formes procédurales

Cet effet construit des cercles SDF avec un min lissé pour des blobs néon. Le DSL gère des effets procéduraux complets tout en gardant la géométrie complexe lisible.

Points clés

  • Le DSL Kotlin génère du AGSL natif sans surcharge — vérifiez via agslSource().
  • Uniforms typés et let() corrigent les maux du code en chaînes et du refactoring.
  • Bibliothèque standard (gridMask, polarCoordinates, arcMask) gagne du temps sur les primitives.
  • fn() et ifElse() permettent la modularité sans impératif.
  • Intégration Compose via autoUniformTime() et bind de paramètres.

— Editorial Team

Advertisement 728x90

Lire ensuite