Android 앱에서 AGSL 사용

이 페이지에서는 AGSL 기본사항과 Android 앱에서 AGSL을 사용하는 다양한 방법을 설명합니다.

간단한 AGSL 셰이더

셰이더 코드는 그려진 각 픽셀에 대해 호출되며 픽셀을 페인팅할 색상을 반환합니다. 매우 간단한 셰이더는 항상 단일 색상을 반환합니다. 이 예에서는 빨간색을 사용합니다. 셰이더는 String 내에서 정의됩니다.

Kotlin

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

Java

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

다음 단계는 셰이더 문자열로 초기화된 RuntimeShader 객체를 만드는 것입니다. 그러면 셰이더도 컴파일됩니다.

Kotlin

val fixedColorShader = RuntimeShader(COLOR_SHADER_SRC)

Java

RuntimeShader fixedColorShader = new RuntimeShader(COLOR_SHADER_SRC);

RuntimeShader는 표준 Android 셰이더가 가능한 곳이면 어디에나 사용할 수 있습니다. 예를 들어, 이 메서드를 사용하여 Canvas를 사용하여 맞춤 View에 그릴 수 있습니다.

Kotlin

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

Java

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 색상을 추가합니다.

Kotlin

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

Java

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

그런 다음 맞춤 View에서 setColorUniform를 호출하여 원하는 색상을 AGSL 셰이더에 전달합니다.

Kotlin

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

Java

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

이제 녹색 View이 표시됩니다. View 색상은 셰이더에 삽입되지 않고 맞춤 View의 코드 매개변수를 사용하여 제어됩니다.

대신 색상 그라데이션 효과를 만들 수 있습니다. 먼저 View 해상도를 입력으로 허용하도록 셰이더를 변경해야 합니다.

Kotlin

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

Java

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" +
   "}";

그라데이션 그리기

이 셰이더는 약간 복잡한 작업을 합니다. 각 픽셀에 대해 해상도로 나눈 x 및 y 좌표를 포함하는 float2 벡터를 만들고 이 벡터는 0과 1 사이의 값을 생성합니다. 그런 다음 조정된 벡터를 사용하여 반환 색상의 빨간색과 녹색 구성요소를 구성합니다.

setFloatUniform를 호출하여 View의 해상도를 AGSL 셰이더 uniform에 전달합니다.

Kotlin

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

Java

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);
   }
}
빨간색 및 녹색 그라데이션
빨간색 및 녹색 그라데이션

셰이더 애니메이션

비슷한 기법을 사용해 iTimeiDuration 유니폼을 받도록 수정하여 셰이더에 애니메이션을 적용할 수 있습니다. 셰이더는 이러한 값을 사용하여 색상의 삼각파를 만들어 그라데이션 값을 앞뒤로 순환합니다.

Kotlin

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

Java

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"+
   "}";

맞춤 뷰 소스 코드에서 ValueAnimatoriTime 유니폼을 업데이트합니다.

Kotlin

// 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()

Java

// 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());
   }
});
빨간색 및 녹색 애니메이션 그라데이션
빨간색 및 녹색 애니메이션 그라데이션

복잡한 물체 색칠하기

배경을 채우기 위해 셰이더를 그릴 필요가 없습니다. drawText와 같이 Paint 객체를 허용하는 모든 위치에서 사용할 수 있습니다.

Kotlin

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

Java

canvas.drawText(ANIMATED_TEXT, TEXT_MARGIN_DP, TEXT_MARGIN_DP + bounds.height(),
   paint);
빨간색 및 녹색 애니메이션 그라데이션 텍스트
빨간색 및 녹색 애니메이션 그라데이션 텍스트

셰이딩 및 캔버스 변환

음영 처리된 텍스트에 회전과 같이 추가 Canvas 변환을 적용할 수 있습니다. ValueAnimator에서 기본 제공되는 android.graphics.Camera 클래스를 사용하여 3D 회전 행렬을 업데이트할 수 있습니다.

Kotlin

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

Java

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

모서리가 아닌 중심 축을 기준으로 텍스트를 회전하려면 텍스트 경계를 가져온 다음 preTranslatepostTranslate를 사용하여 행렬을 변경하여 텍스트를 변환하도록 합니다. 텍스트가 화면에 그려지는 위치를 변경하지 않고 0,0이 회전의 중심이 되도록 합니다.

Kotlin

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

Java

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();
빨간색 및 녹색 회전하는 애니메이션 그라데이션 텍스트
빨간색 및 녹색 회전하는 애니메이션 그라데이션 텍스트

Jetpack Compose와 함께 RuntimeShader 사용

Jetpack Compose를 사용하여 UI를 렌더링하는 경우 RuntimeShader를 사용하는 것이 훨씬 쉽습니다. 이전과 동일한 그라데이션 셰이더로 시작합니다.

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

이 셰이더를 ShaderBrush에 적용할 수 있습니다. 그런 다음 Canvas의 그리기 범위 내에서 그리기 명령어의 매개변수로 ShaderBrush를 사용합니다.

// 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 Compose 그라데이션 원
빨간색과 녹색 그라데이션 원

RenderEffect와 함께 RuntimeShader 사용

RenderEffect를 사용하여 RuntimeShader를 상위 View 모든 하위 뷰에 적용할 수 있습니다. 이는 맞춤 View를 그리는 것보다 비용이 많이 듭니다. 하지만 createRuntimeShaderEffect를 사용하여 원래 그린 요소를 통합하는 효과를 쉽게 만들 수 있습니다.

Kotlin

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

Java

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

두 번째 매개변수는 셰이더 유니폼의 이름으로, 좌표 매개변수 (예: fragCoord에 전달된 값)로 eval하여 RenderNode의 원래 색상 (뷰 및 하위 뷰)을 가져와서 모든 종류의 효과를 실행할 수 있습니다.

uniform shader background;       // Root node of View tree to be altered
return mix(returnColor, background.eval(fragCoord), 0.5);
버튼 위에 그리드가 섞여 있음
버튼 위에 혼합된 AGSL 그리드

버튼 위에 혼합된 그리드 효과가 플로팅 작업 버튼 아래에 나타납니다(다른 View 계층 구조에 있기 때문).