📜 ⬆️ ⬇️

I love graphics programming

cover

I love graphics programming! We all make mistakes in the process of designing and writing code. Sometimes these are errors of logic (when the algorithm is thought out inaccurately or not to the end), sometimes errors arising out of inattention, and many, many more options. And what happens in the normal workflow? - The lists do not have the necessary entries, some numbers are considered incorrect, error messages are falling out, and so on. In graphics programming, everything is a bit more fun, because often we get a result that simply does not match what was expected. In my small project, I decided to keep such “results” throughout the development process and would like to share them with you.

Anyone who does not like Android, Live Wallpaper, Minecraft, bicycles, a stream of consciousness that is loosely attached to the topic and everything, I want to immediately warn you that they can be upset by the content of this post, so keep reading at your own risk. I'll leave here also a warning for users of the mobile or just the limitless Internet: a lot of pictures will follow .

Hello!

I am a software developer with years of experience. This is not to say that my work is monotonous and boring. I solve problems of different levels, sometimes I invent solutions, sometimes I myself implement them, sometimes I search for vulnerabilities in the systems, and sometimes I just tell why the monitor is not a computer. Perhaps the fact that I am not busy just coding, brings a share of diversity and creativity in the workflow.
')
It also happens that due to various problems (the budget or development time is limited, or just someone from the upstream rooster pecked not where it was needed), you have to look for ready-made solutions for many things, which turns my work into a children's dice game where it is necessary to “put a cube on a cube” and watch how the managers clap their hands. Perhaps this is the thing that turns my work into a routine (although yes, even in moments of “playing with cubes,” I usually keep telling that the monitor is not a computer). In order to somehow avoid the routine and not forget what it is to implement simple things, I like to program “everything in a row” in my free time. And I noticed that I love programming graphics ...

What are we talking about?

I will start from the beginning, without any special digressions about who I am and why. Having bought a new phone (here the tendency of my articles on the habr is already emerging ...), I wanted to put myself a “live wallpaper”, because I really don't know why, but I like it when something moves on the screen. Probably all because then I feel that I bought a phone with a quad-core processor for good reason. I walked around the store, thought that I would like to see on the screen and decided that I wanted something in the style of minecraft, but unfortunately I did not find anything that would properly delight me. Then I had the imprudence to decide to do everything myself ...

The first doubts on this subject ...

It should be a little distracted from the main story and explain why I had doubts about the idea of ​​“but I will write - I'll give myself something.” In school years, a game was installed on computers (as I learned quite recently, the game was called “Treasure” for BK-0010), where the white man (for whom it was necessary to play) collected something (in my memory it was just the keys, although as it turned out later, these were supposed to be chests), and the black man for something very much hated the white man and killed him with a touch. I don’t know why, but the thoughts about the game made me feel nostalgic, and so I decided “I’ll write it myself.”

In order not to bore you with a story about the development process and other details (my story is not about that), I will simply describe the meaning: I wrote, it worked exactly as I remembered, I played once, I left it, because already “played up” in the process of debugging .

For those who are interested, the result is this:
image

The inner voice told me that I would not use the result of my work, because I see too much of it during the development, but, as usual, I decided that “this time it will not be exactly like this” (the power of self-conviction, yeah). I will run ahead and say that the inner voice was right ...

And where is the programming graphics?

I will try to return to the main story. I immediately decided that I would not use either OpenGL or anything else that would help me accomplish the task - only hardcore. Moreover, I have always been interested in the interaction of Java code with native code for Android, and now I have a good opportunity to try my hand at solving this problem.
Immediately I decided to check if I could even implement a drawing with a sufficient frame rate, with a constant call to the native library. For verification, I implemented the following task - filling the screen with pictures with some arbitrary coefficient of “shading” (in fact, simply with a brightness parameter, where the original picture is considered as bright as possible). Wrote a variant in Java and C ++. I drove both variants with a rough test of time counting and saw that, on average, the C ++ version worked a little faster, even though the output of the finished image “on the screen” was still made by Java. As pictures I immediately took one of the cutest, in my opinion, textures for minecraft, the result was something like this:
image

Since I decided from the very beginning that I would begin to implement everything myself and would not pry into literature or look for help on the Internet, then at the edges of my image they looked like this:
image

Obviously, the images from which only a part should have been drawn are incorrectly processed, correct ...
So, the problem is solved, which means you can get down to business and start looking for a solution and an optimal implementation.

Since I chose a solution using JNI for implementation, as a result, the development was conducted in mixed mode. I wrote and tested the main logic under Windows, immediately generating an image for 10 screens (these are the wide images that follow in the article), and from time to time I checked the solution on the phone.

