Skip to content

Instantly share code, notes, and snippets.

@savage69kr
Forked from FreyaHolmer/GpuPrinter.cginc
Created August 20, 2024 03:45
Show Gist options
  • Save savage69kr/76b56e756aac06daffc91f38367bb4c9 to your computer and use it in GitHub Desktop.
Save savage69kr/76b56e756aac06daffc91f38367bb4c9 to your computer and use it in GitHub Desktop.
A unity shader .cginc to draw numbers in the fragment shader - see the first comment below for example usage!
///////////////////////////////////////////////////////////////////////////////
// ABOUT: A unity Shader .cginc to draw numbers in the fragment shader
// AUTHOR: Freya Holmér
// LICENSE: Use for whatever, commercial or otherwise!
// Don't hold me liable for issues though
// But pls credit me if it works super well <3
// LIMITATIONS: There's some precision loss beyond 3 decimal places
// CONTRIBUTORS: yes please! if you know a more precise way to get
// decimal digits then pls lemme know!
// GetDecimalSymbolAt() could use some more love/precision
// These are the main drawing functions:
// - returns white text on black background (though trailing zeroes are gray)
// - billboarded to always face the camera
// - you can get pxCoord from the frag shader "SV_POSITION" input
float DrawNumberAtPxPos(float2 pxCoord, float2 pxPos, float number, float fontScale = 2, int decimalCount = 3);
float DrawNumberAtLocalPos(float2 pxCoord, float3 localPos, float number, float scale = 2, int decimalCount = 3);
float DrawNumberAtWorldPos(float2 pxCoord, float3 worldPos, float number, float scale = 2, int decimalCount = 3);
// digit rendering
static uint dBits[5] = {
3959160828,
2828738996,
2881485308,
2853333412,
3958634981
};
static uint po10[] = {1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000, 10000000000};
float DrawDigit(int2 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 (dBits[4 - px.y] & 1 << xId) != 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
if (i > min(decimalCount - 1, 6))
{
symbol = 0;
opacity = 0;
return;
}
// get the i:th decimal
const float scale = po10[i + 1];
const float scaledF = abs(v) * scale;
symbol = (int)(scaledF) % 10;
// fade trailing zeroes
opacity = (frac(scaledF / 10) != 0) ? 1 : 0.5;
}
// indexed like: 210.XXX
void GetIntSymbolAt(const float v, int i, out int symbol, out float opacity)
{
// don't render more than 9 digits
if (i <= 9)
{
const int scale = po10[i];
const float vAbs = abs(v);
// digits
if (vAbs >= scale)
{
const int it = floor(vAbs);
const int rem = it / scale;
symbol = rem % 10;
opacity = 1;
return;
}
// minus symbol
if ((v < 0) & (vAbs * 10 >= 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(float number, int dIndex, 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 pxCoord, draws a number at pxPos
float DrawNumberAtPxPos(float2 pxCoord, float2 pxPos, float number, float fontScale = 2, int decimalCount = 3)
{
int2 p = (int2)(floor((pxCoord - pxPos) / 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
const int dIndex = floor(p.x / SEP); // the digit index to read
float opacity;
int digit;
GetSymbolAtPositionInFloat(number, dIndex, decimalCount, /*out*/ digit, /*out*/ opacity);
const float2 pos = float2(dIndex * SEP + shift, 0);
return opacity * DrawDigit(p - pos, digit);
}
// btw this might not work on all platforms, it might be Y-flipped or whatever!
float2 ClipToPixel(float4 clip)
{
float2 ndc = float2(clip.x, -clip.y) / clip.w;
ndc = (ndc + 1) / 2;
return ndc * _ScreenParams.xy;
}
float2 LocalToPixel(float3 locPos) { return ClipToPixel(UnityObjectToClipPos(float4(locPos, 1))); }
float2 WorldToPixel(float3 wPos) { return ClipToPixel(UnityWorldToClipPos(float4(wPos, 1))); }
float DrawNumberAtLocalPos(float2 pxCoord, float3 localPos, float number, float scale = 2, int decimalCount = 3)
{
const float2 pxPos = LocalToPixel(localPos);
return DrawNumberAtPxPos(pxCoord, pxPos, number, scale, decimalCount);
}
float DrawNumberAtWorldPos(float2 pxCoord, float3 worldPos, float number, float scale = 2, int decimalCount = 3)
{
const float2 pxPos = WorldToPixel(worldPos);
return DrawNumberAtPxPos(pxCoord, pxPos, number, scale, decimalCount);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment