Last active
November 1, 2024 23:31
-
-
Save FreyaHolmer/71717be9f3030c1b0990d3ed1ae833e3 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!
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/////////////////////////////////////////////////////////////////////////////// | |
// 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); | |
} |
sure! feel free to treat this code as an MIT license with attribution
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is really nice! Any chance you'd be willing to use the MIT License or one of the CC ones? Same spirit, and the lawyers won't hassle us.