summaryrefslogtreecommitdiffstats
path: root/Shaders
diff options
context:
space:
mode:
Diffstat (limited to 'Shaders')
-rw-r--r--Shaders/TaSTT_lighting_template.cginc119
-rw-r--r--Shaders/TaSTT_template.shader3
-rw-r--r--Shaders/aa_sample_algorithm.py43
3 files changed, 117 insertions, 48 deletions
diff --git a/Shaders/TaSTT_lighting_template.cginc b/Shaders/TaSTT_lighting_template.cginc
index 48529b1..4fed35e 100644
--- a/Shaders/TaSTT_lighting_template.cginc
+++ b/Shaders/TaSTT_lighting_template.cginc
@@ -39,6 +39,7 @@ float4 BG_Smoothness_ST;
float4 BG_Emission_Mask_ST;
float Enable_Dithering;
+float AA_Amount;
sampler2D _Font_0x0000_0x1FFF;
float4 _Font_0x0000_0x1FFF_TexelSize;
@@ -501,6 +502,13 @@ bool f3ltf3(fixed3 a, fixed3 b)
a[2] < b[2];
}
+float prng(float2 v)
+{
+ float2 res2 = float2(cos(v.x * _Time[2]), sin(v.y * _Time[2]));
+ float res = dot(res2, res2) / 2;
+ return res * res;
+}
+
fixed4 frag(v2f i) : SV_Target
{
float2 uv = i.uv.zw;
@@ -510,22 +518,6 @@ fixed4 frag(v2f i) : SV_Target
uv.x = 1.0 - uv.x;
uv.y *= 2; // Text box has 2:1 aspect ratio
- if (Enable_Dithering) {
- // Add noise to UV.
- // Here, iddx and iddy tell us how big the current UV cell is with respect to
- // screen space (i.e. how many pixels wide it is).
- float iddx = ddx(uv.x);
- float iddy = ddy(uv.y);
- float noise = sin(_Time[3] + 1.0 / frac(iddx * uv.x + iddy * uv.y));
- uv.x += noise * iddx / 4.0;
- uv.y += noise * iddy / 4.0;
- // Too decaffeinated to figure out why: if we don't clamp to [.001, .999],
- // then faint outlines show up around the edge of the box. This is related
- // to the exterior transparency added in margin rounding.
- uv.x = max(0.001, min(0.999, uv.x));
- uv.y = max(0.001, min(0.999, uv.y));
- }
-
// Derived from github.com/pema99/shader-knowledge (MIT license).
if (unity_CameraProjection[2][0] != 0.0 ||
unity_CameraProjection[2][1] != 0.0) {
@@ -567,62 +559,95 @@ fixed4 frag(v2f i) : SV_Target
fixed4 text = fixed4(0, 0, 0, 0);
bool discard_text = false;
+
+ int letter = GetLetterParameter(uv_with_margin);
+
+ float texture_cols;
+ float texture_rows;
+ float2 letter_uv;
+ if (letter < 0xE000) {
+ texture_cols = 128.0;
+ texture_rows = 64.0;
+ letter_uv = GetLetter(uv_with_margin, letter, texture_cols, texture_rows, NCOLS, NROWS);
+ } else {
+ texture_cols = 8.0;
+ texture_rows = 8.0;
+ letter_uv = GetLetter(uv_with_margin, letter, texture_cols, texture_rows, 8, 4);
+ }
+
+ if (letter_uv.x == 0 && letter_uv.y == 0) {
+ discard_text = true;
+ }
+
+ // We use ddx/ddy to get the correct mipmaps of the font textures. This
+ // confers 2 main benefits:
+ // 1. We don't use as much VRAM for distant players.
+ // 2. Glyphs anti-alias much more nicely.
+ const float iddx = ddx(letter_uv.x);
+ const float iddy = ddy(letter_uv.y);
+
+ if (Enable_Dithering) {
+ // Add noise to UV.
+ // Here, iddx and iddy tell us how big the current UV cell is with respect to
+ // screen space (i.e. how many pixels wide it is).
+ float noise = prng(letter_uv);
+ letter_uv.x += noise * iddx / 4.0;
+ letter_uv.y += noise * iddy / 4.0;
+ }
+
+ // Loop-independent anti-aliasing variables.
+ // See `aa_sample_algorithm.py` for simpler code demonstrating this concept.
+ // Basically we're taking evenly spaced samples inside a region as large as
+ // the current pixel.
+ const float iddx_convex = max(iddx, 1.0 / iddx);
+ const float iddy_convex = max(iddy, 1.0 / iddy);
+ const int aa_amount = AA_Amount;
+ const float aa_region = iddx_convex * iddy_convex;
+ const float aa_stride = aa_region / aa_amount;
+
+ [unroll(5)]
+ for (int aa_i = 0; aa_i < aa_amount; aa_i++)
{
- int letter = GetLetterParameter(uv_with_margin);
-
- float texture_cols;
- float texture_rows;
- float2 letter_uv;
- if (letter < 0xE000) {
- texture_cols = 128.0;
- texture_rows = 64.0;
- letter_uv = GetLetter(uv_with_margin, letter, texture_cols, texture_rows, NCOLS, NROWS);
- } else {
- texture_cols = 8.0;
- texture_rows = 8.0;
- letter_uv = GetLetter(uv_with_margin, letter, texture_cols, texture_rows, 8, 4);
- }
+ float aa_region_i = aa_stride * aa_i + aa_stride / 2;
+ float aa_region_x = aa_region_i / iddy_convex;
+ float aa_region_y = fmod(aa_region_i, iddy_convex);
- if (letter_uv.x == 0 && letter_uv.y == 0) {
- discard_text = true;
- }
+ aa_region_x = lerp(0, iddx, aa_region_x / iddx_convex);
+ aa_region_y = lerp(0, iddy, aa_region_y / iddy_convex);
- // We use ddx/ddy to get the correct mipmaps of the font textures. This
- // confers 2 main benefits:
- // 1. We don't use as much VRAM for distant players.
- // 2. Glyphs anti-alias much more nicely.
- float iddx = ddx(letter_uv.x);
- float iddy = ddy(letter_uv.y);
+ float2 cur_letter_uv = letter_uv + float2(aa_region_x, aa_region_y) * 1;
int which_texture = (int) floor(letter / (64 * 128));
[forcecase] switch (which_texture)
{
case 0:
- text = tex2Dgrad(_Font_0x0000_0x1FFF, letter_uv, iddx, iddy);
+ text += tex2Dgrad(_Font_0x0000_0x1FFF, cur_letter_uv, iddx, iddy);
break;
case 1:
- text = tex2Dgrad(_Font_0x2000_0x3FFF, letter_uv, iddx, iddy);
+ text += tex2Dgrad(_Font_0x2000_0x3FFF, cur_letter_uv, iddx, iddy);
break;
case 2:
- text = tex2Dgrad(_Font_0x4000_0x5FFF, letter_uv, iddx, iddy);
+ text += tex2Dgrad(_Font_0x4000_0x5FFF, cur_letter_uv, iddx, iddy);
break;
case 3:
- text = tex2Dgrad(_Font_0x6000_0x7FFF, letter_uv, iddx, iddy);
+ text += tex2Dgrad(_Font_0x6000_0x7FFF, cur_letter_uv, iddx, iddy);
break;
case 4:
- text = tex2Dgrad(_Font_0x8000_0x9FFF, letter_uv, iddx, iddy);
+ text += tex2Dgrad(_Font_0x8000_0x9FFF, cur_letter_uv, iddx, iddy);
break;
case 5:
- text = tex2Dgrad(_Font_0xA000_0xBFFF, letter_uv, iddx, iddy);
+ text += tex2Dgrad(_Font_0xA000_0xBFFF, cur_letter_uv, iddx, iddy);
break;
case 6:
- text = tex2Dgrad(_Font_0xC000_0xDFFF, letter_uv, iddx, iddy);
+ text += tex2Dgrad(_Font_0xC000_0xDFFF, cur_letter_uv, iddx, iddy);
break;
default:
- text = tex2Dgrad(_Img_0xE000_0xE03F, letter_uv, iddx, iddy);
+ text += tex2Dgrad(_Img_0xE000_0xE03F, cur_letter_uv, iddx, iddy);
break;
}
}
+ text /= aa_amount;
+
// The edges of each letter cell can be slightly grey due to mip maps.
// Detect this and shade it as the background.
fixed3 grey = fixed3(.4,.4,.4);
diff --git a/Shaders/TaSTT_template.shader b/Shaders/TaSTT_template.shader
index e0d436c..db5d036 100644
--- a/Shaders/TaSTT_template.shader
+++ b/Shaders/TaSTT_template.shader
@@ -17,7 +17,8 @@
[MaterialToggle] Enable_Margin_Effect_Squares(
"Enable margin effect: Squares", float) = 0
- [MaterialToggle] Enable_Dithering("Enable font dithering", float) = 1
+ [MaterialToggle] Enable_Dithering("Enable dithering", float) = 1
+ AA_Amount("Amount of anti-aliasing", Range(1, 5)) = 5
[MaterialToggle] BG_Enable("Enable custom background", float) = 0
BG_BaseColor("Background base color", 2D) = "black" {}
diff --git a/Shaders/aa_sample_algorithm.py b/Shaders/aa_sample_algorithm.py
new file mode 100644
index 0000000..779e159
--- /dev/null
+++ b/Shaders/aa_sample_algorithm.py
@@ -0,0 +1,43 @@
+#!/usr/bin/env python3
+
+# This is the algorithm that the anti-aliasing logic inside
+# TaSTT_lighting_template.cginc uses.
+
+from math import fmod
+
+x = .5
+y = .1
+aa = 10
+
+# This lets us handle values smaller than 1. We're creating an m*n rectangle
+# and walking a path left-to-right, top-to-bottom through it.
+x_cap = max(x, 1.0 / x)
+y_cap = max(y, 1.0 / y)
+
+print(f"{x_cap} {y_cap}")
+
+def lerp(lo, hi, fract):
+ return lo + (hi - lo) * fract
+
+for i in range(0, aa):
+ # We want to subdivide an x*y area into `aa` evenly spaced pieces.
+ region = x_cap * y_cap
+
+ stride = region / aa
+
+ region_i = i * stride + stride/2
+ region_x = region_i / y_cap
+ region_y = fmod(region_i, y_cap)
+
+ print(f"{region_x} {region_y}")
+
+ region_x = lerp(0, x, region_x / x_cap)
+ region_y = lerp(0, y, region_y / y_cap)
+
+ print(f"{region_x} {region_y}")
+
+ assert(region_x >= 0)
+ assert(region_x <= x)
+ assert(region_y >= 0)
+ assert(region_y <= y)
+