Использование AGSL в вашем приложении для Android

На этой странице описаны основы AGSL и различные способы использования AGSL в вашем приложении для Android.

Простой шейдер AGSL

Код шейдера вызывается для каждого нарисованного пикселя и возвращает цвет, которым пиксель должен быть окрашен. Чрезвычайно простой шейдер — это тот, который всегда возвращает один цвет; в этом примере используется красный цвет. Шейдер определен внутри String .

Котлин

private const val COLOR_SHADER_SRC =
   """half4 main(float2 fragCoord) {
      return half4(1,0,0,1);
   }"""

Ява

private static final String COLOR_SHADER_SRC =
   "half4 main(float2 fragCoord) {\n" +
      "return half4(1,0,0,1);\n" +
   "}";

Следующим шагом будет создание объекта RuntimeShader , инициализированного строкой шейдера. Это также компилирует шейдер.

Котлин

val fixedColorShader = RuntimeShader(COLOR_SHADER_SRC)

Ява

RuntimeShader fixedColorShader = new RuntimeShader(COLOR_SHADER_SRC);

Ваш RuntimeShader можно использовать везде, где доступен стандартный шейдер Android. Например, вы можете использовать его для рисования в пользовательском View с помощью Canvas .

Котлин

val paint = Paint()
paint.shader = fixedColorShader
override fun onDrawForeground(canvas: Canvas?) {
   canvas?.let {
      canvas.drawPaint(paint) // fill the Canvas with the shader
   }
}

Ява

Paint paint = new Paint();
paint.setShader(fixedColorShader);
public void onDrawForeground(@Nullable Canvas canvas) {
   if (canvas != null) {
      canvas.drawPaint(paint); // fill the Canvas with the shader
   }
}

Это рисует красный View . Вы можете использовать uniform для передачи параметра цвета в рисуемый шейдер. Сначала добавим цветовую uniform в шейдер:

Котлин

private const val COLOR_SHADER_SRC =
"""layout(color) uniform half4 iColor;
   half4 main(float2 fragCoord) {
      return iColor;
   }"""

Ява

private static final String COLOR_SHADER_SRC =
   "layout(color) uniform half4 iColor;\n"+
      "half4 main(float2 fragCoord) {\n" +
      "return iColor;\n" +
   "}";

Затем вызовите setColorUniform из вашего пользовательского View , чтобы передать желаемый цвет в шейдер AGSL.

Котлин

fixedColorShader.setColorUniform("iColor", Color.GREEN )

Ява

fixedColorShader.setColorUniform("iColor", Color.GREEN );

Теперь вы получаете зеленый View ; Цвет View контролируется с помощью параметра из кода вашего пользовательского View , а не встроен в шейдер.

Вместо этого вы можете создать эффект цветового градиента. Сначала вам нужно изменить шейдер, чтобы он принимал разрешение View в качестве входных данных:

Котлин

private const val COLOR_SHADER_SRC =
"""uniform float2 iResolution;
   half4 main(float2 fragCoord) {
      float2 scaled = fragCoord/iResolution.xy;
      return half4(scaled, 0, 1);
   }"""

Ява

private static final String COLOR_SHADER_SRC =
   "uniform float2 iResolution;\n" +
      "half4 main(float2 fragCoord) {\n" +
      "float2 scaled = fragCoord/iResolution.xy;\n" +
      "return half4(scaled, 0, 1);\n" +
   "}";

Рисование градиента

Этот шейдер делает что-то немного необычное. Для каждого пикселя создается вектор float2 , содержащий координаты x и y, разделенные на разрешение, что создает значение от нуля до единицы. Затем он использует этот масштабированный вектор для построения красного и зеленого компонентов возвращаемого цвета.

Вы передаете разрешение View в uniform шейдера AGSL, вызывая setFloatUniform .

Котлин

val paint = Paint()
paint.shader = fixedColorShader
override fun onDrawForeground(canvas: Canvas?) {
   canvas?.let {
      fixedColorShader.setFloatUniform("iResolution", width.toFloat(), height.toFloat())
      canvas.drawPaint(paint)
   }
}

