2010-04-03 14 views
11

Mam standardowe okno 800x600 w moim projekcie XNA. Moim celem jest pokolorowanie każdego pojedynczego piksela na podstawie tablicy prostokątów, która przechowuje wartości logiczne. Obecnie używam tekstury 1x1 i rysuję każdy sprite w mojej tablicy.Jak używać Shadera w XNA do kolorowania pojedynczych pikseli?

Jestem bardzo nowy w XNA i pochodzę z tła GDI, więc robię to, co bym zrobił w GDI, ale nie skaluje się zbyt dobrze. W innym pytaniu powiedziano mi, żeby użyć Shadera, ale po wielu badaniach wciąż nie byłem w stanie dowiedzieć się, jak osiągnąć ten cel.

Moja aplikacja wykonuje pętle poprzez współrzędne X i Y mojej tablicy prostokątnej, wykonuje obliczenia na podstawie każdej wartości i ponownie przypisuje/przesuwa tablicę. Na koniec muszę zaktualizować moje "Canvas" o nowe wartości. Mniejszy przykład mojej tablicy będzie wyglądał następująco:

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 

Jak mogę użyć modułu cieniującego do pokolorowania każdego piksela?

bardzo uproszczoną wersję obliczeń byłoby:

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

..each razem Sposób aktualizacji zwany obliczenia są wykonywane w przykładzie powyżej pętli aktualizacji może wyglądać następująco:

początkowa:

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 

pierwsze:

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 

drugie:

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 

końcowa:

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 

Aktualizacja:

Po zastosowaniu kodu Render2DTarget i umieszczenie moich pikseli, ja skończyć z niechcianym granicy na moich pikseli, zawsze w lewo. Jak mogę to usunąć?

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

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

jedne z kodem do nakładania tekstur jest:

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)); 
       } 
      } 
     } 
    } 
+0

Czy mógłbyś dzielić się obliczeniami, które wykonujesz na macierzy prostokątnej? Jeśli możesz wykonać te obliczenia wewnątrz modułu cieniującego, może nie być zbyt trudne do wymyślenia. Mam problem z ustaleniem, jak przekazać tablicę o wymiarach 800x600 do modułu cieniującego, ponieważ tablice są ograniczone do 65536 indeksów (16-bitowy indeks), najwyraźniej; więc gdybyś mógł po prostu wykonać obliczenia wewnątrz modułu cieniującego bez konieczności przesuwania dużej tablicy za każdym razem, łatwiej byłoby to rozgryźć. – Venesectrix

+0

Mam zaktualizowane pytanie z kilkoma dodatkowymi informacjami do zapytania. –

+0

Właściwie, przepraszam, teraz, kiedy myślę o tym, że podejście nie jest dobrym pomysłem z wielu powodów. Wymaga to, aby tekstura reprezentowała początkowy stan twojej macierzy, pikselowy moduł cieniujący nie może modyfikować tekstury, może zwracać tylko różne wartości, więc za każdym razem, gdy został wywołany, będzie to jak rozpoczęcie od stanu początkowego, i to wszystko obliczenia w funkcji draw() zamiast funkcji update(), co prawdopodobnie nie jest dobrym pomysłem. – Venesectrix

Odpowiedz

2

Ta metoda nie używa cieniowania pikseli, ale jeśli chcesz użyć metody SetData Texture2D zamiast wywoływania SpriteBatch.Draw() dla każdego piksela, może się okazać, że jest to przydatne. Użyłem tablicy uint zamiast bool do reprezentowania twoich kolorów. Jeśli uda Ci się uciec 8-bitową kolorową teksturą, możesz przyspieszyć tę zmianę, zmieniając format tekstury.

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

Nie można się zgodzić na więcej. Aby sprawić, by elementy GPU działały szybko, należy pozwolić procesorowi przesłać dane jako teksturę. Używanie indeksowanej tekstury kolorów jest prawdopodobnie najszybszym scenariuszem, ale 32-bitowe kolory prawdopodobnie będą działały dobrze. Shadery są fajne do napisania, ale prawdopodobnie nie dodają wartości w tym scenariuszu. –

+0

Interesujący ... Skąd wiesz, że teksturę do usunięcia z urządzenia znajduje się w indeksie [0]? – izb

+0

Myślę, że ponieważ jest to tak prosty przykład i rysowana jest tylko jedna tekstura, którą można założyć, że będzie to indeks 0. Jeśli przykład miałby więcej niż jedną teksturę, to zakładam, że są one w urządzeniu graficznym w kolejności, w jakiej są rysowane, ale nie znalazłem źródła, które by to poparło, przepraszam. – Venesectrix

0

Jak o tym ...

Utwórz dwa tekstur (800x600)

Inicjalizuj o f je do wartości początkowych.

Dla każdej klatki renderujesz jedną teksturę do drugiej podczas aktualizowania wartości w pikselu.

Po wyświetleniu tekstury wynikowej na ekranie, możesz zamienić je, aby były gotowe do następnej klatki.

Edit:

Potrzebne będą dwie instancje RenderTarget2D i stworzyć im RenderTargetUsage.PreserveContents. Można zacząć korzystać z SurfaceFormat.Color i czarny 0 i bieli 1. (może także być w stanie znaleźć formatu 8 bit do zapisywania pamięci wideo.)

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

przypisać je do rendertaget jak ta :

_device.SetRenderTarget(0, myRenderTarget); 

użyć RenderTarget2D jako tekstury, tak:

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

nadzieję, że pomoże ... mogę odkopać bardziej od mojego silnika, więc po prostu zapytać.

+0

Nie jestem ekspertem od cieniowania, ale nie sądzę, aby można było manipulować leżącymi pod nim teksturami w shaderów wierzchołków lub pikseli. Dlatego musisz zmodyfikować tekstury w kodzie gry, co byłoby podobne do Texture2D.SetData(). Jeśli jednak masz zdefiniowaną teksturę 800x600 na podstawie wartości tablicy, nie potrzebujesz nawet modułu cieniującego pikseli. Wystarczy użyć Spritebatch, aby narysować tę teksturę na ekranie za każdym razem. – Venesectrix

+0

Możesz renderować teksturę podczas odczytu z innej tekstury. Nie modyfikuje to tekstury, ale tworzy nową teksturę każdej klatki.Dlatego użyję dwóch tekstur i zamienię je. – LaZe

+0

Byłbym zainteresowany (a może plakat również), aby zobaczyć, jak to się robi. Czy masz jakieś linki do przykładów, które możesz publikować? Dzięki! – Venesectrix

0

To, co próbujesz zrobić (rysuj piksel po pikselu) jest tym, co robi DirectX. XNA jest warstwą zbudowaną na DirectX, dzięki czemu nie trzeba rysować piksel po pikselu. Jeśli tak naprawdę chcesz to zrobić, prawdopodobnie powinieneś nauczyć się DirectX zamiast XNA. Prawdopodobnie będzie Ci łatwiej ...

0

Dodaj billboard do swojej sceny (bezpośrednio przed kamerą, tak aby zajmował dokładnie 800 x 600 pikseli).

Powiąż binarną tablicę jako teksturę.

W shaderach fragmentów (/ pikseli) obliczyć kolor fragmentu za pomocą pozycji 2D fragmentu i tekstury.

Powiązane problemy