Skip to content
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

Closed
ghost opened this issue Oct 16, 2020 · 4 comments
Closed

Suggestion: Min and Max blend Equations #1710

ghost opened this issue Oct 16, 2020 · 4 comments

Comments

@ghost
Copy link

ghost commented Oct 16, 2020

Would it be possible to add the GL_MIN and GL_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.

light

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:

include/SFML/Graphics/BlendMode.hpp
src/SFML/Graphics/GLExtensions.hpp
src/SFML/Graphics/RenderTarget.cpp

RenderTarget.cpp

        switch (blendEquation)
        {
            case sf::BlendMode::Add:             return GLEXT_GL_FUNC_ADD;
            case sf::BlendMode::Subtract:        return GLEXT_GL_FUNC_SUBTRACT;
            case sf::BlendMode::ReverseSubtract: return GLEXT_GL_FUNC_REVERSE_SUBTRACT;
#if !defined(SFML_OPENGL_ES)
            case sf::BlendMode::Min:             return GLEXT_GL_MIN;
            case sf::BlendMode::Max:             return GLEXT_GL_MAX;
#endif
        }

GLExtensions.hpp

    // Core since 1.2 - EXT_blend_subtract
    #define GLEXT_blend_subtract                      sfogl_ext_EXT_blend_subtract
    #define GLEXT_GL_FUNC_SUBTRACT                    GL_FUNC_SUBTRACT_EXT
    #define GLEXT_GL_FUNC_REVERSE_SUBTRACT            GL_FUNC_REVERSE_SUBTRACT_EXT
    #define GLEXT_GL_MIN                              GL_MIN_EXT
    #define GLEXT_GL_MAX                              GL_MAX_EXT

BlendMode.hpp

#if !defined(SFML_OPENGL_ES)
    enum Equation
    {
        Add,            ///< Pixel = Src * SrcFactor + Dst * DstFactor
        Subtract,       ///< Pixel = Src * SrcFactor - Dst * DstFactor
        ReverseSubtract,///< Pixel = Dst * DstFactor - Src * SrcFactor
        Min,            ///< Pixel = Min(Src, Dst)
        Max             ///< Pixel = Max(Src, Dst)
    };
#else
    enum Equation
    {
        Add,            ///< Pixel = Src * SrcFactor + Dst * DstFactor
        Subtract,       ///< Pixel = Src * SrcFactor - Dst * DstFactor
        ReverseSubtract ///< Pixel = Dst * DstFactor - Src * SrcFactor
    };
#endif

It would just make it simpler for those who want to use these and don't care about Opengl ES.

@ghost ghost changed the title Min and Max blend Equations Suggestion: Min and Max blend Equations Oct 16, 2020
@Bromeon
Copy link
Member

Bromeon commented Oct 18, 2020

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?

@ghost
Copy link
Author

ghost commented Oct 18, 2020

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
https://github.com/dgengin/DGEngine/blob/master/src/ShaderManager.cpp - levelText

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.
Each one of these light circles covers the entire visible screen (so, > 600 pixels in size). when you have just 1 light, any blending mode works ok, but when you have multiple lights, these add up and you end up having full light in the whole screen.

@Sakarah
Copy link
Contributor

Sakarah commented Mar 7, 2021

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.

@eXpl0it3r
Copy link
Member

Added with #1756

@eXpl0it3r eXpl0it3r added this to Discussion in SFML 2.6.0 via automation May 11, 2021
@eXpl0it3r eXpl0it3r added this to the 2.6 milestone May 11, 2021
@eXpl0it3r eXpl0it3r moved this from Discussion to Done in SFML 2.6.0 May 11, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
No open projects
SFML 2.6.0
  
Done
Development

No branches or pull requests

3 participants