Ява

Paint paint = new Paint();
paint.setShader(fixedColorShader);
public void onDrawForeground(@Nullable Canvas canvas) {
   if (canvas != null) {
      fixedColorShader.setFloatUniform("iResolution", (float)getWidth(), (float()getHeight()));
      canvas.drawPaint(paint);
   }
}
Красный и зеленый градиент
Красный и зеленый градиент

Анимация шейдера

Вы можете использовать аналогичный метод для анимации шейдера, изменив его так, чтобы он получал формы iTime и iDuration . Шейдер будет использовать эти значения для создания треугольной волны для цветов, заставляя их циклически перемещаться вперед и назад по значениям градиента.

Котлин

private const val DURATION = 4000f
private const val COLOR_SHADER_SRC = """
   uniform float2 iResolution;
   uniform float iTime;
   uniform float iDuration;
   half4 main(in float2 fragCoord) {
      float2 scaled = abs(1.0-mod(fragCoord/iResolution.xy+iTime/(iDuration/2.0),2.0));
      return half4(scaled, 0, 1.0);
   }
"""

Ява

private static final float DURATION = 4000f;
private static final String COLOR_SHADER_SRC =
   "uniform float2 iResolution;\n"+
   "uniform float iTime;\n"+
   "uniform float iDuration;\n"+
   "half4 main(in float2 fragCoord) {\n"+
      "float2 scaled = abs(1.0-mod(fragCoord/iResolution.xy+iTime/(iDuration/2.0),2.0));\n"+
      "return half4(scaled, 0, 1.0);\n"+
   "}";

Из исходного кода пользовательского представления ValueAnimator обновляет униформу iTime .

Котлин

// declare the ValueAnimator
private val shaderAnimator = ValueAnimator.ofFloat(0f, DURATION)

// use it to animate the time uniform
shaderAnimator.duration = DURATION.toLong()
shaderAnimator.repeatCount = ValueAnimator.INFINITE
shaderAnimator.repeatMode = ValueAnimator.RESTART
shaderAnimator.interpolator = LinearInterpolator()

animatedShader.setFloatUniform("iDuration", DURATION )
shaderAnimator.addUpdateListener { animation ->
    animatedShader.setFloatUniform("iTime", animation.animatedValue as Float )
}
shaderAnimator.start()

Ява

// declare the ValueAnimator
private final ValueAnimator shaderAnimator = ValueAnimator.ofFloat(0f, DURATION);

// use it to animate the time uniform
shaderAnimator.setDuration((long)DURATION);
shaderAnimator.setRepeatCount(ValueAnimator.INFINITE);
shaderAnimator.setRepeatMode(ValueAnimator.RESTART);
shaderAnimator.setInterpolator(new LinearInterpolator());

animatedShader.setFloatUniform("iDuration", DURATION );
shaderAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
   public final void onAnimationUpdate(ValueAnimator animation) {
      animatedShader.setFloatUniform("iTime", (float)animation.getAnimatedValue());
   }
});
Красный и зеленый анимированный градиент
Красный и зеленый анимированный градиент

Рисуем сложные объекты.

Вам не обязательно рисовать шейдер, чтобы заполнить фон; его можно использовать в любом месте, где принимается объект Paint , например drawText .

Котлин

canvas.drawText(ANIMATED_TEXT, TEXT_MARGIN_DP, TEXT_MARGIN_DP + bounds.height(),
   paint)

Ява

canvas.drawText(ANIMATED_TEXT, TEXT_MARGIN_DP, TEXT_MARGIN_DP + bounds.height(),
   paint);
Красный и зеленый анимированный градиентный текст
Красный и зеленый анимированный градиентный текст

Преобразования затенения и холста

Вы можете применить дополнительные преобразования Canvas к затененному тексту, например вращение. В ValueAnimator вы можете обновить матрицу для трехмерного вращения, используя встроенный класс android.graphics.Camera .

