Overview
Custom Pipeline graphics mods allow advanced users to replace Dolphin’s internal pixel shader with custom GLSL code. This enables complete control over final pixel output, including custom lighting, normal mapping, texture manipulation, and visual effects.
This is an advanced feature requiring knowledge of GLSL shading and graphics programming.
Graphics Mod Integration
Custom pipelines are defined as graphics mod actions. You must be familiar with the graphics mod format before using custom pipelines.
Action Type
The action type is custom_pipeline with the following structure:
Array of rendering passes. Currently only single-pass is supported. Name of the material asset containing shader information
Complete Example
{
"assets" : [
{
"name" : "material_replace_normal" ,
"data" : {
"" : "normal.material.json"
}
},
{
"name" : "shader_replace_normal" ,
"data" : {
"metadata" : "replace_normal.shader.json" ,
"shader" : "replace_normal.glsl"
}
},
{
"name" : "normal_texture" ,
"data" : {
"texture" : "normal_texture.png"
}
}
],
"features" : [
{
"action" : "custom_pipeline" ,
"action_data" : {
"passes" : [
{
"pixel_material_asset" : "material_replace_normal"
}
]
},
"group" : "PipelineTarget"
}
],
"groups" : [
{
"name" : "PipelineTarget" ,
"targets" : [
{
"texture_filename" : "tex1_512x512_m_afdbe7efg332229e_14" ,
"type" : "draw_started"
},
{
"texture_filename" : "tex1_512x512_m_afdbe7efg332229e_14" ,
"type" : "create_texture"
}
]
}
]
}
Entry Point
All custom shaders must provide this entry point:
CustomShaderOutput custom_main (in CustomShaderData data )
Output Structure
The main render target’s output color (RGBA)
Pixel position in view space
Pixel normal in view space
Array of texture coordinates (size: texcoord_count)
Number of available texture coordinates
Maps texture units to texture coordinate indices
Color lights for channel 0 (size: light_chan0_color_count)
Alpha lights for channel 0
Color lights for channel 1
Alpha lights for channel 1
Ambient lighting values for each color channel
Base material values for each color channel
TEV (Texture Environment) stages (size: tev_stage_count)
Final color after all TEV stages (what Dolphin would normally output)
Milliseconds since game started (useful for animations)
Lighting Data
Light position in view space
Light direction in view space (point/spot lights)
Attenuation type:
CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_POINT - Point light
CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_DIR - Directional light
CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_SPOT - Spot light
Cosine attenuation values
Distance attenuation values
TEV Stage Data
TEV (Texture Environment) stages represent GameCube/Wii graphics operations.
input_color
CustomShaderTevStageInputColor[4]
Four color inputs for this stage
input_alpha
CustomShaderTevStageInputAlpha[4]
Four alpha inputs for this stage
Texture unit for this stage
Final output color from this stage
CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_PREV // From previous stage
CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_COLOR // From color data
CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_TEX // From texture
CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_RAS // From rasterizer
CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_KONST // Constant from software
CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_NUMERIC // Numeric constant (0 or 1)
Shader Examples
Single Color Output
CustomShaderOutput custom_main (in CustomShaderData data )
{
CustomShaderOutput custom_output;
custom_output . main_rt = vec4 ( 1.0 , 0.0 , 0.0 , 1.0 ); // Red
return custom_output;
}
Display Normal Map
CustomShaderOutput custom_main (in CustomShaderData data )
{
CustomShaderOutput custom_output;
// Convert normal from [-1,1] to [0,1] for display
custom_output . main_rt = vec4 ( data . normal * 0.5 + 0.5 , 1 );
return custom_output;
}
Reading Custom Texture
CustomShaderOutput custom_main (in CustomShaderData data )
{
CustomShaderOutput custom_output;
// samp_MY_TEX is defined in shader asset
// TEX_COORD0 is first texture coordinate
custom_output . main_rt = texture (samp_MY_TEX, TEX_COORD0);
return custom_output;
}
First Game Texture
Display the first texture the game uses, ignoring other operations:
CustomShaderOutput custom_main (in CustomShaderData data )
{
vec4 final_color = data . final_color ;
uint texture_set = 0 ;
for ( uint i = 0 ; i < data . tev_stage_count ; i ++ )
{
// Check 4 color inputs per stage
for ( uint j = 0 ; j < 4 ; j ++ )
{
if ( data . tev_stages [i]. input_color [j]. input_type ==
CUSTOM_SHADER_TEV_STAGE_INPUT_TYPE_TEX && texture_set == 0 )
{
final_color = vec4 ( data . tev_stages [i]. input_color [j]. value , 1.0 );
texture_set = 1 ;
}
}
}
CustomShaderOutput custom_output;
custom_output . main_rt = final_color;
return custom_output;
}
Point Light Lighting
Apply point lighting for channel 0 color lights:
CustomShaderOutput custom_main (in CustomShaderData data )
{
float total_diffuse = 0 ;
vec3 normal = data . normal ;
for ( int i = 0 ; i < data . light_chan0_color_count ; i ++ )
{
if ( data . lights_chan0_color [i]. attenuation_type ==
CUSTOM_SHADER_LIGHTING_ATTENUATION_TYPE_POINT)
{
vec3 light_dir = normalize (
data . lights_chan0_color [i]. position - data . position . xyz
);
float attn = ( dot (normal, light_dir) >= 0.0 )
? max ( 0.0 , dot (normal, data . lights_chan0_color [i]. direction . xyz ))
: 0.0 ;
vec3 cosAttn = data . lights_chan0_color [i]. cosatt . xyz ;
vec3 distAttn = data . lights_chan0_color [i]. distatt . xyz ;
attn = max ( 0.0 , dot (cosAttn, vec3 ( 1.0 , attn, attn * attn)))
/ dot (distAttn, vec3 ( 1.0 , attn, attn * attn));
total_diffuse += attn * max ( 0.0 , dot (normal, light_dir));
}
}
CustomShaderOutput custom_output;
custom_output . main_rt = vec4 (total_diffuse * vec3 ( 0 , 0 , 1 ), 1 ); // Blue base
return custom_output;
}
Texture Sampling
Access textures using samp_<NAME> samplers defined in your material asset:
// Defined in material asset as "MY_TEX"
texture (samp_MY_TEX, data. texcoord [ 0 ])
// Built-in coordinate macros
texture (samp_MY_TEX, TEX_COORD0)
texture (samp_MY_TEX, TEX_COORD1)
Time-Based Animation
Use data.time_ms for animated effects:
CustomShaderOutput custom_main (in CustomShaderData data )
{
float time_sec = float ( data . time_ms ) / 1000.0 ;
float pulse = ( sin (time_sec * 2.0 ) + 1.0 ) / 2.0 ; // 0 to 1
CustomShaderOutput custom_output;
custom_output . main_rt = vec4 (pulse, pulse, pulse, 1.0 );
return custom_output;
}
Best Practices
Performance Considerations
GLSL is converted to backend-specific shaders (D3D, Vulkan, Metal)
Test on multiple backends if possible
Avoid GLSL extensions unless necessary
Use standard GLSL types and functions
Start with simple solid colors to verify shader loads
Output intermediate values as colors for visualization
Check Dolphin log for shader compilation errors
Use data.final_color as fallback
Shaders are compiled at runtime. Syntax errors will prevent the graphics mod from loading.
Limitations
Single pass only : Multi-pass rendering not currently supported
Pixel shaders only : Vertex shaders cannot be customized
View space only : Position and normal data are in view space, not world space
Backend conversion : GLSL is converted to target API, some features may not translate
Graphics Mods Learn the graphics mod system basics
Resource Packs Package mods for distribution