📜 ⬆️ ⬇️

Windows Phone 7 XNA: we are pixels or not shaders

Hello my dear friend.

Again a lot of time passed and I did not please you with interesting information on
about game development. Today I want to dedicate this article to the development of games for WP7 , using
the wonderful XNA framework I wrote about here , here and here and here . Specifically, I will tell you how you can make beautiful effects without shaders . In this article, we consider the effect of distortion . The rest is under the cut.


')


Theory



The last two articles I wrote about shaders and how you can improve the visual perception in your game. But if you look at the comparison of Reach and HiDef XNA profiles, then you can with horror
see that Reach supports Shader Modele 2.0 , and WP7 does not support it at all. And from this I want to take and hit come up with how this can all be done without shaders.

Of course, we are not talking about steep lighting with normal mapping (although you can pervert), but just about how you can bend the pixels using BasicEffect . This method is simple to the point of madness, but extremely effective.

So, if you remember who I am and what I wrote about , you can recall the algorithm that we
used in shaders: there is a distortion map and a color map. According to the distortion map - we are beating the color map Simply? Forget it. This method is extremely difficult to implement under WP7 without the intervention of the GPU (to which we, unfortunately, do not have access).

- How to be a guy?

Recall, on what principle do we draw something in 3D, for example, the usual flat square? It is drawn using two triangles. How can this help us? Everything is simple, we create many triangles, and then, using the texture coordinates, we will move the kakbe triangle itself, which creates a distortion effect.

In fact - there is a picture - 480x800, we create a grid of 48x80 in size (believe me, for a beautiful effect, just right). The grid is a one-dimensional array consisting of 3,840 elements. All this is calculated on WP7 in about 3-4 ms , with a lower grid quality - 1-2 ms . But if the grid is too small, then with distortion it will be noticeable that the triangles still exist. But when the grid is less than 10 times, it is hardly noticeable, for comparison - a step on the screen of 3 mm = 10 pixels. Well, okay, something I talked.

- Hey, dude, enough theory, go to practice.

Practice



To draw anything on the screen of primitives, you need a BasicEffect . For example, spriteBatch is a huge class that hides all kinds of BasicEffect from our eyes, but ultimately it all comes down to drawing primitives, laying textures on them. I will try to explain in more detail about using BasicEffect in our case.

Going on the road, looking for the material.

First we need a texture that we will bend, meet our beloved friend:


And it is somehow strange, but we still need an empty project, we create it.

I’ll say right away that one of the features of XNA under WP7 is that by default there are 30 FPS (in exchange, 60 FPS ). But something tells me that it is possible and more; On the other hand, who needs a killer battery, not a timekiller? Therefore, we will use 30 FPS .

In an empty project you will find:

// Frame rate is 30 fps by default for Windows Phone. TargetElapsedTime = TimeSpan.FromTicks(333333); // Extend battery life under lock. InactiveSleepTime = TimeSpan.FromSeconds(1); 


The line responsible for FPS - guess yourself.

The next moment is the lack of a keyboard, all actions are performed using multitouch.
The only button that can be intercepted is the Back button (by default) - it exits the application:

 if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed) this.Exit(); 


Therefore, we will not touch anything in an empty project, proceed to programming and, in fact, practice. First, let's fill Game1 with meaning and love .

Create variables:

 Texture2D background; BasicEffect basicEffect; 


background - our texture, well, or some kind of RenderTarget.
BasicEffect is our hero, necessary for drawing primitives.

Ship content:

 background = Content.Load<Texture2D>("distortion"); 


I almost forgot, set in the constructor:

 graphics.PreferredBackBufferWidth = 480; graphics.PreferredBackBufferHeight = 800; graphics.IsFullScreen = true; 


So that there was one orientation and there were no problems with positioning.

Initialize BasicEffect (to Initialize) :

 basicEffect = new BasicEffect(GraphicsDevice); basicEffect.TextureEnabled = true; //      basicEffect.Projection = Matrix.CreateOrthographicOffCenter(0, 480, 800, 0, 0f, 10f); basicEffect.View = Matrix.Identity; basicEffect.World = Matrix.Identity; 


Projection is a projection matrix of a three-dimensional object on a two-dimensional plane (screen).
View - view matrix, camera if you want.
World - the world matrix: rotation, size, position.

Let's set View and World - single matrixes.
And Projection, we define an orthogonal projection, i.e. we will have a primitive projected on the screen completely. The ends of the primitive with the ends of the screen, if explained more simply.

