Logo Signal From The Stars

Shaders - light effects

Test whether shaders work

Martin avatar
  • Martin
  • 4 min read

General

Even though I want to give the game a certain retro look, I want to use shaders.

In the video below, you can see me experimenting with light effects. If you look closely, I walk through the light and the player will also be affected by the effect.

This includes moving shaders, e.g. for torches, which we will need in this adventure.

Design

If I had to represent this as a diagram, it would look something like this, but unfortunately it is a lot more complicated to create.

Shader schema

Schader schema

Experience has shown that in love2d it is not possible to render multiple shaders during the same draw loop. This is not possible, and I only discovered this when I had one shader working and wanted to activate a second one.

shader1 = love.graphics.newShader('color-dodge.glsl')
shader2 = love.graphics.newShader('color-dodge.glsl')

function love.draw()
    love.graphics.push("all")
    love.graphics.setCanvas(src)
    love.graphics.clear()

    love.graphics.setShader()
    love.graphics.draw(scene, 0, 0)
    love.graphics.draw(player, playerPos.x, playerPos.y)

    love.graphics.setShader(shader1)
    love.graphics.setShader(shader2)

    love.graphics.setCanvas()
    love.graphics.setShader()
    love.graphics.draw(src, 0, 0)
end

Fortunately, there is a solution to this problem, namely ‘multi-pass’ rendering. This basically means rewriting the result you draw to the canvas to another canvas and then drawing the shader. You can then repeat this for multiple shaders.

The function below currently works well, although I still need to test it further.

function Engine3:applyIntermediateShader(shader)
	if not shader then return end

	local src = self.canvas:getCanvas()
	local front = self.canvas:getCanvasFront()

	-- Store current graphics state
	local currentCanvas = love.graphics.getCanvas()
	local currentShader = love.graphics.getShader()

	-- Apply shader: src -> front
	love.graphics.push("all")
	love.graphics.origin()
	love.graphics.setCanvas(front)
	love.graphics.clear()
	love.graphics.setShader(shader:getShader())
	love.graphics.draw(src, 0, 0)
	love.graphics.setShader()
	love.graphics.pop()

	-- Copy result back to main canvas: front -> src
	love.graphics.push("all")
	love.graphics.origin()
	love.graphics.setCanvas(src)
	love.graphics.clear()
	love.graphics.draw(front, 0, 0)
	love.graphics.pop()

	-- Restore graphics state for continued rendering
	love.graphics.setCanvas(currentCanvas)
	love.graphics.setShader(currentShader)
end

Color Dodge shader

In the Photoshop/Aseprite design, I use the color dodge filter.

Color Dodge Photoshop

Color Dodge Photoshop

So I had to recreate this in the game and the editor somehow.

Editor shader selector

Editor shader selector

I hardly ever use AI for programming when developing the game. Sometimes, however, I know that it is a small part that I am not yet familiar with and can use as a starting point. For example, I asked for a GLSL shader to be created with the name ‘Color Dodge’ that is similar to what Photoshop does.

Of course, that didn’t work well, because the coordinates I use are different and I also use scaling and other things. The final glsl file is currently.

extern vec2 position;
extern Image image;
extern float scale;
extern float overlayIntensity;
extern vec4 quadRect;

vec3 colorDodge(vec3 base, vec3 blend) {
    return vec3(
        blend.r >= 0.999 ? 1.0 : min(1.0, base.r / max(0.001, 1.0 - blend.r)),
        blend.g >= 0.999 ? 1.0 : min(1.0, base.g / max(0.001, 1.0 - blend.g)),
        blend.b >= 0.999 ? 1.0 : min(1.0, base.b / max(0.001, 1.0 - blend.b))
    );
}

vec4 effect(vec4 color, Image tex, vec2 tc, vec2 sc)
{
    vec4 base = texture(tex, tc);
    vec2 texSize = vec2(textureSize(image, 0));
    vec2 qpos  = quadRect.xy;
    vec2 qsize = quadRect.zw;
    vec2 uv = (sc - position) / (qsize * scale);

    if (uv.x < 0.0 || uv.x > 1.0 || uv.y < 0.0 || uv.y > 1.0) {
        return base;
    }

    uv = (uv * qsize + qpos) / texSize;
    vec4 overlay = texture(image, uv);

    if (overlay.a > 0.0) {
        vec3 enhancedOverlay = min(vec3(1.0), overlay.rgb * overlayIntensity);
        vec3 dodged = colorDodge(base.rgb, enhancedOverlay);
        float enhancedAlpha = min(1.0, overlay.a * overlayIntensity);
        vec3 result = mix(base.rgb, dodged, enhancedAlpha);
        return vec4(result, base.a);
    }

    return base;
}

In the shader class, I pass the Frameset to the shader and the selected effect.

if frameset then
  self:setImage(frameset.resource)
  self:setScale(nil, true)
  self:setPosition(frameset:getFrameX(), frameset:getFrameY(), true)
  if frameset:getQuad() then
    self:setQuad(frameset:getQuad():getViewport())
  end
end

In conclusion

I managed to get Photoshop’s color doge into the game, but I still need to try other things such as shadows and a combination with the particle system for fire in combination with shader effects.

Still, I think it’s necessary to make it all a bit more beautiful. Fortunately, the glsl filters can also be found on various websites, so I don’t have to reinvent them from scratch.


✉️ Stay informed!

Receive the latest news for free and motivate me to make this adventure a success!