# 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.
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.
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.
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
Aún no hay comentarios.