Как я могу использовать Shader в XNA для цветных одиночных пикселей?

У меня есть стандартное окно 800x600 в моем проекте XNA. Моя цель - раскрашивать каждый отдельный пиксель на основе массива прямоугольников, который содержит булевские значения. В настоящее время я использую текстуру 1x1 и рисую каждый спрайт в моем массиве.

Я очень новичок в XNA и исхожу из GDI, поэтому я делаю то, что я сделал бы в GDI, но он не очень хорошо масштабируется. Мне было сказано в другом вопросе использовать шейдер, но после долгих исследований я все еще не смог выяснить, как достичь этой цели.

Мое приложение проходит через координаты X и Y моего прямоугольного массива, выполняет вычисления на основе каждого значения и переназначает/перемещает массив вокруг. В конце мне нужно обновить "Canvas" новыми значениями. Меньшая выборка моего массива будет выглядеть так:

0,0,0,0,0,0,0
0,0,0,0,0,0,0
0,0,0,0,0,0,0
1,1,1,1,1,1,1
1,1,1,1,1,1,1

Как я могу использовать шейдер для цвета каждого пикселя?

Очень упрощенная версия вычислений будет:

        for (int y = _horizon; y >= 0; y--)  // _horizon is my ending point
        {
            for (int x = _width; x >= 0; x--) // _width is obviously my x length.
            {
                if (grains[x, y] > 0)
                {
                    if (grains[x, y + 1] == 0)
                    {
                        grains[x, y + 1] = grains[x, y];
                        grains[x, y] = 0;
                    }
                }
            }
        }

.. каждый раз, когда вызывается метод update, вычисления выполняются и в примере выше цикла, обновление может выглядеть так:

Начальное значение:

0,0,0,1,0,0,0
0,0,0,0,0,0,0
0,0,0,0,0,0,0
1,1,1,0,1,1,1
1,1,1,1,1,1,1

Во-первых:

0,0,0,0,0,0,0
0,0,0,1,0,0,0
0,0,0,0,0,0,0
1,1,1,0,1,1,1
1,1,1,1,1,1,1

Во-вторых:

0,0,0,0,0,0,0
0,0,0,0,0,0,0
0,0,0,1,0,0,0
1,1,1,0,1,1,1
1,1,1,1,1,1,1

Финал:

0,0,0,0,0,0,0
0,0,0,0,0,0,0
0,0,0,0,0,0,0
1,1,1,1,1,1,1
1,1,1,1,1,1,1

Update:

После применения кода Render2DTarget и размещения моих пикселей я получаю нежелательную границу на моих пикселях, всегда влево. Как я могу удалить это?

alt text http://www.refuctored.com/borders.png

alt text http://www.refuctored.com/fallingdirt.png

Некоторым кодом для применения текстур является:

    RenderTarget2D target;
    Texture2D texture;
    protected override void LoadContent()
    {
        spriteBatch = new SpriteBatch(GraphicsDevice);
        texture = Content.Load<Texture2D>("grain");
        _width = this.Window.ClientBounds.Width - 1;
        _height = this.Window.ClientBounds.Height - 1;
        target = new RenderTarget2D(this.GraphicsDevice,_width, _height, 1, SurfaceFormat.Color,RenderTargetUsage.PreserveContents);
     }

 protected override void Draw(GameTime gameTime)
    {
        this.GraphicsDevice.SetRenderTarget(0, target);
        this.GraphicsDevice.SetRenderTarget(0, null);
        this.GraphicsDevice.Clear(Color.SkyBlue);
        this.spriteBatch.Begin(SpriteBlendMode.None,SpriteSortMode.Deferred,SaveStateMode.None);
        SetPixels(texture);
        this.spriteBatch.End();
    }

 private void SetPixels(Texture2D texture)
    {
        for (int y = _grains.Height -1; y > 0; y--)
        {
            for (int x = _grains.Width-1; x > 0; x--)
            {
                if (_grains.GetGrain(x, y) >0)
                {
                    this.spriteBatch.Draw(texture, new Vector2(x,y),null, _grains.GetGrainColor(x, y));
                }
            }
        }
    }

Ответ 1

Этот метод не использует пиксельные шейдеры, но если вы хотите использовать метод Texture2D SetData вместо вызова SpriteBatch.Draw() для каждого пикселя, вы можете найти это полезным. Я использовал массив uint вместо bool для представления ваших цветов. Если вы можете уйти с 8-битной текстурой цвета, вы могли бы ускорить это, изменив формат текстуры.

public class Game1 : Microsoft.Xna.Framework.Game
{
    // Set width, height
    const int WIDTH = 800;
    const int HEIGHT = 600;

    // Used to randomly fill in initial data, not necessary
    Random rand;

    // Graphics and spritebatch
    GraphicsDeviceManager graphics;
    SpriteBatch spriteBatch;

    // Texture you will regenerate each call to update
    Texture2D texture;

    // Data array you perform calculations on
    uint[] data;

    // Colors are represented in the texture as 0xAARRGGBB where:
    // AA = alpha
    // RR = red
    // GG = green
    // BB = blue

    // Set the first color to red
    const uint COLOR0 = 0xFFFF0000;

    // Set the second color to blue
    const uint COLOR1 = 0xFF0000FF;

    public Game1()
    {
        graphics = new GraphicsDeviceManager(this);
        Content.RootDirectory = "Content";

        // Set width, height
        graphics.PreferredBackBufferWidth = WIDTH;
        graphics.PreferredBackBufferHeight = HEIGHT;
    }

    protected override void Initialize()
    {
        base.Initialize();

        // Seed random, initialize array with random picks of the 2 colors
        rand = new Random((int)DateTime.Now.Ticks);
        data = new uint[WIDTH * HEIGHT];
        loadInitialData();
    }

    protected override void LoadContent()
    {
        // Create a new SpriteBatch, which can be used to draw textures.
        spriteBatch = new SpriteBatch(GraphicsDevice);

        // Create a new texture
        texture = new Texture2D(GraphicsDevice, WIDTH, HEIGHT);
    }

    protected override void Update(GameTime gameTime)
    {
        // Allows the game to exit
        if (GamePad.GetState(PlayerIndex.One).Buttons.Back == ButtonState.Pressed)
            this.Exit();

        // Run-time error without this
        // Complains you can't modify a texture that has been set on the device
        GraphicsDevice.Textures[0] = null;

        // Do the calculations
        updateData();

        // Update the texture for the next time it is drawn to the screen
        texture.SetData(data);

        base.Update(gameTime);
    }

    protected override void Draw(GameTime gameTime)
    {
        // Draw the texture once
        spriteBatch.Begin();
        spriteBatch.Draw(texture, Vector2.Zero, Color.Purple);
        spriteBatch.End();

        base.Draw(gameTime);
    }

    private void loadInitialData()
    {
        // Don't know where the initial data comes from
        // Just populate the array with a random selection of the two colors
        for (int i = 0; i < WIDTH; i++)
            for (int j = 0; j < HEIGHT; j++)
                data[i * HEIGHT + j] = rand.Next(2) == 0 ? COLOR0 : COLOR1;

    }

    private void updateData()
    {
        // Rough approximation of calculations
        for(int y = HEIGHT - 1; y >= 0; y--)
            for (int x = WIDTH - 1; x >= 0; x--)
                if (data[x * HEIGHT + y] == COLOR1)
                    if (y + 1 < HEIGHT && data[x * HEIGHT + (y + 1)] == COLOR0)
                    {
                        data[x * HEIGHT + (y + 1)] = data[x * HEIGHT + y];
                        data[x * HEIGHT + y] = COLOR0;
                    }
    }
}

Ответ 2

Как насчет этого...

Создайте две текстуры (800x600)

Инициализировать один из них до начальных значений.

Для каждого кадра вы визуализируете одну текстуру в другой, обновляя значения в пикселеhader.

После рендеринга полученной текстуры на экран вы меняете их, чтобы они были готовы для следующего кадра.

Edit:

Вам понадобятся два экземпляра RenderTarget2D и их создание с помощью RenderTargetUsage.PreserveContents. Вы можете начать с SurfaceFormat.Color и использовать черный для 0 и белый для 1. (Вы также можете найти 8-битный формат для сохранения видеопамяти.)

new RenderTarget2D(_device, 800, 600, 1, SurfaceFormat.Color, RenderTargetUsage.PreserveContents);

Вы назначаете их rendertaget следующим образом:

_device.SetRenderTarget(0, myRenderTarget);

Вы используете RenderTarget2D как текстуру, например:

_device.Textures[0] = myRenderTarget.GetTexture();

Надеюсь, что это поможет... Я могу извлечь больше из своего движка, поэтому просто спроси.

Ответ 3

То, что вы пытаетесь сделать (рисовать по пикселям), - это то, что делает DirectX. XNA - это слой, который построен поверх DirectX, так что вам не нужно рисовать пиксель за пикселем. Если это действительно то, что вы хотите сделать, вам, вероятно, следует изучить DirectX вместо XNA. Вероятно, вам будет намного легче...

Ответ 4

Добавьте рекламный щит к своей сцене (прямо перед камерой, чтобы он занимал ровно 800x600 пикселей).

Свяжите двоичный массив как текстуру.

В фрагменте (/пикселе) шейдер вычисляет цвет фрагмента, используя 2D-положение фрагмента и текстуры.