Выполнение эффекта SNES Mode 7 (аффинное преобразование) в Pygame

19

Есть ли такая вещь, как краткий ответ о том, как сделать эффект типа 7 / mario kart в pygame?

Я много гуглил, все документы, которые я могу придумать, - это десятки страниц на других языках (asm, c) с множеством странно выглядящих уравнений и тому подобное.

В идеале я хотел бы найти что-то объясненное больше на английском языке, чем в математических терминах.

Я могу использовать PIL или Pygame, чтобы манипулировать изображением / текстурой, или чем-то еще необходимым.

Я бы очень хотел добиться эффекта режима 7 в пигмеях, но мне кажется, что я близок к концу. Помощь будет принята с благодарностью. Любые ресурсы или объяснения, которые вы можете предоставить, были бы фантастическими, даже если бы они не были такими простыми, как мне бы того хотелось.

Если я смогу это выяснить, я напишу окончательный вариант, как сделать режим 7 для страницы новичков.

редактировать: режим 7 документ: http://www.coranac.com/tonc/text/mode7.htm

2D_Guy
источник
5
здесь, похоже, есть уравнения: en.wikipedia.org/wiki/Mode_7 Хотя в наши дни у нас есть 3D-ускорение, такие вещи, как режим 7 или причудливый способ работы дум, являются скорее любопытством, чем решением.
лосось
3
@ 2D_Guy эта страница объясняет алгоритм очень хорошо для меня. Вы хотите знать, как это сделать, или вы хотите, чтобы это уже было реализовано для вас?
Густаво Масиэль
1
@stephelton В системах SNES единственный слой, который может быть искажен, повернут .. (применены аффинные преобразования с матрицами) - это седьмой уровень. Фоновый слой. Все остальные слои использовались для простых спрайтов, поэтому, если вы хотели получить 3D-эффект, вам нужно было использовать этот слой, отсюда и название :)
Густаво Масиэль
3
@GustavoMaciel: Это немного неточно. У SNES было 8 разных режимов (0-7), в которых до 4 фоновых слоев имели различную функциональность, но только один режим (режим 7, отсюда и название) поддерживал вращение и масштабирование (а также ограничивал вас одним слоем). Вы не могли действительно комбинировать режимы.
Майкл Мэдсен
1
@Michael: я бы также добавил: SNES была одной из первых популярных консолей, которая использовала этот эффект в 90-х годах (с игрой F-Zero), и поэтому после этого люди начинают ссылаться на все 2D-эффекты горизонтальной текстурной плоскости, видимые в других игры как "режим 7". На самом деле такой эффект не был новым и существовал давно в аркадах, ср. Космический Харриер / Hang-On (1985).
tigrou

Ответы:

45

Режим 7 - очень простой эффект. Он проецирует 2D х / у текстуру (или плитки) на некоторый пол / потолок. Старые SNES используют оборудование для этого, но современные компьютеры настолько мощны, что вы можете делать это в реальном времени (и, как вы упоминаете, не нуждаетесь в ASM).

Основная трехмерная математическая формула для проецирования 3D-точки (x, y, z) в 2D-точку (x, y):

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

Когда вы думаете об этом, это имеет смысл. Объекты, которые находятся на большом расстоянии, меньше, чем объекты рядом с вами. Думайте о железнодорожных путях, идущих в никуда:

введите описание изображения здесь

Если мы оглянемся на входные значения формулы: xи yбудет текущий пиксель, который мы обрабатываем, и zбудет информацией о расстоянии до того, как далеко находится точка. Чтобы понять, что zдолжно быть, посмотрите на это изображение, оно показывает zзначения для изображения выше:

введите описание изображения здесь

фиолетовый = близко, красный = далеко

Так что в этом примере zзначение равно y - horizon(при условии, что оно (x:0, y:0)находится в центре экрана)

Если мы соберем все вместе, то получится: (псевдокод)

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

И напоследок: если вы хотите сделать игру для Марио-картов, я думаю, вы также хотите повернуть карту. Ну, это тоже очень просто: повернуть sxи syдо получения значения текстуры. Вот формула:

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

и если вы хотите перемещаться по карте, просто добавьте некоторое смещение, прежде чем получить значение текстуры:

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

ПРИМЕЧАНИЕ: я проверил алгоритм (почти копировать-вставить), и он работает. Вот пример: http://glslsandbox.com/e#26532.3 (требуется недавний браузер и включенный WebGL)

введите описание изображения здесь

ПРИМЕЧАНИЕ 2. Я использую простую математику, потому что вы сказали, что хотите что-то простое (и, кажется, не знакомы с векторной математикой). Вы можете достичь того же, используя формулу Википедии или учебные пособия, которые вы даете. То, как они это сделали, намного сложнее, но у вас гораздо больше возможностей для настройки эффекта (в конце концов, он работает так же ...).

Для получения дополнительной информации я предлагаю прочитать: http://en.wikipedia.org/wiki/3D_projection#Perspective_projection

tigrou
источник
Одна вещь, которую нужно добавить, поскольку sin и cos угла в основном постоянны для каждого кадра, не забудьте вычислить их вне цикла, чтобы выяснить все положения x, y.
hobberwickey
1

Вот код, чтобы сделать это. Я тот же код учебника, который я сделал в своем блоге . Проверьте там, чтобы узнать метод Mode 7 и RayCasting.

По сути, псевдокод таков:

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

Вот код, который я сделал в JAVA, следуя моему уроку.

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

Результат:

введите описание изображения здесь

Винициус Биаватти
источник
Объяснение здесь programandocoisas.blogspot.com.br . Вы можете найти там пошаговое руководство, чтобы сделать этот эффект. Но я буду обновлять свой пост, чтобы комментарии были лучше;).
Vinícius Biavatti