3D planets in 2D through a shader

    Remember how you asked me to write about shaders? Do you remember? Not? But I remember and even wrote. We ask for favors, let’s talk about the beautiful.

    Today I’ll talk about how I made volumetric rotating planets for our blast-off game. That is, of course, they are completely flat, only a couple of triangles, but they look like volumetric.



    Interested in? I ask for cat. The pictures are decent.

    Introductory text


    It’s not for me to tell you about how in movies and games they deceive the audience and players, forcing them to see what they need, and not what they really are. Special effects are not always, but rather, on the contrary, rarely, are made according to all the rules and laws of physics and nature. Everywhere there are simplifications, tricks, or optical illusions. On the other hand, we have technical limitations, and viewers fail - not everyone can imagine what a real black hole looks like or a real helicopter explosion from an anti-aircraft missile, for example.

    Why am I? Oh yes, my effects also do not claim to be the most honest and believable. Strictly speaking, I don’t give a damn how it will be right, if only it would look cool! When developing this or that effect, I am more guided by the ratio of its quality (whether I like it or not), as well as the speed of its work and the complexity of its execution. This I say, not least in order to avoid controversy. Perhaps if I didn’t give explanations and source code, you would hardly notice my deception “by eye”.

    In general, many people ask themselves - “why don’t you hire an artist?” Or “why not 3D?”. Well, seriously, why? We are adults and we know that indie developers make games for fun and make them painstakingly. Groom and cherish. And of course, they like bikes very much. I am no exception, I also love them and periodically do them. There should have been a tirade of "pseudo-indie" developers masquerading as independent and trying to cut the jackpot, but I will omit it. So, you create the world, you create what you want and how you want. You are responsible for every pixel in the final picture, and only you can make it what it should be!

    2D full height


    Today I’ll talk about how I made volumetric rotating planets for our blast-off game. That is, of course, they are completely flat, only a couple of triangles, but they look like volumetric. Our game is completely two-dimensional, so there are not many options for implementing this effect. The option to re-render many frames for each planet, and then change frame by frame, as was done in old games, can be cut off. It is bulky and does not allow, for example, dynamic clouds, thunderstorms on the surface of the planet or to change the direction of lighting. That is, of course, it gives, but for this you need to do just a bunch of sprites for each kind of planet. Of course, this is not acceptable.

    The solution was obvious. You need to do everything on the fly, a shader. Many will immediately have the thought: “Well, nifiga is obvious to yourself! Obviously, this is to do 3D for a 3D planet, isn't it? ” Not. Because the game is exclusively two-dimensional. The only question remains is how. Changing the texture geometry reliably is a rather cumbersome code, only if you do not use an offset map. With a displacement map, the code becomes simpler and, as a result, much faster.

    Speaking of the displacement map, since it contains vectors, and each color component is only 8 bits, it is easy to calculate that we can encode the displacements by no more than 128 pixels. Of course, we can take into account that each gradation is 2 pixels, and then we can encode offsets up to 256 pixels in each direction. However, in our case, the size of a normal displacement map was enough for us. I compensated for the increase in size by linear filtering in the shader.
    Here it is necessary to make a reservation that at the moment my quad-engine engine only supports textures of the A8R8G8B8 format.

    Conveyor


    The shader will have to distort not only the surface of the planet, but also the clouds, shadows, glow and everything that you want to use when rendering the planet. Those same distortions that are not visible to the eye can be easily noticed if you replace the texture of the planet with a grid texture.

    The next steps will be to apply the atmosphere and the shading of the planet. This can be done already without a shader, however, a shader can also apply here, complicating it by entering a parameter for the light point. I managed with a simpler solution with pre-painted textures. Of course, a planet can be, say, without an atmosphere and without clouds. A planet can split into parts and emit light over its entire surface, have geysers and volcanoes, be covered with cities or technological structures. There are a lot of options for choosing.

    Optionally, you can work with the glow of the surface of the planet, or even implement various textures on the day and night parts of the planet. For example, the glow of cities at night.

    Some lyrics
    When writing this article, an interesting thought came up. But what if I move the background with the stars and rotate the planet in the opposite direction, changing the lighting on it? Then there will be a complete illusion of flying around our planet. As I am prompted from the back rows, in this case I must not forget about the focus and add distortion so that it is not too implausible.


    Tools


    • Delphi XE5 Starter - for writing code
    • Quad-engine - for graphics output
    • fxc.exe - for compiling shaders into binary code
    • QuadShade - for editing shader code
    • FilterForge - for procedural texture generation


    Shader
    float2 Velocity : register(c0);
    sampler2D DiffuseMap  : register(s0);   
    sampler2D DuDvMap     : register(s1);
    float4 std_PS(vertexOutput Input) : COLOR {  
        float2 source = Input.TexCoord;      
        source.y -= 2.0 / 512.0;
        source.y = max(source.y, 0.0);
        source.x += 2.0 / 512.0;
        float4 right = (tex2D(DuDvMap, source) * 2.0) - 1.0;
        source.x -= 4.0 / 512.0;                                 
        source.y = max(source.x, 0.0);
        float4 left = (tex2D(DuDvMap, source) * 2.0) - 1.0;
        source.y += 4.0 / 512.0;
        float4 left2 = (tex2D(DuDvMap, source) * 2.0) - 1.0;
        source.x += 4.0 / 512.0;
        float4 right2 = (tex2D(DuDvMap, source) * 2.0) - 1.0;
        float4 middle = right / 4.0 + left / 4.0 + right2 / 4.0 + left2 / 4.0;
        float2 coord = middle.rg;
        coord.x = coord.x / 4.0;
        coord.y = coord.y / 2.0;
        coord += Velocity;
        float4 result = tex2D(DiffuseMap, coord);
        return float4(result.rgb, middle.a * result.a) * Input.Color;
    }
    



    From theory to practice


    We will start working with the grid. The grid will allow us to understand how close we are to the result that suits us. Consider that the length of the texture in width should be twice as long as in height, since the planet has both sides. For the standard, we take a texture with a resolution of 1024x512 pixels.



    The second necessary texture will be the same dUdV card with encoded vectors. We have it equal to 512x512 pixels. Here the sizes are smaller. Why? Because we only see a square piece of mesh distorted by the shader. So we need to tell the shader exactly which square piece of mesh we are giving away. By gradually changing the square with the shift, we get the effect of rotation.



    We apply a shader and distort the original mesh texture using the second texture.



    The noisy black lines are quite clearly visible in the screenshot. They seemed to be covered in pixel waves. Vision does not deceive you, and the problem is in the limitations of dUdV textures. The displacement map has only 256 gradations (as I wrote above), that is, 128 in plus and minus. This is a texture limitation in the A8R8G8B8 format. And the texture size is 512 pixels. Because of this, the gradient is stepwise, in places with repeating pixels. As a result - the picture is distorted as a whole true, but at the pixel level contains artifacts. Of course, the size of the planet and its texture allow us to neglect this visual artifact, but it's a shame.

    Technical details
    In the shader, I tried a little to weaken the effect by taking from a dUdV card not a vector for a specific pixel, but several neighboring ones and interpolated them. Thus averaging the value. Since the video card does not have a limit of 256 gradations (all calculations are performed there with floating-point numbers), the result is closer to what you want.

    In the dUdV card, we have not 2, but 3 channels. RGB Alpha tells the shader about the shape of the planet, smoothing around the edges and therefore is not considered. R and G are used to encode the offset. Another 1 byte remains empty. It could be used to encode an additional 4 bits per color (8 + 4). This would greatly increase accuracy and avoid any problems with flicker and distortion. Obviously, this is the next step in the development of the effect.

    So, move on! Add shading. This can also be done by a shader, but at this stage it is absolutely not important, so I just apply a translucent texture in the subtraction mode:



    Change the texture on the planet’s surface and start playing with it:



    It’s not obvious to everyone what is missing here - the atmosphere. So, as I make a universal class for drawing various types of planets, I will add clouds directly to this one. Clouds are added using the same distortion method as the planet texture itself, but in a separate layer so that the clouds can also be moved and twisted in the other direction than the planet’s surface:



    Combine the clouds with an existing planet. Let's make the clouds 1.5% wider than the planet, they are not very low. And in the end, we get such indecency here:



    What happened, and why is everything so bad? There are clouds, you can even notice the difference between the screenshots, but it is so inconspicuous that everything is really very bad. But all is bad because I did not take into account the fact that the planet can be light and the gray clouds on it can be seen very poorly. On the one hand, this is correct, but clouds do cast a shadow on the surface of the planet, but we don’t. Add another layer of dark clouds under this layer.



    Now the clouds are clearly visible both on the light and on the dark planet. But if there are clouds, then only one thing is missing - the atmosphere. We must add her. The atmosphere should create a little shine on the sunny side, so it must be added in the mode of adding color, not mixing.



    Quite a different matter.

    However, I will return to the above, namely the fact that the class is universal. Let's imagine that the planet is covered with scars, glowing scars. For example, luminous lava, city lights, or, well, I don’t know how, come up with something of your own. And in the air she does not have light clouds, but puffs of black smoke. What can’t you do for the beauty of the game? We will generate such a texture for our planet, leaving all the superimposed effects in place:



    Here. Almost beautiful, but there is one BUT - on the dark side of the planet, nothing glows. We pick up the glow map (also generated along with the surface texture) and add it as well. It would be logical to add a glow already on top of everything, so that no shadows affect the light, right?



    No, not true! I would say that it is beautiful and that it is necessary, but the clouds (smoke) do not block the glow, then the sequence of layers is wrong.

    Obviously, you need to add a rounding shader, applying a shadow to the result depending on whether the layer is shining or not. Otherwise, as you don’t rearrange our layers, you won’t get the desired effect!

    Maybe it's Mars? Well, or something similar, but with an atmosphere and a huge runway in the middle? No, this is not a bug, as it may seem at first, it is such a texture.



    It was here, when it came to something like Mars and Earth, the question arose that the clouds should have been whiter for them. No sooner said than done! And without a home planet, the demo, I think, would be completely unintelligible. Therefore, let’s do the Earth too, which is already there!

    Well, at the same time, it would be necessary to remove the bug that crawls out from above and below the planet. Let's fix it will be prettier.



    Not single planets


    Let's create the scale for the electric gun or mana charge using the same method. This is how anyone likes it more. For clarity, cut a piece from above, we have already spent a little electricity. Be sure to watch the video, as this thing is spinning and looks a bit different than in statics.

    Diablo3:



    Our implementation:



    conclusions


    Yes, what is it here. Shaders are cool. That's all the conclusions! Seriously, without shaders, making a very beautiful and dynamic game will be like hard labor. Almost all the graphics (except the Earth’s surface) are generated and the artist’s intervention does not require. This allows you to make a game with minimal involvement of people from outside.

    Finally, I give the video:


    Also popular now: