rework/rendering #36
@ -3,6 +3,7 @@
|
||||
struct Light {
|
||||
vec2 position;
|
||||
vec3 color;
|
||||
float radius;
|
||||
};
|
||||
|
||||
extern Light lights[MAX_LIGHTS];
|
||||
@ -18,9 +19,14 @@ float easeInSine(float x) {
|
||||
|
||||
vec4 effect(vec4 vcolor, Image tex, vec2 texture_coords, vec2 screen_coords)
|
||||
{
|
||||
// Расчет базового освещения мира: персонаж освещается смесью неба (Sky) и отраженного света (Ambient).
|
||||
// Это гарантирует, что цвета персонажа всегда соответствуют цветовой гамме текущего времени суток.
|
||||
vec3 baseLight = ambient + (vec3(1.0) - ambient) * sky;
|
||||
|
||||
// Десатурация базового света на 30%. Это необходимо, чтобы яркие цвета неба (например, на закате)
|
||||
// не перекрывали собственные цвета персонажа слишком сильно, сохраняя его узнаваемость.
|
||||
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% обесцвечивания, а то глаза выгорают
|
||||
vec3 characterBaseLight = mix(baseLight, vec3(luma), 0.3);
|
||||
|
||||
vec4 texColor = Texel(tex, texture_coords);
|
||||
if (texColor.a == 0.0) {
|
||||
@ -31,36 +37,32 @@ vec4 effect(vec4 vcolor, Image tex, vec2 texture_coords, vec2 screen_coords)
|
||||
|
||||
for (int i = 0; i < num_lights; i++) {
|
||||
vec2 lightPos = lights[i].position;
|
||||
float radius = lights[i].radius;
|
||||
vec2 lightVec = lightPos - sprite_pos;
|
||||
float dist = length(lightVec);
|
||||
|
||||
float attenuation = 0.0;
|
||||
// Плавное затухание света по радиусу.
|
||||
// Свет начинает гаснуть на 20% дистанции до края и полностью исчезает на границе радиуса.
|
||||
// Это обеспечивает мягкий край света.
|
||||
float radiusFalloff = smoothstep(radius, radius * 0.2, dist);
|
||||
|
||||
// Логика из 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()
|
||||
// Реализация псевдо-проекции (3D-эффект):
|
||||
// Чтобы избежать резких скачков яркости при пересечении линии ног персонажа,
|
||||
// мы используем плавный переход веса освещенности.
|
||||
// Если источник света ниже персонажа (lightVec.y > 0), он считается "перед" ним.
|
||||
// Если выше (lightVec.y < 0) — "за спиной".
|
||||
// Мы плавно переходим от 0% до 100% мощности в диапазоне от -2.0 до 0.5 метров по вертикали.
|
||||
float frontWeight = smoothstep(-2.0, 0.5, lightVec.y);
|
||||
|
||||
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);
|
||||
float attenuation = radiusFalloff * frontWeight;
|
||||
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);
|
||||
}
|
||||
|
||||
@ -53,32 +53,51 @@ function sprite:draw()
|
||||
love.graphics.setColor(1, 1, 1)
|
||||
|
||||
-- Собираем источники света для шейдера
|
||||
local lightIds = Tree.level.lightGrid:query(position, 5)
|
||||
local queryRadius = 12 -- Увеличенный радиус для плавности
|
||||
local lightIds = Tree.level.lightGrid:query(position, queryRadius)
|
||||
local lightsData = {}
|
||||
local numLights = 0
|
||||
|
||||
for _, id in ipairs(lightIds) do
|
||||
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
|
||||
local lightChar = Tree.level.characters[id]
|
||||
local b = lightChar:has(Tree.behaviors.light) --[[@as LightBehavior]]
|
||||
local lPos = lightChar:has(Tree.behaviors.positioned).position
|
||||
local dist = (lPos - position):length()
|
||||
|
||||
-- Берем только те, что могут дотянуться до нас своим радиусом
|
||||
if dist < b.intensity + 2 then
|
||||
table.insert(lightsData, {
|
||||
x = lPos.x,
|
||||
y = lPos.y,
|
||||
r = b.color.x,
|
||||
g = b.color.y,
|
||||
b = b.color.z,
|
||||
radius = b.intensity,
|
||||
dist = dist
|
||||
})
|
||||
end
|
||||
end
|
||||
|
||||
-- Сортируем по дистанции, чтобы выбрать 8 самых влиятельных
|
||||
table.sort(lightsData, function(a, b) return a.dist < b.dist end)
|
||||
|
||||
local lightShader = Tree.assets.files.shaders.sprite_light
|
||||
local weather = Tree.level.weather
|
||||
local numLights = math.min(#lightsData, 8)
|
||||
|
||||
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
|
||||
|
||||
for i = 1, numLights do
|
||||
local l = lightsData[i]
|
||||
local idx = i - 1
|
||||
lightShader:send(string.format("lights[%d].position", idx), { l.x, l.y })
|
||||
lightShader:send(string.format("lights[%d].color", idx), { l.r, l.g, l.b })
|
||||
lightShader:send(string.format("lights[%d].radius", idx), l.radius)
|
||||
end
|
||||
|
||||
|
||||
love.graphics.setShader(lightShader)
|
||||
|
||||
if Tree.level.selector.id == self.owner.id then
|
||||
|
||||
14
main.lua
14
main.lua
@ -62,13 +62,13 @@ function love.load()
|
||||
Tree.level.turnOrder:add(id)
|
||||
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 }
|
||||
}
|
||||
-- --- Это тестовый источник света, привязанный к мышке, и я очень прошу его не трогать
|
||||
-- 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()
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user