VGA – Tutorial 8 – Denthor

                       --==[ PARTE 8 ]==--

Para obter os programas mencionados neste tutorial, por favor baixe o zip com a versão antiga do tutorial, neste link.

[Nota: essas coisas entre colchetes foram adicionadas pelo Snownan. O texto original ficou quase todo inalterado, exceto pela inclusão do material em C++]

Introdução

Olá todo mundo! O Natal acabou, os últimos chocolates foram comidos, então é hora de continuar com isso, a oitava parte da série de treinamentos de demos ASPHYXIA. Essa parte, em particular, é primariamente sobre 3D, mas também tem algo sobre otimização.

Se você já é um guru em 3D, você pode bem pular esse arquivo: dê uma olhada no programa de amostra e volte a dormir, porque eu vou explicar em pequenos detalhes como as rotinas funcionam exatamente 😉

Se você gostaria de me contactar, ou ao time, há muitos modos que você pode fazê-lo:
1) Escrever uma mensagem para Grant Smith/Denthor/Asphyxia em email privado na ASPHYXIA BBS.
2) Escrever uma mensagem aqui na conferência de Programação, na
For Your Eyes Only BBS (do qual eu sou Moderador)
De preferência use isso se você tem uma pergunta geral
de programação ou problemas de que outros se beneficiariam.
4) Escrever para Denthor, Eze ou Livewire na Connectix.
5) Escrever para:
Grant Smith
P.O.Box 270 Kloof
3640
Natal
6) Ligar para mim (Grant Smith) no número (031) 73 2129
(deixe uma mensagem se você ligar quando eu estiver na faculdade)
7) Escreva para mcphail@beastie.cs.und.ac.za na InterNet, e
mencione a palavra Denthor perto do topo da carta.

OBS1 : Se você é um representante de uma companhia ou BBS e quer que a ASPHYXIA faça um demo para você, mande um email pra mim; podemos discutir.
OBS2 : Se você fez/tentou fazer um demo, MANDE PARA MIM! Estamos nos sentindo muito solitários e queremos encontrar/ajudar/trocar código com outros grupos de demos. O que você tem a perder? Mande uma mensagem aqui e podemos ver como transferir. Nós realmente queremos ouvir de você.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Otimização

Antes de começar com o 3D, eu gostaria de enfatizar que muitas dessas rotinas, e provavelmente a maioria das suas, poderiam ser aceleradas usando um pouco de otimização. Deve-se entender, porém, que você deve prestar atenção no QUE otimizar… converter uma rotina que só é chamada uma vez, numa rotina em assembly pode até mostrar seus méritos como programador, mas não faz absolutamente nada para acelerar seu programa. Algo que é chamado muito frequentemente é algo que precisa ser o mais rápido o possível. Para alguns, uma rotina muito usada é a PutPixel. Aqui está a putpixel que eu te dei semana passada:

[Nota: Snowman falando! Eu consultei a documentação oficial da Intel e notei que Denthor está se baseando em clocks do 8088 e não dos processadores 286, 386 e 486. Eu aproveitei e incluí a informação para os outros processadores.]

Procedure Putpixel (X,Y : Integer; Col : Byte; where:word);

 BEGIN                                 -clock ticks-
   Asm                               8088 286 386 486
     push    ds                       14   3   2   3
     push    es                       14   3   2   3
     mov     ax,[where]                8   5   4   1
     mov     es,ax                     2   2   2   3
     mov     bx,[X]                    8   5   4   1
     mov     dx,[Y]                    8   5   4   1
     push    bx                       15   3   2   1
     mov     bx, dx                    2   2   2   1
     mov     dh, dl                    2   2   2   1
     xor     dl, dl                    3   2   2   1
     shl     bx, 1                     2   2   3   3
     shl     bx, 1                     2   2   3   3
     shl     bx, 1                     2   2   3   3
     shl     bx, 1                     2   2   3   3
     shl     bx, 1                     2   2   3   3
     shl     bx, 1                     2   2   3   3
     add     dx, bx                    3   2   2   1
     pop     bx                       12   5   4   4
     add     bx, dx                    3   2   2   1
     mov     di, bx                    2   2   2   3
     xor     al,al                     3   2   2   1
     mov     ah, [Col]                 8   5   4   1
     mov     es:[di],ah               10   3   2   1
     pop     es                       12   5   7   3
     pop     ds                       12   5   7   3
   End;                              ---------------
 END;                                153  75  76  52 Total ticks 