"Results"


So, the horizon line (for the time being, I don’t even know why I made random ones - I am writing an article and am surprised at myself):

Large images are clickable, but habrastorage slightly reduced their size (the original was 7200 x 1280)

Now, from random debris, go to meaningful content. The horizon line is “meaningful”:


Next, it was necessary to create a “cave” (depressions in the surface) so that the relief did not look so primitive. Since by the time the implementation was still raw, the test of the cave creation algorithm was a drawing of the “caves” with a different texture:


The next step was to divide everything into foreground and background, and the background should have a darker texture:

This already looks like something, but still very far from the result.

I changed the textures, decided to add a light source at the bottom - lava, but was mistaken with the texture, so the bottom was “filled with torches”:


Correct and add another source of light - torches:


I realized that the ratio of the foreground and background blocks does not suit me and changed the coefficients:

Looking ahead, it should be noted that this is the part that I changed many times in order to achieve such a result, to calm down and not touch it again.

The purpose of adding light sources was more adequate lighting - lighting from light sources. Light sources were divided into three groups:
  1. Illumination from the sky. The brightest light source, but the change of the time of day was originally conceived, and therefore the lighting from the sky depends on time.
  2. Lighting from the lava. A less bright light source than the sky during the day, but the brightness does not change with time.
  3. Lighting from the torches. The least bright light source. Brightness is also constant.

As a result, two parameters began to influence the lighting of the block - the distance to the sky and the distance from the static light source:

On the left, the light propagates from the sources, and on the right, just the “background is darker than the front.”

Here transparency began to ask, because you can not leave the torches with a white background. To avoid the questions “and what is the problem?”, I remind you that I have an array of some tsiferok (pixels) and other tsiferok (also pixels) and all the transfer and drawing rules needed to be written. We wanted transparency - here's your transparency (a little bit more transparent and it would be necessary to draw a picture from the phone’s camera so that it is quite “transparent”):

Correcting ...

Correcting the background, accidentally “painted over” and caves with earth:

I know that it may seem that some of this was done intentionally, but I assure you that all the results were obtained by chance. In truth, I even find it difficult to say how I did it, but the result is as it is. Correct this too ...

The illumination of the block behind the torch is not calculated correctly (the block after the torch is darker than the neighboring blocks):

The error is rather stupid, but at once I somehow did not think that the torch could be in a well-lit place and considered the illumination of the block as the maximum brightness value of the torch. There could be at least two solutions - fix lighting or remove torches from lighted areas. I decided to fix the lighting.

Now I decided that it was necessary to make it possible to specify a string (seed) that sets a unique “map”, which means that I also needed my own implementation of random number generation (in fact, it wasn’t needed, because the usual rand would suffice, but begged the bike):

and


It turned out quite "accidentally", if not more.

The next step, additional blocks were added (I’m missing this step, excuse me, and there are so many pictures) in order to diversify the appearance of the relief, and here came the turn of the trees.

I want to say a few words about trees separately ... By the middle of the development process, I already had a comment in the code, several entries on paper and one note to the screenshots, something like this: “i h8 3s”. And for good reason. The trees immediately went somehow difficult. Every little thing, every editing code necessarily affected the trees. In general, no matter how ridiculous this may sound, but it was the trees that were the biggest splinter.

So, the first iteration of torment with trees:

To begin with, it was decided to make a tree trunk and leaves above the trunk (so far without leaves on the sides), but even here I mixed up the blocks and got an inverted result.

Corrected the error and added leaves on the sides, and again not the same block:


As a result, after a change in the number of trees, the result was:


Then I decided that the algorithm for generating the horizon line upset me and that it would be nice to fix it, which I did. The result was quite predictable - no good:


Then quick-fix, usual for many developers, followed, without much understanding of the essence of the problem (after all, I had just written this code, it is obvious that I can fix it without thinking!) That, as could be expected, to a positive result did not lead:

Curiously enough, the horizon is not completely flat, but has a slight distortion at the beginning.

As a result, I experimented with different parameters and after a lot of results I calmed down and stopped thinking about corrections for the horizon line. Here are some results for the horizon line:


After I got a working prototype, it's time to debug it, clean the code and do the optimization. I started by getting rid of the magic numbers, which were about 10 and which bound most of the parameters to the size of the screen and blocks at the time of the tests.

And the trees continued to grieve - it was necessary to ensure that they were generated only on the ground, added shading to some blocks of leaves for the volume (which, by the way, also didn’t work the way we would like):


It became clear that you need to debug the function of drawing shaded blocks, and at the same time to optimize something in it. Again, a quick fix for the “bugs”, again a pretty funny result:


