Thực hiện hiệu ứng SNES Mode 7 (biến đổi affine) trong pygame


19

Có một câu trả lời ngắn gọn về cách thực hiện hiệu ứng loại kart Mode 7 / mario trong pygame không?

Tôi đã googled rộng rãi, tất cả các tài liệu tôi có thể đưa ra là hàng tá trang bằng các ngôn ngữ khác (asm, c) với rất nhiều phương trình trông lạ và như vậy.

Lý tưởng nhất, tôi muốn tìm một cái gì đó giải thích bằng tiếng Anh hơn là về mặt toán học.

Tôi có thể sử dụng PIL hoặc pygame để thao tác hình ảnh / kết cấu, hoặc bất cứ điều gì khác là cần thiết.

Tôi thực sự muốn đạt được hiệu ứng chế độ 7 trong pygame, nhưng tôi có vẻ gần với kết thúc dí dỏm của mình. Trợ giúp sẽ được đánh giá cao. Bất kỳ và tất cả các tài nguyên hoặc giải thích bạn có thể cung cấp sẽ rất tuyệt vời, ngay cả khi chúng không đơn giản như tôi muốn.

Nếu tôi có thể tìm ra nó, tôi sẽ viết một cách dứt khoát cách thực hiện chế độ 7 cho trang người mới.

chỉnh sửa: chế độ 7 doc: http://www.coranac.com/tonc/text/mode7.htm


5
Dường như có các phương trình ở đây: en.wikipedia.org/wiki/Mode_7 Mặc dù, những ngày này chúng ta có khả năng tăng tốc 3D, những thứ như Chế độ 7, hoặc cách thức hoạt động kỳ quặc thường gây tò mò hơn là một giải pháp.
salmonmoose

3
@ 2D_Guy trang này giải thích thuật toán rất tốt cho tôi. Bạn muốn biết làm thế nào để làm điều đó, hoặc bạn muốn nó đã được thực hiện cho bạn?
Gustavo Maciel

1
@stephelton Trên các hệ thống SNES, lớp duy nhất có thể bị biến dạng, xoay .. (biến đổi affine được áp dụng với ma trận) là lớp thứ bảy. Lớp nền. Tất cả các lớp khác đã được sử dụng cho các họa tiết đơn giản, vì vậy nếu bạn muốn có hiệu ứng 3D, bạn phải sử dụng lớp này, đây là nơi tên đến từ :)
Gustavo Maciel

3
@GustavoMaciel: Điều đó hơi không chính xác. SNES có 8 chế độ khác nhau (0-7), trong đó có tới 4 lớp nền có chức năng khác nhau, nhưng chỉ có một chế độ (chế độ 7, do đó tên) hỗ trợ xoay và chia tỷ lệ (và cũng giới hạn bạn trong một lớp). Bạn không thể thực sự kết hợp các chế độ.
Michael Madsen

1
@Michael: tôi cũng sẽ nói thêm: SNES là một trong những máy chơi game phổ biến đầu tiên sử dụng hiệu ứng này trong những năm 90 (với trò chơi F-Zero), và đó là lý do tại sao sau đó mọi người bắt đầu đề cập đến tất cả các hiệu ứng mặt phẳng được vẽ theo chiều ngang 2D trò chơi như "chế độ 7". Trong thực tế, loại hiệu ứng này không phải là mới và tồn tại từ lâu trong arcade, cf Không gian Harrier / Hang-On (1985).
tigrou

Câu trả lời:


45

Chế độ 7 là một hiệu ứng rất đơn giản. Nó chiếu một kết cấu 2D x / y (hoặc gạch) lên một số sàn / trần. SNES cũ sử dụng phần cứng để làm điều này, nhưng máy tính hiện đại mạnh đến mức bạn có thể thực hiện thời gian thực này (và không cần ASM như bạn đề cập).

Công thức toán học 3D cơ bản để chiếu một điểm 3D (x, y, z) thành điểm 2D (x, y) là:

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

Khi bạn nghĩ về nó, nó có ý nghĩa. Các đối tượng ở xa khoảng cách nhỏ hơn các đối tượng gần bạn. Hãy suy nghĩ về đường ray xe lửa sẽ đi đến đâu:

nhập mô tả hình ảnh ở đây

Nếu chúng ta nhìn lại các giá trị đầu vào của công thức: xysẽ là pixel hiện tại mà chúng ta đang xử lý và zsẽ là thông tin về khoảng cách về điểm đó. Để hiểu những gì znên được, nhìn vào hình ảnh, nó hiển thị zcác giá trị cho hình ảnh ở trên:

nhập mô tả hình ảnh ở đây

tím = khoảng cách gần, đỏ = xa

Vì vậy, trong ví dụ này, z giá trị là y - horizon(giả sử (x:0, y:0)nằm ở giữa màn hình)

Nếu chúng ta đặt mọi thứ lại với nhau, nó sẽ trở thành: (mã giả)

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

Một điều cuối cùng: nếu bạn muốn làm một trò chơi mario kart, tôi cho rằng bạn cũng muốn xoay bản đồ. Nó cũng rất dễ dàng: xoay sxsy trước khi nhận được giá trị kết cấu. Đây là công thức:

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

và nếu bạn muốn di chuyển máng bản đồ, chỉ cần thêm một chút bù trước khi nhận giá trị kết cấu:

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

LƯU Ý: tôi đã thử nghiệm thuật toán (gần như sao chép-dán) và nó hoạt động. Dưới đây là ví dụ: http://glslsandbox.com/e#26532.3 (yêu cầu trình duyệt gần đây và bật WebGL)

nhập mô tả hình ảnh ở đây

CHÚ THÍCH 2: Tôi sử dụng toán đơn giản vì bạn nói rằng bạn muốn một cái gì đó đơn giản (và dường như không quen thuộc với toán vector). Bạn có thể đạt được những điều tương tự bằng cách sử dụng công thức wikipedia hoặc hướng dẫn bạn đưa ra. Cách họ thực hiện nó phức tạp hơn nhiều nhưng bạn có nhiều khả năng hơn để định cấu hình hiệu ứng (cuối cùng thì nó cũng hoạt động tương tự ...).

Để biết thêm thông tin, tôi khuyên bạn nên đọc: http://en.wikipedia.org/wiki/3D_projection#Pers perspective_projection


Một điều cần nói thêm, vì sin và cos của góc chủ yếu là không đổi trên mỗi khung, hãy chắc chắn tính toán chúng bên ngoài vòng lặp để tìm ra tất cả các vị trí x, y.
hobberwickey

1

Đây là mã để làm cho nó. Tôi là cùng một mã của hướng dẫn mà tôi đã thực hiện trên blog của mình . Kiểm tra ở đó để tìm hiểu phương thức Mode 7 và RayCasting.

Về cơ bản, mã giả là nó:

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

Đây là mã mà tôi đã tạo trong JAVA, theo hướng dẫn của tôi.

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

Kết quả là:

nhập mô tả hình ảnh ở đây


Giải thích ở đây là chương trìnhandocoisas.blogspot.com.br . Bạn có thể tìm thấy ở đó hướng dẫn từng bước để thực hiện hiệu ứng này. Nhưng tôi sẽ cập nhật bài viết của mình để đưa các bình luận trở nên tốt hơn;).
Vinícius Biavatti
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.