NOTA: Não leve meus clocks como se fossem um evangelho, provavelmente tem um ou dois errados.

Certo, agora alguma otimização. Primeiramente, se você tem instruções de 286 ligadas, você pode colocar os 6 shl,1 com um shl,6. Em segundo, o compilador Pascal automaticamente faz o push e pop de ES, então essas duas linhas podem ser removidas. DS:[SI] não é alterado nesse procedimento, então podemos remover isso também. E mais, além de mover COL para AH, movemos para AL e chamamos stosb (es:[di]:=al; inc di). Vamos dar uma olhada na rotina agora:

Procedure Putpixel (X,Y : Integer; Col : Byte; where:word);

BEGIN                                 -clock ticks-

 Asm                               8088 286 386 486
    mov     ax,[where]                8   5   4   1
    mov     es,ax                     2   2   2   3
    mov     bx,[X]                    8   5   4   1
    mov     dx,[Y]                    8   5   4   1
    push    bx                       15   3   2   1
    mov     bx, dx                    2   2   2   1
    mov     dh, dl                    2   2   2   1
    xor     dl, dl                    3   2   2   1
    shl     bx, 6                     8  11   3   2
    add     dx, bx                    3   2   2   1
    pop     bx                       12   5   4   4
    add     bx, dx                    3   2   2   1
    mov     di, bx                    2   2   2   3
    mov     al, [Col]                 8   5   4   1
    stosb                            11   3   4   5
  End;                             ----------------

END;                                 95  56  43  27 Total ticks

Agora, vamos mover o valor de BX diretamente para DI, com isso, removendo um push e pop caros. O MOV e o XOR de DX podem ser substituídos pelo seu equivalente SHL DX,8

[Nota: Se você está rodando num 286, esse último conjunto de otimizações faz a rotina ficas mais lenta, na verdade! Num 286, um SHL leva 5 clocks, mais o número de casas que você está deslocando. Por exemplo: um “SHL AX,6” levaria 11 batidas de clocks (5 para o SHL e 6 para o “6”).]

Procedure Putpixel (X,Y : Integer; Col : Byte; where:word); assembler;
 BEGIN                                 -clock ticks-
 asm                                 8088 286 386 486
     mov     ax,[where]                8   5   4   1
     mov     es,ax                     2   2   2   3
     mov     bx,[X]                    8   5   4   1
     mov     dx,[Y]                    8   5   4   1
     mov     di,bx                     2   2   2   3
     mov     bx, dx                    2   2   2   1
     shl     dx, 8                     8  13   3   2
     shl     bx, 6                     8  11   3   2
     add     dx, bx                    3   2   2   1
     add     di, dx                    3   2   2   1
     mov     al, [Col]                 8   5   4   1
     stosb                            11   3   4   5
 end;                               ----------------
                                      71  57  36  22 Total ticks 

Como você pode ver, trouxemos os clocks de 153-52 (8088-486) para 71-22 (8088-486)… uma boa melhora. (A rotina putpixel atual do ASPHYXIA só tem 48 ticks). Como você pode ver, só de repassar nas suas rotinas algumas vezes, você pode encontrar e remover instruções desnecessárias, aumentando enormemente a velocidade do seu programa.

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Definindo um objeto 3-D

Desenhar um objeto 3-D não é tão fácil. Sentar e plotar uma lista do pontos X, Y e Z pode ser algo que demanda muito tempo. Então, vamos primeiro dar uma olhada nos três eixos em que você está desenhando:

                Y    Z
               /|\  /
                | /
         X<-----|----->
                |
               \|/

X é o eixo horizontal da esquerda pra direita. Y é o eixo vertical, de cima pra baixo. Z é a profundidade, indo direto pra dentro da tela.

Nesse treinamento, estamos usando linhas, então definimos 2 coordenadas X, Y e Z, um para cada final da reta. Uma linha de longe, na parte superior esquerda dos eixos X e Y, para mais perto no fundo à direita, dos eixos X e Y, poderia ser parecida com isso:

{       x1 y1  z1   x2  y2 z2    }
    ( (-10,10,-10),(10,-10,10) )

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Rodando um ponto com matrizes

