Been getting into shader writing recently and I love it!

I was playing around in Unity and even though it’s a pretty decent place to start there were a lot of small time consuming roadblocks in the pipeline. First of Unity must recompile all the scripts which take couple of seconds. Then you can’t just drag and drop textures from file explorer, you have to import them into unity project first. Finally you control shader properties trough materials which is great for game development but adds extra unnecessary steps when experimenting and prototyping. That’s why I bought Shadron the same minute I finished watching it’s trailer. Shadron is a perfect tool for writing custom shaders from scratch and I love it! You can find out about Shadron more here.

Naturally after buying Shadron and getting basics down I decided to make something worth showing my friends. What started out as simple led board effect mutated into fully dynamic ASCII art generator.

Code overview/explanation

You can find full source code for shadron here: http://pastebin.com/s5qVEmvx

And here’s character texture I’m using: http://imgur.com/a/U5xje

Downscale(vec2 position, vec2 pixelCount)

It works pretty straightforward. Basically you have to think like you are working with pixels groups. One pixel group contains of nearby pixels that are going to share one color. The number of pixel groups is horizontal pixels multiplied by vertical pixels (note that I am talking about output pixels you set with parameters, not image pixels).

vec2 pixelPosition = floor(position / (1 / pixelCount)) * (1 / pixelCount);

This line finds bottom left position of the pixel group the current position is in.

vec2 sampleLocation = pixelPosition + (1/pixelCount)/2;

Here we just add half of the pixel group width to get sample location that is not at a corner but at a center of pixel group.

return texture(Input, sampleLocation).xyz;

We sample Input texture and return it’s color.





colorBrightness(vec4 color);

This function returns given color brightness. Brightness value returned is not linear but stepped. Count of different brightness levels is the same as count of available characters.

float normalizedBrightness = (0.299*color.r + 0.587*color.g + 0.114*color.b);

Simple magical calculation that I found on internet. Gives color brightness in a range from 0 to 1.

float adjustedBrightness = (normalizedBrightness - brightnessThreshold) / (1-brightnessThreshold);

Here I modify the brightness I got by my modifiers.

float stepBrightness = (floor(clamp((adjustedBrightness + brightnessAdd), 0, 1) * letterIterations)/letterIterations);

Here I get step brightness which I use to differentiate which character I am going to use.





asciiMask(vec2 position);

Basically what this function does is return value between 0 and 1 based on finding position to sample from character texture based on brightness and relative position of current pixel group.

vec2 pixelPosition = floor(position / pixelSize) * pixelSize;

Finding out pixel group position.

vec2 sampleLocationNormalised = vec2((position.x - pixelPosition.x) / pixelSize, (position.y - pixelPosition.y) / pixelSize);

Normalizing position based on pixel group size. It calculates sampled pixel location relative to pixel group bounds and gives 2D vector between 0 and 1.

return texture(AsciiTexture, vec2(lerp(brightness, brightness + 1.0/letterIterations, sampleLocationNormalised.x), lerp(0, 1.0, sampleLocationNormalised.y))).a;