diff --git a/README.md b/README.md index 8972099..062937a 100644 --- a/README.md +++ b/README.md @@ -8,6 +8,7 @@ Chainable post-processing shaders for LÖVE. * [General usage](#general-usage) * [List of effects](#list-of-effects) * [Writing effects](#writing-effects) +* [License](#license) ## Getting started @@ -107,8 +108,8 @@ is equivalent to the above: ```lua chain.parameters = { - glow = {strenght = 10}, - crt = {distortionFactor = {1.06, 1.065}, + glow = {strength = 10}, + crt = {distortionFactor = {1.06, 1.065}}, } ``` @@ -145,6 +146,38 @@ 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 @@ -253,7 +286,7 @@ moonshine.effects.desaturate Name | Type | Default -----|------|-------- -tint | color / table of numbers | {255,255,255} +tint | color / table of numbers | {1,1,1} strength | number between 0 and 1 | 0.5 @@ -279,7 +312,7 @@ DMG ships with 7 palettes: 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`. +where `R`, `G`, and `B` are numbers between `0` and `1`. @@ -403,8 +436,8 @@ moonshine.effects.scanlines Name | Type | Default -----|------|-------- -width | number | 1 -frequency | number | screen-height / 1 +width | number | 2 +frequency | number | screen-height phase | number | 0 thickness | number | 1 opacity | number | 1 @@ -442,13 +475,25 @@ softness | number > 0 | 0.5 opacity | number > 0 | 0.5 color | color / table of numbers | {0,0,0} + +### 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} ## Writing effects -**Under construction** - 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. @@ -479,3 +524,57 @@ 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. + + + +## 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/boxblur.lua b/boxblur.lua index 54924e8..bb6afc1 100644 --- a/boxblur.lua +++ b/boxblur.lua @@ -21,13 +21,13 @@ return function(moonshine) extern vec2 direction; extern number radius; vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _) { - vec4 c = vec4(0.0f); + vec4 c = vec4(0.0); - for (float i = -radius; i <= radius; i += 1.0f) + for (float i = -radius; i <= radius; i += 1.0) { c += Texel(texture, tc + i * direction); } - return c / (2.0f * radius + 1.0f) * color; + return c / (2.0 * radius + 1.0) * color; }]] local setters = {} diff --git a/colorgradesimple.lua b/colorgradesimple.lua index c4728de..f0e0278 100644 --- a/colorgradesimple.lua +++ b/colorgradesimple.lua @@ -19,7 +19,7 @@ return function(moonshine) local shader = love.graphics.newShader[[ extern vec3 factors; vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _) { - return vec4(factors, 1.0f) * Texel(texture, tc) * color; + return vec4(factors, 1.0) * Texel(texture, tc) * color; }]] local setters = {} diff --git a/desaturate.lua b/desaturate.lua index b89af2f..dd51230 100644 --- a/desaturate.lua +++ b/desaturate.lua @@ -21,7 +21,7 @@ return function(moonshine) extern number strength; vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _) { color = Texel(texture, tc); - number luma = dot(vec3(0.299f, 0.587f, 0.114f), color.rgb); + number luma = dot(vec3(0.299, 0.587, 0.114), color.rgb); return mix(color, tint * luma, strength); }]] @@ -29,10 +29,11 @@ return function(moonshine) 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) / 255, - (tonumber(c[2]) or 0) / 255, - (tonumber(c[3]) or 0) / 255, + (tonumber(c[1]) or 0), + (tonumber(c[2]) or 0), + (tonumber(c[3]) or 0), 1 }) end @@ -41,7 +42,7 @@ return function(moonshine) shader:send("strength", math.max(0, math.min(1, tonumber(v) or 0))) end - local defaults = {tint = {255,255,255}, strength = 0.5} + local defaults = {tint = {1,1,1}, strength = 0.5} return moonshine.Effect{ name = "desaturate", diff --git a/fastgaussianblur.lua b/fastgaussianblur.lua index 65b566b..364b766 100644 --- a/fastgaussianblur.lua +++ b/fastgaussianblur.lua @@ -64,7 +64,7 @@ local function build_shader(taps, offset, offset_type, sigma) local norm = 0 if #g_weights % 2 == 0 then - code[#code+1] = 'vec4 c = vec4( 0.0f );' + code[#code+1] = 'vec4 c = vec4( 0.0 );' else local weight = g_weights[1] norm = norm + weight diff --git a/filmgrain.lua b/filmgrain.lua index 8491f17..7044567 100644 --- a/filmgrain.lua +++ b/filmgrain.lua @@ -18,7 +18,7 @@ PERFORMANCE OF THIS SOFTWARE. return function(moonshine) local noisetex = love.image.newImageData(256,256) noisetex:mapPixel(function() - local l = love.math.random() * 255 + local l = love.math.random() return l,l,l,l end) noisetex = love.graphics.newImage(noisetex) diff --git a/fog.lua b/fog.lua new file mode 100644 index 0000000..9c90f9a --- /dev/null +++ b/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/gaussianblur.lua b/gaussianblur.lua index 8d8882a..693414a 100644 --- a/gaussianblur.lua +++ b/gaussianblur.lua @@ -15,7 +15,7 @@ local function resetShader(sigma) local code = {[[ extern vec2 direction; vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _) - { vec4 c = vec4(0.0f); + { vec4 c = vec4(0.0); ]]} local blur_line = "c += vec4(%f) * Texel(texture, tc + vec2(%f) * direction);" diff --git a/glow.lua b/glow.lua index 8e414dd..cec010b 100644 --- a/glow.lua +++ b/glow.lua @@ -25,7 +25,7 @@ local function make_blur_shader(sigma) local code = {[[ extern vec2 direction; vec4 effect(vec4 color, Image texture, vec2 tc, vec2 _) { - vec4 c = vec4(0.0f); + vec4 c = vec4(0.0); ]]} local blur_line = "c += vec4(%f) * Texel(texture, tc + vec2(%f) * direction);" diff --git a/godsray.lua b/godsray.lua index 2c3cfc7..6c9f74a 100644 --- a/godsray.lua +++ b/godsray.lua @@ -52,7 +52,7 @@ return function(moonshine) number illumination = decay; vec4 c = vec4(.0, .0, .0, 1.0); - for (int i = 0; i < samples; ++i) { + for (int i = 0; i < int(samples); ++i) { uv -= offset; c += Texel(tex, uv) * illumination * weight; illumination *= decay; diff --git a/init.lua b/init.lua index 0f0c3a5..431c6c0 100644 --- a/init.lua +++ b/init.lua @@ -27,7 +27,7 @@ local BASE = ... local moonshine = {} moonshine.draw_shader = function(buffer, shader) - front, back = buffer() + local front, back = buffer() love.graphics.setCanvas(front) love.graphics.clear() if shader ~= love.graphics.getShader() then @@ -36,34 +36,47 @@ moonshine.draw_shader = function(buffer, shader) love.graphics.draw(back) end -moonshine.chain = function(effect) - local front, back = love.graphics.newCanvas(), love.graphics.newCanvas() +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 color = {love.graphics.getColor()} + local fg_r, fg_g, fg_b, fg_a = love.graphics.getColor() -- draw scene to front buffer - love.graphics.setCanvas(buffer()) - love.graphics.clear() + 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(color) + love.graphics.setColor(fg_r, fg_g, fg_b, fg_a) love.graphics.setBlendMode("alpha", "premultiplied") for _,e in ipairs(chain) do - (e.draw or moonshine.draw_shader)(buffer, e.shader) + if not disabled[e.name] then + (e.draw or moonshine.draw_shader)(buffer, e.shader) + end end -- present result @@ -87,6 +100,20 @@ moonshine.chain = function(effect) 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) diff --git a/main.lua b/main.lua index 6264284..05afb1e 100644 --- a/main.lua +++ b/main.lua @@ -73,9 +73,9 @@ local ParamInfo = { return {changed = c} end, state = { - {value = r, min = 0, max = 255}, - {value = g, min = 0, max = 255}, - {value = b, min = 0, max = 255} + {value = r, min = 0, max = 1}, + {value = g, min = 0, max = 1}, + {value = b, min = 0, max = 1} }, val = function(s) return {s[1].value, s[2].value, s[3].value} end } @@ -126,7 +126,7 @@ local effects = { feather = ParamInfo.number(0.02, 0, .2) }, EffectInfo'desaturate'{ - tint = ParamInfo.RGB(255,255,255), + tint = ParamInfo.RGB(1,1,1), strength = ParamInfo.number(0.5, 0, 1) }, EffectInfo'dmg'{ @@ -297,7 +297,7 @@ function love.update(dt) end function love.draw() - love.graphics.setColor(255,255,255) + love.graphics.setColor(1,1,1) effect(function() love.graphics.draw(img) @@ -306,12 +306,12 @@ function love.draw() love.graphics.push() love.graphics.translate(550,200) love.graphics.rotate(t) - love.graphics.setColor(100,100,200) + love.graphics.setColor(0.4,0.4,0.8) love.graphics.rectangle('fill', -100,-50,200,100) love.graphics.pop() end) - love.graphics.setColor(0,0,0,200) + love.graphics.setColor(0,0,0,0.8) -- background for chain editor local h = ((show_edit_chain.checked and #effects or 0) + 1) * 22 + 8 love.graphics.rectangle('fill', 5,5, 195, h) diff --git a/scanlines.lua b/scanlines.lua index 59ba947..fb5423d 100644 --- a/scanlines.lua +++ b/scanlines.lua @@ -43,6 +43,9 @@ return function(moonshine) 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 @@ -54,10 +57,11 @@ return function(moonshine) 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[0]) / 255, - (tonumber(c[2]) or defaults.color[1]) / 255, - (tonumber(c[3]) or defaults.color[2]) / 255 + (tonumber(c[1]) or defaults.color[1]), + (tonumber(c[2]) or defaults.color[2]), + (tonumber(c[3]) or defaults.color[3]) }) end diff --git a/sketch.lua b/sketch.lua index befe213..dcd4844 100644 --- a/sketch.lua +++ b/sketch.lua @@ -26,7 +26,7 @@ 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 + return love.math.random(),love.math.random(), 0, 0 end) noisetex = love.graphics.newImage(noisetex) noisetex:setWrap ("repeat", "repeat") diff --git a/suit b/suit index 5d48cf9..1767782 160000 --- a/suit +++ b/suit @@ -1 +1 @@ -Subproject commit 5d48cf97b876696e9ea20a9a9946e26cc7e8c643 +Subproject commit 17677826030a7270b474c5717af43834d583094c diff --git a/vignette.lua b/vignette.lua index a389afa..f13fb8b 100644 --- a/vignette.lua +++ b/vignette.lua @@ -27,7 +27,7 @@ return function(moonshine) 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.5f)) * aspect)); + length((tc - vec2(0.5)) * aspect)); return mix(Texel(tex, tc), color, v*opacity); }]] @@ -37,10 +37,11 @@ return function(moonshine) 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) / 255, - (tonumber(c[2]) or 0) / 255, - (tonumber(c[3]) or 0) / 255, + (tonumber(c[1]) or 0), + (tonumber(c[2]) or 0), + (tonumber(c[3]) or 0), 1 }) end