Here, it almost instantly dawned on me, what did I do wrong, and the new fix did not take long to wait:


There should immediately make a reservation that I usually do not write code in this style (ie, in the style of “first write, then think”). But in this project I found it very funny. After all, my every mistake, every nonsense, necessarily led to a result, and very rarely I could predict this result or immediately explain “why so”.

By this moment, the texture of the leaves and grass (earth with grass) was of a certain green color. I asked for the implementation that would allow to change the color, allowing a little blood to change the time of year. Yes, I know perfectly well that this could have been easily done in Java and did not invent anything, but sports interest was too strong. For this, the texture has been changed and a function for “texture painting” has been written:


The function itself had to be corrected several times, since it created the effect of a variegated whale (two images in the middle), but did not do what was necessary (the right image). Here are some results of the algorithm:


From time to time, I ruled certain methods in order to tidy up the code and from time to time I got very different results. Another example:


Quite funny situations from the world of debugging came up from time to time. For example, an error in the drawing of the gradient (another generator of colorful stripes):


During the debugging of this error, when I already began to run out of ideas, the debugger issued the following color value:

On that day, I left the debugging and decided to rest so that the debugger would stop telling me about the poor quality of the food.

At this point, I decided to start testing the result in parallel on the phone. In the phone, the order of the colors turned out to be slightly different, so the red and blue components of the colors were reversed:


By the way, I forgot to remind once again that I hate trees ... The trees at night behaved strangely:


At this time, the phone generally had its own view on how to draw a picture after shifting (scrolling with your finger):
and

Okay, the color in the picture on the left is this, because I forgot about the position of the blue and red component, but the trendy motion blur effect is already “thank you” android for drawing my image quite correctly, which I did not think about alpha channel (in the alpha channel by that time there could be anything).

By the way! For a long time I did not show you my trees! Here is:

There are quite a few problems here - and the wrong lighting of some blocks of leaves, and the incorrect drawing of “transparent pixels”.

In parallel, I began work on a waypoint system or, in simple terms, a pathfinding algorithm. The path was needed so that there was an opportunity to add zombies and other characters and at the same time would not have to calculate their behavior a few steps forward each time (so that they do not tupili on the spot). For myself, I began to mark the path visually to assess the quality of the algorithm:


A more advanced version of visualization looked like this:


By the way, as a result, I did not add zombies (I didn’t get a hand), but debugged the waypoints system. Pay attention to the trees in these two pictures. The trees still looked beautiful ...

At some point, when I tried to correct the appearance of the trees, I received another “positive”:


Here are some more interesting bugs from the phone that are directly related to transparency (alpha channel):
and

Then there were errors due to the addition of additional textures (and therefore the change of texture indices):


Then I “corrected” something in the drawing algorithm and got a rather strange effect (most likely I messed up with the size and position of the textures):


As a result of the recent optimization of the drawing algorithm, I received two errors at once (again variegates of different-sized):
and

The process of drawing this miracle looks like this (obviously, this is the best option):


The last thing that needed to be realized was the volume effect (the edges of the blocks) so that the picture was not so flat and simple. For the faces, it was necessary to write two algorithms - an algorithm for changing the size of the image and an algorithm for tilting the image.

I'll start with the image size reduction algorithm:


Top down - the results of the algorithm. At the very bottom - the desired result (the test was conducted on a large picture, for clarity).

The rotation algorithm is more distinguished:


At the left above - the initial image, and at the right below - the desired result, and in the middle all that was obtained along the way to the result.

When the algorithms were ready, making the volume was already quite simple. The block consisted of 3 faces (a view from one side, a pseudo-3D or the so-called 2.5d). For beauty, a linear gradient was applied on the verge, which also had to be debugged in order to obtain the desired result:


Since I could not decide on the size of the faces of the block, I made it possible to change this parameter:


After all the changes, it was necessary to optimize the drawing process so as not to draw anything that is not necessary to draw. The result of this optimization was again a problem with the transparency channel that surfaced during tests on the phone. I must admit that the result obtained looked rather curious:
and

The result of correcting this error was not so interesting effect:


Results

In the end, all critical errors were debugged, added textures and buns in the form of effects. Two versions for the store were made - free (very simplified) and paid (with all the buns).

I decided not to raise the technical side in this article, because and so not sure that someone will finish reading it to the end. If it will be interesting for people who have mastered this article to learn about the technical side of this story, then write in the comments or in a personal, and I will definitely tell you about the difficulties encountered, the implementation features and other details.

I hope that this will encourage someone else to create something with their own hands, and not just using ready-made frameworks and libraries.

Thanks to those who even reached the end of the article!

Source: https://habr.com/ru/post/190458/


All Articles