Durchführen eines SNES Mode 7-Effekts (affine Transformation) im Pygame

19

Gibt es eine kurze Antwort, wie man einen Mode 7 / Mario-Kart-Effekt im Pygame macht?

Ich habe ausgiebig gegoogelt, alle Dokumente, die ich finden kann, sind Dutzende von Seiten in anderen Sprachen (asm, c) mit vielen seltsam aussehenden Gleichungen und dergleichen.

Idealerweise würde ich gerne etwas finden, das mehr in Englisch als in mathematischen Begriffen erklärt wird.

Ich kann PIL oder Pygame verwenden, um das Bild / die Textur zu manipulieren, oder was auch immer sonst notwendig ist.

Ich würde wirklich gerne einen Mode 7-Effekt im Pygame erzielen, aber ich scheine dem Ende meines Verstandes nahe zu sein. Hilfe wäre sehr dankbar. Alle Ressourcen oder Erklärungen, die Sie zur Verfügung stellen können, wären fantastisch, auch wenn sie nicht so einfach sind, wie ich sie gerne hätte.

Wenn ich es herausfinden kann, schreibe ich eine definitive Anleitung für Modus 7 für Neulinge.

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

2D_Guy
quelle
5
Hier scheint es Gleichungen zu geben: en.wikipedia.org/wiki/Mode_7 Obwohl wir heutzutage 3D-Beschleunigung haben, sind Dinge wie Mode 7 oder die verrückte Art und Weise, wie das Schicksal funktionierte, eher eine Neugierde als eine Lösung.
Lachs Elch
3
@ 2D_Diese Seite erklärt mir den Algorithmus sehr gut. Sie möchten wissen, wie es geht oder möchten es bereits für Sie implementiert haben?
Gustavo Maciel
1
@stephelton Auf den SNES-Systemen ist die siebte Schicht die einzige Schicht, die verzerrt und gedreht werden kann (angewendete affine Transformationen mit Matrizen). Die Hintergrundebene. Alle anderen Ebenen wurden für einfache Sprites verwendet. Wenn Sie also einen 3D-Effekt wollten, mussten Sie diese Ebene verwenden, von der der Name stammt :)
Gustavo Maciel
3
@GustavoMaciel: Das ist ein bisschen ungenau. Das SNES hatte 8 verschiedene Modi (0-7), in denen bis zu 4 Hintergrundebenen unterschiedliche Funktionen hatten, aber nur ein Modus (Modus 7, daher der Name) unterstützte Rotation und Skalierung (und beschränkte Sie auch auf eine einzelne Ebene). Man konnte die Modi nicht wirklich kombinieren.
Michael Madsen
1
@Michael: Ich möchte auch hinzufügen: SNES war eine der ersten beliebten Konsolen, die diesen Effekt in den 90er Jahren (mit dem Spiel F-Zero) nutzte, und deshalb beziehen sich die Leute danach auf alle horizontalen 2D-Textur-Map-Ebeneneffekte, die in anderen gesehen wurden Spiele als "Modus 7". In Wirklichkeit war diese Art von Effekt nicht neu und existierte schon vor langer Zeit in Arkaden, vgl. Space Harrier / Hang-On (1985).
Tigrou

Antworten:

45

Modus 7 ist ein sehr einfacher Effekt. Es projiziert eine 2D x / y-Textur (oder Kacheln) auf einen Boden / eine Decke. Alte SNES verwenden hierfür Hardware, aber moderne Computer sind so leistungsfähig, dass Sie dies in Echtzeit tun können (und kein Bedarf an ASM, wie Sie bereits erwähnt haben).

Die grundlegende 3D-Mathematikformel zum Projizieren eines 3D-Punkts (x, y, z) auf einen 2D-Punkt (x, y) lautet:

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

Wenn Sie darüber nachdenken, macht es Sinn. Objekte, die weit entfernt sind, sind kleiner als Objekte in Ihrer Nähe. Denken Sie an Eisenbahnschienen, die ins Nirgendwo führen:

Bildbeschreibung hier eingeben

Wenn wir auf die Formeleingabewerte zurückblicken: xund ywerden das aktuelle Pixel sein, das wir verarbeiten, und zwerden Entfernungsinformationen darüber sein, wie weit der Punkt ist. Um zu verstehen, was zsein sollte, sehen Sie sich das Bild an. Es zeigt zWerte für das obige Bild:

Bildbeschreibung hier eingeben

Lila = Nähe, Rot = Ferne

In diesem Beispiel zlautet der Wert y - horizon(vorausgesetzt, er (x:0, y:0)befindet sich in der Mitte des Bildschirms).

Wenn wir alles zusammenfügen, wird es: (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
     ...
  }

Eine letzte Sache: Wenn Sie ein Mario-Kart-Spiel machen möchten, möchten Sie vermutlich auch die Karte drehen. Nun, es ist auch sehr einfach: drehen sxund syvor dem Abrufen des Texturwerts. Hier ist die Formel:

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

und wenn Sie sich durch die Karte bewegen möchten, fügen Sie einfach einen Versatz hinzu, bevor Sie den Texturwert erhalten:

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

HINWEIS: Ich habe den Algorithmus getestet (fast Kopieren-Einfügen) und es funktioniert. Hier ist das Beispiel: http://glslsandbox.com/e#26532.3 (aktueller Browser und aktiviertes WebGL erforderlich)

Bildbeschreibung hier eingeben

ANMERKUNG 2: Ich benutze einfache Mathematik, weil Sie sagten, Sie wollen etwas Einfaches (und scheinen mit Vektor-Mathematik nicht vertraut zu sein). Sie können die gleichen Dinge erreichen, indem Sie Wikipedia-Formeln oder Tutorials verwenden, die Sie geben. Die Art und Weise, wie sie es gemacht haben, ist viel komplexer, aber Sie haben viel mehr Möglichkeiten, den Effekt zu konfigurieren (am Ende funktioniert es genauso ...).

Für weitere Informationen schlage ich vor zu lesen: http://en.wikipedia.org/wiki/3D_projection#Perspective_projection

tigrou
quelle
Da Sinus und Cosinus des Winkels pro Frame meist konstant sind, müssen Sie sie außerhalb der Schleife berechnen, um alle x- und y-Positionen zu ermitteln.
Hobberwickey
1

Hier ist der Code, um es zu machen. Ich bin derselbe Code des Tutorials, den ich in meinem Blog erstellt habe . Hier erfahren Sie mehr über die Mode 7-Methode und das RayCasting.

Grundsätzlich ist der Pseudocode:

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

Hier ist der Code, den ich in JAVA nach meinem Tutorial erstellt habe.

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

Das Ergebnis ist:

Bildbeschreibung hier eingeben

Vinícius Biavatti
quelle
Die Erklärung ist hier programandocoisas.blogspot.com.br . Dort finden Sie das Tutorial Schritt für Schritt, um diesen Effekt zu erzielen. Aber ich werde meinen Beitrag aktualisieren, um die Kommentare zu verbessern;).
Vinícius Biavatti