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