Shader: Lichteffekte
Test, ob Shader funktionieren
Allgemein
Auch wenn ich dem Spiel einen gewissen Retro-Look gebe, möchte ich Shader verwenden.
Im Video unten siehst du, dass ich mit Lichteffekten experimentiere. Wenn du genau hinschaust, laufe ich durch das Licht, und auch der Spieler wird von dem Effekt beeinflusst.
Dazu gehören auch bewegte Shader, zum Beispiel für Fackeln, die wir in diesem Abenteuer brauchen werden.
Design
Wenn ich das als Schema darstellen müsste, sähe es ungefähr so aus, nur ist es leider um einiges komplizierter umzusetzen.

Shader-Schema
Die Erfahrung zeigt, dass es in love2d nicht möglich ist, mehrere Shader im selben Draw-Loop zu rendern. Das geht nicht, und das habe ich erst gemerkt, als ich einen Shader zum Laufen gebracht hatte und einen zweiten aktivieren wollte.
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
Zum Glück gibt es eine Lösung für dieses Problem, nämlich Multi-Pass Rendering. Das bedeutet, dass man das Ergebnis, das man auf das Canvas zeichnet, erneut auf ein anderes Canvas schreibt und danach den Shader zeichnet. Das kann man dann für mehrere Shader wiederholen.
Die Funktion unten funktioniert im Moment gut, auch wenn ich sie noch besser testen muss.
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
Im Design in Photoshop und Aseprite benutze ich den Filter color dodge.

Color Dodge in Photoshop
Im Spiel und im Editor musste ich das also irgendwie nachbauen.

Shader-Auswahl im Editor
Ich nutze beim Entwickeln des Spiels kaum KI zum Programmieren. Manchmal weiß ich aber, dass es um einen kleinen Teil geht, von dem ich noch keine Ahnung habe, und den ich als Ausgangspunkt nutzen kann. So habe ich darum gebeten, einen glsl Shader namens ‘Color Dodge’ zu erstellen, der dasselbe macht wie Photoshop.
Das hat natürlich nicht richtig funktioniert, denn die Koordinaten, die ich verwende, sind anders, und außerdem nutze ich Skalierung und andere Dinge. Die endgültige glsl-Datei sieht im Moment so aus.
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 der Shader-Klasse übergebe ich dem Shader das Frameset und den gewählten Effekt.
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
Zum Schluss
Es ist gelungen, das Color Dodge aus Photoshop ins Spiel zu bringen, aber ich muss noch andere Dinge ausprobieren, wie Schatten und eine Kombination aus dem Partikelsystem für Feuer mit Shader-Effekten.
Trotzdem glaube ich, dass es nötig ist, um alles noch ein bisschen schöner zu machen. Zum Glück findet man glsl-Filter auch auf verschiedenen Websites, sodass ich sie nicht komplett neu erfinden muss.