Shader for writing text to image (shadertoy origin to godot)

Godot Version

4.0+

Question

I’m attempting to convert parts of https://www.shadertoy.com/view/43t3WX for use in Godot, working through my limited grasp of shader code. What I have so far:

shader_type canvas_item;

// — Uniforms —
uniform sampler2D FONT_TEXTURE : filter_linear;
uniform float FONT_SPACING = 2.0;

// — Character Definitions —
#define __    32 // "   "
#define _EX   33 // " ! "
#define _DBQ  34 // " " "
#define _NUM  35 // " # "
#define _DOL  36 // " $ "
#define _PER  37 // " % "
#define _AMP  38 // " & "
#define _QT   39 // " ’ "
#define _LPR  40 // " ( "
#define _RPR  41 // " ) "
#define _MUL  42 // " * "
#define _ADD  43 // " + "
#define _COM  44 // " , "
#define _SUB  45 // " - "
#define _DOT  46 // " . "
#define _DIV  47 // " / "
#define _COL  58 // " : "
#define _SEM  59 // " ; "
#define _LES  60 // " < "
#define _EQ   61 // " = "
#define _GE   62 // " > "
#define _QUE  63 // " ? "
#define _AT   64 // " @ "
#define _LBR  91 // " [ "
#define _ANTI 92 // " \ "
#define _RBR  93 // " ] "
#define _UN   95 // " _ "
#define _0 48
#define _1 49
#define _2 50
#define _3 51
#define _4 52
#define _5 53
#define _6 54
#define _7 55
#define _8 56
#define _9 57
#define _A 65
#define _B 66
#define _C 67
#define _D 68
#define _E 69
#define _F 70
#define _G 71
#define _H 72
#define _I 73
#define _J 74
#define _K 75
#define _L 76
#define _M 77
#define _N 78
#define _O 79
#define _P 80
#define _Q 81
#define _R 82
#define _S 83
#define _T 84
#define _U 85
#define _V 86
#define _W 87
#define _X 88
#define _Y 89
#define _Z 90
#define _a 97
#define _b 98
#define _c 99
#define _d 100
#define _e 101
#define _f 102
#define _g 103
#define _h 104
#define _i 105
#define _j 106
#define _k 107
#define _l 108
#define _m 109
#define _n 110
#define _o 111
#define _p 112
#define _q 113
#define _r 114
#define _s 115
#define _t 116
#define _u 117
#define _v 118
#define _w 119
#define _x 120
#define _y 121
#define _z 122

// — Core Logic Helpers —

#define print_char(i) texture(FONT_TEXTURE, u + vec2(float(i) - float(x) / FONT_SPACING + FONT_SPACING / 8., 15. - float(i) / 16.) / 16.).r

