Image 3D en python avec des performances (presque) normales

Cet article peut être considéré comme la réponse à celui-ci , où il s'agit d'écrire une telle chose en C ++, destinée aux débutants, c'est-à-dire en mettant l'accent sur un code simple et lisible au lieu de hautes performances.

Après avoir lu l'article, j'ai eu l'idée de répéter le programme écrit par l'auteur. Je connais C ++, mais je n'ai jamais écrit de programmes compliqués dessus, préférant python. Ici l'idée est née d'écrire dessus. J'étais particulièrement intéressé par les performances - j'étais presque sûr que quelques images par seconde étaient la limite pour python. J'avais tort.

image

image

La première tentative peut être trouvée ici . Ici, le code est écrit en entier, sans tenir compte des différences de langue, conformément à celui dehaqreu. Et pour cette raison, le rendu est O (n ^ 2) - essentiellement des boucles imbriquées en angle et en distance:

         # iterating alpha
        alpha = player.view - player.fov / 2
        mapFB.drawRectangle(player.y - 1, player.x - 1, player.y + 1, player.x + 1, Color(255, 0, 0))
        rayNum = 0
        while alpha < player.fov / 2 + player.view:
            # iterating distance
            dist = 0
            x = player.x
            y = player.y
            while 0 < x < mapFB.w - 1 and 0 < y < mapFB.h - 1:
                  ...

Pour cette raison, le code est plutôt lent (j'ai réussi à obtenir moins de 3 à 4 images par seconde sur le core Intel i5 de 8e génération).

Une façon évidente d'accélérer les choses et de ne pas trop compliquer le code est de remplacer la boucle interne par des opérations de complexité linéaire. Considérons tout du point de vue des mathématiques: nous devons déterminer le point d'intersection du rayon donné par les coordonnées du joueur et l'angle de vue, et le bloc spécifié par les coordonnées et la taille (constant, pour plus de simplicité). Ensuite, vous devez sélectionner le transfert le plus proche et le renvoyer. Voici le code correspondant (le code complet est ici ):

def block_cross(i, j, k, y_0, alpha, player):
    # cell coordinates
    x_cell = i * H
    y_cell = j * H
    # find collision points
    collisions = []

    if k != 0:
        x = (y_cell - y_0) / k
        y = y_cell
        if x_cell <= x <= x_cell + H and (x - player.x) / cos(alpha) < 0:
            collisions.append((x, y))

    if k != 0:
        x = (y_cell + H - y_0) / k
        y = y_cell + H
        if x_cell <= x <= x_cell + H and (x - player.x) / cos(alpha) < 0:
            collisions.append((x, y))

    x = x_cell
    y = y_0 + x * k
    if y_cell <= y <= y_cell + H and (x - player.x) / cos(alpha) < 0:
        collisions.append((x, y))

    x = x_cell + H
    y = y_0 + (x) * k
    if y_cell <= y <= y_cell + H and (x - player.x) / cos(alpha) < 0:
        collisions.append((x, y))

    # select the closest collision for the block
    dist = 1000 * H
    x = None
    y = None
    for collision in collisions:
        tmp = sqrt((collision[0] - player.x) ** 2 + (collision[1] - player.y) ** 2)
        if tmp < dist:
            dist = tmp;
            x = collision[0]
            y = collision[1]

    return x, y, dist

Un tel changement trivial de 10 lignes donne une accélération de plus de deux fois, soit environ 5-6 images par seconde. Ce n'est plus des secousses, mais une image en mouvement, mais toujours assez lente.
À la recherche d'idées sur l'accélération du code, je suis tombé sur Cython . En bref, c'est un projet qui vous permet de donner une accélération significative au code python sans le changer sérieusement. En bref sur lui - sous le spoiler.

tyts
 cpdef int fun(int num, float val):
    cdef int result
    # do some stuff
    return result

result int( int, , ). , . python-, cdef — python, cython. , python — , :

 cpdef int fun(int num, float val):
    cdef int result
    # do some stuff
    return result

 cdef int fun2(int *arr, float* arr_2):
    cdef int arr_3[10][10]
    # do some stuff
    return result

fun2 python, - fun — .

Cython a donné une accélération, quoique insignifiante - juste pas quelques images par seconde. Cependant, en nombre relatif, ce n'est pas si petit - 8 à 9 images par seconde, soit + 40% pour la meilleure option en python et + 200% pour l'option avec un algorithme naïf. C'est toujours une image très cahoteuse, mais normale à des fins éducatives. En fin de compte, notre objectif est de l'écrire nous-mêmes et d'apprécier le processus, mais pour un vrai jeu, il est plus facile de prendre une bibliothèque comme pygame, ou généralement de mettre de côté python et de prendre quelque chose de plus approprié.

PS Il serait intéressant de voir d'autres options pour optimiser le code dans les commentaires.

Source: https://habr.com/ru/post/undefined/


All Articles