Na tej stronie omawiamy podstawy AGSL i różne sposoby ich używania w aplikacji na Androida.
Prosty program do cieniowania AGSL
Kod programu do cieniowania jest wywoływany dla każdego narysowanego piksela i zwraca kolor, którym piksel powinien być pomalowany. Bardzo prosty cienier to taki, który zawsze zwraca
jeden kolor. W tym przykładzie użyto czerwonego. Program do cieniowania jest zdefiniowany w elemencie 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" + "}";
Następnym krokiem jest utworzenie obiektu RuntimeShader
zainicjowanego przez ciąg znaków cieniowania. Spowoduje to również skompilowanie programu do cieniowania.
Kotlin
val fixedColorShader = RuntimeShader(COLOR_SHADER_SRC)
Java
RuntimeShader fixedColorShader = new RuntimeShader(COLOR_SHADER_SRC);
Elementu RuntimeShader
można używać wszędzie tam, gdzie można korzystać ze standardowego narzędzia do cieniowania Androida. Możesz na przykład użyć jej do rysowania w niestandardowym elemencie View
przy użyciu Canvas
.
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 } }
Generuje się czerwony znak View
. Możesz użyć metody uniform
, aby przekazać do cieniowania parametr koloru, który ma zostać narysowany. Najpierw dodaj kolor uniform
do cieniowania:
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" + "}";
Następnie wywołaj setColorUniform
z niestandardowego elementu View
, aby przekazać wybrany kolor do cieniowania AGSL.
Kotlin
fixedColorShader.setColorUniform("iColor", Color.GREEN )
Java
fixedColorShader.setColorUniform("iColor", Color.GREEN );
Teraz uzyskujesz zielony kolor View
. Kolorem View
steruje się za pomocą parametru z kodu w niestandardowym elemencie View
, zamiast być osadzony w cieniowaniu.
Zamiast tego możesz utworzyć efekt gradientu kolorów. Najpierw musisz zmienić cieniowanie, aby akceptować rozdzielczość View
jako dane wejściowe:
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" + "}";
Rysowanie gradientu
Ten program do cieniowania jest nieco fantazyjny. Dla każdego piksela tworzy się wektor float2
zawierający współrzędne x i y podzielone przez rozdzielczość, co da wartość z zakresu od 0 do 1. Następnie używa przeskalowanego wektora do utworzenia czerwonego i zielonego składu zwracanego koloru.
Przekazujesz rozdzielczość View
do cieniowania AGSL uniform
, wywołując metodę setFloatUniform
.
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); } }
Animowanie programu do cieniowania
Możesz użyć podobnej metody do animowania cieniowania, modyfikując go, aby uzyskać stroje iTime
i iDuration
. cieniowanie użyje tych wartości, aby utworzyć trójkątną falę dla kolorów, co spowoduje, że będą się one zmieniać w obu wartościach gradientu.
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"+ "}";
Na podstawie kodu źródłowego widoku niestandardowego ValueAnimator
aktualizuje uniform 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()); } });
Malowanie złożonych obiektów
Nie musisz rysować programu do cieniowania, aby wypełnić tło. Możesz go używać w dowolnym miejscu, które akceptuje obiekt Paint
, np. drawText
.
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);
Przekształcenia cieniowania i obszaru roboczego
Do zacienionego tekstu możesz zastosować dodatkowe przekształcenia Canvas
, np. obrót. W ValueAnimator
możesz zaktualizować macierz dla obrotów 3D za pomocą wbudowanej klasy android.graphics.Camera
.
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);
Chcesz obracać tekst od osi środkowej, a nie od rogu, ustaw progi tekstu, a następnie użyj preTranslate
i postTranslate
, aby zmienić macierz tak, aby przetłumaczył tekst, tak aby 0,0 był środkiem obrotu, ale nie zmieniono pozycji jego rysowania na ekranie.
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();
Używanie RuntimeShadera z Jetpack Compose
Używanie RuntimeShader
jest jeszcze łatwiejsze, jeśli renderujesz interfejs za pomocą Jetpack Compose. Zaczynam od tego samego
cienia gradientowego co wcześniej:
private const val COLOR_SHADER_SRC =
"""uniform float2 iResolution;
half4 main(float2 fragCoord) {
float2 scaled = fragCoord/iResolution.xy;
return half4(scaled, 0, 1);
}"""
Możesz zastosować go do ShaderBrush
. Możesz następnie używać ShaderBrush
jako parametru poleceń rysowania w zakresie rysowania dostępnym w 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)
}
Używanie RuntimeShader z funkcją RenderEffect
Za pomocą RenderEffect
możesz zastosować RuntimeShader
do nadrzędnych View
i wszystkich widoków podrzędnych. To droższe niż rysowanie niestandardowego elementu View
. ale pozwala łatwo utworzyć efekt obejmujący to, co zostało wcześniej narysowane za pomocą createRuntimeShaderEffect
.
Kotlin
view.setRenderEffect(RenderEffect.createRuntimeShaderEffect(myShader, "background"))
Java
view.setRenderEffect(RenderEffect.createRuntimeShaderEffect(myShader, "background"));
Drugi parametr to nazwa jednolitego programu do cieniowania, który możesz eval
z parametrem współrzędnych (takim jak przekazywany we fragCoord), aby uzyskać pierwotny kolor obiektu RenderNode
(widoku danych i jego widoków podrzędnych), co umożliwia wykonywanie różnego rodzaju efektów.
uniform shader background; // Root node of View tree to be altered
return mix(returnColor, background.eval(fragCoord), 0.5);
Efekt siatki zmieszany nad przyciskiem, ale pod pływającym przyciskiem polecenia (ponieważ znajduje się on w innej hierarchii View
).