Ter um objeto 3-D é inútil a menos que você o rode de algum modo. Para demonstração, vou começar trabalhando com duas dimensões, X e Y.

Digamos que você tem um ponto, A,B, num gráfico.

             Y
             |  /O1 (Cos (a)*A-Sin (a)*B , Sin (a)*A+Cos (a)*B)
             |/      (A,B)
      X<-----|------O-->
             |
             |

Agora, digamos que queiramos rodar esse ponto em 45 graus, no sentido anti-horário. O novo A,B pode ser facilmente calculado usando seno e cosseno, através de uma adaptação do nosso algoritmo de círculo, ou seja

       A2:=Cos (45)*A - Sin (45)*B
       B2:=Sin (45)*A + Cos (45)*B

Eu me lembro que, na oitava série, a gente ia a fundo nesse tipo de matemática. Se você tiver problemas, procure uns bons livros de matemática e dê uma olhada; ele vai te ajudar com provas, etc.

De qualquer modo, temos agora um objeto rodado em duas dimensões, EM TORNO DO EIXO z. Em forma de matriz, a equação se parece com isso:

 [Cos (a)  -Sin (a)    0      0  ]    [ x ]
 [Sin (a)   Cos (a)    0      0  ]  . [ y ]
 [   0        0        1      0  ]    [ z ]
 [   0        0        0      1  ]    [ 1 ]

Eu não vou entrar a fundo em matemáticas de matrizes nesse ponto, já que há muitos livros sobre esse assunto (isso não é parte da matemática de matrizes, no entanto). Para multiplicar uma matriz, adicionar os produtos da linha da matriz da esquerda por todas as colunas da matriz da direita, e repetir isso para todas as colunas da matriz da esquerda. Eu não explico isso tão bem quanto meu professor do primeiro ano, mas dê uma olhada em como eu derivei A2 e B2 acima. Aqui vão as outras matrizes:

Matriz para rodar em torno de Y:

 [ Cos (a)     0     -Sin (a)   0   ]    [ x ]
 [    0        1        0       0   ]  . [ y ]
 [ Sin (a)     0      Cos (a)   0   ]    [ z ]
 [    0        0        0       1   ]    [ 1 ]

Matriz para rodar em torno de X:

 [   1       0                 0   ]    [ x ]
 [   0     Cos (a)  -Sin (a)   0   ]  . [ y ]
 [   0     Sin (a)   Cos (a)   0   ]    [ z ]
 [   0       0         0       1   ]    [ 1 ]

Colocando essas matrizes juntas, podemos transladar nossos pontos 3D em torno de 0, 0, 0. Veja no programa de amostra como as colocamos juntas.

No programa de amostra, temos uma constante, nunca mudando o objeto de base. Este é rodado num segunda variável, que é então desenhada. Tenho certeza de que muitos de vocês podem pensar em modos maneiros de mudar o objeto base, os efeitos do que vai aparecer enquanto o objeto é rodado. Uma ideia é “pulsar” um certo ponto no objeto, de acordo com a batida da música sendo tocada no fundo. Seja criativo. Se você sentir vontade, pode fazer sua própria versão de Transformers 😉

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Desenhando um ponto 3D na tela.

Ter um objeto 3D rotacionado é inútil a menos que o desenhemos na tela. Mas como mostramos um ponto 3D numa tela 2D? A resposta precisa de um pouco de explicação. Examine o seguinte diagrama:

             |       ____--------
          ___|___----  o Objeto em X,Y,Z   o1 
Olho -> O)___|___             Objeto at X,Y,Z2
             |   ----___  
             |           -------- 
            Tela         Campo de visão

Vamos fingir que o centro da tela é o horizonte de nosso pequeno mundo 3D. Se desenharmos uma reta tridimensional do objeto “o” para o centro do nosso olho, e colocar um pixel nas coordenadas X e Y onde ela travessa a tela, notamos que quando fizermos o mesmo com o objeto “o1”, o pixel está mais perto do horizonte, embora suas coordenadas sejam as mesmas, mas o Z de “o1” seja maior que o de “o”. Isso significa que quanto mais longe o ponto está, mais perto do horizonte ele está, ou menor o objeto vai aparecer. Isso parece certo, não? Mas, ouço você gritando, como nós traduzimos isso numa fórmula? A resposta é muito simples. Divida seu X e seu Y pelo seu Z. Pense nisso. Quanto maior o número pelo qual você divide, mais perto de zero, ou o horizonte, é o resultado! Isso significa que, quanto maior Z, mas longe está o objeto! Aqui está uma forma em equação:

