From d975e7f46324f29f417c76f9333308310bd04365 Mon Sep 17 00:00:00 2001 From: Ivan Yuriev Date: Sat, 19 Apr 2025 21:53:40 +0300 Subject: [PATCH] light sources demo --- assets/masks/circle.png | Bin 0 -> 572 bytes assets/masks/noise.png | Bin 0 -> 4004 bytes assets/masks/star8.png | Bin 0 -> 748 bytes assets/shaders/blur.glsl | 18 +++++++ assets/shaders/light.glsl | 30 +++++++++++ lib/light_source.lua | 15 ++++-- main.lua | 106 +++++++++++++++++++++++++++++--------- 7 files changed, 140 insertions(+), 29 deletions(-) create mode 100644 assets/masks/circle.png create mode 100644 assets/masks/noise.png create mode 100644 assets/masks/star8.png create mode 100644 assets/shaders/blur.glsl create mode 100644 assets/shaders/light.glsl diff --git a/assets/masks/circle.png b/assets/masks/circle.png new file mode 100644 index 0000000000000000000000000000000000000000..007a29dc7d8c9b866700ab51aa3127e9fcb312d8 GIT binary patch literal 572 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 KMNUMnLSTXy`@M_+ literal 0 HcmV?d00001 diff --git a/assets/masks/star8.png b/assets/masks/star8.png new file mode 100644 index 0000000000000000000000000000000000000000..824e06ec92c699ecbe6abc5a47a8188b59095a8f GIT binary patch literal 748 zcmVya0#f$Lu;!r>=7{5Uej za~eyMQ-E6Q(Y+=E-y1hCm;)MxYqvHW^|J)XM8RS2P~3rKbKu`ECl|zt0EBKI5DX~wKZE(QsG zN;d+sEAWbJP{DVw@N&#Usks0KK;r)WK*x^+`h!zek~DEe#4sgyL?6rBl?ahaE089E z6sNWVNfL-riYt&JfgGc{0tpf*5y~qNmO%5buRu@&)q4X4LK5h{%N0;5hPOoug4~_7 zDU(2izSM^#5XX9NIE(}P?*Md&#{4x5p2fS}1&>7MAWy$0px;ox&1vXoU|NEHz@}B` z9}ct-{Y_!D0IC-^1!`4(=UMDu;)A z!Xyr%U2&Mh1~(=K0+EBD`);5>EDN8$HJfKDf#_+d`IlE9el1iYR97HlF_2>vS0HCK z6rOqtui-TWFaS#6D_}6q zk6hX7Icg;KYW?9N7T3|WtAKLgR)pituA75W_#zUp-NS36T!l=h60GO%Y)-Dc1ZoZh z-y5pQpq#aiIw!+WcR^!P1%;>rn*;9x%)}xVI>a>)u)trtwc)5A6gDT${z$=E*gT}H z8RS9$%W#!3Smhm&W(3;P2;|t-1c+%(C6Ho!6JTGv%p{Oun-ieJ=Mn-5wmSi8(Zzdr e1qxX7InE!r--+mD(AqWt0000 literal 0 HcmV?d00001 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