-
Notifications
You must be signed in to change notification settings - Fork 0
/
compute-fragment-shader.glsl
170 lines (132 loc) · 5.49 KB
/
compute-fragment-shader.glsl
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
uniform sampler2D sampleTexture;
uniform float cellSize;
varying vec2 uvCoords;
const vec4 UNITY = vec4(1.0);
const float MIN_WATER_LEVEL = 0.01; // minimum level of water before remaining water evaporates and is lost
const float MAX_WATER_LEVEL = 64.0; // maximum water that a single cell can hold
const float MIN_BLUE = 0.4; // this is the level of blue equivalent to the max level of water (more dense means darker)
const float MAX_BLUE = 1.0; // lightest blue, equivalent to the min level of water (less dense)
// percentage of water that is allowed to flow outwards in a single step
const float WATER_CURRENT_COEFFICIENT = 0.85;
bool isWater(vec4 point) {
return point.b >= MIN_BLUE && point.b <= MAX_BLUE && point.r <= 0.0 && point.g <= 0.0;
}
bool isEmpty(vec4 point) {
return point.r + point.g + point.b <= 0.0;
}
bool isBlock(vec4 point) {
return point.r > 0.5 && point.g > 0.5 && point.b > 0.5;
}
float zeroIfOutOfBounds(float value, float min, float max) {
// check if below lower bound
value = step(min, value) * value;
// check if above upper bound
value = (1.0 - step(max, value)) * value;
return value;
}
vec2 diff(vec2 ref, float du, float dv) {
return vec2(ref.x + du * cellSize, ref.y + dv * cellSize);
}
float blueToWater(float blue) {
return (blue - MIN_BLUE) * (MIN_WATER_LEVEL - MAX_WATER_LEVEL) / (MAX_BLUE - MIN_BLUE) + MAX_WATER_LEVEL;
}
float waterToBlue(float water) {
return (water - MAX_WATER_LEVEL) * (MAX_BLUE - MIN_BLUE) / (MIN_WATER_LEVEL - MAX_WATER_LEVEL) + MIN_BLUE;
}
vec4 getInputPixel(vec2 coord) {
return texture2D(sampleTexture, coord.xy);
}
float getWaterLevel(vec2 coord) {
vec4 color = getInputPixel(coord);
if (isWater(color)) {
return blueToWater(color.b);
} else if (isBlock(color)) {
// FixMe this should be working to make water not disappear when in contact with block
return -100000.0;
}
return 0.0;
}
vec4 calculateOutwardsFlowAtPosition(vec2 pos) {
float center = getWaterLevel(pos);
float top = getWaterLevel(diff(pos, +0., +1.));
float right = getWaterLevel(diff(pos, +1., +0.));
float down = getWaterLevel(diff(pos, +0., -1.));
float left = getWaterLevel(diff(pos, -1., +0.));
// FixMe this makes water evaporate very quickly because at each iteration more water is removed
// I guess we need a rule for transferring all water if below a certain threshold,
// so it doesn't fragment and end up evaporating
float totalMaxCurrent = WATER_CURRENT_COEFFICIENT * center;
float potentialTop = max(0.0, totalMaxCurrent - top);
float potentialLeft = max(0.0, totalMaxCurrent - left);
float potentialRight = max(0.0, totalMaxCurrent - right);
float potentialDown = max(0.0, totalMaxCurrent - down);
const float SIDEWAYS_FACTOR = 10.0;
const float DOWN_FACTOR = 200.0;
vec4 potential = vec4(
potentialTop,
potentialRight * SIDEWAYS_FACTOR,
potentialDown * DOWN_FACTOR,
potentialLeft * SIDEWAYS_FACTOR);
float totalPotential = dot(potential, UNITY); // trick to sum all vec4 params
if (totalPotential <= 0.0) {
return vec4(0.0);
}
vec4 conductance = potential / totalPotential;
vec4 waterCurrent = conductance * totalMaxCurrent;
return waterCurrent;
}
float calculateOutwardsFlow() {
vec4 flow = calculateOutwardsFlowAtPosition(uvCoords.xy);
// sums all currents out and considers it negative flow
return -1.0 * dot(flow, UNITY);
}
float calculateDiffFromTop() {
vec4 flow = calculateOutwardsFlowAtPosition(diff(uvCoords.xy, 0.0, +1.0));
return flow.z; // return flow going down
}
float calculateDiffFromDown() {
vec4 flow = calculateOutwardsFlowAtPosition(diff(uvCoords.xy, 0.0, -1.0));
return flow.x; // return flow going up
}
float calculateDiffFromRight() {
vec4 flow = calculateOutwardsFlowAtPosition(diff(uvCoords.xy, +1.0, 0.0));
return flow.w; // return flow going left
}
float calculateDiffFromLeft() {
vec4 flow = calculateOutwardsFlowAtPosition(diff(uvCoords.xy, -1.0, 0.0));
return flow.y; // return flow going right
}
void main() {
vec4 previousColor = getInputPixel(uvCoords.xy);
if (isBlock(previousColor)) {
gl_FragColor = previousColor;
return;
}
// only water and empty space will be processed below here
float initialWaterLevel = isWater(previousColor) ?
max(MIN_WATER_LEVEL, blueToWater(previousColor.b)) : // had to use max() owing to apparent precision problems
0.0;
// outgoing water (negative or zero)
float diffMine = calculateOutwardsFlow();
// incoming water (positive or zero)
float diffFromTop = calculateDiffFromTop();
float diffFromRight = calculateDiffFromRight();
float diffFromDown = calculateDiffFromDown();
float diffFromLeft = calculateDiffFromLeft();
float waterLevel = initialWaterLevel + diffMine + diffFromTop + diffFromRight + diffFromDown + diffFromLeft;
// "evaporate" water if below minimum acceptable level
waterLevel = step(MIN_WATER_LEVEL, waterLevel) * waterLevel;
if (waterLevel <= 0.0) {
// became (or stayed) empty space
gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
return;
}
// clamp if above max water level
waterLevel = min(waterLevel, MAX_WATER_LEVEL);
if (waterLevel < MIN_WATER_LEVEL) {
gl_FragColor = vec4(0.0, 0.0, 0.0, 1.0);
} else {
float blueLevel = waterToBlue(waterLevel);
gl_FragColor = vec4(0.0, 0.0, blueLevel, 1.0);
}
}