Good day! Once a friend came to my job, and I showed him my freshly written shader, at that time it was the first serious experience with them. This firmware converted the image from the camera into the image of a knitted sweater.
At the time of development, the image was taken from the camera of the phone and displayed in a knitted form, the effect was unusual. And a friend threw an idea to make a filter for the camera in the form of a quadratic tree.
')
In general, from that moment I sat down to study the methods of quickly constructing a quadratic tree, and stopped at the idea of HistoPyramid .
Implementation
Initially, you need to prepare the input parameters for the shader. I used Unity3D, so C # code samples.
The process of the algorithm is divided into two stages:
Building a tree
but. Shader Parameters Preparation b. Shader implementation
Creating an image from a tree
Building a tree
Shader Parameters Preparation
All calculations will be conducted on the conditions that the maximum size of the square should be equal to 256 pixels.
int quadSize = 256;
Initially, I will give an example of a built tree:
The number of levels in the tree is calculated as follows:
float levelsAmount = Mathf.Log(quadSize, 2f);
Each next level is two times less than the previous one. Since the tree elements will be located on the right, the size of the tree image will be calculated as follows:
Vector2 histoSize = new Vector2((float)quadSize + (float)quadSize / 2f, (float)quadSize);
The parameters of the shader will need to pass the coordinates of the beginning of the block, the coordinates of the end of the block and the size of the block. You will also need the same parameters for the parent block, i.e. block larger than the current one.
The specified piece of code calculates the coordinates of the beginning, end, and block size, provided that currentLevel is greater than 0, i.e. all elements of the tree to the right of the original image. From this in the coordinates of the beginning and end are "magic numbers" 0.6666666667f (2/3 occupies the original image). _cellSize will store the block size in pixels, after which, for the beginning and end, you will need to convert to relative units.
if (i == 1) { parentStart = Vector4.zero; parentSize = new Vector4 (0.6666666667f, 1f, 0, 0); } else { _cellSize = Mathf.Pow (2f, levelsLog - (float)currentLevel + 1f); parentSize = new Vector4 (_cellSize / histoSize.x, _cellSize / histoSize.y, 0, 0); parentStart = new Vector4 (0.6666666667f, parentSize.y, 0, 0); }
In the case of the parent unit, everything is identical.
Shader implementation
The shader will run levelsAmount times. For each level of the tree, the parameters corresponding to the current level will be transmitted.
The parameters to the shader input are as follows:
To simplify the calculations, the color difference from the source will be considered only at the first level. The relative coordinates of the block are calculated as follows:
The error is calculated in a simple way: if the pixels differ in color, it will add (1 / n) to the error, where n is the number of color comparison combinations.
The result of the function is the average pixel color value, where the alpha channel is an error. This is the main simplification in my algorithm, since I didn’t have enough to calculate the color deviation through the histogram.
return fixed4(col.rgb, errorColor);
At the other levels, the principle is similar, only the error will be calculated from the sum of the pixels of the parent block.
As a result, walking through all levels, our tree will be formed. The next step is to display the tree on the screen, scaling it to fit the screen.
Creating an image from a tree
The task of this shader is as follows: determine an error for each level, and if it does not exceed the specified level, draw the required level with the color of the pixel in the tree. Shader parameters:
The idea is the following, for each pixel of the final image it is necessary to pass from the smallest block of the tree to the big one (bottom to top).
If the error is not more than the specified value (threshold):
if (color.a > 0.59-(0.08*(9-level))) // .
Then we display the desired image from the _TileTex texture.
As a result, in order to run through the required levels, I had to strictly prescribe the conditions without any cycles.
fixed4 value = float4(0,0,0,0); value = getLevelColor(7, vo.uv, float2(0.005208333333, 0.0078125), 0.5); if (value.a > 0) { returnvalue; } value = getLevelColor(6, vo.uv, float2(0.01041666667, 0.015625), 0.25); if (value.a > 0) { returnvalue; } value = getLevelColor(5, vo.uv, float2(0.02083333333, 0.03125), 0.125); if (value.a > 0) { returnvalue; } value = getLevelColor(4, vo.uv, float2(0.04166666667, 0.0625), 0.0625); if (value.a > 0) { returnvalue; } value = getLevelColor(3, vo.uv, float2(0.08333333333, 0.125), 0.03125); if (value.a > 0) { returnvalue; } value = getLevelColor(2, vo.uv, float2(0.1666666667, 0.25), 0.015625); if (value.a > 0) { returnvalue; } value = getLevelColor(1, vo.uv, float2(0.3333333333, 0.5), 0.0078125); if (value.a > 0) { returnvalue; } returnvalue;
At the output we get the desired result:
Epilogue
As a result, we got a shader running on weak mobile devices with a frame rate of 25-30 or more. In this article I touched upon the image processing with “pure” colors, but you can go further and make the calculation of the color deviation error more complicated, then you can use this script in “noisy” images with a complex texture.
Example (from 44 seconds):
I wanted to apologize for the many magic numbers in the article. Thanks for attention!
Links to materials used:
Effcient region segmentation on compressed images using quadtree and shading representation . Kuo-Liang Chunga, Hsu-Lien Huanga, Hsueh-I Lu
RealTime QuadTree Analysis using HistoPyramids. Gernot Ziegler, Rouslan Dimitrovb, Christian Theobalta, Hans-Peter Seidela a Max-Planck-Institut fĂĽr Informatik, Stuhlsatzenhausweg 85, D-66123 SaarbrĂĽcken, Germany. bInternational University Bremen GmbH, Postfach 750561, D-28725 Bremen, Germany
HistoPyramid stream compaction and expansion. Christopher Dyken and Gernot Ziegler