Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 7bf01595 authored by Yein Jo's avatar Yein Jo
Browse files

Add rain splash.

Outline shader takes in the foreground image, checks the alpha and
determines the outline. Then we draw splashes around the outline.

Bug: 325090421
Test: Manual
Flag: N/A
Change-Id: I782e86c691f4a97aee641ef189440fcc15237975
parent 270cc47a
Loading
Loading
Loading
Loading
+36 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

uniform shader texture;
uniform half thickness;

/**
 * Returns whether the current fragcoord lies on the outline, return range [0, 1].
 * 0 = no outline, 1 = outline
 * This assumes that the given texture has a transparent background and evaluates whether it's an
 * outline with finite differencing.
 */
vec4 main(float2 fragCoord) {
    float aN = texture.eval(fragCoord + vec2(0., thickness)).a;
    float aS = texture.eval(fragCoord + vec2(0., -thickness)).a;
    float dY = (aN - aS) * 0.5;
    dY = max(dY, 0.0);

    half outline = smoothstep(0.1, 1.8, dY * 5.0);

    // Return the results in the R channel.
    return vec4(outline, 0., 0., 0.);
}
+48 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

uniform shader foreground;
uniform shader background;
uniform shader outlineBuffer;
uniform float2 uvOffsetFgd;
uniform float2 uvScaleFgd;
uniform float2 uvOffsetBgd;
@@ -29,10 +30,53 @@ uniform half intensity;
#include "shaders/utils.agsl"
#include "shaders/rain_shower.agsl"
#include "shaders/rain_constants.agsl"
#include "shaders/rain_splash.agsl"

// Controls how visible the rain drops are.
const float rainVisibility = 0.4;

/**
 * Draws splashes around the outline of the given image.
 */
vec3 drawSplashes(vec2 uv, vec2 fragCoord, vec3 color) {
    /** 1. Make a grid */
    vec2 gridSize = vec2(15., 15.);
    // Aspect ratio impacts visible cells.
    gridSize.y /= screenAspectRatio;
    // Scale the UV to allocate number of rows and columns.
    vec2 gridUv = uv * gridSize;
    // Invert y (otherwise it goes from 0=top to 1=bottom).
    gridUv.y = 1. - gridUv.y;
    // Generate column id, to offset columns vertically (so rain is not aligned).
    float columnOffset = idGenerator(floor(gridUv.x));
    gridUv.y += columnOffset * 2.6;

    // For each cell, we set the internal UV from -0.5 (left, bottom) to 0.5 (right, top).
    vec2 cellUv = fract(gridUv) - 0.5;
    vec2 pixUv = cellUv;
    pixUv.x *= -1;
    vec2 pixDistance = screenSize * pixUv / gridSize;
    float2 uvTextureFgd = (fragCoord + pixDistance) * uvScaleFgd + uvOffsetFgd;

    float outline = step(0.1, outlineBuffer.eval(uvTextureFgd).r);
    if (outline < 0.1) {
        // Simply return the given color when it's not considered as an outline.
        return color;
    }

    float t = time + 53.512 * columnOffset;
    float delay = 1.5173;
    float duration = 1.2;

    float circletime = floor(t / (duration + delay));
    // Get the cell ID based on the grid position. [0, 1].
    float cellId = idGenerator(floor(gridUv) + vec2(circletime, 23.14));
    // Normalized time [0, 1].
    float cellTime = max((mod(t + delay * cellId, duration + delay) - delay) / duration, 0.);

    return normalBlendWithWhiteSrc(color, drawSplash(cellUv, cellTime));
}

