Shaders: ljuseffekter
Test av om shaders fungerar
Allmänt
Även om jag ger spelet en viss retrokänsla vill jag använda shaders.
I videon nedan ser du att jag experimenterar med ljuseffekter. Om du tittar noga går jag genom ljuset, och spelaren påverkas också av effekten.
Inklusive rörliga shaders, till exempel för facklor som vi kommer att behöva i det här äventyret.
Design
Om jag skulle visa det som ett schema skulle det se ut ungefär så här, fast det är tyvärr en hel del krångligare att göra.

Shaderschema
Erfarenheten visar att det i love2d inte går att rendera flera shaders i samma draw loop. Det går inte, och det upptäckte jag först när jag hade fått en shader att fungera och ville aktivera en till.
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
Som tur är finns det en lösning på problemet, nämligen multi-pass rendering. Det går ut på att man tar resultatet som man ritar på canvas, skriver det igen till ett annat canvas och sedan ritar shadern. Det kan man sedan upprepa för flera shaders.
Funktionen nedan fungerar bra just nu, även om jag behöver testa den mer.
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 designen i Photoshop och Aseprite använder jag filtret color dodge.

Color Dodge i Photoshop
I spelet och editorn behövde jag alltså efterlikna det på något sätt.

Shaderväljare i editorn
Jag använder nästan aldrig AI för att programmera när jag utvecklar spelet. Men ibland vet jag att det är en liten del som jag inte kan något om än och som jag kan använda som startpunkt. Så jag bad om en glsl shader med namnet ‘Color Dodge’ som gör samma sak som Photoshop.
Det fungerade förstås inte bra, eftersom koordinaterna jag använder är annorlunda och jag dessutom använder skalning och annat. Den slutliga glsl-filen ser just nu ut så här.
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 shaderklassen skickar jag in Frameset och den valda effekten till shadern.
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
Till sist
Det har lyckats att få in color dodge från Photoshop i spelet, men jag måste fortfarande prova andra saker, som skuggor och en kombination av partikelsystemet för eld med shadereffekter.
Ändå tror jag att det behövs för att göra allt lite snyggare. Som tur är finns glsl-filter också på olika webbplatser, så jag behöver inte hitta på dem helt från början.