1/*
2 * Copyright (C) 2023 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17struct Snow {
18    highp float flakeMask;
19    highp vec2 cellUv;
20};
21
22const mat2 rot45 = mat2(
23    0.7071067812, 0.7071067812, // First column.
24    -0.7071067812, 0.7071067812 // second column.
25);
26
27uniform half intensity;
28
29const float farthestSnowLayerWiggleSpeed = 5.8;
30const float closestSnowLayerWiggleSpeed = 2.6;
31
32/**
33 * Generates snow flakes.
34 *
35 * @param uv the UV of the fragment where we will display the snow effect.
36 * @param screenAspectRatio the aspect ratio of the fragment where we will display the effect.
37 * @param time the elapsed time.
38 * @param snowGridSize the size of the grid, where each cell contains a snow flake.
39 * @param layerIndex the index of the current layer of snow that we want to draw. (Higher index
40 *                      indicates that it's farther away from camera).
41 * @param minLayerIndex the index of the minimum layer.
42 * @param maxLayerIndex the index of the maximum layers.
43 *
44 * @returns Snow with the snow info.
45 */
46Snow generateSnow(
47    // UVs of the target fragment (normalized).
48    in vec2 uv,
49    in float screenAspectRatio,
50    in float time,
51    in vec2 snowGridSize,
52    in float layerIndex,
53    in float minLayerIndex,
54    in float maxLayerIndex
55) {
56    // Normalize the layer index. 0 is closest, 1 is farthest.
57    half normalizedLayerIndex = map(layerIndex, minLayerIndex, maxLayerIndex, 0, 1);
58
59    /* Grid. */
60    // Increase the last number to make each layer more separate from the previous one.
61    float depth = 0.65 + layerIndex * 0.41;
62    float speedAdj = 1. + layerIndex * 0.15;
63    float layerR = idGenerator(layerIndex);
64    snowGridSize *= depth;
65    time += layerR * 58.3;
66    // Number of rows and columns (each one is a cell, a drop).
67    float cellAspectRatio = snowGridSize.x / snowGridSize.y;
68    // Aspect ratio impacts visible cells.
69    snowGridSize.y /= screenAspectRatio;
70    // Skew uv.x so it goes to left or right
71    uv.x += uv.y * (0.8 * layerR - 0.4);
72    // scale the UV to allocate number of rows and columns.
73    vec2 gridUv = uv * snowGridSize;
74    // Invert y (otherwise it goes from 0=top to 1=bottom).
75    gridUv.y = 1. - gridUv.y;
76    float verticalGridPos = 0.4 * time / speedAdj;
77    // Move grid vertically down.
78    gridUv.y += verticalGridPos;
79    // Generate column id, to offset columns vertically (so snow flakes are not aligned).
80    float columnId = idGenerator(floor(gridUv.x));
81    // Have time affect the position of each column as well.
82    gridUv.y += columnId * 2.6 + time * 0.09 * (1 - columnId);
83
84    /* Cell. */
85    // Get the cell ID based on the grid position. Value from 0 to 1.
86    float cellId = idGenerator(floor(gridUv));
87    // For each cell, we set the internal UV from -0.5 (left, bottom) to 0.5 (right, top).
88    vec2 cellUv = fract(gridUv) - 0.5;
89    cellUv.y *= -1.;
90
91   /*
92    * Disable snow flakes with some probabilty. This is done by 1) assigning a random intensity
93    * value to the cell 2) then compare it with the given intensity.
94    */
95    half cellIntensity = idGenerator(floor(vec2(cellId * 856.16, 272.2)));
96    if (cellIntensity < 1. - intensity) {
97        // Remove snow flakes by seeting flake mask to 0.
98        return Snow(/* flakeMask= */ 0, cellUv);
99    }
100
101    /* Cell-id-based variations. */
102    // 0 = snow flake invisible, 1 = snow flake visible.
103    float visibilityFactor = smoothstep(
104        cellIntensity,
105        max(cellIntensity - (0.02 + 0.18 * intensity), 0.0),
106        1 - intensity);
107    // Adjust the size of each snow flake (higher is smaller) based on cell ID.
108    float decreaseFactor = 2.0 + map(cellId, 0., 1., -0.1, 2.8) + 5. * (1 - visibilityFactor);
109    // Adjust the opacity of the particle based on the cell id and distance from the camera.
110    float farLayerFadeOut = map(normalizedLayerIndex, 0.7, 1, 1, 0.4);
111    float closeLayerFadeOut = map(normalizedLayerIndex, 0, 0.2, 0.6, 1);
112    float opacityVariation =
113        (1. - 0.9 * cellId) *
114        visibilityFactor *
115        closeLayerFadeOut *
116        farLayerFadeOut;
117
118    /* Cell snow flake. */
119    // Horizontal movement: Wiggle (Adjust the wiggle speed based on the distance).
120    float wiggleSpeed = map(
121        normalizedLayerIndex,
122        0.2,
123        0.7,
124        closestSnowLayerWiggleSpeed,
125        farthestSnowLayerWiggleSpeed);
126    // Adjust wiggle based on layer number (0 = closer to screen => we want less movement).
127    float wiggleAmp = 0.6 + 0.4 * smoothstep(0.5, 2.5, layerIndex);
128    // Define the start based on the cell id.
129    float horizontalStartAmp = 0.5;
130    // Add the wiggle (equation decided by testing in Grapher).
131    float horizontalWiggle = wiggle(
132        // Current uv position.
133        uv.y
134        // Adjustment so the shape is not skewed.
135        - cellUv.y / snowGridSize.y
136        // variation based on cell ID.
137        + cellId * 2.1,
138        wiggleSpeed * speedAdj);
139
140    // Add the start and wiggle and make that when we are closer to the edge, we don't wiggle much
141    // (so the drop doesn't go outside it's cell).
142    horizontalWiggle = horizontalStartAmp * wiggleAmp * horizontalWiggle;
143
144    // Calculate main cell drop.
145    float snowFlakePosUncorrected = (cellUv.x - horizontalWiggle);
146
147    // Calculate snow flake.
148    vec2 snowFlakeShape = vec2(1., 1.2);
149    vec2 snowFlakePos = vec2(snowFlakePosUncorrected / cellAspectRatio, cellUv.y);
150    snowFlakePos -= vec2(0., uv.y - 0.5) * cellId;
151    snowFlakePos *= snowFlakeShape * decreaseFactor;
152    vec2 snowFlakeShapeVariation = vec2(0.055) * // max variation
153        vec2((cellId * 2. - 1.), // random A based on cell ID
154            (fract((cellId + 0.03521) * 34.21) * 2. - 1.)); // random B based on cell ID
155    vec2 snowFlakePosR = 1.016 * abs(rot45 * (snowFlakePos + snowFlakeShapeVariation));
156    snowFlakePos = abs(snowFlakePos);
157    // Create the snowFlake mask.
158    float flakeMask = smoothstep(
159        0.3,
160        0.200 - 0.3 * opacityVariation,
161        snowFlakePos.x + snowFlakePos.y + snowFlakePosR.x + snowFlakePosR.y
162    ) * opacityVariation;
163
164    return Snow(flakeMask, cellUv);
165}
166