rework/rendering #36
66
assets/shaders/sprite_light.glsl
Normal file
66
assets/shaders/sprite_light.glsl
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#define MAX_LIGHTS 8
|
||||||
|
|
||||||
|
struct Light {
|
||||||
|
vec2 position;
|
||||||
|
vec3 color;
|
||||||
|
};
|
||||||
|
|
||||||
|
extern Light lights[MAX_LIGHTS];
|
||||||
|
extern int num_lights;
|
||||||
|
extern vec2 sprite_pos; // Мировая позиция спрайта (в метрах)
|
||||||
|
extern vec3 ambient; // Эмбиентное освещение
|
||||||
|
extern vec3 sky; // Цвет неба
|
||||||
|
|
||||||
|
// Функция для имитации easing.easeInSine
|
||||||
|
float easeInSine(float x) {
|
||||||
|
return 1.0 - cos((x * 3.14159) / 2.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec4 effect(vec4 vcolor, Image tex, vec2 texture_coords, vec2 screen_coords)
|
||||||
|
{
|
||||||
|
vec3 baseLight = ambient + (vec3(1.0) - ambient) * sky;
|
||||||
|
float luma = dot(baseLight, vec3(0.2126, 0.7152, 0.0722)); // https://en.wikipedia.org/wiki/Relative_luminance
|
||||||
|
vec3 characterBaseLight = mix(baseLight, vec3(luma), 0.3); // 30% обесцвечивания, а то глаза выгорают
|
||||||
|
|
||||||
|
vec4 texColor = Texel(tex, texture_coords);
|
||||||
|
if (texColor.a == 0.0) {
|
||||||
|
return vec4(0.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
vec3 pointLight = vec3(0.0);
|
||||||
|
|
||||||
|
for (int i = 0; i < num_lights; i++) {
|
||||||
|
vec2 lightPos = lights[i].position;
|
||||||
|
vec2 lightVec = lightPos - sprite_pos;
|
||||||
|
float dist = length(lightVec);
|
||||||
|
|
||||||
|
float attenuation = 0.0;
|
||||||
|
|
||||||
|
// Логика из shadowcaster.lua:
|
||||||
|
// if lightPos.y > position.y then
|
||||||
|
// 1 - 0.3 * lightVec:length()
|
||||||
|
// elseif position.y - lightPos.y < 3 then
|
||||||
|
// (1 - easing.easeInSine((position.y - lightPos.y))) - 0.3 * lightVec:length()
|
||||||
|
|
||||||
|
if (lightPos.y > sprite_pos.y) {
|
||||||
|
attenuation = 1.0 - 0.3 * dist;
|
||||||
|
} else {
|
||||||
|
float yDiff = sprite_pos.y - lightPos.y;
|
||||||
|
if (yDiff < 3.0) {
|
||||||
|
attenuation = (1.0 - easeInSine(yDiff)) - 0.3 * dist;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
attenuation = max(attenuation, 0.0);
|
||||||
|
pointLight += lights[i].color * attenuation;
|
||||||
|
}
|
||||||
|
|
||||||
|
pointLight = clamp(pointLight, 0.0, 1.0);
|
||||||
|
vec3 a = clamp(ambient, 0.0, 1.0);
|
||||||
|
|
||||||
|
// Канальный множитель: от ambient до 1 в зависимости от точечного света
|
||||||
|
// Это гарантирует, что спрайт не будет черным в отсутствие источников света
|
||||||
|
vec3 lightMultiplier = a + (vec3(1.0) - a) * pointLight;
|
||||||
|
|
||||||
|
return vec4(texColor.rgb * (characterBaseLight + pointLight), texColor.a);
|
||||||
|
}
|
||||||
@ -30,32 +30,6 @@ function behavior:draw()
|
|||||||
love.graphics.ellipse("fill", 0, 0, sprite.manifest.size / 2, sprite.manifest.size / 2 * math.cos(math.pi / 4))
|
love.graphics.ellipse("fill", 0, 0, sprite.manifest.size / 2, sprite.manifest.size / 2 * math.cos(math.pi / 4))
|
||||||
love.graphics.pop()
|
love.graphics.pop()
|
||||||
end)
|
end)
|
||||||
|
|
||||||
-- 2. Свет на спрайте
|
|
||||||
if #lights > 0 then
|
|
||||||
Tree.level.render:enqueue(Tree.level.render.LAYERS.SPRITE_LIGHT, position.y, function()
|
|
||||||
love.graphics.setBlendMode("add")
|
|
||||||
for _, light in ipairs(lights) do
|
|
||||||
local lightPos = light:has(Tree.behaviors.positioned).position
|
|
||||||
local lightVec = lightPos - position
|
|
||||||
|
|
||||||
local lightColor = light:has(Tree.behaviors.light).color
|
|
||||||
if lightPos.y > position.y then
|
|
||||||
love.graphics.setColor(lightColor.x, lightColor.y, lightColor.z,
|
|
||||||
1 - 0.3 * lightVec:length())
|
|
||||||
elseif position.y - lightPos.y < 3 then
|
|
||||||
love.graphics.setColor(lightColor.x, lightColor.y, lightColor.z,
|
|
||||||
(1 - easing.easeInSine((position.y - lightPos.y))) - 0.3 * lightVec:length())
|
|
||||||
end
|
|
||||||
|
|
||||||
sprite.animationTable[sprite.state]:draw(sprite.sheets[sprite.state],
|
|
||||||
position.x,
|
|
||||||
position.y, nil, 1 / ppm * sprite.side, 1 / ppm, sprite.manifest.base.x, sprite.manifest.base.y)
|
|
||||||
end
|
|
||||||
love.graphics.setBlendMode("alpha")
|
|
||||||
love.graphics.setColor(1, 1, 1)
|
|
||||||
end)
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return behavior
|
return behavior
|
||||||
|
|||||||
@ -51,14 +51,42 @@ function sprite:draw()
|
|||||||
|
|
||||||
Tree.level.render:enqueue(Tree.level.render.LAYERS.SPRITE, position.y, function()
|
Tree.level.render:enqueue(Tree.level.render.LAYERS.SPRITE, position.y, function()
|
||||||
love.graphics.setColor(1, 1, 1)
|
love.graphics.setColor(1, 1, 1)
|
||||||
if Tree.level.selector.id == self.owner.id then
|
|
||||||
local texW, texH = self.sheets[self.state]:getWidth(),
|
-- Собираем источники света для шейдера
|
||||||
self.sheets[self.state]:getHeight()
|
local lightIds = Tree.level.lightGrid:query(position, 5)
|
||||||
local shader = Tree.assets.files.shaders.outline
|
local lightsData = {}
|
||||||
shader:send("texSize", { texW, texH })
|
local numLights = 0
|
||||||
shader:send("time", love.timer:getTime())
|
for _, id in ipairs(lightIds) do
|
||||||
love.graphics.setShader(shader)
|
local light = Tree.level.characters[id]
|
||||||
|
local lPos = light:has(Tree.behaviors.positioned).position
|
||||||
|
local lColor = light:has(Tree.behaviors.light).color
|
||||||
|
table.insert(lightsData, { lPos.x, lPos.y, lColor.x, lColor.y, lColor.z })
|
||||||
|
numLights = numLights + 1
|
||||||
|
if numLights >= 8 then break end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
local lightShader = Tree.assets.files.shaders.sprite_light
|
||||||
|
local weather = Tree.level.weather
|
||||||
|
lightShader:send("sprite_pos", { position.x, position.y })
|
||||||
|
lightShader:send("sky", { weather.skyLight.x, weather.skyLight.y, weather.skyLight.z })
|
||||||
|
lightShader:send("ambient", { weather.ambientLight.x, weather.ambientLight.y, weather.ambientLight.z })
|
||||||
|
lightShader:send("num_lights", numLights)
|
||||||
|
if numLights > 0 then
|
||||||
|
-- Формируем массив для шейдера: lights[i].position и lights[i].color
|
||||||
|
for i, data in ipairs(lightsData) do
|
||||||
|
lightShader:send(string.format("lights[%d].position", i - 1), { data[1], data[2] })
|
||||||
|
lightShader:send(string.format("lights[%d].color", i - 1), { data[3], data[4], data[5] })
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
love.graphics.setShader(lightShader)
|
||||||
|
|
||||||
|
if Tree.level.selector.id == self.owner.id then
|
||||||
|
-- Если выбран, то рисуем еще и обводку?
|
||||||
|
-- В LÖVE нельзя поставить два шейдера сразу через setShader.
|
||||||
|
-- Для простоты пока оставим только свет, либо нужно комбинировать шейдеры.
|
||||||
|
end
|
||||||
|
|
||||||
self.animationTable[self.state]:draw(self.sheets[self.state],
|
self.animationTable[self.state]:draw(self.sheets[self.state],
|
||||||
position.x,
|
position.x,
|
||||||
position.y, nil, 1 / ppm * self.side, 1 / ppm, self.manifest.base.x, self.manifest.base.y)
|
position.y, nil, 1 / ppm * self.side, 1 / ppm, self.manifest.base.x, self.manifest.base.y)
|
||||||
|
|||||||
@ -9,8 +9,7 @@ local render = {
|
|||||||
SHADOW = 2,
|
SHADOW = 2,
|
||||||
LIGHT = 3,
|
LIGHT = 3,
|
||||||
SPRITE = 4,
|
SPRITE = 4,
|
||||||
SPRITE_LIGHT = 5,
|
OVERLAY = 5
|
||||||
OVERLAY = 6
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -23,8 +22,6 @@ function render:clear()
|
|||||||
love.graphics.clear()
|
love.graphics.clear()
|
||||||
love.graphics.setCanvas(txs.spriteLayer)
|
love.graphics.setCanvas(txs.spriteLayer)
|
||||||
love.graphics.clear()
|
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.setCanvas(txs.floorLayer)
|
||||||
love.graphics.clear()
|
love.graphics.clear()
|
||||||
love.graphics.setCanvas(txs.lightLayer)
|
love.graphics.setCanvas(txs.lightLayer)
|
||||||
@ -86,7 +83,6 @@ local function new(params)
|
|||||||
textures = {
|
textures = {
|
||||||
shadowLayer = love.graphics.newCanvas(w * lowResScale, h * lowResScale),
|
shadowLayer = love.graphics.newCanvas(w * lowResScale, h * lowResScale),
|
||||||
spriteLayer = love.graphics.newCanvas(w, h),
|
spriteLayer = love.graphics.newCanvas(w, h),
|
||||||
spriteLightLayer = love.graphics.newCanvas(w * lowResScale, h * lowResScale),
|
|
||||||
floorLayer = love.graphics.newCanvas(w, h),
|
floorLayer = love.graphics.newCanvas(w, h),
|
||||||
overlayLayer = love.graphics.newCanvas(w, h),
|
overlayLayer = love.graphics.newCanvas(w, h),
|
||||||
lightLayer = love.graphics.newCanvas(w * lowResScale, h * lowResScale),
|
lightLayer = love.graphics.newCanvas(w * lowResScale, h * lowResScale),
|
||||||
@ -114,15 +110,13 @@ function render:draw()
|
|||||||
if entry.layer ~= currentLayer then
|
if entry.layer ~= currentLayer then
|
||||||
if currentLayer then
|
if currentLayer then
|
||||||
Tree.level.camera:detach()
|
Tree.level.camera:detach()
|
||||||
local wasLowRes = currentLayer == self.LAYERS.SHADOW or currentLayer == self.LAYERS.SPRITE_LIGHT or
|
local wasLowRes = currentLayer == self.LAYERS.SHADOW or currentLayer == self.LAYERS.LIGHT
|
||||||
currentLayer == self.LAYERS.LIGHT
|
|
||||||
if wasLowRes then
|
if wasLowRes then
|
||||||
love.graphics.pop()
|
love.graphics.pop()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
currentLayer = entry.layer
|
currentLayer = entry.layer
|
||||||
local isLowRes = currentLayer == self.LAYERS.SHADOW or currentLayer == self.LAYERS.SPRITE_LIGHT or
|
local isLowRes = currentLayer == self.LAYERS.SHADOW or currentLayer == self.LAYERS.LIGHT
|
||||||
currentLayer == self.LAYERS.LIGHT
|
|
||||||
|
|
||||||
if currentLayer == self.LAYERS.FLOOR then
|
if currentLayer == self.LAYERS.FLOOR then
|
||||||
love.graphics.setCanvas(txs.floorLayer)
|
love.graphics.setCanvas(txs.floorLayer)
|
||||||
@ -132,8 +126,6 @@ function render:draw()
|
|||||||
love.graphics.setCanvas(txs.lightLayer)
|
love.graphics.setCanvas(txs.lightLayer)
|
||||||
elseif currentLayer == self.LAYERS.SPRITE then
|
elseif currentLayer == self.LAYERS.SPRITE then
|
||||||
love.graphics.setCanvas(txs.spriteLayer)
|
love.graphics.setCanvas(txs.spriteLayer)
|
||||||
elseif currentLayer == self.LAYERS.SPRITE_LIGHT then
|
|
||||||
love.graphics.setCanvas(txs.spriteLightLayer)
|
|
||||||
elseif currentLayer == self.LAYERS.OVERLAY then
|
elseif currentLayer == self.LAYERS.OVERLAY then
|
||||||
love.graphics.setCanvas(txs.overlayLayer)
|
love.graphics.setCanvas(txs.overlayLayer)
|
||||||
end
|
end
|
||||||
@ -148,8 +140,7 @@ function render:draw()
|
|||||||
end
|
end
|
||||||
if currentLayer then
|
if currentLayer then
|
||||||
Tree.level.camera:detach()
|
Tree.level.camera:detach()
|
||||||
local wasLowRes = currentLayer == self.LAYERS.SHADOW or currentLayer == self.LAYERS.SPRITE_LIGHT or
|
local wasLowRes = currentLayer == self.LAYERS.SHADOW or currentLayer == self.LAYERS.LIGHT
|
||||||
currentLayer == self.LAYERS.LIGHT
|
|
||||||
if wasLowRes then
|
if wasLowRes then
|
||||||
love.graphics.pop()
|
love.graphics.pop()
|
||||||
end
|
end
|
||||||
@ -171,12 +162,10 @@ function render:draw()
|
|||||||
|
|
||||||
love.graphics.setShader()
|
love.graphics.setShader()
|
||||||
love.graphics.draw(txs.overlayLayer)
|
love.graphics.draw(txs.overlayLayer)
|
||||||
love.graphics.setShader(lightShader)
|
|
||||||
|
|
||||||
lightShader:send("scene", txs.spriteLayer)
|
-- Спрайты уже полностью освещены в SpriteBehavior (с учетом ambient и point lights)
|
||||||
lightShader:send("light", txs.spriteLightLayer)
|
-- Поэтому рисуем их "как есть"
|
||||||
love.graphics.draw(txs.spriteLayer)
|
love.graphics.draw(txs.spriteLayer)
|
||||||
love.graphics.setShader()
|
|
||||||
end
|
end
|
||||||
|
|
||||||
return { new = new }
|
return { new = new }
|
||||||
|
|||||||
8
main.lua
8
main.lua
@ -62,6 +62,14 @@ function love.load()
|
|||||||
Tree.level.turnOrder:add(id)
|
Tree.level.turnOrder:add(id)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
--- Это тестовый источник света, привязанный к мышке, и я очень прошу его не трогать
|
||||||
|
character.spawn("light")
|
||||||
|
:addBehavior {
|
||||||
|
Tree.behaviors.positioned.new(),
|
||||||
|
Tree.behaviors.cursor.new(),
|
||||||
|
Tree.behaviors.light.new { color = Vec3 { 0.5, 0.5, 0.5 }, intensity = 5 }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
Tree.level.turnOrder:endRound()
|
Tree.level.turnOrder:endRound()
|
||||||
print("Now playing:", Tree.level.turnOrder.current)
|
print("Now playing:", Tree.level.turnOrder.current)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user