1 | |
2 | //////////////////////////////////////////////////////////// |
3 | // Headers |
4 | //////////////////////////////////////////////////////////// |
5 | #include "Effect.hpp" |
6 | #include <vector> |
7 | #include <cmath> |
8 | |
9 | |
10 | const sf::Font* Effect::s_font = NULL; |
11 | |
12 | //////////////////////////////////////////////////////////// |
13 | // "Pixelate" fragment shader |
14 | //////////////////////////////////////////////////////////// |
15 | class Pixelate : public Effect |
16 | { |
17 | public: |
18 | |
19 | Pixelate() : |
20 | Effect("pixelate" ) |
21 | { |
22 | } |
23 | |
24 | bool onLoad() |
25 | { |
26 | // Load the texture and initialize the sprite |
27 | if (!m_texture.loadFromFile("resources/background.jpg" )) |
28 | return false; |
29 | m_sprite.setTexture(m_texture); |
30 | |
31 | // Load the shader |
32 | if (!m_shader.loadFromFile("resources/pixelate.frag" , sf::Shader::Fragment)) |
33 | return false; |
34 | m_shader.setUniform("texture" , sf::Shader::CurrentTexture); |
35 | |
36 | return true; |
37 | } |
38 | |
39 | void onUpdate(float, float x, float y) |
40 | { |
41 | m_shader.setUniform("pixel_threshold" , (x + y) / 30); |
42 | } |
43 | |
44 | void onDraw(sf::RenderTarget& target, sf::RenderStates states) const |
45 | { |
46 | states.shader = &m_shader; |
47 | target.draw(m_sprite, states); |
48 | } |
49 | |
50 | private: |
51 | |
52 | sf::Texture m_texture; |
53 | sf::Sprite m_sprite; |
54 | sf::Shader m_shader; |
55 | }; |
56 | |
57 | |
58 | //////////////////////////////////////////////////////////// |
59 | // "Wave" vertex shader + "blur" fragment shader |
60 | //////////////////////////////////////////////////////////// |
61 | class WaveBlur : public Effect |
62 | { |
63 | public: |
64 | |
65 | WaveBlur() : |
66 | Effect("wave + blur" ) |
67 | { |
68 | } |
69 | |
70 | bool onLoad() |
71 | { |
72 | // Create the text |
73 | m_text.setString("Praesent suscipit augue in velit pulvinar hendrerit varius purus aliquam.\n" |
74 | "Mauris mi odio, bibendum quis fringilla a, laoreet vel orci. Proin vitae vulputate tortor.\n" |
75 | "Praesent cursus ultrices justo, ut feugiat ante vehicula quis.\n" |
76 | "Donec fringilla scelerisque mauris et viverra.\n" |
77 | "Maecenas adipiscing ornare scelerisque. Nullam at libero elit.\n" |
78 | "Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.\n" |
79 | "Nullam leo urna, tincidunt id semper eget, ultricies sed mi.\n" |
80 | "Morbi mauris massa, commodo id dignissim vel, lobortis et elit.\n" |
81 | "Fusce vel libero sed neque scelerisque venenatis.\n" |
82 | "Integer mattis tincidunt quam vitae iaculis.\n" |
83 | "Vivamus fringilla sem non velit venenatis fermentum.\n" |
84 | "Vivamus varius tincidunt nisi id vehicula.\n" |
85 | "Integer ullamcorper, enim vitae euismod rutrum, massa nisl semper ipsum,\n" |
86 | "vestibulum sodales sem ante in massa.\n" |
87 | "Vestibulum in augue non felis convallis viverra.\n" |
88 | "Mauris ultricies dolor sed massa convallis sed aliquet augue fringilla.\n" |
89 | "Duis erat eros, porta in accumsan in, blandit quis sem.\n" |
90 | "In hac habitasse platea dictumst. Etiam fringilla est id odio dapibus sit amet semper dui laoreet.\n" ); |
91 | m_text.setFont(getFont()); |
92 | m_text.setCharacterSize(22); |
93 | m_text.setPosition(30, 20); |
94 | |
95 | // Load the shader |
96 | if (!m_shader.loadFromFile("resources/wave.vert" , "resources/blur.frag" )) |
97 | return false; |
98 | |
99 | return true; |
100 | } |
101 | |
102 | void onUpdate(float time, float x, float y) |
103 | { |
104 | m_shader.setUniform("wave_phase" , time); |
105 | m_shader.setUniform("wave_amplitude" , sf::Vector2f(x * 40, y * 40)); |
106 | m_shader.setUniform("blur_radius" , (x + y) * 0.008f); |
107 | } |
108 | |
109 | void onDraw(sf::RenderTarget& target, sf::RenderStates states) const |
110 | { |
111 | states.shader = &m_shader; |
112 | target.draw(m_text, states); |
113 | } |
114 | |
115 | private: |
116 | |
117 | sf::Text m_text; |
118 | sf::Shader m_shader; |
119 | }; |
120 | |
121 | |
122 | //////////////////////////////////////////////////////////// |
123 | // "Storm" vertex shader + "blink" fragment shader |
124 | //////////////////////////////////////////////////////////// |
125 | class StormBlink : public Effect |
126 | { |
127 | public: |
128 | |
129 | StormBlink() : |
130 | Effect("storm + blink" ) |
131 | { |
132 | } |
133 | |
134 | bool onLoad() |
135 | { |
136 | // Create the points |
137 | m_points.setPrimitiveType(sf::Points); |
138 | for (int i = 0; i < 40000; ++i) |
139 | { |
140 | float x = static_cast<float>(std::rand() % 800); |
141 | float y = static_cast<float>(std::rand() % 600); |
142 | sf::Uint8 r = std::rand() % 255; |
143 | sf::Uint8 g = std::rand() % 255; |
144 | sf::Uint8 b = std::rand() % 255; |
145 | m_points.append(sf::Vertex(sf::Vector2f(x, y), sf::Color(r, g, b))); |
146 | } |
147 | |
148 | // Load the shader |
149 | if (!m_shader.loadFromFile("resources/storm.vert" , "resources/blink.frag" )) |
150 | return false; |
151 | |
152 | return true; |
153 | } |
154 | |
155 | void onUpdate(float time, float x, float y) |
156 | { |
157 | float radius = 200 + std::cos(time) * 150; |
158 | m_shader.setUniform("storm_position" , sf::Vector2f(x * 800, y * 600)); |
159 | m_shader.setUniform("storm_inner_radius" , radius / 3); |
160 | m_shader.setUniform("storm_total_radius" , radius); |
161 | m_shader.setUniform("blink_alpha" , 0.5f + std::cos(time * 3) * 0.25f); |
162 | } |
163 | |
164 | void onDraw(sf::RenderTarget& target, sf::RenderStates states) const |
165 | { |
166 | states.shader = &m_shader; |
167 | target.draw(m_points, states); |
168 | } |
169 | |
170 | private: |
171 | |
172 | sf::VertexArray m_points; |
173 | sf::Shader m_shader; |
174 | }; |
175 | |
176 | |
177 | //////////////////////////////////////////////////////////// |
178 | // "Edge" post-effect fragment shader |
179 | //////////////////////////////////////////////////////////// |
180 | class Edge : public Effect |
181 | { |
182 | public: |
183 | |
184 | Edge() : |
185 | Effect("edge post-effect" ) |
186 | { |
187 | } |
188 | |
189 | bool onLoad() |
190 | { |
191 | // Create the off-screen surface |
192 | if (!m_surface.create(800, 600)) |
193 | return false; |
194 | m_surface.setSmooth(true); |
195 | |
196 | // Load the textures |
197 | if (!m_backgroundTexture.loadFromFile("resources/sfml.png" )) |
198 | return false; |
199 | m_backgroundTexture.setSmooth(true); |
200 | if (!m_entityTexture.loadFromFile("resources/devices.png" )) |
201 | return false; |
202 | m_entityTexture.setSmooth(true); |
203 | |
204 | // Initialize the background sprite |
205 | m_backgroundSprite.setTexture(m_backgroundTexture); |
206 | m_backgroundSprite.setPosition(135, 100); |
207 | |
208 | // Load the moving entities |
209 | for (int i = 0; i < 6; ++i) |
210 | { |
211 | sf::Sprite entity(m_entityTexture, sf::IntRect(96 * i, 0, 96, 96)); |
212 | m_entities.push_back(entity); |
213 | } |
214 | |
215 | // Load the shader |
216 | if (!m_shader.loadFromFile("resources/edge.frag" , sf::Shader::Fragment)) |
217 | return false; |
218 | m_shader.setUniform("texture" , sf::Shader::CurrentTexture); |
219 | |
220 | return true; |
221 | } |
222 | |
223 | void onUpdate(float time, float x, float y) |
224 | { |
225 | m_shader.setUniform("edge_threshold" , 1 - (x + y) / 2); |
226 | |
227 | // Update the position of the moving entities |
228 | for (std::size_t i = 0; i < m_entities.size(); ++i) |
229 | { |
230 | sf::Vector2f position; |
231 | position.x = std::cos(0.25f * (time * i + (m_entities.size() - i))) * 300 + 350; |
232 | position.y = std::sin(0.25f * (time * (m_entities.size() - i) + i)) * 200 + 250; |
233 | m_entities[i].setPosition(position); |
234 | } |
235 | |
236 | // Render the updated scene to the off-screen surface |
237 | m_surface.clear(sf::Color::White); |
238 | m_surface.draw(m_backgroundSprite); |
239 | for (std::size_t i = 0; i < m_entities.size(); ++i) |
240 | m_surface.draw(m_entities[i]); |
241 | m_surface.display(); |
242 | } |
243 | |
244 | void onDraw(sf::RenderTarget& target, sf::RenderStates states) const |
245 | { |
246 | states.shader = &m_shader; |
247 | target.draw(sf::Sprite(m_surface.getTexture()), states); |
248 | } |
249 | |
250 | private: |
251 | |
252 | sf::RenderTexture m_surface; |
253 | sf::Texture m_backgroundTexture; |
254 | sf::Texture m_entityTexture; |
255 | sf::Sprite m_backgroundSprite; |
256 | std::vector<sf::Sprite> m_entities; |
257 | sf::Shader m_shader; |
258 | }; |
259 | |
260 | |
261 | //////////////////////////////////////////////////////////// |
262 | // "Geometry" geometry shader example |
263 | //////////////////////////////////////////////////////////// |
264 | class Geometry : public Effect |
265 | { |
266 | public: |
267 | |
268 | Geometry() : |
269 | Effect("geometry shader billboards" ), |
270 | m_pointCloud(sf::Points, 10000) |
271 | { |
272 | } |
273 | |
274 | bool onLoad() |
275 | { |
276 | // Check if geometry shaders are supported |
277 | if (!sf::Shader::isGeometryAvailable()) |
278 | return false; |
279 | |
280 | // Move the points in the point cloud to random positions |
281 | for (std::size_t i = 0; i < 10000; i++) |
282 | { |
283 | // Spread the coordinates from -480 to +480 |
284 | // So they'll always fill the viewport at 800x600 |
285 | m_pointCloud[i].position.x = rand() % 960 - 480.f; |
286 | m_pointCloud[i].position.y = rand() % 960 - 480.f; |
287 | } |
288 | |
289 | // Load the texture |
290 | if (!m_logoTexture.loadFromFile("resources/logo.png" )) |
291 | return false; |
292 | |
293 | // Load the shader |
294 | if (!m_shader.loadFromFile("resources/billboard.vert" , "resources/billboard.geom" , "resources/billboard.frag" )) |
295 | return false; |
296 | m_shader.setUniform("texture" , sf::Shader::CurrentTexture); |
297 | |
298 | // Set the render resolution (used for proper scaling) |
299 | m_shader.setUniform("resolution" , sf::Vector2f(800, 600)); |
300 | |
301 | return true; |
302 | } |
303 | |
304 | void onUpdate(float time, float x, float y) |
305 | { |
306 | // Reset our transformation matrix |
307 | m_transform = sf::Transform::Identity; |
308 | // Move to the center of the window |
309 | m_transform.translate(400, 300); |
310 | // Rotate everything based on cursor position |
311 | m_transform.rotate(x * 360.f); |
312 | |
313 | // Adjust billboard size to scale between 25 and 75 |
314 | float size = 25 + std::abs(y) * 50; |
315 | |
316 | // Update the shader parameter |
317 | m_shader.setUniform("size" , sf::Vector2f(size, size)); |
318 | } |
319 | |
320 | void onDraw(sf::RenderTarget& target, sf::RenderStates states) const |
321 | { |
322 | // Prepare the render state |
323 | states.shader = &m_shader; |
324 | states.texture = &m_logoTexture; |
325 | states.transform = m_transform; |
326 | |
327 | // Draw the point cloud |
328 | target.draw(m_pointCloud, states); |
329 | } |
330 | |
331 | private: |
332 | |
333 | sf::Texture m_logoTexture; |
334 | sf::Transform m_transform; |
335 | sf::Shader m_shader; |
336 | sf::VertexArray m_pointCloud; |
337 | }; |
338 | |
339 | |
340 | //////////////////////////////////////////////////////////// |
341 | /// Entry point of application |
342 | /// |
343 | /// \return Application exit code |
344 | /// |
345 | //////////////////////////////////////////////////////////// |
346 | int main() |
347 | { |
348 | // Create the main window |
349 | sf::RenderWindow window(sf::VideoMode(800, 600), "SFML Shader" , |
350 | sf::Style::Titlebar | sf::Style::Close); |
351 | window.setVerticalSyncEnabled(true); |
352 | |
353 | // Load the application font and pass it to the Effect class |
354 | sf::Font font; |
355 | if (!font.loadFromFile("resources/sansation.ttf" )) |
356 | return EXIT_FAILURE; |
357 | Effect::setFont(font); |
358 | |
359 | // Create the effects |
360 | std::vector<Effect*> effects; |
361 | effects.push_back(new Pixelate); |
362 | effects.push_back(new WaveBlur); |
363 | effects.push_back(new StormBlink); |
364 | effects.push_back(new Edge); |
365 | effects.push_back(new Geometry); |
366 | std::size_t current = 0; |
367 | |
368 | // Initialize them |
369 | for (std::size_t i = 0; i < effects.size(); ++i) |
370 | effects[i]->load(); |
371 | |
372 | // Create the messages background |
373 | sf::Texture textBackgroundTexture; |
374 | if (!textBackgroundTexture.loadFromFile("resources/text-background.png" )) |
375 | return EXIT_FAILURE; |
376 | sf::Sprite textBackground(textBackgroundTexture); |
377 | textBackground.setPosition(0, 520); |
378 | textBackground.setColor(sf::Color(255, 255, 255, 200)); |
379 | |
380 | // Create the description text |
381 | sf::Text description("Current effect: " + effects[current]->getName(), font, 20); |
382 | description.setPosition(10, 530); |
383 | description.setFillColor(sf::Color(80, 80, 80)); |
384 | |
385 | // Create the instructions text |
386 | sf::Text instructions("Press left and right arrows to change the current shader" , font, 20); |
387 | instructions.setPosition(280, 555); |
388 | instructions.setFillColor(sf::Color(80, 80, 80)); |
389 | |
390 | // Start the game loop |
391 | sf::Clock clock; |
392 | while (window.isOpen()) |
393 | { |
394 | // Process events |
395 | sf::Event event; |
396 | while (window.pollEvent(event)) |
397 | { |
398 | // Close window: exit |
399 | if (event.type == sf::Event::Closed) |
400 | window.close(); |
401 | |
402 | if (event.type == sf::Event::KeyPressed) |
403 | { |
404 | switch (event.key.code) |
405 | { |
406 | // Escape key: exit |
407 | case sf::Keyboard::Escape: |
408 | window.close(); |
409 | break; |
410 | |
411 | // Left arrow key: previous shader |
412 | case sf::Keyboard::Left: |
413 | if (current == 0) |
414 | current = effects.size() - 1; |
415 | else |
416 | current--; |
417 | description.setString("Current effect: " + effects[current]->getName()); |
418 | break; |
419 | |
420 | // Right arrow key: next shader |
421 | case sf::Keyboard::Right: |
422 | if (current == effects.size() - 1) |
423 | current = 0; |
424 | else |
425 | current++; |
426 | description.setString("Current effect: " + effects[current]->getName()); |
427 | break; |
428 | |
429 | default: |
430 | break; |
431 | } |
432 | } |
433 | } |
434 | |
435 | // Update the current example |
436 | float x = static_cast<float>(sf::Mouse::getPosition(window).x) / window.getSize().x; |
437 | float y = static_cast<float>(sf::Mouse::getPosition(window).y) / window.getSize().y; |
438 | effects[current]->update(clock.getElapsedTime().asSeconds(), x, y); |
439 | |
440 | // Clear the window |
441 | window.clear(sf::Color(255, 128, 0)); |
442 | |
443 | // Draw the current example |
444 | window.draw(*effects[current]); |
445 | |
446 | // Draw the text |
447 | window.draw(textBackground); |
448 | window.draw(instructions); |
449 | window.draw(description); |
450 | |
451 | // Finally, display the rendered frame on screen |
452 | window.display(); |
453 | } |
454 | |
455 | // delete the effects |
456 | for (std::size_t i = 0; i < effects.size(); ++i) |
457 | delete effects[i]; |
458 | |
459 | return EXIT_SUCCESS; |
460 | } |
461 | |