dely Tech Blog

クラシル・TRILLを運営するdely株式会社の開発ブログです

Android:パーリンノイズを使ったアニメーションを試してみる

はじめに

立夏も過ぎて気温が上がってくるのを感じる中、少しずつ夏が近づいて来ているのを感じますね。 このテックブログを読んでいただいている皆さんいかがお過ごしでしょうか? 近所でセミがもう鳴いてるのに気付いて温暖化をひしひしと感じている、みうらです。

皆さんAGSLをご存知でしょうか?Androidでシェーダープログラミングが行えるものです。 2022年に公開されてから少し経ちますが、要求されるAndroidバージョンが13と高いこともあり使ったことがなく、どこかで試してみたいと思っていました。 そんな中、少し前ですがGoogleのRebeccaさんから以下の記事が共有されているのを拝見しました。 この記事でAGSLの概要とパーリンノイズについて知ったので、試していきたいと思います。

medium.com

パーリンノイズとは?

かなり簡単に言うと、ノイズとはランダム値ですが、パーリンノイズは完全ランダムではなく連続性を持っている自然的なランダム値のことです。 雲や水、波といった自然的なものを表現するのに使ったりします。

AGSLでパーリンノイズを使ってみよう

では早速AGSLを使ってみましょう。 以下のクラシウサギ画像にAGSLでシェーダーを適用します。

ここでは記事で紹介されているパーリンノイズをそのまま使ってみます。 AGSL用のパーリンノイズは以下のGistを利用しています。 [part] Jellyfish perlin noise · GitHub

次にAGSLを適用するImageを定義します。

@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@Composable
private fun UsagiPerlinNoiseImage(
    modifier: Modifier = Modifier
) {
    val time by produceState(0f) {
        while (true) {
            withInfiniteAnimationFrameMillis {
                value = it / 1000f
            }
        }
    }

    val shader = RuntimeShader(PERLIN_NOISE)

    Image(
        painter = painterResource(id = R.drawable.kurashi_normal_bow),
        contentDescription = null,
        modifier = modifier
            .onSizeChanged { size ->
                shader.setFloatUniform(
                    "resolution",
                    size.width.toFloat(),
                    size.height.toFloat()
                )
            }
            .graphicsLayer {
                shader.setFloatUniform("time", time)
                renderEffect = android.graphics.RenderEffect
                    .createRuntimeShaderEffect(
                        shader,
                        "contents"
                    )
                    .asComposeRenderEffect()
            }
    )
}

このパーリンノイズが適用されたAGSLを実行してみましょう。

シェーダーとして波のようなアニメーション効果が発生しました。 時間の経過と共に変化するノイズを画像に適用することで、波のような表現が発生しています。

ポジション値に対してノイズを適用してみよう

パーリンノイズは他のものにも適用可能です。今度はポジション値に対して適用してみましょう。 画像は以下のクラシウサギ画像を使います。

AGSLのままではポジション値に対して適用することが難しいと思ったため、ここでは疑似的な2次元ノイズ生成メソッドを定義します。 このメソッドはChatGPTで生成したものを一部加工しています。

private fun perlinNoise(x: Float, y: Float): Float {
    val n = floor(x) + floor(y) * 57 // 57はオフセット値
    return sin(n) * 43758.547f
}

このノイズ生成メソッドからポジション値にノイズを適用させることでアニメーションさせてみます。 Imageにoffsetを適用することでランダムにアニメーションさせます。

@RequiresApi(Build.VERSION_CODES.TIRAMISU)
@Composable
private fun UsagiImage(
    modifier: Modifier = Modifier
) {
    val time by produceState(0f) {
        while (true) {
            withInfiniteAnimationFrameMillis {
                value = it / 10f
            }
        }
    }

    val noiseOffsetX = remember { mutableFloatStateOf(0f) }
    val noiseOffsetY = remember { mutableFloatStateOf(0f) }

    LaunchedEffect(time) {
        noiseOffsetX.floatValue = perlinNoise(time, 0f) % 0.5f * 100
        noiseOffsetY.floatValue = perlinNoise(0f, time) % 0.5f * 100
    }

    Image(
        painter = painterResource(id = R.drawable.kurashi_normal_cry),
        contentDescription = null,
        modifier = modifier
            .offset {
                IntOffset(noiseOffsetX.floatValue.toInt(), noiseOffsetY.floatValue.toInt())
            }
    )
}

ポジション値に設定することでガクブル震えるようなアニメーションを作ることができました。
※GIFだとわかりづらいですが、激しく震えるアニメーションをさせています

まとめ

今回はAndroid上でパーリンノイズを使って画像を変化させてみました。 使ってみるととてもシンプルに使える割に、いつもとは違った自然的な印象を与えることができるので、表現の一つとして覚えておくと使えそうです。

最後に

delyではエンジニアを大募集しています!今は特にバックエンドエンジニアを募集しています💡
実際にメンバーと話してみたいと思ってくださった方にはカジュアルにお話しする場も設けられますので、どうぞお気軽にお声かけください 🙌
Androidエンジニアも募集しています!下記の採用リンクから応募いただけますのでお願いします!
herp.careers dely.jp