Print floats for debugging
Print for shaders, you can print float number to display in screen space, object space or world space.
This shader is adapted from Freya’s shader for Unity. It can be helpful if you are learning shaders.
How to use this shader :
Create a shader include file print.gdshaderinc containing all the code, and include it in any shader using #include "print.gdshaderinc". Then, use functions below :
For canvas item and spatial shader :
DrawNumberAtPxPos :
DrawNumberAtPxPos(vec2 fragCoord, vec2 pixelPosition, float value, float fontScale, int decimalCount)
Display value at screen space position pixelPosition.
You must provide the fragCoord parameter :
vec2 fragCoord = FRAGCOORD.xy;
For spatial Shader only :
DrawNumberAtLocalPos :
DrawNumberAtLocalPos(vec2
fragCoord, vec3 localPosition, float value, float fontScale, int
decimalCount, mat4 mat_obj_2_clip, vec2 screen_resolution)
Display value at local position localPosition relative to the mesh.
You need to give the object-to-clip-space matrix and the screen resolution :
mat4 mat_world_2_clip = PROJECTION_MATRIX * VIEW_MATRIX;
vec2 screen_resolution = VIEWPORT_SIZE;
DrawNumberAtWorldPos :
DrawNumberAtWorldPos(vec2 fragCoord, vec3 worldPosition, float value, float fontScale, int decimalCount, mat4 mat_world_2_clip, vec2 screen_resolution)
Display value at world position worldPosition.
You must provide the world-to-clip-space matrix and the screen resolution :
mat4 mat_world_2_clip = PROJECTION_MATRIX * VIEW_MATRIX;
vec2 screen_resolution = VIEWPORT_SIZE;
To be placed in the shader include file :
// Readapted from https://gist.github.com/FreyaHolmer/71717be9f3030c1b0990d3ed1ae833e3
// for Godot Engine
// digit rendering
const uint dBits[5] = {
3959160828u, //111 010 111 111 101 111 111 111 111 111 00
2828738996u, //101 010 001 001 101 100 100 001 101 101 00
2881485308u, //101 010 111 011 111 111 111 001 111 111 00
2853333412u, //101 010 100 001 001 001 101 001 101 001 00
3958634981u //111 010 111 111 001 111 111 001 111 001 01
};
const uint po10[] = {1u, 10u, 100u, 1000u, 10000u, 100000u, 1000000u, 10000000u, 100000000u, 1000000000u, 10000000000u};
float DrawDigit(const ivec2 px, const int digit)
{
if (px.x < 0 || px.x > 2 || px.y < 0 || px.y > 4)
return 0.; // pixel out of bounds
const int xId = (digit == -1) ? 18 : 31 - (3 * digit + px.x);
return (int(dBits[4 - px.y]) & 1 << xId) != 0 ? 1. : 0.;
}
// indexed like: XXX.0123
void GetDecimalSymbolAt(const float v, const int i, const int decimalCount, out int symbol, out float opacity)
{
// hide if outside the decimal range (change 6 to 9) (lack of precision at the end)
if (i > min(decimalCount - 1, 9))
{
symbol = 0;
opacity = 0.;
return;
}
// get the i:th decimal
const float scale = float(po10[i + 1]);
const float scaledF = abs(v) * scale;
symbol = int(scaledF) % 10;
// fade trailing zeroes
opacity = (fract(scaledF / 10.) > 0.) ? 1. : 0.3;
}
// indexed like: 210.XXX
void GetIntSymbolAt(const float v, const int i, out int symbol, out float opacity)
{
// don't render more than 9 digits
if (i <= 9)
{
const uint scale = po10[i];
const float vAbs = abs(v);
// digits
if (vAbs >= float(scale))
{
const int it = int(floor(vAbs));
const int rem = it / int(scale);
symbol = rem % 10;
opacity = 1.;
return;
}
// minus symbol
if ((v < 0.) && (vAbs * 10. >= float(scale)))
{
symbol = -1;
opacity = 1.;
return;
}
}
// leading zeroes
symbol = 0;
opacity = 0.;
}
// Get the digit at the given index of a floating point number
// with -45.78, then with a given dIndex:
// [-3] = - (digit -1)
// [-2] = 4
// [-1] = 5
// [ 0] = . (digit 10)
// [ 1] = 7
// [ 2] = 8
void GetSymbolAtPositionInFloat(const float number, const int dIndex, const int decimalCount, out int symbol, out float opacity)
{
opacity = 1.;
if (dIndex == 0)
symbol = 10; // period
else if (dIndex > 0)
GetDecimalSymbolAt(number, dIndex - 1, decimalCount, symbol, opacity);
else
GetIntSymbolAt(number, -dIndex - 1, symbol, opacity);
}
// Given a pixel coordinate fragCoord, draws a number at pxPos (fontScale = 2, decimalCount = 3)
float DrawNumberAtPxPos(vec2 fragCoord, vec2 pxPos, float number, float fontScale, int decimalCount)
{
// Adapted for Godot (if not, digits are upside down)
vec2 pxDiff = pxPos-fragCoord;
pxDiff.x *= -1.;
ivec2 p = ivec2(floor(pxDiff / fontScale));
// p.y += 0; // 0 = bottom aligned, 2 = vert. center aligned, 5 = top aligned
// p.x += 0; // 0 = integers are directly to the left, decimal separator and decimals, to the right
if (p.y < 0 || p.y > 4)
return 0.; // out of bounds vertically
// shift placement to make it tighter around the decimal separator
float shift = 0.;
if (p.x > 1) // decimal digits
p.x += 1;
else if (p.x < 0) // integer digits
{
p.x += -3;
shift = -2.;
}
const int SEP = 4; // separation between characters
int dIndex = int(floor(float(p.x / SEP))); // the digit index to read
float opacity;
int digit;
GetSymbolAtPositionInFloat(number, dIndex, decimalCount, /*out*/ digit, /*out*/ opacity);
ivec2 pos = ivec2(dIndex * SEP + int(shift), 0);
return opacity * DrawDigit(p - pos, digit);
}
// btw this might not work on all platforms, it might be Y-flipped or whatever!
vec2 ClipToPixel(vec4 clip, vec2 screen_resolution)
{
// Adapted for Godot (-clip.y => clip.y)
vec2 ndc = vec2(clip.x, clip.y) / clip.w;
ndc = (ndc + 1.) / 2.;
return ndc * screen_resolution;
}
vec2 LocalToPixel(mat4 mat_obj_2_clip, vec3 locPos, vec2 screen_resolution) {
vec4 clip = mat_obj_2_clip * vec4(locPos, 1);
return ClipToPixel(clip, screen_resolution);
}
vec2 WorldToPixel(mat4 mat_world_2_clip, vec3 wPos, vec2 screen_resolution) {
vec4 clip = mat_world_2_clip * (vec4(wPos, 1));
return ClipToPixel(clip, screen_resolution);
}
float DrawNumberAtLocalPos(vec2 fragCoord, vec3 localPos, float number, float scale, int decimalCount,mat4 mat_obj_2_clip, vec2 screen_resolution)
{
vec2 pxPos = LocalToPixel(mat_obj_2_clip, localPos, screen_resolution);
return DrawNumberAtPxPos(fragCoord, pxPos, number, scale, decimalCount);
}
float DrawNumberAtWorldPos(vec2 fragCoord, vec3 worldPos, float number, float scale, int decimalCount, mat4 mat_world_2_clip, vec2 screen_resolution)
{
vec2 pxPos = WorldToPixel(mat_world_2_clip, worldPos, screen_resolution);
return DrawNumberAtPxPos(fragCoord, pxPos, number, scale, decimalCount);
}
Example for spatial shader :
shader_type spatial;
render_mode unshaded;
#include "print_debug.gdshaderinc"
/** In which space we print the value */
uniform int _DrawMode : hint_enum("Screen", "Local", "World") = 1;
/** What type of value we print */
uniform int _DrawType : hint_enum("Float", "Matrix") = 0;
uniform float _FloatToDraw = 1234.123;
uniform mat4 _MatrixToDraw = mat4(1.);
/** Position in unit world for local and world space */
uniform vec3 _Position = vec3(0.);
/** Screen position in pixel for screen space */
uniform vec2 _ScreenPosition = vec2(500., 250.);
/** Decimal precision, above 7 is useless */
uniform int _DecimalCount : hint_range(0, 10, 1) = 3;
/** Font scale */
uniform float _Scale : hint_range(1.0, 10.0, 0.1) = 3.;
// A little wrapper
struct ProjectionInfo{
vec2 screen_res; // VIEWPORT_SIZE;
vec2 px_coord; // FRAGCOORD.xy;
mat4 world_to_clip; // PROJECTION_MATRIX * VIEW_MATRIX;
mat4 obj_to_clip; // PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX;
};
float PrintNumberAt(float number, ProjectionInfo proj_info){
mat4 obj_to_clip = proj_info.obj_to_clip;
mat4 world_to_clip = proj_info.world_to_clip;
vec2 screen_res = proj_info.screen_res;
vec2 px_coord = proj_info.px_coord;
switch(_DrawMode){
case 0: // Pixel
return DrawNumberAtPxPos(px_coord, _ScreenPosition, number, _Scale, _DecimalCount);
case 1: // Local
return DrawNumberAtLocalPos(px_coord, _Position, number, _Scale, _DecimalCount, obj_to_clip, screen_res);
case 2: // World
return DrawNumberAtWorldPos(px_coord, _Position, number, _Scale, _DecimalCount, world_to_clip, screen_res);
}
}
float PrintMatrixAt(mat4 matrix, ProjectionInfo proj_info){
mat4 obj_to_clip = proj_info.obj_to_clip;
mat4 world_to_clip = proj_info.world_to_clip;
vec2 screen_res = proj_info.screen_res;
vec2 px_coord = proj_info.px_coord;
float mask = 0.;
float step_x = 0.25 * _Scale / 3.;
float step_y = 0.15 * _Scale / 3.;
float screen_scaler = 350. * _Scale / 3.;
for (int i = 0; i <= 3; i++){
for (int j = 0; j <= 3; j++){
float v = matrix[i][j];
vec3 local_coord = vec3(-step_x + float(j) * step_x, -step_y - float(i) * step_y, 0.);
switch(_DrawMode){
case 0: // Pixel
mask += DrawNumberAtPxPos(px_coord, _ScreenPosition + screen_scaler * local_coord.xy, v, _Scale, _DecimalCount);
break;
case 1: // Local
mask += DrawNumberAtLocalPos(px_coord, _Position + local_coord, v, _Scale, _DecimalCount, obj_to_clip, screen_res);
break;
case 2: // World
mask += DrawNumberAtWorldPos(px_coord, _Position + local_coord, v, _Scale, _DecimalCount, world_to_clip, screen_res);
break;
}
}
}
return mask;
}
void fragment(){
ProjectionInfo proj_info;
proj_info.screen_res = VIEWPORT_SIZE;
proj_info.px_coord = FRAGCOORD.xy;
proj_info.world_to_clip = PROJECTION_MATRIX * VIEW_MATRIX;
proj_info.obj_to_clip = PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX;
float numberMask = 0.;
switch(_DrawType){
case 0: // Float
numberMask = PrintNumberAt(_FloatToDraw, proj_info);
break;
case 1: // Matrix
numberMask = PrintMatrixAt(_MatrixToDraw, proj_info);
break;
}
ALBEDO.rgb = vec3(numberMask);
}
Shader code
shader_type spatial;
render_mode unshaded;
//#include "print_debug.gdshaderinc"
// Readapted from https://gist.github.com/FreyaHolmer/71717be9f3030c1b0990d3ed1ae833e3
// for Godot Engine
// digit rendering
const uint dBits[5] = {
3959160828u, //111 010 111 111 101 111 111 111 111 111 00
2828738996u, //101 010 001 001 101 100 100 001 101 101 00
2881485308u, //101 010 111 011 111 111 111 001 111 111 00
2853333412u, //101 010 100 001 001 001 101 001 101 001 00
3958634981u //111 010 111 111 001 111 111 001 111 001 01
};
const uint po10[] = {1u, 10u, 100u, 1000u, 10000u, 100000u, 1000000u, 10000000u, 100000000u, 1000000000u, 10000000000u};
float DrawDigit(const ivec2 px, const int digit)
{
if (px.x < 0 || px.x > 2 || px.y < 0 || px.y > 4)
return 0.; // pixel out of bounds
const int xId = (digit == -1) ? 18 : 31 - (3 * digit + px.x);
return (int(dBits[4 - px.y]) & 1 << xId) != 0 ? 1. : 0.;
}
// indexed like: XXX.0123
void GetDecimalSymbolAt(const float v, const int i, const int decimalCount, out int symbol, out float opacity)
{
// hide if outside the decimal range (change 6 to 9) (lack of precision at the end)
if (i > min(decimalCount - 1, 9))
{
symbol = 0;
opacity = 0.;
return;
}
// get the i:th decimal
const float scale = float(po10[i + 1]);
const float scaledF = abs(v) * scale;
symbol = int(scaledF) % 10;
// fade trailing zeroes
opacity = (fract(scaledF / 10.) > 0.) ? 1. : 0.3;
}
// indexed like: 210.XXX
void GetIntSymbolAt(const float v, const int i, out int symbol, out float opacity)
{
// don't render more than 9 digits
if (i <= 9)
{
const uint scale = po10[i];
const float vAbs = abs(v);
// digits
if (vAbs >= float(scale))
{
const int it = int(floor(vAbs));
const int rem = it / int(scale);
symbol = rem % 10;
opacity = 1.;
return;
}
// minus symbol
if ((v < 0.) && (vAbs * 10. >= float(scale)))
{
symbol = -1;
opacity = 1.;
return;
}
}
// leading zeroes
symbol = 0;
opacity = 0.;
}
// Get the digit at the given index of a floating point number
// with -45.78, then with a given dIndex:
// [-3] = - (digit -1)
// [-2] = 4
// [-1] = 5
// [ 0] = . (digit 10)
// [ 1] = 7
// [ 2] = 8
void GetSymbolAtPositionInFloat(const float number, const int dIndex, const int decimalCount, out int symbol, out float opacity)
{
opacity = 1.;
if (dIndex == 0)
symbol = 10; // period
else if (dIndex > 0)
GetDecimalSymbolAt(number, dIndex - 1, decimalCount, symbol, opacity);
else
GetIntSymbolAt(number, -dIndex - 1, symbol, opacity);
}
// Given a pixel coordinate fragCoord, draws a number at pxPos (fontScale = 2, decimalCount = 3)
float DrawNumberAtPxPos(vec2 fragCoord, vec2 pxPos, float number, float fontScale, int decimalCount)
{
// Adapted for Godot (if not, digits are upside down)
vec2 pxDiff = pxPos-fragCoord;
pxDiff.x *= -1.;
ivec2 p = ivec2(floor(pxDiff / fontScale));
// p.y += 0; // 0 = bottom aligned, 2 = vert. center aligned, 5 = top aligned
// p.x += 0; // 0 = integers are directly to the left, decimal separator and decimals, to the right
if (p.y < 0 || p.y > 4)
return 0.; // out of bounds vertically
// shift placement to make it tighter around the decimal separator
float shift = 0.;
if (p.x > 1) // decimal digits
p.x += 1;
else if (p.x < 0) // integer digits
{
p.x += -3;
shift = -2.;
}
const int SEP = 4; // separation between characters
int dIndex = int(floor(float(p.x / SEP))); // the digit index to read
float opacity;
int digit;
GetSymbolAtPositionInFloat(number, dIndex, decimalCount, /*out*/ digit, /*out*/ opacity);
ivec2 pos = ivec2(dIndex * SEP + int(shift), 0);
return opacity * DrawDigit(p - pos, digit);
}
// btw this might not work on all platforms, it might be Y-flipped or whatever!
vec2 ClipToPixel(vec4 clip, vec2 screen_resolution)
{
// Adapted for Godot (-clip.y => clip.y)
vec2 ndc = vec2(clip.x, clip.y) / clip.w;
ndc = (ndc + 1.) / 2.;
return ndc * screen_resolution;
}
vec2 LocalToPixel(mat4 mat_obj_2_clip, vec3 locPos, vec2 screen_resolution) {
vec4 clip = mat_obj_2_clip * vec4(locPos, 1);
return ClipToPixel(clip, screen_resolution);
}
vec2 WorldToPixel(mat4 mat_world_2_clip, vec3 wPos, vec2 screen_resolution) {
vec4 clip = mat_world_2_clip * (vec4(wPos, 1));
return ClipToPixel(clip, screen_resolution);
}
float DrawNumberAtLocalPos(vec2 fragCoord, vec3 localPos, float number, float scale, int decimalCount,mat4 mat_obj_2_clip, vec2 screen_resolution)
{
vec2 pxPos = LocalToPixel(mat_obj_2_clip, localPos, screen_resolution);
return DrawNumberAtPxPos(fragCoord, pxPos, number, scale, decimalCount);
}
float DrawNumberAtWorldPos(vec2 fragCoord, vec3 worldPos, float number, float scale, int decimalCount, mat4 mat_world_2_clip, vec2 screen_resolution)
{
vec2 pxPos = WorldToPixel(mat_world_2_clip, worldPos, screen_resolution);
return DrawNumberAtPxPos(fragCoord, pxPos, number, scale, decimalCount);
}
/** In which space we print the value */
uniform int _DrawMode : hint_enum("Screen", "Local", "World") = 1;
/** What type of value we print */
uniform int _DrawType : hint_enum("Float", "Matrix") = 0;
uniform float _FloatToDraw = 1234.123;
uniform mat4 _MatrixToDraw = mat4(1.);
/** Position in unit world for local and world space */
uniform vec3 _Position = vec3(0.);
/** Screen position in pixel for screen space */
uniform vec2 _ScreenPosition = vec2(500., 250.);
/** Decimal precision, above 7 is useless */
uniform int _DecimalCount : hint_range(0, 10, 1) = 3;
/** Font scale */
uniform float _Scale : hint_range(1.0, 10.0, 0.1) = 3.;
// A little wrapper
struct ProjectionInfo{
vec2 screen_res; // VIEWPORT_SIZE;
vec2 px_coord; // FRAGCOORD.xy;
mat4 world_to_clip; // PROJECTION_MATRIX * VIEW_MATRIX;
mat4 obj_to_clip; // PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX;
};
float PrintNumberAt(float number, ProjectionInfo proj_info){
mat4 obj_to_clip = proj_info.obj_to_clip;
mat4 world_to_clip = proj_info.world_to_clip;
vec2 screen_res = proj_info.screen_res;
vec2 px_coord = proj_info.px_coord;
switch(_DrawMode){
case 0: // Pixel
return DrawNumberAtPxPos(px_coord, _ScreenPosition, number, _Scale, _DecimalCount);
case 1: // Local
return DrawNumberAtLocalPos(px_coord, _Position, number, _Scale, _DecimalCount, obj_to_clip, screen_res);
case 2: // World
return DrawNumberAtWorldPos(px_coord, _Position, number, _Scale, _DecimalCount, world_to_clip, screen_res);
}
}
float PrintMatrixAt(mat4 matrix, ProjectionInfo proj_info){
mat4 obj_to_clip = proj_info.obj_to_clip;
mat4 world_to_clip = proj_info.world_to_clip;
vec2 screen_res = proj_info.screen_res;
vec2 px_coord = proj_info.px_coord;
float mask = 0.;
float step_x = 0.25 * _Scale / 3.;
float step_y = 0.15 * _Scale / 3.;
float screen_scaler = 350. * _Scale / 3.;
for (int i = 0; i <= 3; i++){
for (int j = 0; j <= 3; j++){
float v = matrix[i][j];
vec3 local_coord = vec3(-step_x + float(j) * step_x, -step_y - float(i) * step_y, 0.);
switch(_DrawMode){
case 0: // Pixel
mask += DrawNumberAtPxPos(px_coord, _ScreenPosition + screen_scaler * local_coord.xy, v, _Scale, _DecimalCount);
break;
case 1: // Local
mask += DrawNumberAtLocalPos(px_coord, _Position + local_coord, v, _Scale, _DecimalCount, obj_to_clip, screen_res);
break;
case 2: // World
mask += DrawNumberAtWorldPos(px_coord, _Position + local_coord, v, _Scale, _DecimalCount, world_to_clip, screen_res);
break;
}
}
}
return mask;
}
void fragment(){
ProjectionInfo proj_info;
proj_info.screen_res = VIEWPORT_SIZE;
proj_info.px_coord = FRAGCOORD.xy;
proj_info.world_to_clip = PROJECTION_MATRIX * VIEW_MATRIX;
proj_info.obj_to_clip = PROJECTION_MATRIX * VIEW_MATRIX * MODEL_MATRIX;
float numberMask = 0.;
switch(_DrawType){
case 0: // Float
numberMask = PrintNumberAt(_FloatToDraw, proj_info);
break;
case 1: // Matrix
numberMask = PrintMatrixAt(_MatrixToDraw, proj_info);
break;
}
ALBEDO.rgb = vec3(numberMask);
}



