Volver al inicio

Kotlin DSL para shaders AGSL Android

Kotlin DSL simplifica el desarrollo de shaders AGSL para Android, reemplazando literales de cadena con construcciones tipadas. Ejemplos de efectos wave, signal, radar usando stdlib. Rendimiento nativo e integración con Compose preservados.

DSL para shaders AGSL: de cadenas a tipos en Android
Advertisement 728x90

# DSL de Kotlin para Shaders AGSL en Android: Enfoque Tipado

Desarrollar shaders AGSL para Android con RuntimeShader y RenderEffect suele toparse con obstáculos como literales de cadena, vinculación manual de uniforms y pobre soporte del IDE. El DSL de Kotlin resuelve estos problemas generando código AGSL nativo sin impacto en el rendimiento. La librería convierte construcciones tipadas en shaders listos para usar, manteniendo acceso al AGSL crudo mediante agslSource().

Esto te permite centrarte en la lógica del efecto en lugar de la sintaxis de cadenas. Compatible con API 33+, se integra con Compose e incluye una biblioteca estándar de primitivas procedurales.

Problemas del AGSL Crudo en Kotlin

Los shaders como cadenas pierden las ventajas del IDE como autocompletado, refactorización y verificación de tipos. Los uniforms se declaran manualmente: un error en el nombre hace que tu app se estrelle. Efectos complejos con smoothstep, fract y mix se convierten en un galimatías ilegible. En Compose, añades código extra para tiempo y actualizaciones.

Google AdInline article slot

El DSL elimina estos obstáculos: los uniforms son tipados, los cálculos intermedios se nombran con let(), y las funciones estándar se invocan de forma declarativa.

Añadir la Librería

Añade estas dependencias a tu 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")
}

Comparación: Desplazamiento de Onda

AGSL Crudo

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

El AGSL generado coincide exactamente con la lógica, pero tu código fuente es tipado y legible.

Google AdInline article slot

Ejemplos de Efectos por Complejidad

1. Onda: Efecto Básico

Desplazamiento simple en Y con sin. Ideal para aprender fragCoord, sample(), uniforms y let() para nombrar pasos sin complejidad extra.

2. Señal: Funciones y Máscaras

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() extrae funciones personalizadas, gridMask() y scanlines() de la stdlib simplifican máscaras. ifElse() mantiene todo declarativo.

3. Radar: Coordenadas Polares

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() y ringMask() de la stdlib reemplazan cadenas manuales de smoothstep. maskedScreen() y maskedOverlay() agilizan la mezcla.

Google AdInline article slot

4. Metabolas: Formas Procedimentales

Este efecto construye círculos SDF con min suavizado para blobs de neón. El DSL maneja efectos procedurales completos manteniendo la geometría compleja legible.

Lecciones Clave

  • El DSL de Kotlin genera AGSL nativo sin sobrecarga — verifica con agslSource().
  • Uniforms tipados y let() solucionan problemas de código basado en cadenas y refactorización.
  • Biblioteca estándar (gridMask, polarCoordinates, arcMask) ahorra tiempo en primitivas.
  • fn() e ifElse() permiten modularidad sin volverse imperativo.
  • Integración con Compose vía autoUniformTime() y vinculación de parámetros.

— Editorial Team

Advertisement 728x90

Leer después