Motion blur shader

I made a shader that produces a motion blur effect. It reminds me of handheld consoles’ LCD screens with their slow response times (specifically, GBA):

<?xml version="1.0" encoding="UTF-8"?>
<!--
    Motion Blur
    Author: hunterk

    This program is free software; you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by the Free
    Software Foundation; either version 2 of the License, or (at your option)
    any later version.
    -->
<shader language="GLSL" style="GLES2">
   <vertex><![CDATA[
      #version 120
      uniform mat4 rubyMVPMatrix;
      attribute vec2 rubyVertexCoord;
      attribute vec2 rubyTexCoord;
      varying vec2 tex_coord;

      void main()
      {
         gl_Position = rubyMVPMatrix * vec4(rubyVertexCoord, 0.0, 1.0);
         tex_coord = rubyTexCoord;
      }
   ]]></vertex>
<fragment><![CDATA[
uniform sampler2D rubyTexture;
uniform sampler2D rubyPrev6Texture;
varying vec2 tex_coord;

void main()
{
vec4 current = texture2D(rubyTexture, tex_coord);
vec4 prev = texture2D(rubyPrev6Texture, tex_coord);

gl_FragColor = (current + prev) / 2.0;
}
]]></fragment>
   <vertex><![CDATA[
      #version 120
      uniform mat4 rubyMVPMatrix;
      attribute vec2 rubyVertexCoord;
      attribute vec2 rubyTexCoord;
      varying vec2 tex_coord;

      void main()
      {
         gl_Position = rubyMVPMatrix * vec4(rubyVertexCoord, 0.0, 1.0);
         tex_coord = rubyTexCoord;
      }
   ]]></vertex>
<fragment><![CDATA[
uniform sampler2D rubyTexture;
uniform sampler2D rubyPrev5Texture;
varying vec2 tex_coord;

void main()
{
vec4 current = texture2D(rubyTexture, tex_coord);
vec4 prev = texture2D(rubyPrev5Texture, tex_coord);

gl_FragColor = (current + prev) / 2.0;
}
]]></fragment> 
  <vertex><![CDATA[
      #version 120
      uniform mat4 rubyMVPMatrix;
      attribute vec2 rubyVertexCoord;
      attribute vec2 rubyTexCoord;
      varying vec2 tex_coord;

      void main()
      {
         gl_Position = rubyMVPMatrix * vec4(rubyVertexCoord, 0.0, 1.0);
         tex_coord = rubyTexCoord;
      }
   ]]></vertex>
<fragment><![CDATA[
uniform sampler2D rubyTexture;
uniform sampler2D rubyPrev4Texture;
varying vec2 tex_coord;

void main()
{
vec4 current = texture2D(rubyTexture, tex_coord);
vec4 prev = texture2D(rubyPrev4Texture, tex_coord);

gl_FragColor = (current + prev) / 2.0;
}
]]></fragment>
   <vertex><![CDATA[
      #version 120
      uniform mat4 rubyMVPMatrix;
      attribute vec2 rubyVertexCoord;
      attribute vec2 rubyTexCoord;
      varying vec2 tex_coord;

      void main()
      {
         gl_Position = rubyMVPMatrix * vec4(rubyVertexCoord, 0.0, 1.0);
         tex_coord = rubyTexCoord;
      }
   ]]></vertex>
<fragment><![CDATA[
uniform sampler2D rubyTexture;
uniform sampler2D rubyPrev3Texture;
varying vec2 tex_coord;

void main()
{
vec4 current = texture2D(rubyTexture, tex_coord);
vec4 prev = texture2D(rubyPrev3Texture, tex_coord);

gl_FragColor = (current + prev) / 2.0;
}
]]></fragment>
   <vertex><![CDATA[
      #version 120
      uniform mat4 rubyMVPMatrix;
      attribute vec2 rubyVertexCoord;
      attribute vec2 rubyTexCoord;
      varying vec2 tex_coord;

      void main()
      {
         gl_Position = rubyMVPMatrix * vec4(rubyVertexCoord, 0.0, 1.0);
         tex_coord = rubyTexCoord;
      }
   ]]></vertex>
