New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Suggestion: Min and Max blend Equations #1710
Comments
Back in the thread you linked, the consensus was that ReverseSubtract would have a meaningful use case, but Min/Max would not -- and that SFML should offer the most useful ones, not mirror OpenGL 1:1. So do you think that Min/Max is something that you cannot easily solve otherwise? Could you maybe elaborate your use case with the lights a bit? Are lights rendered to a separate texture, and this is then rendered to the main screen using a sprite? |
The lighting I have committed uses a shader to calculate the distance between the center and the radius: https://github.com/dgengin/DGEngine/blob/master/src/Game/Level.cpp old lighting: passes an array with position (x, y), light intensity and radius. Then, it loops that array to calculate the lighting in the shader #version 110
uniform sampler2D texture;
uniform vec4 visibleRect;
uniform int numberOfLights;
uniform float lights[512];
uniform float defaultLight;
uniform float lightRadius;
uniform float elapsedTime;
void main()
{
float light = 1.0 - defaultLight;
if (numberOfLights > 0 && light > 0.0)
{
for(int i = 0; i < numberOfLights; i += 4)
{
vec2 coord = vec2(gl_TexCoord[0].x, 1.0 - gl_TexCoord[0].y);
vec2 pixelPos = visibleRect.xy + (visibleRect.zw * coord);
float dist = distance(pixelPos, vec2(lights[i], lights[i+1]));
dist = clamp(dist / lightRadius, 0.0, lights[i+3]) / lights[i+3];
light = clamp(dist, 0.0, light);
if (light == 0.0)
{
break;
}
}
}
vec4 pixel = texture2D(texture, gl_TexCoord[0].xy);
pixel.r = pixel.r - light;
pixel.g = pixel.g - light;
pixel.b = pixel.b - light;
gl_FragColor = pixel;
} Level.cpp // calls to RenderTexture.draw before this
sf::RenderStates states(sf::RenderStates::Default);
if (shader != nullptr)
{
states.shader = shader;
shader->setUniform("elapsedTime", game.getTotalElapsedTime().asSeconds());
if (hasMouseInside == true)
{
shader->setUniform("mousePosition", sf::Glsl::Vec2(
(game.MousePositionf().x - surface.Position().x) /
surface.Size().x,
(game.MousePositionf().y - surface.Position().y) /
surface.Size().y
));
}
shader->setUniform("textureSize", sf::Glsl::Vec2(
surface.Size().x,
surface.Size().y
));
shader->setUniform("visibleRect", sf::Glsl::Vec4(
surface.visibleRect.left,
surface.visibleRect.top,
surface.visibleRect.width,
surface.visibleRect.height
));
shader->setUniform("numberOfLights", (int)map.lightArray.size());
shader->setUniformArray("lights", map.lightArray.data(), map.lightArray.size());
shader->setUniform("defaultLight", ((float)map.getDefaultLight() / 255.f));
shader->setUniform("lightRadius", lightRadius * surface.getLightZoomFactor());
}
surface.draw(target, states); // calls RenderTexture.display() and uses a sprite to draw to the target new lighting using Min: draws an array of circles whose first vertex has color (0, 0, 0, 0) and all other vertexes have (0, 0, 0, light) and then uses a simple shader to calculate lighting based on the alpha channel and replaces alpha in the final renderTexture with 1. #version 110
uniform sampler2D texture;
uniform float defaultLight;
void main()
{
vec4 pixel = texture2D(texture, gl_TexCoord[0].xy);
float light = min(pixel.a, defaultLight);
pixel.r = max(pixel.r - light, 0.0);
pixel.g = max(pixel.g - light, 0.0);
pixel.b = max(pixel.b - light, 0.0);
pixel.a = 1.0;
gl_FragColor = pixel;
} update light circles: void Level::updateLights()
{
map.updateLights(levelObjects, currentMapViewCenter);
lights.clear();
for (const auto& light : map.AllLights())
{
auto radius = (float)light.lightSource.radius * lightRadius;
GradientCircle circle(radius, 9);
circle.setInnerColor(sf::Color(0, 0, 0, 0));
circle.setOuterColor(sf::Color(0, 0, 0, light.lightSource.light));
circle.setOrigin(radius, radius);
circle.setPosition(light.drawPos);
lights.push_back(std::move(circle));
}
} Level.cpp // calls to RenderTexture.draw before this
if (lights.empty() == false && map.getDefaultLight() < 255)
{
#if !defined(SFML_OPENGL_ES)
const static sf::BlendMode lightBlend(
sf::BlendMode::Zero, sf::BlendMode::One, sf::BlendMode::Add,
sf::BlendMode::One, sf::BlendMode::Zero, sf::BlendMode::Min
);
#else
const static sf::BlendMode minLightBlend(
sf::BlendMode::Zero, sf::BlendMode::One, sf::BlendMode::Add,
sf::BlendMode::DstAlpha, sf::BlendMode::Zero, sf::BlendMode::Add
);
#endif
sf::RenderStates lightStates;
lightStates.blendMode = lightBlend;
for (const auto& light : lights)
{
surface.draw(light, lightStates); // draws to the RenderTexture and only changes the alpha channel
// using min here allows overlapping lights to use the highest light of all the overlapping lights, instead of adding to existing light
}
}
sf::RenderStates surfaceStates(sf::RenderStates::Default);
if (shader != nullptr)
{
surfaceStates.shader = shader;
shader->setUniform("elapsedTime", game.getTotalElapsedTime().asSeconds());
if (hasMouseInside == true)
{
shader->setUniform("mousePosition", sf::Glsl::Vec2(
(game.MousePositionf().x - surface.Position().x) /
surface.Size().x,
(game.MousePositionf().y - surface.Position().y) /
surface.Size().y
));
}
shader->setUniform("textureSize", sf::Glsl::Vec2(
surface.Size().x,
surface.Size().y
));
shader->setUniform("visibleRect", sf::Glsl::Vec4(
surface.visibleRect.left,
surface.visibleRect.top,
surface.visibleRect.width,
surface.visibleRect.height
));
shader->setUniform("defaultLight", (float)(255 - map.getDefaultLight()) / 255);
}
surface.draw(target, surfaceStates); // calls RenderTexture.display() and uses a sprite to draw to the target This new lighting design has a simpler shader (only 2 inputs) and zoom works well. in the ond one, zoom shifts lights, as it's all being calculated in the shader. |
I also described today another usecase for which I cannot figure out a simple solution without Min and Max blending in this new forum thread: https://en.sfml-dev.org/forums/index.php?topic=27917 Though, according to the documentation of OpenGL ES there seem to be no specific restriction that prevent Min and Max to be available. Exactly as for old OpenGL versions, it requires the extension GL_EXT_blend_minmax to be available, but nothing more. And the extension is part of the core since OpenGL ES 3.0. |
Added with #1756 |
Would it be possible to add the
GL_MIN
andGL_MAX
blending equations to SFML when not using Opengl ES?My understanding is that they're not a part of SFML because Opengl ES doesn't have them.
Reason: drawing overlapping lights gets too bright with multiply and other equations aren't suitable in some cases.
the water tiles emit light. with multiply, all this area is nearly fully lit. This is my personal example. I'm sure others exist.
Discussion: https://en.sfml-dev.org/forums/index.php?topic=18797
I have a fork of SFML where I modified these files like this:
RenderTarget.cpp
GLExtensions.hpp
BlendMode.hpp
It would just make it simpler for those who want to use these and don't care about Opengl ES.
The text was updated successfully, but these errors were encountered: