Glusoft

Shader with SFML

In this tutorial I will show you how to make and use a fragment shader with SFML.
The goal is not to learn GLSL but to integrate a shader taken from Shadertoy or GLSL Sandbox into an SFML apps.

At the end of the tutorial you will have:
Fire shader SFML

A fragment shader is a type of shader program used in computer graphics, specifically within the graphics pipeline of a GPU (Graphics Processing Unit). It is responsible for determining the color and other attributes of each fragment, which can be thought of as a potential pixel on the screen.

The Shader code

Here is the code of the shader we will use:

#ifdef GL_ES
precision mediump float;
#endif

uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;

float snoise(vec3 uv, float res)
{
	const vec3 s = vec3(1e0, 1e2, 1e3);
	
	uv *= res;
	
	vec3 uv0 = floor(mod(uv, res))*s;
	vec3 uv1 = floor(mod(uv+vec3(1.), res))*s;
	
	vec3 f = fract(uv); f = f*f*(3.0-2.0*f);

	vec4 v = vec4(uv0.x+uv0.y+uv0.z, uv1.x+uv0.y+uv0.z,
		      	  uv0.x+uv1.y+uv0.z, uv1.x+uv1.y+uv0.z);

	vec4 r = fract(sin(v*1e-1)*1e3);
	float r0 = mix(mix(r.x, r.y, f.x), mix(r.z, r.w, f.x), f.y);
	
	r = fract(sin((v + uv1.z - uv0.z)*1e-1)*1e3);
	float r1 = mix(mix(r.x, r.y, f.x), mix(r.z, r.w, f.x), f.y);
	
	return mix(r0, r1, f.z)*2.-1.;
}

void main( void ) {	
	vec2 p = gl_FragCoord.xy / resolution.xy;
	p.x = p.x - mouse.x / resolution.x;
	p.y = p.y + mouse.y / resolution.y;
	p.y = p.y - 1.0;
	
	p.x*=resolution.x/resolution.y;			
			  	
	float color = 3.0 - (6.*length(p));		
	
	vec3 coord = vec3(atan(p.x,p.y)/6.2832, length(p)*0.4, .5);
	
	for(int i = 1; i <= 7; i++){
		float power = pow(2.0, float(i));
		color += (1.5 / power) * snoise(coord + vec3(0.,-time*.05, time*.01), power*16.);
	}
	
	gl_FragColor = vec4( color, pow(max(color,0.),2.)*0.4, pow(max(color,0.),3.)*0.15 , 1.0);
	
}

As you can see there is a lot of mathematics involved when we want to make a shader with some blur or in this case noise.

Another interesting things are the parameters we will need to give to the shader:

uniform float time;
uniform vec2 mouse;
uniform vec2 resolution;

time = the time elapsed since the beginning of the program (in seconds).
mouse = coordinate (x, y) of the cursor (in pixel).
resolution = resolution of the window (in pixel).

The Shader with SFML Apps

Now we need to create the window and load the fragment shader in the main. We will need to create a sprite to display the shader. And after that we will update the shader paramaters. Here is the source code.

#include <SFML/Graphics.hpp>
#include <iostream>

int main() {
	const float winW = 800;
	const float winH = 600;

	sf::RenderWindow window(sf::VideoMode(winW, winH), "SFML Shader Example");
	window.setMouseCursorVisible(false); // hide the cursor

	// Create a texture and a sprite for the shader
	sf::Texture tex;
	tex.create(winW, winH);
	sf::Sprite spr(tex);

	sf::Shader shader;
	shader.loadFromFile("fire.glsl", sf::Shader::Fragment); // load the shader

	if (!shader.isAvailable()) {
		std::cout << "The shader is not available\n";
	}

	// Set the resolution parameter (the resoltion is divided to make the fire smaller)
	shader.setParameter("resolution", sf::Vector2f(winW / 2, winH / 2));

	// Use a timer to obtain the time elapsed
	sf::Clock clk;
	clk.restart(); // start the timer

	while (window.isOpen()) {
		// Event handling
		sf::Event event;

		while (window.pollEvent(event)) {
			// Exit the app when a key is pressed
			if (event.type == sf::Event::KeyPressed) 
				window.close();
		}

		// Set the others parameters who need to be updated every frames
		shader.setParameter("time", clk.getElapsedTime().asSeconds());

		sf::Vector2i mousePos = sf::Mouse::getPosition(window);
		shader.setParameter("mouse", sf::Vector2f(mousePos.x, mousePos.y - winH/2));

		// Draw the sprite with the shader on it
		window.clear();
		window.draw(spr, &shader);
		window.display();
	}

	return 0;
}

You can download the project shader with SFML for VS2015: SFMLShaderExample.7z