summaryrefslogtreecommitdiffstats
path: root/Shaders/STT_text.cginc
blob: 095df6ad0f068cd39bebfe889c973eca00890049 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
#ifndef __STT_TEXT_INC__
#define __STT_TEXT_INC__

#include "stt_generated.cginc"

float Enable_Dithering;

SamplerState linear_clamp_sampler;

Texture2D _Font_0x0000_0x1FFF;
float4 _Font_0x0000_0x1FFF_TexelSize;
Texture2D _Font_0x2000_0x3FFF;
float4 _Font_0x2000_0x3FFF_TexelSize;
Texture2D _Font_0x4000_0x5FFF;
float4 _Font_0x4000_0x5FFF_TexelSize;
Texture2D _Font_0x6000_0x7FFF;
float4 _Font_0x6000_0x7FFF_TexelSize;
Texture2D _Font_0x8000_0x9FFF;
float4 _Font_0x8000_0x9FFF_TexelSize;
Texture2D _Font_0xA000_0xBFFF;
float4 _Font_0xA000_0xBFFF_TexelSize;
Texture2D _Font_0xC000_0xDFFF;
float4 _Font_0xC000_0xDFFF_TexelSize;
Texture2D _Img_0xE000_0xE03F;
float4 _Img_0xE000_0xE03F_TexelSize;

float2 AddMarginToUV(float2 uv, float2 margin)
{
  float2 lo = float2(-margin.x / 2, -margin.y / 2);
  float2 hi = float2(1.0 + margin.x / 2, 1.0 + margin.y / 2);

  return clamp(lerp(lo, hi, uv), 0.0, 1.0);
}

// Generate a random number on [0, 1].
float prng(float2 p)
{
  return frac(sin(dot(p, float2(561.0, 885.0))) * 776.2) / 2.0;
}

bool f3ltf3(fixed3 a, fixed3 b)
{
  return (a[0] < b[0]) *
    (a[1] < b[1]) *
    (a[2] < b[2]);
}

// Write the nth letter in the current cell and return the value of the
// pixel.
// `texture_rows` and `texture_cols` indicate how many rows and columns are
// in the texture being sampled.
float2 GetLetterUV(float2 uv, int nth_letter,
    float texture_cols, float texture_rows,
    float board_cols, float board_rows,
    float margin)
{
  // UV spans from [0,1] to [0,1].
  // 'U' is horizontal; cols.
  // 'V' is vertical; rows.
  //
  // I want to divide the mesh into an m x n grid.
  // I want to know what grid cell I'm in. This is simply u * m, v * n.

  // OK, I know what cell I'm in. Now I need to know how far across it I
  // am. Produce a float in the range [0, 1).
  float CHAR_FRAC_COL = uv.x * board_cols - floor(uv.x * board_cols);
  float CHAR_FRAC_ROW = uv.y * board_rows - floor(uv.y * board_rows);

  // Avoid rendering pixels right on the edge of the slot. If we were to
  // do this, then that value would get stretched due to clamping
  // (AddMarginToUV), resulting in long lines on the edge of the display.
  float lo = margin / 2;
  float hi = 1.0 - margin / 2;
  bool skip_result = (margin != 0) *
      !(CHAR_FRAC_ROW > lo *
        CHAR_FRAC_COL > lo *
        CHAR_FRAC_ROW < hi *
        CHAR_FRAC_COL < hi);

  float LETTER_COL = fmod(nth_letter, floor(texture_cols));
  float LETTER_ROW = floor(texture_rows) - floor(nth_letter / floor(texture_cols));

  float LETTER_UV_ROW = (LETTER_ROW + CHAR_FRAC_ROW - 1.00) / texture_rows;
  float LETTER_UV_COL = (LETTER_COL + CHAR_FRAC_COL) / texture_cols;

  float2 result;
  result.x = LETTER_UV_COL;
  result.y = LETTER_UV_ROW;

  return lerp(result, -1, skip_result);;
}

float4 GetLetter(float2 uv) {
  uint letter = GetLetterParameter(uv);

  float texture_cols;
  float texture_rows;
  float2 letter_uv;
  bool is_emote = false;

  if (letter < 0xE000) {
    letter_uv = GetLetterUV(uv, letter % 0x2000, TEXTURE_NCOLS, TEXTURE_NROWS, BOARD_NCOLS, BOARD_NROWS, /*margin=*/0.02);
  } else {
    is_emote = true;
    texture_cols = 16.0;
    texture_rows = 8.0;
    // This will need to be updated if we create multiple emote textures.
    letter_uv = GetLetterUV(uv, letter % 0x2000, texture_cols, texture_rows, BOARD_NCOLS, BOARD_NROWS, /*margin=*/0);
  }

  bool discard_text = (letter_uv.x == -1) * (letter_uv.y == -1);

  // 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);

  bool add_dithering = Enable_Dithering * !is_emote;
  // 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 = frac(prng(letter_uv) + _Time[0]);
  letter_uv.x += lerp(0, (noise - 0.5) * iddx / 4.0, add_dithering);
  letter_uv.y += lerp(0, (noise - 0.5) * iddy / 4.0, add_dithering);

  fixed4 text = fixed4(0, 0, 0, 0);
  int which_texture = (int) floor(letter / (uint) (64 * 128));
  [forcecase] switch (which_texture)
  {
    case 0:
      // Divide iddx, iddy by 2.0 to remain on a higher-detail mip level for
      // longer.
      text = _Font_0x0000_0x1FFF.SampleGrad(linear_clamp_sampler,
          letter_uv, iddx / 2.0, iddy / 2.0);
      break;
    case 1:
      text = _Font_0x2000_0x3FFF.SampleGrad(linear_clamp_sampler,
          letter_uv, iddx / 2.0, iddy / 2.0);
      break;
    case 2:
      text = _Font_0x4000_0x5FFF.SampleGrad(linear_clamp_sampler,
          letter_uv, iddx / 2.0, iddy / 2.0);
      break;
    case 3:
      text = _Font_0x6000_0x7FFF.SampleGrad(linear_clamp_sampler,
          letter_uv, iddx / 2.0, iddy / 2.0);
      break;
    case 4:
      text = _Font_0x8000_0x9FFF.SampleGrad(linear_clamp_sampler,
          letter_uv, iddx / 2.0, iddy / 2.0);
      break;
    case 5:
      text = _Font_0xA000_0xBFFF.SampleGrad(linear_clamp_sampler,
          letter_uv, iddx / 2.0, iddy / 2.0);
      break;
    case 6:
      text = _Font_0xC000_0xDFFF.SampleGrad(linear_clamp_sampler,
          letter_uv, iddx / 2.0, iddy / 2.0);
      break;
    case 7:
      text = _Img_0xE000_0xE03F.SampleGrad(linear_clamp_sampler,
          letter_uv, iddx / 2.0, iddy / 2.0);
      break;
    default:
      // Return some distinctive pattern that will look like a bug.
      return fixed4(1, 0, _SinTime[0], 1);
  }

  // The edges of each letter cell can be slightly grey due to mip maps.
  // Detect this and shade it as the background.
  fixed3 grey = 0.7;
  bool disc = !(!f3ltf3(text.rgb, grey) * !discard_text * !is_emote);
  return lerp(fixed4(text.rgb, 1), 0, disc);
}

#endif //  __STT_TEXT_INC__