#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; if (margin != 0 && (CHAR_FRAC_ROW < lo || CHAR_FRAC_COL < lo || CHAR_FRAC_ROW > hi || CHAR_FRAC_COL > hi)) { return float2(-1, -1); } 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 result; } float4 GetLetter(float2 uv) { fixed4 text = fixed4(0, 0, 0, 0); bool discard_text = false; 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); } if (letter_uv.x == -1 && letter_uv.y == -1) { 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 && !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 += (noise - 0.5) * iddx / 4.0; letter_uv.y += (noise - 0.5) * iddy / 4.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.5; if (f3ltf3(text.rgb, grey) || discard_text || is_emote) { return 0; } return fixed4(text.rgb, 1); } #endif // __STT_TEXT_INC__