heroes-of-nerevelon/assets/shaders/sprite_light.glsl

69 lines
3.8 KiB
GLSL
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#define MAX_LIGHTS 8
struct Light {
vec2 position;
vec3 color;
float radius;
};
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)
{
// Расчет базового освещения мира: персонаж освещается смесью неба (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);
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;
float radius = lights[i].radius;
vec2 lightVec = lightPos - sprite_pos;
float dist = length(lightVec);
// Плавное затухание света по радиусу.
// Свет начинает гаснуть на 20% дистанции до края и полностью исчезает на границе радиуса.
// Это обеспечивает мягкий край света.
float radiusFalloff = smoothstep(radius, radius * 0.2, dist);
// Реализация псевдо-проекции (3D-эффект):
// Чтобы избежать резких скачков яркости при пересечении линии ног персонажа,
// мы используем плавный переход веса освещенности.
// Если источник света ниже персонажа (lightVec.y > 0), он считается "перед" ним.
// Если выше (lightVec.y < 0) — "за спиной".
// Мы плавно переходим от 0% до 100% мощности в диапазоне от -2.0 до 0.5 метров по вертикали.
float frontWeight = smoothstep(-2.0, 0.5, lightVec.y);
float attenuation = radiusFalloff * frontWeight;
pointLight += lights[i].color * attenuation;
}
// Ограничиваем суммарную яркость точечных источников, чтобы избежать пересветов в белое
pointLight = clamp(pointLight, 0.0, 1.0);
// Финальный расчет цвета:
// Мы берем текстуру персонажа и умножаем ее на сумму базового света мира и всех точечных источников.
// Это создает эффект, где персонаж одновременно "вписан" в атмосферу уровня и реагирует на динамические огни.
return vec4(texColor.rgb * (characterBaseLight + pointLight), texColor.a);
}