Shaders: efectos de luz
Prueba para ver si los shaders funcionan
General
Aunque le doy al juego un cierto aspecto retro, quiero usar shaders.
En el vídeo de abajo ves que estoy experimentando con efectos de luz. Si te fijas bien, camino a través de la luz y el jugador también se ve afectado por el efecto.
Incluidos shaders en movimiento, por ejemplo para las antorchas que vamos a necesitar en esta aventura.
Diseño
Si tuviera que representarlo como un esquema sería más o menos así, solo que por desgracia es bastante más complicado de hacer.

Esquema de shaders
La experiencia me ha enseñado que en love2d no se pueden renderizar varios shaders en el mismo bucle de dibujo. No es posible, y me di cuenta cuando ya tenía un shader funcionando y quise activar un segundo.
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
Por suerte hay una solución para este problema: el renderizado multi-pass. Consiste en tomar lo que dibujas en el canvas, escribirlo de nuevo en otro canvas y luego dibujar el shader. Esto lo puedes repetir para varios shaders.
La función de abajo funciona bien por ahora, aunque todavía tengo que probarla mejor.
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
En el diseño en Photoshop y Aseprite uso el filtro color dodge.

Color Dodge en Photoshop
Así que en el juego y en el editor tenía que imitarlo de alguna manera.

Selector de shaders del editor
Casi no uso IA para programar mientras desarrollo el juego. Pero a veces sé que es una parte pequeña de la que todavía no sé nada y que puedo usar como punto de partida. Así que pedí que me hiciera un shader glsl llamado ‘Color Dodge’ que hiciera lo mismo que Photoshop.
Evidentemente no funcionó bien, porque las coordenadas que uso son distintas y además uso escalado y otras cosas. El archivo glsl final, por ahora, es este.
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;
}
En la clase del shader le paso al shader el Frameset y el efecto elegido.
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
Para terminar
He conseguido llevar el color dodge de Photoshop al juego, pero todavía tengo que probar otras cosas, como sombras y combinar el sistema de partículas para el fuego con efectos de shader.
Aun así creo que hace falta para que todo quede un poco más bonito. Por suerte los filtros glsl también se encuentran en varias webs, así que no tengo que inventarlos todos desde cero.