Shaders: lyseffekter
Test af om shaders virker
Generelt
Selvom jeg giver spillet et bestemt retro-look, vil jeg gerne bruge shaders.
I videoen herunder kan du se, at jeg eksperimenterer med lyseffekter. Hvis du kigger godt efter, går jeg gennem lyset, og spilleren bliver også påvirket af effekten.
Inklusive bevægelige shaders, fx til fakler, som vi får brug for i dette eventyr.
Design
Hvis jeg skulle vise det som et diagram, ville det se nogenlunde sådan ud, bare er det desværre noget mere kompliceret at lave.

Shader-diagram
Erfaringen viser, at det i love2d ikke er muligt at rendere flere shaders i samme draw loop. Det kan ikke lade sig gøre, og det fandt jeg først ud af, da jeg havde én shader til at virke og ville aktivere en til.
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
Heldigvis er der en løsning på problemet, nemlig multi-pass rendering. Det går ud på at tage det, du tegner på canvas, skrive det igen til et andet canvas og derefter tegne shaderen. Det kan du så gentage for flere shaders.
Funktionen herunder virker godt lige nu, selvom jeg stadig skal teste den bedre.
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
I designet i Photoshop og Aseprite bruger jeg filteret color dodge.

Color Dodge i Photoshop
I spillet og editoren måtte jeg derfor efterligne det på en eller anden måde.

Shader-vælger i editoren
Jeg bruger næsten ikke AI til at programmere, mens jeg udvikler spillet. Men nogle gange ved jeg, at det er en lille del, som jeg endnu ikke har viden om, og som jeg kan bruge som udgangspunkt. Så jeg bad om at lave en glsl shader med navnet ‘Color Dodge’, der gør det samme som Photoshop.
Det virkede selvfølgelig ikke godt, for de koordinater, jeg bruger, er anderledes, og desuden bruger jeg også skalering og andre ting. Den endelige glsl-fil ser lige nu sådan ud.
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;
}
I shader-klassen giver jeg shaderen Frameset og den valgte 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
Til sidst
Det er lykkedes at få color dodge fra Photoshop ind i spillet, men jeg skal stadig prøve andre ting, som skygger og en kombination af partikelsystemet til ild med shader-effekter.
Alligevel tror jeg, det er nødvendigt for at gøre det hele lidt pænere. Heldigvis kan glsl-filtre også findes på forskellige hjemmesider, så dem skal jeg ikke finde på helt fra bunden.