#define makeStr(func_name) \ 
float func_name(vec2 u) { \
    if (u.x < 0. || abs(u.y - .03) > .03) return 0.; \ 
    int str[] = int[](

#define _end 0); \
    int x = int(u.x * 16. * FONT_SPACING); \
    if (x >= str.length() - 1) return 0.; \
    return print_char(str[x]); \
}

// — String Definitions —
makeStr(printGodot) _G _o _d _o _t __ _V _e _r _s _i _o _n _end

void fragment() {
// Godot uses SCREEN_PIXEL_SIZE to simulate iResolution
vec2 iResolution = 1.0 / SCREEN_PIXEL_SIZE;
vec2 uv = FRAGCOORD.xy / iResolution.y;
vec3 col = vec3(0.0);

// Initial positioning
uv.y -= 0.9;
uv = uv * 0.8 - vec2(0.02, 0.0);

// Color definitions translated from Shadertoy
vec3 orange = (1.0 + cos(uv.y * 12.0 + 0.7 + vec3(0.0, 1.0, 2.0)));

// 1. Static Text
col += orange * printGodot(uv * 0.8);

COLOR = vec4(col, 1.0);

}

The problem comes at the line

makeStr(printGodot) _G _o _d _o _t __ _V _e _r _s _i _o _n _end

with the error as: Expected a ‘{‘, nothing I’ve done with my limited & augmented understanding has come on a solution, and made the issue more convoluted.

I’m putting this out there for someone with a greater understanding of Godot shader code to help me make sense of and learn a little more about shaders.

edits: cleanup code and language. The primary issue is the complex makeStr and _end defines, I guess thats not allowed so a simpler method must be refactored in.

Doing a bit of rearranging and rethinking yields:

class_name WTT
extends Shader

const defs:Dictionary[String,int] = {
	" ": 32, #define __    32 // "   "
	"!": 33, #define _EX   33 // " ! "
	"E": 69, #define _E 69
	"S": 83, #define _S 83
	"T": 84, #define _T 84
}

const coded:StringName = r'''
shader_type canvas_item;

uniform sampler2D FONT_TEXTURE : filter_linear;
uniform float FONT_SPACING = 2.0;
uniform int MESSAGE[16];
uniform int MESSAGE_ACTUAL;

#define print_char(i) \
	texture(FONT_TEXTURE, u + vec2(float(i) - float(x) / FONT_SPACING + FONT_SPACING / 8., 15. - float(i) / 16.) / 16.).r
	
float render_message(vec2 u) {
	if (u.x < 0. || abs(u.y - .03) > .03) return 0.;
	int x = int(u.x * 16. * FONT_SPACING);
	if (x >= MESSAGE_ACTUAL - 1) return 0.;
	return print_char(MESSAGE[x]);
}

void fragment() {
	// Godot uses SCREEN_PIXEL_SIZE to simulate iResolution
	vec2 iResolution = 1.0 / SCREEN_PIXEL_SIZE;
	vec2 uv = FRAGCOORD.xy / iResolution.y;
	vec3 col = vec3(0.0);

	// Initial positioning
	uv.y -= 0.9;
	uv = uv * 0.8 - vec2(0.02, 0.0);

	// Color definitions translated from Shadertoy
	vec3 orange = (1.0 + cos(uv.y * 12.0 + 0.7 + vec3(0.0, 1.0, 2.0)));

	// Static Text
	col += orange * render_message(uv * 0.8);

	COLOR = vec4(col, 1.0);
}
'''

const FONT_TEXTURE:StringName = &"FONT_TEXTURE"
const font_path:StringName = &"res://local/resources/codepage12.png"
const MESSAGE:StringName = &"MESSAGE"
const MESSAGE_ACTUAL:StringName = &"MESSAGE_ACTUAL"

var _arrayed:Array[int]

func _init(m:ShaderMaterial=null, w:String="T E S T !") -> void:
	set_code(coded)
	_arrayed = _to_arrayed(w)
	on_material(m)
	prints(_arrayed, _arrayed.size(), font_path)

static func _to_arrayed(w:String) -> Array[int]:
	var a:Array[int]
	a.resize(256)
	var spl:PackedStringArray = w.split("")
	for i in range(spl.size()):
		a[i] = defs.get(spl[i], 0)
	return a

func on_material(m:ShaderMaterial) -> void:
    if m:
	    m.set_shader_parameter(MESSAGE, _arrayed)
	    m.set_shader_parameter(MESSAGE_ACTUAL, _arrayed.size())
	    m.set_shader_parameter(FONT_TEXTURE, load(font_path))

But this only results in:

Its progress, but not the end result and is just jank to boot. Any suggestions appreciated.

@normalized likes shaders.

Where’s the texture?

This hackery is not a good learning material :smile:

The error from your initial post is caused by difference in array definition syntax between GLSL and Godot’s shading language.

In GLSL it’s:

const int[] foo = int[](1, 2, 3);

While in Godot it’s:

const int[] foo = {1, 2, 3};

That’s why Godot is complaining that it expects {

So when you change that inside makeStr macro, The error should go away:

float func_name(vec2 u) { \
    if (u.x < 0. || abs(u.y - .03) > .03) return 0.; \ 
    int str[] = {

#define _end 0}; \
    int x = int(u.x * 16. * FONT_SPACING); \
    if (x >= str.length() - 1) return 0.; \
    return print_char(str[x]); \
}
1 Like

Ok, thats one thing I did not see. I did try fiddle with that momentarily, but didn’t get it. Its obvious now when its shown to me.

Just a copy the texture at shadertoy

1 Like

However, running that doesn’t quite get there

SHADER ERROR: Global non-constant variables are not supported. Expected ‘const’ keyword before constant definition.
at: (null) (:106)

105 |
E 106->  int str
 = {
107 |
108 |
109 |
110 |
111 |
112 |
113 |  float printGodot(vec2 u) { 	if (u.x < 0. || abs(u.y - .03) > .03) return 0.; \   71   111   100   111   116   32   86   101   114   115   105   111   110   0}; 	int x = int(u.x * 16. * FONT_SPACING); 	if (x >= str.length() - 1) return 0.; 	return  texture(FONT_TEXTURE, u + vec2(float(str
) - float(x) / FONT_SPACING + FONT_SPACING / 8., 15. - float(str
) / 16.) / 16.).r ; }

My advice is to unroll the main macros so you can see the actual code in one piece. That way it’ll be much easier to spot syntax incompatibilities.

1 Like