Skip to content

Post Processing Shaders

lioncash edited this page Oct 12, 2014 · 8 revisions

Starting with Dolphin 4.0-2554 there has been work done to improve post processing shaders to allow more user configurability without having to modify the shader sources themselves. In order to take advantage of these features, post processing shaders must be updated to take advantage of these new features.

Staying Portable


The first step to making a great user experience is to try and be as neutral as possible for a Direct3D or OpenGL backend. Once the post processing shaders are implemented in Direct3D, users would like their favourite shaders to work in both video backends. Some tips for making sure your shader will compile on all backends.

  • Instead of using GLSL vecX types, using HLSL floatX types. Our shader compiler supports floatX in GLSL, but not the other way around.
  • Use our embedded functions for determining UV location and sampling due to that location. In the future we can work around D3D having a different coordinate system using our own functions.
  • Try to stay away from HLSL only functions, Our shader compiler supports frac and lerp. But nothing else.
  • We try to support GLSL 1.30 and GLSL ES 3.00 minimum. If you don't have a good reason, try to stay away from using OpenGL shader extensions. These aren't portable and we won't be able to work around them. If the functionality of the extension is absolutely necessary, try to talk with some developers to see if we can implement the functionality in both D3D and OGL in a clean fashion using our own functions.

Configuration Setup


The Post Processing shader's configuration sits inside of the actual post processing shader code. It's recommended to have the configuration towards the top of the shader, outside of the main function. There are two requirements to this

  • It must be in a comment block
  • It must be surrounded by [configuration] and [/configuration]

The configuration inside of of this block is similar to how Windows' INI files work and we have three sections that we support for different options.

  • [OptionBool]
    • This is a boolean option, this'll show as a checkbox in the GUI
  • [OptionRangeInteger] or [OptionRangeFloat]
    • This is a integer or float range that is anywhere from one to four elements
    • This will show up as one to four sliders in the GUI

Any of these options require all three settings set at all times

  • GUIName
    • This is a string that will show up in the GUI for that corresponding option
  • OptionName
    • This is a case sensitive unique identifier that is used in the post processing shader
    • You'll get errors if this isn't unique
  • DefaultValue
    • This is the default value that is set prior to any sort of user configuration
    • For [OptionBool] this can be 'True' or 'False'
    • For [OptionRangeInteger] and [OptionRangeFloat] this can be a single value or a vector
    • How many values in this setting determine how many values show up in the UI
    • Examples
      • DefaultValue = True
      • DefaultValue = 1
      • DefaultValue = 1.0, 1.0, 1.0, 1.0

There is one optional setting that all three configuration types can have; This is 'DependentOption'. This allows options to be children of [OptionBool] options. This allows basic grouping of options, which effects how options will show up in the GUI. If a option has children they will show up in the same tab, if there aren't any children they will show up in a general option tab. The value of this option must be the 'OptionName' of a [OptionBool] If you make circular dependencies of options, you reap what you sow.

[OptionRangeInteger] and [OptionRangeFloat] have other required settings to be set

  • MinValue
    • This is the minimum value that a integer or float range has
    • In a vector each value can have a different value
  • MaxValue
    • This is the maximum value that a integer or float range has
    • In a vector each value can have a different value
  • StepAmount
    • This is how much the range changes in the UI per step
    • In a vector each value can have a different value

Sample Configuration

/*
[configuration]

[OptionBool]
GUIName = Skylight Bevel Shader
OptionName = BEVEL
DefaultValue = true

[OptionRangeFloat]
GUIName = Skylight Amount
OptionName = SKYLIGHT_AMOUNT
MinValue = 0.0
MaxValue = 1.0
StepAmount = 0.05,
DefaultValue = 0.5
DependentOption = BEVEL

[OptionRangeFloat]
GUIName = Sky Color
OptionName = SKY_COLOR
MinValue = 0.0, 0.0, 0.0
MaxValue = 1.0, 1.0, 1.0
StepAmount = 0.05, 0.05, 0.05
DefaultValue = 0.1, 0.1, 0.5
DependentOption = BEVEL

[OptionRangeInteger]
GUIName = Ground Color
OptionName = GROUND_COLOR
MinValue = 0, 0, 0
MaxValue = 256, 256, 256
StepAmount = 1, 1, 1
DefaultValue = 1, 1, 1
DependentOption = BEVEL

[/configuration]
*/

Embedded Functions


float4 Sample();

Samples the current location, returning a vector containing the colour of the current sample


float4 SampleLocation(float2 location);

Samples a location that doesn't have to be the current sample location location is a float vector which contains the X Y coordinates of where to sample from. The sample range is 0.0 to 1.0 using the OpenGL coordinate system


float4 SampleOffset(int2 offset);

This samples at a offset of the current location. The argument is a integer vector containing the X Y offset of where to sample from. The offset is in pixels instead of using UV coordinates. This requires the offset to be a compile time constant, if it isn't then compiling will fail. This is similar to SampleLocation, except the video drivers and hardware can optimize to be faster. This is a limitation in how large the offset can be. To be safe on all platforms, limit the offset from ranges -8 to 7.


float4 SampleFontLocation(float2 location);

This doesn't sample the framebuffer like the previous functions. This actually samples a location from Dolphin's bitmap font that it uses for on screen text. The best example of how to use this is to look at Dolphin's asciiart PP shader


float2 GetResolution();

Returns a float vector contain the resolution of the framebuffer


float2 GetInvResolution();

Returns a float vector containing the reciprocal of the framebuffer resolution This can be used to quickly get a float offset from the current sample position


float2 GetCoordinates();

Returns a float vector containing the UV coordinates where we are currently sampling from This can be used when sampling from a location offset from the current location. Tends to be used in conjunction with GetInvResolution() Recommended to use SampleOffset instead if the offset is a compile time constant


uint GetTime();

This returns a 32bit unsigned integer of the elapsed time since emulation has started The time is in milliseconds This allows for shaders that can do certain effects with the passage of time


void SetOutput(float4 colour);

The argument is a float vector containing what the resulting output should be at the current sampling location


bool OptionEnabled(option);

Returns a boolean for if a boolean option is enabled or not This only works for the Option type configuration option Takes the OptionName that was defined in the configuration

  • Example
if (OptionEnabled(BEVEL)) { /* Do Stuff*/ }

genType GetOption(option);

Returns an options current setting genType is either int, float, float2/3/4, int2/3/4 Depending on what type your option is in the configuration it'll return the correct type here Takes the OptionName that was defined in the configuration

  • Example
float3 colour_boost = GetOption(SKY_COLOR);

Sample Shader


/*
[configuration]
[OptionBool]
GUIName = Skylight Bevel Shader
OptionName = BEVEL
DefaultValue = true

[OptionRangeFloat]
GUIName = Skylight Amount
OptionName = SKYLIGHT_AMOUNT
MinValue = 0.0
MaxValue = 1.0
StepAmount = 0.05,
DefaultValue = 0.5
DependentOption = BEVEL

[OptionRangeFloat]
GUIName = Sky Color
OptionName = SKY_COLOR
MinValue = 0.0, 0.0, 0.0
MaxValue = 1.0, 1.0, 1.0
StepAmount = 0.05, 0.05, 0.05
DefaultValue = 0.1, 0.1, 0.5
DependentOption = BEVEL

[OptionRangeFloat]
GUIName = Base Color
OptionName = BASE_COLOR
MinValue = 0.0, 0.0, 0.0
MaxValue = 1.0, 1.0, 1.0
StepAmount = 0.05, 0.05, 0.05
DefaultValue = 0.0, 0.0, 0.1
DependentOption = BEVEL

[OptionRangeFloat]
GUIName = Ground Color
OptionName = GROUND_COLOR
MinValue = 0.0, 0.0, 0.0
MaxValue = 1.0, 1.0, 1.0
StepAmount = 0.05, 0.05, 0.05
DefaultValue = 0.1, 0.4, 0.1
DependentOption = BEVEL

[OptionBool]
GUIName = Sharpen
OptionName = SHARPNESS
DefaultValue = true

[OptionRangeFloat]
GUIName = Sharpness Amount
OptionName = SHARPNESS_VALUE
MinValue = 0.0
MaxValue = 10.0
StepAmount = 0.5
DefaultValue = 1.5
DependentOption = SHARPNESS

[OptionBool]
GUIName = Color Boost
OptionName = COLOR_BOOST
DefaultValue = true

[OptionRangeFloat]
GUIName = Color Boost Red
OptionName = RED_BOOST
MinValue = 0.0
MaxValue = 5.0
StepAmount = 0.1
DefaultValue = 1.0
DependentOption = COLOR_BOOST

[OptionRangeFloat]
GUIName = Color Boost Green
OptionName = GREEN_BOOST
MinValue = 0.0
MaxValue = 5.0
StepAmount = 0.1
DefaultValue = 1.0
DependentOption = COLOR_BOOST

[OptionRangeFloat]
GUIName = Color Boost Blue
OptionName = BLUE_BOOST
MinValue = 0.0
MaxValue = 5.0
StepAmount = 0.1
DefaultValue = 1.0
DependentOption = COLOR_BOOST
[/configuration]
*/

void main() {
  float3 curr = Sample().xyz;

  if (OptionEnabled(SHARPNESS))
  {
	  float3 s0 = SampleOffset(int2(-1, -1)).xyz;
	  float3 s1 = SampleOffset(int2( 1, -1)).xyz;
	  float3 s2 = SampleOffset(int2(-1,  1)).xyz;
	  float3 s3 = SampleOffset(int2( 1,  1)).xyz;
	  float3 blur = (curr + s0 + s1 + s2 + s3) / 5;
	  curr = curr + (curr - blur) * GetOption(SHARPNESS_VALUE);
  }
  
  if (OptionEnabled(BEVEL))
  {
	  float mid = dot(curr, float3(0.33, 0.33, 0.33));
	  float top = dot(SampleOffset(int2(0,  1)).xyz, float3(0.33, 0.33, 0.33));
	  float bot = dot(SampleOffset(int2(0, -1)).xyz, float3(0.33, 0.33, 0.33));
	  float upw = ((top - mid) + (mid - bot)) / 4 + 0.5;
	  float3  col = mix(GetOption(SKY_COLOR), GetOption(GROUND_COLOR), upw);
	  float3 shde = mix(GetOption(BASE_COLOR), col, clamp(abs(upw * 2.0 - 1.0) * 1, 0.0, 1.0));
	  curr = curr + shde * GetOption(SKYLIGHT_AMOUNT);
  }

  if (OptionEnabled(COLOR_BOOST))
	  curr = curr * float3(GetOption(RED_BOOST), GetOption(GREEN_BOOST), GetOption(BLUE_BOOST));

  /* Done */
  SetOutput(float4(curr, 1.0));
}

Translations


TBD