175 lines
5.9 KiB
Lua
175 lines
5.9 KiB
Lua
--- @class Render
|
||
--- @field textures table<string, love.Canvas>
|
||
--- @field queue table[]
|
||
--- @field lowResScale number
|
||
--- @field LAYERS table<string, integer>
|
||
local render = {
|
||
textures = {},
|
||
queue = {},
|
||
lowResScale = 1.0,
|
||
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 }
|