So, while with Game1 , let's create a new class GridVertexPositionColorTexture , and here is its full listing (I apologize for the full, but it is with comments):

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework; namespace GridDistortion { public class GridVertexPositionColorTexture { public VertexPositionColorTexture[] Vertices; //   ,      ,    UV   public short[] Indices; // ,       ,   3D ,   ,    public int Width; //    X public int Height; //    Y public Vector2 CellSize; //       X  Y  public void BuildGeometry(int colums, int rows, Vector2 cellSize) //     { Width = colums; Height = rows; CellSize = cellSize; Vertices = new VertexPositionColorTexture[(Width + 1) * (Height + 1)]; //    Indices = new short[Width * Height * 6]; //      /*    */ for (int i = 0; i < Width + 1; i++) { for (int j = 0; j < Height + 1; j++) { int index = j * (Width + 1) + i; VertexPositionColorTexture vertex = new VertexPositionColorTexture() { Position = new Vector3(new Vector2(i, j) * CellSize, 0f), //   Color = Color.White, TextureCoordinate = GetDefaultUV(index) //   }; Vertices[index] = vertex; } } /*    */ int indexPos = 0; for (int i = 0; i < Width; i++) { for (int j = 0; j < Height; j++) { int v0 = j * (Width + 1) + i; int v1 = j * (Width + 1) + i + 1; int v2 = (j + 1) * (Width + 1) + i; int v3 = (j + 1) * (Width + 1) + i + 1; Indices[indexPos] = (short)v0; Indices[indexPos + 1] = (short)v1; Indices[indexPos + 2] = (short)v2; Indices[indexPos + 3] = (short)v2; Indices[indexPos + 4] = (short)v1; Indices[indexPos + 5] = (short)v3; indexPos += 6; } } } public void Draw(GraphicsDevice graphicsDevice) //     VertexPositionColorTexture { graphicsDevice.DrawUserIndexedPrimitives<VertexPositionColorTexture>(PrimitiveType.TriangleList, Vertices, 0, Vertices.Length, Indices, 0, Indices.Length / 3); } public void ResetUVs() //  UV  { for (int i = 0; i < Vertices.Length; i++) { VertexPositionColorTexture v = Vertices[i]; v.TextureCoordinate = GetDefaultUV(i); Vertices[i] = v; } } public Vector2 GetUV0(int index) //    { return Vertices[index].TextureCoordinate; } public void SetUV0(int index, Vector2 value) //    { Vertices[index].TextureCoordinate = value; } public Vector2 GetDefaultUV(int index) //       { int i = index % (Width + 1); int j = index / (Width + 1); return new Vector2((float)i / Width, (float)j / Height); } } } 


Everything is good, the class responsible for drawing primitives and the grid itself is created. Now we need to come up with a controller for this grid, which will bend it. In this article I will tell you about two grid controllers: SimpleGrid , ElasticGrid .

First we will have to reset the grid, apply current distortion to it.
The second will turn our grid into a jelly, which will fluctuate until it comes to a default state.

Let's write the first controller, create a new SimpleGrid class and its listing:

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Xna.Framework; namespace GridDistortion { public class SimpleGrid { publicGridVertexPositionColorTexture grid; //    public SimpleGrid(GridVertexPositionColorTexture grid) { this.grid = grid; } public void Update() { swap(); } public virutal void swap() //       { grid.ResetUVs(); } public void Rebellion(Vector2 pos_rebellion, float radius) //         { Vector2 gridSize = new Vector2(grid.Width, grid.Height) * grid.CellSize; //    for (int i = 0; i < grid.Vertices.Length; i++) { Vector2 pos = grid.GetUV0(i) * gridSize; //       Vector2 newPos = pos; Vector2 center = pos_rebellion; //    float distance = Distance(pos, center); //         if (distance < radius) //    ,    ,   { Vector2 dir = pos - center; //    float length = dir.Length(); float minDisplacement = -length; if (dir.Length() != 0) { dir.Normalize(); //   } Vector2 displacement = dir * Math.Max(-100f, minDisplacement); //   ,  -100f —  ,   —   ,   newPos += displacement * (1f - distance / radius) * 0.25f; grid.SetUV0(i, newPos / gridSize); //    } } } public static float Distance(Vector2 vector1, Vector2 vector2) { double value = ((vector2.X - vector1.X) * (vector2.X - vector1.X)) + ((vector2.Y - vector1.Y) * (vector2.Y - vector1.Y)); return (float)Math.Sqrt(value); } } } 


The controller is written, now back to Game1 , two new variables:

 GridVertexPositionColorTexture grid; SimpleGrid simpleGrid; 


Their initialization:

 grid = new GridVertexPositionColorTexture(); grid.BuildGeometry(48, 80, new Vector2(10, 10)); simpleGrid = new SimpleGrid(grid); 


Update itself:

 simpleGrid.Update(); //    TouchCollection collection = TouchPanel.GetState(); //    foreach (TouchLocation point in collection) { if (point.State == TouchLocationState.Moved) { simpleGrid.Rebellion(point.Position, 100f); //     point.Position   100f } } 


Well, finally drawing:

 GraphicsDevice.SamplerStates[0] = SamplerState.LinearClamp; //  Clamp, ..  Wrap-    Reach ,      basicEffect.Texture = background; //     basicEffect.CurrentTechnique.Passes[0].Apply(); //  basicEffect grid.Draw(GraphicsDevice); //   


Launch, touch the screen and see the distortion or effect of the lens.
But let's have some more fun, make a jelly from texture, the ElasticGrid class, inherited from SimpleGrid :

 using System; using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.Xna.Framework; namespace GridDistortion { public class ElasticGrid : SimpleGrid { private Vector2[] Velocity; //   public ElasticGrid(GridVertexPositionColorTexture grid) : base(grid) { this.Velocity = new Vector2[(grid.Width + 1) * (grid.Height + 1)]; } public override void swap() { Vector2 gridSize = new Vector2(grid.Width, grid.Height) * grid.CellSize; for (int i = 0; i < grid.Vertices.Length; i++) { //Get the position in pixels Vector2 pos = grid.GetUV0(i) * gridSize; Vector2 pos_default = grid.GetDefaultUV(i) * gridSize; Vector2 dir = (pos_default - pos) / 1.1f; //          1.1f, //Set the new Texture Coordinates grid.SetUV0(i, (pos + Velocity[i]) / gridSize); //   +   Velocity[i] = dir; //     } } } } 


Change the grid controller in Game1 and admire the distortion.

Here is such a simple and interesting approach. There can be an infinite number of controllers, for example, realistic water in real-time without any shaders with waves and whores .

At another time, I will try to describe other methods of making your game beautiful.
Also from the XNA series is planned to write articles on the subject of 3D.
Well, as a bonus, the next article will probably be about how you can write a game on WP7, place it in the market for free, without ads and sit in poverty.

Sources can be downloaded here .

Experiment, create; to new meetings :-)

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


All Articles