<fragment><![CDATA[
uniform sampler2D rubyTexture;
uniform sampler2D rubyPrev2Texture;
varying vec2 tex_coord;

void main()
{
vec4 current = texture2D(rubyTexture, tex_coord);
vec4 prev = texture2D(rubyPrev2Texture, tex_coord);

gl_FragColor = (current + prev) / 2.0;
}
]]></fragment>
   <vertex><![CDATA[
      #version 120
      uniform mat4 rubyMVPMatrix;
      attribute vec2 rubyVertexCoord;
      attribute vec2 rubyTexCoord;
      varying vec2 tex_coord;

      void main()
      {
         gl_Position = rubyMVPMatrix * vec4(rubyVertexCoord, 0.0, 1.0);
         tex_coord = rubyTexCoord;
      }
   ]]></vertex>
<fragment><![CDATA[
uniform sampler2D rubyTexture;
uniform sampler2D rubyPrev1Texture;
varying vec2 tex_coord;

void main()
{
vec4 current = texture2D(rubyTexture, tex_coord);
vec4 prev = texture2D(rubyPrev1Texture, tex_coord);

gl_FragColor = (current + prev) / 2.0;
}
]]></fragment>
   <vertex><![CDATA[
      #version 120
      uniform mat4 rubyMVPMatrix;
      attribute vec2 rubyVertexCoord;
      attribute vec2 rubyTexCoord;
      varying vec2 tex_coord;

      void main()
      {
         gl_Position = rubyMVPMatrix * vec4(rubyVertexCoord, 0.0, 1.0);
         tex_coord = rubyTexCoord;
      }
   ]]></vertex>
<fragment><![CDATA[
uniform sampler2D rubyTexture;
uniform sampler2D rubyPrevTexture;
varying vec2 tex_coord;

void main()
{
vec4 current = texture2D(rubyTexture, tex_coord);
vec4 prev = texture2D(rubyPrevTexture, tex_coord);

gl_FragColor = (current + prev) / 2.0;
}
]]></fragment>
</shader>

Are you using shader stacking or something?

I’d really prefer if shaders could be written in Cg first - since conversion to GLSL is already possible through a script.

PS3 and 360 are reliant on Cg / HLSL - it would be nice if we could keep building up common-shaders with new shaders as we go on instead of having to hand-convert each and every one of them back to Cg.

Ooh, looks neat. I’d love to somehow combine this with the variant of cgwg’s shader I modified to have thin scanlines, which I use for Gameboy and GBA games since it kinda sorta creates a dot matrix effect.

This motion blur is faster for me if you do it all in one pass; see here (this is also fixed so that the current frame has a weight of 1/2, rather than 1/128).

Not all conversions are currently possible with cg2xml though. Right now, only the simplest shaders can be converted :frowning:

Yes, I would also like all of these in Cg form.

@cgwg Oh yeah, that makes sense >.<

@GPDP Here’s a fixed-function version that should work in conjunction with your shader. Put it as the first shader at 1.0 x- and y- scales and your shader as the second.

@Squarepusher I’ll have to learn Cg for that, unfortunately. My GLSL skills are rudimentary, at best, as it is.

i did one some months ago.

I made a fun little edit to the XML motion blur shader to only kick in during rewind and add a sepia-tone effect at the same time, similar to what Braid does when you rewind. Here’s the code:

<?xml version="1.0" encoding="UTF-8"?>
<!--
    Braid Rewind
    Authors: hunterk, cgwg

    This program is free software; you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by the Free
    Software Foundation; either version 2 of the License, or (at your option)
    any later version.
    -->
<shader language="GLSL">
   <vertex><![CDATA[

      void main()
      {
         gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex;
         gl_TexCoord[0].xy = gl_MultiTexCoord0.xy;
      }
   ]]></vertex>