Котлин

// in the ValueAnimator
camera.rotate(0.0f, animation.animatedValue as Float / DURATION * 360f, 0.0f)

Ява

// in the ValueAnimator
camera.rotate(0.0f, (Float)animation.getAnimatedValue() / DURATION * 360f, 0.0f);

Поскольку вы хотите повернуть текст от центральной оси, а не от угла, получите границы текста, а затем используйте preTranslate и postTranslate , чтобы изменить матрицу для перевода текста так, чтобы 0,0 был центром вращения без изменения положения. текст рисуется на экране.

Котлин

linearColorPaint.getTextBounds(ANIMATED_TEXT, 0, ANIMATED_TEXT.length, bounds)
camera.getMatrix(rotationMatrix)
val centerX = (bounds.width().toFloat())/2
val centerY = (bounds.height().toFloat())/2
rotationMatrix.preTranslate(-centerX, -centerY)
rotationMatrix.postTranslate(centerX, centerY)
canvas.save()
canvas.concat(rotationMatrix)
canvas.drawText(ANIMATED_TEXT, 0f, 0f + bounds.height(), paint)
canvas.restore()

Ява

linearColorPaint.getTextBounds(ANIMATED_TEXT, 0, ANIMATED_TEXT.length(), bounds);
camera.getMatrix(rotationMatrix);
float centerX = (float)bounds.width()/2.0f;
float centerY = (float)bounds.height()/2.0f;
rotationMatrix.preTranslate(-centerX, -centerY);
rotationMatrix.postTranslate(centerX, centerY);
canvas.save();
canvas.concat(rotationMatrix);
canvas.drawText(ANIMATED_TEXT, 0f, 0f + bounds.height(), paint);
canvas.restore();
Красный и зеленый вращающийся анимированный градиентный текст
Красный и зеленый вращающийся анимированный градиентный текст

Использование RuntimeShader с Jetpack Compose

Использовать RuntimeShader еще проще, если вы визуализируете свой пользовательский интерфейс с помощью Jetpack Compose . Начнем с того же шейдера градиента, что и раньше:

private const val COLOR_SHADER_SRC =
    """uniform float2 iResolution;
   half4 main(float2 fragCoord) {
   float2 scaled = fragCoord/iResolution.xy;
   return half4(scaled, 0, 1);
}"""

Вы можете применить этот шейдер к ShaderBrush . Затем вы используете ShaderBrush в качестве параметра для команд рисования в области рисования вашего Canvas .

// created as top level constants
val colorShader = RuntimeShader(COLOR_SHADER_SRC)
val shaderBrush = ShaderBrush(colorShader)

Canvas(
   modifier = Modifier.fillMaxSize()
) {
   colorShader.setFloatUniform("iResolution",
   size.width, size.height)
   drawCircle(brush = shaderBrush)
}
AGSL Составление градиентного круга
Красный и зеленый градиентный круг

Использование RuntimeShader с RenderEffect

Вы можете использовать RenderEffect , чтобы применить RuntimeShader к родительскому View и всем дочерним представлениям. Это дороже, чем рисование собственного View . но он позволяет легко создавать эффект, включающий в себя то, что изначально было нарисовано с помощью createRuntimeShaderEffect .

Котлин

view.setRenderEffect(RenderEffect.createRuntimeShaderEffect(myShader, "background"))

Ява

view.setRenderEffect(RenderEffect.createRuntimeShaderEffect(myShader, "background"));

Второй параметр — это имя формы шейдера, которую вы можете eval с помощью параметра координат (например, переданного в fragCoord), чтобы получить исходный цвет RenderNode (представление и его дочерние представления), что позволяет вам выполнять все виды операций. эффекты.

uniform shader background;       // Root node of View tree to be altered
return mix(returnColor, background.eval(fragCoord), 0.5);
Сетка наложена на кнопку
Сетка AGSL наложена на кнопку

Эффект сетки, смешанный с кнопкой, но под кнопкой плавающего действия (поскольку она находится в другой иерархии View ).