Faire un effet SNES Mode 7 (transformation affine) dans Pygame

19

Existe-t-il une réponse courte sur la façon de faire un effet de type Mode 7 / Mario Kart dans Pygame?

J'ai beaucoup cherché sur Google, tous les documents que je peux trouver sont des dizaines de pages dans d'autres langues (asm, c) avec beaucoup d'équations étranges et autres.

Idéalement, je voudrais trouver quelque chose expliqué plus en anglais qu'en termes mathématiques.

Je peux utiliser PIL ou pygame pour manipuler l'image / la texture, ou tout ce qui est nécessaire.

J'aimerais vraiment obtenir un effet mode 7 en pygame, mais je semble proche de la fin de mon esprit. De l'aide serait grandement appréciée. Toutes les ressources ou explications que vous pouvez fournir seraient fantastiques, même si elles ne sont pas aussi simples que je le souhaiterais.

Si je peux le comprendre, je vais écrire une page définitive sur le mode 7 pour les débutants.

modifier: mode 7 doc: http://www.coranac.com/tonc/text/mode7.htm

2D_Guy
la source
5
il semble y avoir des équations ici: en.wikipedia.org/wiki/Mode_7 Bien que, de nos jours, nous ayons une accélération 3D, des choses comme le Mode 7, ou la façon dingue de travailler, sont plus une curiosité qu'une solution.
salmonmoose
3
@ 2D_Guy cette page explique très bien l'algorithme pour moi. Vous voulez savoir comment le faire, ou vous voulez qu'il soit déjà implémenté pour vous?
Gustavo Maciel
1
@stephelton Sur les systèmes SNES, la seule couche qui pourrait être déformée, tournée .. (transformations affines appliquées avec des matrices) est la septième couche. Le calque d'arrière-plan. Tous les autres calques étaient utilisés pour de simples sprites, donc si vous vouliez un effet 3D, vous deviez utiliser ce calque, c'est de là que vient le nom :)
Gustavo Maciel
3
@GustavoMaciel: C'est un peu inexact. Le SNES avait 8 modes différents (0-7), dans lesquels jusqu'à 4 couches d'arrière-plan avaient des fonctionnalités différentes, mais un seul mode (mode 7, d'où le nom) supportait la rotation et la mise à l'échelle (et vous limitait également à une seule couche). Vous ne pouviez pas vraiment combiner les modes.
Michael Madsen
1
@Michael: j'ajouterais également: SNES a été l'une des premières consoles populaires à utiliser cet effet dans les années 90 (avec le jeu F-Zero), et c'est pourquoi après cela, les gens commencent à se référer à tous les effets de plan mappés en texture horizontale 2D vus dans d'autres jeux en "mode 7". En réalité, ce genre d'effet n'était pas nouveau et existait il y a longtemps en arcade, cf. Space Harrier / Hang-On (1985).
tigrou

Réponses:

45

Le mode 7 est un effet très simple. Il projette une texture 2D x / y (ou des tuiles) sur certains sols / plafonds. Les anciens SNES utilisent du matériel pour ce faire, mais les ordinateurs modernes sont si puissants que vous pouvez le faire en temps réel (et pas besoin d'ASM comme vous le mentionnez).

La formule mathématique 3D de base pour projeter un point 3D (x, y, z) vers un point 2D (x, y) est:

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

Quand on y pense, cela a du sens. Les objets éloignés sont plus petits que les objets près de chez vous. Pensez aux voies ferrées qui ne vont nulle part:

entrez la description de l'image ici

Si nous regardons en arrière les valeurs d'entrée de formule: xet ysera le pixel actuel que nous traitons, et zsera des informations de distance sur la distance du point. Pour comprendre ce qui zdevrait être, regardez cette image, elle montre les zvaleurs de l'image ci-dessus:

entrez la description de l'image ici

violet = près de la distance, rouge = loin

Donc, dans cet exemple, la zvaleur est y - horizon(en supposant qu'elle se (x:0, y:0)trouve au centre de l'écran)

Si nous mettons tout ensemble, cela devient: (pseudocode)

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

Une dernière chose: si vous voulez faire un jeu de mario kart, je suppose que vous voulez aussi faire pivoter la carte. Eh bien, c'est aussi très facile: faites pivoter sxet syavant d'obtenir la valeur de la texture. Voici la formule:

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

et si vous voulez vous déplacer dans la carte, ajoutez simplement un décalage avant d'obtenir la valeur de la texture:

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

REMARQUE: j'ai testé l'algorithme (presque copier-coller) et cela fonctionne. Voici l'exemple: http://glslsandbox.com/e#26532.3 (nécessite un navigateur récent et WebGL activé)

entrez la description de l'image ici

NOTE 2: j'utilise des mathématiques simples parce que vous avez dit que vous vouliez quelque chose de simple (et que vous ne semblez pas familier avec les mathématiques vectorielles). Vous pouvez réaliser les mêmes choses en utilisant la formule wikipedia ou les tutoriels que vous donnez. La façon dont ils l'ont fait est beaucoup plus complexe mais vous avez beaucoup plus de possibilités pour configurer l'effet (au final ça marche de la même façon ...).

Pour plus d'informations, je suggère de lire: http://en.wikipedia.org/wiki/3D_projection#Perspective_projection

tigrou
la source
Une chose à ajouter, puisque le sin et le cos de l'angle sont pour la plupart constants par image, assurez-vous de les calculer en dehors de la boucle pour déterminer toutes les positions x, y.
hobberwickey
1

Voici le code pour le faire. Je suis le même code du tutoriel que j'ai fait sur mon blog . Cochez là pour apprendre la méthode Mode 7 et le RayCasting.

Fondamentalement, le pseudo-code est le suivant:

//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

Voici le code que j'ai fait en JAVA, suite à mon tutoriel.

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

Le résultat est:

entrez la description de l'image ici

Vinícius Biavatti
la source
L'explication est ici programandocoisas.blogspot.com.br . Vous pouvez y trouver le tutoriel étape par étape pour faire cet effet. Mais je mettrai à jour mon post afin de mieux mettre les commentaires;).
Vinícius Biavatti