11
votes

Comment puis-je utiliser un shader dans Xna pour colorer des pixels simples?

J'ai une fenêtre standard 800x600 dans mon projet XNA. Mon objectif est de colorer chaque pixel individuel basé sur un tableau rectangle qui détient des valeurs booléennes. Actuellement, j'utilise une texture 1x1 et dessine chaque sprite dans mon tableau.

Je suis très nouveau à Xna et viennent d'un contexte GDI, alors je fais que j'aurais fait ce que j'aurais fait à GDI, mais ça ne fait pas échouer très bien. On m'a dit dans une autre question d'utiliser un shader, mais après de nombreuses recherches, je n'ai toujours pas été en mesure de savoir comment accomplir cet objectif.

Mon application se bouclait des coordonnées X et Y de Mon tableau rectangulaire, effectue des calculs basés sur chaque valeur et réaffectaient / déplace la matrice autour. À la fin, je dois mettre à jour ma "toile" avec les nouvelles valeurs. Un échantillon plus petit de mon tableau ressemblerait à: xxx

Comment puis-je utiliser un shader pour colorer chaque pixel?

une version très simplifiée des calculs serait: xxx

.. Chaque fois que la méthode de mise à jour est appelée, les calculs sont effectués et, par exemple de la boucle ci-dessus, une mise à jour peut ressembler à:

initiale: xxx

premier: xxx

secondaire: xxx

finale: xxx

mise à jour:

Après avoir appliqué le code rendu2dtarget et placer mes pixels, je me retrouve avec une bordure indésirable sur mes pixels , toujours à gauche. Comment puis-je supprimer cela?

ALT Texte http://www.refuctored.com /borders.png

Texte alt http: //www.refuctored .com / Fallingdirt.png

La partie du code d'application des textures est la suivante: xxx


4 commentaires

Souhaitez-vous partager les calculs que vous effectuez sur votre tableau rectangulaire? Si vous pouvez effectuer ces calculs à l'intérieur du shader, il peut ne pas être trop difficile de comprendre. Je rencontre des difficultés à comprendre comment passer un tableau de taille 800x600 sur le shader car les tableaux sont limités à 65536 indices (indice 16 bits), apparemment; Donc, si vous pouviez simplement effectuer les calculs à l'intérieur du shader sans avoir à passer le grand tableau à chaque fois, il serait plus facile de comprendre.


J'ai mis à jour la question avec quelques informations supplémentaires pour votre demande de renseignements.


En fait, désolé, maintenant que j'y pense que l'approche n'est pas une bonne idée de plusieurs raisons. Il faut qu'une texture représente l'état initial de votre réseau, le shader de pixel ne peut pas modifier la texture, il ne peut renvoyer que des valeurs différentes, de sorte que chaque fois qu'il a été appelé, il serait comme à partir de l'état initial, et il fait tout Les calculs de la fonction Draw () au lieu de la fonction de mise à jour (), ce qui n'est probablement pas une bonne idée.


Appelez-vous le tirage au sort une fois Pr. pixel ou je manque quelque chose? Si oui, c'est un très grand tueur de performance. Vous devez remplir votre texture à l'aide de la méthode SetData.


4 Réponses :


0
votes

Que diriez-vous ...

Créer deux textures (800x600) p>

Initialisez l'une d'entre elles aux valeurs initiales. P>

pour chaque image que vous rendez une texture au Autre Tout en mettant à jour les valeurs dans un pixelshader. P>

Après avoir rendu la texture résultante à l'écran, vous les échangez, ils sont donc prêts pour votre cadre suivant. P>

EDIT: strong> p>

Vous aurez besoin de deux cas de RenderTarget2D et de les créer avec RenderTargetusage.Presservecontents. Vous pouvez commencer avec superformat.color et utiliser noir pour 0 et blanc pour 1. (Vous pouvez également trouver un format 8 bits pour enregistrer la mémoire vidéo.) P>

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


10 commentaires

Je ne suis pas un expert en shader, mais je ne pense pas que vous puissiez manipuler les textures sous-jacentes dans un sommet ou un shader de pixel. Par conséquent, vous devez modifier les textures du code de jeu, ce qui serait quelque chose comme texture2d.setdata (). Toutefois, si vous avez une texture de 800x600 définie en fonction de vos valeurs de matrice, vous n'avez même pas besoin d'un shader de pixel. Il suffit d'utiliser SpritTarbatch pour dessiner cette texture à l'écran à chaque fois.


Vous pouvez rendre une texture en lisant une autre texture. Donc, il ne modifie pas la texture, mais générer une nouvelle texture chaque image. C'est pourquoi je voudrais utiliser deux textures et les échangeriez.