vec4 main(float2 fragCoord) {
    float2 uv = fragCoord / screenSize;

@@ -71,7 +115,10 @@ vec4 main(float2 fragCoord) {
    // 4. Blend with the foreground. Any effect from here will be in front of the subject.
    color.rgb = normalBlend(color.rgb, colorForeground.rgb, colorForeground.a);

    // 5. Generate a layer of rain in front of the subject (bigger and faster).
    // 5. Draw splashes
    color.rgb = drawSplashes(uv, fragCoord, color.rgb);

    // 6. Generate a layer of rain in front of the subject (bigger and faster).
    rain = generateRain(
          uv,
          screenAspectRatio,
+94 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

/**
 * Draw a single splash.
 * @param cellUv Range of [-0.5, 0.5]: left bottom (-0.5, -0.5), right top (0.5, 0.5).
 * @param cellTime Normalized cellTime range of [0, 1].
 */
float drawSplash(vec2 cellUv, float cellTime) {
    /** 0. Adjust UV and time. */
    cellUv = cellUv * 0.5;
    cellUv += 0.1;
    float t = 0.408 + cellTime * 4.;

    /** 1. Start of drawing a splash */

    // Draw one side of a splash (a crescent) using two circles, by overlapping them with a slight
    // offset. Then we reflect the splash to get the full splash.

    // Reflect the splash.
    vec2 uvAbs = abs(cellUv);

    // Center of the splash (of the right splash)
    vec2 center = vec2(0.22, -0.01);
    float splashMaskCircle1 = sdfCircle(uvAbs - center, 0.164);
    float splashMaskCircle2 = sdfCircle(uvAbs - vec2(0.04, 0.) - center, 0.164);

    splashMaskCircle1 = smoothstep(0.052, 0.12, splashMaskCircle1);
    splashMaskCircle2 = smoothstep(0.1, 0.152, splashMaskCircle2);

    // Here, we have a full splash that covers the entire cell
    float splash = splashMaskCircle1 - splashMaskCircle2;

    // Mask the splash so that it doesn't cover the entire cell.
    float circleMask = sdfCircle(cellUv - vec2(0., 0.15), -0.004);
    float splashColor = splash * smoothstep(0.152, 0.1, circleMask);

    // Another mask for the splash to reveal it vertically.
    float maskThickHalf = 0.052;
    float maskFade = 0.05;
    vec2 maskMiddle = vec2(-0.1, -0.5);
    maskMiddle.y *= sin(mod(t, 4.4));

    float mask =
       smoothstep(maskMiddle.y - maskThickHalf - maskFade,
                  maskMiddle.y - maskThickHalf,
                  cellUv.y) -
       smoothstep(maskMiddle.y + maskThickHalf,
                  maskMiddle.y + maskThickHalf + maskFade,
                  cellUv.y);
    splashColor *= mask;
    /** End of drawing a splash */

    /** 2. Start of drawing a vertical line of the splash */

    // Draw the vertical line.
    float verticalLine = 0.035;
    float lineCenter = 0.192;
    float lineColor = 0.4 *
       (smoothstep(-verticalLine, 0., cellUv.x) - smoothstep(0., verticalLine, cellUv.x)) *
        smoothstep(0.008, -0.156, sdfCircle(cellUv - vec2(0., lineCenter), 0.164));

    // Mask the vertical line to reveal it vertically.
    float lineMaskThickHalf = 0.124;
    float lineMaskFade = 0.170;
    vec2 lineMaskMiddle = vec2(-0.01, 2.71);
    lineMaskMiddle.y *= sin(mod(t, 4.4) - 0.168);
    float lineMask =
       smoothstep(lineMaskMiddle.y - lineMaskThickHalf - lineMaskFade,
                  lineMaskMiddle.y - lineMaskThickHalf,
                  cellUv.y) -
       smoothstep(lineMaskMiddle.y + lineMaskThickHalf,
                  lineMaskMiddle.y + lineMaskThickHalf + lineMaskFade,
                  cellUv.y);
    float line = lineColor * lineMask;

    /** End of drawing the vertical line of the splash */

    /** 3. Composite the splash and the line. */
    return splashColor * (1. - line) + line;
}
+4 −0
Original line number Diff line number Diff line
@@ -35,6 +35,10 @@ mat2 rotationMat(float angleRad) {
  );
}

float sdfCircle(vec2 p, float r) {
    return length(p) - r;
}

vec2 rotateAroundPoint(vec2 point, vec2 centerPoint, float angleRad) {
    return (point - centerPoint) * rotationMat(angleRad) + centerPoint;
}
+38 −4
Original line number Diff line number Diff line
@@ -19,11 +19,14 @@ package com.google.android.wallpaper.weathereffects.graphics.rain
import android.graphics.BitmapShader
import android.graphics.Canvas
import android.graphics.Paint
import android.graphics.RenderEffect
import android.graphics.Shader
import android.util.SizeF
import com.google.android.wallpaper.weathereffects.graphics.FrameBuffer
import com.google.android.wallpaper.weathereffects.graphics.WeatherEffect
import com.google.android.wallpaper.weathereffects.graphics.utils.GraphicsUtils
import com.google.android.wallpaper.weathereffects.graphics.utils.ImageCrop
import java.util.concurrent.Executor
import kotlin.random.Random

/** Defines and generates the rain weather effect animation. */
@@ -31,10 +34,19 @@ class RainEffect(
    /** The config of the rain effect. */
    private val rainConfig: RainEffectConfig,
    /** The initial size of the surface where the effect will be shown. */
    surfaceSize: SizeF
    surfaceSize: SizeF,
    private val mainExecutor: Executor
) : WeatherEffect {

    private val rainPaint = Paint().also { it.shader = rainConfig.colorGradingShader }
    // Set blur effect to reduce the outline noise. No need to set blur effect every time we
    // re-generate the outline buffer.
    private val outlineBuffer =
        FrameBuffer(rainConfig.background.width, rainConfig.background.height).apply {
            setRenderEffect(RenderEffect.createBlurEffect(2f, 2f, Shader.TileMode.CLAMP))
        }
    private val outlineBufferPaint = Paint().also { it.shader = rainConfig.outlineShader }

    private var elapsedTime: Float = 0f

    init {
@@ -66,6 +78,7 @@ class RainEffect(

    override fun release() {
        rainConfig.lut?.recycle()
        outlineBuffer.close()
    }

    override fun setIntensity(intensity: Float) {
@@ -75,6 +88,11 @@ class RainEffect(
            "intensity",
            rainConfig.colorGradingIntensity * intensity
        )
        val thickness = 1f + intensity * 10f
        rainConfig.outlineShader.setFloatUniform("thickness", thickness)

        // Need to recreate the outline buffer as the uniform has changed.
        createOutlineBuffer()
    }

    private fun adjustCropping(surfaceSize: SizeF) {
@@ -131,10 +149,10 @@ class RainEffect(
    }

    private fun updateTextureUniforms() {
        rainConfig.rainShowerShader.setInputBuffer(
            "foreground",
        val foregroundBuffer =
            BitmapShader(rainConfig.foreground, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
        )
        rainConfig.rainShowerShader.setInputBuffer("foreground", foregroundBuffer)
        rainConfig.outlineShader.setInputBuffer("texture", foregroundBuffer)

        rainConfig.rainShowerShader.setInputBuffer(
            "background",
@@ -142,6 +160,22 @@ class RainEffect(
        )
    }

    private fun createOutlineBuffer() {
        val canvas = outlineBuffer.beginDrawing()
        canvas.drawPaint(outlineBufferPaint)
        outlineBuffer.endDrawing()

        outlineBuffer.tryObtainingImage(
            { buffer ->
                rainConfig.rainShowerShader.setInputBuffer(
                    "outlineBuffer",
                    BitmapShader(buffer, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
                )
            },
            mainExecutor
        )
    }

    private fun prepareColorGrading() {
        rainConfig.colorGradingShader.setInputShader("texture", rainConfig.glassRainShader)
        rainConfig.lut?.let {
Loading