I would like to replicate this in SFML, so I wrote this:
Main.cpp
#include <SFML/Graphics.hpp>
#define WIDTH 800
int main() {
sf::RenderWindow window(sf::VideoMode(WIDTH, WIDTH), "Test");
sf::Shader shader, buffer;
shader.loadFromFile("shader.frag", sf::Shader::Fragment);
buffer.loadFromFile("buffer.frag", sf::Shader::Fragment);
sf::Shader *shaders[] = {&shader, &buffer};
sf::Texture texture;
texture.create(WIDTH, WIDTH);
sf::RenderTexture renderTexture;
sf::Vector2f mousePos = sf::Vector2f(-1.f, -1.f);
for(auto &shader : shaders) {
shader->setUniform("iChannel0", texture);
shader->setUniform("iResolution", sf::Glsl::Vec2(WIDTH, WIDTH));
}
auto time = sf::Clock();
sf::Sprite sprite(texture);
while(window.isOpen()) {
sf::Event currEvent;
while(window.pollEvent(currEvent)) {
switch(currEvent.type) {
case(sf::Event::Closed): window.close(); break;
case(sf::Event::MouseMoved):
mousePos = sf::Vector2f(
currEvent.mouseMove.x, currEvent.mouseMove.y);
break;
}
}
buffer.setUniform("iTime", time.getElapsedTime().asSeconds());
buffer.setUniform("iMouse", sf::Glsl::Vec2(
mousePos.x, WIDTH - mousePos.y));
sf::RenderStates states;
states.blendMode = sf::BlendAdd;
states.shader = &buffer;
renderTexture.clear(sf::Color::Transparent);
renderTexture.draw(sf::Sprite(texture), states);
renderTexture.display();
sprite.setTexture(renderTexture.getTexture());
window.clear(sf::Color::Black);
window.draw(sprite, &shader);
window.display();
}
}
Buffer.frag
#version 140
out vec4 out_Colour;
uniform sampler2D iChannel0;
uniform vec2 iResolution;
uniform vec2 iMouse;
uniform float iTime;
void main(void){
vec2 mouse = iMouse.xy;
if(mouse.x <= 0.)
mouse = vec2(iResolution.x * (sin(iTime)+1.)/2., iResolution.y/2.);
vec3 blob = vec3(.11-clamp(
length((
gl_FragCoord.xy-mouse.xy)/iResolution.x),0.,.11))*2.;
vec3 stack = texture(
iChannel0,gl_FragCoord.xy/iResolution.xy).xyz * vec3(0.99,.982,.93);
out_Colour = vec4(stack + blob, 1.0);
}
Shader.frag
#version 140
out vec4 out_Colour;
uniform sampler2D iChannel0;
uniform vec2 iResolution;
void main(void){
out_Colour = vec4(texture(
iChannel0, gl_FragCoord.xy / iResolution.xy).xyz, 1.0);
}
I am probably missing something with the buffers.
Similar questions without clear answer:
shadertoy to SFML
In the following example I am trying to have a trailing glow by using sf::RenderTexture. Unfortunately, the texture seem to be mirrored. I don't understand why.
Main program
#include <SFML/Graphics.hpp>
#include <iostream>
#define WIDTH 800
int main() {
sf::RenderWindow window(sf::VideoMode(WIDTH, WIDTH), "Test");
sf::Shader buffer;
buffer.loadFromFile("buffer.frag", sf::Shader::Fragment);
sf::Texture texture;
texture.create(WIDTH, WIDTH);
sf::Sprite sprite(texture);
sf::RenderTexture renderTexture;
renderTexture.create(WIDTH, WIDTH);
sf::Vector2f mousePos = sf::Vector2f(-1.f, -1.f);
buffer.setUniform("iChannel0", texture);
buffer.setUniform("iResolution", sf::Glsl::Vec2(WIDTH, WIDTH));
auto time = sf::Clock();
window.clear();
while(window.isOpen()) {
sf::Event currEvent;
while(window.pollEvent(currEvent))
if (currEvent.type == sf::Event::Closed) window.close();
buffer.setUniform("iTime", (float)time.getElapsedTime().asSeconds());
renderTexture.clear();
renderTexture.draw(sprite, &buffer);
renderTexture.display();
texture.update(renderTexture.getTexture());
window.clear();
window.draw(sprite);
window.display();
}
}
Fragment Shader
#version 140
out vec4 out_Colour;
uniform sampler2D iChannel0;
uniform vec2 iResolution;
uniform float iTime;
void main(void) {
vec2 spot = vec2(sin(iTime*5)*200+400, cos(iTime*15.0)*70+150);
vec3 color = vec3(sin(iTime*5), sin(iTime*1), sin(iTime*10)) * 0.5 + 0.5;
vec2 uv = (gl_FragCoord.xy - spot.xy) / iResolution.xy;
vec3 blob = 1.0 - vec3(smoothstep(length(uv), 0, .02));
vec3 stack = texture(iChannel0, gl_FragCoord.xy / iResolution.xy).xyz;
stack -= vec3(0.003);
out_Colour = vec4(stack + color * blob* 0.1, 1.0);
}
I get this weird result where half the screen is mirrored:
But if I render directly the buffer I don't get this result:
window.clear();
window.draw(sprite, &buffer);
window.display();
texture.update(window);
I am trying to implement a spotlight (cone-shape) detection FOV for a game. I am currently calculating the lighting using the following function inside my fragment shader.
vec4 calculateLight(Light light)
{
float aspectRatio = resolution.x / resolution.y; //amt of width / height
if (aspectRatio > 1.0)
light.radius.x /= aspectRatio;
else
light.radius.x /= aspectRatio;
vec2 lightDir = fragmentPosition.xy - light.position.xy;
float lightDistance = length(lightDir);
if (length(lightDir / light.radius) >= 1.0)
return vec4(0, 0, 0, 1); //outside of radius make it black
if (dot(normalize(lightDir), normalize(light.spotDir.xy)) < cos(light.spotAngle/2))
return vec4(0, 0, 0, 1); //outside of radius make it black
return light.intensity * (1 - length(lightDir / light.radius)) * light.colour;
}
However, I would also like to use my light as something like a detection tool, which represents the enemy's line of sight, is there a way for me to do that? My LightComponent:
struct LightComponent
{
Vec4 colour;
Vec3 pos;
Vec2 radius;
float intensity;
bool lightEnabled;
//spotlight stuff
float spotAngle;
Vec3 spotDir;
float DirectionAngle;
bool takeVector = true;
static Vec4 ambientLight;
LightComponent();
void ImGuiDraw();
};
Range here is the radius.
I want to create a glow effect for my game. In order to keep this minimalistic let's say I want to glow an Image. :)
Starting with this one:
To get something like this:
It's a three step way.
save all bright pixels from the scene (= luminescence)
Apply a blur effect on those pixels (= blur)
draw original picture and the blur texture ontop (= assemble)
Step 1 and 3 are no Problem. The blur part just doesn't want to work correctly.
Before I explain further, here's my luminescence result:
(threshold = 0.67f)
An now when I blur this, I get some unlucky results:
This black edge comes from the fact, that any transparent color is black vec4(0.0, 0.0, 0.0, 0.0). It's not an unkown Problem within SFML/GLSL, and the suggestion was to use SFML's sf::BlendMode for this and multiply the .rgb value of the final pixel color in the fragment shader with its alpha value. So I did and now this my result:
It's better, but definetely not good. The blur shader now also avarages out the surrounding pixels of the luminescence mask. After assembling it's just a blurry picture:
.. I tried "fixing" this in the shader files by checking if the pixel's alpha is zero. This way I don't value them when avaraging out. But since sf::BlendMode is activated, I don't know how alpha behaves now - So I deactivated the blendmode but I still have weird results. (at the very of this question I provided the code and a result from this attempt)
none of my attempts to fix this work. I really could use some help here. Maybe I'm doing something fundamentally wrong in the shaders.. here's the full code -
If you want to compile it, make a folder resources with the 2 Fragment shaders and the background.jpg (in 1280x720).
luminescence.frag
#version 120
uniform sampler2D texture;
uniform float threshold;
void main(void){
vec3 current_color = texture2D(texture, gl_TexCoord[0].xy).rgb;
vec4 pixel = vec4(current_color.rgb, 0.0);
float brightness = dot(current_color.rgb, vec3(0.2126, 0.7152, 0.0722));
if (brightness >= threshold){
pixel = texture2D(texture, gl_TexCoord[0].xy);
}
gl_FragColor = pixel;
}
boxblur.frag
#version 120
uniform sampler2D texture;
uniform float texture_inverse;
uniform int blur_radius;
uniform vec2 blur_direction;
void main(void){
vec4 sum = texture2D(texture, gl_TexCoord[0].xy);
for (int i = 0; i < blur_radius; ++i){
sum += texture2D(texture, gl_TexCoord[0].xy + (i * texture_inverse) * blur_direction);
sum += texture2D(texture, gl_TexCoord[0].xy - (i * texture_inverse) * blur_direction);
}
vec4 pixel = vec4(sum / (blur_radius * 2 + 1));
pixel.rgb *= pixel.a;
gl_FragColor = pixel;
}
main.cpp
#include <SFML/Graphics.hpp>
#include <iostream>
#include <exception>
void run() {
const sf::Vector2f SIZE(1280, 720);
sf::Texture background_tex;
background_tex.loadFromFile("resources/background.jpg");
sf::Sprite background(background_tex);
sf::Shader luminescence;
luminescence.loadFromFile("resources/luminescence.frag", sf::Shader::Fragment);
luminescence.setUniform("texture", sf::Shader::CurrentTexture);
luminescence.setUniform("threshold", 0.67f);
sf::Shader blur;
blur.loadFromFile("resources/boxblur.frag", sf::Shader::Fragment);
blur.setUniform("texture", sf::Shader::CurrentTexture);
blur.setUniform("texture_inverse", 1.0f / SIZE.x);
sf::RenderStates shader_states;
shader_states.blendMode = sf::BlendMode(sf::BlendMode::One, sf::BlendMode::OneMinusSrcAlpha);
sf::ContextSettings context_settings;
context_settings.antialiasingLevel = 12;
//draws background
sf::RenderTexture scene_render;
scene_render.create(SIZE.x, SIZE.y, context_settings);
//draws luminescence and blur
sf::RenderTexture shader_render;
shader_render.create(SIZE.x, SIZE.y, context_settings);
sf::RenderWindow window(sf::VideoMode(SIZE.x, SIZE.y), "glsl fun", sf::Style::Default, context_settings);
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed) {
window.close();
}
}
scene_render.clear();
scene_render.draw(background);
scene_render.display();
//apply luminescence
shader_states.shader = &luminescence;
shader_render.clear(sf::Color::Transparent);
shader_render.draw(sf::Sprite(scene_render.getTexture()), shader_states);
shader_render.display();
//apply two pass gaussian blur 3 times to simulate gaussian blur.
shader_states.shader = &blur;
float blur_radius = 30.0f;
for (int i = 0; i < 3; ++i) {
blur.setUniform("blur_radius", static_cast<int>(blur_radius));
//vertical blur
blur.setUniform("blur_direction", sf::Glsl::Vec2(1.0, 0.0));
shader_render.draw(sf::Sprite(shader_render.getTexture()), shader_states);
shader_render.display();
//horizontal blur
blur.setUniform("blur_direction", sf::Glsl::Vec2(0.0, 1.0));
shader_render.draw(sf::Sprite(shader_render.getTexture()), shader_states);
shader_render.display();
//decrease blur_radius to simulate a gaussian blur
blur_radius *= 0.45f;
}
//assembly
window.clear();
window.draw(sf::Sprite(scene_render.getTexture()));
window.draw(sf::Sprite(shader_render.getTexture()));
window.display();
}
}
int main() {
try {
run();
}
catch (std::exception e) {
std::cerr << "caught exception - - - " << e.what() << '\n';
return 1;
}
return 0;
}
This is the boxblur.frag where I tried to exclude zero alpha values: (I removed shader_states.blendMode = sf::BlendMode(sf::BlendMode::One, sf::BlendMode::OneMinusSrcAlpha);on line 29 in main.cpp of course):
#version 120
uniform sampler2D texture;
uniform float texture_inverse;
uniform int blur_radius;
uniform vec2 blur_direction;
void main(void){
float div = 0.0;
vec4 sum = vec4(0.0, 0.0, 0.0, 0.0);
vec4 temp_color = texture2D(texture, gl_TexCoord[0].xy);
if (temp_color.a > 0.0){
sum += temp_color;
div += 1.0;
}
for (int i = 0; i < blur_radius; ++i){
temp_color = texture2D(texture, gl_TexCoord[0].xy + (i * texture_inverse) * blur_direction);
if (temp_color.a > 0.0){
sum += temp_color;
div += 1.0;
}
temp_color = texture2D(texture, gl_TexCoord[0].xy - (i * texture_inverse) * blur_direction);
if (temp_color.a > 0.0){
sum += temp_color;
div += 1.0;
}
}
vec4 pixel;
if (div == 0.0){
pixel = vec4(texture2D(texture, gl_TexCoord[0].xy).rgb, 0.0);
}
else{
pixel = vec4(sum / div);
}
gl_FragColor = pixel;
}
Resulting in:
[I am using Visual Studio 2017 Community] - Thanks for any help!
I posted this question also on en.sfml-dev.org (here) and fallahn showed me the correct approach.
Before tackling that, here are the picture results:
Luminescence (threshold = 0.24f):
Blur (4 layers):
Assembled:
Yay! The solution is to set all transparent pixels to solid black vec4(0.0, 0.0, 0.0, 1.0) and than after they've been blurred, just add them ontop the Scene:
vec4 tex_color = texture2D(texture, gl_TexCoord[0].xy);
vec4 add_color = texture2D(add_texture, gl_TexCoord[0].xy);
gl_FragColor = tex_color + add_color;
This way, if add_color is black ("transparent"), we add tex_color + vec4(0.0, 0.0, 0.0, 1.0) which results in no change!
This is great, because now you can ignore the alpha channel completely.
To understand why I find this so great, you can read this little rant here (feel free to skip it):
Without worrying about alpha you can ignore any sf::BlendMode like the confusing sf::BlendMode::OneMinusSrcAlpha which caused me headaches for 2 solid days. Try calculating any reasonable "true" alpha value when you know they are all premultiplied. Of course you also have to multiply all rgb values with the pixel's alpha to reverse the prumultiplication… the formulas escalate quite quickly from here. Also subtract 1 from alpha because it's OneMinusSrcAlpha... and don't forget to check for cases where the sum of all alphas (yes, you need to sum that) is 0 (or in OneMinusSrcAlpha matter, something else), because otherwise you get division by 0 (or in OneMinusSrcAlpha matter a division by 0 when all surrounding pixels are solid). Also sometimes weird alpha values might work, but only for a single pass of blur, but in my case I have multiple passes.. etc.
Here's the final code:
luminescence.frag
#version 120
uniform sampler2D texture;
uniform float threshold;
void main(void){
vec3 current_color = texture2D(texture, gl_TexCoord[0].xy).rgb;
vec4 pixel = vec4(0.0, 0.0, 0.0, 1.0);
float brightness = dot(current_color.rgb, vec3(0.2126, 0.7152, 0.0722));
if (brightness >= threshold){
pixel = texture2D(texture, gl_TexCoord[0].xy);
}
gl_FragColor = pixel;
}
boxblur.frag
#version 120
uniform sampler2D texture;
uniform float texture_inverse;
uniform int blur_radius;
uniform vec2 blur_direction;
void main(void){
vec4 sum = texture2D(texture, gl_TexCoord[0].xy);
for (int i = 0; i < blur_radius; ++i){
sum += texture2D(texture, gl_TexCoord[0].xy + (i * texture_inverse) * blur_direction);
sum += texture2D(texture, gl_TexCoord[0].xy - (i * texture_inverse) * blur_direction);
}
gl_FragColor = sum / (blur_radius * 2 + 1);
}
multiply.frag
#version 120
uniform sampler2D texture;
uniform float multiply;
void main(void){
gl_FragColor = texture2D(texture, gl_TexCoord[0].xy) * multiply;
}
assemble.frag
#version 120
uniform sampler2D texture;
uniform sampler2D add_texture;
uniform float add_weight;
void main(void){
vec4 tex_color = texture2D(texture, gl_TexCoord[0].xy);
vec4 add_color = texture2D(add_texture, gl_TexCoord[0].xy) * add_weight;
gl_FragColor = tex_color + add_color;
}
main.cpp
#include <SFML/Graphics.hpp>
#include <iostream>
#include <array>
void run() {
const sf::Vector2f SIZE(1280, 720);
sf::Texture background_tex;
background_tex.loadFromFile("resources/background.jpg");
sf::Sprite background(background_tex);
sf::Shader luminescence_shader;
luminescence_shader.loadFromFile("resources/luminescence.frag", sf::Shader::Fragment);
luminescence_shader.setUniform("texture", sf::Shader::CurrentTexture);
luminescence_shader.setUniform("threshold", 0.24f);
sf::Shader blur_shader;
blur_shader.loadFromFile("resources/boxblur.frag", sf::Shader::Fragment);
blur_shader.setUniform("texture", sf::Shader::CurrentTexture);
blur_shader.setUniform("texture_inverse", 1.0f / SIZE.x);
sf::Shader assemble_shader;
assemble_shader.loadFromFile("resources/assemble.frag", sf::Shader::Fragment);
assemble_shader.setUniform("texture", sf::Shader::CurrentTexture);
sf::Shader multiply_shader;
multiply_shader.loadFromFile("resources/multiply.frag", sf::Shader::Fragment);
multiply_shader.setUniform("texture", sf::Shader::CurrentTexture);
sf::RenderStates shader_states;
//no blendmode! we make our own - assemble.frag
sf::ContextSettings context_settings;
context_settings.antialiasingLevel = 12;
//draws background
sf::RenderTexture scene_render;
scene_render.create(SIZE.x, SIZE.y, context_settings);
sf::RenderTexture luminescence_render;
luminescence_render.create(SIZE.x, SIZE.y, context_settings);
//draws luminescence and blur
sf::RenderTexture assemble_render;
assemble_render.create(SIZE.x, SIZE.y, context_settings);
//addding multiple boxblurs with different radii looks really nice! in this case 4 layers
std::array<sf::RenderTexture, 4> blur_renders;
for (int i = 0; i < blur_renders.size(); ++i) {
blur_renders[i].create(SIZE.x, SIZE.y, context_settings);
}
const int BLUR_RADIUS_VALUES[] = { 250, 180, 125, 55 };
float blur_weight = blur_renders.empty() ? 0.0 : 1.0 / blur_renders.size();
sf::RenderWindow window(sf::VideoMode(SIZE.x, SIZE.y), "glsl fun", sf::Style::Default, context_settings);
while (window.isOpen()) {
sf::Event event;
while (window.pollEvent(event)) {
if (event.type == sf::Event::Closed) {
window.close();
}
}
//first draw the scene
scene_render.clear();
scene_render.draw(background);
scene_render.display();
//apply luminescence
shader_states.shader = &luminescence_shader;
luminescence_render.clear();
luminescence_render.draw(sf::Sprite(scene_render.getTexture()), shader_states);
luminescence_render.display();
//apply two pass gaussian blur n times to simulate gaussian blur.
shader_states.shader = &blur_shader;
for (int i = 0; i < blur_renders.size(); ++i) {
blur_shader.setUniform("blur_radius", BLUR_RADIUS_VALUES[i]);
blur_renders[i].clear();
blur_renders[i].draw(sf::Sprite(luminescence_render.getTexture()));
blur_renders[i].display();
//vertical blur
blur_shader.setUniform("blur_direction", sf::Glsl::Vec2(1.0, 0.0));
blur_renders[i].draw(sf::Sprite(blur_renders[i].getTexture()), shader_states);
blur_renders[i].display();
//horizontal blur
blur_shader.setUniform("blur_direction", sf::Glsl::Vec2(0.0, 1.0));
blur_renders[i].draw(sf::Sprite(blur_renders[i].getTexture()), shader_states);
blur_renders[i].display();
}
//load blur_renders[0] into assemble_render so we can add the other blurs ontop of it
shader_states.shader = &multiply_shader;
multiply_shader.setUniform("multiply", blur_weight);
assemble_render.clear();
assemble_render.draw(sf::Sprite(blur_renders[0].getTexture()), shader_states);
assemble_render.display();
//adding the rest ontop creating a final blur
shader_states.shader = &assemble_shader;
assemble_shader.setUniform("add_weight", blur_weight);
for (int i = 1; i < blur_renders.size(); ++i) {
assemble_shader.setUniform("add_texture", blur_renders[i].getTexture());
assemble_render.draw(sf::Sprite(assemble_render.getTexture()), shader_states);
assemble_render.display();
}
//final result; scene + blur
assemble_shader.setUniform("add_weight", 1.0f);
assemble_shader.setUniform("add_texture", assemble_render.getTexture());
assemble_render.draw(sf::Sprite(scene_render.getTexture()), shader_states);
assemble_render.display();
window.clear();
window.draw(sf::Sprite(assemble_render.getTexture()));
window.display();
}
}
int main() {
try {
run();
}
catch (std::exception e) {
std::cerr << "caught exception - - - " << e.what() << '\n';
return 1;
}
return 0;
}
Try to make a tiny example where you just want to average TWO pixels. Left (L) and right (R). Then the left pixel is made up of R(L), G(L), B(L), A(L) and the right pixel is made up of R(R), G(R), B(R) and A(R).
Without alpha, the averaging of Blue would just be:
(B(L)+B(R)) / 2
Taking alpha into account, it becomes:
(B(L)*A(L)+B(R)*A(R)) / (A(L)+A(R))
We can directly see that in the case of completely solid pixels (alpha=1) we get exactly the same formula as above:
(B(L)*1+B(R)*1) / (1+1) = (B(L)+B(R)) / 2
Furthermore, let's say that the right pixel is completely transparent and the left is solid, then the right pixel's color component won't affect anything, leaving the result exactly like the left pixel, which is exactly what we want:
(B(L)*1+B(R)*0) / (1+0) = (B(L)) / 1 = B(L)
Both pixels being completely transparent becomes a degenerate case which must be handled in some elegant way.
Now all you have to do is to extend this beyond two pixels. :-)
I have a Grey Scale Shader for the Shaders Mod for Minecraft. I have the final.fsh, and it works real nice with this resource pack:
"Craftboy Grey"
However, I'd like to modify the shader to show up in green, similar to the grey does.
"Craftboy Green"
The reason I need the shader, is to modify all the colors the resource pack cannot, and also change other player's skins to the same scale, without them needing to manually do it.
Here's the code for the shader:
// Grayscale shader by daxnitro.
// Small edit by Edrem
// It makes the green brighter =D
uniform sampler2D sampler0;
uniform sampler2D sampler1;
uniform sampler2D sampler2;
uniform float near;
uniform float far;
float getBrightness(vec4 color);
float getDepth(vec2 coord);
void applyEffect() {
float brightness = getBrightness(gl_FragColor);
gl_FragColor = vec4(brightness, brightness, brightness, gl_FragColor[3]);
}
void main() {
vec4 baseColor = texture2D(sampler0, gl_TexCoord[0].st);
gl_FragColor = baseColor;
float depth = getDepth(gl_TexCoord[0].st);
if (gl_FragColor[3] == 0.0) {
gl_FragColor = gl_Fog.color;
}
applyEffect();
}
float getBrightness(vec4 color) {
return color[0] * 0.299f + color[1] * 0.587f + color[2] * 0.114f;
}
float getDepth(vec2 coord) {
float depth = texture2D(sampler1, coord).x;
float depth2 = texture2D(sampler2, coord).x;
if (depth2 < 1.0) {
depth = depth2;
}
depth = 2.0 * near * far / (far + near - (2.0 * depth - 1.0) * (far - near));
return depth;
}
To make it "green scale" instead of grey scale, write the brightness only to the green component of the output:
void applyEffect() {
float brightness = getBrightness(gl_FragColor);
gl_FragColor = vec4(0.0, brightness, 0.0, gl_FragColor[3]);
}
If you want more overall brightness while still having the whole thing tinted green, you can add some brightness back in the red and blue components. For example:
gl_FragColor = vec4(brightness * vec3(0.5, 1.0, 0.5), gl_FragColor[3]);