Fazendo um efeito SNES Mode 7 (transformação afim) em pygame

19

Existe uma resposta curta sobre como fazer um efeito do tipo Mode / mario kart no pygame?

Pesquisei bastante no Google, todos os documentos que consigo encontrar são dezenas de páginas em outros idiomas (asm, c) com muitas equações de aparência estranha e outras coisas.

Idealmente, gostaria de encontrar algo explicado mais em inglês do que em termos matemáticos.

Eu posso usar PIL ou pygame para manipular a imagem / textura, ou o que mais for necessário.

Eu realmente gostaria de obter o efeito do modo 7 no pygame, mas parece que estou perto do fim da minha inteligência. A ajuda seria muito apreciada. Todo e qualquer recurso ou explicação que você possa fornecer seria fantástico, mesmo que não seja tão simples quanto eu gostaria que fosse.

Se eu conseguir descobrir, escreverei uma página definitiva sobre como fazer o modo 7 para iniciantes.

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

2D_Guy
fonte
5
parece haver equações aqui: en.wikipedia.org/wiki/Mode_7 Embora, hoje em dia, tenhamos aceleração 3D, coisas como o Modo 7 ou a maneira desastrada que a desgraça funcionou sejam mais uma curiosidade do que uma solução.
salmonmoose
3
@ 2D_Compre esta página para explicar o algoritmo muito bem para mim. Você quer saber como fazê-lo ou já o implementou?
Gustavo Maciel
1
@stephelton Nos sistemas SNES, a única camada que pode ser distorcida, girada .. (transformações afins aplicadas com matrizes) é a sétima camada. A camada de fundo. Todas as outras camadas foram usadas para sprites simples, Então, se você queria um efeito 3D, você tinha que usar essa camada, este é o lugar onde o nome veio de :)
Gustavo Maciel
3
@GustavoMaciel: Isso é um pouco impreciso. O SNES tinha 8 modos diferentes (0-7), nos quais até 4 camadas de segundo plano tinham funcionalidade diferente, mas apenas um modo (modo 7, daí o nome) suportava rotação e dimensionamento (e também restringia você a uma única camada). Você realmente não podia combinar os modos.
Michael Madsen
1
@ Michael: eu também acrescentaria: o SNES foi um dos primeiros consoles populares a usar esse efeito nos anos 90 (com o jogo F-Zero), e é por isso que depois disso as pessoas começam a se referir a todos os efeitos de planos horizontais 2D mapeados em textura vistos em outros jogos como "modo 7". Na realidade, esse tipo de efeito não era novo e existia há muito tempo no arcade, cf. Space Harrier / Hang-On (1985).
tigrou

Respostas:

45

O modo 7 é um efeito muito simples. Ele projeta uma textura 2D x / y (ou ladrilhos) em algum piso / teto. Os SNES antigos usam hardware para fazer isso, mas os computadores modernos são tão poderosos que você pode fazer isso em tempo real (e não precisa do ASM, como você mencionou).

A fórmula matemática 3D básica para projetar um ponto 3D (x, y, z) em um ponto 2D (x, y) é:

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

Quando você pensa sobre isso, faz sentido. Objetos distantes são menores que objetos próximos a você. Pense em trilhos de trem que não levam a lugar nenhum:

insira a descrição da imagem aqui

Se olharmos para os valores de entrada da fórmula: xe yserá o pixel atual que estamos processando, e zhaverá informações de distância sobre a distância que o ponto está. Para entender o que zdeve ser, olhe para a figura, ela mostra zvalores para a imagem acima:

insira a descrição da imagem aqui

roxo = próximo da distância, vermelho = distante

Portanto, neste exemplo, o zvalor é y - horizon(assumindo que (x:0, y:0)está no centro da tela)

Se juntarmos tudo, ele se torna: (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
     ...
  }

Uma última coisa: se você quiser fazer um jogo de mario kart, suponho que você também queira girar o mapa. Bem, também é muito fácil: gire sxe syantes de obter o valor da textura. Aqui está a fórmula:

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

e se você quiser mover-se pelo mapa, basta adicionar algum deslocamento antes de obter o valor da textura:

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

NOTA: testei o algoritmo (quase copiar e colar) e funciona. Aqui está o exemplo: http://glslsandbox.com/e#26532.3 (requer navegador recente e WebGL ativado)

insira a descrição da imagem aqui

NOTA2: eu uso matemática simples porque você disse que quer algo simples (e não parece familiarizado com a matemática vetorial). Você pode conseguir as mesmas coisas usando a fórmula da wikipedia ou os tutoriais fornecidos. O modo como eles fizeram isso é muito mais complexo, mas você tem muito mais possibilidades de configurar o efeito (no final, funciona da mesma maneira ...).

Para mais informações, sugiro a leitura: http://en.wikipedia.org/wiki/3D_projection#Perspective_projection

tigrou
fonte
Uma coisa a acrescentar, como o pecado e o cos do ângulo são principalmente constantes por quadro, calcule-os fora do loop para descobrir todas as posições x, y.
Hbberwickey
1

Aqui está o código para fazê-lo. Eu sou o mesmo código do tutorial que fiz no meu blog . Confira aqui para aprender o método Mode 7 e o RayCasting.

Basicamente, o pseudo-código é:

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

Aqui está o código que eu criei em JAVA, seguindo o meu 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);
        }
    }
}

O resultado é:

insira a descrição da imagem aqui

Vinícius Biavatti
fonte
A explicação está aqui programandocoisas.blogspot.com.br . Você pode encontrar lá o tutorial passo a passo para fazer esse efeito. Mas vou atualizar meu post para que os comentários sejam melhores;).
Vinícius Biavatti