2010-04-03 10 views
11

Tengo una ventana estándar de 800x600 en mi proyecto XNA. Mi objetivo es colorear cada píxel individual en función de una matriz rectangular que contenga valores booleanos. Actualmente estoy usando una textura 1x1 y dibujo cada sprite en mi matriz.¿Cómo puedo usar un Shader en XNA para colorear píxeles individuales?

Soy muy nuevo en XNA y vengo de un fondo GDI, así que estoy haciendo lo que habría hecho en GDI, pero no escala muy bien. Me han dicho en otra pregunta que use un Shader, pero después de mucha investigación, todavía no he podido averiguar cómo lograr este objetivo.

Mi aplicación recorre las coordenadas X e Y de mi matriz rectangular, realiza cálculos basados ​​en cada valor y reasigna/mueve la matriz. Al final, tengo que actualizar mi "Lienzo" con los nuevos valores. Una muestra más pequeña de mi matriz se vería así:

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 

¿Cómo puedo usar un sombreador para colorear cada píxel?

Una versión muy simplificada de los cálculos sería:

 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 vez que el método de actualización se llama, los cálculos se realizan y en el ejemplo del ciclo anterior, una actualización pueden verse como:

inicial:

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 

primero:

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 

Segundo:

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 

Actualización:

Después de aplicar el código Render2DTarget y colocando mis píxeles, termino con un borde no deseado en mis píxeles, siempre a la izquierda. ¿Cómo puedo eliminar esto?

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

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

La parte del código para la aplicación de las texturas es:

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

¿Te importaría compartir los cálculos que realizas en tu matriz rectangular? Si puede hacer esos cálculos dentro del sombreador, puede que no sea demasiado difícil de descifrar. Tengo problemas para averiguar cómo pasar una matriz de tamaño 800x600 al sombreador porque las matrices están limitadas a 65536 índices (índice de 16 bits), al parecer; por lo tanto, si pudiera realizar los cálculos dentro del sombreador sin tener que pasar la matriz grande en cada momento, sería más fácil averiguarlo. – Venesectrix

+0

He actualizado la pregunta con más información de apoyo para su consulta. –

+0

En realidad, lo siento, ahora que lo pienso, ese enfoque no es una buena idea por varias razones. Requiere que una textura represente el estado inicial de su matriz, el sombreador de píxeles no puede modificar la textura, solo puede devolver valores diferentes, por lo que cada vez que se llamó sería como comenzar desde el estado inicial, y lo hace todo los cálculos en la función draw() en lugar de la función update(), que probablemente no es una buena idea. – Venesectrix

Respuesta

2

Este método no usa sombreadores de píxeles, pero si está buscando utilizar el método SetData de Texture2D en lugar de hacer una llamada a SpriteBatch.Draw() para cada píxel, puede que le resulte útil. Usé una matriz de uint en lugar de bool para representar tus colores. Si puede salirse con una textura de color de 8 bits, es posible que pueda acelerar esto cambiando el formato de textura.

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

No podría estar más de acuerdo. Para hacer que las GPU funcionen rápidamente, el camino a seguir es dejar que la CPU cargue tus datos como una textura. Usar una textura de color indexada es probablemente el escenario más rápido, pero los colores de 32 bits probablemente funcionen bien. Los sombreadores son divertidos de escribir, pero probablemente no agreguen valor en este escenario. –

+0

Fuera de interés ... ¿Cómo sabría usted que la textura para eliminar del dispositivo está en el índice [0]? – izb

+0

Creo que es un ejemplo tan simple y solo se dibuja una textura que puede asumir que va a estar en el índice 0. Si el ejemplo tiene más de una textura, entonces supongo que asumiría que están en el dispositivo gráfico en el orden en que se dibujan, pero no he encontrado una fuente para respaldar eso, lo siento. – Venesectrix

0

¿Qué tal esto ...

Cree dos texturas (800x600)

Inicializa uno o f ellos a los valores iniciales.

Para cada cuadro, renderiza una textura a la otra mientras actualiza los valores en un pixelder.

Después de mostrar la textura resultante a la pantalla, las cambia, por lo que están listas para su siguiente fotograma.

Editar:

Se necesitan dos casos de RenderTarget2D y crearlos con RenderTargetUsage.PreserveContents. Se podría empezar con SurfaceFormat.Color y usar negro de 0 y blanco para 1. (También puede ser capaz de encontrar un formato de 8 bits para ahorrar memoria de vídeo.)

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

Se les asigna a la rendertaget como esto :

_device.SetRenderTarget(0, myRenderTarget); 

se utiliza un RenderTarget2D como una textura, como esto:

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

Espero que ayude ... me puede cavar más de mi motor, así que pregunte.

+0

No soy un experto en shaders, pero no creo que pueda manipular las texturas subyacentes en un vertex o pixel shader. Por lo tanto, tendría que modificar las texturas en el código del juego, que sería algo así como Texture2D.SetData(). Sin embargo, si tiene una textura de 800x600 definida en función de los valores de su matriz, ni siquiera necesita un sombreador de píxeles. Simplemente usa Spritebatch para dibujar esa textura en la pantalla cada vez. – Venesectrix

+0

Puede renderizar una textura mientras lee desde otra textura. Por lo tanto, no modifica la textura, sino que genera una nueva textura en cada fotograma.Es por eso que usaría dos texturas y las cambiaría. – LaZe

+0

Me interesaría (y tal vez el póster sería también) para ver cómo se hace esto. ¿Tiene algún enlace a ejemplos que pueda publicar? ¡Gracias! – Venesectrix

0

Lo que estamos tratando de hacer (dibujar pixel por pixel) es lo que hace DirectX. XNA es una capa construida sobre DirectX para que no tenga que dibujar píxel por píxel. Si eso es realmente lo que quieres hacer, entonces probablemente deberías aprender DirectX en lugar de XNA. Probablemente lo encuentre mucho más fácil ...

0

Agregue una cartelera a su escena (directamente en frente de la cámara, de modo que ocupe exactamente 800x600 píxeles).

Enlaza la matriz binaria como una textura.

En el sombreador de fragmentos (/ píxeles) calcule el color del fragmento utilizando la posición 2D del fragmento y la textura.

Cuestiones relacionadas