Hi, any idea how can I achieve this stunning effec...
# compose
y
Hi, any idea how can I achieve this stunning effect on player? 🙏
j
I think this is your system that is doing that. It's a normal media style notification
y
On player I suppose yes. But I would like to achieve this in some my card in App
The Wave 🌊 effect.
j
I think this is a shader
y
I tried to do with shader something similar. But it’s not even close …
I have no idea how they made it on player
s
If it’s the squiggly thing you’re after, I know I’ve seen it before in this https://github.com/saket/ExtendedSpans repo, maybe you can look into it and get some inspiration about how to implement it? If it’s about the thing going on around the entire player, maybe that code is open source and you can search for it? I haven’t done any serious code searching inside the system aside from
<https://cs.android.com/>
, so not sure if you’d find that in there too.
y
🤔 I guess we don’t understand what effect I mean
I mean the effect that appears after the ripple effect on the entire surface of the player.
Such dark chaotic ripples across the entire surface
s
I re-read what you said and I think you mean the effect that goes on top of the entire player, that looks like there’s some “water” on top of it or something like that. So scratch the link about the squiggly lines, my bad. Still, for that ripple, as I said above, maybe there’s a way for you to try and find the code for that effect in AOSP? Shouldn’t it be in there somewhere?
y
Yes. The water effect!!
s
I guess the question is not so much if there is a way to do this, but more like how to get exactly the same effect, which to me sounds impossible unless you find the specific source code that is used by the system too?
a
Copy code
class TurbulenceNoiseShader : RuntimeShader(TURBULENCE_NOISE_SHADER) {
    // language=AGSL
    companion object {
        private const val UNIFORMS =
            """
            uniform float in_gridNum;
            uniform vec3 in_noiseMove;
            uniform vec2 in_size;
            uniform float in_aspectRatio;
            uniform float in_opacity;
            uniform float in_pixelDensity;
            layout(color) uniform vec4 in_color;
            layout(color) uniform vec4 in_backgroundColor;
        """

        private const val SHADER_LIB =
            """
            float getLuminosity(vec3 c) {
                return 0.3*c.r + 0.59*c.g + 0.11*c.b;
            }

            vec3 maskLuminosity(vec3 dest, float lum) {
                dest.rgb *= vec3(lum);
                // Clip back into the legal range
                dest = clamp(dest, vec3(0.), vec3(1.0));
                return dest;
            }
        """

        private const val MAIN_SHADER =
            """
            vec4 main(vec2 p) {
                vec2 uv = p / in_size.xy;
                uv.x *= in_aspectRatio;

                vec3 noiseP = vec3(uv + in_noiseMove.xy, in_noiseMove.z) * in_gridNum;
                float luma = simplex3d(noiseP) * in_opacity;
                vec3 mask = maskLuminosity(in_color.rgb, luma);
                vec3 color = in_backgroundColor.rgb + mask * 0.6;

                // Add dither with triangle distribution to avoid color banding. Ok to dither in the
                // shader here as we are in gamma space.
                float dither = triangleNoise(p * in_pixelDensity) / 255.;

                // The result color should be pre-multiplied, i.e. [R*A, G*A, B*A, A], thus need to
                // multiply rgb with a to get the correct result.
                color = (color + dither.rrr) * in_color.a;
                return vec4(color, in_color.a);
            }
        """

        private const val TURBULENCE_NOISE_SHADER =
            ShaderUtilLibrary.SHADER_LIB + UNIFORMS + SHADER_LIB + MAIN_SHADER
    }

    /** Sets the number of grid for generating noise. */
    fun setGridCount(gridNumber: Float = 1.0f) {
        setFloatUniform("in_gridNum", gridNumber)
    }

    /**
     * Sets the pixel density of the screen.
     *
     * Used it for noise dithering.
     */
    fun setPixelDensity(pixelDensity: Float) {
        setFloatUniform("in_pixelDensity", pixelDensity)
    }

    /** Sets the noise color of the effect. */
    fun setColor(color: Int) {
        setColorUniform("in_color", color)
    }

    /** Sets the background color of the effect. */
    fun setBackgroundColor(color: Int) {
        setColorUniform("in_backgroundColor", color)
    }

    /**
     * Sets the opacity to achieve fade in/ out of the animation.
     *
     * Expected value range is [1, 0].
     */
    fun setOpacity(opacity: Float) {
        setFloatUniform("in_opacity", opacity)
    }

    /** Sets the size of the shader. */
    fun setSize(width: Float, height: Float) {
        setFloatUniform("in_size", width, height)
        setFloatUniform("in_aspectRatio", width / max(height, 0.001f))
    }

    /** Current noise movements in x, y, and z axes. */
    var noiseOffsetX: Float = 0f
        private set
    var noiseOffsetY: Float = 0f
        private set
    var noiseOffsetZ: Float = 0f
        private set

    /** Sets noise move offset in x, y, and z direction. */
    fun setNoiseMove(x: Float, y: Float, z: Float) {
        noiseOffsetX = x
        noiseOffsetY = y
        noiseOffsetZ = z
        setFloatUniform("in_noiseMove", noiseOffsetX, noiseOffsetY, noiseOffsetZ)
    }
}
This is from AOSP
r
You might want to use AGSL, but have your image as input so it looks like water is on top of the surface of your content. I did something similar with this Jelly fish post https://medium.com/androiddevelopers/making-jellyfish-move-in-compose-animating-imagevectors-and-applying-agsl-rendereffects-3666596a8888
In particular, the part where you set a renderEffect of graphicsLayer on the Image composable like so
Copy code
Image(
   vectorPainter, contentDescription = "",
   modifier = Modifier
       .fillMaxSize()
       .background(largeRadialGradient)
       .onSizeChanged { size ->
           shader.setFloatUniform(
               "resolution",
               size.width.toFloat(),
               size.height.toFloat()
           )
       }
       .graphicsLayer {
           shader.setFloatUniform("time", time)
           renderEffect = android.graphics.RenderEffect
               .createRuntimeShaderEffect(
                   shader,
                   "contents"
               )
               .asComposeRenderEffect()
       }
)
You can set the render effect on a whole tree of composables
k
ShaderToy is a decent source of implementation “inspirations” for various effects. https://www.shadertoy.com/results?query=water+ripples&amp;sort=popular&amp;from=12&amp;num=12 is for some water ripples. I don’t think you can expect to get the exact effect you’re always looking for, but rather a starting point for your own take on it.
a
I mean, the AOSP sample is AGSL code, it can probably be ported over pretty easily to compose with some minor tweaking
y
@andrew @Rebecca Franks @Kirill Grouchnikov thank you all. It’s completely new to me. Thanks a lot for the resources. It will probably take me a while to try it out and at least understand a little. Thank you very much!