XNA Draw or write a particle system. Part II: Shaders

  • Tutorial
Hello to all game developers and just people who are interested in game dev.

It's time to tell you about pixel shaders and how to do post-proccesing. This is the second part of the article on graphical methods in XNA, in the last article - we examined the Draw and Begin methods of spriteBatch. For example: we will improve our particle system by adding a pixel shader that will distort the space.







In this part:
  • What is a pixel shader?
  • What is post-processing
  • Briefly: What is RenderTarget2D and what do they eat it with
  • Distorting shader with Displacemenet-map
  • Practice: modifying the particle system


Shader



Let's talk a little about shaders. There are two types of shaders (in Shader Model 2.0, which we use): vertex and pixel.

The vertex shader operates on data mapped to the vertices of the polyhedra. Such data, in particular, include the coordinates of the vertex in space, texture coordinates, tangent vector, binormal vector, normal vector. A vertex shader can be used for species and perspective transformation of vertices, generation of texture coordinates, calculation of lighting, etc.

Pixel shadersare performed for each fragment in the phase of rasterization of triangles. A fragment (or pixel) is a point, with window coordinates, received by the rasterizer after performing a series of operations on it. Simply put, the resulting point in the frame buffer, the combination of these points then forms the image. Pixel shaders operate on fragments until the final stages, i.e. to depth tests, alpha and stencil. The pixel shader receives interpolated data (color, texture coordinates) from the vertex shader.



If to say very briefly about the pixel shader, then this is the finished image processor.

In the case of the Displacement shader, vertex shaders are not needed, let's consider pixel shaders .

Post processing


If to be a lazy concise person, then Post-processing shaders are executed when the entire picture of the game is already drawn: the shader is superimposed immediately on the whole picture, not on separate sprites.

- Because spriteBatch.Begin has a parameter, effect, isn’t it easier to apply a shader right away, how do we draw it?
I answer: it ’s precisely that such a shader is applied to single sprites, as a result, the Displacement shader will function crookedly .

To create a Post-process processing, you must first draw what should be drawn on the screen - on a separate texture, and then draw this very texture using the Post-process shader. Thus, the shader does not act on single sprites, but on the picture as a whole.

-Stop, but how to draw on a separate texture?
The answer is: meet RenderTarget2D

RenderTarget2D



And again, hello my friend - brevity. RenderTarget2D - essentially a texture that you can draw on.

We go to where we usually draw the scene, before cleaning, we insert:

GraphicsDevice.SetRenderTarget(renderTarget);


Now everything will be drawn not on the screen, but on RenderTarget2D.
To switch back to the screen, we use the construction:

GraphicsDevice.SetRenderTarget(null);


Remember to clear the RenderTarget before drawing.

Distorting shader with Displacemenet-map



The idea of ​​such a pixel shader is very simple: the input is a texture that needs to be “bent”, the second input is a map about how to bend.

We will generate a map about how - in practice.

By the way, about the map. The map is the same size image as the texture of the scene, except, perhaps, that we will not see the drawn image.

More similarly about the map and how the shader works:

In the process of image processing - we get the current pixel position, we get the color. We do the same for the card. Those. ultimately, we will have available for modification: pixel color, pixel position, pixel color on the map of the corresponding pixel position in the image.

We will use the colors of the map to convey information to the shader how to bend a pixel.

For example, the R-channel (red) receives values ​​from 0f to 1f. If we see distortions R = 0.5f on the map, then we simply shift the position of the image pixel by 10f * 0.5f pixels. 10f is the force with which we move.

Accordingly, the R-channel will correspond to the X coordinate, and the G-channel will correspond to Y.

If you need pictures, get them:

Original picture:


Map:


Final picture:


So, we sort of figured out the theory, now let's try to implement it all with code.

Action plan:
  • Program the shader.
  • We implement post-processing
  • Creates another system of particles, but this time unusual, these particles will be drawn into a map for the shader.
  • We pass the map to the shader and apply it with the Post-process drawing.
  • ???
  • PROFIT!


Practice: modifying the particle system



We are finalizing the source code from the previous article .
Immediately add some picture so that the distortions are noticeable, for example this one:



Copy the ParticleController and call it ShaderController , in it we need to change only the process of creating the particle, namely :

public void EngineRocketShader(Vector2 position) // функция, которая будет генерировать частицы шейдера
{
            for (int a = 0; a < 2; a++) // создаем 2 частицы дыма для трейла
            {
                Vector2 velocity = AngleToV2((float)(Math.PI * 2d * random.NextDouble()), 1.6f);
                float angle = (float)(Math.PI * 2d * random.NextDouble());
                float angleVel = 0;
                Vector4 color = new Vector4((float)random.NextDouble(), (float)random.NextDouble(), 1f, (float)random.NextDouble()); // задаем случайными R и G и A каналы.
                float size = 1f;
                int ttl = 80;
                float sizeVel = 0;
                float alphaVel = 0.01f;
                GenerateNewParticle(smoke, position, velocity, angle, angleVel, color, size, ttl, sizeVel, alphaVel);
            }
}


We implement post-processing , create new variables:

RenderTarget2D shader_map; // карта для шейдера
        RenderTarget2D renderTarget; // готовое к обработке изображение


We initialize them:

shader_map = new RenderTarget2D(GraphicsDevice, 800, 600);
renderTarget = new RenderTarget2D(GraphicsDevice, 800, 600);


We go to the Draw method of the main class and write:

protected override void Draw(GameTime gameTime)
        {
            GraphicsDevice.SetRenderTarget(renderTarget); // рисуем в renderTarget
            GraphicsDevice.Clear(Color.Black);
            spriteBatch.Begin();
            spriteBatch.Draw(background, new Rectangle(0, 0, 800, 600), Color.White);
            spriteBatch.End();
            part.Draw(spriteBatch);
            GraphicsDevice.SetRenderTarget(shader_map); // рисуем в карту шейдера
            GraphicsDevice.Clear(Color.Black);
            shad.Draw(spriteBatch);
            GraphicsDevice.SetRenderTarget(null); // рисуем в сцену
            GraphicsDevice.Clear(Color.Black);
            spriteBatch.Begin();
                spriteBatch.Draw(renderTarget, new Rectangle(0, 0, 800, 600), Color.White);
            spriteBatch.End();
            base.Draw(gameTime);
        }


Post-processing is ready, now let's create a shader .

Create a new Effect (fx) file ( this is a shader file written in HLSL ), enter it there, something like:

texture displacementMap; // наша карта
sampler TextureSampler : register(s0); // тут та текстура, которая отрисовалась на экран
sampler DisplacementSampler : samplerState{ // устанавливаем TextureAddress
Texture = displacementMap;
MinFilter = Linear;
MagFilter = Linear;
AddressU = Clamp;
AddressV = Clamp;
};
float4 main(float4 color : COLOR0, float2 texCoord : TEXCOORD0) : COLOR0
{
   /* PIXEL DISTORTION BY DISPLACEMENT MAP */
    float3 displacement = tex2D(DisplacementSampler, texCoord); // получаем R,G,B из карты
    // Offset the main texture coordinates.
    texCoord.x += displacement.r * 0.1; // меняем позицию пикселя
    texCoord.y += displacement.g * 0.1; // меняем позицию пикселя
   float4 output = tex2D(TextureSampler, texCoord); // получаем цвет для нашей текстуры
    return color * output;
}
technique DistortionPosteffect
{
    pass Pass1
    {
        PixelShader = compile ps_2_0 main(); // компилируем шейдер
    }
}


The shader is created, you can load it in the same way as a regular texture, except that the type is not Texture2D , but Effect .

Now update our Draw :

effect1.Parameters["displacementMap"].SetValue(shader_map); // задаем карту шейдеру
            spriteBatch.Begin(SpriteSortMode.Immediate, BlendState.Opaque, SamplerState.LinearClamp, DepthStencilState.None, RasterizerState.CullCounterClockwise, effect1); // рисуем с приминением шейдера
                spriteBatch.Draw(renderTarget, new Rectangle(0, 0, 800, 600), Color.White);
            spriteBatch.End();


We start, admire beautiful, realistic animal distortions (it is better to see the demo ) :


In fact, this implementation of the particle system (not shaders, but what was in the first lesson) as a whole is not very good for performance. There are other methods that are more difficult to understand, I will talk about them later.

I am applying source codes and a demo (this time, it will run on any computer with XNA 4.0 and hardware support DirectX9, inc sh 2.0)

Maybe this week, it may not be known when - I will talk about the Update method and how to implement physics using Box2D .

Good luck and again withprogrammer's holiday 0xFF + 1 afternoon! ;)

Also popular now: