Last active
September 29, 2024 05:43
-
-
Save Polkm/6fa5c263efb6cbab9018 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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