#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); }