[Pascal]
nx := 256x div (z-Zoff)+Xoff    
ny := 256y div (z-Zoff)+Yoff

 [C++] 
nx =  ((256x) / (z-Zoff)) + Xoff;    
nx =  ((256y) / (z-Zoff)) + Yoff;

NOTA: Zoff é o qual longe o objeto inteiro está, Xoff é o valor X do objeto, e Yoff é o valor Y do objeto. No programa de amostra, Xoff começa em 160 e Yoff em 100, de modo que o objeto esteja no meio da tela.

O 256 que você multiplica é a perspectiva com que você está vendo. Mudar isso te dá uma efeito de “visão de peixe”, quando você está visualizando o objeto. De qualquer modo, aí está! Desenhe um pixel em nx,ny e voila! Você está fazendo 3D! Fácil, não foi?

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Possíveis melhorias

Esse programa não é a rotina mais otimizada que você vai encontrar (;-))… ele usa 12 MUL’s e 2 DIV’s por ponto. (A rotina da Asphyxia atualmente tem 9 muls e 2 divs por ponto), matemática de números reais é usada para todos os cálculos no programa de amostra, o que é lento então a gente deveria implementar algo em matemática de pontos fixos (eu vou falar sobre isso mais pra frente). A rotina de linhas usada atualmente é lenta. Chain-4 poderia ser usada para cortar o tempo de flipping para a tela.

Valores de cores para as linhas deveriam ser adicionados, morphing do objeto poderia ser colocado, polígonos poderiam ser usados ao invés de linhas, poderia-se implementar a manipulação de mais de um objeto, clipping poderia ser implementado, ao invés de não desenhar nada se alguma parte dela está fora da tela.

Em outras palavras, você tem um monte de trabalho pela frente 😉

=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=

Fechando

Há muitos livros por aí sobre 3D, e uns poucos exemplos bons também. Dê uma olhada neles, e use os melhores bits para criar a sua própria e única engine 3D, com a qual você pode fazer qualquer coisa que você quiser. Estou muito interessado em 3D (embora EzE e Goth tenham escrito a maioria das rotinas de 3D da ASPHYXIA), e gostaria de ver o que você pode fazer com ele. Deixe uma mensagem para mim de algum dos modos descritos acima.

Estou me aprofundando no obscuro mundo de mapeamento de texturas. Se alguém aí fora tiver algumas rotinas sobre esse assunto e estiver interessado em trocar comigo, me avisa!

O que fazer nos próximos tutoriais? Me ajude com essa! Há algum efeito/área que você gostaria de um pouco de informação? Mande-me uma mensagem!

Infelizmente não recebi nenhuma mensagem sobre as BBS’s que têm essa sériem então a lista que segue é a mesma da vez passada. Me deem seus nomes, sysops!

Aaaaargh!!! Tentei o que pude, mas não consigo pensar numa quote. Fica pra próxima, eu prometo! 😉

Tchau, por enquanto,

  • Denthor
  • Krull

Essas BBS’s possuem a série de treinamento de DEMOS da ASPHYXIA: (ordem alfabética)

ÉÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍËÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍËÍÍÍÍÍËÍÍÍËÍÍÍÍËÍÍÍÍ»
ºBBS Name          ºTelephone No.  ºOpenºMsgºFileºPastº
ÌÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÎÍÍÍÍÍÎÍÍÍÎÍÍÍÍÎÍÍÍ͹
ºASPHYXIA BBS #1   º(031) 765-5312 ºALL º * º *  º *  º
ºASPHYXIA BBS #2   º(031) 765-6293 ºALL º * º *  º *  º
ºConnectix BBS     º(031) 266-9992 ºALL º * º    º    º
ºFor Your Eyes Only BBSº(031)285-318ºA/Hº * º *  º *  º
ÈÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÍÊÍÍÍÍÍÍÍÍÍÍÍÊÍÍÍÍÍÊÍÍÍÊÍÍÍÍÊÍÍÍͼ

Open = Open at all times or only A/H
Msg = Available in message base
File = Available in file base
Past = Previous Parts available

A Nova Krull's HomePage