diff --git a/assets/masks/circle.png b/assets/masks/circle.png new file mode 100644 index 0000000..007a29d Binary files /dev/null and b/assets/masks/circle.png differ diff --git a/assets/masks/noise.png b/assets/masks/noise.png new file mode 100644 index 0000000..3329bbd Binary files /dev/null and b/assets/masks/noise.png differ diff --git a/assets/masks/star8.png b/assets/masks/star8.png new file mode 100644 index 0000000..824e06e Binary files /dev/null and b/assets/masks/star8.png differ diff --git a/assets/shaders/blur.glsl b/assets/shaders/blur.glsl new file mode 100644 index 0000000..43a35a5 --- /dev/null +++ b/assets/shaders/blur.glsl @@ -0,0 +1,18 @@ +extern vec2 direction; // (1.0, 0.0) для X, (0.0, 1.0) для Y +extern number radius; // радиус размытия + +vec4 effect(vec4 vcolor, Image tex, vec2 texture_coords, vec2 screen_coords) +{ + vec4 sum = vec4(0.0); + float weightTotal = 0.0; + + for (int i = -10; i <= 10; i++) { + float offset = float(i); + float weight = exp(-offset * offset / (2.0 * radius * radius)); + vec2 shift = direction * offset / love_ScreenSize.xy; + sum += Texel(tex, texture_coords + shift) * weight; + weightTotal += weight; + } + + return sum / weightTotal; +} \ No newline at end of file diff --git a/assets/shaders/light.glsl b/assets/shaders/light.glsl new file mode 100644 index 0000000..72eeefb --- /dev/null +++ b/assets/shaders/light.glsl @@ -0,0 +1,30 @@ +extern vec3 color; +extern number time; + +vec4 effect(vec4 vcolor, Image tex, vec2 texture_coords, vec2 screen_coords) +{ + vec4 texColor = Texel(tex, texture_coords); + + float mask = texColor.r; + + vec2 uv = texture_coords - 0.5; + float dist = length(uv * 2.0); + + float t = time; + + float wave = sin((uv.x + uv.y) * 6.0 + t * 1.5) * 0.03; + float ripple = sin(length(uv) * 20.0 - t * 2.0) * 0.02; + float flicker = sin(t * 2.5) * 0.02; + + dist += wave + ripple + flicker; + + float intensity = 1.0 - smoothstep(0.0, 1.0, dist); + intensity = pow(intensity, 2.0); + + float colorShift = sin(t * 3.0) * 0.1; + vec3 flickerColor = color + vec3(colorShift, colorShift * 0.5, -colorShift * 0.3); + + vec3 finalColor = flickerColor * intensity * mask; + + return vec4(finalColor, mask * intensity); +} diff --git a/lib/light_source.lua b/lib/light_source.lua index 1e38dfe..f11bedc 100644 --- a/lib/light_source.lua +++ b/lib/light_source.lua @@ -1,14 +1,21 @@ __LightSource = { position = Vec3 {}, - power = 0, -- in meters - color = Vec3 {} --r, g, b + power = 0, -- in meters + color = Vec3 {}, --r, g, b + seed = 0, -- random float to make every light unique, + shader = nil, + mask = nil } -function LightSource(position, power, color) +function LightSource(position, power, color, shader, mask) local l = { position = position, power = power, - color = color + color = color, + seed = math.random() * math.pi * 2, + shader = shader or AssetBundle.files.shaders.light, + mask = mask or AssetBundle.files.masks.circle + } return setmetatable(l, { __index = __LightSource }) end diff --git a/main.lua b/main.lua index 2ded9c4..ca525a8 100644 --- a/main.lua +++ b/main.lua @@ -9,11 +9,7 @@ PIXELS_PER_METER = 48 P = nil Entities = {} -Lights = { - LightSource(Vec3 { 1, 1 }, 5), - -- LightSource(Vec3 { 300, 200 }, 5), - -- LightSource(Vec3 { 400, 200 }, 5) -} +Lights = nil function love.conf(t) t.console = true @@ -21,13 +17,18 @@ end function love.load() AssetBundle:load() - love.window.setMode(1080, 720, { resizable = true, msaa = 4 }) + love.window.setMode(1080, 720, { resizable = true, msaa = 4, vsync = true }) + + Lights = { LightSource(Vec3 { 1, 1 }, 3, Vec3 { 0.5, 0.2, 1.0 }), + LightSource(Vec3 { 4, 1 }, 10, Vec3 { 0.3, 0.7, 1.0 }, nil, AssetBundle.files.masks.noise), + LightSource(Vec3 { 7, 1 }, 3, Vec3 { 0.8, 0.3, 1.0 }), } P = Player('fox') P.entity.position = Vec3 { 7, 7 } Entities.player = P.entity Camera.position = Vec3 { 0, 0 } + print('ready') end @@ -44,41 +45,85 @@ local function worldToScreen(worldPos) end -local function drawShadow(entity, light, radius) - local shadow_vec = light - entity.position +local function drawShadow(entity, light) + local shadow_vec = light.position - entity.position local dist = shadow_vec:length() - if dist > radius then return end + if dist > light.power then return end - love.graphics.setColor(0, 0, 0, math.min(0.5, 1 - (dist / radius))) + love.graphics.setColor(0, 0, 0, math.min(0.5, 1 - (dist / light.power))) local tex, quad = entity:spriteFromAngle(shadow_vec:direction()) love.graphics.draw(tex, quad, worldToScreen(entity.position).x, worldToScreen(entity.position).y - 10, - math.step_floor(shadow_vec:direction() - math.pi / 2, math.pi / 4), + shadow_vec:direction() - math.pi / 2, 1, math.sin(20), tex:getHeight() / 2, tex:getHeight()) love.graphics.setColor(1, 1, 1, 1) end +local function drawLight(light) + local shader = light.shader + + local pos = worldToScreen(light.position) + shader:send("color", { light.color.x, light.color.y, light.color.z }) + shader:send("time", love.timer.getTime() + light.seed) + + local scale = light.power * PIXELS_PER_METER / 64 + love.graphics.setShader(shader) + love.graphics.setColor(1, 1, 1, 1) + love.graphics.draw(light.mask, + pos.x - light.power * PIXELS_PER_METER, + pos.y - light.power * PIXELS_PER_METER, + 0, + scale, + scale) + love.graphics.setShader() +end + + +local function applyBlur(input, output, radius) + local blurShader = AssetBundle.files.shaders.blur + + -- Горизонтальный проход + blurShader:send("direction", { 1.0, 0.0 }) + blurShader:send("radius", radius) + + output:renderTo(function() + love.graphics.setShader(blurShader) + love.graphics.draw(input) + love.graphics.setShader() + end) + + -- Вертикальный проход + input:renderTo(function() + love.graphics.setShader(blurShader) + blurShader:send("direction", { 0.0, 1.0 }) + love.graphics.draw(output) + love.graphics.setShader() + end) +end function love.draw() - love.graphics.clear(1, 1, 1) - local spriteCanvas = love.graphics.newCanvas() - local width, height = spriteCanvas:getDimensions() - spriteCanvas:setFilter("linear", "linear") + local width, height = love.graphics.getDimensions() + love.graphics.clear(0.1, 0.1, 0.1, 1.0) - love.graphics.setCanvas(spriteCanvas) - love.graphics.clear(1, 1, 1) + local spriteCanvas = love.graphics.newCanvas() + local shadowCanvas = love.graphics.newCanvas() + local lightCanvas = love.graphics.newCanvas() + + love.graphics.clear(0.1, 0.1, 0.1, 1.0) love.graphics.push() love.graphics.translate(width / 2, height / 2) -- теперь экранный ноль координат будет в центре экрана for _, e in pairs(Entities) do for _, l in pairs(Lights) do - drawShadow(e, l.position, l.power) + love.graphics.setCanvas(shadowCanvas) + drawShadow(e, l) end + love.graphics.setCanvas(spriteCanvas) local tex, quad = e:spriteFromAngle(math.pi / 2) love.graphics.draw(tex, quad, worldToScreen(e.position).x, @@ -88,17 +133,28 @@ function love.draw() end - love.graphics.setColor(1, 0, 0, 1) - love.graphics.push() - love.graphics.translate(worldToScreen(Lights[1].position).x, worldToScreen(Lights[1].position).y) - love.graphics.circle("fill", 0, 0, 0.1 * PIXELS_PER_METER) - love.graphics.pop() - love.graphics.pop() - love.graphics.setColor(1, 1, 1, 1) + love.graphics.setCanvas(lightCanvas) + love.graphics.clear(0, 0, 0, 1) + + for _, light in ipairs(Lights) do + drawLight(light) + end love.graphics.setCanvas() + + + love.graphics.pop() + love.graphics.setColor(1, 1, 1, 1) + love.graphics.setCanvas() + + applyBlur(shadowCanvas, love.graphics.newCanvas(), 10.0) -- тени мягче + love.graphics.draw(shadowCanvas) love.graphics.draw(spriteCanvas) + love.graphics.setBlendMode("add", "premultiplied") + love.graphics.draw(lightCanvas) + love.graphics.setBlendMode("alpha") + love.graphics.setColor(1, 1, 1, 1) end