Files
mod_core/shaders/SHADER_CONTRACT.md
T
2026-09-10 10:59:36 +03:00

4.3 KiB
Raw Blame History

Shader contract for gd_cubism (Live2D)

Overview

gd_cubism uses 10 shaders to render Live2D models. Each drawable (mesh part) selects one of these shaders based on its blend mode and mask configuration. A mod can replace any shader by placing a .gdshader file with the same name in its shaders/ directory.

Mandatory contract

Uniforms — DO NOT remove or rename

Every shader below MUST declare the uniforms listed in its section. The C++ renderer sets these uniforms by name via ShaderMaterial::set_shader_parameter(). Removing or renaming a uniform will break rendering.

You MAY add new uniforms (e.g. uniform float pixel_size = 4.0;), but you must set them manually in code via model.GetMeshes().

Vertex shader

All shaders MUST include UV.y = 1.0 - UV.y; in the vertex function. This flips the texture vertically to match Live2D's coordinate system.

blend_mode must match

The render_mode line must remain compatible with the blend operation the C++ code expects. Changing blend_premul_alpha to blend_mix will produce incorrect transparency.


Shader catalog

1. 2d_cubism_norm_mix.gdshader — Normal blend (most drawables)

render_mode: blend_premul_alpha, unshaded

Mandatory uniforms:

uniform vec4 color_base : source_color;
uniform vec4 color_screen : source_color;
uniform vec4 color_multiply : source_color;
uniform vec4 channel : source_color;
uniform sampler2D tex_main : filter_linear_mipmap;

Varying: varying vec4 modulate (passed from vertex: modulate = COLOR)

Fragment output: COLOR = vec4(color.rgb * color.a, color.a);

2. 2d_cubism_norm_add.gdshader — Additive blend

Same uniforms and contract as norm_mix. Fragment output: COLOR = vec4(color.rgb * color.a, 0.0); (alpha=0 for additive)

3. 2d_cubism_norm_mul.gdshader — Multiply blend

Same uniforms and contract as norm_mix.

4. 2d_cubism_mask.gdshader — Mask generation (offscreen)

render_mode: blend_mix, unshaded

Mandatory uniforms:

uniform vec4 channel : source_color;
uniform sampler2D tex_main : filter_linear_mipmap;

Fragment output: COLOR = channel * texture(tex_main, UV).a;

This shader writes alpha to the mask texture. Not visible on screen.

510. Masked drawable shaders

2d_cubism_mask_mix, mask_add, mask_mul, mask_mix_inv, mask_add_inv, mask_mul_inv

render_mode: same as their non-mask counterpart

Mandatory uniforms (all of norm_mix PLUS):

uniform sampler2D tex_mask : filter_linear_mipmap;

Additional varyings (computed in vertex):

varying vec2 MASK_UV;

Mask UV computation in vertex:

MASK_UV = (VERTEX - mesh_offset) / mask_size;
// where mask_size = textureSize(tex_mask, 0) / mask_scale

Fragment must sample tex_mask and multiply/lerp the main color by the sum of mask channels: clip_mask.r + clip_mask.g + clip_mask.b + clip_mask.a. The _inv variants invert this: (1.0 - mask_val).


How to add custom logic

Safe additions (will not break rendering):

  • Add new uniform declarations (set values via ShaderMaterial.SetShaderParameter)
  • Modify internal fragment calculations (e.g. pixel-art downscale)
  • Add conditional logic (e.g. if (pixel_size > 1.0) { ... })

Allowed modifications:

  • Change texture filtering: filter_linear_mipmapfilter_nearest
  • Add post-processing in fragment (color grading, dithering, outline)
  • Add noise/procedural effects using new uniforms

Forbidden modifications:

  • Remove or rename mandatory uniforms
  • Change render_mode blend type
  • Remove UV.y = 1.0 - UV.y from vertex
  • Change the data type of mandatory uniforms (vec4 → vec3, etc.)

Mod override example

To create a pixel-art Live2D shader:

  1. Copy 2d_cubism_norm_mix.gdshader to mods/your_mod/shaders/
  2. Add a new uniform: uniform float pixel_size : hint_range(1.0, 16.0) = 4.0;
  3. Modify fragment to downsample:
vec2 tex_size = vec2(textureSize(tex_main, 0));
vec2 snapped_uv = floor(UV * tex_size / pixel_size) * pixel_size / tex_size;
vec4 color_tex = texture(tex_main, snapped_uv);
  1. Keep all mandatory uniforms and the vertex shader intact.
  2. In code, find the material and set pixel_size:
var meshes = model.GetMeshes();
foreach (var kv in meshes) {
    var mesh = (ArrayMesh)kv.Value;
    var mat = (ShaderMaterial)mesh.SurfaceGetMaterial(0);
    mat.SetShaderParameter("pixel_size", 4.0f);
}