3D Post-Processing: Dithering + Color Palettes
To use this shader:
Create a MeshInstance 3D in your main 3D scene, set its mesh to a BoxMesh, and in the BoxMesh properties, set its scale to (2, 2, 2). Apply this shader, and in its properties, give it a Gradient1D in the “pal” slot.
If you just want to dither your colors, uncomment line 101 in the shader code, and comment out line 92.
The “Rows” uniform is the color detail level, and the “dither_size” uniform is the dithering size.
The palette mode supports multiple color palettes.
The uniforms “Rows” and “Cols” determine colors per palette, and palette count respectively.
The palettes are read with the red and green input colors.
Red picks which color in a palette to use, and green picks which palette to use.
In my case, the following code sets me up with four palettes with four colors each:
*hint: the shader looks for evenly spaced placements of “Rows” x “Cols”
in my case, the “i/16.0” in the for loop does that for me.
Just make sure to populate the cList array if you choose to use my code.
@tool
extends MeshInstance3D
@export var cList:Array[Color] = []
var newtext:Gradient = Gradient.new()
func _ready():
var newImg:GradientTexture1D = GradientTexture1D.new()
newImg.gradient = gen_gradient()
print(newImg.gradient.get_point_count())
material_override.set_shader_parameter("pal", newImg)
pass
func gen_gradient() -> Gradient:
#cList.clear()
for i in newtext.get_point_count()-1:
newtext.remove_point(0)
newtext.interpolation_mode = Gradient.GRADIENT_INTERPOLATE_CONSTANT
for i in cList.size():
newtext.add_point(i/16.0, cList[i])
newtext.remove_point(0)
return newtext
Feb 29, 2024: Fixed(?) the dither code; Not gonna lie. If you’re looking for a dithering shader, look somewhere else for the time being. It only looks right when “rows” is set to 4.0. It’s still a bit janky.
Shader code
shader_type spatial;
render_mode unshaded;
const float lin = .454545;
const float srgb = 2.2;
//render_mode blend_mul;
//INSTRUCTIONS: Simply uncomment or comment lines 92 and/or 101 to
// activate/deactivate color behaviors
// If you feel so inclined, make yourself some uniforms to toggle/interpolate between the behaviors
//uniform float fiddler: hint_range(0.0, 1.0);
uniform float rows = 4.0;
uniform float cols = 4.0;
uniform sampler2D pal: source_color, filter_nearest, repeat_enable;
uniform sampler2D SCREEN_TEXTURE: hint_screen_texture;
uniform float dither_size;
uniform float dither_amount: hint_range(0.0, 1.0);
float lerp(float val1, float val2, float mul) {
return val1 + (val2 - val1) * clamp(mul, 0.0, 1.0);
}
vec3 get_srgb(vec3 xyz) {
vec3 output = vec3(pow(xyz.x, srgb), pow(xyz.y, srgb), pow(xyz.z,srgb));
return output;
}
vec3 get_linear(vec3 xyz) {
vec3 output = vec3(pow(xyz.x, lin), pow(xyz.y, lin), pow(xyz.z, lin));
return output;
}
float PickColor(float x, float y) {
float output = 0.0;
float offset = +0.75/(rows*cols);
x = mod(x, 1.0);
y = mod(y, 1.0);
x = clamp(floor(x*(rows))/((cols*rows)), 0.0, 250.0/256.0);
y = clamp(floor(y*(cols))/((cols)), 0.0, 250.0/256.0);
output = y+x;
return output;
}
vec3 dither(vec3 color_in, vec2 uv, vec2 screen_size) {
float dith = max(1.0, dither_size);
vec3 unaltered = color_in;
float m = rows; float d = rows*cols;
float f = 0.25/rows;
color_in = get_linear(color_in);
float output = 0.0;
float a = floor(mod(uv.x * screen_size.x/dith, 2.0));
float b = floor(mod(uv.y * screen_size.y/dith, 2.0));
float c = mod(a + b , 2.0)+f;
color_in.r += lerp( -f, f, c);
color_in = get_srgb(color_in);
color_in = clamp(vec3(round(color_in.r*(d))/(d),round(color_in.g*(d))/(d),round(color_in.b*(d))/(d)),0.0, 1023.5/1024.0);
return color_in;
}
void vertex() {
POSITION = vec4(vec3(VERTEX.x, VERTEX.y, 0.0), 1.0);
// Called for every vertex the material is visible on.
}
void fragment() {
float index = 0.0;
vec3 tex_uv = vec3(UV, 0.0);
vec4 _tex = textureLod(SCREEN_TEXTURE, SCREEN_UV, 0.0);
float newval = 0.0;
vec3 color = _tex.rgb;
color.r = lerp(color.r, dither(color, SCREEN_UV, VIEWPORT_SIZE).r, dither_amount); // comment this line if you do not want to dither the palette
vec3 corr = get_linear(color.rgb);
index = PickColor(corr.r, corr.g);
vec3 pal_read = get_srgb(textureLod(pal, vec2(index, 0.0), 0.0).rgb);
ALBEDO.rgb = pal_read;
//ALBEDO.rgb = dither(color, SCREEN_UV, VIEWPORT_SIZE);
/* Uncomment the line above to disable the palette, and dither the regular screen colors*/
}
can your share your project on github, I can’t reproduce it in engine, thanks
I have added the project. Dissect it to your liking!