<fragment><![CDATA[
uniform sampler2D rubyTexture;
uniform sampler2D rubyPrev6Texture;
uniform sampler2D rubyPrev5Texture;
uniform sampler2D rubyPrev4Texture;
uniform sampler2D rubyPrev3Texture;
uniform sampler2D rubyPrev2Texture;
uniform sampler2D rubyPrev1Texture;
uniform sampler2D rubyPrevTexture;
uniform int rubyFrameDirection;

vec4 Sepia( in vec4 color )
{
    return vec4(
          clamp(color.r * 0.393 + color.g * 0.769 + color.b * 0.189, 0.0, 1.0)
        , clamp(color.r * 0.349 + color.g * 0.686 + color.b * 0.168, 0.0, 1.0)
        , clamp(color.r * 0.272 + color.g * 0.534 + color.b * 0.131, 0.0, 1.0)
        , color.a
    );
}

void main()
{
vec4 color = texture2D(rubyPrev6Texture, gl_TexCoord[0].xy);
color = (color + texture2D(rubyPrev5Texture, gl_TexCoord[0].xy)) / 2.0;
color = (color + texture2D(rubyPrev4Texture, gl_TexCoord[0].xy)) / 2.0;
color = (color + texture2D(rubyPrev3Texture, gl_TexCoord[0].xy)) / 2.0;
color = (color + texture2D(rubyPrev2Texture, gl_TexCoord[0].xy)) / 2.0;
color = (color + texture2D(rubyPrev1Texture, gl_TexCoord[0].xy)) / 2.0;
color = (color + texture2D(rubyPrevTexture, gl_TexCoord[0].xy)) / 2.0;
color = (Sepia(color) + 0.8 * (texture2D(rubyTexture, gl_TexCoord[0].xy))) / 2.0;

if (rubyFrameDirection < 0.0)
    {
    gl_FragColor = color;
    }
else
{
    gl_FragColor = texture2D(rubyTexture, gl_TexCoord[0].xy);
    }
}
]]></fragment>
</shader>

You can make this shader run as the first pass at scale 1.0 and then run any other (non-multipass) shader as the second pass.

I ported the motion blur shader to Cg, as well:

/*
    Motion Blur
    Authors: hunterk, cgwg
 
    This program is free software; you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by the Free
    Software Foundation; either version 2 of the License, or (at your option)
    any later version.
*/

struct previous
{
   uniform sampler2D texture;
   float2 tex_coord;
};

struct input
{
    float2 video_size;
    float2 texture_size;
    float2 output_size;
    float frame_count;
    float frame_direction;
    float frame_rotation;
    sampler2D texture : TEXUNIT0;
};

struct tex_coords
{
   float2 tex;
   float2 prev;
   float2 prev1;
   float2 prev2;
   float2 prev3;
   float2 prev4;
   float2 prev5;
   float2 prev6;
};

void main_vertex
(
   float4 position : POSITION,
   out float4 oPosition : POSITION,
   uniform float4x4 modelViewProj,
   float2 tex : TEXCOORD,

   previous PREV,
   previous PREV1,
   previous PREV2,
   previous PREV3,
   previous PREV4,
   previous PREV5,
   previous PREV6,
   out tex_coords coords
)
{
   oPosition = mul(modelViewProj, position);
   coords = tex_coords(tex, PREV.tex_coord,
      PREV1.tex_coord,
      PREV2.tex_coord,
      PREV3.tex_coord,
      PREV4.tex_coord,
      PREV5.tex_coord,
      PREV6.tex_coord);
}

struct output 
{
  float4 col    : COLOR;
};

