Shader: effetti di luce

Prova per vedere se gli shader funzionano

In generale

Anche se do al gioco un certo aspetto retrò, voglio usare gli shader.

Nel video qui sotto vedi che sto sperimentando con gli effetti di luce. Se guardi bene, cammino attraverso la luce e anche il giocatore viene influenzato dall’effetto.

Compresi shader in movimento, per esempio per le torce che ci serviranno in questa avventura.

Design

Se dovessi rappresentarlo come uno schema sarebbe più o meno così, solo che purtroppo è molto più complicato da realizzare.

Schema degli shader

Schema degli shader

L’esperienza insegna che in love2d non è possibile renderizzare più shader nello stesso ciclo di disegno. Non si può fare, e l’ho scoperto solo quando avevo uno shader funzionante e volevo attivarne un secondo.

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

Per fortuna esiste una soluzione a questo problema: il rendering multi-pass. In pratica prendi il risultato che hai disegnato sul canvas, lo riscrivi su un altro canvas e poi disegni lo shader. Puoi ripetere questa operazione per più shader.

La funzione qui sotto per ora funziona bene, anche se devo ancora testarla meglio.

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

Shader Color Dodge

Nel design in Photoshop e Aseprite uso il filtro color dodge.

Color Dodge in Photoshop

Color Dodge in Photoshop

Nel gioco e nell’editor dovevo quindi ricrearlo in qualche modo.

Selettore di shader nell’editor

Selettore di shader nell’editor

Non uso quasi mai l’AI per programmare durante lo sviluppo del gioco. A volte però so che si tratta di una piccola parte su cui non ho ancora conoscenze e che posso usare come punto di partenza. Così ho chiesto di creare uno shader glsl chiamato ‘Color Dodge’ che facesse la stessa cosa di Photoshop.

Ovviamente non ha funzionato bene, perché le coordinate che uso sono diverse e in più uso anche lo scaling e altre cose. Il file glsl finale al momento è questo.

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;
}

Nella classe dello shader passo allo shader il Frameset e l’effetto scelto.

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

Per finire

Sono riuscito a portare nel gioco il color dodge di Photoshop, ma devo ancora provare altre cose, come le ombre e una combinazione del sistema di particelle per il fuoco con gli effetti shader.

Credo comunque che serva a rendere tutto un po’ più bello. Per fortuna i filtri glsl si trovano anche su vari siti, quindi non devo inventarli tutti da zero.