Shader outlined around

I don’t know :frowning:

Good luck :slight_smile:

Thanks anyway :slight_smile:

I will wait…

you could replace the laplacian with:

 P\_UV float GetLaplacian(sampler2D s, P\_UV vec2 uv, P\_UV float a0, P\_UV float thickness) { a0 \*= 8.; a0 -= texture2D(s, uv + vec2(thickness \* CoronaTexelSize.x, 0.)).a; a0 -= texture2D(s, uv - vec2(thickness \* CoronaTexelSize.x, 0.)).a; a0 -= texture2D(s, uv + vec2(0., thickness \* CoronaTexelSize.y)).a; a0 -= texture2D(s, uv - vec2(0., thickness \* CoronaTexelSize.y)).a; a0 -= texture2D(s, uv + vec2(thickness \* CoronaTexelSize.x, thickness \* CoronaTexelSize.y)).a; a0 -= texture2D(s, uv + vec2(thickness \* CoronaTexelSize.x, -thickness \* CoronaTexelSize.y)).a; a0 -= texture2D(s, uv - vec2(thickness \* CoronaTexelSize.x, thickness \* CoronaTexelSize.y)).a; a0 -= texture2D(s, uv - vec2(thickness \* CoronaTexelSize.x, -thickness \* CoronaTexelSize.y)).a; return a0; }

which would better sample the diagonals, but while this will fill in the corners on a true rect, it’ll OVER-correct on a rounded rect.  

(both are just discrete approximations, so you’ll have to accept compromise “somewhere”)

@davebollinger 

Thank you! Actually it’s better but we’re not there yet. Do you think there is not a way to get even better?

“better” under what usage conditions?  for this SPECIFIC rounded rect?  if so, then it’s not even needed - just draw a larger white one first into the snapshot then overlay with the smaller orange one == perfect.  (or just create it in illustrator, export a bitmap and be done with it - you’ll get better antialiasing too)

so, “better” for any arbitrary shape?  probably not.  as you tweak it (say a larger or differently weighted kernel) to improve one kind of situation (say acute angles) you’ll likely worsen it in some other situation (say obtuse angles).  the next step up from laplacian would be canny (for gaussian weighting), short of that I’d think you’d be unlikely to see general-case improvement.

if it were just a  rounded rect I would have made a bigger copy (To reproduce the border).

But my snap actually contains many elements for this I thought to use this shader effect.

I was thinking of something but I do not know if it can be done.

Once you have created the edge with the kernel, fill the area inside the edge. Once this is done bring the basic image to the foreground. Can it work?

Hi @maximo97. There might be ways to make marginal improvements, but I think @davebollinger more or less nailed it. You can scale up to different convolution strategies, but some of the hardware’s tradeoffs then become an issue, e.g. too many costly texture fetches (and immediately queried for alpha, at that) + the multliplier (4, 8, etc.) is going to shove out some bits of precision besides (and colors are already pretty low).

Assuming you don’t need interactive generation of the outline (your last post suggest you don’t?), a semi-offline way to go would be canvases.

Many games build outlines using a stencil buffer: turn off normal rendering, then “draw” the object, but set some conditions, such as alpha > 0. If the condition passes, increment the stencil count, and so on. You would do this a few times, say with your object shifted n pixels up, down, left, and right.

Wherever the count is exactly 1, you’ve crossed an alpha-no alpha boundary, and so can emit a border.

Corona doesn’t expose the stencil buffer at the moment, but in certain scenarios you can fake it. In fact this situation might not be so different from this one.

Once you had the border, you could merge it with your image in a canvas.

Unfortunately I don’t have anything on hand, but maybe I’ll give it a shot. Otherwise, it’s probably a good challenge if you want to get some shader experience.  :slight_smile:

Hi @StarCrunch

Thank you so much for the advice. I will do some tests even if I doubt I will get results soon. In any case I have many ideas and I’m not going to give up!

Could you clarify one last thing?

Why the border created with your kernel and semi-transparent? Can I change this?

My guess is that it’s finding non-transparent pixels (thus tcolor.a is close to 1), yet because these are along the curved part the Laplacian isn’t sampling as many opaque neighbors (two rather than three, basically), thus you’re getting a fairly low alpha  and alpha * (1. - tcolor.a) is lower still. Maybe there’s a smarter way to mix them together, but I don’t have any suggestions, unfortunately.

Well, I start to understand something…

for empty pixels (Image attached on) it is sufficient for me to put:

a0 \*= 1.;

But then the problem remains for transparency.

A step at a time…

I always thank you for sharing your ideas