output main_fragment(in float2 texCoord : TEXCOORD0,
uniform input IN,
      previous PREV,
      previous PREV1,
      previous PREV2,
      previous PREV3,
      previous PREV4,
      previous PREV5,
      previous PREV6
)
{
   float4 color = tex2D(PREV6.texture, texCoord);
   color = (color + tex2D(PREV5.texture, texCoord)) / 2.0;
   color = (color + tex2D(PREV4.texture, texCoord)) / 2.0;
   color = (color + tex2D(PREV3.texture, texCoord)) / 2.0;
   color = (color + tex2D(PREV2.texture, texCoord)) / 2.0;
   color = (color + tex2D(PREV1.texture, texCoord)) / 2.0;
   color = (color + tex2D(PREV.texture, texCoord)) / 2.0;
   color = (color + tex2D(IN.texture, texCoord)) / 2.0;


   output OUT;
   OUT.col = color;
   return OUT;
}

I’ll get it added to common-shaders soon, and maybe make a Braid version, too :slight_smile:

Strangely this fails to init with RA v0.9.9-wip1 + Mednafen-vb core only! (With the other cores is working fine btw)

Weird. It seems okay here with an older version of RA. I’ll update and do some more testing.

Does it give you any kind of informative error in the log? EDIT: d’oh, I tested mednafen-psx, not mednafen-vb. I’ll have to take your word for it, as I don’t actually have any VB games to test with :X

EDIT: here’s a Braid rewind version:

/*
    Braid Rewind
    Authors: hunterk, cgwg
 
    This program is free software; you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by the Free
    Software Foundation; either version 2 of the License, or (at your option)
    any later version.
*/

struct previous
{
   uniform sampler2D texture;
   float2 tex_coord;
};

struct input
{
    float2 video_size;
    float2 texture_size;
    float2 output_size;
    float frame_count;
    float frame_direction;
    float frame_rotation;
    sampler2D texture : TEXUNIT0;
};

struct tex_coords
{
   float2 tex;
   float2 prev;
   float2 prev1;
   float2 prev2;
   float2 prev3;
   float2 prev4;
   float2 prev5;
   float2 prev6;
};

void main_vertex
(
   float4 position : POSITION,
   out float4 oPosition : POSITION,
   uniform float4x4 modelViewProj,
   float2 tex : TEXCOORD,

   previous PREV,
   previous PREV1,
   previous PREV2,
   previous PREV3,
   previous PREV4,
   previous PREV5,
   previous PREV6,
   out tex_coords coords
)
{
   oPosition = mul(modelViewProj, position);
   coords = tex_coords(tex, PREV.tex_coord,
      PREV1.tex_coord,
      PREV2.tex_coord,
      PREV3.tex_coord,
      PREV4.tex_coord,
      PREV5.tex_coord,
      PREV6.tex_coord);
}

struct output 
{
  float4 col    : COLOR;
};

output main_fragment(in float2 texCoord : TEXCOORD0,
uniform input IN,
      previous PREV,
      previous PREV1,
      previous PREV2,
      previous PREV3,
      previous PREV4,
      previous PREV5,
      previous PREV6
)
{

   float4 color = tex2D(PREV6.texture, texCoord);
   color = color + tex2D(PREV5.texture, texCoord);
   color = color + tex2D(PREV4.texture, texCoord);
   color = color + tex2D(PREV3.texture, texCoord);
   color = color + tex2D(PREV2.texture, texCoord);
   color = color + tex2D(PREV1.texture, texCoord);
   color = color + tex2D(PREV.texture, texCoord);
   color = color + tex2D(IN.texture, texCoord);
   
   float4 sepia = float4(1.0,0.8,0.6,0.5); 
   
   float4 final = tex2D(IN.texture, texCoord);
   
   if (IN.frame_direction < 0.0)
    {
        final = (final + color * sepia / 7.0) / 2.0;
    }
    else
    {
        final = tex2D(IN.texture, texCoord);
    }

   output OUT;
   OUT.col = final;
   return OUT;
}

Unfortunately, the sepia effect is much simpler and kinda sucks compared with GLSL version.

You could try a PD homebrew.

Btw i’ve found the reason why it didn’t work: i had to put it on top of the shaders stack!