Hi, I’m looking for a good blur shader, something similar to the blur you get in the iOS Notification Center or OS X Yosemite. The blur shader included doesn’t really do what I want. Also, preferably something non GPU intensive, as I have a lot of other stuff going on in the background (Even if it is GPU intensive, id still love to see it and test it) (also, preferably something with an open licence, as this will be used in a game that will most likely be on the App Store in the next couple of months). The background will constantly be changing, as this will be a background for a store which is “on top” or the menu screen which has several effects in it.
Most of the examples I’ve seen online require 2 passes. It also seems that you’d have to render the underlying scene to an image, and then pass that image as a texture to the shader. I don’t know what the performance of that would be like if you were doing it every frame. This one looks interesting:
the fastest method may be to create an overlay image which can be sprited over the drawn screen, creating the impression of a blur. This is much faster than creating a blur in real time (and does the difference really matter to users?).
The code below demonstrates. Touch the screen to toggle the “blur” on and off. You can adjust the light drop off towards the edges with this line in the shader: f = fff; (the more you mutiply f by itself, the faster the light drops off, and vice versa)
function setup()
--create "blur" image using shader
blurImg=image(WIDTH,HEIGHT)
setContext(blurImg)
local m=mesh()
m:addRect(WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
m.shader=shader(BlurShader.vertexShader,BlurShader.fragmentShader)
m.shader.centre=vec2(WIDTH/2,HEIGHT/2)
m:draw()
setContext()
--image completed
showBlur=false --blur toggle
end
function draw()
background(50)
--draw something on the screen
sprite("SpaceCute:Rocketship",WIDTH-200,HEIGHT-200)
sprite("Small World:Store Extra Large",200,200,300)
sprite("SpaceCute:Beetle Ship",WIDTH/2,HEIGHT/2)
if showBlur then --draw blur if required
sprite(img,WIDTH/2,HEIGHT/2)
sprite("Cargo Bot:Codea Icon",WIDTH/2,HEIGHT/2)
end
end
--touching toggles blur on and off
function touched(t)
if t.state==ENDED then showBlur=not showBlur end
end
--blur shader
BlurShader = {
vertexShader = [[
uniform mat4 modelViewProjection;
attribute vec4 position;
attribute vec4 color;
varying highp vec4 vPosition;
void main()
{
gl_Position = modelViewProjection * position;
vPosition = position;
}
]],
fragmentShader = [[
precision highp float;
uniform vec2 centre;
varying highp vec4 vPosition;
float L = sqrt(centre.x*centre.x+centre.y*centre.y)*1.4;
void main()
{
float f=1.0 - distance(vPosition.xy,centre)/L;
f=f*f*f;
gl_FragColor = vec4(f,f,f,1.0-f);
}
]]}
Here’s a Gaussian blur shader. It looks great, but on an iPad Air, the FPS drops to 35 with two passes. I haven’t tried optimising it, but I’m sure the bottleneck is sending two screen-sized images as textures to the shader, rather than the calculations in the shader itself. i.e. I’m not sure that reducing the number of samples in the shader from 14 would necessarily help. Could be worth trying?
--# Main
--Gaussian blur
--adapted by Yojimbo2000 from http://xissburg.com/faster-gaussian-blur-in-glsl/
function setup()
blur = {} --images
blurred = {} --meshes
for i=1,2 do --2 passes, one for horizontal, one vertical
blur[i]=image(WIDTH,HEIGHT)
blurred[i]=mesh()
blurred[i].texture=blur[i]
blurred[i]:addRect(WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
blurred[i].shader=shader(Gaussian.vs[i], Gaussian.fs)
end
unblurred=mesh() --mesh w/o the blur shader
unblurred.texture=blur[1]
unblurred:addRect(WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
showBlur=false --blur toggle
pos={x1=200, y2=200, x3=800}
tween(2, pos, {x1=800, y2=800, x3=200}, {easing=tween.easing.cubicInOut, loop=tween.loop.pingpong})
movement = true
profiler.init()
end
function draw()
if movement then
setContext(blur[1])
background(50)
sprite("SpaceCute:Rocketship",pos.x1,HEIGHT-200)
sprite("Small World:Store Extra Large",pos.x3,200,300)
sprite("SpaceCute:Beetle Ship",WIDTH/2,pos.y2)
end
if showBlur then --draw blur if required
setContext(blur[2])
blurred[1]:draw() --pass one, offscreen
setContext()
blurred[2]:draw() --pass two
else
setContext()
unblurred:draw()
end
profiler.draw()
end
--touching toggles blur on and off
function touched(t)
if t.state==ENDED then showBlur=not showBlur end
end
profiler={}
function profiler.init(quiet)
profiler.del=0
profiler.c=0
profiler.fps=0
profiler.mem=0
if not quiet then
parameter.watch("profiler.fps")
parameter.watch("profiler.mem")
end
end
function profiler.draw()
profiler.del = profiler.del + DeltaTime
profiler.c = profiler.c + 1
if profiler.c==10 then
profiler.fps=profiler.c/profiler.del
profiler.del=0
profiler.c=0
profiler.mem=collectgarbage("count", 2)
end
end
--# Gaussian
Gaussian = {
vs = {[[
uniform mat4 modelViewProjection;
attribute vec4 position;
attribute vec2 texCoord;
varying vec2 vTexCoord;
varying vec2 v_blurTexCoords[14];
void main()
{
gl_Position = modelViewProjection * position;
vTexCoord = texCoord;
v_blurTexCoords[ 0] = vTexCoord + vec2(-0.028, 0.0);
v_blurTexCoords[ 1] = vTexCoord + vec2(-0.024, 0.0);
v_blurTexCoords[ 2] = vTexCoord + vec2(-0.020, 0.0);
v_blurTexCoords[ 3] = vTexCoord + vec2(-0.016, 0.0);
v_blurTexCoords[ 4] = vTexCoord + vec2(-0.012, 0.0);
v_blurTexCoords[ 5] = vTexCoord + vec2(-0.008, 0.0);
v_blurTexCoords[ 6] = vTexCoord + vec2(-0.004, 0.0);
v_blurTexCoords[ 7] = vTexCoord + vec2( 0.004, 0.0);
v_blurTexCoords[ 8] = vTexCoord + vec2( 0.008, 0.0);
v_blurTexCoords[ 9] = vTexCoord + vec2( 0.012, 0.0);
v_blurTexCoords[10] = vTexCoord + vec2( 0.016, 0.0);
v_blurTexCoords[11] = vTexCoord + vec2( 0.020, 0.0);
v_blurTexCoords[12] = vTexCoord + vec2( 0.024, 0.0);
v_blurTexCoords[13] = vTexCoord + vec2( 0.028, 0.0);
}]],
[[
uniform mat4 modelViewProjection;
attribute vec4 position;
attribute vec2 texCoord;
varying vec2 vTexCoord;
varying vec2 v_blurTexCoords[14];
void main()
{
gl_Position = modelViewProjection * position;
vTexCoord = texCoord;
v_blurTexCoords[ 0] = vTexCoord + vec2(0.0, -0.028);
v_blurTexCoords[ 1] = vTexCoord + vec2(0.0, -0.024);
v_blurTexCoords[ 2] = vTexCoord + vec2(0.0, -0.020);
v_blurTexCoords[ 3] = vTexCoord + vec2(0.0, -0.016);
v_blurTexCoords[ 4] = vTexCoord + vec2(0.0, -0.012);
v_blurTexCoords[ 5] = vTexCoord + vec2(0.0, -0.008);
v_blurTexCoords[ 6] = vTexCoord + vec2(0.0, -0.004);
v_blurTexCoords[ 7] = vTexCoord + vec2(0.0, 0.004);
v_blurTexCoords[ 8] = vTexCoord + vec2(0.0, 0.008);
v_blurTexCoords[ 9] = vTexCoord + vec2(0.0, 0.012);
v_blurTexCoords[10] = vTexCoord + vec2(0.0, 0.016);
v_blurTexCoords[11] = vTexCoord + vec2(0.0, 0.020);
v_blurTexCoords[12] = vTexCoord + vec2(0.0, 0.024);
v_blurTexCoords[13] = vTexCoord + vec2(0.0, 0.028);
}]]},
fs = [[precision mediump float;
uniform lowp sampler2D texture;
varying vec2 vTexCoord;
varying vec2 v_blurTexCoords[14];
void main()
{
gl_FragColor = vec4(0.0);
gl_FragColor += texture2D(texture, v_blurTexCoords[ 0])*0.0044299121055113265;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 1])*0.00895781211794;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 2])*0.0215963866053;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 3])*0.0443683338718;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 4])*0.0776744219933;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 5])*0.115876621105;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 6])*0.147308056121;
gl_FragColor += texture2D(texture, vTexCoord )*0.159576912161;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 7])*0.147308056121;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 8])*0.115876621105;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 9])*0.0776744219933;
gl_FragColor += texture2D(texture, v_blurTexCoords[10])*0.0443683338718;
gl_FragColor += texture2D(texture, v_blurTexCoords[11])*0.0215963866053;
gl_FragColor += texture2D(texture, v_blurTexCoords[12])*0.00895781211794;
gl_FragColor += texture2D(texture, v_blurTexCoords[13])*0.0044299121055113265;
}]]
}
Personally, I wouldn’t go to a lot of effort just to blur the background that nobody will notice anyway
Well, it’s what the OP asked for
I was wrong about optimisation: if you comment out half the calculations in the fragment shader, it runs at about 58 fps, and still looks acceptably blurry. I’ll have to recalculate what the brightness values should be. I can post an optimised version later
Plus, with all of these things, it’s often the things you discover along the way. I’m interested in this because I’m currently exploring the possibility of creating an underwater ripple effect for a platform game I’m working on, which would be the same principle of sending the entire screen to an effect shader.
One really fun thing I just discovered: if you comment out every other line in the fragment shader above, you get a really cool after-image trail as the sprites move. With further adaptions so that the blur only goes in one direction, it would be a great effect
Ok, here’s an optimised version. Tap to cycle through 3 states, no blur, sideways trails, 2-pass Gaussian. This runs at 59/60 fps on the Air:
--# Main
--Gaussian blur
--adapted by Yojimbo2000 from http://xissburg.com/faster-gaussian-blur-in-glsl/
function setup()
--2 pass Gaussian blur:
blur = {} --images
blurred = {} --meshes
for i=1,2 do --2 passes, one for horizontal, one vertical
blur[i]=image(WIDTH,HEIGHT)
blurred[i]=mesh()
blurred[i].texture=blur[i]
blurred[i]:addRect(WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
blurred[i].shader=shader(Gaussian.vs[i], Gaussian.fsO)
end
--mesh w/o the blur shader:
unblurred=mesh()
unblurred.texture=blur[1]
unblurred:addRect(WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
--mesh with sideways after images:
sideTrails=mesh()
sideTrails.texture=blur[1]
sideTrails.shader=shader(Gaussian.vs[1], Gaussian.fsTrails) --try Gaussian.fsTesselated for a different effect
sideTrails:addRect(WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
showBlur=1 --blur state
--some animations:
pos={x1=200, y2=200, x3=800}
tween(2, pos, {x1=800, y2=800, x3=200}, {easing=tween.easing.cubicInOut, loop=tween.loop.pingpong})
movement = true
profiler.init()
print ("tap screen to cycle through blur states")
end
function draw()
if movement then
setContext(blur[1])
background(50)
sprite("SpaceCute:Rocketship",pos.x1,HEIGHT-200)
sprite("Small World:Store Extra Large",pos.x3,200,300)
sprite("SpaceCute:Beetle Ship",WIDTH/2,pos.y2)
end
if showBlur==1 then --draw blur if required
setContext()
background(50)
unblurred:draw()
elseif showBlur==2 then
setContext()
-- background(50)
sideTrails:draw()
elseif showBlur==3 then
setContext(blur[2])
-- background(50) --hard to tell whether these background calls make a difference
blurred[1]:draw() --pass one, offscreen
setContext()
-- background(50)
blurred[2]:draw() --pass two
end
profiler.draw()
end
--touching toggles blur on and off
local blurStates = {"no blur", "1 pass, horizontal trails", "2 pass Gaussian blur, optimized"}
function touched(t)
if t.state==ENDED then
showBlur = showBlur + 1
if showBlur==4 then showBlur=1 end
output.clear()
print(blurStates[showBlur])
end
end
profiler={}
function profiler.init(quiet)
profiler.del=0
profiler.c=0
profiler.fps=0
profiler.mem=0
if not quiet then
parameter.watch("profiler.fps")
parameter.watch("profiler.mem")
end
end
function profiler.draw()
profiler.del = profiler.del + DeltaTime
profiler.c = profiler.c + 1
if profiler.c==10 then
profiler.fps=profiler.c/profiler.del
profiler.del=0
profiler.c=0
profiler.mem=collectgarbage("count", 2)
end
end
--# Gaussian
Gaussian = {
vs = { -- horizontal pass vertex shader
[[
uniform mat4 modelViewProjection;
attribute vec4 position;
attribute vec2 texCoord;
varying vec2 vTexCoord;
varying vec2 v_blurTexCoords[14];
void main()
{
gl_Position = modelViewProjection * position;
vTexCoord = texCoord;
v_blurTexCoords[ 0] = vTexCoord + vec2(-0.028, 0.0);
v_blurTexCoords[ 1] = vTexCoord + vec2(-0.024, 0.0);
v_blurTexCoords[ 2] = vTexCoord + vec2(-0.020, 0.0);
v_blurTexCoords[ 3] = vTexCoord + vec2(-0.016, 0.0);
v_blurTexCoords[ 4] = vTexCoord + vec2(-0.012, 0.0);
v_blurTexCoords[ 5] = vTexCoord + vec2(-0.008, 0.0);
v_blurTexCoords[ 6] = vTexCoord + vec2(-0.004, 0.0);
v_blurTexCoords[ 7] = vTexCoord + vec2( 0.004, 0.0);
v_blurTexCoords[ 8] = vTexCoord + vec2( 0.008, 0.0);
v_blurTexCoords[ 9] = vTexCoord + vec2( 0.012, 0.0);
v_blurTexCoords[10] = vTexCoord + vec2( 0.016, 0.0);
v_blurTexCoords[11] = vTexCoord + vec2( 0.020, 0.0);
v_blurTexCoords[12] = vTexCoord + vec2( 0.024, 0.0);
v_blurTexCoords[13] = vTexCoord + vec2( 0.028, 0.0);
}]],
-- vertical pass vertex shader
[[
uniform mat4 modelViewProjection;
attribute vec4 position;
attribute vec2 texCoord;
varying vec2 vTexCoord;
varying vec2 v_blurTexCoords[14];
void main()
{
gl_Position = modelViewProjection * position;
vTexCoord = texCoord;
v_blurTexCoords[ 0] = vTexCoord + vec2(0.0, -0.028);
v_blurTexCoords[ 1] = vTexCoord + vec2(0.0, -0.024);
v_blurTexCoords[ 2] = vTexCoord + vec2(0.0, -0.020);
v_blurTexCoords[ 3] = vTexCoord + vec2(0.0, -0.016);
v_blurTexCoords[ 4] = vTexCoord + vec2(0.0, -0.012);
v_blurTexCoords[ 5] = vTexCoord + vec2(0.0, -0.008);
v_blurTexCoords[ 6] = vTexCoord + vec2(0.0, -0.004);
v_blurTexCoords[ 7] = vTexCoord + vec2(0.0, 0.004);
v_blurTexCoords[ 8] = vTexCoord + vec2(0.0, 0.008);
v_blurTexCoords[ 9] = vTexCoord + vec2(0.0, 0.012);
v_blurTexCoords[10] = vTexCoord + vec2(0.0, 0.016);
v_blurTexCoords[11] = vTexCoord + vec2(0.0, 0.020);
v_blurTexCoords[12] = vTexCoord + vec2(0.0, 0.024);
v_blurTexCoords[13] = vTexCoord + vec2(0.0, 0.028);
}]]},
--optimised fragment shader
fsO = [[precision mediump float;
uniform lowp sampler2D texture;
varying vec2 vTexCoord;
varying vec2 v_blurTexCoords[14];
void main()
{
gl_FragColor = vec4(0.0);
gl_FragColor += texture2D(texture, v_blurTexCoords[ 5])* 0.08; //0.169179866813; //0.115876621105;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 6])* 0.26; //0.215069761937; //0.147308056121;
gl_FragColor += texture2D(texture, vTexCoord )* 0.32; //0.232982291755; //0.159576912161;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 7])* 0.26; //0.215069761937; //0.147308056121;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 8])* 0.08; //0.169179866813; //0.115876621105;
}]],
--fragment shader --awesome after-image trails!
fsTrails = [[precision mediump float;
uniform lowp sampler2D texture;
varying vec2 vTexCoord;
varying vec2 v_blurTexCoords[14];
void main()
{
gl_FragColor = vec4(0.0);
gl_FragColor += texture2D(texture, v_blurTexCoords[ 0])*0.0044299121055113265;
// gl_FragColor += texture2D(texture, v_blurTexCoords[ 1])*0.00895781211794;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 2])*0.0215963866053;
// gl_FragColor += texture2D(texture, v_blurTexCoords[ 3])*0.0443683338718;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 4])*0.0776744219933;
// gl_FragColor += texture2D(texture, v_blurTexCoords[ 5])*0.115876621105;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 6])*0.147308056121;
gl_FragColor += texture2D(texture, vTexCoord )*0.159576912161;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 7])*0.147308056121;
// gl_FragColor += texture2D(texture, v_blurTexCoords[ 8])*0.115876621105;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 9])*0.0776744219933;
// gl_FragColor += texture2D(texture, v_blurTexCoords[10])*0.0443683338718;
gl_FragColor += texture2D(texture, v_blurTexCoords[11])*0.0215963866053;
// gl_FragColor += texture2D(texture, v_blurTexCoords[12])*0.00895781211794;
gl_FragColor += texture2D(texture, v_blurTexCoords[13])*0.0044299121055113265;
}]],
--fragment shader. tesselated frosted glass effect (also try commenting out 3-10)
fsTesselated = [[precision mediump float;
uniform lowp sampler2D texture;
varying vec2 vTexCoord;
varying vec2 v_blurTexCoords[14];
void main()
{
gl_FragColor = vec4(0.0);
// gl_FragColor += texture2D(texture, v_blurTexCoords[ 0])*0.0044299121055113265;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 1])*0.00895781211794;
// gl_FragColor += texture2D(texture, v_blurTexCoords[ 2])*0.0215963866053;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 3])*0.0443683338718;
// gl_FragColor += texture2D(texture, v_blurTexCoords[ 4])*0.0776744219933;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 5])*0.115876621105;
// gl_FragColor += texture2D(texture, v_blurTexCoords[ 6])*0.147308056121;
gl_FragColor += texture2D(texture, vTexCoord )*0.159576912161;
// gl_FragColor += texture2D(texture, v_blurTexCoords[ 7])*0.147308056121;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 8])*0.115876621105;
// gl_FragColor += texture2D(texture, v_blurTexCoords[ 9])*0.0776744219933;
gl_FragColor += texture2D(texture, v_blurTexCoords[10])*0.0443683338718;
// gl_FragColor += texture2D(texture, v_blurTexCoords[11])*0.0215963866053;
gl_FragColor += texture2D(texture, v_blurTexCoords[12])*0.00895781211794;
// gl_FragColor += texture2D(texture, v_blurTexCoords[13])*0.0044299121055113265;
}]]
}
On the above code,the weightings aren’t right (I made them up). This blog uses the same technique as mine (ie 5 readings, fragment interpolation). I might see if I can copy their weightings. You could then emulate the iOS 7 panel effect, fullscreen, 60 fps.
http://www.sunsetlakesoftware.com/2013/10/21/optimizing-gaussian-blurs-mobile-gpu
You must have a fast iPad!
My iPad 3 runs your code with one pass at 40 FPS, two passes at 30 FPS.
@Ignatz (or anyone else with an iPad 3), try this one. It downsamples by half, meaning there are a quarter as many calculations in the fragment shader. Because OpenGL up scaling adds a blurring effect anyway (as long as you don’t invoke noSmooth()
), the visual difference between this and full-pixel calculation is unnoticeable, but it should be a lot faster:
--# Main
--Gaussian blur
--adapted by Yojimbo2000 from http://xissburg.com/faster-gaussian-blur-in-glsl/
function setup()
--2 pass Gaussian blur:
local downSample = 0.5
local dimensions = {
vec2(WIDTH, HEIGHT), --full size
vec2(WIDTH, HEIGHT) * downSample, --down sampled
vec2(WIDTH, HEIGHT) * 0.5, --centre of fullsize (for positioning rect)
vec2(WIDTH, HEIGHT) * downSample * 0.5, --centre of downsampled
}
blur = {} --images
blurred = {} --meshes
for i=1,2 do --2 passes, one for horizontal, one vertical
blur[i]=image(dimensions[i].x, dimensions[i].y) --image 1 is full sized, image 2 is downsampled
blurred[i]=mesh()
blurred[i].texture=blur[i]
local j=3-i --invert i so that...
blurred[i]:addRect(dimensions[j+2].x, dimensions[j+2].y, dimensions[j].x, dimensions[j].y) --mesh 1 rect is down-sampled, mesh 2 rect is full-sized
blurred[i].shader=shader(Gaussian.vs[i], Gaussian.fsO)
end
--mesh w/o the blur shader:
unblurred=mesh()
unblurred.texture=blur[1]
unblurred:addRect(WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
--mesh with sideways after images:
sideTrails=mesh()
sideTrails.texture=blur[1]
sideTrails.shader=shader(Gaussian.vs[1], Gaussian.fsTrails) --try Gaussian.fsTesselated for a different effect
sideTrails:addRect(WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
showBlur=1 --blur state
--some animations:
pos={x1=200, y2=200, x3=800}
tween(2, pos, {x1=800, y2=800, x3=200}, {easing=tween.easing.cubicInOut, loop=tween.loop.pingpong})
movement = true
profiler.init()
print ("tap screen to cycle through blur states")
end
function draw()
if movement then
setContext(blur[1])
background(50)
sprite("SpaceCute:Rocketship",pos.x1,HEIGHT-200)
sprite("Small World:Store Extra Large",pos.x3,200,300)
sprite("SpaceCute:Beetle Ship",WIDTH/2,pos.y2)
end
if showBlur==1 then --draw blur if required
setContext()
background(50)
unblurred:draw()
elseif showBlur==2 then
setContext()
-- background(50)
sideTrails:draw()
elseif showBlur==3 then
setContext(blur[2])
-- background(50) --hard to tell whether these background calls make a difference
blurred[1]:draw() --pass one, offscreen
setContext()
-- background(50)
blurred[2]:draw() --pass two
end
profiler.draw()
end
--touching toggles blur on and off
local blurStates = {"no blur", "1 pass, horizontal trails", "2 pass Gaussian blur, optimized"}
function touched(t)
if t.state==ENDED then
showBlur = showBlur + 1
if showBlur==4 then showBlur=1 end
output.clear()
print(blurStates[showBlur])
end
end
profiler={}
function profiler.init(quiet)
profiler.del=0
profiler.c=0
profiler.fps=0
profiler.mem=0
if not quiet then
parameter.watch("profiler.fps")
parameter.watch("profiler.mem")
end
end
function profiler.draw()
profiler.del = profiler.del + DeltaTime
profiler.c = profiler.c + 1
if profiler.c==10 then
profiler.fps=profiler.c/profiler.del
profiler.del=0
profiler.c=0
profiler.mem=collectgarbage("count", 2)
end
end
--# Gaussian
Gaussian = {
vs = { -- horizontal pass vertex shader
[[
uniform mat4 modelViewProjection;
attribute vec4 position;
attribute vec2 texCoord;
varying vec2 vTexCoord;
varying vec2 v_blurTexCoords[14];
void main()
{
gl_Position = modelViewProjection * position;
vTexCoord = texCoord;
v_blurTexCoords[ 0] = vTexCoord + vec2(-0.028, 0.0);
v_blurTexCoords[ 1] = vTexCoord + vec2(-0.024, 0.0);
v_blurTexCoords[ 2] = vTexCoord + vec2(-0.020, 0.0);
v_blurTexCoords[ 3] = vTexCoord + vec2(-0.016, 0.0);
v_blurTexCoords[ 4] = vTexCoord + vec2(-0.012, 0.0);
v_blurTexCoords[ 5] = vTexCoord + vec2(-0.008, 0.0);
v_blurTexCoords[ 6] = vTexCoord + vec2(-0.004, 0.0);
v_blurTexCoords[ 7] = vTexCoord + vec2( 0.004, 0.0);
v_blurTexCoords[ 8] = vTexCoord + vec2( 0.008, 0.0);
v_blurTexCoords[ 9] = vTexCoord + vec2( 0.012, 0.0);
v_blurTexCoords[10] = vTexCoord + vec2( 0.016, 0.0);
v_blurTexCoords[11] = vTexCoord + vec2( 0.020, 0.0);
v_blurTexCoords[12] = vTexCoord + vec2( 0.024, 0.0);
v_blurTexCoords[13] = vTexCoord + vec2( 0.028, 0.0);
}]],
-- vertical pass vertex shader
[[
uniform mat4 modelViewProjection;
attribute vec4 position;
attribute vec2 texCoord;
varying vec2 vTexCoord;
varying vec2 v_blurTexCoords[14];
void main()
{
gl_Position = modelViewProjection * position;
vTexCoord = texCoord;
v_blurTexCoords[ 0] = vTexCoord + vec2(0.0, -0.028);
v_blurTexCoords[ 1] = vTexCoord + vec2(0.0, -0.024);
v_blurTexCoords[ 2] = vTexCoord + vec2(0.0, -0.020);
v_blurTexCoords[ 3] = vTexCoord + vec2(0.0, -0.016);
v_blurTexCoords[ 4] = vTexCoord + vec2(0.0, -0.012);
v_blurTexCoords[ 5] = vTexCoord + vec2(0.0, -0.008);
v_blurTexCoords[ 6] = vTexCoord + vec2(0.0, -0.004);
v_blurTexCoords[ 7] = vTexCoord + vec2(0.0, 0.004);
v_blurTexCoords[ 8] = vTexCoord + vec2(0.0, 0.008);
v_blurTexCoords[ 9] = vTexCoord + vec2(0.0, 0.012);
v_blurTexCoords[10] = vTexCoord + vec2(0.0, 0.016);
v_blurTexCoords[11] = vTexCoord + vec2(0.0, 0.020);
v_blurTexCoords[12] = vTexCoord + vec2(0.0, 0.024);
v_blurTexCoords[13] = vTexCoord + vec2(0.0, 0.028);
}]]},
--optimised fragment shader
fsO = [[precision mediump float;
uniform lowp sampler2D texture;
varying vec2 vTexCoord;
varying vec2 v_blurTexCoords[14];
void main()
{
gl_FragColor = vec4(0.0);
gl_FragColor += texture2D(texture, v_blurTexCoords[ 5])* 0.08; //0.169179866813; //0.115876621105;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 6])* 0.26; //0.215069761937; //0.147308056121;
gl_FragColor += texture2D(texture, vTexCoord )* 0.32; //0.232982291755; //0.159576912161;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 7])* 0.26; //0.215069761937; //0.147308056121;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 8])* 0.08; //0.169179866813; //0.115876621105;
}]],
--fragment shader --awesome after-image trails!
fsTrails = [[precision mediump float;
uniform lowp sampler2D texture;
varying vec2 vTexCoord;
varying vec2 v_blurTexCoords[14];
void main()
{
gl_FragColor = vec4(0.0);
gl_FragColor += texture2D(texture, v_blurTexCoords[ 0])*0.0044299121055113265;
// gl_FragColor += texture2D(texture, v_blurTexCoords[ 1])*0.00895781211794;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 2])*0.0215963866053;
// gl_FragColor += texture2D(texture, v_blurTexCoords[ 3])*0.0443683338718;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 4])*0.0776744219933;
// gl_FragColor += texture2D(texture, v_blurTexCoords[ 5])*0.115876621105;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 6])*0.147308056121;
gl_FragColor += texture2D(texture, vTexCoord )*0.159576912161;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 7])*0.147308056121;
// gl_FragColor += texture2D(texture, v_blurTexCoords[ 8])*0.115876621105;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 9])*0.0776744219933;
// gl_FragColor += texture2D(texture, v_blurTexCoords[10])*0.0443683338718;
gl_FragColor += texture2D(texture, v_blurTexCoords[11])*0.0215963866053;
// gl_FragColor += texture2D(texture, v_blurTexCoords[12])*0.00895781211794;
gl_FragColor += texture2D(texture, v_blurTexCoords[13])*0.0044299121055113265;
}]],
--fragment shader. tesselated frosted glass effect (also try commenting out 3-10)
fsTesselated = [[precision mediump float;
uniform lowp sampler2D texture;
varying vec2 vTexCoord;
varying vec2 v_blurTexCoords[14];
void main()
{
gl_FragColor = vec4(0.0);
// gl_FragColor += texture2D(texture, v_blurTexCoords[ 0])*0.0044299121055113265;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 1])*0.00895781211794;
// gl_FragColor += texture2D(texture, v_blurTexCoords[ 2])*0.0215963866053;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 3])*0.0443683338718;
// gl_FragColor += texture2D(texture, v_blurTexCoords[ 4])*0.0776744219933;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 5])*0.115876621105;
// gl_FragColor += texture2D(texture, v_blurTexCoords[ 6])*0.147308056121;
gl_FragColor += texture2D(texture, vTexCoord )*0.159576912161;
// gl_FragColor += texture2D(texture, v_blurTexCoords[ 7])*0.147308056121;
gl_FragColor += texture2D(texture, v_blurTexCoords[ 8])*0.115876621105;
// gl_FragColor += texture2D(texture, v_blurTexCoords[ 9])*0.0776744219933;
gl_FragColor += texture2D(texture, v_blurTexCoords[10])*0.0443683338718;
// gl_FragColor += texture2D(texture, v_blurTexCoords[11])*0.0215963866053;
gl_FragColor += texture2D(texture, v_blurTexCoords[12])*0.00895781211794;
// gl_FragColor += texture2D(texture, v_blurTexCoords[13])*0.0044299121055113265;
}]]
}
Ok, last one I promise. This one lets you set varying amounts of horizontal and vertical blur, and has better weighting of the pixels. I’m not sure, but it could be that the downsampling only quarters the number of calls to the fragment shader on the first pass, but it depends on how OpenGL handles upscaling (ie does it call the fragment shader for the number of pixels in the source, or in the destination, when upscaling?). So it’s possible that we’re getting five-eighths the number of calculations, rather than a quarter. I can’t really check this because it all runs at 60fps for me.
--# Main
--Gaussian blur
--adapted by Yojimbo2000 from http://xissburg.com/faster-gaussian-blur-in-glsl/ and http://www.sunsetlakesoftware.com/2013/10/21/optimizing-gaussian-blurs-mobile-gpu
function setup()
--2 pass Gaussian blur:
local downSample = 0.5 -- going down to 0.25 actually looks pretty good!
local dimensions = {
vec2(WIDTH, HEIGHT), --full size
vec2(WIDTH, HEIGHT) * downSample --down sampled
}
local blurRadii = {
vec2(0.002,0), --horizontal pass, increase x value for more horizontal blur
vec2(0,0.002) --vertical pass, increase y value for more vertical blur
}
blur = {} --images
blurred = {} --meshes
for i=1,2 do --2 passes, one for horizontal, one vertical
blur[i]=image(dimensions[i].x, dimensions[i].y) --image 1 is full sized, image 2 is downsampled
blurred[i]=mesh()
blurred[i].texture=blur[i]
local j=3-i --invert i so that...
blurred[i]:addRect(dimensions[j].x/2, dimensions[j].y/2, dimensions[j].x, dimensions[j].y) --mesh 1 rect is down-sampled, mesh 2 rect is full-sized
blurred[i].shader=shader(Gaussian.vs, Gaussian.fs)
blurred[i].shader.blurRadius=blurRadii[i]
end
--mesh w/o the blur shader:
unblurred=mesh()
unblurred.texture=blur[1]
unblurred:addRect(WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
showBlur=false --blur state
--some animations:
pos={x1=200, y2=200, x3=800}
tween(2, pos, {x1=800, y2=800, x3=200}, {easing=tween.easing.cubicInOut, loop=tween.loop.pingpong})
movement = true
profiler.init()
print ("tap screen to toggle blur")
end
function draw()
if movement then
setContext(blur[1])
background(50)
sprite("SpaceCute:Rocketship",pos.x1,HEIGHT-200)
sprite("Small World:Store Extra Large",pos.x3,200,300)
sprite("SpaceCute:Beetle Ship",WIDTH/2,pos.y2)
end
if showBlur then --draw blur if required
setContext(blur[2])
-- background(50) --nice after-image effect if you dont clear the intermediary layer
blurred[1]:draw() --pass one, offscreen
setContext()
blurred[2]:draw() --pass two, onscreen
else
setContext()
-- background(50) --doesn't seem to be necesary to clear screen, for some reason
unblurred:draw()
end
profiler.draw()
end
--touching toggles blur on and off
function touched(t)
if t.state==ENDED then showBlur = not showBlur end
end
profiler={}
function profiler.init(quiet)
profiler.del=0
profiler.c=0
profiler.fps=0
profiler.mem=0
if not quiet then
parameter.watch("profiler.fps")
parameter.watch("profiler.mem")
end
end
function profiler.draw()
profiler.del = profiler.del + DeltaTime
profiler.c = profiler.c + 1
if profiler.c==10 then
profiler.fps=profiler.c/profiler.del
profiler.del=0
profiler.c=0
profiler.mem=collectgarbage("count", 2)
end
end
--# Gaussian
Gaussian = {vs = [[
uniform mat4 modelViewProjection;
uniform vec2 blurRadius;
attribute vec4 position;
attribute vec2 texCoord;
varying vec2 vBlurCoords[5];
void main()
{
gl_Position = modelViewProjection * position;
vBlurCoords[0] = texCoord;
vBlurCoords[1] = texCoord + blurRadius * 1.407333;
vBlurCoords[2] = texCoord - blurRadius * 1.407333;
vBlurCoords[3] = texCoord + blurRadius * 3.294215;
vBlurCoords[4] = texCoord - blurRadius * 3.294215;
}
]],
--optimised fragment shader
fs = [[
precision mediump float;
uniform lowp sampler2D texture;
varying vec2 vBlurCoords[5];
void main()
{
// gl_FragColor = vec4(0.0);
gl_FragColor = texture2D(texture, vBlurCoords[ 0]) * 0.304005;
gl_FragColor += texture2D(texture, vBlurCoords[ 1])* 0.204164;
gl_FragColor += texture2D(texture, vBlurCoords[ 2])* 0.204164;
gl_FragColor += texture2D(texture, vBlurCoords[ 3])* 0.093913;
gl_FragColor += texture2D(texture, vBlurCoords[ 4])* 0.093913;
}]]}
Ok, I lied in my previous post, this really is the last one! I added a tween to the x and y radii of the blur to create a fun, “drunken insect eye” effect. Have a play, and let me know your fps (Added it to Codea Community) :
--# Main
--Gaussian blur
--adapted by Yojimbo2000 from http://xissburg.com/faster-gaussian-blur-in-glsl/ and http://www.sunsetlakesoftware.com/2013/10/21/optimizing-gaussian-blurs-mobile-gpu
function setup()
--2 pass Gaussian blur:
local downSample = 0.5 -- going down to 0.25 actually looks pretty good!
local dimensions = {
vec2(WIDTH, HEIGHT), --full size
vec2(WIDTH, HEIGHT) * downSample --down sampled
}
blurRadii = {
vec2(0.002,0), --horizontal pass, increase x value for more horizontal blur
vec2(0,0.002) --vertical pass, increase y value for more vertical blur
}
blur = {} --images
blurred = {} --meshes
for i=1,2 do --2 passes, one for horizontal, one vertical
blur[i]=image(dimensions[i].x, dimensions[i].y) --image 1 is full sized, image 2 is downsampled
blurred[i]=mesh()
blurred[i].texture=blur[i]
local j=3-i --invert i so that...
blurred[i]:addRect(dimensions[j].x/2, dimensions[j].y/2, dimensions[j].x, dimensions[j].y) --mesh 1 rect is down-sampled, mesh 2 rect is full-sized (ie, opposite of their images)
blurred[i].shader=shader(Gaussian.vs, Gaussian.fs)
blurred[i].shader.blurRadius=blurRadii[i]
end
--mesh w/o the blur shader:
unblurred=mesh()
unblurred.texture=blur[1]
unblurred:addRect(WIDTH/2,HEIGHT/2,WIDTH,HEIGHT)
showBlur=1 --blur state
--some animations:
pos={x1=200, y2=200, x3=800}
radii={r1 = vec2(0.03,0.01), r2=vec2(-0.01,-0.03)}
tween(2, pos, {x1=800, y2=800, x3=200}, {easing=tween.easing.cubicInOut, loop=tween.loop.pingpong})
tween(3, radii, {r1 = vec2(-0.03,-0.01), r2=vec2(0.01,0.03)}, {easing=tween.easing.sineInOut, loop=tween.loop.pingpong})
movement = true
profiler.init()
print ("tap screen to cycle blur effects")
end
function draw()
if movement then
setContext(blur[1])
background(50)
sprite("SpaceCute:Rocketship",pos.x1,HEIGHT-200)
sprite("Small World:Store Extra Large",pos.x3,200,300)
sprite("SpaceCute:Beetle Ship",WIDTH/2,pos.y2)
end
if showBlur<3 then --draw blur if required
if showBlur==2 then
blurred[1].shader.blurRadius = radii.r1
blurred[2].shader.blurRadius = radii.r2
else
blurred[1].shader.blurRadius = blurRadii[1]
blurred[2].shader.blurRadius = blurRadii[2]
end
setContext(blur[2])
-- background(50) --nice after-image effect if you dont clear the intermediary layer
blurred[1]:draw() --pass one, offscreen
setContext()
blurred[2]:draw() --pass two, onscreen
elseif showBlur==3 then
setContext()
-- background(50) --doesn't seem to be necesary to clear screen, for some reason
unblurred:draw()
end
profiler.draw()
end
--touching toggles blur on and off
function touched(t)
if t.state==ENDED then
showBlur = showBlur + 1
if showBlur==4 then showBlur=1 end
end
end
profiler={}
function profiler.init(quiet)
profiler.del=0
profiler.c=0
profiler.fps=0
profiler.mem=0
if not quiet then
parameter.watch("profiler.fps")
parameter.watch("profiler.mem")
end
end
function profiler.draw()
profiler.del = profiler.del + DeltaTime
profiler.c = profiler.c + 1
if profiler.c==10 then
profiler.fps=profiler.c/profiler.del
profiler.del=0
profiler.c=0
profiler.mem=collectgarbage("count", 2)
end
end
--# Gaussian
Gaussian = {vs = [[
uniform mat4 modelViewProjection;
uniform vec2 blurRadius;
attribute vec4 position;
attribute vec2 texCoord;
varying vec2 vBlurCoords[5];
void main()
{
gl_Position = modelViewProjection * position;
vBlurCoords[0] = texCoord;
vBlurCoords[1] = texCoord + blurRadius * 1.407333;
vBlurCoords[2] = texCoord - blurRadius * 1.407333;
vBlurCoords[3] = texCoord + blurRadius * 3.294215;
vBlurCoords[4] = texCoord - blurRadius * 3.294215;
}
]],
--optimised fragment shader
fs = [[
precision mediump float;
uniform lowp sampler2D texture;
varying vec2 vBlurCoords[5];
void main()
{
// gl_FragColor = vec4(0.0);
gl_FragColor = texture2D(texture, vBlurCoords[ 0]) * 0.304005;
gl_FragColor += texture2D(texture, vBlurCoords[ 1])* 0.204164;
gl_FragColor += texture2D(texture, vBlurCoords[ 2])* 0.204164;
gl_FragColor += texture2D(texture, vBlurCoords[ 3])* 0.093913;
gl_FragColor += texture2D(texture, vBlurCoords[ 4])* 0.093913;
}]]}
I think that what Notification Center does (it wasn’t made in Codea, but if it was) is draw the screen into a small image, then scale that up and display the blurred result. I know it might create some odd effects when things are moving on the blurred screen, but the same happens in the real Notification Center. Try scrolling up this post, and while it’s moving, drag down Notification Center.
Yes, I think so. That’s what my code does. Change the downsamples variable to affect how small the intermediary image is.
Wow, thanks for all this! I just came to check, and saw all the code! Not sure which method will work best for me, but I’ll definantly look into all of them. Again, thanks a ton!
With my ones, I think you can just look at the last one.
Ok, will do
@Ignatz cool, glad to hear that. I put the downsampling code (from my last entry) into my first entry at the top of this thread, the one that samples the texture 15 times per fragment, to see if I could get that up to speed on the iPad Air. At full-pixel it runs at around 35 fps, downsampling by 0.5, at 50 fps. Interestingly though, if you go down to 0.25, it goes at about 40 fps or so. So presumably there’s a point at which too big a downscale/ upscale operation cancels out the benefit of quartering the number of calls to the fragment shader. In this particular case, downsampling by 0.5 seems to be optimal. You could also investigate scaling all of the draw operations too (of the 3 sprites I mean), see if that’s quicker than drawing a 0.5 image of the whole scene.