Radial (zoom) blur shader

So, here I presented a problem with my motion blur attempt. It was partially resolved (thanks), but the result still looks awful, not to mention that it ignores depth changes altogether. Then I thought a radial blur might work for my case quite well, so here’s what I came up with:

from direct.filter.FilterManager import FilterManager
from pandac.PandaModules import loadPrcFileData

loadPrcFileData("", "want-directtools #t")
loadPrcFileData("", "textures-auto-power-2 #t")
loadPrcFileData("", "win-size 1200 900")

from direct.showbase.ShowBase import ShowBase
from panda3d.core import *

zoom_blur_pass = """\
//Cg
//
//Cg profile arbvp1 arbfp1

void vshader(
    in float4 vtx_position : POSITION,
    out float4 l_position : POSITION,
    out float2 l_texcoord0 : TEXCOORD0,
    out float2 l_center,
    uniform float4x4 mat_modelproj,
    uniform float4 texpad_frame,
    uniform float2 k_center
    )
{
    l_position = mul(mat_modelproj, vtx_position);
    l_texcoord0 = vtx_position.xz * texpad_frame.xy + texpad_frame.xy;
    l_center = texpad_frame.xy * k_center * 2;
}

void fshader(
    in float2 l_texcoord0 : TEXCOORD0,
    in float2 l_center,
    out float4 o_color : COLOR,
    uniform sampler2D k_frame,
    uniform float k_shift
    )
{
    float2 vec = (l_center - l_texcoord0) * k_shift;
    o_color = tex2D(k_frame, l_texcoord0 - vec) * 0.25 +
              tex2D(k_frame, l_texcoord0      ) * 0.5  +
              tex2D(k_frame, l_texcoord0 + vec) * 0.25;
}
"""

zoom_blur_merge = """\
//Cg
//
//Cg profile arbvp1 arbfp1

// Merges the frame (k_frame) with the blurred sample (k_blur) based on the distance from center

const float INV_AREA_SIZE = 2.;
const float WEIGHT_EXP = 3.;

void vshader(
    in float4 vtx_position : POSITION,
    out float4 l_position : POSITION,
    out float4 l_texcoord0 : TEXCOORD0,
    out float2 l_center,
    uniform float4x4 mat_modelproj,
    uniform float4 texpad_frame,
    uniform float2 k_center
    )
{
    l_position = mul(mat_modelproj, vtx_position);
    l_texcoord0.xy = vtx_position.xz * texpad_frame.xy + texpad_frame.xy;
    l_texcoord0.wz = vtx_position.xz * texpad_frame.zy * INV_AREA_SIZE + texpad_frame.xy;
    // .wz used for correcting the weighting pattern shape
    l_center = texpad_frame.xy * k_center * 2;
}

void fshader(
    in float4 l_texcoord0 : TEXCOORD0,
    in float2 l_center,
    out float4 o_color : COLOR,
    uniform sampler2D k_frame,
    uniform sampler2D k_blur
    )
{
    float dist = length(l_center - l_texcoord0.wz);
    float weight = smoothstep(0., 1., pow(dist, WEIGHT_EXP)); // It's so smooth I'm slidin' outta this chair!
    o_color = lerp(tex2D(k_frame, l_texcoord0.xy), tex2D(k_blur, l_texcoord0.xy), weight);
    //o_color = float4(weight, weight, weight, 1); // Shows the blur weighting pattern
}
"""

class BlurTest(ShowBase):
    def __init__(self):
        ShowBase.__init__(self)

        environ = base.loader.loadModel("models/environment")
        environ.reparentTo(base.render)
        environ.setScale(0.25, 0.25, 0.25)
        environ.setPos(-8, 42, -1)

        def _makeTexture():
            tex = Texture()
            tex.setWrapU(Texture.WMClamp)
            tex.setWrapV(Texture.WMClamp)
            return tex


        scale = 2     # Amount the blur texture is downscaled for some extra... blur.
        passes = 5    # Increases the quality ... exponentially! Yay!
        # For more fine-tuning change the constants in zoom_blur_merge

        # variable center.. hard coded for now
        center = Vec2(0.5, 0.5)
        # Blur length control... camera velocity can be used for this.
        shift = 0.05


        fm = FilterManager(base.win, base.cam)
        source = _makeTexture()
        f_quad = fm.renderSceneInto(colortex = source)

        prevtex = source
        for i in xrange(passes):
            blurtex = _makeTexture()
            b_quad = fm.renderQuadInto(colortex = blurtex, div = scale, align = scale)
            b_quad.setShaderInput('frame', prevtex)
            b_quad.setShaderInput('shift', shift)
            b_quad.setShaderInput('center', center)
            b_quad.setShader(Shader.make(zoom_blur_pass))
            prevtex = blurtex
            shift /= 2.

        f_quad.setShaderInput('frame', source)
        f_quad.setShaderInput('blur', prevtex)
        f_quad.setShaderInput('center', center)
        f_quad.setShader(Shader.make(zoom_blur_merge))

app = BlurTest()
app.run()

It works quite well… for a hack. You’d just have to wire it up with camera movement (center being the projected direction and shift it’s velocity). The only little problem is that the borders flicker a bit.

Screenshot:

Still, a working REAL motion blur would be welcome…

your shader is very good !!
I think you should decrease strength of blur in edge and add more in the center of screen !! It will more beautiful :smiley: ! And of course, It’s only my idea.

Thanks, but this works better for my purposes. You can do just that by decreasing the WEIGHT_EXP value in the zoom_blur_merge shader, though.