diff options
Diffstat (limited to 'libs/moonshine')
-rw-r--r-- | libs/moonshine/README.md | 580 | ||||
-rw-r--r-- | libs/moonshine/boxblur.lua | 62 | ||||
-rw-r--r-- | libs/moonshine/chromasep.lua | 49 | ||||
-rw-r--r-- | libs/moonshine/colorgradesimple.lua | 33 | ||||
-rw-r--r-- | libs/moonshine/crt.lua | 79 | ||||
-rw-r--r-- | libs/moonshine/desaturate.lua | 53 | ||||
-rw-r--r-- | libs/moonshine/dmg.lua | 153 | ||||
-rw-r--r-- | libs/moonshine/fastgaussianblur.lua | 139 | ||||
-rw-r--r-- | libs/moonshine/filmgrain.lua | 63 | ||||
-rw-r--r-- | libs/moonshine/fog.lua | 129 | ||||
-rw-r--r-- | libs/moonshine/gaussianblur.lua | 55 | ||||
-rw-r--r-- | libs/moonshine/glow.lua | 104 | ||||
-rw-r--r-- | libs/moonshine/godsray.lua | 107 | ||||
-rw-r--r-- | libs/moonshine/init.lua | 171 | ||||
-rw-r--r-- | libs/moonshine/pixelate.lua | 55 | ||||
-rw-r--r-- | libs/moonshine/posterize.lua | 59 | ||||
-rw-r--r-- | libs/moonshine/scanlines.lua | 74 | ||||
-rw-r--r-- | libs/moonshine/sketch.lua | 64 | ||||
-rw-r--r-- | libs/moonshine/vignette.lua | 60 |
19 files changed, 2089 insertions, 0 deletions
diff --git a/libs/moonshine/README.md b/libs/moonshine/README.md new file mode 100644 index 0000000..062937a --- /dev/null +++ b/libs/moonshine/README.md @@ -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 | {1,1,1} +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 `1`. + + +<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. diff --git a/libs/moonshine/boxblur.lua b/libs/moonshine/boxblur.lua new file mode 100644 index 0000000..bb6afc1 --- /dev/null +++ b/libs/moonshine/boxblur.lua @@ -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 diff --git a/libs/moonshine/chromasep.lua b/libs/moonshine/chromasep.lua new file mode 100644 index 0000000..f528733 --- /dev/null +++ b/libs/moonshine/chromasep.lua @@ -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 diff --git a/libs/moonshine/colorgradesimple.lua b/libs/moonshine/colorgradesimple.lua new file mode 100644 index 0000000..f0e0278 --- /dev/null +++ b/libs/moonshine/colorgradesimple.lua @@ -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 diff --git a/libs/moonshine/crt.lua b/libs/moonshine/crt.lua new file mode 100644 index 0000000..9b73799 --- /dev/null +++ b/libs/moonshine/crt.lua @@ -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 diff --git a/libs/moonshine/desaturate.lua b/libs/moonshine/desaturate.lua new file mode 100644 index 0000000..dd51230 --- /dev/null +++ b/libs/moonshine/desaturate.lua @@ -0,0 +1,53 @@ +--[[ +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'") + assert(c[1] <= 1, "Colors should be normalized in [0,1]") + shader:send("tint", { + (tonumber(c[1]) or 0), + (tonumber(c[2]) or 0), + (tonumber(c[3]) or 0), + 1 + }) + end + + setters.strength = function(v) + shader:send("strength", math.max(0, math.min(1, tonumber(v) or 0))) + end + + local defaults = {tint = {1,1,1}, strength = 0.5} + + return moonshine.Effect{ + name = "desaturate", + shader = shader, + setters = setters, + defaults = defaults + } +end diff --git a/libs/moonshine/dmg.lua b/libs/moonshine/dmg.lua new file mode 100644 index 0000000..b426852 --- /dev/null +++ b/libs/moonshine/dmg.lua @@ -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 diff --git a/libs/moonshine/fastgaussianblur.lua b/libs/moonshine/fastgaussianblur.lua new file mode 100644 index 0000000..364b766 --- /dev/null +++ b/libs/moonshine/fastgaussianblur.lua @@ -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 diff --git a/libs/moonshine/filmgrain.lua b/libs/moonshine/filmgrain.lua new file mode 100644 index 0000000..7044567 --- /dev/null +++ b/libs/moonshine/filmgrain.lua @@ -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() + 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 diff --git a/libs/moonshine/fog.lua b/libs/moonshine/fog.lua new file mode 100644 index 0000000..9c90f9a --- /dev/null +++ b/libs/moonshine/fog.lua @@ -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 diff --git a/libs/moonshine/gaussianblur.lua b/libs/moonshine/gaussianblur.lua new file mode 100644 index 0000000..693414a --- /dev/null +++ b/libs/moonshine/gaussianblur.lua @@ -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 diff --git a/libs/moonshine/glow.lua b/libs/moonshine/glow.lua new file mode 100644 index 0000000..cec010b --- /dev/null +++ b/libs/moonshine/glow.lua @@ -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 diff --git a/libs/moonshine/godsray.lua b/libs/moonshine/godsray.lua new file mode 100644 index 0000000..6c9f74a --- /dev/null +++ b/libs/moonshine/godsray.lua @@ -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 diff --git a/libs/moonshine/init.lua b/libs/moonshine/init.lua new file mode 100644 index 0000000..431c6c0 --- /dev/null +++ b/libs/moonshine/init.lua @@ -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}) diff --git a/libs/moonshine/pixelate.lua b/libs/moonshine/pixelate.lua new file mode 100644 index 0000000..42fd76e --- /dev/null +++ b/libs/moonshine/pixelate.lua @@ -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 diff --git a/libs/moonshine/posterize.lua b/libs/moonshine/posterize.lua new file mode 100644 index 0000000..bc98805 --- /dev/null +++ b/libs/moonshine/posterize.lua @@ -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 diff --git a/libs/moonshine/scanlines.lua b/libs/moonshine/scanlines.lua new file mode 100644 index 0000000..fb5423d --- /dev/null +++ b/libs/moonshine/scanlines.lua @@ -0,0 +1,74 @@ +--[[ +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'") + assert(c[1] <= 1, "Colors should be normalized in [0,1]") + shader:send("color", { + (tonumber(c[1]) or defaults.color[1]), + (tonumber(c[2]) or defaults.color[2]), + (tonumber(c[3]) or defaults.color[3]) + }) + end + + return moonshine.Effect{ + name = "scanlines", + shader = shader, + setters = setters, + defaults = defaults, + } +end diff --git a/libs/moonshine/sketch.lua b/libs/moonshine/sketch.lua new file mode 100644 index 0000000..dcd4844 --- /dev/null +++ b/libs/moonshine/sketch.lua @@ -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(),love.math.random(), 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 diff --git a/libs/moonshine/vignette.lua b/libs/moonshine/vignette.lua new file mode 100644 index 0000000..f13fb8b --- /dev/null +++ b/libs/moonshine/vignette.lua @@ -0,0 +1,60 @@ +--[[ +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'") + assert(c[1] <= 1, "Colors should be normalized in [0,1]") + shader:send("color", { + (tonumber(c[1]) or 0), + (tonumber(c[2]) or 0), + (tonumber(c[3]) or 0), + 1 + }) + end + + return moonshine.Effect{ + name = "vignette", + shader = shader, + setters = setters, + defaults = { + radius = .8, + softness = .5, + opacity = .5, + color = {0,0,0} + } + } +end |