Skip to content

Instantly share code, notes, and snippets.

@BenMcLean
Last active December 15, 2023 00:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save BenMcLean/9327690b93690b8a92a921df003f7954 to your computer and use it in GitHub Desktop.
Save BenMcLean/9327690b93690b8a92a921df003f7954 to your computer and use it in GitHub Desktop.
Draw a procedurally-generated palette-indexed texture using a shader in Godot.
//Uses a byte retrieval function to treat a texture like a byte array
shader_type canvas_item;
render_mode blend_disabled;
uniform vec4[256] u_palette;
uniform sampler2D u_texture : filter_nearest;
uint byte(uint index, uint width){
//uint width = uint(textureSize(u_texture, 0).x);
uint index4th = index >> uint(2);
return uint(texelFetch(u_texture, ivec2(int(index4th % width), int(index4th / width)), 0)[index % uint(4)] * 255.);
}
void fragment(){
uvec2 texture_size = uvec2(textureSize(u_texture, 0)),
real_texture_size = uvec2(texture_size.x << uint(2), texture_size.y),
position = uvec2(UV * vec2(real_texture_size));
COLOR = u_palette[byte(uint(position.y * real_texture_size.x + position.x), texture_size.x)];
}
public partial class ShadedColorRect : ColorRect
{
public const string GodotShaderCode = @"
shader_type canvas_item;
render_mode blend_disabled;
uniform vec4[7] u_palette;
uniform sampler2D u_texture : filter_nearest;
void fragment(){
vec2 texture_size = vec2(textureSize(u_texture, 0));
//COLOR = texture(u_texture, (floor(UV * texture_size) + .5) / texture_size);
COLOR = u_palette[int(texture(u_texture,
(floor(UV * texture_size) + .5) / texture_size
)[
int(UV.x * texture_size.x * 4.) % 4
] * 255.)];
}
";
public static readonly ShaderMaterial ShaderMaterial = new ShaderMaterial
{
Shader = new Shader { Code = GodotShaderCode, },
};
public static readonly ReadOnlyCollection<uint> Rainbow = Array.AsReadOnly(new uint[7]
{//Just a color test, not a political statement.
0xFF0000FFu,//Red
0xFFA500FFu,//Orange
0xFFFF00FFu,//Yellow
0x00FF00FFu,//Green
0x0000FFFFu,//Blue
0x4B0082FFu,//Indigo
0x8F00FFFFu,//Violet
});
public static byte[] RainbowTexture(ushort width, ushort height = 0)
{
if (height < 1)
height = width;
byte[] result = new byte[width * height];
ushort stripe = (ushort)(width / 7);
byte color = 0;
for (ushort start = 0; start < width && color < 7; start += stripe, color++)
for (ushort x = 0; x < stripe && start + x < width; x++)
result[start + x] = color;
for (ushort x = (ushort)(stripe * 7); x < width; x++)
result[x] = 6;
for (int start = width, y = 1; start < result.Length - 1; start += width, y++)
Array.Copy(
sourceArray: result,
sourceIndex: 0,
destinationArray: result,
destinationIndex: start + y,
length: width - y);
return result;
}
public override void _Ready()
{
TextureFilter = TextureFilterEnum.Nearest;
ushort width = 256, height = 256;
Size = new Vector2(x: width, y: height);
Material = ShaderMaterial;
ShaderMaterial.SetShaderParameter("u_palette", Rainbow.Select(color => new Godot.Color(color)).ToArray());
ShaderMaterial.SetShaderParameter("u_texture", Godot.ImageTexture.CreateFromImage(Godot.Image.CreateFromData(
width: width >> 2,
height: height,
useMipmaps: false,
format: Godot.Image.Format.Rgba8,
data: RainbowTexture(width, height))));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment