Как я могу использовать шейдер в XNA, чтобы раскрасить отдельные пиксели? - PullRequest
11 голосов
/ 03 апреля 2010

У меня есть стандартное окно 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;
                    }
                }
            }
        }

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

Начальная:

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

Final:

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

Обновление:

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

альтернативный текст http://www.refuctored.com/borders.png

альтернативный текст 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));
                }
            }
        }
    }

Ответы [ 4 ]

2 голосов
/ 10 апреля 2010

Этот метод не использует пиксельные шейдеры, но если вы хотите использовать метод SetData для Texture2D вместо вызова 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;
                    }
    }
}
0 голосов
/ 13 апреля 2010

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

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

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

0 голосов
/ 12 апреля 2010

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

0 голосов
/ 07 апреля 2010

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

Создание двух текстур (800x600)

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

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

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

Edit:

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

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

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

_device.SetRenderTarget(0, myRenderTarget);

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

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

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

...