69 lines
3.8 KiB
GLSL
69 lines
3.8 KiB
GLSL
#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);
|
||
}
|