Hacer un efecto SNES Mode 7 (transformación afín) en pygame

19

¿Existe una respuesta breve sobre cómo hacer un efecto de tipo Modo 7 / mario kart en pygame?

He buscado mucho en Google, todos los documentos que se me ocurren son docenas de páginas en otros idiomas (asm, c) con muchas ecuaciones de aspecto extraño y demás.

Idealmente, me gustaría encontrar algo más explicado en inglés que en términos matemáticos.

Puedo usar PIL o pygame para manipular la imagen / textura, o cualquier otra cosa que sea necesaria.

Realmente me gustaría lograr un efecto de modo 7 en pygame, pero parece estar cerca del final de mi ingenio. La ayuda sería muy apreciada. Cualquiera y todos los recursos o explicaciones que pueda proporcionar serían fantásticos, incluso si no son tan simples como me gustaría que fueran.

Si puedo resolverlo, escribiré una forma definitiva de cómo hacer el modo 7 para la página de novatos.

editar: modo 7 doc: http://www.coranac.com/tonc/text/mode7.htm

2D_Guy
fuente
55
Parece que hay ecuaciones aquí: en.wikipedia.org/wiki/Mode_7 Aunque, en estos días tenemos aceleración 3D, cosas como el Modo 7, o la forma en que funcionaba la fatalidad son más una curiosidad que una solución.
salmonmoose
3
@ 2D_Guy esta página explica el algoritmo muy bien para mí. ¿Quieres saber cómo hacerlo o quieres que ya esté implementado para ti?
Gustavo Maciel
1
@stephelton En los sistemas SNES, la única capa que podría distorsionarse, rotarse ... (transformaciones afines aplicadas con matrices) es la séptima capa. La capa de fondo. Todas las otras capas se usaron para sprites simples, así que si querías un efecto 3D, tenías que usar esta capa, de ahí viene el nombre :)
Gustavo Maciel
3
@GustavoMaciel: Eso es un poco inexacto. El SNES tenía 8 modos diferentes (0-7), en los que hasta 4 capas de fondo tenían una funcionalidad diferente, pero solo un modo (modo 7, de ahí el nombre) admitía rotación y escala (y también lo restringía a una sola capa). Realmente no podrías combinar los modos.
Michael Madsen
1
@Michael: también agregaría: SNES fue una de las primeras consolas populares en usar este efecto en los años 90 (con el juego F-Zero), y es por eso que después de eso, la gente comienza a referirse a todos los efectos de planos de textura horizontal 2D que se ven en otros juegos como "modo 7". En realidad, este tipo de efecto no era nuevo y existía hace mucho tiempo en la sala de juegos, cf. Space Harrier / Hang-On (1985).
tigrou

Respuestas:

45

El modo 7 es un efecto muy simple. Proyecta una textura 2D x / y (o mosaicos) en algún piso / techo. Los SNES antiguos usan hardware para hacer esto, pero las computadoras modernas son tan poderosas que puedes hacer esto en tiempo real (y no necesitas ASM como mencionas).

La fórmula matemática 3D básica para proyectar un punto 3D (x, y, z) en un punto 2D (x, y) es:

x' = x / z;
y' = y / z; 

Cuando lo piensas, tiene sentido. Los objetos que están muy lejos son más pequeños que los objetos cercanos a usted. Piensa en las vías del ferrocarril que no van a ninguna parte:

ingrese la descripción de la imagen aquí

Si miramos hacia atrás a los valores de entrada de la fórmula: xy yserá el píxel actual que estamos procesando, y zserá la información de distancia sobre qué tan lejos está el punto. Para entender lo que zdebería ser, mira esa imagen, muestra zvalores para la imagen de arriba:

ingrese la descripción de la imagen aquí

púrpura = distancia cercana, rojo = lejos

Entonces, en este ejemplo, el zvalor es y - horizon(suponiendo que (x:0, y:0)esté en el centro de la pantalla)

Si ponemos todo junto, se convierte en: (pseudocódigo)

for (y = -yres/2 ; y < yres/2 ; y++)
  for (x = -xres/2 ; x < xres/2 ; x++)
  {
     horizon = 20; //adjust if needed
     fov = 200; 

     px = x;
     py = fov; 
     pz = y + horizon;      

     //projection 
     sx = px / pz;
     sy = py / pz; 

     scaling = 100; //adjust if needed, depends of texture size
     color = get2DTexture(sx * scaling, sy * scaling);  

     //put (color) at (x, y) on screen
     ...
  }

