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);
}
Live Preview
Tags
Debug, print
The shader code and all code snippets in this post are under CC0 license and can be used freely without the author's permission. Images and videos, and assets depicted in those, do not fall under this license. For more info, see our License terms.

More from Elephando

Related shaders

guest

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments