Imagem 3D em python com desempenho (quase) normal

Este artigo pode ser considerado a resposta para este , onde se trata de escrever algo em C ++, voltado para iniciantes, ou seja, com ênfase no código legível simples em vez do alto desempenho.

Depois de ler o artigo, tive a idéia de repetir o programa escrito pelo autor. Eu estou familiarizado com C ++, mas nunca escrevi nenhum programa complicado, preferindo python. Aqui nasceu a ideia de escrever sobre ela. Eu estava especialmente interessado em desempenho - eu tinha quase certeza de que alguns quadros por segundo são o limite para o python. Eu estava errado.

imagem

imagem

A primeira tentativa pode ser encontrada aqui . Aqui o código está escrito na íntegra, sem contar as diferenças de idioma, de acordo com o dehaqreu. E por causa disso, a renderização é O (n ^ 2) - essencialmente loops aninhados em ângulo e distância:

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

Por esse motivo, o código é bastante lento (consegui obter menos de 3-4 quadros por segundo no Intel Core i5 de 8ª geração).

Uma maneira óbvia de acelerar as coisas e não complicar muito o código é substituir o loop interno por operações de complexidade linear. Vamos considerar tudo do ponto de vista da matemática: precisamos determinar o ponto de interseção do raio dado pelas coordenadas do jogador e o ângulo de visão e o bloco especificado pelas coordenadas e tamanho (constante, por simplicidade). Em seguida, você precisa selecionar a transferência mais próxima e devolvê-la. Abaixo está o código correspondente (o código completo está aqui ):

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

Uma mudança tão trivial de 10 linhas dá uma aceleração de mais que o dobro, isto é, cerca de 5-6 quadros por segundo. Isso não é mais idiota, mas uma imagem em movimento, mas ainda bem lenta.
Procurando idéias sobre como acelerar o código, me deparei com o Cython . Em resumo, este é um projeto que permite que você dê aceleração significativa ao código python sem alterá-lo seriamente. Brevemente sobre ele - sob o 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 deu alguma aceleração, embora insignificante - apenas alguns quadros por segundo. No entanto, em números relativos, isso não é tão pequeno - 8 a 9 imagens por segundo, ou seja, + 40% para a melhor opção em python e + 200% para a opção com um algoritmo ingênuo. Essa ainda é uma imagem muito instável, mas normal para fins educacionais. No final, nosso objetivo é escrever por conta própria e aproveitar o processo, mas, para um jogo real, é mais fácil pegar uma biblioteca como pygame ou, geralmente, deixar de lado o python e pegar algo mais adequado.

PS Seria interessante ver outras opções para otimizar o código nos comentários.

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


All Articles