Una última cosa: si quieres hacer un juego de Mario Kart, supongo que también quieres rotar el mapa. Bueno, también es muy fácil: girar sxy syantes de obtener el valor de la textura. Aquí está la fórmula:

  x' = x * cos(angle) - y * sin(angle);
  y' = x * sin(angle) + y * cos(angle);

y si quieres moverte por el mapa, solo agrega algo de desplazamiento antes de obtener el valor de textura:

  get2DTexture(sx * scaling + xOffset, sy * scaling + yOffset);

NOTA: probé el algoritmo (casi copiar y pegar) y funciona. Aquí está el ejemplo: http://glslsandbox.com/e#26532.3 (requiere navegador reciente y WebGL habilitado)

ingrese la descripción de la imagen aquí

NOTA 2: uso matemáticas simples porque dijiste que quieres algo simple (y no pareces familiarizado con las matemáticas vectoriales). Puede lograr lo mismo utilizando la fórmula de Wikipedia o los tutoriales que ofrece. La forma en que lo hicieron es mucho más compleja, pero tiene muchas más posibilidades para configurar el efecto (al final funciona igual ...).

Para obtener más información, sugiero leer: http://en.wikipedia.org/wiki/3D_projection#Perspective_projection

tigrou
fuente
Una cosa para agregar, ya que el pecado y el cos del ángulo son en su mayoría constantes por cuadro, asegúrese de calcularlos fuera del bucle para calcular todas las posiciones x, y.
hobberwickey
1

Aquí está el código para hacerlo. Soy el mismo código del tutorial que hice en mi blog . Verifique allí para aprender el método del Modo 7 y RayCasting.

Básicamente, el pseudocódigo es:

//This is the pseudo-code to generate the basic mode7

for each y in the view do
    y' <- y / z
    for each x in the view do
        x' <- x / z
        put x',y' texture pixel value in x,y view pixel
    end for
    z <- z + 1
end for

Aquí está el código que hice en JAVA, siguiendo mi tutorial.

package mode7;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import javax.imageio.ImageIO;
import javax.swing.JFrame;

/**
 * Mode 7 - Basic Implementation
 * This code will map a texture to create a pseudo-3d perspective.
 * This is an infinite render mode. The texture will be repeated without bounds.
 * @author VINICIUS
 */
public class BasicModeSeven {

    //Sizes
    public static final int WIDTH = 800;
    public static final int WIDTH_CENTER = WIDTH/2;
    public static final int HEIGHT = 600;
    public static final int HEIGHT_CENTER = HEIGHT/2;

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws IOException {

        //Create Frame
        JFrame frame = new JFrame("Mode 7");
        frame.setSize(WIDTH, HEIGHT);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);

        //Create Buffered Images:
        //image - This is the image that will be printed in the render view
        //texture - This is the image that will be mapped to the render view
        BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
        BufferedImage texture = ImageIO.read(new File("src/mode7/texture.png"));

        //The new coords that will be used to get the pixel on the texture
        double _x, _y;

        //z - the incrementable variable that beggins at -300 and go to 300, because 
        //the depth will be in the center of the HEIGHT
        double z =  HEIGHT_CENTER * -1;

        //Scales just to control de scale of the printed pixel. It is not necessary
        double scaleX = 16.0;
        double scaleY = 16.0; 

        //Mode 7 - loop (Left Top to Down)
        for(int y = 0; y < HEIGHT; y++){

            _y = y / z; //The new _y coord generated
            if(_y < 0)_y *= -1; //Control the _y because the z starting with a negative number
            _y *= scaleY; //Increase the size using scale
            _y %= texture.getHeight(); //Repeat the pixel avoiding get texture out of bounds 

            for(int x = 0; x < WIDTH; x++){

                _x = (WIDTH_CENTER - x) / z; //The new _x coord generated
                if(_x < 0)_x *= -1; //Control the _x to dont be negative
                _x *= scaleX; //Increase the size using scale
                _x %= texture.getWidth(); //Repeat the pixel avoiding get texture out of bounds 

                //Set x,y of the view image with the _x,_y pixel in the texture
                image.setRGB(x, y, texture.getRGB((int)_x, (int)_y));
            }

            //Increment depth
            z++;
        }

        //Loop to render the generated image
        while(true){
            frame.getGraphics().drawImage(image, 0, 0, null);
        }
    }
}

El resultado es:

ingrese la descripción de la imagen aquí

Vinícius Biavatti
fuente
La explicación está aquí programandocoisas.blogspot.com.br . Puedes encontrar el tutorial paso a paso para hacer este efecto. Pero actualizaré mi publicación para que los comentarios sean mejores;).
Vinícius Biavatti