2019-05-18 21:31 - Technical
I gave ShaderToy a try
If you have some knowledge of computer-generated imagery, you have probably heard about ShaderToy. This tool is the sandbox of graphics developers, a huge collection of user-added shaders, i.e programs intended to generate an image.
I finally gave the tool a try.
ShaderToy is a website where everyone can add their own shaders, check the work of others and interact with them.
The website is based on WebGL, a graphics programming standard for the web, supported by all the major browsers. The shaders are written in GLSL, OpenGL’s shading language, a standard widely used in the industry (for videogames, movies, or computer-aided design).
The website has a lot of restrictions, though, compared to a classic OpenGL or WebGL program; indeed, a graphic program is usually made of a sequence of steps that turn the geometry and textures into an actual image. But, in ShaderToy, the programmer can only implement one step: the pixel shader, i.e a program that is executed for each pixel and decides its final color. All the operations, including the creation of the geometry, must then be executed for each pixel, which is quite costly in terms of performance. Also, it’s impossible to use textures, apart from those added by the developers of the website.
This section is technical, so if you’re not interested by these details you can move directly to the next section.
To implement Pikachu’s “Thunderbolt” attack, I needed to create the lightning and also manage to display a texture of Pikachu.
The goal was to reduce as much as possible the amount of memory used and the time needed to display the texture. The image is 128*120 pixels wide; storing it in an array of RGBA values stored in single-precision floating-point numbers requires
I chose to use indexed colors instead, i.e I chose only 7 colors (the 8th being for transparent pixels), and I only need to store the id of the color of each pixel. Then, each pixel only needs 3 bits. By storing the texture in an array of 32-bit integers, I can store 10 pixels per integer. 2 bits out of 32 are unused (including the sign bit so no need to worry about the sign or use an unsigned type). The texture only takes
128*120/10*4=6144 bytes, which is 40 times less than before (though we also have less possible colors than before).
I created the pixel-art image, starting from an image of Pikachu found on the Internet, modifying it to fit the right dimensions and color palette, and then fixed the details by hand in Krita. I then wrote a Python script to turn this image into a GLSL function.
The base idea of the function is to read the indices of the colors in tha array, with bitwise operators to only select the 3 relevant bits:
const vec3 colors = (...); int idx = idx_y * width + idx_x; const int[...] indexv = int[...] (...); int index = (indexv[uint(idx / 10)] >> (3u * (idx % 10u))) & 7;
The real code is a bit more complex for performance reasons, as I noticed that it is more efficient to cut the array in sections of size 128, and use
if statements and the modulo operator to use the right section.
The lightning is the combination of multiple effects, but I’ll only describe the general idea. Basically, I hardcoded the general structure of the lightning as a tree (each point has exactly one parent except for the root), and then applied the following transforms:
- move the points: each point is displaced in function of the time and its parent’s displacement, with transversal and longitudinal components which amplitude is proportional to the distance of the point to its parent. The displacement functions are based on cosinus.
- subdivide each line in sublines. I fixed their number to 3 and made them ondulate with sine waves in function of the time.
- deform the position of the pixel to create a more “organic” lightning: I stretched horizontally in function of the y position to round the lines a bit, and moved the pixels according to simplex noise with two levels: the macro level to deform the general shape, and the micro level to create a nice level that makes the lines look like actual electricity
- finally, calculate the color of the pixels in function of their distance to the lines. The closest pixels have a very bright yellow color, which fades out with a darker yellow
Sooo… the result now
If your computer has a modern GPU, you can contemplate the result at the following address: https://www.shadertoy.com/view/3tfGWl
Otherwise, the cover picture of this article is a screenshot, which is unfortunately the only frame of this procedural animation that you’ll ever see. Opening the link on an old computer or a mobile phone is at your own risk (though in the worst case, it should only crash your browser).
This tool is interesting to practise and learn from others, but the idea of hardcoding my texture was a bit stupid, the tool is more intended to do procedural graphics or test effects. For graphics experiments with geometry and textures an OpenGL or WebGL program is better suited (or an engine which supports GLSL shader programming)