├── .gitignore ├── LICENSE ├── README.md ├── conf.lua ├── example.png ├── examples └── base │ ├── conf.lua │ ├── light │ └── main.lua ├── light ├── draw_light.glsl ├── init.lua ├── light.lua ├── render_light.glsl ├── shadow_map.glsl └── world.lua └── main.lua /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.swp 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Jon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Light & Shadow 2 | ============== 3 | 4 | A simple dynamic light shadow library for LOVE2D 11.2. 5 | 6 | It's easy to use, don't need to create body for shadow, directly generate shadow from canvas/image according alpha. 7 | So it's not fast, and don't support 3D shadow. 8 | If you want a fast and 3D light shadow library, [Light World](https://github.com/xiejiangzhi/light_world.lua) and [Shadow](https://github.com/matiasah/shadows) is better choice. 9 | 10 | 11 | ## Example 12 | 13 | ![Example Image](./example.png) 14 | 15 | 16 | ##Usage 17 | 18 | ``` 19 | local Lib = require 'light' 20 | local light_world = Lib.World.new() 21 | local light 22 | 23 | local lg = love.grpahics 24 | 25 | function love.load() 26 | light = light_world:add(200, 200, 200, 1, 0, 0) 27 | end 28 | 29 | function love.update(dt) 30 | light.x, light.y = love.mouse.getPosition() 31 | end 32 | 33 | function love.draw() 34 | -- reset light world 35 | light_world:begin() 36 | 37 | for i = 1, 10 do 38 | lg.circle('fill', 100 + i * 70, 300, 10) 39 | end 40 | 41 | -- track shadow objects 42 | light_world:track_obj() 43 | for i = 1, 10 do 44 | lg.circle('fill', 100 + i * 70, 300 + i * 50, 30) 45 | end 46 | -- draw background 47 | light_world:track_bg() 48 | 49 | for i = 1, 10 do 50 | lg.circle('fill', 100 + i * 70, 500 + i * 50, 30) 51 | end 52 | 53 | -- draw scene, light and shadow 54 | light_world:finish() 55 | 56 | lg.print('mouse: '..mx..','..my..' light: '..light.x..','..light.y, 50, 50) 57 | end 58 | ``` 59 | 60 | 61 | ## Functions 62 | 63 | ### World 64 | 65 | * `World.new({ env_light = { 0.5, 0.4, 0.5, 0.5 }, alpha_through = 0.3 })` if a object alpha less than `alpha_through`, we will not generate shadow for it 66 | * `World:begin()` reset light world and start track background pixels. 67 | * `World:track_obj()` switch to draw object mode, pixels is affected by light and has shadow. 68 | * `World:track_light_objs()` switch to draw object mode, pixels is always light and has shadow. 69 | * `World:track_bg()` switch to draw background mode, new pixels is affected by light. 70 | * `World:track_light_bg()` switch to draw background mode, the pixels always is light and it has no shadow 71 | * `World:finish()` draw bg, objects, light and shadow to screen. 72 | * `World:add(x, y, radius, r, g, b, a)` add a light to world, return `light` 73 | * `World:remove(light)` remove the light from world 74 | * `World:clear()` remove all lights 75 | * `World:setEnvLight(r, g, b, a)` 76 | * `World:resize(w, h)` you must call it after change window size 77 | * `World:setTranslate(x, y, scale)` you must call it if your applied `love.graphics.translate` or `love.graphics.scale` 78 | * `World.env_tex=[canvas/image]` 79 | * `World.pause=true` Stop light and shadow feature. 80 | 81 | 82 | ### Light 83 | 84 | * `Light:setSize(radius)` resize the light 85 | 86 | 87 | ## Tips 88 | 89 | * If the `env_light` is `1, 1, 1, 1`, no light and shadow can be draw. In an overly bright environment you will not see light and shadow 90 | * If the background alpha is too little, we will not see the light because no object can reflect this light into your eyes 91 | * When you have a lot of light, it will very slow. the light size and quantity will affect performance. (I will try to make it faster) 92 | 93 | 94 | ## TODO 95 | 96 | * Fix shadow for `source_radius` argument. 97 | * Support shadow for semitransparent objects.(shadow map able to save four floats.) 98 | * Optimize according to [here](https://github.com/mattdesl/lwjgl-basics/wiki/2D-Pixel-Perfect-Shadows#optimizations) 99 | -------------------------------------------------------------------------------- /conf.lua: -------------------------------------------------------------------------------- 1 | function love.conf(t) 2 | t.title = "Endless" 3 | t.version = "11.2" 4 | t.window.width = 1400 5 | t.window.height = 900 6 | -- t.window.fullscreen = true 7 | t.window.vsync = false 8 | t.window.fsaa = false 9 | t.window.msaa = 0 10 | t.window.highdpi = false 11 | end 12 | 13 | -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiejiangzhi/light/85b453c92230451d4437f82be1cee21e7b038c8e/example.png -------------------------------------------------------------------------------- /examples/base/conf.lua: -------------------------------------------------------------------------------- 1 | function love.conf(t) 2 | t.title = "Endless" 3 | t.version = "11.2" 4 | t.window.width = 1400 5 | t.window.height = 900 6 | -- t.window.fullscreen = true 7 | t.window.vsync = false 8 | t.window.fsaa = false 9 | t.window.msaa = 0 10 | t.window.highdpi = false 11 | end 12 | 13 | -------------------------------------------------------------------------------- /examples/base/light: -------------------------------------------------------------------------------- 1 | ../../light -------------------------------------------------------------------------------- /examples/base/main.lua: -------------------------------------------------------------------------------- 1 | local Lib = require 'light' 2 | local light_world = Lib.World.new() 3 | local light1, light2, light3, light4 4 | 5 | local lg = love.graphics 6 | 7 | function love.load() 8 | light1 = light_world:add(0, 0, 200, 1, 1, 1) 9 | light2 = light_world:add(0, 0, 200, 0, 1, 0) 10 | light3 = light_world:add(0, 0, 200, 0, 0, 1) 11 | light4 = light_world:add(0, 0, 200, 1, 0, 0) 12 | end 13 | 14 | function love.update(dt) 15 | local mx, my = love.mouse.getPosition() 16 | light1.x, light1.y = mx, my 17 | 18 | light2.x, light2.y = mx - 300, my 19 | light3.x, light3.y = mx + 300, my 20 | light4.x, light4.y = mx, my - 350 21 | end 22 | 23 | function love.draw() 24 | -- reset light world 25 | light_world:begin() 26 | 27 | lg.print('FPS: '..love.timer.getFPS(), 10, 10) 28 | 29 | lg.setColor(0.7, 0.7, 0.7) 30 | lg.rectangle('fill', 0, 0, 400, 700) 31 | lg.setColor(0.3, 0.3, 0.3) 32 | lg.rectangle('fill', 400, 0, 400, 700) 33 | lg.setColor(1, 1, 1) 34 | 35 | for i = 1, 10 do 36 | lg.circle('fill', 100 + i * 70, 300, 10) 37 | end 38 | 39 | -- draw shadow for those objects 40 | light_world:track_obj() 41 | for i = 1, 10 do 42 | lg.circle('fill', 100 + i * 70, 300 + i * 50, 30) 43 | end 44 | -- stop track, new object is background 45 | light_world:track_bg() 46 | 47 | for i = 1, 10 do 48 | lg.circle('fill', 100 + i * 70, 500 + i * 50, 30) 49 | end 50 | 51 | -- draw scene, light and shadow 52 | light_world:finish() 53 | 54 | end 55 | -------------------------------------------------------------------------------- /light/draw_light.glsl: -------------------------------------------------------------------------------- 1 | #pragma language glsl3 2 | 3 | uniform Image bg_tex; 4 | uniform Image obj_tex; 5 | uniform Image light_obj_tex; 6 | uniform Image light_bg_tex; 7 | uniform float alpha_through; 8 | 9 | const float reflectance = 0.1; 10 | 11 | vec4 colorMix(vec4 s, vec4 t) { 12 | s.rgb *= 1 - t.a; 13 | // canvas' rgb default is mixed 14 | // s.rgb += t.rgb * t.a; 15 | s.rgb += t.rgb; 16 | s.a += t.a - t.a * s.a; 17 | 18 | return s; 19 | } 20 | 21 | vec4 effect(vec4 color, Image tex, vec2 tex_coords, vec2 screen_coords) { 22 | vec4 light_color = Texel(tex, tex_coords); 23 | vec4 bg_color = Texel(bg_tex, tex_coords); 24 | vec4 obj_color = Texel(obj_tex, tex_coords); 25 | vec4 light_obj_color = Texel(light_obj_tex, tex_coords); 26 | vec4 light_bg_color = Texel(light_bg_tex, tex_coords); 27 | 28 | bg_color.rgb *= light_color.rgb; 29 | bg_color.rgb += light_color.rgb * reflectance; 30 | bg_color = colorMix(bg_color, light_bg_color); 31 | 32 | if (light_obj_color.a > 0) { 33 | return colorMix( 34 | bg_color, 35 | vec4(light_obj_color.rgb + light_color.rgb * reflectance, light_obj_color.a) 36 | ); 37 | } else if (obj_color.a > alpha_through) { 38 | obj_color.rgb *= light_color.rgb; 39 | obj_color.rgb += light_color.rgb * reflectance; 40 | } else { 41 | obj_color.rgb += light_color.rgb * reflectance; 42 | } 43 | 44 | if (obj_color.a > 0) { 45 | return colorMix(bg_color, obj_color); 46 | } else { 47 | return bg_color; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /light/init.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | 3 | 4 | local conf = { 5 | lib_path = ..., 6 | shadow_map_size = 512 7 | } 8 | 9 | local sub_libs = {} 10 | sub_libs.Light = require((...)..'.light') 11 | sub_libs.World = require((...)..'.world') 12 | 13 | for k, sub_lib in pairs(sub_libs) do 14 | M[k] = sub_lib 15 | if sub_lib.init then sub_lib.init(sub_libs, conf) 16 | end 17 | end 18 | 19 | return M 20 | -------------------------------------------------------------------------------- /light/light.lua: -------------------------------------------------------------------------------- 1 | local M = {} 2 | M.__index = M 3 | 4 | local lg = love.graphics 5 | 6 | local lib_conf 7 | 8 | function M.init(sub_libs, conf) 9 | lib_conf = conf 10 | end 11 | 12 | -- opts.source_radius: the source of the light 13 | function M.new(x, y, radius, r, g, b, a, opts) 14 | local light = setmetatable({ 15 | x = x, y = y, 16 | radius = radius, 17 | r = r, g = g, b = b, a = a or 1, 18 | source_radius = 0 19 | }, M) 20 | 21 | if opts then 22 | for k, v in pairs(opts) do light[k] = v end 23 | end 24 | 25 | local size = radius * 2 26 | light.size = size 27 | light.scale = lib_conf.shadow_map_size / size 28 | 29 | light.render_canvas = lg.newCanvas(size, size) 30 | 31 | return light 32 | end 33 | 34 | function M:setSize(radius) 35 | self.radius = radius 36 | self.size = radius * 2 37 | self.scale = lib_conf.shadow_map_size / self.size 38 | self.render_canvas = lg.newCanvas(self.size, self.size) 39 | end 40 | 41 | return M 42 | -------------------------------------------------------------------------------- /light/render_light.glsl: -------------------------------------------------------------------------------- 1 | #define PI 3.14159265359 2 | 3 | uniform vec2 resolution; 4 | uniform Image obj_tex; 5 | uniform float alpha_through; 6 | uniform float source_radius; 7 | 8 | // use light (1, 0, 0) to item (0, 0, 1) 9 | // item will get color: item * light + light * reflectance 10 | const float reflectance = 0.3; 11 | 12 | //sample from the 1D distance map 13 | float sampleTex(Image tex, vec2 coord, float r) { 14 | return step(r, Texel(tex, coord).r); 15 | } 16 | 17 | float blurShadow(Image tex, vec2 tc, float dist, float blur, float shadow) { 18 | float sum = 0.0; 19 | sum += sampleTex(tex, vec2(tc.x - 4.0*blur, tc.y), dist) * 0.05; 20 | sum += sampleTex(tex, vec2(tc.x - 3.0*blur, tc.y), dist) * 0.09; 21 | sum += sampleTex(tex, vec2(tc.x - 2.0*blur, tc.y), dist) * 0.12; 22 | sum += sampleTex(tex, vec2(tc.x - 1.0*blur, tc.y), dist) * 0.15; 23 | sum += shadow * 0.16; 24 | sum += sampleTex(tex, vec2(tc.x + 1.0*blur, tc.y), dist) * 0.15; 25 | sum += sampleTex(tex, vec2(tc.x + 2.0*blur, tc.y), dist) * 0.12; 26 | sum += sampleTex(tex, vec2(tc.x + 3.0*blur, tc.y), dist) * 0.09; 27 | sum += sampleTex(tex, vec2(tc.x + 4.0*blur, tc.y), dist) * 0.05; 28 | 29 | return sum; 30 | } 31 | 32 | vec4 effect(vec4 color, Image tex, vec2 tex_coords, vec2 screen_coords) { 33 | // Transform rectangular to polar coordinates. 34 | vec2 norm = tex_coords.st * 2.0 - 1.0; 35 | float theta = atan(norm.y, norm.x); 36 | float dist = length(norm); 37 | float coord = (theta + PI) / (2.0 * PI); 38 | float light_rate = max(0.0, dist - source_radius) / (1.0 - source_radius); 39 | 40 | // The tex coordinate to sample our 1D lookup texture. 41 | //always 0.0 on y axis 42 | vec2 tc = vec2(coord, 0.0); 43 | 44 | 45 | // Multiply the blur amount by our distance from center. 46 | // this leads to more blurriness as the shadow "fades away" 47 | float blur = (1./resolution.x) * smoothstep(0., 1., light_rate); 48 | 49 | float shadow = sampleTex(tex, tc, dist); 50 | vec4 obj_col = Texel(obj_tex, vec2(tex_coords.x, 1 - tex_coords.y)); 51 | 52 | if (obj_col.a == 0) { 53 | // Use a simple gaussian blur. 54 | shadow = blurShadow(tex, tc, dist, blur, shadow); 55 | } 56 | 57 | // Sum of 1.0 -> in light, 0.0 -> in shadow. 58 | // Multiply the summed amount by our distance, which gives us a radial falloff. 59 | float sr = smoothstep(1.0, 0.0, light_rate); 60 | if (obj_col.a > alpha_through) { 61 | return obj_col * color * sr + color * reflectance * sr; 62 | } else { 63 | return vec4(color.rgb, color.a * shadow * sr); 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /light/shadow_map.glsl: -------------------------------------------------------------------------------- 1 | #define PI 3.14159265359 2 | 3 | uniform vec2 resolution; 4 | uniform float alpha_through; 5 | uniform float source_radius; 6 | 7 | vec4 effect(vec4 color, Image img, vec2 texture_coords, vec2 screen_coords) { 8 | float dist = 1.0; 9 | 10 | // Iterate through the occluder map's y-axis. 11 | for (float y = 0.0; y < resolution.y; y++) { 12 | // Rectangular to polar 13 | vec2 norm = vec2(texture_coords.s, y / resolution.y) * 2.0 - 1.0; 14 | float theta = PI * 1.5 + norm.x * PI; 15 | float r = (1.0 + norm.y) * 0.5; 16 | if (r <= source_radius) { continue; } 17 | 18 | //coord which we will sample from occlude map 19 | vec2 coord = vec2(-r * sin(theta), -r * cos(theta)) / 2.0 + 0.5; 20 | 21 | //sample the occlusion map 22 | vec4 data = Texel(img, coord); 23 | 24 | //the current distance is how far from the top we've come 25 | float dst = y / resolution.y; 26 | 27 | //if we've hit an opaque fragment (occluder), then get new distance 28 | //if the new distance is below the current, then we'll use that for our ray 29 | float caster = data.a; 30 | if (caster > alpha_through) { 31 | dist = min(dist, dst); 32 | break; 33 | } 34 | } 35 | 36 | return vec4(vec3(dist), 1.0); 37 | } 38 | -------------------------------------------------------------------------------- /light/world.lua: -------------------------------------------------------------------------------- 1 | -- https://github.com/mattdesl/lwjgl-basics/wiki/2D-Pixel-Perfect-Shadows 2 | 3 | local M = {} 4 | M.__index = M 5 | local private = {} 6 | 7 | local lg = love.graphics 8 | 9 | local Light 10 | 11 | local shadow_map_size 12 | local shadow_area_canvas, shadow_map_canvas 13 | local shadow_map_shader, render_light_shader, draw_light_shader 14 | 15 | function M.init(sub_libs, conf) 16 | Light = sub_libs.Light 17 | local lib_path = conf.lib_path 18 | 19 | shadow_map_size = conf.shadow_map_size 20 | shadow_area_canvas = lg.newCanvas(shadow_map_size, shadow_map_size) 21 | shadow_map_canvas = lg.newCanvas(shadow_map_size, 1) 22 | 23 | local glsl_path = lib_path:gsub('%.', '/') 24 | shadow_map_shader = lg.newShader(glsl_path..'/shadow_map.glsl') 25 | render_light_shader = lg.newShader(glsl_path..'/render_light.glsl') 26 | draw_light_shader = lg.newShader(glsl_path..'/draw_light.glsl') 27 | end 28 | 29 | function M.new(opts) 30 | local obj = setmetatable({}, M) 31 | 32 | obj.env_tex = nil 33 | obj.env_light = { 0.5, 0.5, 0.5, 0.5 } 34 | 35 | -- coord translate 36 | obj.x = 0 37 | obj.y = 0 38 | obj.scale = 1 39 | obj.w, obj.h = lg.getDimensions() 40 | obj.alpha_through = 0.3 41 | 42 | obj:resize(obj.w, obj.h) 43 | 44 | for k, v in pairs(opts or {}) do 45 | if obj[k] ~= nil then 46 | obj[k] = v 47 | else 48 | error("Invalid key "..k) 49 | end 50 | end 51 | 52 | obj.lights = {} 53 | obj.pause = nil 54 | 55 | return obj 56 | end 57 | 58 | function M:add(...) 59 | local light = Light.new(...) 60 | local idx = #self.lights + 1 61 | self.lights[idx] = light 62 | light.idx = idx 63 | return light 64 | end 65 | 66 | function M:remove(light) 67 | if self.lights[light.idx] == light then 68 | table.remove(self.lights, light.idx) 69 | end 70 | end 71 | 72 | function M:setEnvLight(r, g, b, a) 73 | self.env_light = { r, g, b, a } 74 | end 75 | 76 | function M:begin() 77 | if self.pause then return end 78 | self.last_canvas = lg.getCanvas() 79 | lg.setCanvas(self.object_canvas) 80 | lg.clear() 81 | 82 | lg.setCanvas(self.light_obj_canvas) 83 | lg.clear() 84 | 85 | lg.setCanvas(self.light_bg_canvas) 86 | lg.clear() 87 | 88 | lg.setCanvas(self.scene_canvas) 89 | lg.clear() 90 | end 91 | 92 | function M:track_obj() 93 | if self.pause then return end 94 | lg.setCanvas(self.object_canvas) 95 | end 96 | 97 | function M:track_bg() 98 | if self.pause then return end 99 | lg.setCanvas(self.scene_canvas) 100 | end 101 | 102 | function M:track_light_objs() 103 | if self.pause then return end 104 | lg.setCanvas(self.light_obj_canvas) 105 | end 106 | 107 | function M:track_light_bg() 108 | if self.pause then return end 109 | lg.setCanvas(self.light_bg_canvas) 110 | end 111 | 112 | function M:finish() 113 | if self.pause then return end 114 | lg.setBlendMode('alpha') 115 | lg.setCanvas(self.object_canvas) 116 | lg.draw(self.light_obj_canvas) 117 | lg.setCanvas(self.last_canvas) 118 | 119 | private.reset_light_buffer(self.light_buffer, self) 120 | 121 | local sx, sy 122 | for i, light in ipairs(self.lights) do 123 | private.cutLightArea(self.object_canvas, light, self.x, self.y, self.scale) 124 | private.generateShadowMap(light, self.alpha_through) 125 | private.generateLight(light, self.scale, self.alpha_through) 126 | sx = light.x - light.radius 127 | sy = light.y - light.radius + light.size 128 | 129 | private.drawto(self.light_buffer, nil, function() 130 | lg.setBlendMode("add") 131 | lg.scale(self.scale) 132 | lg.translate(self.x, self.y) 133 | lg.draw(light.render_canvas, sx, sy, 0, 1, -1) 134 | lg.setBlendMode("alpha") 135 | end) 136 | end 137 | 138 | draw_light_shader:send('bg_tex', self.scene_canvas) 139 | draw_light_shader:send('light_bg_tex', self.light_bg_canvas) 140 | draw_light_shader:send('obj_tex', self.object_canvas) 141 | draw_light_shader:send('light_obj_tex', self.light_obj_canvas) 142 | draw_light_shader:send('alpha_through', self.alpha_through) 143 | private.drawto(nil, draw_light_shader, function() 144 | lg.setBlendMode("alpha", "premultiplied") 145 | lg.draw(self.light_buffer) 146 | lg.setBlendMode("alpha", "alphamultiply") 147 | end) 148 | end 149 | 150 | function M:resize(w, h) 151 | assert(w > 0 and h > 0, "Invalid w or h, cannot <= 0") 152 | 153 | self.w, self.h = w, h 154 | self.scene_canvas = lg.newCanvas(w, h) 155 | self.object_canvas = lg.newCanvas(w, h) 156 | self.light_buffer = lg.newCanvas(w, h) 157 | 158 | self.light_obj_canvas = lg.newCanvas(w, h) 159 | self.light_bg_canvas = lg.newCanvas(w, h) 160 | end 161 | 162 | function M:setTranslate(x, y, scale) 163 | self.x, self.y = x, y 164 | if scale then self.scale = scale end 165 | end 166 | 167 | -- Clear all lights. 168 | function M:clear() 169 | self.lights = {} 170 | end 171 | 172 | 173 | ----------------------------- 174 | 175 | function private.cutLightArea(canvas, light, ox, oy, scale) 176 | local sx, sy = (light.x - light.radius + ox) * scale, (light.y - light.radius + oy) * scale 177 | scale = shadow_map_size / (light.size * scale) 178 | 179 | -- lg.print(''..sx..','..sy..', scale: '..light.scale..' -> '..scale, 10, 10) 180 | 181 | private.drawto(shadow_area_canvas, nil, function() 182 | lg.clear() 183 | lg.push() 184 | lg.scale(scale) 185 | lg.translate(-sx, -sy) 186 | lg.draw(canvas) 187 | lg.pop() 188 | end) 189 | 190 | -- lg.draw(shadow_area_canvas) 191 | -- lg.rectangle('line', 0, 0, shadow_area_canvas:getDimensions()) 192 | -- lg.rectangle('line', light.x - light.radius, light.y - light.radius, light.size, light.size) 193 | end 194 | 195 | function private.generateShadowMap(light, alpha_through) 196 | private.drawto(shadow_map_canvas, shadow_map_shader, function() 197 | lg.clear() 198 | shadow_map_shader:send("resolution", { light.size, light.size }); 199 | shadow_map_shader:send("alpha_through", alpha_through); 200 | shadow_map_shader:send("source_radius", light.source_radius / light.radius); 201 | lg.draw(shadow_area_canvas) 202 | end) 203 | end 204 | 205 | function private.generateLight(light, scale, alpha_through) 206 | render_light_shader:send('obj_tex', shadow_area_canvas) 207 | render_light_shader:send("resolution", { light.size, light.size }); 208 | render_light_shader:send("alpha_through", alpha_through); 209 | render_light_shader:send("source_radius", light.source_radius / light.radius); 210 | private.drawto(light.render_canvas, render_light_shader, function() 211 | lg.clear() 212 | lg.setColor(light.r, light.g, light.b, light.a) 213 | lg.draw(shadow_map_canvas, 0, 0, 0, 1 / light.scale, light.size) 214 | lg.setColor(1, 1, 1, 1) 215 | end) 216 | end 217 | 218 | function private.drawto(canvas, shader, fn, fn_args) 219 | lg.push() 220 | lg.origin() 221 | lg.setCanvas(canvas) 222 | lg.setShader(shader) 223 | 224 | fn() 225 | 226 | lg.setShader() 227 | lg.setCanvas() 228 | lg.pop() 229 | end 230 | 231 | function private.reset_light_buffer(canvas, world) 232 | lg.setCanvas(canvas) 233 | lg.clear() 234 | 235 | local r, g, b, a = lg.getColor() 236 | if world.env_light then 237 | lg.setColor(unpack(world.env_light)) 238 | end 239 | 240 | if world.env_tex then 241 | local cw, ch = canvas:getDimensions() 242 | local tw, th = world.env_tex:getDimensions() 243 | lg.draw(world.env_tex, 0, 0, 0, cw / tw, ch / th) 244 | else 245 | lg.rectangle('fill', 0, 0, canvas:getDimensions()) 246 | end 247 | 248 | lg.setColor(r, g, b, a) 249 | lg.setCanvas() 250 | end 251 | 252 | function private.reset_buffer_canvas() 253 | end 254 | 255 | return M 256 | -------------------------------------------------------------------------------- /main.lua: -------------------------------------------------------------------------------- 1 | local Light = require 'light' 2 | local private = {} 3 | 4 | local light_world = Light.World.new({ 5 | env_light = { 0.3, 0.3, 0.3, 1 }, 6 | alpha_through = 0.5, 7 | }) 8 | 9 | local lg = love.graphics 10 | 11 | local light 12 | local mx, my = 0, 0 13 | local ox, oy, scale = 500, 100, 0.5 14 | 15 | function love.load() 16 | light_world:setTranslate(ox, oy, scale) 17 | light = light_world:add(200, 200, 500, 1, 0.1, 0.1, 1, { source_radius = 10 }) 18 | light.source_radius = 35 19 | end 20 | 21 | function love.update(dt) 22 | mx, my = love.mouse.getPosition() 23 | light.x = mx / scale - ox 24 | light.y = my / scale - oy 25 | light:setSize(300 + math.sin(love.timer.getTime() / 2) * 200) 26 | end 27 | 28 | function love.draw() 29 | lg.setColor(1, 1, 0.3, 1) 30 | lg.rectangle('fill', 0, 0, 1300, 450) 31 | 32 | light_world:begin() 33 | 34 | lg.setColor(0.3, 0.3, 0.3, 0.7) 35 | lg.rectangle('fill', 0, 0, 500, 400) 36 | lg.setColor(1, 1, 1, 0.7) 37 | lg.rectangle('fill', 500, 0, 500, 400) 38 | lg.setColor(0.21, 0.21, 0.21, 1) 39 | lg.rectangle('fill', 1000, 0, 100, 400) 40 | 41 | light_world:track_light_bg() 42 | lg.setColor(1, 1, 1, 1) 43 | lg.print('FPS: '..love.timer.getFPS(), 10, 10) 44 | lg.print('mouse: '..mx..','..my..' light: '..light.x..','..light.y, 10, 50) 45 | 46 | light_world:track_obj() 47 | private.translate() 48 | 49 | for i = 1, 10 do 50 | lg.circle('fill', 100 + i * 70, 100, 10) 51 | lg.circle('fill', 100 + i * 80, 200, 20) 52 | lg.circle('fill', 100 + i * 90, 300, 30) 53 | lg.circle('fill', 100 + i * 100, 400, 40) 54 | end 55 | lg.setColor(1, 0, 0, 0.5) 56 | lg.circle('fill', 300, 100, 50) 57 | lg.circle('fill', 300, 700, 50) 58 | 59 | lg.setColor(1, 0, 0, 0.7) 60 | lg.circle('fill', 500, 100, 50) 61 | lg.circle('fill', 500, 700, 50) 62 | 63 | lg.setColor(0, 1, 0, 1) 64 | lg.circle('fill', 900, 300, 50) 65 | light_world:track_bg() 66 | lg.setColor(0, 0, 1, 1) 67 | lg.circle('fill', 900, 700, 50) 68 | light_world:track_obj() 69 | lg.setColor(1, 0, 0, 1) 70 | lg.circle('fill', 1000, 500, 50) 71 | 72 | light_world:track_light_objs() 73 | lg.setColor(0.7, 0.7, 0.1, 1) 74 | lg.print("Hello Light", 1000, 0, 5, 10, 10) 75 | 76 | light_world:track_obj() 77 | lg.print("Hello Light", 1500, 0, 5, 10, 10) 78 | 79 | light_world:track_light_bg() 80 | lg.setColor(1, 1, 0.5, 1) 81 | lg.print("Hello Light", 300, -200, 6, 10, 10) 82 | lg.setColor(1, 1, 1, 1) 83 | 84 | private.reset() 85 | 86 | light_world:finish() 87 | 88 | -- lg.rectangle('line', 150, 150, 100, 100) 89 | private.translate() 90 | -- lg.rectangle('line', 150, 150, 100, 100) 91 | lg.rectangle('line', light.x - light.radius, light.y - light.radius, light.size, light.size) 92 | private.reset() 93 | end 94 | 95 | function love.mousepressed(x, y, btn) 96 | if btn == 1 then 97 | light = light_world:add(x / scale - ox, y / scale - oy, 500, love.random_color()) 98 | elseif btn == 2 then 99 | light_world:clear() 100 | end 101 | end 102 | 103 | function love.resize(w, h) 104 | if w > 0 and h > 0 then 105 | light_world:resize(w, h) 106 | end 107 | end 108 | 109 | function love.random_color() 110 | local r = love.math.random() 111 | 112 | if r < 0.25 then 113 | return 1, 0.1, 0.1, 1 114 | elseif r < 0.5 then 115 | return 0.1, 1, 0.1, 1 116 | elseif r < 0.75 then 117 | return 0.1, 0.1, 1, 1 118 | else 119 | return 1, 1, 1, 1 120 | end 121 | end 122 | 123 | function private.translate() 124 | lg.push() 125 | lg.scale(scale) 126 | lg.translate(ox, oy) 127 | lg.rotate(0.5) 128 | end 129 | 130 | function private.reset() 131 | lg.pop() 132 | end 133 | 134 | --------------------------------------------------------------------------------