Imagen en 3D en Python con un rendimiento (casi) normal

Este artículo se puede considerar la respuesta a este , donde se trata de escribir algo así en C ++, dirigido a principiantes, es decir, con énfasis en un código legible simple en lugar de un alto rendimiento.

Después de leer el artículo, tuve la idea de repetir el programa escrito por el autor. Estoy familiarizado con C ++, pero nunca escribí ningún programa complicado en él, prefiero Python. Aquí nació la idea de escribir sobre ella. Estaba especialmente interesado en el rendimiento: estaba casi seguro de que un par de cuadros por segundo es el límite para Python. Estaba equivocado.

imagen

imagen

El primer intento se puede encontrar aquí . Aquí el código está escrito en su totalidad, sin contar las diferencias de idioma, de acuerdo con el dehaqreu. Y debido a esto, la representación es O (n ^ 2), esencialmente bucles anidados en ángulo y distancia:

         # 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:
                  ...

Debido a esto, el código es bastante lento (pude obtener menos de 3-4 cuadros por segundo en el Intel Core i5 de octava generación).

Una forma obvia de acelerar las cosas y no complicar mucho el código es reemplazar el bucle interno con operaciones de complejidad lineal. Consideremos todo desde el punto de vista de las matemáticas: necesitamos determinar el punto de intersección del rayo dado por las coordenadas del jugador y el ángulo de visión, y el bloque especificado por las coordenadas y el tamaño (constante, por simplicidad). A continuación, debe seleccionar la transferencia más cercana y devolverla. A continuación se muestra el código correspondiente (el código completo está aquí ):

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

Tal cambio trivial de 10 líneas da una aceleración de más del doble, es decir, aproximadamente 5-6 cuadros por segundo. Esto ya no es tirones, sino una imagen en movimiento, pero sigue siendo bastante lento.
Buscando ideas para acelerar el código, me encontré con Cython . En resumen, este es un proyecto que le permite acelerar significativamente el código de Python sin cambiarlo seriamente. Brevemente sobre él, debajo del 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 dio algo de aceleración, aunque insignificante, solo que no un par de cuadros por segundo. Sin embargo, en números relativos, esto no es tan pequeño: 8-9 imágenes por segundo, es decir, + 40% a la mejor opción en Python y + 200% a la opción con un algoritmo ingenuo. Esta sigue siendo una imagen muy nerviosa, pero normal para fines educativos. Al final, nuestro objetivo es escribirlo nosotros mismos y disfrutar del proceso, pero para un juego real es más fácil tomar una biblioteca como pygame, o en general posponer Python y tomar algo más adecuado.

PD: Sería interesante ver otras opciones para optimizar el código en los comentarios.

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


All Articles