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

Commit 9289a4d9 authored by Yein Jo's avatar Yein Jo Committed by Android (Google) Code Review
Browse files

Merge "Sync snow effect" into main

parents 910027d6 9cbc24e0
Loading
Loading
Loading
Loading
+54 −17
Original line number Diff line number Diff line
@@ -26,6 +26,9 @@ const mat2 rot45 = mat2(

uniform half intensity;

const float farthestSnowLayerWiggleSpeed = 5.8;
const float closestSnowLayerWiggleSpeed = 2.6;

/**
 * Generates snow flakes.
 *
@@ -33,7 +36,10 @@ uniform half intensity;
 * @param screenAspectRatio the aspect ratio of the fragment where we will display the effect.
 * @param time the elapsed time.
 * @param snowGridSize the size of the grid, where each cell contains a snow flake.
 * @param layerNumber the layer of snow that we want to draw. Higher is farther from camera.
 * @param layerIndex the index of the current layer of snow that we want to draw. (Higher index
 *                      indicates that it's farther away from camera).
 * @param minLayerIndex the index of the minimum layer.
 * @param maxLayerIndex the index of the maximum layers.
 *
 * @returns Snow with the snow info.
 */
@@ -43,13 +49,18 @@ Snow generateSnow(
    in float screenAspectRatio,
    in float time,
    in vec2 snowGridSize,
    in float layerNumber
    in float layerIndex,
    in float minLayerIndex,
    in float maxLayerIndex
) {
    // Normalize the layer index. 0 is closest, 1 is farthest.
    half normalizedLayerIndex = map(layerIndex, minLayerIndex, maxLayerIndex, 0, 1);

    /* Grid. */
    // Increase the last number to make each layer more separate from the previous one.
    float depth = 1. + layerNumber * 0.35;
    float speedAdj = 1. + layerNumber * 0.15;
    float layerR = idGenerator(layerNumber);
    float depth = 0.65 + layerIndex * 0.41;
    float speedAdj = 1. + layerIndex * 0.15;
    float layerR = idGenerator(layerIndex);
    snowGridSize *= depth;
    time += layerR * 58.3;
    // Number of rows and columns (each one is a cell, a drop).
@@ -67,7 +78,8 @@ Snow generateSnow(
    gridUv.y += verticalGridPos;
    // Generate column id, to offset columns vertically (so snow flakes are not aligned).
    float columnId = idGenerator(floor(gridUv.x));
    gridUv.y += columnId * 2.6;
    // Have time affect the position of each column as well.
    gridUv.y += columnId * 2.6 + time * 0.09 * (1 - columnId);

    /* Cell. */
    // Get the cell ID based on the grid position. Value from 0 to 1.
@@ -87,21 +99,43 @@ Snow generateSnow(
    }

    /* Cell-id-based variations. */
    // Adjust time based on columnId.
    // Adjusts scale of each snow flake (higher is smaller).
    float scaleVariation = 2.0 + 2.7 * cellId;
    float opacityVariation = (1. - 0.9 * cellId);
    // 0 = snow flake invisible, 1 = snow flake visible.
    float visibilityFactor = smoothstep(
        cellIntensity,
        max(cellIntensity - (0.02 + 0.18 * intensity), 0.0),
        1 - intensity);
    // Adjust the size of each snow flake (higher is smaller) based on cell ID.
    float decreaseFactor = 2.0 + map(cellId, 0., 1., -0.1, 2.8) + 5. * (1 - visibilityFactor);
    // Adjust the opacity of the particle based on the cell id and distance from the camera.
    float farLayerFadeOut = map(normalizedLayerIndex, 0.7, 1, 1, 0.4);
    float closeLayerFadeOut = map(normalizedLayerIndex, 0, 0.2, 0.6, 1);
    float opacityVariation =
        (1. - 0.9 * cellId) *
        visibilityFactor *
        closeLayerFadeOut *
        farLayerFadeOut;

    /* Cell snow flake. */

    // Horizontal movement: Wiggle.
    float wiggleSpeed = 3.0;
    // Horizontal movement: Wiggle (Adjust the wiggle speed based on the distance).
    float wiggleSpeed = map(
        normalizedLayerIndex,
        0.2,
        0.7,
        closestSnowLayerWiggleSpeed,
        farthestSnowLayerWiggleSpeed);
    // Adjust wiggle based on layer number (0 = closer to screen => we want less movement).
    float wiggleAmp = 0.4 + 0.4 * smoothstep(0.5, 2.5, layerNumber);
    float wiggleAmp = 0.6 + 0.4 * smoothstep(0.5, 2.5, layerIndex);
    // Define the start based on the cell id.
    float horizontalStartAmp = 0.5;
    // Add the wiggle (equation decided by testing in Grapher).
    float horizontalWiggle = wiggle(uv.y + cellId * 2.1, wiggleSpeed * speedAdj);
    float horizontalWiggle = wiggle(
        // Current uv position.
        uv.y
        // Adjustment so the shape is not skewed.
        - cellUv.y / snowGridSize.y
        // variation based on cell ID.
        + cellId * 2.1,
        wiggleSpeed * speedAdj);

    // Add the start and wiggle and make that when we are closer to the edge, we don't wiggle much
    // (so the drop doesn't go outside it's cell).
@@ -114,8 +148,11 @@ Snow generateSnow(
    vec2 snowFlakeShape = vec2(1., 1.2);
    vec2 snowFlakePos = vec2(snowFlakePosUncorrected / cellAspectRatio, cellUv.y);
    snowFlakePos -= vec2(0., uv.y - 0.5) * cellId;
    snowFlakePos *= snowFlakeShape * scaleVariation;
    vec2 snowFlakePosR = 1.016 * abs(rot45 * (snowFlakePos + (cellId * 2. - 1.) * vec2(0.050)));
    snowFlakePos *= snowFlakeShape * decreaseFactor;
    vec2 snowFlakeShapeVariation = vec2(0.055) * // max variation
        vec2((cellId * 2. - 1.), // random A based on cell ID
            (fract((cellId + 0.03521) * 34.21) * 2. - 1.)); // random B based on cell ID
    vec2 snowFlakePosR = 1.016 * abs(rot45 * (snowFlakePos + snowFlakeShapeVariation));
    snowFlakePos = abs(snowFlakePos);
    // Create the snowFlake mask.
    float flakeMask = smoothstep(
+71 −45
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
uniform shader foreground;
uniform shader background;
uniform shader accumulatedSnow;
uniform shader noise;
uniform float2 uvOffsetFgd;
uniform float2 uvScaleFgd;
uniform float2 uvOffsetBgd;
@@ -29,71 +30,96 @@ uniform float2 screenSize;
#include "shaders/utils.agsl"
#include "shaders/snow.agsl"

/* Constants that can be modified. */
// Snow tint.
const vec3 snowColor = vec3(0.9);
// Glass tint.
const vec3 glassTint = vec3(0.8); // gray
const vec4 snowColor = vec4(1., 1., 1., 0.95);
// Background tint
const vec4 bgdTint = vec4(0.8, 0.8, 0.8, 0.07);

// snow opacity (how visible it is).
const float snowOpacity = 1.4;

// how frosted the glass is.
const float frostedGlassIntensity = 0.07;
// Indices of the different snow layers.
const float farthestSnowLayerIndex = 9;
const float midSnowLayerIndex = 3;
const float closestSnowLayerIndex = 0;

vec4 main(float2 fragCoord) {
    float2 uv = fragCoord / screenSize;
    float2 uvAdjusted = vec2(uv.x, uv.y / screenAspectRatio);

    /**
     * The effect is consisted of 2 image textures (foreground and background) + 10 layers of
     * snow + 1 layer of snow accumulation. Below describes the rendering order (back to front):
     * 1. Background
     * 2. Background snow layers (from farthest layer to mid layer)
     * 3. Foreground
     * 4. Snow accumulation layer (on subject)
     * 5. Foreground snow layers (from mid layer to closest layer)
     */

    // Adjusts the UVs to have the expected rect of the image.
    float2 adjustedUvForeground = fragCoord * uvScaleFgd + uvOffsetFgd;
    vec4 colorForeground = foreground.eval(adjustedUvForeground);
    vec4 colorBackground = background.eval(fragCoord * uvScaleBgd + uvOffsetBgd);

    vec4 color = vec4(0., 0., 0., 1.);
    // 1. Draw background.
    vec4 color = colorBackground;

    // Add some slight tint to the frosted glass.
    // Add slight tint to the background.
    color.rgb = normalBlendNotPremultiplied(color.rgb, bgdTint.rgb, bgdTint.a);

    // Get color of the background texture.
    color.rgb = mix(colorBackground.rgb, glassTint, frostedGlassIntensity);
    for (half i = 9.; i > 2.; i--) {
        // Generate snow behind the subject.
        // Normalized layer index.
        half idx = (i - 2.) / (9. - 2.);
    // 2. Generate snow layers behind the subject.
    for (float i = farthestSnowLayerIndex; i > midSnowLayerIndex; i--) {
        Snow snow = generateSnow(
            uv,
            screenAspectRatio,
              time * mix(1.25, 5., idx),
              /* Grid size = */ vec2(mix(3.0, 6.0, idx), mix(1.0, 3.0, idx)),
              /* layer number = */ i);

        color.rgb = mix(color.rgb, snowColor, snowOpacity * snow.flakeMask);
            time,
            // TODO: adjust grid size based on aspect ratio.
            /* Grid size = */ vec2(7., 1.5),
            /* layer number = */ i,
            closestSnowLayerIndex,
            farthestSnowLayerIndex);

        color.rgb =
            normalBlendNotPremultiplied(color.rgb, snowColor.rgb, snowColor.a * snow.flakeMask);
    }

    // Add the foreground. Any effect from here will be in front of the subject.
    // 3. Add the foreground layer. Any effect from here will be in front of the subject.
    color.rgb = normalBlend(color.rgb, colorForeground.rgb, colorForeground.a);

    // Add accumulated snow.
    vec2 accSnow = accumulatedSnow.eval(adjustedUvForeground).rg;
    float snowLayer = smoothstep(0.2, 0.8, accSnow.r);
    float snowTexture = smoothstep(0.2, 0.7, accSnow.g);
    color.rgb = mix(color.rgb, vec3(0.95), 0.98 * snowLayer * (0.05 + 0.95 * snowTexture));
    // 4. Add accumulated snow layer.
    // Load noise texture to give "fluffy-ness" to the snow. Displace the sampling of the noise.
    vec3 cloudsNoise = noise.eval(uvAdjusted * 7000 + vec2(fragCoord.y, -fragCoord.x)).rgb;
    // Add dither to give texture to the snow and ruffle the edges.
    float dither = abs(triangleNoise(fragCoord * 0.01));

    for (half i = 2.; i >= 0.; i--) {
        // Generate snow behind the subject.
    // Get the accumulated snow buffer. r contains its mask, g contains some random noise.
    vec2 accSnow = accumulatedSnow.eval(adjustedUvForeground).rg;
    // Sharpen the mask of the accumulated snow, but not in excess.
    float accSnowMask = smoothstep(0.1, 0.9, /* mask= */ accSnow.r);
    // Makes the edges of the snow layer accumulation rougher.
    accSnowMask = map(accSnowMask, 1. - cloudsNoise.b - 0.3 * dither, 1., 0., 1.);
    // Load snow texture and dither. Make it have gray-ish values.
    float accSnowTexture = smoothstep(0.2, 0.7, /* noise= */ accSnow.g) * 0.7;
    accSnowTexture = map(accSnowTexture, dither - 1, 1, 0, 1);
    // Adjust snow texture coverage/shape.
    accSnowTexture = map(accSnowTexture, 0.67, 0.8, 0, 1);
    accSnowMask = map(accSnowMask, 0., 1., 0., 1.- 0.6 * accSnowTexture - 0.35 * dither);

    color.rgb = normalBlendNotPremultiplied(color.rgb, snowColor.rgb, snowColor.a * accSnowMask);

    // 5. Generate snow in front of the subject.
    for (float i = midSnowLayerIndex; i >= closestSnowLayerIndex; i--) {
        Snow snow = generateSnow(
            uv,
            screenAspectRatio,
              time * 1.25,
              /* Grid size = */ vec2(i + 1., 1.4),
              /* layer number = */ i);

        color.rgb = mix(color.rgb, snowColor, snowOpacity * snow.flakeMask);
            time,
            // TODO: adjust grid size based on aspect ratio
            /* Grid size = */ vec2(7., 1.5),
            /* layer number = */ i,
            closestSnowLayerIndex,
            farthestSnowLayerIndex);

        color.rgb =
            normalBlendNotPremultiplied(color.rgb, snowColor.rgb, snowColor.a * snow.flakeMask);
    }

    /* Debug snow */
    // resets color.
    // color.rgb *= 0.;
    // color.rgb += snow.flakeMask;
    // if (snow.cellUv.x > 0.49 || snow.cellUv.y > 0.49) color.r = 1.0;

    return color;
}
+14 −0
Original line number Diff line number Diff line
@@ -94,6 +94,20 @@ vec3 normalBlendWithWhiteSrc(vec3 b, float o) {
    return b * (1. - o) + o;
}

/*
 * This is the normal blend mode in which the foreground is painted on top of the background based
 * on the foreground opacity.
 *
 * @param b the background color.
 * @param f the foreground color.
 * @param o the mask or the foreground alpha.
 *
 * Note: this blending function expects the foreground to NOT have premultiplied alpha.
 */
vec3 normalBlendNotPremultiplied(vec3 b, vec3 f, float o) {
    return mix(b, f, o);
}

/** Math Utils */
// function created on Grapher (equation decided by testing in Grapher).
float wiggle(float time, float wiggleSpeed) {
+5 −0
Original line number Diff line number Diff line
@@ -153,6 +153,11 @@ class SnowEffect(
            "background",
            BitmapShader(snowConfig.background, Shader.TileMode.MIRROR, Shader.TileMode.MIRROR)
        )

        snowConfig.shader.setInputBuffer(
            "noise",
            BitmapShader(snowConfig.noiseTexture, Shader.TileMode.REPEAT, Shader.TileMode.REPEAT)
        )
    }

    private fun prepareColorGrading() {
+8 −0
Original line number Diff line number Diff line
@@ -30,6 +30,11 @@ data class SnowEffectConfig(
    val accumulatedSnowShader: RuntimeShader,
    /** The color grading shader. */
    val colorGradingShader: RuntimeShader,
    /**
     * The noise texture, which will be used to add fluffiness to the snow flakes. The texture is
     * expected to be tileable, and at least 16-bit per channel for render quality.
     */
    val noiseTexture: Bitmap,
    /** The main lut (color grading) for the effect. */
    val lut: Bitmap?,
    /** A bitmap containing the foreground of the image. */
@@ -64,6 +69,8 @@ data class SnowEffectConfig(
        accumulatedSnowShader =
            GraphicsUtils.loadShader(context.assets, ACCUMULATED_SNOW_SHADER_PATH),
        colorGradingShader = GraphicsUtils.loadShader(context.assets, COLOR_GRADING_SHADER_PATH),
        noiseTexture = GraphicsUtils.loadTexture(context.assets, NOISE_TEXTURE_PATH)
                ?: throw RuntimeException("Noise texture is missing."),
        lut = GraphicsUtils.loadTexture(context.assets, LOOKUP_TABLE_TEXTURE_PATH),
        foreground,
        background,
@@ -77,6 +84,7 @@ data class SnowEffectConfig(
        private const val SHADER_PATH = "shaders/snow_effect.agsl"
        private const val ACCUMULATED_SNOW_SHADER_PATH = "shaders/snow_accumulation.agsl"
        private const val COLOR_GRADING_SHADER_PATH = "shaders/color_grading_lut.agsl"
        private const val NOISE_TEXTURE_PATH = "textures/clouds.png"
        private const val LOOKUP_TABLE_TEXTURE_PATH = "textures/lut_rain_and_fog.png"
        private const val BLUR_RADIUS = 20f
        private const val DEFAULT_INTENSITY = 1f