I'm working on a top-down 2D game and I would like to show the silhouette of my character and other dynamic objects when they are "behind" walls, trees, etc. Has anyone here done this, can you give me some pointers?
This is the effect I want:
- I'm dynamically sorting the draw order of my objects by their bottom Y-position. Like so:
- Ideally I would like to do this for multiple objects in the scene, at different "depth" levels.
I've been using LÖVE and Lua for a while and done a little bit of shader stuff, but nothing with the depth buffer, stencil buffer, or anything like that.
All the tutorials for this effect that I've found so far have been pretty engine-specific, incomplete, or don't cover my use-case where the draw-order changes all the time.
How to show silhouette when behind objects
Forum rules
Before you make a thread asking for help, read this.
Before you make a thread asking for help, read this.
Re: How to show silhouette when behind objects
Do you know what's the next Y layer? If so, can you calculate offset of Y and just render (hidden) part of the player shape in gray in this layer?
Also see:
https://love2d.org/wiki/Contact
Also see:
https://love2d.org/wiki/Contact
My boat driving game demo: https://dusoft.itch.io/captain-bradley- ... itius-demo
- BrotSagtMist
- Party member
- Posts: 661
- Joined: Fri Aug 06, 2021 10:30 pm
Re: How to show silhouette when behind objects
Render the toplayer to a transparent canvas first.
Next render the player/enemy/shadow/silloute again using a blendmode that does not interact with the transparent pixels.
Next render the player/enemy/shadow/silloute again using a blendmode that does not interact with the transparent pixels.
obey
Re: How to show silhouette when behind objects
I do. All of the objects are in one sorted list. Of course I don't know which ones are overlapping without checking bounding boxes or something. The trouble is how to only render the hidden part of the player, especially since there can be multiple other objects overlapping it.dusoft wrote: ↑Sun Feb 13, 2022 9:19 pm Do you know what's the next Y layer? If so, can you calculate offset of Y and just render (hidden) part of the player shape in gray in this layer?
Also see:
https://love2d.org/wiki/Contact
Hmm, yeah I'll try that, thanks. Unfortunately that only lets me have one object (or at least, one depth level) that I can show the silhouette for, but it's a start.BrotSagtMist wrote: ↑Sun Feb 13, 2022 9:36 pm Render the toplayer to a transparent canvas first.
Next render the player/enemy/shadow/silloute again using a blendmode that does not interact with the transparent pixels.
Re: How to show silhouette when behind objects
Hey, hope this helps (most likely not the only way):
1. draw normally
2. to off-sceen canvas: draw mask so we know where the silhouette could should be drawn (on objects, not on player, if you have layers - e.g. floor, you might skip drawing it
3. to another off-screen canvas: draw all silhouettes for the player (and other things)
4. combine the two and draw to screen (or wherever)
I guess you could be smarter with blendModes or something, but at least it works
1. draw normally
2. to off-sceen canvas: draw mask so we know where the silhouette could should be drawn (on objects, not on player, if you have layers - e.g. floor, you might skip drawing it
3. to another off-screen canvas: draw all silhouettes for the player (and other things)
4. combine the two and draw to screen (or wherever)
Code: Select all
local box = function(self, w, h)
love.graphics.rectangle("fill", self.x - w/2, self.y - h, w, h)
end
local things = {
-- character
{
isSilhouetteable = true,
x = 200,
y = 300,
draw = function(self)
love.graphics.setColor(1, 0, 0)
love.graphics.circle("fill", self.x, self.y - 50, 50)
love.graphics.setColor(1, 1, 1)
end
},
-- obstacles
{ x = 250, y = 250, draw = function(self) box(self, 100 ,100) end },
{ x = 150, y = 380, draw = function(self) box(self, 100 ,100) end },
}
local player = things[1]
local maskC = love.graphics.newCanvas(400, 400)
local silhouetteC = love.graphics.newCanvas(400, 400)
local maskSh = love.graphics.newShader([[
extern bool on = false;
vec4 effect(vec4 color, Image tex, vec2 texture_coords, vec2 screen_coords)
{
return on ? vec4(0,0,0,1) : vec4(1,1,1,1);
}
]])
local silhouetteSh = love.graphics.newShader([[
extern vec4 tint = vec4(0, 1, 0, 0.5);
vec4 effect(vec4 color, Image tex, vec2 texture_coords, vec2 screen_coords)
{
return tint;
}
]])
local combineSh = love.graphics.newShader([[
extern Image mask;
extern vec4 tint = vec4(0, 1, 0, 0.5);
vec4 effect(vec4 color, Image tex, vec2 texture_coords, vec2 screen_coords)
{
vec4 m = Texel(mask, texture_coords);
vec4 s = Texel(tex, texture_coords);
s.a *= m.r;
return s;
}
]])
function love.update()
if love.keyboard.isDown("w", "up") then player.y = player.y - 1 end
if love.keyboard.isDown("s", "down") then player.y = player.y + 1 end
if love.keyboard.isDown("a", "left") then player.x = player.x - 1 end
if love.keyboard.isDown("d", "right") then player.x = player.x + 1 end
end
function love.draw()
love.graphics.clear()
-- sort by Y
table.sort(things, function(a, b) return a.y < b.y end)
-- draw normal
for i, v in ipairs(things) do v:draw() end
-- draw mask (where one can put shiluette)
love.graphics.setCanvas(maskC)
love.graphics.clear(0,0,0,1)
love.graphics.setShader(maskSh)
for i, v in ipairs(things) do
maskSh:send("on", not not v.isSilhouetteable)
v:draw()
end
love.graphics.setColor(1, 1, 1)
-- draw silhouettes
love.graphics.setCanvas(silhouetteC)
love.graphics.setShader(silhouetteSh)
love.graphics.clear(1,1,1,0)
for i, v in ipairs(things) do
if v.isSilhouetteable then
v:draw()
end
end
-- combine both
love.graphics.setCanvas()
love.graphics.setShader(combineSh)
combineSh:send("mask", maskC)
love.graphics.draw(silhouetteC)
-- draw to screen
love.graphics.setShader()
love.graphics.setCanvas()
love.graphics.draw(maskC,500, 0, 0, 0.5)
love.graphics.draw(silhouetteC,500, 300, 0, 0.5)
end
Last edited by 0x72 on Mon Feb 14, 2022 10:57 pm, edited 1 time in total.
Re: How to show silhouette when behind objects
Ooh, nice. Thank you so much for writing a demo! That works exactly the way I wanted.
I found that I could eliminate the second canvas and just draw each silhouetteable object with a variation of your combine-shader (just to save a bunch of overdraw) if I pass in the size of the mask to convert screen_coords to mask UVs (assuming it's drawn at 0,0).
...But anyway. Thanks! This is great.
I found that I could eliminate the second canvas and just draw each silhouetteable object with a variation of your combine-shader (just to save a bunch of overdraw) if I pass in the size of the mask to convert screen_coords to mask UVs (assuming it's drawn at 0,0).
Code: Select all
local onScreenCombineShader = love.graphics.newShader([[
extern Image mask;
extern vec2 maskSize;
extern vec4 tint = vec4(0, 1, 0, 0.5);
vec4 effect(vec4 color, Image tex, vec2 texture_coords, vec2 screen_coords)
{
vec4 m = Texel(mask, screen_coords/maskSize);
vec4 s = Texel(tex, texture_coords);
vec4 c = vec4(tint.rgb, tint.a * s.a * m.r);
return c;
}
]])
Re: How to show silhouette when behind objects
Great advice without any math needed.BrotSagtMist wrote: ↑Sun Feb 13, 2022 9:36 pm Render the toplayer to a transparent canvas first.
Next render the player/enemy/shadow/silloute again using a blendmode that does not interact with the transparent pixels.
My boat driving game demo: https://dusoft.itch.io/captain-bradley- ... itius-demo
Re: How to show silhouette when behind objects
This has hardware support if you use stencil masking, so a shader wouldn't be needed.
You have to activate the stencil feature on the config file and use some functions to mark the pixels on the stencil buffer to use as mask.
Check the example at the bottom:
https://love2d.org/wiki/love.graphics.stencil
You have to activate the stencil feature on the config file and use some functions to mark the pixels on the stencil buffer to use as mask.
Check the example at the bottom:
https://love2d.org/wiki/love.graphics.stencil
Who is online
Users browsing this forum: Google [Bot] and 3 guests