diff --git a/examples/shader/CMakeLists.txt b/examples/shader/CMakeLists.txt index 5b610d4d8..acb1bac8d 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 000000000..cb5b36689 --- /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 e8456fd41..4451c301d 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 000000000..07c8ddb29 --- /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 bde2fa655..c40e5b3f4 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 eeb407c8e..000000000 --- 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 000000000..6b1cbc85b Binary files /dev/null and b/examples/shader/resources/devices.png differ diff --git a/examples/shader/resources/edge.sfx b/examples/shader/resources/edge.frag similarity index 81% rename from examples/shader/resources/edge.sfx rename to examples/shader/resources/edge.frag index d944c5832..173a1e71c 100644 --- a/examples/shader/resources/edge.sfx +++ b/examples/shader/resources/edge.frag @@ -1,5 +1,5 @@ uniform sampler2D texture; -uniform float threshold; +uniform float edge_threshold; void main() { @@ -23,9 +23,10 @@ void main() vec3 result = sqrt(hEdge.rgb * hEdge.rgb + vEdge.rgb * vEdge.rgb); float edge = length(result); - if (edge > 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 97e3f8e45..000000000 --- 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 cde0473a7..000000000 --- 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 12a334e79..3c8eb0e05 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 000000000..1da719ff0 Binary files /dev/null and b/examples/shader/resources/sfml.png differ diff --git a/examples/shader/resources/sprite.png b/examples/shader/resources/sprite.png deleted file mode 100644 index 7b508f681..000000000 Binary files a/examples/shader/resources/sprite.png and /dev/null differ diff --git a/examples/shader/resources/storm.vert b/examples/shader/resources/storm.vert new file mode 100644 index 000000000..442f80a9a --- /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 000000000..c86e9b6c0 Binary files /dev/null and b/examples/shader/resources/text-background.png differ diff --git a/examples/shader/resources/wave.jpg b/examples/shader/resources/wave.jpg deleted file mode 100644 index cd125b8c8..000000000 Binary files a/examples/shader/resources/wave.jpg and /dev/null differ diff --git a/examples/shader/resources/wave.sfx b/examples/shader/resources/wave.sfx deleted file mode 100644 index ec5aaf759..000000000 --- a/examples/shader/resources/wave.sfx +++ /dev/null @@ -1,12 +0,0 @@ -uniform sampler2D texture; -uniform sampler2D wave; -uniform vec2 offset; - -void main() -{ - vec2 texoffset = vec2(texture2D(wave, (gl_TexCoord[0].xy * offset).xy)); - texoffset -= vec2(0.5, 0.5); - texoffset *= 0.05; - - gl_FragColor = texture2D(texture, gl_TexCoord[0].xy + texoffset) * gl_Color; -} diff --git a/examples/shader/resources/wave.vert b/examples/shader/resources/wave.vert new file mode 100644 index 000000000..5089cfbbd --- /dev/null +++ b/examples/shader/resources/wave.vert @@ -0,0 +1,15 @@ +uniform float wave_phase; +uniform vec2 wave_amplitude; + +void main() +{ + vec4 vertex = gl_Vertex; + vertex.x += cos(gl_Vertex.y * 0.02 + wave_phase * 3.8) * wave_amplitude.x + + sin(gl_Vertex.y * 0.02 + wave_phase * 6.3) * wave_amplitude.x * 0.3; + vertex.y += sin(gl_Vertex.x * 0.02 + wave_phase * 2.4) * wave_amplitude.y + + cos(gl_Vertex.x * 0.02 + wave_phase * 5.2) * wave_amplitude.y * 0.3; + + gl_Position = gl_ModelViewProjectionMatrix * vertex; + gl_TexCoord[0] = gl_TextureMatrix[0] * gl_MultiTexCoord0; + gl_FrontColor = gl_Color; +} diff --git a/include/SFML/Graphics/Shader.hpp b/include/SFML/Graphics/Shader.hpp index e91d538c9..07640c627 100644 --- a/include/SFML/Graphics/Shader.hpp +++ b/include/SFML/Graphics/Shader.hpp @@ -29,8 +29,10 @@ // Headers //////////////////////////////////////////////////////////// #include -#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 ea0243d31..8847b0faf 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