Je serais intéressé (et peut-être que l'affiche serait aussi bien) de voir comment cela se fait. Avez-vous des liens vers des exemples que vous pouvez poster? Merci!


Merci d'avoir posté le code! J'ai quelques questions de plus. Settexture est-elle une fonction de membre d'une classe? Je n'ai pas trouvé de documentation XNA sur cette fonction que vous l'avez utilisée. De plus, si vous regardez sa fonction de mise à jour, il fait référence et met à jour les pixels adjacents. Je pensais qu'un pixel shader n'était capable de retourner la valeur d'un pixel à la fois. Comment le code dans son pixel Shader serait-il capable de calculer la valeur d'un pixel à proximité et de définir cette valeur spécifique lorsqu'elle est censée renvoyer la valeur du pixel actuel qu'il traite?


Argh, le monstre de copie-coller l'a fait ... C'était simplement une méthode d'emballage pour définir une texture sur l'appareil. Ont édité le message maintenant.


Merci, c'était le chemin que je descendais réellement. Semble fonctionner un peu plus vite. Le seul problème que j'ai maintenant, c'est qu'un contour noir est attiré autour des bords de mes pixels. Savez-vous ce qui cause ce comportement?


Ressemble à une erreur obsolète. Vos graphiques sont-ils coupés à droite / en bas? Si oui, vous devez ajouter un pixel à vos coordonnées de la texture de votre masque ou assurez-vous que vous calculez correctement les coordonnées de la texture.


J'ai vérifié tout, et comme je fais un pixel 1x1, je ne vois pas où quelque chose pouvait être éteint. Mes 1x1 pixels, une fois qu'ils sont combinés dans un groupe, tels que le petit monticule de la saleté montrant dans le graphique ci-dessus, une fois combiné, rend la frontière disparaître, à l'exception de gauche.


Cela pourrait être causé par vos coordonnées de texture. Essayez de les déplacer un demi-pixels. (Par exemple 0.5F / 800.0F)


Vérifiez votre GetGrainColor (x, y) méthode pour vous assurer que vous n'êtes pas éteint par un calcul de la couleur appropriée pour votre grain. En tant que note latérale, vous ne devriez pas appeler dessiner () pour chaque pixel, créez-le à la mémoire de votre texture, remplissez-la avec votre méthode SetPixels , puis téléchargez la totalité de la chose. à la fois à une texture.



2
votes

Cette méthode n'utilise pas de pixels shaders, mais si vous cherchez à utiliser la méthode SetData de Texture2D au lieu de passer un appel à Spritbatch.Draw () pour chaque pixel, vous pouvez le trouver utile. J'ai utilisé un tableau d'UINT au lieu de Bool pour représenter vos couleurs. Si vous pouvez vous échapper avec une texture de couleur 8 bits, vous pourriez peut-être accélérer cela en modifiant le format de texture.

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;
                    }
    }
}


3 commentaires

Je ne pourrais pas être plus d'accord. Pour faire fonctionner rapidement le GPU, la voie à suivre est de laisser le CPU télécharger vos données comme une texture. L'utilisation d'une texture de couleur indexée est probablement le scénario le plus rapide, mais les couleurs 32 bits seront probablement bien performantes. Les shaders sont amusants à écrire, mais ils n'ajoutent probablement pas de valeur dans ce scénario.


Par intérêt ... Comment sauriez-vous que la texture à supprimer de l'appareil est à l'index [0]?


Je pense que parce que c'est un exemple aussi simple et une seule texture est tiré que vous pouvez supposer que cela va être à l'index 0. Si l'exemple avait plus d'une texture, je suppose que je suppose que elles sont dans le dispositif graphique de L'ordre qu'ils sont tirés, mais je n'ai pas trouvé de source pour soutenir cela, désolé.



0
votes

Qu'est-ce que vous essayez de faire (dessiner Pixel de Pixel) est ce que DirectX fait. XNA est une couche qui est construite sur DirectX afin que vous n'ayez pas à dessiner pixel par pixel. Si c'est vraiment ce que vous voulez faire, vous devriez probablement apprendre DirectX au lieu de XNA. Vous le trouveriez probablement beaucoup plus facilement ...


0 commentaires

0
votes

Ajoutez un panneau d'affichage à votre scène (directement devant la caméra, de sorte qu'il faut exactement 800 x 600 pixels).

lier le tableau binaire comme une texture.

Dans le fragment (/ pixel) Shader Calculez la couleur du fragment à l'aide de la position 2D du fragment et de la texture.


0 commentaires