-
-
Save bestknighter/660e6a53cf6a6643618d8531f962be2c to your computer and use it in GitHub Desktop.
// This gist can be found at https://gist.github.com/bestknighter/660e6a53cf6a6643618d8531f962be2c | |
// I modified Aras Pranckevičius's awesome shader code to be able to handle Infinite and Not A Number numbers | |
// Tested on Unity 2023.1.0a15 with ShaderGraph 15.0.1. | |
// Quick try at doing a "print value" node for Unity ShaderGraph. | |
// | |
// Use with CustomFunction node, with two inputs: | |
// - Vector1 Value, the value to display, | |
// - Vector2 UV, the UVs of area to display at. | |
// And one output: | |
// - Vector4 Color, the color. | |
// Function name is DoDebug. | |
// "print in shader" based on this excellent ShaderToy by @P_Malin: | |
// https://www.shadertoy.com/view/4sBSWW | |
float DigitBin(const int x) | |
{ | |
return x==0?480599.0:x==1?139810.0:x==2?476951.0:x==3?476999.0:x==4?350020.0:x==5?464711.0:x==6?464727.0:x==7?476228.0:x==8?481111.0:x==9?481095.0:0.0; | |
} | |
float DigitLet(const int x) | |
{ | |
// 1=I, 2=n, 3=F, 4=a | |
return x==1?467495.0:x==2?1877.0:x==3?463633.0:x==4?30069.0:0.0; | |
} | |
bool isnan_NonOptimizableAway(const float fValue) { | |
return ((asuint(fValue) & 0x7FFFFFFF) > 0x7F800000); | |
} | |
bool isinf_NonOptimizableAway(const float fValue) { | |
return ((asuint(fValue) & 0x7FFFFFFF) == 0x7F800000); | |
} | |
float GetDigit(const float fValue, const float fDigitIndex) { | |
float res = fValue; | |
for (int i = 0; i < fDigitIndex; i++) { | |
res = res / 10; | |
} | |
for (int i = 0; i > fDigitIndex; i--) { | |
res = res * 10; | |
} | |
return trunc(res % 10); | |
} | |
float PrintValue(float2 vStringCoords, float fValue, float fMaxDigits, float fDecimalPlaces) | |
{ | |
if ((vStringCoords.y < 0.0) || (vStringCoords.y >= 1.0)) | |
return 0.0; | |
bool bNeg = (fValue < 0.0); | |
bool bNan = isnan_NonOptimizableAway(fValue); | |
bool bInf = isinf_NonOptimizableAway(fValue); | |
if(bInf) fValue = 123.0; | |
else if(bNan) fValue = 242.0; | |
fValue = abs(fValue); | |
float fLog10Value = log2(fValue) / log2(10.0); | |
float fBiggestIndex = max(floor(fLog10Value), 0.0); | |
float fDigitIndex = fMaxDigits - floor(vStringCoords.x); | |
if (bInf || bNan) fDigitIndex += 2.0f; // Offset so Inf and nan are centralized | |
float fCharBin = 0.0; | |
if (fDigitIndex > (-fDecimalPlaces - 1.01)) | |
{ | |
if(fDigitIndex > fBiggestIndex) | |
{ | |
if(fDigitIndex < (fBiggestIndex+1.5)) { | |
if (bNeg) fCharBin = 1792.0; // Minus sign | |
else if(bInf) fCharBin = 10016.0; // Plus sign | |
} | |
} | |
else | |
{ | |
if(fDigitIndex == -1.0) | |
{ | |
if(fDecimalPlaces > 0.0 && !(bInf || bNan)) fCharBin = 2.0; | |
} | |
else | |
{ | |
float fReducedRangeValue = fValue; | |
if(fDigitIndex < 0.0) { fReducedRangeValue = frac( fValue ); fDigitIndex += 1.0; } | |
//float fDigitValue = (abs(fReducedRangeValue / (pow(10.0, fDigitIndex)))); // Without workaround | |
float fDigitValue = GetDigit(abs(fReducedRangeValue), fDigitIndex); // Workaround for precision issues found on more recent versions of Unity | |
int arg = int(floor(fmod(fDigitValue, 10.0))); | |
fCharBin = (bNan || bInf) ? DigitLet(arg) : DigitBin(arg); | |
} | |
} | |
} | |
return floor(fmod((fCharBin / pow(2.0, floor(frac(vStringCoords.x) * 4.0) + (floor(vStringCoords.y * 5.0) * 4.0))), 2.0)); | |
} | |
float PrintValue(const in float2 fragCoord, const in float2 vPixelCoords, const in float2 vFontSize, const in float fValue, const in float fMaxDigits, const in float fDecimalPlaces) | |
{ | |
float2 vStringCharCoords = (fragCoord.xy - vPixelCoords) / vFontSize; | |
return PrintValue( vStringCharCoords, fValue, fMaxDigits, fDecimalPlaces ); | |
} | |
void DoDebug_float(float val, float2 uv, int decimalDigits, out float4 res) | |
{ | |
res = float4(0.3,0.2,0.1,1); | |
res = PrintValue(uv*200, float2(10,100), float2(8,15), val, 10, decimalDigits); | |
} |
@bestknighter Fantastic helper node but unfortunately on the latest Unity 6 the dot is missing completely in the output, do you know what could cause that?
@bestknighter Fantastic helper node but unfortunately on the latest Unity 6 the dot is missing completely in the output, do you know what could cause that?
Hey @lukasreuter! Thank you! Do you have any screen capture of an example? I haven't played around with Unity 6 yet and that probably won't happen soon. But I can help you reason about it.
What I can already tell you in advance is that fCharBin is a 4-bit wide binary mapping of bits representing a bitmap of the character. For example, on line 76 we have fCharBin = 10016.0; // Plus sign
. 10016 in binary is 0010 0111 0010 0000
, which stacked gives us
0010 = ▯▯▮▯
0111 = ▯▮▮▮
0010 = ▯▯▮▯
0000 = ▯▯▯▯
a plus sign. The dot is on line 78: if(fDecimalPlaces > 0.0 && !(bInf || bNan)) fCharBin = 2.0;
. So, if the only thing wrong is the dot, then for some reason that assignment is never happening. My initial naïve guess is that the fDecimalPlaces input is somewhere somehow being set to 0 or a negative number.
Thank you for that explanation, I was definitely wondering where the comma comes from and how it all lines up to get the final character rendered. I found out what the reason for the rendering artefacts is, though the solution seems very weird.
I noticed that it was not actually the Unity version that caused the issue but it would only happen on macOS (running with metal) and would look like this:
Notice the minus sign looking weird and the 4 and 7 both have a pixel shifted. Armed with this knowledge I was sure it had to be a rounding/precision error somewhere as forcing the if you mentioned changed nothing.
So I tried replacing basically every builtin function like fmod, floor, frac and pow. Nothing changed until I attempted to replace the pow builtin and it completely scrambled the output.
Without any further progress I tried replacing the 2.0 in the pow on line 91 with 1.999999 and it worked! 🎉
You can replicate the rendering artefacts on Windows by changing the value to 2.000001 so I am assuming there must be some kind of precision issue where the pow function works slightly differently on DirectX vs Metal.
But to be honest this fix feels very hacky so maybe you have a better idea how to properly fix it.
@lukasreuter Impressive! Congratulations on finding that workaround!
This would not be the first nor the fifth time I had precision issues supporting this gist lol
I believe we might have enough reproducibility to dig deeper for the correct fix. Either that or to file a bug report. I'm inclined to go with the latter.
Usage:

ATTENTION:
There has been found a precision error with some HLSL functions. You can change which line is commented in the code to see if your version is affected. I updated to add a workaround. It is NOT perfect (it also is a victim of said precision bug) but it works better than without the workaround. If you are experiencing such issue, that is.