Initial Commit

* Basic Game Implementation
This commit is contained in:
2024-08-07 02:48:17 +05:30
commit 6baf6c48b0
26 changed files with 2560 additions and 0 deletions

335
libs/lovebpm.lua Normal file
View File

@@ -0,0 +1,335 @@
--
-- lovebpm
--
-- Copyright (c) 2016 rxi
--
-- This library is free software; you can redistribute it and/or modify it
-- under the terms of the MIT license. See LICENSE for details.
--
local lovebpm = { _version = "0.0.0" }
local Track = {}
Track.__index = Track
function lovebpm.newTrack()
local self = setmetatable({}, Track)
self.source = nil
self.offset = 0
self.volume = 1
self.pitch = 1
self.looping = false
self.listeners = {}
self.period = 60 / 120
self.lastBeat = nil
self.lastUpdateTime = nil
self.lastSourceTime = 0
self.time = 0
self.totalTime = 0
self.dtMultiplier = 1
return self
end
function lovebpm.detectBPM(filename, opts)
-- Init options table
opts = opts or {}
local t = { minbpm = 75, maxbpm = 300 }
for k, v in pairs(t) do
t[k] = opts[k] or v
end
opts = t
-- Load data
local data = filename
if type(data) == "string" then
data = love.sound.newSoundData(data)
else
data = filename
end
local channels = data:getChannels()
local samplerate = data:getSampleRate()
-- Gets max amplitude over a number of samples at `n` seconds
local function getAmplitude(n)
local count = samplerate * channels / 200
local at = n * channels * samplerate
if at + count > data:getSampleCount() then
return 0
end
local a = 0
for i = 0, count - 1 do
a = math.max(a, math.abs( data:getSample(at + i) ))
end
return a
end
-- Get track duration and init results table
local dur = data:getDuration("seconds")
local results = {}
-- Get maximum allowed BPM
local step = 8
local n = (dur * opts.maxbpm / 60)
n = math.floor(n / step) * step
-- Fill table with BPMs and their average on-the-beat amplitude until the
-- minimum allowed BPM is reached
while true do
local bpm = n / dur * 60
if bpm < opts.minbpm then
break
end
local acc = 0
for i = 0, n - 1 do
acc = acc + getAmplitude(dur / n * i)
end
-- Round BPM to 3 decimal places
bpm = math.floor(bpm * 1000 + .5) / 1000
-- Add result to table
table.insert(results, { bpm = bpm, avg = acc / n })
n = n - step
end
-- Sort table by greatest average on-the-beat amplitude. The one with the
-- greatest average is assumed to be the correct bpm
table.sort(results, function(a, b) return a.avg > b.avg end)
return results[1].bpm
end
function Track:load(filename)
-- Deinit old source
self:stop()
-- Init new source
-- "static" mode is used here instead of "stream" as the time returned by
-- :tell() seems to go out of sync after the first loop otherwise
self.source = love.audio.newSource(filename, "static")
self:setLooping(self.looping)
self:setVolume(self.volume)
self:setPitch(self.pitch)
self.totalTime = self.source:getDuration("seconds")
self:stop()
return self
end
function Track:setBPM(n)
self.period = 60 / n
return self
end
function Track:setOffset(n)
self.offset = n or 0
return self
end
function Track:setVolume(volume)
self.volume = volume or 1
if self.source then
self.source:setVolume(self.volume)
end
return self
end
function Track:setPitch(pitch)
self.pitch = pitch or 1
if self.source then
self.source:setPitch(self.pitch)
end
return self
end
function Track:setLooping(loop)
self.looping = loop
if self.source then
self.source:setLooping(self.looping)
end
return self
end
function Track:on(name, fn)
self.listeners[name] = self.listeners[name] or {}
table.insert(self.listeners[name], fn)
return self
end
function Track:emit(name, ...)
if self.listeners[name] then
for i, fn in ipairs(self.listeners[name]) do
fn(...)
end
end
return self
end
function Track:play(restart)
if not self.source then return self end
if self.restart then
self:stop()
end
self.source:play()
return self
end
function Track:pause()
if not self.source then return self end
self.source:pause()
return self
end
function Track:stop()
self.lastBeat = nil
self.time = 0
self.lastUpdateTime = nil
self.lastSourceTime = 0
if self.source then
self.source:stop()
end
return self
end
function Track:setTime(n)
if not self.source then return end
self.source:seek(n)
self.time = n
self.lastSourceTime = n
self.lastBeat = self:getBeat() - 1
return self
end
function Track:setBeat(n)
return self:setTime(n * self.period)
end
function Track:getTotalTime()
return self.totalTime
end
function Track:getTotalBeats()
if not self.source then
return 0
end
return math.floor(self:getTotalTime() / self.period + 0.5)
end
function Track:getTime()
return self.time
end
function Track:getBeat(multiplier)
multiplier = multiplier or 1
local period = self.period * multiplier
return math.floor(self.time / period), (self.time % period) / period
end
function Track:update()
if not self.source then return self end
-- Get delta time: getTime() is used for time-keeping as the value returned by
-- :tell() is updated at a potentially lower rate than the framerate
local t = love.timer.getTime()
local dt = self.lastUpdateTime and (t - self.lastUpdateTime) or 0
self.lastUpdateTime = t
-- Set new time
local time
if self.source:isPlaying() then
time = self.time + dt * self.dtMultiplier * self.pitch
else
time = self.time
end
-- Get source time and apply offset
local sourceTime = self.source:tell("seconds")
sourceTime = sourceTime + self.offset
-- If the value returned by the :tell() function has updated we check to see
-- if we are in sync within an allowed threshold -- if we're out of sync we
-- adjust the dtMultiplier to resync gradually
if sourceTime ~= self.lastSourceTime then
local diff = time - sourceTime
-- Check if the difference is beyond the threshold -- If the difference is
-- too great we assume the track has looped and treat it as being within the
-- threshold
if math.abs(diff) > 0.01 and math.abs(diff) < self.totalTime / 2 then
self.dtMultiplier = math.max(0, 1 - diff * 2)
else
self.dtMultiplier = 1
end
self.lastSourceTime = sourceTime
end
-- Assure time is within proper bounds in case the offset or added
-- frame-delta-time made it overshoot
time = time % self.totalTime
-- Calculate deltatime and emit update event; set time
if self.lastBeat then
local t = time
if t < self.time then
t = t + self.totalTime
end
self:emit("update", t - self.time)
else
self:emit("update", 0)
end
self.time = time
-- Current beat doesn't match last beat?
local beat = self:getBeat()
local last = self.lastBeat
if beat ~= last then
-- Last beat is set here as one of the event handlers can potentially set it
-- by calling :setTime() or :setBeat()
self.lastBeat = beat
-- Assure that the `beat` event is done once for each beat, even in cases
-- where more than one beat has passed since the last update, or the song
-- has looped
local total = self:getTotalBeats()
local b = beat
local x = 0
if last then
x = last + 1
-- If the last beat is greater than the current beat then the song has
-- reached the end: if we're looping then set the current beat to after
-- the tracks's end so incrementing towards it still works.
if x > b then
if self.looping then
self:emit("loop")
b = b + total
else
self:emit("end")
self:stop()
end
end
end
-- Emit beat event for each passed beat
while x <= b do
self:emit("beat", x % total)
x = x + 1
end
end
return self
end
return lovebpm

580
libs/moonshine/README.md Normal file
View File

@@ -0,0 +1,580 @@
# moonshine
Chainable post-processing shaders for LÖVE.
## Overview
* [Getting started](#getting-started)
* [General usage](#general-usage)
* [List of effects](#list-of-effects)
* [Writing effects](#writing-effects)
* [License](#license)
<a name="getting-started"></a>
## Getting started
Clone this repository into your game folder:
git clone https://github.com/vrld/moonshine.git
This will create the folder `moonshine`.
In your `main.lua`, or wherever you load your libraries, add the following:
```lua
local moonshine = require 'moonshine'
```
Create and parametrize the post-processing effect in `love.load()`, for example:
```lua
function love.load()
effect = moonshine(moonshine.effects.filmgrain)
.chain(moonshine.effects.vignette)
effect.filmgrain.size = 2
end
```
Lastly, wrap the things you want to be drawn with the effect inside a function:
```lua
function love.draw()
effect(function()
love.graphics.rectangle("fill", 300,200, 200,200)
end)
end
```
When you package your game for release, you might want consider deleting the
(hidden) `.git` folder in the moonshine directory.
<a name="general-usage"></a>
## General usage
The main concept behind moonshine are chains. A chain consists of one or more
effects. Effects that come later in the chain will be applied to the result of
the effects that come before. In the example above, the vignette is drawn on
top of the filmgrain.
### Chains
Chains are created using the `moonshine.chain` function:
```lua
chain = moonshine.chain(effect)
```
For convenience, `moonshine(effect)` is an alias to `moonshine.chain(effect)`.
You can add new effects to a chain using
```lua
chain = chain.chain(another_effect)
```
or using `chain.next()`, which is an alias to `chain.chain()`.
As the function returns the chain, you can specify your whole chain in one go,
as shown in the example above.
### Effects and effect parameters
The effects that come bundled with moonshine (see [List of effects](#list-of-effects))
are accessed by `chain.effects.<effect-name>`, e.g.,
```lua
moonshine.effects.glow
```
Most effects are parametrized to change how they look. In the example above,
the size of the grains was set to 2 pixels (the default is 1 pixel).
Effect parameters are set by first specifying the name of the effect and then
the name of the parameter:
```lua
chain.<effect>.<parameter> = <value>
```
For example, if `chain` contained the `glow` and `crt` effects, you can set the
glow `strength` parameter and crt `distortionFactor` parameter as such:
```lua
chain.glow.strength = 10
chain.crt.distortionFactor = {1.06, 1.065}
```
Because you likely initialize a bunch of parameters at once, you can set all
parameters with the special key `parameters` (or `params` or `settings`). This
is equivalent to the above:
```lua
chain.parameters = {
glow = {strength = 10},
crt = {distortionFactor = {1.06, 1.065}},
}
```
Note that this will only set the parameters specified in the table. The crt
parameter `feather`, for example, will be left untouched.
### Drawing effects
Creating effects and setting parameters is fine, but not very useful on its
own. You also need to apply it to something. This is done using `chain.draw()`:
```lua
chain.draw(func, ...)
```
This will apply the effect to everything that is drawn inside `func(...)`.
Everything that is drawn outside of `func(...)` will not be affected. For
example,
```lua
love.graphics.draw(img1, 0,0)
chain.draw(function()
love.graphics.draw(img2, 200,0)
end)
love.graphics.draw(img3, 400,0)
```
will apply the effect to `img2`, but not to `img1` and `img3`. Note that some
effects (like filmgrain) draw on the whole screen. So if in this example `chain`
would consist of a gaussianblur and filmgrain effect, `img1` will be covered
with grain, but will not be blurred, `img2` will get both effects, and `img3`
will be left untouched.
Similar to chain creation, `chain(func, ...)` is an alias to the more verbose
`chain.draw(func, ...)`.
### Temporarily disabling effects
You can disable effects in a chain by using `chain.disable(names...)` and
`chain.enable(names...)`.
For example,
```lua
effect = moonshine(moonshine.effects.boxblur)
.chain(moonshine.effects.filmgrain)
.chain(moonshine.effects.vignette)
effect.disable("boxblur", "filmgrain")
effect.enable("filmgrain")
```
would first disable the boxblur and filmgrain effect, and then enable the
filmgrain again.
Note that the effects are still in the chain, they are only not drawn.
### Canvas size
You can change the size of the internal canvas, for example when the window was
resized, by calling `chain.resize(width, height)`.
Do this anytime you want, but best not during `chain.draw()`.
You can also specify the initial canvas size by starting the chain like this:
```lua
effect = moonshine(400,300, moonshine.effects.vignette)
```
That is, you specify the width and height before the first effect in the chain.
### Is this efficient?
Of course, using moonshine is not as efficient as writing your own shader that
does all the effects you want in the least amount of passes, but moonshine
tries to minimize the overhead.
On the other hand, you don't waste time writing the same shader over and over
again when using moonshine: You're trading a small amount of computation time
for a large amount of development time.
<a name="list-of-effects"></a>
## List of effects
Currently, moonshine contains the following effects (in alphabetical order):
* [boxblur](#effect-boxblur): simple blurring
* [chromasep](#effect-chromasep): cheap/fake chromatic aberration
* [colorgradesimple](#effect-colorgradesimple): weighting of color channels
* [crt](#effect-crt): crt/barrel distortion
* [desaturate](#effect-desaturate): desaturation and tinting
* [dmg](#effect-dmg): Gameboy and other four color palettes
* [fastgaussianblur](#effect-fastgaussianblur): faster Gaussian blurring
* [filmgrain](#effect-filmgrain): image noise
* [gaussianblur](#effect-gaussianblur): Gaussian blurring
* [glow](#effect-glow): aka (light bloom
* [godsray](#effect-godsray): aka light scattering
* [pixelate](#effect-pixelate): sub-sampling (for that indie look)
* [posterize](#effect-posterize): restrict number of colors
* [scanlines](#effect-scanlines): horizontal lines
* [sketch](#effect-sketch): simulate pencil drawings
* [vignette](#effect-vignette): shadow in the corners
<a name="effect-boxblur"></a>
### boxblur
```lua
moonshine.effects.boxblur
```
**Parameters:**
Name | Type | Default
-----|------|--------
radius | number or table of numbers | {3,3}
radius_x | number | 3
radius_y | number | 3
<a name="effect-chromasep"></a>
### chromasep
```lua
moonshine.effects.chromasep
```
**Parameters:**
Name | Type | Default
-----|------|--------
angle | number (in radians) | 0
radius | number | 0
<a name="effect-colorgradesimple"></a>
### colorgradesimple
```lua
moonshine.effects.colorgradesimple
```
**Parameters:**
Name | Type | Default
-----|------|--------
factors | table of numbers | {1,1,1}
<a name="effect-crt"></a>
### crt
```lua
moonshine.effects.crt
```
**Parameters:**
Name | Type | Default
-----|------|--------
distortionFactor | table of numbers | {1.06, 1.065}
x | number | 1.06
y | number | 1.065
scaleFactor | number or table of numbers | {1,1}
feather | number | 0.02
<a name="effect-desaturate"></a>
### desaturate
```lua
moonshine.effects.desaturate
```
**Parameters:**
Name | Type | Default
-----|------|--------
tint | color / table of numbers | {255,255,255}
strength | number between 0 and 1 | 0.5
<a name="effect-dmg"></a>
### dmg
```lua
moonshine.effects.dmg
```
Name | Type | Default
-----|------|--------
palette | number or string or table of table of numbers | "default"
DMG ships with 7 palettes:
1. `default`
2. `dark_yellow`
3. `light_yellow`
4. `green`
5. `greyscale`
6. `stark_bw`
7. `pocket`
Custom palettes must be in the format `{{R,G,B}, {R,G,B}, {R,G,B}, {R,G,B}}`,
where `R`, `G`, and `B` are numbers between `0` and `255`.
<a name="effect-fastgaussianblur"></a>
### fastgaussianblur
```lua
moonshine.effects.fastgaussianblur
```
**Parameters:**
Name | Type | Default
-----|------|--------
taps | odd number >= 3 | 7 | (amount of blur)
offset | number | 1
sigma | number | -1
<a name="effect-filmgrain"></a>
### filmgrain
```lua
moonshine.effects.filmgrain
```
**Parameters:**
Name | Type | Default
-----|------|--------
opacity | number | 0.3
size | number | 1
<a name="effect-gaussianblur"></a>
### gaussianblur
```lua
moonshine.effects.gaussianblur
```
**Parameters:**
Name | Type | Default
-----|------|--------
sigma | number | 1 | (amount of blur)
<a name="effect-glow"></a>
### glow
```lua
moonshine.effects.glow
```
**Parameters:**
Name | Type | Default
-----|------|--------
min_luma | number between 0 and 1 | 0.7
strength | number >= 0 | 5
<a name="effect-godsray"></a>
### godsray
```lua
moonshine.effects.godsray
```
**Parameters:**
Name | Type | Default
-----|------|--------
exposire | number between 0 and 1 | 0.5
decay | number between 0 and 1 | 0.95
density | number between 0 and 1 | 0.05
weight | number between 0 and 1 | 0.5
light_position | table of two numbers | {0.5, 0.5}
light_x | number | 0.5
light_y | number | 0.5
samples | number >= 1 | 70
<a name="effect-pixelate"></a>
### pixelate
```lua
moonshine.effects.pixelate
```
**Parameters:**
Name | Type | Default
-----|------|--------
size | number or table of two numbers | {5,5}
feedback | number between 0 and 1 | 0
<a name="effect-posterize"></a>
### posterize
```lua
moonshine.effects.posterize
```
**Parameters:**
Name | Type | Default
-----|------|--------
num_bands | number >= 1 | 3
<a name="effect-scanlines"></a>
### scanlines
```lua
moonshine.effects.scanlines
```
**Parameters:**
Name | Type | Default
-----|------|--------
width | number | 2
frequency | number | screen-height
phase | number | 0
thickness | number | 1
opacity | number | 1
color | color / table of numbers | {0,0,0}
<a name="effect-sketch"></a>
### sketch
```lua
moonshine.effects.sketch
```
**Parameters:**
Name | Type | Default
-----|------|--------
amp | number | 0.0007
center | table of numbers | {0,0}
<a name="effect-vignette"></a>
### vignette
```lua
moonshine.effects.vignette
```
**Parameters:**
Name | Type | Default
-----|------|--------
radius | number > 0 | 0.8
softness | number > 0 | 0.5
opacity | number > 0 | 0.5
color | color / table of numbers | {0,0,0}
<a name="effect-fog"></a>
### fog
```lua
moonshine.effects.fog
```
**Parameters:**
Name | Type | Default
-----|------|--------
fog_color | color/table of numbers | {0.35, 0.48, 0.95}
octaves | number > 0 | 4
speed | vec2/table of numbers | {0.5, 0.5}
<a name="writing-effects"></a>
## Writing effects
An effect is essentially a function that returns a `moonshine.Effect{}`, which
must specify at least a `name` and a `shader` or a `draw` function.
It may also specify a `setters` table that contains functions that set the
effect parameters and a `defaults` table with the corresponding default values.
The default values will be set when the effect is instantiated.
A good starting point to see how to write effects is the `colorgradesimple`
effect, which uses the `shader`, `setters` and `defaults` fields.
Moonshine uses double buffering to draw the effects. A function to swap and
access the buffers is provided to the `draw(buffer)` function of your effect:
```lua
front, back = buffer() -- swaps front and back buffer and returns both
```
You don't have to care about canvases or restoring defaults, moonshine handles
all that for you.
If you only need a custom draw function because your effect needs multiple
shader passes, moonshine provides the `draw_shader(buffer, shader)` function.
As you might have guessed, this function uses `shader` to draw the front buffer
to the back buffer. The `boxblur` effect gives a simple example how to use this
function.
If for some reason you need more than two buffer, you are more or less on your
own. You can do everything, but make sure that the blend mode and the order of
back and front buffer is the same before and after your custom `draw` function.
The `glow` effect gives an example of a more complicated `draw` function.
<a name="license"></a>
## License
See [here](https://github.com/vrld/moonshine/graphs/contributors) for a list of
contributors.
The main library can freely be used under the following conditions:
The MIT License (MIT)
Copyright (c) 2017 Matthias Richter
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
Most of the effects are public domain (see comments inside the files):
* boxblur.lua
* chromasep.lua
* colorgradesimple.lua
* crt.lua
* desaturate.lua
* filmgrain.lua
* gaussianblur.lua
* glow.lua
* pixelate.lua
* posterize.lua
* scanlines.lua
* vignette.lua
These effects are MIT-licensed with multiple authors:
* dmg.lua: Joseph Patoprsty, Matthias Richter
* fastgaussianblur.lua: Tim Moore, Matthias Richter
* godsray.lua: Joseph Patoprsty, Matthias Richter. Based on work by ioxu, Fabien Sanglard, Kenny Mitchell and Jason Mitchell.
* sketch.lua: Martin Felis, Matthias Richter
* fog.lua: Brandon Blanker Lim-it. Based on work by Gonkee.

View File

@@ -0,0 +1,62 @@
--[[
Public domain:
Copyright (C) 2017 by Matthias Richter <vrld@vrld.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
]]--
return function(moonshine)
local radius_x, radius_y = 3, 3
local shader = love.graphics.newShader[[
extern vec2 direction;
extern number radius;
vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _) {
vec4 c = vec4(0.0);
for (float i = -radius; i <= radius; i += 1.0)
{
c += Texel(texture, tc + i * direction);
}
return c / (2.0 * radius + 1.0) * color;
}]]
local setters = {}
setters.radius = function(v)
if type(v) == "number" then
radius_x, radius_y = v, v
elseif type(v) == "table" and #v >= 2 then
radius_x, radius_y = tonumber(v[1] or v.h or v.x), tonumber(v[2] or v.v or v.y)
else
error("Invalid argument `radius'")
end
end
setters.radius_x = function(v) radius_x = tonumber(v) end
setters.radius_y = function(v) radius_y = tonumber(v) end
local draw = function(buffer)
shader:send('direction', {1 / love.graphics.getWidth(), 0})
shader:send('radius', math.floor(radius_x + .5))
moonshine.draw_shader(buffer, shader)
shader:send('direction', {0, 1 / love.graphics.getHeight()})
shader:send('radius', math.floor(radius_y + .5))
moonshine.draw_shader(buffer, shader)
end
return moonshine.Effect{
name = "boxblur",
draw = draw,
setters = setters,
defaults = {radius = 3}
}
end

View File

@@ -0,0 +1,49 @@
--[[
Public domain:
Copyright (C) 2017 by Matthias Richter <vrld@vrld.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
]]--
return function(moonshine)
local shader = love.graphics.newShader[[
extern vec2 direction;
vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _)
{
return color * vec4(
Texel(texture, tc - direction).r,
Texel(texture, tc).g,
Texel(texture, tc + direction).b,
1.0);
}]]
local angle, radius = 0, 0
local setters = {
angle = function(v) angle = tonumber(v) or 0 end,
radius = function(v) radius = tonumber(v) or 0 end
}
local draw = function(buffer, effect)
local dx = math.cos(angle) * radius / love.graphics.getWidth()
local dy = math.sin(angle) * radius / love.graphics.getHeight()
shader:send("direction", {dx,dy})
moonshine.draw_shader(buffer, shader)
end
return moonshine.Effect{
name = "chromasep",
draw = draw,
setters = setters,
defaults = {angle = 0, radius = 0}
}
end

View File

@@ -0,0 +1,33 @@
--[[
Public domain:
Copyright (C) 2017 by Matthias Richter <vrld@vrld.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
]]--
return function(moonshine)
local shader = love.graphics.newShader[[
extern vec3 factors;
vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _) {
return vec4(factors, 1.0) * Texel(texture, tc) * color;
}]]
local setters = {}
return moonshine.Effect{
name = "colorgradesimple",
shader = shader,
setters = {factors = function(v) shader:send("factors", v) end},
defaults = {factors = {1,1,1}}
}
end

79
libs/moonshine/crt.lua Normal file
View File

@@ -0,0 +1,79 @@
--[[
Public domain:
Copyright (C) 2017 by Matthias Richter <vrld@vrld.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
]]--
return function(moonshine)
-- Barrel distortion adapted from Daniel Oaks (see commit cef01b67fd)
-- Added feather to mask out outside of distorted texture
local distortionFactor
local shader = love.graphics.newShader[[
extern vec2 distortionFactor;
extern vec2 scaleFactor;
extern number feather;
vec4 effect(vec4 color, Image tex, vec2 uv, vec2 px) {
// to barrel coordinates
uv = uv * 2.0 - vec2(1.0);
// distort
uv *= scaleFactor;
uv += (uv.yx*uv.yx) * uv * (distortionFactor - 1.0);
number mask = (1.0 - smoothstep(1.0-feather,1.0,abs(uv.x)))
* (1.0 - smoothstep(1.0-feather,1.0,abs(uv.y)));
// to cartesian coordinates
uv = (uv + vec2(1.0)) / 2.0;
return color * Texel(tex, uv) * mask;
}
]]
local setters = {}
setters.distortionFactor = function(v)
assert(type(v) == "table" and #v == 2, "Invalid value for `distortionFactor'")
distortionFactor = {unpack(v)}
shader:send("distortionFactor", v)
end
setters.x = function(v) setters.distortionFactor{v, distortionFactor[2]} end
setters.y = function(v) setters.distortionFactor{distortionFactor[1], v} end
setters.scaleFactor = function(v)
if type(v) == "table" and #v == 2 then
shader:send("scaleFactor", v)
elseif type(v) == "number" then
shader:send("scaleFactor", {v,v})
else
error("Invalid value for `scaleFactor'")
end
end
setters.feather = function(v) shader:send("feather", v) end
local defaults = {
distortionFactor = {1.06, 1.065},
feather = 0.02,
scaleFactor = 1,
}
return moonshine.Effect{
name = "crt",
shader = shader,
setters = setters,
defaults = defaults
}
end

View File

@@ -0,0 +1,52 @@
--[[
Public domain:
Copyright (C) 2017 by Matthias Richter <vrld@vrld.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
]]--
return function(moonshine)
local shader = love.graphics.newShader[[
extern vec4 tint;
extern number strength;
vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _) {
color = Texel(texture, tc);
number luma = dot(vec3(0.299, 0.587, 0.114), color.rgb);
return mix(color, tint * luma, strength);
}]]
local setters = {}
setters.tint = function(c)
assert(type(c) == "table" and #c == 3, "Invalid value for `tint'")
shader:send("tint", {
(tonumber(c[1]) or 0) / 255,
(tonumber(c[2]) or 0) / 255,
(tonumber(c[3]) or 0) / 255,
1
})
end
setters.strength = function(v)
shader:send("strength", math.max(0, math.min(1, tonumber(v) or 0)))
end
local defaults = {tint = {255,255,255}, strength = 0.5}
return moonshine.Effect{
name = "desaturate",
shader = shader,
setters = setters,
defaults = defaults
}
end

153
libs/moonshine/dmg.lua Normal file
View File

@@ -0,0 +1,153 @@
--[[
The MIT License (MIT)
Original code: Copyright (c) 2015 Josef Patoprsty
Port to moonshine: Copyright (c) 2017 Matthias Richter <vrld@vrld.org>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]--
local palettes = {
-- Default color palette. Source:
-- http://en.wikipedia.org/wiki/List_of_video_game_console_palettes#Original_Game_Boy
{
name = "default",
colors = {
{ 15/255, 56/255, 15/255},
{ 48/255, 98/255, 48/255},
{139/255,172/255, 15/255},
{155/255,188/255, 15/255}
}
},
-- Hardcore color profiles. Source:
-- http://www.hardcoregaming101.net/gbdebate/gbcolours.htm
{
name = "dark_yellow",
colors = {
{33/255,32/255,16/255},
{107/255,105/255,49/255},
{181/255,174/255,74/255},
{255/255,247/255,123/255}
}
},
{
name = "light_yellow",
colors = {
{102/255,102/255,37/255},
{148/255,148/255,64/255},
{208/255,208/255,102/255},
{255/255,255/255,148/255}
}
},
{
name = "green",
colors = {
{8/255,56/255,8/255},
{48/255,96/255,48/255},
{136/255,168/255,8/255},
{183/255,220/255,17/255}
}
},
{
name = "greyscale",
colors = {
{56/255,56/255,56/255},
{117/255,117/255,117/255},
{178/255,178/255,178/255},
{239/255,239/255,239/255}
}
},
{
name = "stark_bw",
colors = {
{0/255,0/255,0/255},
{117/255,117/255,117/255},
{178/255,178/255,178/255},
{255/255,255/255,255/255}
}
},
{
name = "pocket",
colors = {
{108/255,108/255,78/255},
{142/255,139/255,87/255},
{195/255,196/255,165/255},
{227/255,230/255,201/255}
}
}
}
local lookup_palette = function(name)
for _,palette in pairs(palettes) do
if palette.name == name then
return palette
end
end
end
local is_valid_palette = function(v)
-- Needs to match: {{R,G,B},{R,G,B},{R,G,B},{R,G,B}}
if #v ~= 4 then return false end
for i = 1,4 do
if type(v[i]) ~= "table" or #v[i] ~= 3 then return false end
for c = 1,3 do
if type(v[i][c]) ~= "number" then return false end
local x = v[i][c]
if x > 1 then x = x / 255 end
if x < 0 or x > 1 then return false end
v[i][c] = x
end
end
return true
end
return function(moonshine)
local shader = love.graphics.newShader[[
extern vec3 palette[ 4 ];
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 pixel_coords) {
vec4 pixel = Texel(texture, texture_coords);
float avg = min(0.9999,max(0.0001,(pixel.r + pixel.g + pixel.b)/3));
int index = int(avg*4);
return vec4(palette[index], pixel.a);
}]]
local setters = {}
setters.palette = function(v)
if type(v) == "number" and palettes[math.floor(v)] then -- indexed palette
palette = palettes[math.floor(v)]
elseif type(v) == "string" then -- named palette
palette = lookup_palette(v)
elseif type(v) == "table" and is_valid_palette(v) then -- custom palette
palette = {colors=v}
else -- Fall back to default
palette = palettes[1]
end
shader:send("palette", palette.colors[1], palette.colors[2],
palette.colors[3], palette.colors[4], {})
end
return moonshine.Effect{
name = "dmg",
shader = shader,
setters = setters,
defaults = {palette = "default"}
}
end

View File

@@ -0,0 +1,139 @@
--[[
The MIT License (MIT)
Copyright (c) 2017 Tim Moore
Adapted for new moonshine API by Matthias Richter
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]--
-- Bilinear Gaussian blur filter as detailed here: http://rastergrid.com/blog/2010/09/efficient-gaussian-blur-with-linear-sampling/
-- Produces near identical results to a standard Gaussian blur by using sub-pixel sampling,
-- this allows us to do ~1/2 the number of pixel lookups.
-- unroll convolution loop
local function build_shader(taps, offset, offset_type, sigma)
taps = math.floor(taps)
sigma = sigma >= 1 and sigma or (taps - 1) * offset / 6
sigma = math.max(sigma, 1)
local steps = (taps + 1) / 2
-- Calculate gaussian function.
local g_offsets = {}
local g_weights = {}
for i = 1, steps, 1 do
g_offsets[i] = offset * (i - 1)
-- We don't need to include the constant part of the gaussian function as we normalize later.
-- 1 / math.sqrt(2 * sigma ^ math.pi) * math.exp(-0.5 * ((offset - 0) / sigma) ^ 2 )
g_weights[i] = math.exp(-0.5 * (g_offsets[i] - 0) ^ 2 * 1 / sigma ^ 2 )
end
-- Calculate offsets and weights for sub-pixel samples.
local offsets = {}
local weights = {}
for i = #g_weights, 2, -2 do
local oA, oB = g_offsets[i], g_offsets[i - 1]
local wA, wB = g_weights[i], g_weights[i - 1]
wB = oB == 0 and wB / 2 or wB -- On center tap the middle is getting sampled twice so half weight.
local weight = wA + wB
offsets[#offsets + 1] = offset_type == 'center' and (oA + oB) / 2 or (oA * wA + oB * wB) / weight
weights[#weights + 1] = weight
end
local code = {[[
extern vec2 direction;
vec4 effect(vec4 color, Image tex, vec2 tc, vec2 sc) {]]}
local norm = 0
if #g_weights % 2 == 0 then
code[#code+1] = 'vec4 c = vec4( 0.0 );'
else
local weight = g_weights[1]
norm = norm + weight
code[#code+1] = ('vec4 c = %f * texture2D(tex, tc);'):format(weight)
end
local tmpl = 'c += %f * ( texture2D(tex, tc + %f * direction)+ texture2D(tex, tc - %f * direction));\n'
for i = 1, #offsets, 1 do
local offset = offsets[i]
local weight = weights[i]
norm = norm + weight * 2
code[#code+1] = tmpl:format(weight, offset, offset)
end
code[#code+1] = ('return c * vec4(%f) * color; }'):format(1 / norm)
local shader = table.concat(code)
return love.graphics.newShader(shader)
end
return function(moonshine)
local taps, offset, offset_type, sigma = 7, 1, 'weighted', -1
local shader = build_shader(taps, offset, offset_type, sigma)
local function draw(buffer)
shader:send('direction', {1 / love.graphics.getWidth(), 0})
moonshine.draw_shader(buffer, shader)
shader:send('direction', {0, 1 / love.graphics.getHeight()})
moonshine.draw_shader(buffer, shader)
end
local setters = {}
-- Number of effective samples to take per pass. e.g. 3-tap is the current pixel and the neighbors each side.
-- More taps = larger blur, but slower.
setters.taps = function(v)
assert(tonumber(v) >= 3, "Invalid value for `taps': Must be >= 3")
assert(tonumber(v)%2 == 1, "Invalid value for `taps': Must be odd")
taps = tonumber(v)
shader = build_shader(taps, offset, offset_type, sigma)
end
-- Offset of each tap.
-- For highest quality this should be <=1 but if the image has low entropy we
-- can approximate the blur with a number > 1 and less taps, for better performance.
setters.offset = function(v)
offset = tonumber(v) or 0
shader = build_shader(taps, offset, offset_type, sigma)
end
-- Offset type, either 'weighted' or 'center'.
-- 'weighted' gives a more accurate gaussian decay but can introduce modulation
-- for high frequency details.
setters.offset_type = function(v)
assert(v == 'weighted' or v == 'center', "Invalid value for 'offset_type': Must be 'weighted' or 'center'.")
offset_type = v
shader = build_shader(taps, offset, offset_type, sigma)
end
-- Sigma value for gaussian distribution. You don't normally need to set this.
setters.sigma = function(v)
sigma = tonumber(v) or -1
shader = build_shader(taps, offset, offset_type, sigma)
end
return moonshine.Effect{
name = "fastgaussianblur",
draw = draw,
setters = setters,
-- no defaults here, as we dont want the shader to be built 3 times on startup
}
end

View File

@@ -0,0 +1,63 @@
--[[
Public domain:
Copyright (C) 2017 by Matthias Richter <vrld@vrld.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
]]--
return function(moonshine)
local noisetex = love.image.newImageData(256,256)
noisetex:mapPixel(function()
local l = love.math.random() * 255
return l,l,l,l
end)
noisetex = love.graphics.newImage(noisetex)
local shader = love.graphics.newShader[[
extern number opacity;
extern number size;
extern vec2 noise;
extern Image noisetex;
extern vec2 tex_ratio;
float rand(vec2 co) {
return Texel(noisetex, mod(co * tex_ratio / vec2(size), vec2(1.0))).r;
}
vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _) {
return color * Texel(texture, tc) * mix(1.0, rand(tc+vec2(noise)), opacity);
}]]
shader:send("noisetex", noisetex)
shader:send("tex_ratio", {love.graphics.getWidth() / noisetex:getWidth(),
love.graphics.getHeight() / noisetex:getHeight()})
local setters = {}
for _,k in ipairs{"opacity", "size"} do
setters[k] = function(v) shader:send(k, math.max(0, tonumber(v) or 0)) end
end
local defaults = {opacity = .3, size = 1}
local draw = function(buffer)
shader:send("noise", {love.math.random(), love.math.random()})
moonshine.draw_shader(buffer, shader)
end
return moonshine.Effect{
name = "filmgrain",
draw = draw,
setters = setters,
defaults = defaults
}
end

129
libs/moonshine/fog.lua Normal file
View File

@@ -0,0 +1,129 @@
--[[
Animated 2D Fog (procedural)
Originally for Godot Engine by Gonkee https://www.youtube.com/watch?v=QEaTsz_0o44&t=6s
Translated for löve by Brandon Blanker Lim-it @flamendless
]]--
--[[
SAMPLE USAGE:
local moonshine = require("moonshine")
local effect
local image, bg
local image_data
local shader_fog
local time = 0
function love.load()
image_data = love.image.newImageData(love.graphics.getWidth(), love.graphics.getHeight())
image = love.graphics.newImage(image_data)
bg = love.graphics.newImage("bg.png")
effect = moonshine(moonshine.effects.fog)
effect.fog.fog_color = {0.1, 0.0, 0.0}
effect.fog.speed = {0.2, 0.9}
end
function love.update(dt)
time = time + dt
effect.fog.time = time
end
function love.draw()
love.graphics.draw(bg)
effect(function()
love.graphics.draw(image)
end)
end
]]
return function(moonshine)
local fog_color
local octaves
local speed
local time
local shader = love.graphics.newShader([[
extern vec3 fog_color = vec3(0.35, 0.48, 0.95);
extern int octaves = 4;
extern vec2 speed = vec2(0.0, 1.0);
extern float time;
float rand(vec2 coord)
{
return fract(sin(dot(coord, vec2(56, 78)) * 1000.0) * 1000.0);
}
float noise(vec2 coord)
{
vec2 i = floor(coord); //get the whole number
vec2 f = fract(coord); //get the fraction number
float a = rand(i); //top-left
float b = rand(i + vec2(1.0, 0.0)); //top-right
float c = rand(i + vec2(0.0, 1.0)); //bottom-left
float d = rand(i + vec2(1.0, 1.0)); //bottom-right
vec2 cubic = f * f * (3.0 - 2.0 * f);
return mix(a, b, cubic.x) + (c - a) * cubic.y * (1.0 - cubic.x) + (d - b) * cubic.x * cubic.y; //interpolate
}
float fbm(vec2 coord) //fractal brownian motion
{
float value = 0.0;
float scale = 0.5;
for (int i = 0; i < octaves; i++)
{
value += noise(coord) * scale;
coord *= 2.0;
scale *= 0.5;
}
return value;
}
vec4 effect(vec4 color, Image texture, vec2 tc, vec2 sc)
{
vec2 coord = tc * 20.0;
vec2 motion = vec2(fbm(coord + vec2(time * speed.x, time * speed.y)));
float final = fbm(coord + motion);
return vec4(fog_color, final * 0.5);
}
]])
local setters = {}
setters.fog_color = function(t)
assert(type(t) == "table", "Passed argument to fog_color must be a table containing 3 color values")
fog_color = t
shader:send("fog_color", fog_color)
end
setters.octaves = function(i)
assert(type(i) == "number", "Passed argument to octaves must be an integer")
octaves = i
shader:send("octaves", octaves)
end
setters.speed = function(t)
assert(type(t) == "table", "Passed argument to speed must be a table containing 2 values")
speed = t
shader:send("speed", speed)
end
setters.time = function(n)
assert(type(n) == "number", "Passed argument to time must be a number")
time = n
shader:send("time", time)
end
local defaults = {
fog_color = {0.35, 0.48, 0.95},
octaves = 4,
speed = {0.5, 0.5},
}
return moonshine.Effect({
name = "fog",
shader = shader,
setters = setters,
defaults = defaults,
})
end

View File

@@ -0,0 +1,55 @@
--[[
Public domain:
Copyright (C) 2017 by Matthias Richter <vrld@vrld.org>
Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
]]--
local function resetShader(sigma)
local support = math.max(1, math.floor(3*sigma + .5))
local one_by_sigma_sq = sigma > 0 and 1 / (sigma * sigma) or 1
local norm = 0
local code = {[[
extern vec2 direction;
vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _)
{ vec4 c = vec4(0.0);
]]}
local blur_line = "c += vec4(%f) * Texel(texture, tc + vec2(%f) * direction);"
for i = -support,support do
local coeff = math.exp(-.5 * i*i * one_by_sigma_sq)
norm = norm + coeff
code[#code+1] = blur_line:format(coeff, i)
end
code[#code+1] = ("return c * vec4(%f) * color;}"):format(norm > 0 and 1/norm or 1)
return love.graphics.newShader(table.concat(code))
end
return function(moonshine)
local shader
local setters = {}
setters.sigma = function(v)
shader = resetShader(math.max(0,tonumber(v) or 1))
end
local draw = function(buffer)
shader:send('direction', {1 / love.graphics.getWidth(), 0})
moonshine.draw_shader(buffer, shader)
shader:send('direction', {0, 1 / love.graphics.getHeight()})
moonshine.draw_shader(buffer, shader)
end
return moonshine.Effect{
name = "gaussianblur",
draw = draw,
setters = setters,
defaults = {sigma = 1},
}
end

104
libs/moonshine/glow.lua Normal file
View File

@@ -0,0 +1,104 @@
--[[
Public domain:
Copyright (C) 2017 by Matthias Richter <vrld@vrld.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
]]--
-- unroll convolution loop for gaussian blur shader
local function make_blur_shader(sigma)
local support = math.max(1, math.floor(3*sigma + .5))
local one_by_sigma_sq = sigma > 0 and 1 / (sigma * sigma) or 1
local norm = 0
local code = {[[
extern vec2 direction;
vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _) {
vec4 c = vec4(0.0);
]]}
local blur_line = "c += vec4(%f) * Texel(texture, tc + vec2(%f) * direction);"
for i = -support,support do
local coeff = math.exp(-.5 * i*i * one_by_sigma_sq)
norm = norm + coeff
code[#code+1] = blur_line:format(coeff, i)
end
code[#code+1] = ("return c * vec4(%f) * color;}"):format(1 / norm)
return love.graphics.newShader(table.concat(code))
end
return function(moonshine)
local blurshader -- set in setters.glow_strength
local threshold = love.graphics.newShader[[
extern number min_luma;
vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _) {
vec4 c = Texel(texture, tc);
number luma = dot(vec3(0.299, 0.587, 0.114), c.rgb);
return c * step(min_luma, luma) * color;
}]]
local setters = {}
setters.strength = function(v)
blurshader = make_blur_shader(math.max(0,tonumber(v) or 1))
end
setters.min_luma = function(v)
threshold:send("min_luma", math.max(0, math.min(1, tonumber(v) or 0.5)))
end
local scene = love.graphics.newCanvas()
local draw = function(buffer)
local front, back = buffer() -- scene so far is in `back'
scene, back = back, scene -- save it for second draw below
-- 1st pass: draw scene with brightness threshold
love.graphics.setCanvas(front)
love.graphics.clear()
love.graphics.setShader(threshold)
love.graphics.draw(scene)
-- 2nd pass: apply blur shader in x
blurshader:send('direction', {1 / love.graphics.getWidth(), 0})
love.graphics.setCanvas(back)
love.graphics.clear()
love.graphics.setShader(blurshader)
love.graphics.draw(front)
-- 3nd pass: apply blur shader in y and draw original and blurred scene
love.graphics.setCanvas(front)
love.graphics.clear()
-- original scene without blur shader
love.graphics.setShader()
love.graphics.setBlendMode("add", "premultiplied")
love.graphics.draw(scene) -- original scene
-- second pass of light blurring
blurshader:send('direction', {0, 1 / love.graphics.getHeight()})
love.graphics.setShader(blurshader)
love.graphics.draw(back)
-- restore things as they were before entering draw()
love.graphics.setBlendMode("alpha", "premultiplied")
scene = back
end
return moonshine.Effect{
name = "glow",
draw = draw,
setters = setters,
defaults = {min_luma=.7, strength = 5}
}
end

107
libs/moonshine/godsray.lua Normal file
View File

@@ -0,0 +1,107 @@
--[[
The MIT License (MIT)
Original code: Copyright (c) 2015 Josef Patoprsty
Port to moonshine: Copyright (c) 2017 Matthias Richter <vrld@vrld.org>
Based on work by: ioxu
https://www.love2d.org/forums/viewtopic.php?f=4&t=3733&start=120#p71099
Based on work by: Fabien Sanglard
http://fabiensanglard.net/lightScattering/index.php
Based on work from:
[Mitchell]: Kenny Mitchell "Volumetric Light Scattering as a Post-Process" GPU Gems 3 (2005).
[Mitchell2]: Jason Mitchell "Light Shaft Rendering" ShadersX3 (2004).
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]--
return function(moonshine)
local shader = love.graphics.newShader[[
extern number exposure;
extern number decay;
extern number density;
extern number weight;
extern vec2 light_position;
extern number samples;
vec4 effect(vec4 color, Image tex, vec2 uv, vec2 px) {
color = Texel(tex, uv);
vec2 offset = (uv - light_position) * density / samples;
number illumination = decay;
vec4 c = vec4(.0, .0, .0, 1.0);
for (int i = 0; i < int(samples); ++i) {
uv -= offset;
c += Texel(tex, uv) * illumination * weight;
illumination *= decay;
}
return vec4(c.rgb * exposure + color.rgb, color.a);
}]]
local setters, light_position = {}
for _,k in ipairs{"exposure", "decay", "density", "weight"} do
setters[k] = function(v)
shader:send(k, math.min(1, math.max(0, tonumber(v) or 0)))
end
end
setters.light_position = function(v)
light_position = {unpack(v)}
shader:send("light_position", v)
end
setters.light_x = function(v)
assert(type(v) == "number", "Invalid value for `light_x'")
setters.light_position{v, light_position[2]}
end
setters.light_y = function(v)
assert(type(v) == "number", "Invalid value for `light_y'")
setters.light_position{light_position[1], v}
end
setters.samples = function(v)
shader:send("samples", math.max(1,tonumber(v) or 1))
end
local defaults = {
exposure = 0.25,
decay = 0.95,
density = 0.15,
weight = 0.5,
light_position = {0.5,0.5},
samples = 70
}
return moonshine.Effect{
name = "godsray",
shader = shader,
setters = setters,
defaults = defaults
}
end

171
libs/moonshine/init.lua Normal file
View File

@@ -0,0 +1,171 @@
--[[
The MIT License (MIT)
Copyright (c) 2017 Matthias Richter
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]--
local BASE = ...
local moonshine = {}
moonshine.draw_shader = function(buffer, shader)
local front, back = buffer()
love.graphics.setCanvas(front)
love.graphics.clear()
if shader ~= love.graphics.getShader() then
love.graphics.setShader(shader)
end
love.graphics.draw(back)
end
moonshine.chain = function(w,h,effect)
-- called as moonshine.chain(effect)'
if h == nil then
effect, w,h = w, love.window.getMode()
end
assert(effect ~= nil, "No effect")
local front, back = love.graphics.newCanvas(w,h), love.graphics.newCanvas(w,h)
local buffer = function()
back, front = front, back
return front, back
end
local disabled = {} -- set of disabled effects
local chain = {}
chain.resize = function(w, h)
front, back = love.graphics.newCanvas(w,h), love.graphics.newCanvas(w,h)
return chain
end
chain.draw = function(func, ...)
-- save state
local canvas = love.graphics.getCanvas()
local shader = love.graphics.getShader()
local fg_r, fg_g, fg_b, fg_a = love.graphics.getColor()
-- draw scene to front buffer
love.graphics.setCanvas((buffer())) -- parens are needed: take only front buffer
love.graphics.clear(love.graphics.getBackgroundColor())
func(...)
-- save more state
local blendmode = love.graphics.getBlendMode()
-- process all shaders
love.graphics.setColor(fg_r, fg_g, fg_b, fg_a)
love.graphics.setBlendMode("alpha", "premultiplied")
for _,e in ipairs(chain) do
if not disabled[e.name] then
(e.draw or moonshine.draw_shader)(buffer, e.shader)
end
end
-- present result
love.graphics.setShader()
love.graphics.setCanvas(canvas)
love.graphics.draw(front,0,0)
-- restore state
love.graphics.setBlendMode(blendmode)
love.graphics.setShader(shader)
end
chain.next = function(e)
if type(e) == "function" then
e = e()
end
assert(e.name, "Invalid effect: must provide `name'.")
assert(e.shader or e.draw, "Invalid effect: must provide `shader' or `draw'.")
table.insert(chain, e)
return chain
end
chain.chain = chain.next
chain.disable = function(name, ...)
if name then
disabled[name] = name
return chain.disable(...)
end
end
chain.enable = function(name, ...)
if name then
disabled[name] = nil
return chain.enable(...)
end
end
setmetatable(chain, {
__call = function(_, ...) return chain.draw(...) end,
__index = function(_,k)
for _, e in ipairs(chain) do
if e.name == k then return e end
end
error(("Effect `%s' not in chain"):format(k), 2)
end,
__newindex = function(_, k, v)
if k == "parameters" or k == "params" or k == "settings" then
for e,par in pairs(v) do
for k,v in pairs(par) do
chain[e][k] = v
end
end
else
rawset(chain, k, v)
end
end
})
return chain.next(effect)
end
moonshine.Effect = function(e)
-- set defaults
for k,v in pairs(e.defaults or {}) do
assert(e.setters[k], ("No setter for parameter `%s'"):format(k))(v, k)
e.setters[k](v,k)
end
-- expose setters
return setmetatable(e, {
__newindex = function(self,k,v)
assert(self.setters[k], ("Unknown property: `%s.%s'"):format(e.name, k))
self.setters[k](v, k)
end})
end
-- autoloading effects
moonshine.effects = setmetatable({}, {__index = function(self, key)
local ok, effect = pcall(require, BASE .. "." .. key)
if not ok then
error("No such effect: "..key, 2)
end
-- expose moonshine to effect
local con = function(...) return effect(moonshine, ...) end
-- cache effect constructor
self[key] = con
return con
end})
return setmetatable(moonshine, {__call = function(_, ...) return moonshine.chain(...) end})

View File

@@ -0,0 +1,55 @@
--[[
Public domain:
Copyright (C) 2017 by Matthias Richter <vrld@vrld.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
]]--
return function(moonshine)
local shader = love.graphics.newShader[[
extern vec2 size;
extern number feedback;
vec4 effect(vec4 color, Image tex, vec2 tc, vec2 _)
{
vec4 c = Texel(tex, tc);
// average pixel color over 5 samples
vec2 scale = love_ScreenSize.xy / size;
tc = floor(tc * scale + vec2(.5));
vec4 meanc = Texel(tex, tc/scale);
meanc += Texel(tex, (tc+vec2( 1.0, .0))/scale);
meanc += Texel(tex, (tc+vec2(-1.0, .0))/scale);
meanc += Texel(tex, (tc+vec2( .0, 1.0))/scale);
meanc += Texel(tex, (tc+vec2( .0,-1.0))/scale);
return color * mix(.2*meanc, c, feedback);
}
]]
local setters = {}
setters.size = function(v)
if type(v) == "number" then v = {v,v} end
assert(type(v) == "table" and #v == 2, "Invalid value for `size'")
shader:send("size", v)
end
setters.feedback = function(v)
shader:send("feedback", math.min(1, math.max(0, tonumber(v) or 0)))
end
return moonshine.Effect{
name = "pixelate",
shader = shader,
setters = setters,
defaults = {size = {5,5}, feedback = 0}
}
end

View File

@@ -0,0 +1,59 @@
--[[
Public domain:
Copyright (C) 2017 by Matthias Richter <vrld@vrld.org>
shader based on code by sam hocevar, see
https://gamedev.stackexchange.com/questions/59797/glsl-shader-change-hue-saturation-brightness
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
]]--
return function(moonshine)
local shader = love.graphics.newShader[[
extern number num_bands;
vec3 rgb2hsv(vec3 c)
{
vec4 K = vec4(0.0, -1.0 / 3.0, 2.0 / 3.0, -1.0);
vec4 p = mix(vec4(c.bg, K.wz), vec4(c.gb, K.xy), step(c.b, c.g));
vec4 q = mix(vec4(p.xyw, c.r), vec4(c.r, p.yzx), step(p.x, c.r));
float d = q.x - min(q.w, q.y);
float e = 1.0e-10;
return vec3(abs(q.z + (q.w - q.y) / (6.0 * d + e)), d / (q.x + e), q.x);
}
vec3 hsv2rgb(vec3 c)
{
vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0);
vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www);
return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y);
}
vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _)
{
color = Texel(texture, tc);
vec3 hsv = floor((rgb2hsv(color.rgb) * num_bands) + vec3(0.5)) / num_bands;
return vec4(hsv2rgb(hsv), color.a);
}]]
return moonshine.Effect{
name = "posterize",
shader = shader,
setters = {
num_bands = function(v)
shader:send("num_bands", math.max(1, tonumber(v) or 1))
end
},
defaults = {num_bands = 3}
}
end

View File

@@ -0,0 +1,73 @@
--[[
Public domain:
Copyright (C) 2017 by Matthias Richter <vrld@vrld.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
]]--
return function(moonshine)
local shader = love.graphics.newShader[[
extern number width;
extern number phase;
extern number thickness;
extern number opacity;
extern vec3 color;
vec4 effect(vec4 c, Image tex, vec2 tc, vec2 _) {
number v = .5*(sin(tc.y * 3.14159 / width * love_ScreenSize.y + phase) + 1.);
c = Texel(tex,tc);
//c.rgb = mix(color, c.rgb, mix(1, pow(v, thickness), opacity));
c.rgb -= (color - c.rgb) * (pow(v,thickness) - 1.0) * opacity;
return c;
}]]
local defaults = {
width = 2,
phase = 0,
thickness = 1,
opacity = 1,
color = {0,0,0},
}
local setters = {}
setters.width = function(v)
shader:send("width", tonumber(v) or defaults.width)
end
setters.frequency = function(v)
shader:send("width", love.graphics.getHeight()/(tonumber(v) or love.graphics.getHeight()))
end
setters.phase = function(v)
shader:send("phase", tonumber(v) or defaults.phase)
end
setters.thickness = function(v)
shader:send("thickness", math.max(0, tonumber(v) or defaults.thickness))
end
setters.opacity = function(v)
shader:send("opacity", math.min(1, math.max(0, tonumber(v) or defaults.opacity)))
end
setters.color = function(c)
assert(type(c) == "table" and #c == 3, "Invalid value for `color'")
shader:send("color", {
(tonumber(c[1]) or defaults.color[0]) / 255,
(tonumber(c[2]) or defaults.color[1]) / 255,
(tonumber(c[3]) or defaults.color[2]) / 255
})
end
return moonshine.Effect{
name = "scanlines",
shader = shader,
setters = setters,
defaults = defaults,
}
end

64
libs/moonshine/sketch.lua Normal file
View File

@@ -0,0 +1,64 @@
--[[
The MIT License (MIT)
Copyright (c) 2015 Martin Felis
Copyright (c) 2017 Matthias Richter
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
]]--
return function(moonshine)
local noisetex = love.image.newImageData(256,256)
noisetex:mapPixel(function()
return love.math.random() * 255,love.math.random() * 255, 0, 0
end)
noisetex = love.graphics.newImage(noisetex)
noisetex:setWrap ("repeat", "repeat")
noisetex:setFilter("nearest", "nearest")
local shader = love.graphics.newShader[[
extern Image noisetex;
extern number amp;
extern vec2 center;
vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _) {
vec2 displacement = Texel(noisetex, tc + center).rg;
tc += normalize(displacement * 2.0 - vec2(1.0)) * amp;
return Texel(texture, tc);
}]]
shader:send("noisetex", noisetex)
local setters = {}
setters.amp = function(v)
shader:send("amp", math.max(0, tonumber(v) or 0))
end
setters.center = function(v)
assert(type(v) == "table" and #v == 2, "Invalid value for `center'")
shader:send("center", v)
end
return moonshine.Effect{
name = "sketch",
shader = shader,
setters = setters,
defaults = {amp = .0007, center = {0,0}}
}
end

View File

@@ -0,0 +1,59 @@
--[[
Public domain:
Copyright (C) 2017 by Matthias Richter <vrld@vrld.org>
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
]]--
return function(moonshine)
local shader = love.graphics.newShader[[
extern number radius;
extern number softness;
extern number opacity;
extern vec4 color;
vec4 effect(vec4 c, Image tex, vec2 tc, vec2 _)
{
number aspect = love_ScreenSize.x / love_ScreenSize.y;
aspect = max(aspect, 1.0 / aspect); // use different aspect when in portrait mode
number v = 1.0 - smoothstep(radius, radius-softness,
length((tc - vec2(0.5)) * aspect));
return mix(Texel(tex, tc), color, v*opacity);
}]]
local setters = {}
for _,k in ipairs{"radius", "softness", "opacity"} do
setters[k] = function(v) shader:send(k, math.max(0, tonumber(v) or 0)) end
end
setters.color = function(c)
assert(type(c) == "table" and #c == 3, "Invalid value for `color'")
shader:send("color", {
(tonumber(c[1]) or 0) / 255,
(tonumber(c[2]) or 0) / 255,
(tonumber(c[3]) or 0) / 255,
1
})
end
return moonshine.Effect{
name = "vignette",
shader = shader,
setters = setters,
defaults = {
radius = .8,
softness = .5,
opacity = .5,
color = {0,0,0}
}
}
end