I'm trying to find information of how to implement a composite layer function. By composite layer function I mean a function whose input is two color/alpha component values and its output is a color/alpha component value. Something very similar to love.graphics.setBlendMode but with my own function (which is not one of these, otherwise I would not be asking of course!)
I figure it can't be done directly (that is, implement a new blend mode for setBlendMode). But I've found this: http://distrustsimplicity.net/articles/ ... g-in-glsl/ which looks like a step in the right direction.
So I imagine I need to use setShader. I'm new to using shaders, but the AWESOME shader basics tutorial in the love2d blog has been enlightening. Is there a way to retrieve the pixels already drawn? Or do I need to paint my lower layer to a canvas and pass the canvas through shader:send()? And in that case, how would I handle the coordinates so that only the area of the canvas corresponding to the drawn rectangle is used for compositing?
EDIT: Got it now. Here's the resulting .love file I've been using for experimenting.
[Solved] Composite layers?
Forum rules
Before you make a thread asking for help, read this.
Before you make a thread asking for help, read this.
[Solved] Composite layers?
- Attachments
-
- composite.love
- Composite layers test using shaders (updated, with UI)
- (185.24 KiB) Downloaded 82 times
Last edited by pgimeno on Fri Nov 13, 2015 4:00 am, edited 4 times in total.
Re: Composite layers?
So I finally got to implementing it in the only way I figured out. I realized that I could use the screen position parameter for the lower layer coordinates that I needed.
However, this way feels a bit convoluted. Is there a better way than this? Currently it involves four steps:
- draw everything to a canvas rather than to the screen,
- pass the canvas to the shader,
- draw the canvas to the screen,
- draw the overlaid layer using the shader.
I wonder if that can be made simpler. Skipping the 'draw to a canvas' part would be ideal. Am I stuck with doing it this way?
.love file:
(edit: uploaded to the OP now)
However, this way feels a bit convoluted. Is there a better way than this? Currently it involves four steps:
- draw everything to a canvas rather than to the screen,
- pass the canvas to the shader,
- draw the canvas to the screen,
- draw the overlaid layer using the shader.
Code: Select all
local canvas, shader, img1, img2
function love.load(args)
shader = love.graphics.newShader[[
extern Image canvas;
// Composite function
vec4 composite(vec4 lower, vec4 upper)
{
return vec4(
// "Overlay": lerp between multiply and screen modes, based on lower
mix(lower.x*upper.x, 1.-(1.-lower.x)*(1.-upper.x), lower.x),
mix(lower.y*upper.y, 1.-(1.-lower.y)*(1.-upper.y), lower.y),
mix(lower.z*upper.z, 1.-(1.-lower.z)*(1.-upper.z), lower.z),
upper.w
);
}
vec4 effect(vec4 clr, Image img, vec2 imgpos, vec2 scrpos)
{
vec2 cpos;
cpos.x = scrpos.x/love_ScreenSize.x;
cpos.y = 1-scrpos.y/love_ScreenSize.y;
// Composite mode.
return composite(Texel(canvas, cpos), Texel(img, imgpos)*clr);
}
]]
image1 = love.graphics.newImage("image1.jpg")
image2 = love.graphics.newImage("image2.jpg")
canvas = love.graphics.newCanvas(800, 600)
end
local imgx, imgy
function love.update(dt)
-- update imgx, imgy
end
function love.draw()
-- Draw the bottom layer to the canvas. This could be more complicated than
-- just drawing a static image; that's just an example.
canvas:clear()
love.graphics.setCanvas(canvas)
love.graphics.draw(image1, 0, 0)
love.graphics.setCanvas()
-- Send the canvas (bottom layer) to the shader
shader:send("canvas", canvas)
-- Actually draw the canvas
love.graphics.draw(canvas, 0, 0)
-- Draw the top layer with shader
love.graphics.setShader(shader)
love.graphics.draw(image2, imgx, imgy)
love.graphics.setShader()
end
.love file:
(edit: uploaded to the OP now)
Last edited by pgimeno on Fri Nov 13, 2015 3:35 am, edited 2 times in total.
Re: Composite layers?
I've done something similar to this myself. And it seems completely impossible to access what you are drawing to. (I have no idea why. Could anyone explain why this limitation exist?)
So I did the same as you. Drawing to a canvas and sending the canvas to the shader then drawing a blendmode image. What I've done different is that I draw the image to back on the canvas. I don't know if that is a good idea.
So I've implemented: Screen, multiply, and overlay. All with proper opacity alpha blend (love2d blend mode screen and multiply does not correctly alpha blend)
So I did the same as you. Drawing to a canvas and sending the canvas to the shader then drawing a blendmode image. What I've done different is that I draw the image to back on the canvas. I don't know if that is a good idea.
So I've implemented: Screen, multiply, and overlay. All with proper opacity alpha blend (love2d blend mode screen and multiply does not correctly alpha blend)
Artal, A .PSD loader: https://github.com/EvineDev/Artal
Re: Composite layers?
Thanks for the video and for the reply.
It's also more powerful, in that you can stack as many layers as you want. With the method I outlined, I would not be able to add another layer on top of the last. That was why I wanted to try writing back to the canvas. I wonder if a double-buffer scheme would help. I remember having seen a recommendation about using a double buffer in the OpenGL docs, but I can't remember enough details to know if it applies to this case.
Lots of formulas to try now! http://www.w3.org/TR/compositing-1/#blending
That was the next thing I was going to try. I feel uneasy about modifying the same canvas I am reading from, but I guess it must be prepared to work.Evine wrote:So I did the same as you. Drawing to a canvas and sending the canvas to the shader then drawing a blendmode image. What I've done different is that I draw the image to back on the canvas. I don't know if that is a good idea.
It's also more powerful, in that you can stack as many layers as you want. With the method I outlined, I would not be able to add another layer on top of the last. That was why I wanted to try writing back to the canvas. I wonder if a double-buffer scheme would help. I remember having seen a recommendation about using a double buffer in the OpenGL docs, but I can't remember enough details to know if it applies to this case.
Lots of formulas to try now! http://www.w3.org/TR/compositing-1/#blending
Re: [Solved] Composite layers?
I think your formula to overlay blending might be wrong. I've attached my code so you could take a look at that. (I did some pretty stupid things in there though. Like placing where the image is drawn inside the shader. But the formulas for blending is correct.)
Code: Select all
--[[
The MIT License (MIT)
Copyright (c) 2015 Daniel Rasmussen
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.
--]]
blendMode = {}
blendMode.common = [[
// This file is concatenated in front of other shaderfiles to give them access to generic functions.
vec4 loadImage(Image loadImage , vec2 texture_coords , vec2 pos, vec2 size )
{
return Texel(loadImage, vec2(
texture_coords.x * love_ScreenSize.x/size.x - pos.x/size.x ,
texture_coords.y * love_ScreenSize.y/size.y - pos.y/size.y));
}
vec4 alphaBlend(vec4 SOURCE_C , vec4 DRAW_C , vec4 result)
{
result = (SOURCE_C*(1-DRAW_C.a)+result*DRAW_C.a) ; // alpha blend
result.a = (SOURCE_C.a*(1-DRAW_C.a)+DRAW_C.a) ;
return result;
}
//Avoid drawing outside the DRAW_C texture
vec4 clipOutline(vec2 texture_coords, vec2 pos, vec2 size, vec4 SOURCE_C , vec4 result)
{
if( texture_coords.x * love_ScreenSize.x < pos.x ||
texture_coords.y * love_ScreenSize.y < pos.y ||
texture_coords.x * love_ScreenSize.x > pos.x + size.x ||
texture_coords.y * love_ScreenSize.y > pos.y + size.y )
{
return SOURCE_C;
}
else
{
return result;
}
}
]]
blendMode.overlay = love.graphics.newShader(blendMode.common..[[
extern Image drawingImage; //This is image being added
extern vec2 pos; //Position on screen
extern vec2 size; //Size of image Being added.
vec4 result ; //result
vec4 effect( vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords )
{
vec4 DRAW_C = loadImage( drawingImage , texture_coords , pos, size );
vec4 SOURCE_C = Texel(texture, texture_coords );
for ( int i = 0; i < 3; i++ )
{
result[i] = (SOURCE_C[i] < 0.5) ?
(2*SOURCE_C[i] * DRAW_C[i]) : //Multi blend
(1-2*(1-SOURCE_C[i]) * (1-DRAW_C[i])) ; //Screen blend
}
//result = (SOURCE_C*(1-DRAW_C.a)+result); // premultiplied
result = alphaBlend(SOURCE_C , DRAW_C , result);
result = clipOutline(texture_coords, pos, size, SOURCE_C, result);
return result;
}
]])
blendMode.multiply = love.graphics.newShader(blendMode.common..[[
extern Image drawingImage; //This is image being added
extern vec2 pos; //Position on screen
extern vec2 size; //Size of image.
vec4 result ; //result
vec4 effect( vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords )
{
vec4 DRAW_C = loadImage( drawingImage , texture_coords , pos, size );
vec4 SOURCE_C = Texel(texture, texture_coords );
result = (SOURCE_C * DRAW_C) ; //Multi blend
//result = (SOURCE_C*(1-DRAW_C.a)+result); // premultiplied
result = alphaBlend(SOURCE_C , DRAW_C , result);
result = clipOutline(texture_coords, pos, size, SOURCE_C, result);
return result;
}
]])
blendMode.screen = love.graphics.newShader(blendMode.common..[[
extern Image drawingImage; //This is image being added
extern vec2 pos; //Position on screen
extern vec2 size; //Size of image.
vec4 result ; //result
vec4 effect( vec4 color, Image texture, vec2 texture_coords, vec2 screen_coords )
{
vec4 DRAW_C = loadImage( drawingImage , texture_coords , pos, size );
vec4 SOURCE_C = Texel(texture, texture_coords );
result = (1-(1-SOURCE_C) * (1-DRAW_C)) ; //Screen blend
//result = (SOURCE_C*(1-DRAW_C.a)+result); // premultiplied
result = alphaBlend(SOURCE_C , DRAW_C , result);
result = clipOutline(texture_coords, pos, size, SOURCE_C, result);
return result;
}
]])
blendMode.canvas = love.graphics.newCanvas(1920,1080)
local bufferImage = bufferImage or love.graphics.newImage("beautifulbox.png")
local blendInImage = blendInImage or love.graphics.newImage("beautifulbox2.png")
function drawBlend(image,mode,x,y)
x = x or 0
y = y or 0
if mode == "overlay" or mode == "multiply" or mode == "screen" then
love.graphics.setShader(blendMode[mode])
blendMode[mode]:send("drawingImage",image)
blendMode[mode]:send("pos",{x,y})
blendMode[mode]:send("size",{image:getWidth(),image:getHeight()})
else
assert(false, "Invalid blend mode, \""..tostring(mode).."\"\nValid blend modes == \"screen\" , \"multiply\" , \"overlay\"")
end
love.graphics.draw(blendMode.canvas)
love.graphics.setShader()
end
function love.draw()
love.graphics.setCanvas(blendMode.canvas)
blendMode.canvas:clear()
love.graphics.setColor(255,255,255)
love.graphics.draw(bufferImage,0,0)
if love.timer.getTime() % 4 < 1 then
drawBlend(blendInImage,"multiply",0,0)
love.graphics.print("multiply")
elseif love.timer.getTime() % 4 < 2 then
drawBlend(blendInImage,"screen",0,0)
love.graphics.setColor(0,0,0)
love.graphics.print("screen")
elseif love.timer.getTime() % 4 < 3 then
love.graphics.setColor(0,0,0)
drawBlend(blendInImage,"overlay",0,0)
love.graphics.print("overlay")
else
love.graphics.draw(blendInImage)
love.graphics.setColor(255,255,255)
love.graphics.print("alpha")
end
love.graphics.setCanvas()
love.graphics.setColor(255,255,255)
love.graphics.draw(blendMode.canvas)
end
- Attachments
-
- blendMode.love
- (41.5 KiB) Downloaded 80 times
Artal, A .PSD loader: https://github.com/EvineDev/Artal
Re: [Solved] Composite layers?
Don'tcha love feature creep...
I like GIMP's better because it's more continuous and softer. Compare the blend graphs. I love it for colorizing, which is what I may be using this for in future.
(edit: uploaded to the OP now)
There are several formulas called "overlay". I used the one GIMP used until 2.8. I think it's been recently changed in GIMP to match SVG's.Evine wrote:I think your formula to overlay blending might be wrong.
I like GIMP's better because it's more continuous and softer. Compare the blend graphs. I love it for colorizing, which is what I may be using this for in future.
(edit: uploaded to the OP now)
Last edited by pgimeno on Fri Nov 13, 2015 3:34 am, edited 1 time in total.
Re: [Solved] Composite layers?
Ah cool. I took a look over your code and it looked really good, though you should send the canvas to the shader immediately after you set the shader. Currently the blendmode is being set a frame late. You can easily see this if you insert a love.timer.sleep(0.5)
Artal, A .PSD loader: https://github.com/EvineDev/Artal
Re: [Solved] Composite layers?
Oh, good catch! I reorganized the order somewhat, trying to implement your suggestion of drawing the top layer to the canvas rather than separately, but got distracted adding features and forgot to finish that part.
And, well, sorry for the mess, I realize it's a lot of ad-hoc code for the controls rather than making functions or modules to deal with them... it was too tiny a project for me to care about that
Updated now and uploaded to the OP. Thanks a lot!
And, well, sorry for the mess, I realize it's a lot of ad-hoc code for the controls rather than making functions or modules to deal with them... it was too tiny a project for me to care about that
Updated now and uploaded to the OP. Thanks a lot!
Who is online
Users browsing this forum: No registered users and 189 guests