이 페이지에서는 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); } }
셰이더 애니메이션
비슷한 기법을 사용해 iTime
및 iDuration
유니폼을 받도록 수정하여 셰이더에 애니메이션을 적용할 수 있습니다. 셰이더는 이러한 값을 사용하여 색상의 삼각파를 만들어 그라데이션 값을 앞뒤로 순환합니다.
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"+ "}";
맞춤 뷰 소스 코드에서 ValueAnimator
는 iTime
유니폼을 업데이트합니다.
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);
모서리가 아닌 중심 축을 기준으로 텍스트를 회전하려면 텍스트 경계를 가져온 다음 preTranslate
및 postTranslate
를 사용하여 행렬을 변경하여 텍스트를 변환하도록 합니다. 텍스트가 화면에 그려지는 위치를 변경하지 않고 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)
}
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);
버튼 위에 혼합된 그리드 효과가 플로팅 작업 버튼 아래에 나타납니다(다른 View
계층 구조에 있기 때문).