ASCII renderer in Unity
For a gamejam in association with the REPLAY Masters programme, I challenged myself to create a custom ASCII renderer in the Unity Engine. This idea stemmed from my recent new-found love for TUIs.
Unity Setup
The Unity version being used is version 2022.3.36f1 and Universal Render Pipeline. Although, I don’t see a reason this approach would not work in more recent/older versions.
On the scene, I have 2 Cameras:
- Main Camera - Renders the game
- ASCII Camera - Renders the objects to be in ASCII into a render texture.
For the ASCII Render Texture, it I picked a resolution to match 16:9, in my case I chose 128x72px.
First approach: TextMeshPro
My initial thought was to use Unity’s TextMeshPro as the ASCII renderer.
In my head, this idea never seemed like the propper and correct idea, but I still gave it a fair shot.
After a few hours wrapping my head with the immense parammeters a modern font style that never ligned up where they should, I gave up and started looking towards a different approach.
// Show TMP filling the entire screen
Second approach: 1 Pixel, 1 Object
After some back and forth and help, I settled on a promissing but harduous solution: every letter-pixel would be a mesh quad and the only thing that changes are the UVs. This would allow for accurate pixel-perfect text rendering while taking some advantage of the GPU.
For a jam this seemed like too much, but I was already in too deep to give up. Fortunately, in the end, it worked out.
// Image of subdiv screen sketc
My first idea on how to implement such a thing was to Instantiate a quad Prefab, move and scale it to match the target Pixel.
Which meant that I was spawning (128x72) 9216 individual objects… Unity running on a 2070 RTX did not like that a single bit. So I had to think of yet another approach to the problem.
// Show FPS and image
Third and final approach: Generate a custom Mesh
The final solution I came to was to generate a Mesh filling the entire screen where, for every pixel, there was a quad. This way, unity only has to render a single object with the same material and texture and I’m able to programatically control the UVs to match it to a letter (more on that later).
Matching mesh to screen
In order to have a “fullscreen” mesh, we take into account the Main Camera’s parameters, namely, the Orthographic size and Aspect. With these two values and the target size of the downsampled render texture, we have all we need to know about the mesh base positions.
// BaseX gives us the left edge of the screen
float baseX = -(camera.orthographicSize * camera.aspect);
// BaseY gives us the bottom edge of the screen
float baseY = -(camera.orthographicSize);
// Cell size gives us the "Pixel" size in Main Camera units.
float cellSizeX = (camera.orthographicSize * camera.aspect * 2.0f) / bufferSize.x;
float cellSizeY = (camera.orthographicSize * 2.0f) / bufferSize.y;
By having these values, we can now iterate for every pixel and create a quad there.
for (int y = 0; y < pixelY; y++)
for(int x = 0; x < pixelX; x++)
Create Top Left Vertex
Create Top Right Vertex
Create Bottom Right Vertex
Create Bottom Left Vertex
Same for UVs
Same for Tris, counter-clock-wise
After successful generation of the mesh data, we can now create a Mesh object:
Mesh mesh = new Mesh(); // Create object
mesh.name = "TextMesh"; // Add a name
mesh.SetVertices(vtxs); // Add the vertices
mesh.SetUVs(0, uvs); // Set the respective UVs to slot 0
mesh.SetTriangles(tris, 0); // Specify Tri order
mesh.RecalculateBounds(); // Force bound calculations to avoid wrong camera culling
mesh.MarkDynamic(); // Is going to be changed at runtime (UVs)
mesh.UploadMeshData(false); // Notify that all required data is complete!