183 lines
6.3 KiB
Lua

--- @class Render
--- @field textures table<string, love.Canvas>
--- @field queue table[]
local render = {
textures = {},
queue = {},
LAYERS = {
FLOOR = 1,
SHADOW = 2,
LIGHT = 3,
SPRITE = 4,
SPRITE_LIGHT = 5,
OVERLAY = 6
}
}
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.spriteLightLayer)
love.graphics.clear(weather.skyLight.x, weather.skyLight.y, weather.skyLight.z)
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),
spriteLightLayer = love.graphics.newCanvas(w * lowResScale, h * lowResScale),
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.SPRITE_LIGHT 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.SPRITE_LIGHT 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.SPRITE_LIGHT then
love.graphics.setCanvas(txs.spriteLightLayer)
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.SPRITE_LIGHT 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)
love.graphics.setShader(lightShader)
lightShader:send("scene", txs.spriteLayer)
lightShader:send("light", txs.spriteLightLayer)
love.graphics.draw(txs.spriteLayer)
love.graphics.setShader()
end
return { new = new }