Shadere: lyseffekter

Test av om shadere fungerer

Generelt

Selv om jeg gir spillet et visst retropreg, vil jeg bruke shadere.

I videoen under ser du at jeg eksperimenterer med lyseffekter. Hvis du ser godt etter, går jeg gjennom lyset, og spilleren blir også påvirket av effekten.

Inkludert bevegelige shadere, for eksempel for fakler som vi kommer til å trenge i dette eventyret.

Design

Hvis jeg skulle vist dette som et diagram, ville det sett omtrent slik ut, bare at det dessverre er en god del mer komplisert å lage.

Shaderdiagram

Shaderdiagram

Erfaringen viser at det i love2d ikke er mulig å rendre flere shadere i samme draw loop. Det går ikke, og det fant jeg først ut da jeg hadde fått én shader til å 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 finnes det en løsning på problemet, nemlig multi-pass rendering. Det går ut på å ta resultatet du tegner på canvaset, skrive det på nytt til et annet canvas og deretter tegne shaderen. Dette kan du så gjenta for flere shadere.

Funksjonen under fungerer bra akkurat nå, selv om jeg fortsatt må 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 bruker jeg filteret color dodge.

Color Dodge i Photoshop

Color Dodge i Photoshop

I spillet og editoren måtte jeg derfor etterligne dette på et vis.

Shadervelger i editoren

Shadervelger i editoren

Jeg bruker nesten ikke AI til å programmere når jeg utvikler spillet. Men noen ganger vet jeg at det er en liten del jeg ennå ikke kan noe om, og som jeg kan bruke som utgangspunkt. Så jeg ba om å lage en glsl shader med navnet ‘Color Dodge’ som gjør det samme som Photoshop.

Det fungerte selvfølgelig ikke bra, for koordinatene jeg bruker er annerledes, og i tillegg bruker jeg skalering og andre ting. Den endelige glsl-filen ser akkurat nå slik ut.

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 sender jeg Frameset og den valgte effekten til shaderen.

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 slutt

Jeg har klart å få color dodge fra Photoshop inn i spillet, men jeg må fortsatt prøve andre ting, som skygger og en kombinasjon av partikkelsystemet for ild med shadereffekter.

Likevel tror jeg det trengs for å gjøre alt litt penere. Heldigvis finnes glsl-filtre også på ulike nettsider, så jeg trenger ikke finne på alt fra bunnen av.