From c9b87ec8a907023284d16a69956dcf7ef428b342 Mon Sep 17 00:00:00 2001 From: Laurent Gomila Date: Sat, 10 Dec 2011 13:02:38 +0100 Subject: [PATCH] Added support for vertex shaders in sf::Shader Rewrote the Shader example --- examples/shader/CMakeLists.txt | 4 +- examples/shader/Effect.hpp | 74 +++ examples/shader/Shader.cpp | 489 +++++++++++------- examples/shader/resources/blink.frag | 9 + .../shader/resources/{blur.sfx => blur.frag} | 10 +- examples/shader/resources/colorize.sfx | 11 - examples/shader/resources/devices.png | Bin 0 -> 51410 bytes .../shader/resources/{edge.sfx => edge.frag} | 11 +- examples/shader/resources/fisheye.sfx | 13 - examples/shader/resources/nothing.sfx | 6 - .../resources/{pixelate.sfx => pixelate.frag} | 5 +- examples/shader/resources/sfml.png | Bin 0 -> 25973 bytes examples/shader/resources/sprite.png | Bin 6241 -> 0 bytes examples/shader/resources/storm.vert | 19 + examples/shader/resources/text-background.png | Bin 0 -> 745 bytes examples/shader/resources/wave.jpg | Bin 23249 -> 0 bytes examples/shader/resources/wave.sfx | 12 - examples/shader/resources/wave.vert | 15 + include/SFML/Graphics/Shader.hpp | 384 +++++++++----- src/SFML/Graphics/Shader.cpp | 317 ++++++++---- 20 files changed, 893 insertions(+), 486 deletions(-) create mode 100644 examples/shader/Effect.hpp create mode 100644 examples/shader/resources/blink.frag rename examples/shader/resources/{blur.sfx => blur.frag} (73%) delete mode 100644 examples/shader/resources/colorize.sfx create mode 100644 examples/shader/resources/devices.png rename examples/shader/resources/{edge.sfx => edge.frag} (81%) delete mode 100644 examples/shader/resources/fisheye.sfx delete mode 100644 examples/shader/resources/nothing.sfx rename examples/shader/resources/{pixelate.sfx => pixelate.frag} (63%) create mode 100644 examples/shader/resources/sfml.png delete mode 100644 examples/shader/resources/sprite.png create mode 100644 examples/shader/resources/storm.vert create mode 100644 examples/shader/resources/text-background.png delete mode 100644 examples/shader/resources/wave.jpg delete mode 100644 examples/shader/resources/wave.sfx create mode 100644 examples/shader/resources/wave.vert diff --git a/examples/shader/CMakeLists.txt b/examples/shader/CMakeLists.txt index 5b610d4d..acb1bac8 100644 --- a/examples/shader/CMakeLists.txt +++ b/examples/shader/CMakeLists.txt @@ -2,7 +2,9 @@ set(SRCROOT ${PROJECT_SOURCE_DIR}/examples/shader) # all source files -set(SRC ${SRCROOT}/Shader.cpp) +set(SRC + ${SRCROOT}/Effect.hpp + ${SRCROOT}/Shader.cpp) # define the shader target sfml_add_example(shader GUI_APP diff --git a/examples/shader/Effect.hpp b/examples/shader/Effect.hpp new file mode 100644 index 00000000..cb5b3668 --- /dev/null +++ b/examples/shader/Effect.hpp @@ -0,0 +1,74 @@ +#ifndef EFFECT_HPP +#define EFFECT_HPP + +//////////////////////////////////////////////////////////// +// Headers +//////////////////////////////////////////////////////////// +#include +#include + + +//////////////////////////////////////////////////////////// +// Base class for effects +//////////////////////////////////////////////////////////// +class Effect : public sf::Drawable +{ +public : + + virtual ~Effect() + { + } + + const std::string& GetName() const + { + return myName; + } + + void Load() + { + myIsLoaded = sf::Shader::IsAvailable() && OnLoad(); + } + + void Update(float time, float x, float y) + { + if (myIsLoaded) + OnUpdate(time, x, y); + } + + void Draw(sf::RenderTarget& target, sf::RenderStates states) const + { + if (myIsLoaded) + { + OnDraw(target, states); + } + else + { + sf::Text error("Shader not\nsupported"); + error.SetPosition(320.f, 200.f); + error.SetCharacterSize(36); + target.Draw(error, states); + } + } + +protected : + + Effect(const std::string& name) : + myName(name), + myIsLoaded(false) + { + } + +private : + + // Virtual functions to be implemented in derived effects + virtual bool OnLoad() = 0; + virtual void OnUpdate(float time, float x, float y) = 0; + virtual void OnDraw(sf::RenderTarget& target, sf::RenderStates states) const = 0; + +private : + + std::string myName; + bool myIsLoaded; +}; + +#endif // EFFECT_HPP diff --git a/examples/shader/Shader.cpp b/examples/shader/Shader.cpp index e8456fd4..4451c301 100644 --- a/examples/shader/Shader.cpp +++ b/examples/shader/Shader.cpp @@ -2,71 +2,257 @@ //////////////////////////////////////////////////////////// // Headers //////////////////////////////////////////////////////////// +#include "Effect.hpp" #include -#include +#include #include -void DisplayError(); - //////////////////////////////////////////////////////////// -/// A class to simplify shader selection -/// +// "Pixelate" fragment shader //////////////////////////////////////////////////////////// -class ShaderSelector +class Pixelate : public Effect { public : - // Constructor - ShaderSelector(std::map& owner, const std::string& shader) : - myOwner (&owner), - myIterator(owner.find(shader)) + Pixelate() : + Effect("pixelate") { } - // Select the previous shader - void GotoPrevious() + bool OnLoad() { - if (myIterator == myOwner->begin()) - myIterator = myOwner->end(); - myIterator--; + // Load the texture and initialize the sprite + if (!myTexture.LoadFromFile("resources/background.jpg")) + return false; + mySprite.SetTexture(myTexture); + + // Load the shader + if (!myShader.LoadFromFile("resources/pixelate.frag", sf::Shader::Fragment)) + return false; + myShader.SetParameter("texture", sf::Shader::CurrentTexture); + + return true; } - // Select the next shader - void GotoNext() + void OnUpdate(float, float x, float y) { - myIterator++; - if (myIterator == myOwner->end()) - myIterator = myOwner->begin(); + myShader.SetParameter("pixel_threshold", (x + y) / 30); } - // Update the shader parameters - void Update(float x, float y) + void OnDraw(sf::RenderTarget& target, sf::RenderStates states) const { - if (myIterator->first == "blur") myIterator->second.SetParameter("offset", x * y * 0.03f); - else if (myIterator->first == "colorize") myIterator->second.SetParameter("color", 0.3f, x, y); - else if (myIterator->first == "edge") myIterator->second.SetParameter("threshold", x * y); - else if (myIterator->first == "fisheye") myIterator->second.SetParameter("mouse", x, y); - else if (myIterator->first == "wave") myIterator->second.SetParameter("offset", x, y); - else if (myIterator->first == "pixelate") myIterator->second.SetParameter("mouse", x, y); + states.Shader = &myShader; + target.Draw(mySprite, states); } - // Get the name of the current shader - const std::string& GetName() const +private: + + sf::Texture myTexture; + sf::Sprite mySprite; + sf::Shader myShader; +}; + + +//////////////////////////////////////////////////////////// +// "Wave" vertex shader + "blur" fragment shader +//////////////////////////////////////////////////////////// +class WaveBlur : public Effect +{ +public : + + WaveBlur() : + Effect("wave + blur") { - return myIterator->first; } - // Get the current shader - const sf::Shader* GetShader() const + bool OnLoad() { - return &myIterator->second; + // Create the text + myText.SetString("Praesent suscipit augue in velit pulvinar hendrerit varius purus aliquam.\n" + "Mauris mi odio, bibendum quis fringilla a, laoreet vel orci. Proin vitae vulputate tortor.\n" + "Praesent cursus ultrices justo, ut feugiat ante vehicula quis.\n" + "Donec fringilla scelerisque mauris et viverra.\n" + "Maecenas adipiscing ornare scelerisque. Nullam at libero elit.\n" + "Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.\n" + "Nullam leo urna, tincidunt id semper eget, ultricies sed mi.\n" + "Morbi mauris massa, commodo id dignissim vel, lobortis et elit.\n" + "Fusce vel libero sed neque scelerisque venenatis.\n" + "Integer mattis tincidunt quam vitae iaculis.\n" + "Vivamus fringilla sem non velit venenatis fermentum.\n" + "Vivamus varius tincidunt nisi id vehicula.\n" + "Integer ullamcorper, enim vitae euismod rutrum, massa nisl semper ipsum,\n" + "vestibulum sodales sem ante in massa.\n" + "Vestibulum in augue non felis convallis viverra.\n" + "Mauris ultricies dolor sed massa convallis sed aliquet augue fringilla.\n" + "Duis erat eros, porta in accumsan in, blandit quis sem.\n" + "In hac habitasse platea dictumst. Etiam fringilla est id odio dapibus sit amet semper dui laoreet.\n"); + myText.SetCharacterSize(22); + myText.SetPosition(30, 20); + + // Load the shader + //if (!myShader.LoadFromFile("resources/wave.vert", sf::Shader::Vertex)) + if (!myShader.LoadFromFile("resources/wave.vert", "resources/blur.frag")) + return false; + + return true; } -private : + void OnUpdate(float time, float x, float y) + { + myShader.SetParameter("wave_phase", time); + myShader.SetParameter("wave_amplitude", x * 40, y * 40); + myShader.SetParameter("blur_radius", (x + y) * 0.008f); + } - std::map* myOwner; - std::map::iterator myIterator; + void OnDraw(sf::RenderTarget& target, sf::RenderStates states) const + { + states.Shader = &myShader; + target.Draw(myText, states); + } + +private: + + sf::Text myText; + sf::Shader myShader; +}; + + +//////////////////////////////////////////////////////////// +// "Storm" vertex shader + "blink" fragment shader +//////////////////////////////////////////////////////////// +class StormBlink : public Effect +{ +public : + + StormBlink() : + Effect("storm + blink") + { + } + + bool OnLoad() + { + // Create the points + myPoints.SetPrimitiveType(sf::Points); + for (int i = 0; i < 40000; ++i) + { + float x = static_cast(std::rand() % 800); + float y = static_cast(std::rand() % 600); + sf::Uint8 r = std::rand() % 255; + sf::Uint8 g = std::rand() % 255; + sf::Uint8 b = std::rand() % 255; + myPoints.Append(sf::Vertex(sf::Vector2f(x, y), sf::Color(r, g, b))); + } + + // Load the shader + if (!myShader.LoadFromFile("resources/storm.vert", "resources/blink.frag")) + return false; + + return true; + } + + void OnUpdate(float time, float x, float y) + { + float radius = 200 + std::cos(time) * 150; + myShader.SetParameter("storm_position", x * 800, y * 600); + myShader.SetParameter("storm_inner_radius", radius / 3); + myShader.SetParameter("storm_total_radius", radius); + myShader.SetParameter("blink_alpha", 0.5f + std::cos(time * 3) * 0.25f); + } + + void OnDraw(sf::RenderTarget& target, sf::RenderStates states) const + { + states.Shader = &myShader; + target.Draw(myPoints, states); + } + +private: + + sf::VertexArray myPoints; + sf::Shader myShader; +}; + + +//////////////////////////////////////////////////////////// +// "Edge" post-effect fragment shader +//////////////////////////////////////////////////////////// +class Edge : public Effect +{ +public : + + Edge() : + Effect("edge post-effect") + { + } + + bool OnLoad() + { + // Create the off-screen surface + if (!mySurface.Create(800, 600)) + return false; + mySurface.SetSmooth(true); + + // Load the textures + if (!myBackgroundTexture.LoadFromFile("resources/sfml.png")) + return false; + myBackgroundTexture.SetSmooth(true); + if (!myEntityTexture.LoadFromFile("resources/devices.png")) + return false; + myEntityTexture.SetSmooth(true); + + // Initialize the background sprite + myBackgroundSprite.SetTexture(myBackgroundTexture); + myBackgroundSprite.SetPosition(135, 100); + + // Load the moving entities + for (int i = 0; i < 6; ++i) + { + sf::Sprite entity(myEntityTexture, sf::IntRect(96 * i, 0, 96, 96)); + myEntities.push_back(entity); + } + + // Load the shader + if (!myShader.LoadFromFile("resources/edge.frag", sf::Shader::Fragment)) + return false; + myShader.SetParameter("texture", sf::Shader::CurrentTexture); + + return true; + } + + void OnUpdate(float time, float x, float y) + { + myShader.SetParameter("edge_threshold", 1 - (x + y) / 2); + + // Update the position of the moving entities + for (std::size_t i = 0; i < myEntities.size(); ++i) + { + float x = std::cos(0.25f * (time * i + (myEntities.size() - i))) * 300 + 350; + float y = std::sin(0.25f * (time * (myEntities.size() - i) + i)) * 200 + 250; + myEntities[i].SetPosition(x, y); + } + + // Render the updated scene to the off-screen surface + mySurface.Clear(sf::Color::White); + mySurface.Draw(myBackgroundSprite); + for (std::size_t i = 0; i < myEntities.size(); ++i) + mySurface.Draw(myEntities[i]); + mySurface.Display(); + } + + void OnDraw(sf::RenderTarget& target, sf::RenderStates states) const + { + states.Shader = &myShader; + target.Draw(sf::Sprite(mySurface.GetTexture()), states); + } + +private: + + sf::RenderTexture mySurface; + sf::Texture myBackgroundTexture; + sf::Texture myEntityTexture; + sf::Sprite myBackgroundSprite; + std::vector myEntities; + sf::Shader myShader; }; @@ -78,203 +264,110 @@ private : //////////////////////////////////////////////////////////// int main() { - // Check that the system can use shaders - if (sf::Shader::IsAvailable() == false) - { - DisplayError(); - return EXIT_SUCCESS; - } - // Create the main window sf::RenderWindow window(sf::VideoMode(800, 600), "SFML Shader"); + window.EnableVerticalSync(true); - // Create the render texture - sf::RenderTexture texture; - if (!texture.Create(window.GetWidth(), window.GetHeight())) + // Create the effects + std::vector effects; + effects.push_back(new Pixelate); + effects.push_back(new WaveBlur); + effects.push_back(new StormBlink); + effects.push_back(new Edge); + std::size_t current = 0; + + // Initialize them + for (std::size_t i = 0; i < effects.size(); ++i) + effects[i]->Load(); + + // Create the messages background + sf::Texture textBackgroundTexture; + if (!textBackgroundTexture.LoadFromFile("resources/text-background.png")) return EXIT_FAILURE; + sf::Sprite textBackground(textBackgroundTexture); + textBackground.SetPosition(0, 520); + textBackground.SetColor(sf::Color(255, 255, 255, 200)); - // Load a background texture to display - sf::Texture backgroundTexture; - if (!backgroundTexture.LoadFromFile("resources/background.jpg")) - return EXIT_FAILURE; - sf::Sprite background(backgroundTexture); - - // Load a sprite which we'll move into the scene - sf::Texture entityTexture; - if (!entityTexture.LoadFromFile("resources/sprite.png")) - return EXIT_FAILURE; - sf::Sprite entity(entityTexture); - - // Load the text font + // Load the messages font sf::Font font; if (!font.LoadFromFile("resources/sansation.ttf")) return EXIT_FAILURE; - // Load the texture needed for the wave shader - sf::Texture waveTexture; - if (!waveTexture.LoadFromFile("resources/wave.jpg")) - return EXIT_FAILURE; + // Create the description text + sf::Text description("Current effect: " + effects[current]->GetName(), font, 20); + description.SetPosition(10, 530); + description.SetColor(sf::Color(80, 80, 80)); - // Load all shaders - std::map shaders; - if (!shaders["nothing"].LoadFromFile("resources/nothing.sfx")) return EXIT_FAILURE; - if (!shaders["blur"].LoadFromFile("resources/blur.sfx")) return EXIT_FAILURE; - if (!shaders["colorize"].LoadFromFile("resources/colorize.sfx")) return EXIT_FAILURE; - if (!shaders["edge"].LoadFromFile("resources/edge.sfx")) return EXIT_FAILURE; - if (!shaders["fisheye"].LoadFromFile("resources/fisheye.sfx")) return EXIT_FAILURE; - if (!shaders["wave"].LoadFromFile("resources/wave.sfx")) return EXIT_FAILURE; - if (!shaders["pixelate"].LoadFromFile("resources/pixelate.sfx")) return EXIT_FAILURE; - ShaderSelector backgroundShader(shaders, "nothing"); - ShaderSelector entityShader(shaders, "nothing"); - ShaderSelector globalShader(shaders, "nothing"); - - // Do specific initializations - shaders["nothing"].SetCurrentTexture("texture"); - shaders["blur"].SetCurrentTexture("texture"); - shaders["blur"].SetParameter("offset", 0.f); - shaders["colorize"].SetCurrentTexture("texture"); - shaders["colorize"].SetParameter("color", 1.f, 1.f, 1.f); - shaders["edge"].SetCurrentTexture("texture"); - shaders["fisheye"].SetCurrentTexture("texture"); - shaders["wave"].SetCurrentTexture("texture"); - shaders["wave"].SetTexture("wave", waveTexture); - shaders["pixelate"].SetCurrentTexture("texture"); - - // Define a string for displaying the description of the current shader - sf::Text shaderStr; - shaderStr.SetFont(font); - shaderStr.SetCharacterSize(20); - shaderStr.SetColor(sf::Color(250, 100, 30)); - shaderStr.SetPosition(5.f, 0.f); - shaderStr.SetString("Background shader: \"" + backgroundShader.GetName() + "\"\n" - "Flower shader: \"" + entityShader.GetName() + "\"\n" - "Global shader: \"" + globalShader.GetName() + "\"\n"); - - // Define a string for displaying help - sf::Text infoStr; - infoStr.SetFont(font); - infoStr.SetCharacterSize(20); - infoStr.SetColor(sf::Color(250, 100, 30)); - infoStr.SetPosition(5.f, 500.f); - infoStr.SetString("Move your mouse to change the shaders' parameters\n" - "Press numpad 1/4 to change the background shader\n" - "Press numpad 2/5 to change the flower shader\n" - "Press numpad 3/6 to change the global shader"); - - // Create a clock to measure the total time elapsed - sf::Clock clock; + // Create the instructions text + sf::Text instructions("Press left and right arrows to change the current shader", font, 20); + instructions.SetPosition(280, 555); + instructions.SetColor(sf::Color(80, 80, 80)); // Start the game loop + sf::Clock clock; while (window.IsOpened()) { // Process events sf::Event event; while (window.PollEvent(event)) { - // Close window : exit + // Close window: exit if (event.Type == sf::Event::Closed) window.Close(); if (event.Type == sf::Event::KeyPressed) { - // Escape key : exit - if (event.Key.Code == sf::Keyboard::Escape) - window.Close(); - - // Numpad : switch effect switch (event.Key.Code) { - case sf::Keyboard::Numpad1 : backgroundShader.GotoPrevious(); break; - case sf::Keyboard::Numpad4 : backgroundShader.GotoNext(); break; - case sf::Keyboard::Numpad2 : entityShader.GotoPrevious(); break; - case sf::Keyboard::Numpad5 : entityShader.GotoNext(); break; - case sf::Keyboard::Numpad3 : globalShader.GotoPrevious(); break; - case sf::Keyboard::Numpad6 : globalShader.GotoNext(); break; - default : break; - } + // Escape key: exit + case sf::Keyboard::Escape: + window.Close(); + break; - // Update the text - shaderStr.SetString("Background shader: \"" + backgroundShader.GetName() + "\"\n" - "Entity shader: \"" + entityShader.GetName() + "\"\n" - "Global shader: \"" + globalShader.GetName() + "\"\n"); + // Left arrow key: previous shader + case sf::Keyboard::Left: + if (current == 0) + current = effects.size() - 1; + else + current--; + description.SetString("Current effect: " + effects[current]->GetName()); + break; + + // Right arrow key: next shader + case sf::Keyboard::Right: + if (current == effects.size() - 1) + current = 0; + else + current++; + description.SetString("Current effect: " + effects[current]->GetName()); + break; + } } } - // Get the mouse position in the range [0, 1] - float mouseX = sf::Mouse::GetPosition(window).x / static_cast(window.GetWidth()); - float mouseY = sf::Mouse::GetPosition(window).y / static_cast(window.GetHeight()); + // Update the current example + float x = static_cast(sf::Mouse::GetPosition(window).x) / window.GetWidth(); + float y = static_cast(sf::Mouse::GetPosition(window).y) / window.GetHeight(); + effects[current]->Update(clock.GetElapsedTime() / 1000.f, x, y); - // Update the shaders - backgroundShader.Update(mouseX, mouseY); - entityShader.Update(mouseX, mouseY); - globalShader.Update(mouseX, mouseY); + // Clear the window + window.Clear(sf::Color(255, 128, 0)); - // Animate the entity - float entityX = (std::cos(clock.GetElapsedTime() * 0.0013f) + 1.2f) * 300; - float entityY = (std::cos(clock.GetElapsedTime() * 0.0008f) + 1.2f) * 200; - entity.SetPosition(entityX, entityY); - entity.Rotate(window.GetFrameTime() * 0.1f); + // Draw the current example + window.Draw(*effects[current]); - // Draw the background and the moving entity to the render texture - texture.Clear(); - texture.Draw(background, backgroundShader.GetShader()); - texture.Draw(entity, entityShader.GetShader()); - texture.Display(); - - // Draw the contents of the render texture to the window - sf::Sprite screen(texture.GetTexture()); - window.Draw(screen, globalShader.GetShader()); - - // Draw the interface texts - window.Draw(shaderStr); - window.Draw(infoStr); + // Draw the text + window.Draw(textBackground); + window.Draw(instructions); + window.Draw(description); // Finally, display the rendered frame on screen window.Display(); } + // delete the effects + for (std::size_t i = 0; i < effects.size(); ++i) + delete effects[i]; + return EXIT_SUCCESS; } - - -//////////////////////////////////////////////////////////// -/// Fonction called when the post-effects are not supported ; -/// Display an error message and wait until the user exits -/// -//////////////////////////////////////////////////////////// -void DisplayError() -{ - // Create the main window - sf::RenderWindow window(sf::VideoMode(800, 600), "SFML Shader"); - - // Define a string for displaying the error message - sf::Text error("Sorry, your system doesn't support shaders"); - error.SetColor(sf::Color(200, 100, 150)); - error.SetPosition(100.f, 250.f); - - // Start the game loop - while (window.IsOpened()) - { - // Process events - sf::Event event; - while (window.PollEvent(event)) - { - // Close window : exit - if (event.Type == sf::Event::Closed) - window.Close(); - - // Escape key : exit - if ((event.Type == sf::Event::KeyPressed) && (event.Key.Code == sf::Keyboard::Escape)) - window.Close(); - } - - // Clear the window - window.Clear(); - - // Draw the error message - window.Draw(error); - - // Finally, display the rendered frame on screen - window.Display(); - } -} diff --git a/examples/shader/resources/blink.frag b/examples/shader/resources/blink.frag new file mode 100644 index 00000000..07c8ddb2 --- /dev/null +++ b/examples/shader/resources/blink.frag @@ -0,0 +1,9 @@ +uniform sampler2D texture; +uniform float blink_alpha; + +void main() +{ + vec4 pixel = gl_Color; + pixel.a = blink_alpha; + gl_FragColor = pixel; +} diff --git a/examples/shader/resources/blur.sfx b/examples/shader/resources/blur.frag similarity index 73% rename from examples/shader/resources/blur.sfx rename to examples/shader/resources/blur.frag index bde2fa65..c40e5b3f 100644 --- a/examples/shader/resources/blur.sfx +++ b/examples/shader/resources/blur.frag @@ -1,12 +1,12 @@ uniform sampler2D texture; -uniform float offset; +uniform float blur_radius; void main() { - vec2 offx = vec2(offset, 0.0); - vec2 offy = vec2(0.0, offset); + vec2 offx = vec2(blur_radius, 0.0); + vec2 offy = vec2(0.0, blur_radius); - vec4 pixel = texture2D(texture, gl_TexCoord[0].xy) * 1.0 + + vec4 pixel = texture2D(texture, gl_TexCoord[0].xy) * 4.0 + texture2D(texture, gl_TexCoord[0].xy - offx) * 2.0 + texture2D(texture, gl_TexCoord[0].xy + offx) * 2.0 + texture2D(texture, gl_TexCoord[0].xy - offy) * 2.0 + @@ -16,5 +16,5 @@ void main() texture2D(texture, gl_TexCoord[0].xy + offx - offy) * 1.0 + texture2D(texture, gl_TexCoord[0].xy + offx + offy) * 1.0; - gl_FragColor = gl_Color * (pixel / 13.0); + gl_FragColor = gl_Color * (pixel / 16.0); } diff --git a/examples/shader/resources/colorize.sfx b/examples/shader/resources/colorize.sfx deleted file mode 100644 index eeb407c8..00000000 --- a/examples/shader/resources/colorize.sfx +++ /dev/null @@ -1,11 +0,0 @@ -uniform sampler2D texture; -uniform vec3 color; - -void main() -{ - vec4 pixel = texture2D(texture, gl_TexCoord[0].xy) * gl_Color; - float gray = pixel.r * 0.39 + pixel.g * 0.50 + pixel.b * 0.11; - - gl_FragColor = vec4(gray * color, 1.0) * 0.6 + pixel * 0.4; - gl_FragColor.a = pixel.a; -} diff --git a/examples/shader/resources/devices.png b/examples/shader/resources/devices.png new file mode 100644 index 0000000000000000000000000000000000000000..6b1cbc85b8f59a269c58f1fc031c19464693b06d GIT binary patch literal 51410 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�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 threshold) - gl_FragColor.rgb = vec3(0, 0, 0); + vec4 pixel = gl_Color * texture2D(texture, gl_TexCoord[0].xy); + if (edge > edge_threshold * 8) + pixel.rgb = vec3(0.0, 0.0, 0.0); else - gl_FragColor.rgb = vec3(1, 1, 1); - gl_FragColor.a = gl_Color.a * texture2D(texture, gl_TexCoord[0].xy).a; + pixel.a = edge_threshold; + gl_FragColor = pixel; } diff --git a/examples/shader/resources/fisheye.sfx b/examples/shader/resources/fisheye.sfx deleted file mode 100644 index 97e3f8e4..00000000 --- a/examples/shader/resources/fisheye.sfx +++ /dev/null @@ -1,13 +0,0 @@ -uniform sampler2D texture; -uniform vec2 mouse; - -void main() -{ - float len = distance(gl_TexCoord[0].xy, mouse) * 7.0; - - vec2 coords = gl_TexCoord[0].xy; - if (len < 1.0) - coords += (gl_TexCoord[0].xy - mouse) * len; - - gl_FragColor = texture2D(texture, coords) * gl_Color; -} diff --git a/examples/shader/resources/nothing.sfx b/examples/shader/resources/nothing.sfx deleted file mode 100644 index cde0473a..00000000 --- a/examples/shader/resources/nothing.sfx +++ /dev/null @@ -1,6 +0,0 @@ -uniform sampler2D texture; - -void main() -{ - gl_FragColor = texture2D(texture, gl_TexCoord[0].xy) * gl_Color; -} diff --git a/examples/shader/resources/pixelate.sfx b/examples/shader/resources/pixelate.frag similarity index 63% rename from examples/shader/resources/pixelate.sfx rename to examples/shader/resources/pixelate.frag index 12a334e7..3c8eb0e0 100644 --- a/examples/shader/resources/pixelate.sfx +++ b/examples/shader/resources/pixelate.frag @@ -1,10 +1,9 @@ uniform sampler2D texture; -uniform vec2 mouse; +uniform float pixel_threshold; void main() { - float factor = 5.0 + 100.0 * length(mouse); + float factor = 1.0 / (pixel_threshold + 0.001); vec2 pos = floor(gl_TexCoord[0].xy * factor + 0.5) / factor; - gl_FragColor = texture2D(texture, pos) * gl_Color; } diff --git a/examples/shader/resources/sfml.png b/examples/shader/resources/sfml.png new file mode 100644 index 0000000000000000000000000000000000000000..1da719ff0afbd9549533bf992b5fc13ad957d9f0 GIT binary patch literal 25973 zcmXteWmFu`)Ai!+F2UX1A-F?ucXxM(;JUa34Fq?0cY?cXaChg~-~T=Dhut}+XQsMu zch#-iH65j-D1`)%4-WtUkYuFARR92RbN~QM7#14Ta*~|71-f9lNocu=xtJTfSvxwA zs9D>a17Ow>;SngQKE~01gC2l!lTncPUlR{1?l)LXP!pV!w6-e%0Dt}84@|U@BLe_1 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
A(GOC&9E< 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 LNkvXXu0mjfpAzUi diff --git a/examples/shader/resources/storm.vert b/examples/shader/resources/storm.vert new file mode 100644 index 00000000..442f80a9 --- /dev/null +++ b/examples/shader/resources/storm.vert @@ -0,0 +1,19 @@ +uniform vec2 storm_position; +uniform float storm_total_radius; +uniform float storm_inner_radius; + +void main() +{ + vec4 vertex = gl_ModelViewMatrix * gl_Vertex; + vec2 offset = vertex.xy - storm_position; + float len = length(offset); + if (len < storm_total_radius) + { + float push_distance = storm_inner_radius + len / storm_total_radius * (storm_total_radius - storm_inner_radius); + vertex.xy = storm_position + normalize(offset) * push_distance; + } + + gl_Position = gl_ProjectionMatrix * vertex; + gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0; + gl_FrontColor = gl_Color; +} diff --git a/examples/shader/resources/text-background.png b/examples/shader/resources/text-background.png new file mode 100644 index 0000000000000000000000000000000000000000..c86e9b6c0ec49ab9c6a0208b22d5b811f0681443 GIT binary patch literal 745 zcmeAS@N?(olHy`uVBq!ia0y~yU{(OK12~v~WZsRoWFRG4;u=xnT$Gwvl9`{U5R#dj z%D{e`hm%jw;NKKsTc85=5>H=Ou#h$L;t%EiKq1ZokH}&M20djEW~^9hU&g?|lAydb|aAM`} zzx%$MGcfQh-Eys}o|)l5K-pe<28IL^HZcZ=8I7zA0%-}13@wro3=D~63w`~*eCxOW z4}-eaLBP8SvEn)m4I6I#E6IHQ3Ih0UmL6nc;3z(L=_&;9uHI|6Zpxz75MZ5FetX-S zZU%k zZQ1Vg&y9a8F&uajv!SFE0xsV@{&-@oAVWftUc@dac=_&c-FXZv%64N|QN1^Avpy4p zPu}(4%nS@}kHcFKMm#M#iD5*IT{_s6^-uU11i~Jh8-kq&4C$Sq;6@{^o_~J%w=@Go zThRUYSM*5 nzq||#&MTK+UI|VqPq}B`Jmh literal 0 HcmV?d00001 diff --git a/examples/shader/resources/wave.jpg b/examples/shader/resources/wave.jpg deleted file mode 100644 index cd125b8c82fe5b51d1d15f75b95cba2b5ca4a516..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 23249 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 gRk54Iu4Y9ghG72y(FAw7Vc0$N1)N=g -#include +#include +#include #include +#include #include #include #include @@ -39,30 +41,45 @@ namespace sf { +class InputStream; +class Texture; + //////////////////////////////////////////////////////////// -/// \brief Pixel/fragment shader class +/// \brief Shader class (vertex and fragment) /// //////////////////////////////////////////////////////////// -class SFML_API Shader : GlResource +class SFML_API Shader : GlResource, NonCopyable { +public : + + //////////////////////////////////////////////////////////// + /// \brief Types of shaders + /// + //////////////////////////////////////////////////////////// + enum Type + { + Vertex, ///< Vertex shader + Fragment ///< Fragment (pixel) shader + }; + + //////////////////////////////////////////////////////////// + /// \brief Special type/value that can be passed to SetParameter, + /// and that represents the texture of the object being drawn + /// + //////////////////////////////////////////////////////////// + struct CurrentTextureType {}; + static CurrentTextureType CurrentTexture; + public : //////////////////////////////////////////////////////////// /// \brief Default constructor /// - /// This constructor creates an invalid shader + /// This constructor creates an invalid shader. /// //////////////////////////////////////////////////////////// Shader(); - //////////////////////////////////////////////////////////// - /// \brief Copy constructor - /// - /// \param copy Instance to copy - /// - //////////////////////////////////////////////////////////// - Shader(const Shader& copy); - //////////////////////////////////////////////////////////// /// \brief Destructor /// @@ -70,64 +87,139 @@ public : ~Shader(); //////////////////////////////////////////////////////////// - /// \brief Load the shader from a file + /// \brief Load either the vertex or fragment shader from a file /// + /// This function loads a single shader, either vertex or + /// fragment, identified by the second argument. /// The source must be a text file containing a valid - /// fragment shader in GLSL language. GLSL is a C-like - /// language dedicated to OpenGL shaders; you'll probably - /// need to read a good documentation for it before writing - /// your own shaders. + /// shader in GLSL language. GLSL is a C-like language + /// dedicated to OpenGL shaders; you'll probably need to + /// read a good documentation for it before writing your + /// own shaders. /// - /// \param filename Path of the shader file to load + /// \param filename Path of the vertex or fragment shader file to load + /// \param type Type of shader (vertex or fragment) /// /// \return True if loading succeeded, false if it failed /// /// \see LoadFromMemory, LoadFromStream /// //////////////////////////////////////////////////////////// - bool LoadFromFile(const std::string& filename); + bool LoadFromFile(const std::string& filename, Type type); //////////////////////////////////////////////////////////// - /// \brief Load the shader from a source code in memory + /// \brief Load both the vertex and fragment shaders from files /// - /// The source code must be a valid fragment shader in - /// GLSL language. GLSL is a C-like language dedicated - /// to OpenGL shaders; you'll probably need to read a - /// good documentation for it before writing your own shaders. + /// This function loads both the vertex and the fragment + /// shaders. If one of them fails to load, the shader is left + /// empty (the valid shader is unloaded). + /// The sources must be text files containing valid shaders + /// in GLSL language. GLSL is a C-like language dedicated to + /// OpenGL shaders; you'll probably need to read a good documentation + /// for it before writing your own shaders. + /// + /// \param vertexShaderFilename Path of the vertex shader file to load + /// \param fragmentShaderFilename Path of the fragment shader file to load + /// + /// \return True if loading succeeded, false if it failed + /// + /// \see LoadFromMemory, LoadFromStream + /// + //////////////////////////////////////////////////////////// + bool LoadFromFile(const std::string& vertexShaderFilename, const std::string& fragmentShaderFilename); + + //////////////////////////////////////////////////////////// + /// \brief Load either the vertex or fragment shader from a source code in memory + /// + /// This function loads a single shader, either vertex or + /// fragment, identified by the second argument. + /// The source code must be a valid shader in GLSL language. + /// GLSL is a C-like language dedicated to OpenGL shaders; + /// you'll probably need to read a good documentation for + /// it before writing your own shaders. /// /// \param shader String containing the source code of the shader + /// \param type Type of shader (vertex or fragment) /// /// \return True if loading succeeded, false if it failed /// /// \see LoadFromFile, LoadFromStream /// //////////////////////////////////////////////////////////// - bool LoadFromMemory(const std::string& shader); + bool LoadFromMemory(const std::string& shader, Type type); //////////////////////////////////////////////////////////// - /// \brief Load the shader from a custom stream + /// \brief Load both the vertex and fragment shaders from source codes in memory /// - /// The source code must be a valid fragment shader in - /// GLSL language. GLSL is a C-like language dedicated - /// to OpenGL shaders; you'll probably need to read a - /// good documentation for it before writing your own shaders. + /// This function loads both the vertex and the fragment + /// shaders. If one of them fails to load, the shader is left + /// empty (the valid shader is unloaded). + /// The sources must be valid shaders in GLSL language. GLSL is + /// a C-like language dedicated to OpenGL shaders; you'll + /// probably need to read a good documentation for it before + /// writing your own shaders. + /// + /// \param vertexShader String containing the source code of the vertex shader + /// \param fragmentShader String containing the source code of the fragment shader + /// + /// \return True if loading succeeded, false if it failed + /// + /// \see LoadFromFile, LoadFromStream + /// + //////////////////////////////////////////////////////////// + bool LoadFromMemory(const std::string& vertexShader, const std::string& fragmentShader); + + //////////////////////////////////////////////////////////// + /// \brief Load either the vertex or fragment shader from a custom stream + /// + /// This function loads a single shader, either vertex or + /// fragment, identified by the second argument. + /// The source code must be a valid shader in GLSL language. + /// GLSL is a C-like language dedicated to OpenGL shaders; + /// you'll probably need to read a good documentation for it + /// before writing your own shaders. /// /// \param stream Source stream to read from + /// \param type Type of shader (vertex or fragment) /// /// \return True if loading succeeded, false if it failed /// /// \see LoadFromFile, LoadFromMemory /// //////////////////////////////////////////////////////////// - bool LoadFromStream(InputStream& stream); + bool LoadFromStream(InputStream& stream, Type type); + + //////////////////////////////////////////////////////////// + /// \brief Load both the vertex and fragment shaders from custom streams + /// + /// This function loads both the vertex and the fragment + /// shaders. If one of them fails to load, the shader is left + /// empty (the valid shader is unloaded). + /// The source codes must be valid shaders in GLSL language. + /// GLSL is a C-like language dedicated to OpenGL shaders; + /// you'll probably need to read a good documentation for + /// it before writing your own shaders. + /// + /// \param vertexShaderStream Source stream to read the vertex shader from + /// \param fragmentShaderStream Source stream to read the fragment shader from + /// + /// \return True if loading succeeded, false if it failed + /// + /// \see LoadFromFile, LoadFromMemory + /// + //////////////////////////////////////////////////////////// + bool LoadFromStream(InputStream& vertexShaderStream, InputStream& fragmentShaderStream); //////////////////////////////////////////////////////////// /// \brief Change a float parameter of the shader /// /// \a name is the name of the variable to change in the shader. - /// For example: + /// The corresponding parameter in the shader must be a float + /// (float GLSL type). + /// + /// Example: /// \code - /// uniform float myparam; // this is the variable in the pixel shader + /// uniform float myparam; // this is the variable in the shader /// \endcode /// \code /// shader.SetParameter("myparam", 5.2f); @@ -136,8 +228,6 @@ public : /// \param name Name of the parameter in the shader /// \param x Value to assign /// - /// \see SetTexture, SetCurrentTexture - /// //////////////////////////////////////////////////////////// void SetParameter(const std::string& name, float x); @@ -145,9 +235,12 @@ public : /// \brief Change a 2-components vector parameter of the shader /// /// \a name is the name of the variable to change in the shader. - /// For example: + /// The corresponding parameter in the shader must be a 2x1 vector + /// (vec2 GLSL type). + /// + /// Example: /// \code - /// uniform vec2 myparam; // this is the variable in the pixel shader + /// uniform vec2 myparam; // this is the variable in the shader /// \endcode /// \code /// shader.SetParameter("myparam", 5.2f, 6.0f); @@ -157,8 +250,6 @@ public : /// \param x First component of the value to assign /// \param y Second component of the value to assign /// - /// \see SetTexture, SetCurrentTexture - /// //////////////////////////////////////////////////////////// void SetParameter(const std::string& name, float x, float y); @@ -166,9 +257,12 @@ public : /// \brief Change a 3-components vector parameter of the shader /// /// \a name is the name of the variable to change in the shader. - /// For example: + /// The corresponding parameter in the shader must be a 3x1 vector + /// (vec3 GLSL type). + /// + /// Example: /// \code - /// uniform vec3 myparam; // this is the variable in the pixel shader + /// uniform vec3 myparam; // this is the variable in the shader /// \endcode /// \code /// shader.SetParameter("myparam", 5.2f, 6.0f, -8.1f); @@ -179,8 +273,6 @@ public : /// \param y Second component of the value to assign /// \param z Third component of the value to assign /// - /// \see SetTexture, SetCurrentTexture - /// //////////////////////////////////////////////////////////// void SetParameter(const std::string& name, float x, float y, float z); @@ -188,9 +280,12 @@ public : /// \brief Change a 4-components vector parameter of the shader /// /// \a name is the name of the variable to change in the shader. - /// For example: + /// The corresponding parameter in the shader must be a 4x1 vector + /// (vec4 GLSL type). + /// + /// Example: /// \code - /// uniform vec4 myparam; // this is the variable in the pixel shader + /// uniform vec4 myparam; // this is the variable in the shader /// \endcode /// \code /// shader.SetParameter("myparam", 5.2f, 6.0f, -8.1f, 0.4f); @@ -202,8 +297,6 @@ public : /// \param z Third component of the value to assign /// \param w Fourth component of the value to assign /// - /// \see SetTexture, SetCurrentTexture - /// //////////////////////////////////////////////////////////// void SetParameter(const std::string& name, float x, float y, float z, float w); @@ -211,9 +304,12 @@ public : /// \brief Change a 2-components vector parameter of the shader /// /// \a name is the name of the variable to change in the shader. - /// For example: + /// The corresponding parameter in the shader must be a 2x1 vector + /// (vec2 GLSL type). + /// + /// Example: /// \code - /// uniform vec2 myparam; // this is the variable in the pixel shader + /// uniform vec2 myparam; // this is the variable in the shader /// \endcode /// \code /// shader.SetParameter("myparam", sf::Vector2f(5.2f, 6.0f)); @@ -222,8 +318,6 @@ public : /// \param name Name of the parameter in the shader /// \param vector Vector to assign /// - /// \see SetTexture, SetCurrentTexture - /// //////////////////////////////////////////////////////////// void SetParameter(const std::string& name, const Vector2f& vector); @@ -231,9 +325,12 @@ public : /// \brief Change a 2-components vector parameter of the shader /// /// \a name is the name of the variable to change in the shader. - /// For example: + /// The corresponding parameter in the shader must be a 3x1 vector + /// (vec3 GLSL type). + /// + /// Example: /// \code - /// uniform vec3 myparam; // this is the variable in the pixel shader + /// uniform vec3 myparam; // this is the variable in the shader /// \endcode /// \code /// shader.SetParameter("myparam", sf::Vector3f(5.2f, 6.0f, -8.1f)); @@ -242,59 +339,113 @@ public : /// \param name Name of the parameter in the shader /// \param vector Vector to assign /// - /// \see SetTexture, SetCurrentTexture - /// //////////////////////////////////////////////////////////// void SetParameter(const std::string& name, const Vector3f& vector); //////////////////////////////////////////////////////////// - /// \brief Change a texture parameter of the shader + /// \brief Change a color parameter of the shader + /// + /// \a name is the name of the variable to change in the shader. + /// The corresponding parameter in the shader must be a 4x1 vector + /// (vec4 GLSL type). + /// + /// It is important to note that the components of the color are + /// normalized before being passed to the shader. Therefore, + /// they are converted from range [0 .. 255] to range [0 .. 1]. + /// For example, a sf::Color(255, 125, 0, 255) will be transformed + /// to a vec4(1.0, 0.5, 0.0, 1.0) in the shader. /// - /// \a name is the name of the texture to change in the shader. - /// This function maps an external texture to the given shader - /// variable; to use the current texture of the object being drawn, - /// use SetCurrentTexture instead. /// Example: /// \code - /// // These are the variables in the pixel shader - /// uniform sampler2D the_texture; + /// uniform vec4 color; // this is the variable in the shader + /// \endcode + /// \code + /// shader.SetParameter("color", sf::Color(255, 128, 0, 255)); + /// \endcode + /// + /// \param name Name of the parameter in the shader + /// \param color Color to assign + /// + //////////////////////////////////////////////////////////// + void SetParameter(const std::string& name, const Color& color); + + //////////////////////////////////////////////////////////// + /// \brief Change a matrix parameter of the shader + /// + /// \a name is the name of the variable to change in the shader. + /// The corresponding parameter in the shader must be a 4x4 matrix + /// (mat4 GLSL type). + /// + /// Example: + /// \code + /// uniform mat4 matrix; // this is the variable in the shader + /// \endcode + /// \code + /// sf::Transform transform; + /// transform.Translate(5, 10); + /// shader.SetParameter("matrix", transform); + /// \endcode + /// + /// \param name Name of the parameter in the shader + /// \param transform Transform to assign + /// + //////////////////////////////////////////////////////////// + void SetParameter(const std::string& name, const sf::Transform& transform); + + //////////////////////////////////////////////////////////// + /// \brief Change a texture parameter of the shader + /// + /// \a name is the name of the variable to change in the shader. + /// The corresponding parameter in the shader must be a 2D texture + /// (sampler2D GLSL type). + /// + /// Example: + /// \code + /// uniform sampler2D the_texture; // this is the variable in the shader /// \endcode /// \code /// sf::Texture texture; /// ... - /// shader.SetTexture("the_texture", texture); + /// shader.SetParameter("the_texture", texture); /// \endcode /// It is important to note that \a texture must remain alive as long /// as the shader uses it, no copy is made internally. /// + /// To use the texture of the object being draw, which cannot be + /// known in advance, you can pass the special value + /// sf::Shader::CurrentTexture: + /// \code + /// shader.SetParameter("the_texture", sf::Shader::CurrentTexture). + /// \endcode + /// /// \param name Name of the texture in the shader /// \param texture Texture to assign /// - /// \see SetParameter, SetCurrentTexture - /// //////////////////////////////////////////////////////////// - void SetTexture(const std::string& name, const Texture& texture); + void SetParameter(const std::string& name, const Texture& texture); //////////////////////////////////////////////////////////// - /// \brief Set the current object texture in the shader + /// \brief Change a texture parameter of the shader + /// + /// This overload maps a shader texture variable to the + /// texture of the object being drawn, which cannot be + /// known in advance. The second argument must be + /// sf::Shader::CurrentTexture. + /// The corresponding parameter in the shader must be a 2D texture + /// (sampler2D GLSL type). /// - /// This function maps a shader texture variable to the - /// texture of the object being drawn. /// Example: /// \code - /// // This is the variable in the pixel shader - /// uniform sampler2D current; + /// uniform sampler2D current; // this is the variable in the shader /// \endcode /// \code - /// shader.SetCurrentTexture("current"); + /// shader.SetParameter("current", sf::Shader::CurrentTexture); /// \endcode /// /// \param name Name of the texture in the shader /// - /// \see SetParameter, SetTexture - /// //////////////////////////////////////////////////////////// - void SetCurrentTexture(const std::string& name); + void SetParameter(const std::string& name, CurrentTextureType); //////////////////////////////////////////////////////////// /// \brief Bind the shader for rendering (activate it) @@ -326,16 +477,6 @@ public : //////////////////////////////////////////////////////////// void Unbind() const; - //////////////////////////////////////////////////////////// - /// \brief Overload of assignment operator - /// - /// \param right Instance to assign - /// - /// \return Reference to self - /// - //////////////////////////////////////////////////////////// - Shader& operator =(const Shader& right); - //////////////////////////////////////////////////////////// /// \brief Tell whether or not the system supports shaders /// @@ -351,12 +492,18 @@ public : private : //////////////////////////////////////////////////////////// - /// \brief Create the program and attach the shaders + /// \brief Compile the shader(s) and create the program + /// + /// If one of the arguments is NULL, the corresponding shader + /// is not created. + /// + /// \param vertexShaderCode Source code of the vertex shader + /// \param fragmentShaderCode Source code of the fragment shader /// /// \return True on success, false if any error happened /// //////////////////////////////////////////////////////////// - bool CompileProgram(); + bool CompileProgram(const char* vertexShaderCode, const char* fragmentShaderCode); //////////////////////////////////////////////////////////// /// \brief Bind all the textures used by the shader @@ -367,16 +514,6 @@ private : //////////////////////////////////////////////////////////// void BindTextures() const; - //////////////////////////////////////////////////////////// - /// \brief Make sure that the shader is ready to be used - /// - /// This function is called by the Renderer class, to make - /// sure that the shader's parameters are properly applied - /// even when Use() is not called due to internal optimizations. - /// - //////////////////////////////////////////////////////////// - void Use() const; - //////////////////////////////////////////////////////////// // Types //////////////////////////////////////////////////////////// @@ -388,7 +525,6 @@ private : unsigned int myShaderProgram; ///< OpenGL identifier for the program int myCurrentTexture; ///< Location of the current texture in the shader TextureTable myTextures; ///< Texture variables in the shader, mapped to their location - std::string myFragmentShader; ///< Fragment shader source code }; } // namespace sf @@ -401,34 +537,44 @@ private : /// \class sf::Shader /// \ingroup graphics /// -/// Pixel shaders (or fragment shaders) are programs written -/// using a specific language, executed directly by the -/// graphics card and allowing to apply per-pixel real-time -/// operations to the rendered entities. +/// Shaders are programs written using a specific language, +/// executed directly by the graphics card and allowing +/// to apply real-time operations to the rendered entities. /// -/// Pixel shaders are written in GLSL, which is a C-like +/// There are two kinds of shaders: +/// \li Vertex shaders, that process vertices +/// \li Fragment (pixel) shaders, that process pixels +/// +/// A sf::Shader can be composed of either a vertex shader +/// alone, a fragment shader alone, or both combined +/// (see the variants of the Load functions). +/// +/// Shaders are written in GLSL, which is a C-like /// language dedicated to OpenGL shaders. You'll probably /// need to learn its basics before writing your own shaders /// for SFML. /// /// Like any C/C++ program, a shader has its own variables /// that you can set from your C++ application. sf::Shader -/// handles 3 different types of variables: +/// handles 4 different types of variables: /// \li floats /// \li vectors (2, 3 or 4 components) /// \li textures +/// \li transforms (matrices) /// /// The value of the variables can be changed at any time -/// with either Shader::SetParameter or Shader::SetTexture: +/// with either the various overloads of the SetParameter function: /// \code /// shader.SetParameter("offset", 2.f); /// shader.SetParameter("color", 0.5f, 0.8f, 0.3f); -/// shader.SetTexture("overlay", texture); // texture is a sf::Texture -/// shader.SetCurrentTexture("texture"); +/// shader.SetParameter("matrix", transform); // transform is a sf::Transform +/// shader.SetParameter("overlay", texture); // texture is a sf::Texture +/// shader.SetParameter("texture", sf::Shader::CurrentTexture); /// \endcode /// -/// Shader::SetCurrentTexture maps the given texture variable -/// to the current texture of the object being drawn. +/// The special Shader::CurrentTexture argument maps the +/// given texture variable to the current texture of the +/// object being drawn (which cannot be known in advance). /// /// To apply a shader to a drawable, you must pass it as an /// additional parameter to the Draw function: @@ -443,13 +589,15 @@ private : /// window.Draw(sprite, states); /// \endcode /// -/// Shaders can be used on any drawable, but they are mainly -/// made for sprites and shapes. Using a shader on a sf::String -/// is more limited, because the texture of the text is not the -/// actual text that you see on screen, it is a big texture -/// containing all the characters of the font in an arbitrary -/// order. Thus, texture lookups on pixels other than the current -/// one may not give you the expected result. +/// Shaders can be used on any drawable, but some combinations are +/// not interesting. For example, using a vertex shader on a sf::Sprite +/// is limited because there are only 4 vertices, the sprite would +/// have to be subdivided in order to apply wave effects. +/// Another bad example is a fragment shader with sf::Text: the texture +/// of the text is not the actual text that you see on screen, it is +/// a big texture containing all the characters of the font in an +/// arbitrary order; thus, texture lookups on pixels other than the +/// current one may not give you the expected result. /// /// Shaders can also be used to apply global post-effects to the /// current contents of the target (like the old sf::PostFx class @@ -466,8 +614,8 @@ private : /// easily inserted anywhere without impacting all the code. /// /// Like sf::Texture that can be used as a raw OpenGL texture, -/// sf::Shader can also be used directly as a raw fragment -/// shader for custom OpenGL geometry. +/// sf::Shader can also be used directly as a raw shader for +/// custom OpenGL geometry. /// \code /// window.SetActive(); /// shader.Bind(); diff --git a/src/SFML/Graphics/Shader.cpp b/src/SFML/Graphics/Shader.cpp index ea0243d3..8847b0fa 100644 --- a/src/SFML/Graphics/Shader.cpp +++ b/src/SFML/Graphics/Shader.cpp @@ -27,6 +27,7 @@ // Headers //////////////////////////////////////////////////////////// #include +#include #include #include #include @@ -46,30 +47,50 @@ namespace GLCheck(glGetIntegerv(GL_MAX_TEXTURE_COORDS_ARB, &maxUnits)); return maxUnits; } + + // Read the contents of a file into an array of char + bool GetFileContents(const std::string& filename, std::vector& buffer) + { + std::ifstream file(filename.c_str(), std::ios_base::binary); + if (file) + { + file.seekg(0, std::ios_base::end); + std::streamsize size = file.tellg(); + file.seekg(0, std::ios_base::beg); + buffer.resize(size); + file.read(&buffer[0], size); + buffer.push_back('\0'); + return true; + } + else + { + return false; + } + } + + // Read the contents of a stream into an array of char + bool GetStreamContents(sf::InputStream& stream, std::vector& buffer) + { + sf::Int64 size = stream.GetSize(); + buffer.resize(static_cast(size)); + sf::Int64 read = stream.Read(&buffer[0], size); + buffer.push_back('\0'); + return read == size; + } } namespace sf { +//////////////////////////////////////////////////////////// +Shader::CurrentTextureType Shader::CurrentTexture; + + //////////////////////////////////////////////////////////// Shader::Shader() : myShaderProgram (0), myCurrentTexture(-1) { - -} - - -//////////////////////////////////////////////////////////// -Shader::Shader(const Shader& copy) : -myShaderProgram (0), -myCurrentTexture(copy.myCurrentTexture), -myTextures (copy.myTextures), -myFragmentShader(copy.myFragmentShader) -{ - // Create the shaders and the program - if (copy.myShaderProgram) - CompileProgram(); } @@ -85,48 +106,107 @@ Shader::~Shader() //////////////////////////////////////////////////////////// -bool Shader::LoadFromFile(const std::string& filename) +bool Shader::LoadFromFile(const std::string& filename, Type type) { - // Open the file - std::ifstream file(filename.c_str()); - if (!file) + // Read the file + std::vector shader; + if (!GetFileContents(filename, shader)) { Err() << "Failed to open shader file \"" << filename << "\"" << std::endl; return false; } - // Read the shader code from the file - myFragmentShader.clear(); - std::string line; - while (std::getline(file, line)) - myFragmentShader += line + "\n"; - - // Create the shaders and the program - return CompileProgram(); + // Compile the shader program + if (type == Vertex) + return CompileProgram(&shader[0], NULL); + else + return CompileProgram(NULL, &shader[0]); } //////////////////////////////////////////////////////////// -bool Shader::LoadFromMemory(const std::string& shader) +bool Shader::LoadFromFile(const std::string& vertexShaderFilename, const std::string& fragmentShaderFilename) { - // Save the shader code - myFragmentShader = shader; + // Read the vertex shader file + std::vector vertexShader; + if (!GetFileContents(vertexShaderFilename, vertexShader)) + { + Err() << "Failed to open vertex shader file \"" << vertexShaderFilename << "\"" << std::endl; + return false; + } - // Create the shaders and the program - return CompileProgram(); + // Read the fragment shader file + std::vector fragmentShader; + if (!GetFileContents(fragmentShaderFilename, fragmentShader)) + { + Err() << "Failed to open fragment shader file \"" << fragmentShaderFilename << "\"" << std::endl; + return false; + } + + // Compile the shader program + return CompileProgram(&vertexShader[0], &fragmentShader[0]); } //////////////////////////////////////////////////////////// -bool Shader::LoadFromStream(InputStream& stream) +bool Shader::LoadFromMemory(const std::string& shader, Type type) +{ + // Compile the shader program + if (type == Vertex) + return CompileProgram(shader.c_str(), NULL); + else + return CompileProgram(NULL, shader.c_str()); +} + + +//////////////////////////////////////////////////////////// +bool Shader::LoadFromMemory(const std::string& vertexShader, const std::string& fragmentShader) +{ + // Compile the shader program + return CompileProgram(vertexShader.c_str(), fragmentShader.c_str()); +} + + +//////////////////////////////////////////////////////////// +bool Shader::LoadFromStream(InputStream& stream, Type type) { // Read the shader code from the stream - std::vector buffer(static_cast(stream.GetSize())); - Int64 read = stream.Read(&buffer[0], buffer.size()); - myFragmentShader.assign(&buffer[0], &buffer[0] + read); + std::vector shader; + if (!GetStreamContents(stream, shader)) + { + Err() << "Failed to read shader from stream" << std::endl; + return false; + } - // Create the shaders and the program - return CompileProgram(); + // Compile the shader program + if (type == Vertex) + return CompileProgram(&shader[0], NULL); + else + return CompileProgram(NULL, &shader[0]); +} + + +//////////////////////////////////////////////////////////// +bool Shader::LoadFromStream(InputStream& vertexShaderStream, InputStream& fragmentShaderStream) +{ + // Read the vertex shader code from the stream + std::vector vertexShader; + if (!GetStreamContents(vertexShaderStream, vertexShader)) + { + Err() << "Failed to read vertex shader from stream" << std::endl; + return false; + } + + // Read the fragment shader code from the stream + std::vector fragmentShader; + if (!GetStreamContents(fragmentShaderStream, fragmentShader)) + { + Err() << "Failed to read fragment shader from stream" << std::endl; + return false; + } + + // Compile the shader program + return CompileProgram(&vertexShader[0], &fragmentShader[0]); } @@ -241,7 +321,38 @@ void Shader::SetParameter(const std::string& name, const Vector3f& v) //////////////////////////////////////////////////////////// -void Shader::SetTexture(const std::string& name, const Texture& texture) +void Shader::SetParameter(const std::string& name, const Color& color) +{ + SetParameter(name, color.r / 255.f, color.g / 255.f, color.b / 255.f, color.a / 255.f); +} + + +//////////////////////////////////////////////////////////// +void Shader::SetParameter(const std::string& name, const sf::Transform& transform) +{ + if (myShaderProgram) + { + EnsureGlContext(); + + // Enable program + GLhandleARB program = glGetHandleARB(GL_PROGRAM_OBJECT_ARB); + GLCheck(glUseProgramObjectARB(myShaderProgram)); + + // Get parameter location and assign it new values + GLint location = glGetUniformLocationARB(myShaderProgram, name.c_str()); + if (location != -1) + GLCheck(glUniformMatrix4fvARB(location, 1, GL_FALSE, transform.GetMatrix())); + else + Err() << "Parameter \"" << name << "\" not found in shader" << std::endl; + + // Disable program + GLCheck(glUseProgramObjectARB(program)); + } +} + + +//////////////////////////////////////////////////////////// +void Shader::SetParameter(const std::string& name, const Texture& texture) { if (myShaderProgram) { @@ -279,7 +390,7 @@ void Shader::SetTexture(const std::string& name, const Texture& texture) //////////////////////////////////////////////////////////// -void Shader::SetCurrentTexture(const std::string& name) +void Shader::SetParameter(const std::string& name, CurrentTextureType) { if (myShaderProgram) { @@ -322,20 +433,6 @@ void Shader::Unbind() const } -//////////////////////////////////////////////////////////// -Shader& Shader::operator =(const Shader& right) -{ - Shader temp(right); - - std::swap(myShaderProgram, temp.myShaderProgram); - std::swap(myCurrentTexture, temp.myCurrentTexture); - std::swap(myTextures, temp.myTextures); - std::swap(myFragmentShader, temp.myFragmentShader); - - return *this; -} - - //////////////////////////////////////////////////////////// bool Shader::IsAvailable() { @@ -352,7 +449,7 @@ bool Shader::IsAvailable() //////////////////////////////////////////////////////////// -bool Shader::CompileProgram() +bool Shader::CompileProgram(const char* vertexShaderCode, const char* fragmentShaderCode) { EnsureGlContext(); @@ -368,74 +465,73 @@ bool Shader::CompileProgram() if (myShaderProgram) GLCheck(glDeleteObjectARB(myShaderProgram)); - // Define the vertex shader source (we provide it directly as it doesn't have to change) - static const char* vertexSrc = - "void main()" - "{" - " gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0;" - " gl_FrontColor = gl_Color;" - " gl_Position = ftransform();" - "}"; - // Create the program myShaderProgram = glCreateProgramObjectARB(); - // Create the shaders - GLhandleARB vertexShader = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB); - GLhandleARB fragmentShader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB); - - // Compile them - const char* fragmentSrc = myFragmentShader.c_str(); - GLCheck(glShaderSourceARB(vertexShader, 1, &vertexSrc, NULL)); - GLCheck(glShaderSourceARB(fragmentShader, 1, &fragmentSrc, NULL)); - GLCheck(glCompileShaderARB(vertexShader)); - GLCheck(glCompileShaderARB(fragmentShader)); - - // Check the compile logs - GLint success; - GLCheck(glGetObjectParameterivARB(vertexShader, GL_OBJECT_COMPILE_STATUS_ARB, &success)); - if (success == GL_FALSE) + // Create the vertex shader if needed + if (vertexShaderCode) { - char log[1024]; - GLCheck(glGetInfoLogARB(vertexShader, sizeof(log), 0, log)); - Err() << "Failed to compile shader:" << std::endl - << log << std::endl; + // Create and compile the shader + GLhandleARB vertexShader = glCreateShaderObjectARB(GL_VERTEX_SHADER_ARB); + GLCheck(glShaderSourceARB(vertexShader, 1, &vertexShaderCode, NULL)); + GLCheck(glCompileShaderARB(vertexShader)); + + // Check the compile log + GLint success; + GLCheck(glGetObjectParameterivARB(vertexShader, GL_OBJECT_COMPILE_STATUS_ARB, &success)); + if (success == GL_FALSE) + { + char log[1024]; + GLCheck(glGetInfoLogARB(vertexShader, sizeof(log), 0, log)); + Err() << "Failed to compile vertex shader:" << std::endl + << log << std::endl; + GLCheck(glDeleteObjectARB(vertexShader)); + GLCheck(glDeleteObjectARB(myShaderProgram)); + myShaderProgram = 0; + return false; + } + + // Attach the shader to the program, and delete it (not needed anymore) + GLCheck(glAttachObjectARB(myShaderProgram, vertexShader)); GLCheck(glDeleteObjectARB(vertexShader)); - GLCheck(glDeleteObjectARB(fragmentShader)); - GLCheck(glDeleteObjectARB(myShaderProgram)); - myShaderProgram = 0; - return false; - } - GLCheck(glGetObjectParameterivARB(fragmentShader, GL_OBJECT_COMPILE_STATUS_ARB, &success)); - if (success == GL_FALSE) - { - char log[1024]; - GLCheck(glGetInfoLogARB(fragmentShader, sizeof(log), 0, log)); - Err() << "Failed to compile shader:" << std::endl - << log << std::endl; - GLCheck(glDeleteObjectARB(vertexShader)); - GLCheck(glDeleteObjectARB(fragmentShader)); - GLCheck(glDeleteObjectARB(myShaderProgram)); - myShaderProgram = 0; - return false; } - // Attach the shaders to the program - GLCheck(glAttachObjectARB(myShaderProgram, vertexShader)); - GLCheck(glAttachObjectARB(myShaderProgram, fragmentShader)); + // Create the fragment shader if needed + if (fragmentShaderCode) + { + // Create and compile the shader + GLhandleARB fragmentShader = glCreateShaderObjectARB(GL_FRAGMENT_SHADER_ARB); + GLCheck(glShaderSourceARB(fragmentShader, 1, &fragmentShaderCode, NULL)); + GLCheck(glCompileShaderARB(fragmentShader)); - // We can now delete the shaders - GLCheck(glDeleteObjectARB(vertexShader)); - GLCheck(glDeleteObjectARB(fragmentShader)); + // Check the compile log + GLint success; + GLCheck(glGetObjectParameterivARB(fragmentShader, GL_OBJECT_COMPILE_STATUS_ARB, &success)); + if (success == GL_FALSE) + { + char log[1024]; + GLCheck(glGetInfoLogARB(fragmentShader, sizeof(log), 0, log)); + Err() << "Failed to compile fragment shader:" << std::endl + << log << std::endl; + GLCheck(glDeleteObjectARB(fragmentShader)); + GLCheck(glDeleteObjectARB(myShaderProgram)); + myShaderProgram = 0; + return false; + } + + // Attach the shader to the program, and delete it (not needed anymore) + GLCheck(glAttachObjectARB(myShaderProgram, fragmentShader)); + GLCheck(glDeleteObjectARB(fragmentShader)); + } // Link the program GLCheck(glLinkProgramARB(myShaderProgram)); - // Get link log + // Check the link log + GLint success; GLCheck(glGetObjectParameterivARB(myShaderProgram, GL_OBJECT_LINK_STATUS_ARB, &success)); if (success == GL_FALSE) { - // Oops... link errors char log[1024]; GLCheck(glGetInfoLogARB(myShaderProgram, sizeof(log), 0, log)); Err() << "Failed to link shader:" << std::endl @@ -466,11 +562,4 @@ void Shader::BindTextures() const GLCheck(glActiveTextureARB(GL_TEXTURE0_ARB)); } - -//////////////////////////////////////////////////////////// -void Shader::Use() const -{ - BindTextures(); -} - } // namespace sf