--- @class Render --- @field textures table --- @field queue table[] local render = { textures = {}, queue = {}, LAYERS = { FLOOR = 1, SHADOW = 2, LIGHT = 3, SPRITE = 4, OVERLAY = 5 } } function render:clear() local weather = Tree.level.weather local txs = self.textures self.queue = {} love.graphics.setCanvas(txs.shadowLayer) love.graphics.clear() love.graphics.setCanvas(txs.spriteLayer) love.graphics.clear() love.graphics.setCanvas(txs.floorLayer) love.graphics.clear() love.graphics.setCanvas(txs.lightLayer) love.graphics.clear(weather.skyLight.x, weather.skyLight.y, weather.skyLight.z) love.graphics.setCanvas(txs.overlayLayer) love.graphics.clear() end function render:enqueue(layer, z, func) table.insert(self.queue, { layer = layer, z = z, func = func }) end function render:free() for _, tx in pairs(self.textures) do tx:release() end self.textures = nil end --- TODO: это используется для блюра, должно кэшироваться и поддерживать ресайз function render:applyBlur(input, radius) local blurShader = Tree.assets.files.shaders.blur -- Горизонтальный проход blurShader:send("direction", { 1.0, 0.0 }) blurShader:send("radius", radius) self.textures.tmp1:renderTo(function() love.graphics.clear() love.graphics.setShader(blurShader) love.graphics.draw(input) love.graphics.setShader() end) -- Вертикальный проход self.textures.tmp2:renderTo( function() love.graphics.clear() love.graphics.setShader(blurShader) blurShader:send("direction", { 0.0, 1.0 }) love.graphics.draw(self.textures.tmp1) love.graphics.setShader() end ) return self.textures.tmp2 end ---@param params {w: number?, h: number?} ---@return table|Render local function new(params) local w = params.w or love.graphics.getWidth() local h = params.h or love.graphics.getHeight() local lowResScale = 0.5 return setmetatable({ lowResScale = lowResScale, queue = {}, textures = { shadowLayer = love.graphics.newCanvas(w * lowResScale, h * lowResScale), spriteLayer = love.graphics.newCanvas(w, h), floorLayer = love.graphics.newCanvas(w, h), overlayLayer = love.graphics.newCanvas(w, h), lightLayer = love.graphics.newCanvas(w * lowResScale, h * lowResScale), tmp1 = love.graphics.newCanvas(w * lowResScale, h * lowResScale), tmp2 = love.graphics.newCanvas(w * lowResScale, h * lowResScale), } }, { __index = render }) end function render:draw() local weather = Tree.level.weather local txs = self.textures -- 1. Сортировка очереди table.sort(self.queue, function(a, b) if a.layer ~= b.layer then return a.layer < b.layer end return a.z < b.z end) -- 2. Рендеринг очереди в соответствующие Canvas local currentLayer = nil for _, entry in ipairs(self.queue) do if entry.layer ~= currentLayer then if currentLayer then Tree.level.camera:detach() local wasLowRes = currentLayer == self.LAYERS.SHADOW or currentLayer == self.LAYERS.LIGHT if wasLowRes then love.graphics.pop() end end currentLayer = entry.layer local isLowRes = currentLayer == self.LAYERS.SHADOW or currentLayer == self.LAYERS.LIGHT if currentLayer == self.LAYERS.FLOOR then love.graphics.setCanvas(txs.floorLayer) elseif currentLayer == self.LAYERS.SHADOW then love.graphics.setCanvas(txs.shadowLayer) elseif currentLayer == self.LAYERS.LIGHT then love.graphics.setCanvas(txs.lightLayer) elseif currentLayer == self.LAYERS.SPRITE then love.graphics.setCanvas(txs.spriteLayer) elseif currentLayer == self.LAYERS.OVERLAY then love.graphics.setCanvas(txs.overlayLayer) end if isLowRes then love.graphics.push() love.graphics.scale(self.lowResScale) end Tree.level.camera:attach() end entry.func() end if currentLayer then Tree.level.camera:detach() local wasLowRes = currentLayer == self.LAYERS.SHADOW or currentLayer == self.LAYERS.LIGHT if wasLowRes then love.graphics.pop() end end love.graphics.setCanvas() -- 3. Пост-процессинг и композиция love.graphics.setCanvas(txs.lightLayer) -- Радиус блюра тоже масштабируем, так как текстура меньше love.graphics.draw(self:applyBlur(txs.shadowLayer, 4 * Tree.level.camera.scale * self.lowResScale)) love.graphics.setCanvas() local lightShader = Tree.assets.files.shaders.light_postprocess lightShader:send("scene", txs.floorLayer) lightShader:send("light", self:applyBlur(txs.lightLayer, 2 * self.lowResScale)) lightShader:send("ambient", { weather.ambientLight.x, weather.ambientLight.y, weather.ambientLight.z }) love.graphics.setShader(lightShader) love.graphics.draw(txs.floorLayer) love.graphics.setShader() love.graphics.draw(txs.overlayLayer) -- Спрайты уже полностью освещены в SpriteBehavior (с учетом ambient и point lights) -- Поэтому рисуем их "как есть" love.graphics.draw(txs.spriteLayer) end return { new = new }