Skip to content

Instantly share code, notes, and snippets.

@Polkm
Last active March 5, 2022 10:25
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Polkm/6fa5c263efb6cbab9018 to your computer and use it in GitHub Desktop.
Save Polkm/6fa5c263efb6cbab9018 to your computer and use it in GitHub Desktop.
-- So shaders in love only are applied to drawables, which means you can apply
-- diffrent shaders to diffrent sprites, which is nice. Although if you want to
-- apply a shader to a whole scene at once, for example a bloom shader, then you
-- will have to draw all your sprites to a canvas or image, and then draw that
-- composite with the shader.
-- Creates a shader with the given vertex and fragment shader source code. I'll
-- explain that in more detail later. You want to be caching this shader somewhere
-- because it's expensive to create.
local shader = love.graphics.newShader(fragmentSource, vertexSource)
-- Creates a canvas, by defualt it is the window size, which is usually what you
-- want. Note: you don't need this if you just want to apply a shader to sprites
-- individually. Also Note: A canvas is a drawable.
local canvas = love.graphics.newCanvas()
-- This is what a basic pair of shaders looks like. This shader does nothing, it
-- mimics a default sprite draw. Use this as a base to work off of. Shaders use a
-- special language called GLSL, but love uses a slightly modiffied version that
-- simplifes it a bit and renames some things. You can convert GLSL code into love
-- shader code easily enough though.
local fragmentSource = [[
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) {
return Texel(texture, texture_coords) * color;
}
]]
local vertexSource = [[
vec4 position(mat4 transform_projection, vec4 vertex_position) {
return transform_projection * vertex_position;
}
]]
-- Similar to love.graphics.setColor, setShader will apply the shader to all draw
-- calls following it being set. So you need to set it back to nil once you're done.
love.graphics.setShader(shader)
-- Draw what you need to draw here
love.graphics.setShader(nil)
-- Note: this could be written as love.graphics.setShader() but it's less readable
-- in my opinion.
-- setCanvas works the same as setShader, and setColor. It will make it so instead
-- of love drawing things directly to the screen, it draws to an off screen buffer.
-- Which you can later use to draw to the actual screen.
love.graphics.setCanvas(canvas)
-- Draw all the sprites you want to be in the canvas.
-- Setting the canvas to nil means that the following draw calls will be drawn to
-- the actual screen.
love.graphics.setCanvas(nil)
-- So lets put it all together now.
-- For the sake of brevity I'm using a local variable in file scope to cache the
-- shader and canvas. This is lazy and generally not good design though.
local shader = nil -- Note: Again, the nil is for clarity.
local canvas = nil
-- This doesn't necessarily have to be in love.load but where ever it is, it should
-- only be called once.
function love.load()
-- This shader doesn't do anything so it's not exciting but it's something to
-- go on.
local fragmentSource = [[
vec4 effect(vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords) {
return Texel(texture, texture_coords) * color;
}
]]
local vertexSource = [[
vec4 position(mat4 transform_projection, vec4 vertex_position) {
return transform_projection * vertex_position;
}
]]
shader = love.graphics.newShader(fragmentSource, vertexSource)
canvas = love.graphics.newCanvas()
end
-- This table would be filled with the your drawables. Again, this isn't a great
-- way to do it, but it works.
local drawables = {}
function love.draw()
-- In this example I'm going to apply shaders to sprites individually and as a
-- group.
-- Idealy you would loop through your drawables such that you minimize the number
-- of times you have to swap the shader or canvas. This is out of the scope of
-- this gruide though.
for _, drawable in pairs(drawables) do
if usesShader then
-- In this case we want to apply the shader individually.
love.graphics.setShader(shader)
elseif belongsInCanvas then
-- In this case we want to apply the shader to multiple sprites at once.
love.graphics.setCanvas(canvas)
end
-- Draw as you normally would.
love.draw(drawable)
-- Clean up the love state machine for the next drawable.
if usesShader then
love.graphics.setShader(nil)
elseif belongsInCanvas then
love.graphics.setCanvas(nil)
end
end
-- If we didn't know for a fact that the canvas has already been set to nil by
-- this point in the code, then we would have to call the following line to make
-- sure that the canvas gets drawn to the screen.
-- love.graphics.setCanvas(nil)
-- We need to set the shader again so that the canvas can use it, when it gets
-- draw to the screen.
love.graphics.setShader(shader)
-- Finally we draw our canvas.
love.draw(canvas)
-- We need to "clear" the canvas now, other wise the sprites from last draw call
-- will still be in the canvas, making a crazy tracer effect on moving sprites.
-- Unless that's what you want.
-- Note: canvas:clear() is surprisingly expensive.
canvas:clear()
love.graphics.setShader(nil)
end
-- Now lets try looking at a more complicated shader.
local fragmentSource = [[
vec2 image_size;
vec4 effect(vec4 color, Image tex, vec2 tc, vec2 pc)
{
vec2 pixel_offset = vec2(1.0)/image_size;
color = Texel(tex, tc); // maybe add a weight here?
color += Texel(tex, tc + vec2(-offset.x, offset.y));
color += Texel(tex, tc + vec2(0, offset.y));
color += Texel(tex, tc + vec2(offset.x, offset.y));
color += Texel(tex, tc + vec2(-offset.x, 0));
color += Texel(tex, tc + vec2(0, 0));
color += Texel(tex, tc + vec2(offset.x, 0));
color += Texel(tex, tc + vec2(-offset.x, -offset.y));
color += Texel(tex, tc + vec2(0, -offset.y));
color += Texel(tex, tc + vec2(offset.x, -offset.y));
return color / 9.0; // use 10.0 for regular blurring.
}
]]
-- As you can see at the top of the shader, there is a variable defined, called
-- "image_size". Variables defined like this can be changed dynamically by lua.
-- The way you would set "image_size" in the above example is like this.
shader:send("image_size", {canvas:getDimensions()})
-- For more examples of love shaders see here.
-- https://love2d.org/forums/viewtopic.php?f=4&p=38565
-- If you are interested in learning more about GLSL and how to do cool effects
-- with it, I would recommend reading the second half of this tutorial.
-- https://open.gl/framebuffers
-- And if you want to see less useful but more complex shaders go here.
-- http://glslsandbox.com/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment