Quick Links: Download Gideros Studio | Gideros Documentation | Gideros Development Center | Gideros community chat | DONATE
Shape blur problem — Gideros Forum

Shape blur problem

rrraptorrrraptor Member
edited October 2019 in General questions
So Im trying to achieve this effect:

Its working, yes, but there is some problems.
First, diferent blur levels:

And second, this mess:


Im doing it like in "blur" example that cames with gideros.
function GShape:initShader(blurLevel)
	local texw, texh = self:getSize()
	--Initial blur level
	self.blurShader:setConstant("fRad",Shader.CINT,1,blurLevel)
	--Initial texel size
	self.blurShader:setConstant("fTexelSize",Shader.CFLOAT4,1,{1/texw,1/texh,0,0})
 
	self.blurBG = RenderTarget.new(texw,texh,false)
	self.blurBG:draw(stage)
	self.blurIMG = Bitmap.new(self.blurBG)
	self.blurIMG:setShader(self.blurShader)
 
	self.RT = RenderTarget.new(texw,texh,true)
	self:setTexture(self.RT)
	self:setShader(self.blurShader)
end
--
function GShape:updateBlur()
	local x,y = self:getPosition()
	local offsetX, offsetY = 0, 0
	local texw, texh = self:getSize()
 
	-- circle is centerd, so we need to offset drawing position by its radius
	if (self.name == "circle") then 
		offsetX = self.r
		offsetY = self.r
	end
	-- render stage except this object
	self:setVisible(false)
	self.blurBG:draw(stage, -x+offsetX, -y+offsetY)
	self:setVisible(true)
 
	self.blurShader:setConstant("fTexelSize",Shader.CFLOAT4,1,{0,1/texh,0,0}) --Step 1: Vertical blur
	self.RT:draw(self.blurIMG)
	self.blurShader:setConstant("fTexelSize",Shader.CFLOAT4,1,{1/texw,0,0,0}) --Step 2: Horizontal blur
end
--
function GShape:setPosition(x, y)
	Path2D.setPosition(self, x, y)
	self:updateBlur()
end
I spent whole day trying to fix it, but i have no idea how to do it. Maybe I need to modify shader, but I dont know how to work with them. Maybe someone will have a solution.

Project atached.

Comments

  • hey that's pretty nice thing you have here.
    What exactly is your problem I don't really get it?
    For your second question I think it's a mess because the shader is on top of another.
    I am sure you have read this https://wiki.giderosmobile.com/index.php/Writing_Shaders
    my growING GIDEROS github repositories: https://github.com/mokalux?tab=repositories
  • and of course the top of the top http://forum.giderosmobile.com/discussion/6667
    but that's pretty hard core, so beware :o
    my growING GIDEROS github repositories: https://github.com/mokalux?tab=repositories
  • rrraptorrrraptor Member
    edited October 2019
    I have 0 experience writing shaders, but i gave it a try. All in all, im getting somewhere, but I have one question for now.
    @hgy29 how can I pass vec2 value to shader instead of vec4?
    For vec4 I must use this:
    {name="fDir",type=Shader.CFLOAT4,vertex=false}
    In shader:
    uniform vec4 fDir
    In Lua:
    shader:setConstant("fDir",Shader.CFLOAT4,1,{0,1,0,0})
    But I use same structure for vec2, and in shader code I only use xy component (e.g. fDir.xy).
    I tried to use Shader.DFLOAT, but it wont work, maybe I missed something...
    MoKaLux said:

    and of course the top of the top http://forum.giderosmobile.com/discussion/6667
    but that's pretty hard core, so beware :o

    I think its an overkill for just a blur shader :blush:

    Likes: Yan

    +1 -1 (+1 / -0 )Share on Facebook
  • hgy29hgy29 Maintainer
    In theory you should use a Shader.CFLOAT2, but I am not completely sure I implemented it in gideros.

    Likes: rrraptor

    +1 -1 (+1 / -0 )Share on Facebook
  • rrraptorrrraptor Member
    edited October 2019
    Aaaah...seems like wiki is missing this one :smile:



    Also, maybe its better to rewrite Shader constatnts description to somethink like:
    Shader.CFLOAT2
    Uniform descriptor CFLOAT2 data type
    Equivalent to glsl "uniform vec2"
    333.png
    245 x 174 - 5K
  • rrraptorrrraptor Member
    edited October 2019
    Awww, I made this to work..



    Whole problem was in background rendering. So I did this:
    function GShape:renderBG()
    	local x,y = self:getPosition()
    	local offsetX, offsetY = 0, 0
     
    	-- circle is centerd, so we need to offset drawing position by its radius
    	if (self.name == "circle") then 
    		offsetX = self.r
    		offsetY = self.r
    	end
     
    	local par = self:getParent()
    	if (not par) then return end
    	local ind = par:getChildIndex(self)
    	local l = par:getNumChildren()
    	print("Hide from " .. ind .. " to "..l)
    	for i = ind, l do 
    		local c = par:getChildAt(i)
    		c:setVisible(false)
    	end
    	self.blurBG:draw(stage, -x+offsetX, -y+offsetY)
    	for i = ind, l do 
    		local c = par:getChildAt(i)
    		c:setVisible(true)
    	end
    end
    --
    function GShape:updateBlur()
    	if (self.needToUpdate) then 
    		self:forceUpdate()
    	end
    end
    --
    function GShape:forceUpdate()
    	self:renderBG()	
    	-- vertical blur
    	shader:setConstant("fDir",Shader.CFLOAT2,1,{0,1})
    	self.RT:draw(self.blurIMG)
    	-- horizontal blur
    	shader:setConstant("fDir",Shader.CFLOAT2,1,{1,0})
    end
    --
    function GShape:setPosition(x, y)
    	Path2D.setPosition(self, x, y)
    	self.needToUpdate = true
    end
    Probably not the best solution, and I need to try to ptimize it, but a BIG progress there :blush:
    I also modifed the shader (not by myself ofcourse :smiley: ).
    Source link
    Project attached.

    And the final queston, how can a recolor the texture? I mean, equivalent to setColorTransform, that only applies to texture. I understand that to achieve this I need to edit fragment shader, but idk how)))

    Tried this:
    vec4 toGrayscale(in vec4 color)
    {
    	float average = (color.r + color.g + color.b) / 3.0;
    	return vec4(average, average, average, 1.0);
    }
     
    vec4 colorize(in vec4 grayscale, in vec4 color)
    {
    	return (grayscale * color);
    }
     
    void main() {
    	vec4 outColor = blur13(fTexture, fTexCoord, fResolution, fDir);
    	vec4 grayscale = toGrayscale(outColor);
    	vec4 finalColor = colorize(grayscale, fColorTransform);
    	gl_FragColor = finalColor;
    }
    But this is not what im looking for))
    I need you @hgy29 :blush:
    vJ1na8h0ySM.jpg
    264 x 236 - 22K
    zip
    zip
    ShapeBlur.zip
    317K
  • hgy29hgy29 Maintainer
    to mimic setColorTransform you would just need:
     vec4 finalColor=outColor*fColorTransform;
    but I am not sure that's what you want to achieve, btw your toGrayscale is wrong:
    float average = color.r*0.4+color.g*0.5+color.b*0.1;
    would be more correct, and those are only approximate coefficients.

    Likes: rrraptor

    +1 -1 (+1 / -0 )Share on Facebook
  • hgy29hgy29 Maintainer
    More typical coefs: 0.2989, 0.5870, 0.1140 for R,G,B

    Likes: rrraptor

    +1 -1 (+1 / -0 )Share on Facebook
  • Added shadows :o


    Blur works very strange. Every shape that is not rectangle offsets the background a bit (as you can see on a picture above). Need to fix it somehow... Probably texel size issue, but idk, need to do some tests.

    Source
  • hgy29hgy29 Maintainer
    About texel size, beware about gideros extending textures so that they are power of two sized. The texel size should be computed from the extended texture size.
  • rrraptorrrraptor Member
    edited October 2019
    hgy29 said:

    About texel size, beware about gideros extending textures so that they are power of two sized.

    So what could posibly go wrong? :smiley: Out of memory?
    Also, to render shadow I downscale original shape, then render it to RenderTarget, apply blur, and upscale Bitmap to shape scale like that: 1/downScale. And for shadows I use different shader.
    hgy29 said:

    The texel size should be computed from the extended texture size.

    I use shape:getSize() and set texel size to a {1/w, 0} for horizontal blur, and {0, 1/h} for vertical, but something is wrong...

  • hgy29hgy29 Maintainer
    edited October 2019
    rrraptor said:


    I use shape:getSize() and set texel size to a {1/w, 0} for horizontal blur, and {0, 1/h} for vertical, but something is wrong...

    Yes thats what I mean: if your w and h aren't power of two, then the texel size will be wrong! When you create the a rendertarget of 50x120 pixels (for exemple), gideros internals makes one of 64x128 to please the GPU. So your texel size will be 1/64 and 1/128, not 1/50 and 1/120

    EDIT: BTW gideros can compute the texel size for you with Shader.SYS TEXTUREINFO, with this attribute on a CFLOAT4 constant, gideros will fill it with the extent of the original texture in x and y, and the texel size in z and w
  • rrraptorrrraptor Member
    edited October 2019
    hgy29 said:

    So your texel size will be 1/64 and 1/128, not 1/50 and 1/120

    Then why it works for rectangles? Their rendertargets w,h aren't power of two, but everything works.

    Btw, I added SYS_TEXTUREINFO flag, but they now flicking and look a bit strange:

    33.png
    468 x 388 - 219K
    33.png 218.6K
  • hgy29hgy29 Maintainer
    Ok, I tried your code and turns out that the rendering offset is not at all correct by texel size issue. Now I think about it, a texel size problem would just have made the blur cover and unexpected amount of texels, but not have changed the center position.

    Your issue is that you apply your blur to a Path2D shape, which only use texture coordinates for the 'filled' part, not for the outline. Since there is an outline in your case, the shape size is slightly bigger than the filled part (by the line thickness exactly). In theory, substracting the line width from the shape size before creating the render target should fix it.
  • hgy29hgy29 Maintainer
    Try this
    lua
    lua
    GShape.lua
    10K

    Likes: rrraptor

    +1 -1 (+1 / -0 )Share on Facebook
  • rrraptorrrraptor Member
    edited October 2019
    Hmmm
    It works even if I just use
    local tw,th = self.w,self.h
    Probably because self.w,self.h is not including stroke width.
    But i discovered a very strange bug.


    First, shape that is on top blures incorrectly (even with your fix), it dont have shadow, and its stroke is blured somehow.
    Second, the circle also dont have shadow, and also have blured stroke, BUT! Its texture is 100% correct

    Also, Im using Path2D because I didt find another way of applying some sort of mask on shader. Pixel and Bitmap are squares, Mesh is a black box for me, Shape... well I almost forgot about it, because we have more powerful and friendly Path2D :smiley:
    33.jpg
    401 x 297 - 48K
    33.jpg 48.2K
  • hgy29hgy29 Maintainer
    Ah, I forgot to remove the setAnchorPoint call around line 300 (for rrect)
  • rrraptorrrraptor Member
    edited October 2019
    Also, any way to fix this?


    P.S. lol, looking like this cat xDDD

    1233.jpg
    36 x 31 - 2K
    e8ecd48d9f71e8d75c5c7057105afda4.jpg
    120 x 120 - 17K
  • hgy29hgy29 Maintainer
    Not sure, it could be due to using a radius too small as compared with the line width in your rrect
  • rrraptorrrraptor Member
    edited November 2019
    In my first post you can see exactly same problem with rounded rectangle. It hapens at any radius/width/feather value. Even on a circle, but not that frequntly. Also, I noticed that this gaps appears only at 45 degrees step (0, 45, 90, 135, 180, 225, 270, 315).

    EDIT: Ok, i tried the Shape object aaaand i got this


    Stroke looks like a mess with virtices, but also texture itself is a small square (idk why).

    In initBlurShader I added this:
    		self.textureB = RenderTarget.new(tw*2,th*2,true)
    		-- remove Path2D
    		self:removeChild(self.shape)
    		self.shape = Shape.new()
    		self.shape:setFillStyle(Shape.TEXTURE, self.textureB)
    		self.shape:setLineStyle(10,0xffffff,1)
    		self.shape:setShader(self.blurShader)
     
    		self.shape:beginPath()
    		self.shape:moveTo(0,0)
    		self.shape:lineTo(tw,0)
    		self.shape:lineTo(tw*2,th)
    		self.shape:lineTo(0,th*2)
    		self.shape:lineTo(0,0)
    		self.shape:endPath()
     
    		self.shape:setAnchorPoint(self.ax, self.ay)
    		self:addChild(self.shape)
    223.png
    262 x 247 - 86K
    223.png 86.4K
  • rrraptorrrraptor Member
    edited November 2019
    Updated.
    • Reworked shadows
    • Fixed anchor points
    • Shadow and blur now uses 1 shader
    • Added "updateRelativeXY" to correctly update blur if object is a child of another sprite
    • Overridden "setPosition", to apply blur when shape changes its position
    • Added setBlurColor (setBlurColorR, G, B, A) to mirror setColorTransform
    TODO:
    • Comments :wink:

    Likes: MoKaLux

    +1 -1 (+1 / -0 )Share on Facebook
  • rrraptorrrraptor Member
    edited January 2021
    For some reason I decided to update this thing for lua shaders, but it no longer works...
    Shape do not have a stroke anymore.
    Works on 2019.9, but does not on 2021.1

    It uses Path2D as a shape.

    @hgy29 is it possible to make it work again?)

    Here is a simple version (lua shaders):
    require("Library/luashader/luac_loader")
    require("Library/luashader/luac_codegen")
    require("Library/luashader/luashader") -- include luashader to project
     
    local function makeEffect(vshader,fshader)
    	local shader = Shader.lua(vshader,fshader,0, {
    			{name="vMatrix",type=Shader.CMATRIX,sys=Shader.SYS_WVP,vertex=true},
    			{name="fColor",type=Shader.CFLOAT4,sys=Shader.SYS_COLOR,vertex=false},
    			{name="fTexture",type=Shader.CTEXTURE,vertex=false},
    			{name="fTextureInfo",type=Shader.CFLOAT4,sys=Shader.SYS_TEXTUREINFO,vertex=false},
     
    			{name="fTexelSize",type=Shader.CFLOAT2,vertex=false},
    			{name="fColorTransform",type=Shader.CFLOAT4,vertex=false},
    			{name="fRad",type=Shader.CINT,vertex=false},
    		}, {
    			{name="vVertex",type=Shader.DFLOAT,mult=2,slot=0,offset=0},
    			{name="vColor",type=Shader.DUBYTE,mult=4,slot=1,offset=0},
    			{name="vTexCoord",type=Shader.DFLOAT,mult=2,slot=2,offset=0},
    		}, {
    			{name="fTexCoord",type=Shader.CFLOAT2},
    		}
    	)
    	return shader
    end
     
    local function blurVert(vVertex,vColor,vTexCoord)
    	local vertex = hF4(vVertex,0.0,1.0)
    	fTexCoord=vTexCoord
    	return vMatrix*vertex
    end
    local function blurFrag()
    	local frag=lF4(0,0,0,0)
    	local ext=2*fRad+1
    	local tc=fTexCoord-fTexelSize*fRad
    	for v=0,19 do
    		if v<ext then
    			frag=frag+texture2D(fTexture, tc)
    		end
    		tc+=fTexelSize
    	end
    	frag=frag/ext
    	if (frag.a==0.0) then 
    		discard() 
    	end
    	return frag * fColorTransform
    end
     
    local minX, minY, maxX, maxY = app:getLogicalBounds()
    local SW = maxX - minX
    local SH = maxY - minY
     
    local w,h = 400, 400
    local ax,ay=0.5,0.5
    local blurLevel = 2
     
    local tex = Texture.new("bg2.jpg", true)
    local bg = Bitmap.new(tex)
    bg:setPosition(minX, minY)
    bg:setScale(SW / tex:getWidth(), SH / tex:getHeight())
    stage:addChild(bg)
     
    local shape = Path2D.new()
    shape:setPath("MHVHVZ",{0,0, w, h, 0, 0})
    shape:setFillColor(0xffffff, 1)
    shape:setLineColor(0, 1)
    shape:setLineThickness(8, 0.1)
    shape:setAnchorPosition(w * ax, h * ay)
    stage:addChild(shape)
     
    shape:setPosition(minX + SW / 2, minY + SH / 2)
     
    local blur = makeEffect(blurVert, blurFrag)
    blur:setConstant("fRad",Shader.CINT,1,blurLevel)
    blur:setConstant("fTexelSize",Shader.CFLOAT2,1,{1/w,1/h})
    blur:setConstant("fColorTransform",Shader.CFLOAT4,1,{1,1,1,1})
     
    local textureA = RenderTarget.new(w,h,true)
    local textureB = RenderTarget.new(w,h,true)
     
    local bufferA = Bitmap.new(textureA)
    bufferA:setShader(blur)
     
    shape:setTexture(textureA)
    shape:setShader(blur)
     
    local function updateBlur()
    	local x, y = shape:getPosition()
    	textureA:draw(stage, -x + w * ax, -y + h * ay)
    	blur:setConstant("fTexelSize",Shader.CFLOAT2,1,{0,1/h})
    	textureB:draw(bufferA)
    	blur:setConstant("fTexelSize",Shader.CFLOAT2,1,{1/w,0})
    end
     
    updateBlur()
     
    stage:addEventListener("mouseMove", function(e)
    	shape:setPosition(e.x,e.y)
    	updateBlur()
    end)


    Old version (using glsl shaders):
    local blurVert = [[attribute highp vec3 vVertex;
    attribute mediump vec2 vTexCoord;
    uniform highp mat4 vMatrix;
    varying mediump vec2 fTexCoord;
     
    void main() {
    	vec4 vertex = vec4(vVertex,1.0);
    	gl_Position = vMatrix*vertex;
    	fTexCoord=vTexCoord;
    }
    ]]
     
    local blurFrag = [[#define STEPS 20
    uniform lowp vec4 fColor;
    uniform lowp sampler2D fTexture;
    uniform int fRad;
    uniform mediump vec2 fTexelSize;
    varying mediump vec2 fTexCoord;
    uniform lowp vec4 fColorTransform;
     
    void main() {
    	lowp vec4 frag=vec4(0,0,0,0);
    	int ext=2*fRad+1;	
    	mediump vec2 tc=fTexCoord-fTexelSize*float(fRad);
    	for (int v=0;v<STEPS;v++)	
    	{
    		if (v<ext)
    			frag=frag+texture2D(fTexture, tc);
    		tc+=fTexelSize;
    	}
    	frag=frag/float(ext);
    	if (frag.a==0.0) discard;
    	gl_FragColor = frag * fColorTransform;
    }
    ]]
     
    local minX, minY, maxX, maxY = app:getLogicalBounds()
    local SW = maxX - minX
    local SH = maxY - minY
     
    local w,h = 400, 400
    local ax,ay=0.5,0.5
    local blurLevel = 2
     
    local tex = Texture.new("bg2.jpg", true)
    local bg = Bitmap.new(tex)
    bg:setPosition(minX, minY)
    bg:setScale(SW / tex:getWidth(), SH / tex:getHeight())
    stage:addChild(bg)
     
    local shape = Path2D.new()
    shape:setPath("MHVHVZ",{0,0, w, h, 0, 0})
    shape:setFillColor(0xffffff, 1)
    shape:setLineColor(0, 1)
    shape:setLineThickness(8, 0.1)
    shape:setAnchorPosition(w * ax, h * ay)
    stage:addChild(shape)
     
    shape:setPosition(minX + SW / 2, minY + SH / 2)
     
    local blur = Shader.new(blurVert, blurFrag, 4, {
    		{name="vMatrix",type=Shader.CMATRIX,sys=Shader.SYS_WVP,vertex=true},
    		{name="fColor",type=Shader.CFLOAT4,sys=Shader.SYS_COLOR,vertex=false},
    		{name="fTexture",type=Shader.CTEXTURE,vertex=false},
    		{name="fTexelSize",type=Shader.CFLOAT2,vertex=false},
    		{name="fColorTransform",type=Shader.CFLOAT4,vertex=false},
    		{name="fRad",type=Shader.CINT,vertex=false},
    	},
    	{
    		{name="vVertex",type=Shader.DFLOAT,mult=3,slot=0,offset=0},
    		{name="vColor",type=Shader.DUBYTE,mult=4,slot=1,offset=0},
    		{name="vTexCoord",type=Shader.DFLOAT,mult=2,slot=2,offset=0},
    	}
    )
    blur:setConstant("fRad",Shader.CINT,1,blurLevel)
    blur:setConstant("fTexelSize",Shader.CFLOAT2,1,{1/w,1/h})
    blur:setConstant("fColorTransform",Shader.CFLOAT4,1,{1,1,1,1})
     
    local textureA = RenderTarget.new(w,h,true)
    local textureB = RenderTarget.new(w,h,true)
     
    local bufferA = Bitmap.new(textureA)
    bufferA:setShader(blur)
     
    shape:setTexture(textureA)
    shape:setShader(blur)
     
    local function updateBlur()
    	local x, y = shape:getPosition()
    	textureA:draw(stage, -x + w * ax, -y + h * ay)
    	blur:setConstant("fTexelSize",Shader.CFLOAT2,1,{0,1/h})
    	textureB:draw(bufferA)
    	blur:setConstant("fTexelSize",Shader.CFLOAT2,1,{1/w,0})
    end
     
    updateBlur()
     
    stage:addEventListener("mouseMove", function(e)
    	shape:setPosition(e.x,e.y)
    	updateBlur()
    end)
    blur1.jpg
    1380 x 507 - 123K
  • hgy29hgy29 Maintainer
    Accepted Answer
    The issue here is that Path2D uses several highly specific shaders to render the outline. Thoses shaders used to be "hidden" to setShader() which would apply only to the fill operation, but in 2021.1 I decided to make them overridable too.
    To do so, setShader() was extended to take three more parameters, see https://wiki.gideros.rocks/index.php/Sprite:setShader

    Extra parameters allows to select which kind of default shader to override, default being all. So if you don't specify a programType, your shader will be used for stroking too.

    Since you want the blur to be applied to the shape fill operation, select program type Shader.SHADER PROGRAM TEXTURE:
    shape:setShader(blur,Shader.SHADER PROGRAM TEXTURE)

    Likes: rrraptor

    +1 -1 (+1 / -0 )Share on Facebook
Sign In or Register to comment.