Shader: kesan cahaya

Ujian sama ada shader berfungsi

Umum

Walaupun saya memberi permainan ini rupa retro tertentu, saya tetap mahu menggunakan shader.

Dalam video di bawah anda boleh lihat saya bereksperimen dengan kesan cahaya. Jika anda perhatikan, saya berjalan menembusi cahaya dan pemain juga terkesan oleh kesan tersebut.

Termasuk shader yang bergerak, contohnya untuk obor yang akan kita perlukan dalam pengembaraan ini.

Reka bentuk

Jika saya perlu menggambarkannya sebagai rajah, lebih kurang begini, cuma malangnya ia jauh lebih rumit untuk dibuat.

Rajah shader

Rajah shader

Pengalaman menunjukkan bahawa dalam love2d tidak boleh merender beberapa shader dalam draw loop yang sama. Ia tidak boleh dilakukan, dan saya hanya sedar selepas satu shader berjaya berfungsi dan saya mahu mengaktifkan yang kedua.

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

Nasib baik ada penyelesaian untuk masalah ini, iaitu rendering multi-pass. Maksudnya, hasil yang anda lukis pada canvas ditulis semula ke canvas lain, kemudian shader dilukis. Ini boleh diulang untuk beberapa shader.

Fungsi di bawah berfungsi dengan baik buat masa ini, walaupun saya masih perlu mengujinya dengan lebih teliti.

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

Dalam reka bentuk Photoshop dan Aseprite saya menggunakan penapis color dodge.

Color Dodge dalam Photoshop

Color Dodge dalam Photoshop

Jadi dalam permainan dan editor saya perlu meniru kesan ini dengan satu cara.

Pemilih shader dalam editor

Pemilih shader dalam editor

Saya hampir tidak menggunakan AI untuk memprogram semasa membangunkan permainan ini. Tetapi kadangkala saya tahu ia bahagian kecil yang saya belum ada pengetahuan dan boleh digunakan sebagai titik permulaan. Jadi saya meminta untuk membuat shader glsl bernama ‘Color Dodge’ yang sama seperti yang dilakukan Photoshop.

Sudah tentu ia tidak berfungsi dengan baik, kerana koordinat yang saya gunakan berbeza dan saya juga menggunakan penskalaan dan perkara lain. Fail glsl akhir buat masa ini ialah seperti ini.

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

Dalam kelas shader saya menghantar Frameset dan kesan yang dipilih kepada shader.

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

Akhir kata

Saya berjaya membawa color dodge daripada Photoshop ke dalam permainan, tetapi masih perlu mencuba perkara lain seperti bayang-bayang dan gabungan sistem zarah untuk api dengan kesan shader.

Namun saya rasa ia perlu untuk menjadikan semuanya lebih cantik. Nasib baik penapis glsl juga boleh didapati di pelbagai laman web, jadi saya tidak perlu mereka semuanya dari awal.