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
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
|
#version 330 core
layout (points) in;
layout (triangle_strip, max_vertices = 36) out;
uniform mat4 view, projection;
uniform vec3 worldSpace_camPos;
uniform float u_time;
uniform sampler2D wind_texture;
uniform sampler2D meadow_texture;
uniform vec3 playerPos;
uniform vec4 plane;
// texture atlasing
//uniform float numRows;
//uniform vec2 atlas_offset;
in VS_OUT {
float visibility;
} gs_in[];
out GS_OUT {
vec2 texCoord;
float visibility;
} gs_out;
// grass shader referenced from: https://vulpinii.github.io/tutorials/grass-modelisation/en/
/////////
float PI = 3.141592653589793;
mat4 windModel = mat4(1.f);
mat4 trampleModel = mat4(1.f);
int numRows = 1;
vec2 atlas_offset = vec2(0.f);
vec2 getOffset(int index, int numRows){
int column = int(mod(index, numRows));
float xoffset = float(column)/float(numRows);
int row = index/numRows;
float yOffset = float(row)/float(numRows);
return vec2(xoffset, yOffset);
}
mat4 rotX(float a){
mat4 rx = mat4(1.f);
rx[1] = vec4(0.f, cos(a), -sin(a), 0.f);
rx[2] = vec4(0.f, sin(a), cos(a), 0.f);
return rx;
}
mat4 rotY(float a){
mat4 ry = mat4(1.f);
ry[0] = vec4(cos(a), 0.f, sin(a), 0.f);
ry[2] = vec4(-sin(a), 0.f, cos(a), 0.f);
return ry;
}
mat4 rotZ(float a){
mat4 rz = mat4(1.f);
rz[0] = vec4(cos(a), sin(a), 0.f, 0.f);
rz[1] = vec4(-sin(a), cos(a), 0.f, 0.f);
return rz;
}
float randomize(vec2 st){
return fract(sin(dot(st.xy,vec2(12.9898,78.233)))*43758.5453123);
}
void makeQuad(vec4 grass_pos, mat4 crossmodel){
vec4 vertexPos[4];
vertexPos[0] = vec4(-.9f, 0.f, 0.f, 0.f); // bottom left
vertexPos[1] = vec4(0.9f, 0.f, 0.f, 0.f); // bottom right
vertexPos[2] = vec4(-0.9f, 1.f, 0.f, 0.f); // upper left
vertexPos[3] = vec4(0.9f, 1.f, 0.f, 0.f); // upper right
vec2 texCoords[4];
texCoords[0] = vec2(0.f, 0.f);
texCoords[1] = vec2(1.f, 0.f);
texCoords[2] = vec2(0.f, 1.f);
texCoords[3] = vec2(1.f, 1.f);
mat4 randomY = rotY(randomize(grass_pos.zx)*PI);
// will apply wind only to top two corners of quad
mat4 defaultWind = mat4(1.f);
mat4 defaultTrample = mat4(1.f);
gl_ClipDistance[0]=dot(vec4(grass_pos), plane);
vec2 clampedOffset = atlas_offset;
for (int i=0; i<4; i++){
if (i>=2) {
defaultWind = windModel;
defaultTrample = trampleModel;
}
gl_Position = projection*view*(grass_pos + (defaultTrample*defaultWind*randomY*crossmodel*vertexPos[i]));
gs_out.texCoord = (texCoords[i]/numRows) + clampedOffset;
EmitVertex();
}
EndPrimitive();
}
struct TrampleInfo {
vec3 trampleOffset;
float windMultiplier;
};
// trample referenced from NedMakesGames: https://www.youtube.com/watch?v=AmO7k-Lr0XM
TrampleInfo calculateTrample(vec3 entityWorldPos, float maxDistance, float falloff, float pushAwayStrength, float pushDownStrength){
vec3 offset = vec3(0.f);
float windMultiplier = 1.f;
vec3 distanceVec = gl_in[0].gl_Position.xyz-playerPos;
float distance = length(distanceVec);
// convert to trample strength
float strength = 1 - pow(clamp(distance / maxDistance, 0.f, 1.f), falloff);
// apply pushAway offest in xz plane
vec3 xzDistance = vec3(distanceVec.x, 0.f, distanceVec.z);
vec3 pushAwayOffset = normalize(xzDistance) * pushAwayStrength * strength;
// pushDown offset always points downwards
vec3 squishOffset = vec3(0.f, -1.f, 0.f) * pushDownStrength * strength;
offset += pushAwayOffset + squishOffset;
// supress wind when this grass is being trampled
windMultiplier = min(windMultiplier, 1-strength);
TrampleInfo info;
info.trampleOffset = offset;
info.windMultiplier = windMultiplier;
return info;
}
void makeGrass(int numQuads){
mat4 model0, model45, modelm45;
model0 = mat4(1.f);
model45 = rotY(radians(45));
modelm45 = rotY(-radians(45));
// wind calculation
vec2 windDir = vec2(1.f);
float windStrength = .05f;
// uv coordinates of wind
vec2 uv = gl_in[0].gl_Position.xz/10.f + windDir * u_time;
uv.x = mod(uv.x, 1.0);
uv.y = mod(uv.y, 1.0);
vec4 windSpot = texture(wind_texture, uv);
// get index in meadow texture atlas depending on where uv is
vec2 meadow_uv = gl_in[0].gl_Position.xz * .03f;
meadow_uv.x = mod(meadow_uv.x, 1.0);
meadow_uv.y = mod(meadow_uv.y, 1.0);
vec4 meadowSpot = texture(meadow_texture, meadow_uv);
float meadowSpotAccumulate = meadowSpot.r + meadowSpot.g + meadowSpot.b;
// convert meadowSpot to range 0-maxindex
numRows = 4;
int index = int(((meadowSpotAccumulate) / (3.f) ) * (16.f));
if (index < 0) index = 0;
if (index > 15) index = 15;
atlas_offset = getOffset(index, numRows);
// matrix that rotates top of grass away from player
TrampleInfo info = calculateTrample(playerPos, 2.f, 5.f, .52f, .51f);
trampleModel = (rotX(info.trampleOffset.x*PI*1.f) * rotZ(info.trampleOffset.z*PI*1.f));
// matrix that tilts quad in x and z dir, accordiing to wind dir and force
windModel = (rotX(windSpot.x*PI*.75f*info.windMultiplier - PI*.25f) * rotZ(windSpot.z*PI*.75f*info.windMultiplier - PI*.25f));
// draw number of quads based on level of detail
switch(numQuads){
case 1: {
makeQuad(gl_in[0].gl_Position, model0);
break;
}
case 2: {
makeQuad(gl_in[0].gl_Position, model45);
makeQuad(gl_in[0].gl_Position, modelm45);
break;
}
default: {
makeQuad(gl_in[0].gl_Position, model0);
makeQuad(gl_in[0].gl_Position, model45);
makeQuad(gl_in[0].gl_Position, modelm45);
break;
}
}
}
const float LOD1 = 5.f;
const float LOD2 = 10.f;
const float LOD3 = 30.f;
void main()
{
gs_out.visibility = gs_in[0].visibility;
vec3 dist_to_camera = gl_in[0].gl_Position.xyz - worldSpace_camPos;
float dist_length = length(dist_to_camera);
// add transition for smooth levels
float t = 6.f;
if (dist_length > LOD2) t*=1.5f;
dist_length += (randomize(gl_in[0].gl_Position.xz)*t - t/2.f);
// change depending on distance
int detailLevel = 3;
if (dist_length > LOD1) detailLevel = 2;
if (dist_length > LOD2) detailLevel = 1;
if (dist_length > LOD3) detailLevel = 0;
// make grass with transition levels
if (detailLevel != 1
|| (detailLevel == 1 && (int(gl_in[0].gl_Position.x * 10) % 1) == 0
|| (int(gl_in[0].gl_Position.z * 10) % 1) == 0)
|| (detailLevel == 2 && (int(gl_in[0].gl_Position.x * 5) % 1) == 0
|| (int(gl_in[0].gl_Position.z * 5) % 1) == 0)
) {
makeGrass(detailLevel);
}
}
|