for transparency issues, I suppose you could just brute force it?, fe:

 P\_COLOR vec4 FragmentKernel (P\_UV vec2 uv) { P\_UV vec4 tcolor = texture2D(CoronaSampler0, uv); P\_UV float alpha = GetLaplacian(CoronaSampler0, uv, tcolor.a, CoronaVertexUserData.x); if (alpha \< 0.) tcolor = vec4( CoronaVertexUserData.yzw, 1. ); return CoronaColorScale(tcolor); }

Hi @davebollinger

Sorry for the delay.

With control, it removes transparency but it also removes the basic image.

Everything seems more complicated than expected …

Well, this is a bit quick and dirty, but is sort of along the ideas of the stencil buffer mentioned above:

local kernel = { name = "outline", category = "filter" } kernel.vertexData = { { index = 0, name = "extra", default = 1.725, min = 0 }, { index = 1, name = "cutoff", default = 0, min = 0 } } kernel.vertex = [[// varying P\_UV vec2 v\_our\_uv; // see note on v\_TexCoord P\_POSITION vec2 VertexKernel (P\_POSITION vec2 pos) { P\_UV vec2 offset = sign(CoronaTexCoord - .5); pos += offset \* CoronaVertexUserData.xx; v\_TexCoord += offset \* CoronaTexelSize.xy; // undocumented varying! (more proper to just use another of our own) return pos; }]] kernel.fragment = [[// varying P\_UV vec2 v\_our\_uv; // see note above on v\_TexCoord (if nervous, use this instead of uv below) P\_COLOR float Sample (P\_UV vec2 uv) { P\_COLOR float alpha = texture2D(CoronaSampler0, uv).a; return step(CoronaVertexUserData.y, alpha); } P\_COLOR vec4 FragmentKernel (P\_UV vec2 uv) { P\_COLOR float sum = 0.; sum += Sample(uv - vec2(CoronaTexelSize.x \* CoronaVertexUserData.x, 0.)); sum += Sample(uv + vec2(CoronaTexelSize.x \* CoronaVertexUserData.x, 0.)); sum += Sample(uv - vec2(0., CoronaTexelSize.y \* CoronaVertexUserData.x)); sum += Sample(uv + vec2(0., CoronaTexelSize.y \* CoronaVertexUserData.x)); sum \*= step(sum, 3.); // can vary from 1 to 3 (0 = nothing, 4 = part of original) return CoronaColorScale(vec4(min(sum, 1.))); // saturate to rgba = 1 }]] graphics.defineEffect(kernel) local File = "game/dot/gfx/Toon-Up.png" local Mode = 0 if Mode == 0 then local aa = display.newImage(File, display.contentCenterX, display.contentCenterY) local rr = display.newRect(aa.x,aa.y,aa.width,aa.height) rr.fill = { type = "image", filename = File } rr.fill.effect = "filter.custom.outline" rr.fill.effect.cutoff = .5 elseif Mode == 1 then -- poor man's sprite (nothing really handy) local frames, w, h = {}, 0, 0 for \_, name in ipairs{ "game/dot/gfx/Toon-Down.png", "game/dot/gfx/Toon-Half.png", File } do local temp = display.newImage(name) frames[#frames + 1] = { name = name, w = temp.width, h = temp.height } w, h = math.max(w, temp.width), math.max(h, temp.height) temp:removeSelf() end local canvas = graphics.newTexture{ type = "canvas", width = w, height = h, pixelWidth = math.min(w, display.contentWidth), pixelHeight = math.min(h, display.contentHeight) } local frame = 0 local aa = display.newRect(display.contentCenterX, display.contentCenterY, 1, 1) local ii = display.newRect(0, 0, 1, 1) local rr = display.newRect(display.contentCenterX, display.contentCenterY, 1, 1) rr.fill = { type = "image", filename = canvas.filename, baseDir = canvas.baseDir } rr.fill.effect = "filter.custom.outline" rr.fill.effect.cutoff = .25 canvas:draw(ii) local function UpdateFrame () frame = frame + 1 if frame \> #frames then frame = 1 end local info = frames[frame] local ifill = { type = "image", filename = info.name } aa.width, aa.height, aa.fill = info.w, info.h, ifill ii.xScale, ii.yScale, ii.width, ii.height, ii.fill = w / info.w, h / info.h, info.w, info.h, ifill rr.width, rr.height = info.w, info.h end timer.performWithDelay(250, function() UpdateFrame() canvas:draw(ii) canvas:invalidate("cache") end, 0) end

Mode = 0 for regular image, Mode = 1 for a “sprite” (just a canvas, which was slightly more general).

Test graphics attached.

Very useful thanks.

This will come back to me very useful with images or sprites!

I do not think it can be used with snapshots. I will do some tests