Saltar para o conteúdo

Panda3D/Manual/Instancing

Origem: Wikilivros, livros abertos por um mundo aberto.

Digamos que você criou um inimigo para o heroi de seu jogo em algum programa de modelagem 3D e você o coloca no seu cenário. Só que uma copia do inimigo não é o suficiente não é? Que tal ter 50 cópias exatas do seu inimigo, realizando as mesmas ações, lado a lado, para dar um pouco de medo no seu heroi? Para fazer algo assim no Panda3D, você precisa disso:

  for i in range(50):
    inimigo = Actor.Actor("inimigo.egg", {"ataque":"ataque.egg"})
    inimigo.loop("ataque")
    inimigo.setPos(i*5,0,0)
    inimigo.reparentTo(render)

Aqui está o grafico de cena que acabamos de criar:

Isso funciona direito, mas é meio dispendioso. Animar um modelo envolve muitos calculos de matriz por vertice. Nesse caso, nós estamos animando 50 copias do mesmo modelo usando 50 copias da mesma animação. Isso é um bocado de calculo desnecessario. Porém existe uma forma de contornar esse problema: A tecnica é chamada instancionar.

A idéia é essa: Ao invés de criar 50 inimigos separados, crie apenas um inimigo, então a engine vai atualizar a animação do inimigo apenas uma vez. A engine renderiza o inimigo 50 vezes, ao inserir ele no grafico de cena em 50 lugares diferentes. Aqui está como isso é feito:

  inimigo = Actor.Actor("inimigo.egg", {"ataque":"ataque.egg"})
  inimigo.loop("ataque")
  inimigo.setPos(0,0,0)
  for i in range(50):
    placeholder = render.attachNewNode("inimigo-Placeholder")
    placeholder.setPos(i*5,0,0)
    inimigo.instanceTo(placeholder)

Aqui está o diagrama que acabamos de criar:

Não é mais uma arvore, é um gráfico direcionado acíclico. Mas o renderizador ainda dá traverses no grafico usando um traversal de arvore recursivo. Como resultado, ele acaba dando traverse no node de inimigo 50 vezes. Aqui está o diagrama da profundidade do primeiro traversal que o renderer leva pelo gráfico. Note que esse diagrama não é de grafico de cena - é um diagrama do caminho do render pelo grafico de cena.

Em outras palavras, o render visita o Actor "inimigo" 50 vezes. Ele nem percebe que esta visitando mesmo o Actor 50 vezes, ao inves de visitar 50 diferentes Actors. É tudo o mesmo para o render.

TEmos 50 nodes para armazenar a localização das copias do inimigo( placeholder ) alinhados pelo palco. Esses são chamados de nodes vazios. Eles não contem nenhum poligono, eles são objetos minusculos usados principalmente para organização. Nesse caso, eu estou usando cada placeholder como uma plataforma onde o inimigo pode se apoiar.

A posição do inimigo é ( 0, 0, 0 ). Porém essa é a posição relativa do parente. Quando o render está dando traverse na primeira sub-árvore do placeholder, a posição do inimigo é tratada como relativa ao placeholder 1. Quando o render está dando traverse na segunda sub-arvore do placeholder, a posição do inimigo é tratada como relativa ao placeholder 2. Então, apesar do inimigo estar fixado em ( 0, 0, 0 ), ele aparece em multiplas locações na mesma cena ( no topo de cada placeholder ).

Dessa forma, é possivel renderizar o modelo varias vezes sem armazenar e animar o mesmo varias vezes.

Instancionamento Avançado

[editar | editar código-fonte]

Agora, vamos um passo além:

   inimigo = Actor.Actor("inimigo.egg", {"ataque":"ataque.egg"})
   inimigo.loop("kick")
   inimigo.setPos(0,0,0)
   inimigolinha = NodePath('inimigo linha')
   for i in range(50):
     placeholder = inimigolinha.attachNewNode("inimigo-Placeholder")
     placeholder.setPos(i*5,0,0)
     inimigo.instanceTo(placeholder)

Esse é o mesmo codigo de antes, porém ao inves de usar 50 placeholders debaixo do render, eu os coloco embaixo de um node vazio chamado inimigo linha. Então minha linha de inimigos não é parte do gráfico de cena ainda. Agora eu posso fazer isso:

   for i in range(3):
     placeholder = render.attachNewNode("Linha-Placeholder")
     placeholder.setPos(0,i*10,0)
     inimigolinha.instanceTo(placeholder)

Aqui está o grafico de cena que eu acabei de criar:

Mas quando o render der traverse nele usando um algoritmo recursivo de traversal em arvore, ele vai ver 3 principais subarvores ( enraizadas no linha-placeholder), e cada subarvore vai ter 50 placeholders e 50 inimigos, para o total de 150 inimigos aparentes.

Instancing: uma decisão importante

[editar | editar código-fonte]

Instancionar economiza um pouco do tempo da CPU ao animar um modelo no panda. Mas ele não muda o fato de o render ainda precisar renderizar o modelo 150 vezes. Se o inimigo é um modelo de 1000 poligonos, isso daria 150,000 poligonos.

Note que cada instancia tem sua propria caixxa de colisão, á cada uma é dada um occlusion-cull e um frustum-cull diferente.

O nodepath: um Pointer para o Node mais um único ID de instancia

[editar | editar código-fonte]

Se eu tivesse um pointer para o modelo de inimigo da linha de inimigos, e eu tentasse perguntar "onde esta o inimigo", não haveria uma resposta bem definida. O inimigo não é um lugar, ele esta em 150 lugares. Por causa disso, o tipo de pointer de data para o node não tem um metodo que consiga o net transform

Isso é muito incoveniente. Ser capaz de perguntar "onde esta localizado o objeto" é fundamental. Existem outras perguntas úteis que você não pode executar por causa do instancionamento. Por exemplo, você não pode conseguir o "Pai" do node. Você não pode determinar sua cor glocal, ou qualquer outro atributo global. Todas essa perguntas são mal definidas, porque um unico node pode ter muitas posições, cores e "pais". ainda, essas perguntas são essenciais. Então era necessario para os designers do panda3d inventarem alguma forma de executar essas perguntas, apesar do node poder ter varias localizações ao mesmo tempo.

A solução é baseada na seguinte observação: Se eu tenho um pointer para o modelo do inimigo da linha de inimigos, eu tambem teria um identificador unico que o distingue uma das instancias das outras 150, entao eu poderia perguntar pelo net transform daquela instancia particular do node.

Antes, foi notado que o NodePath contem um pointer para um node, alem de algumas informações administraticas. O proposito dessa informação administrativa é de unicamente identificar uma das instancias. Não há um metodo Node::getNetTransform, mas tem um metodo NodePath::getNetTransform. Agora voce sabe porque.

Para entender como o Nodepath conseguiu o nome da instancia, pense sobre o que é necessario para identificar uma isntancia unica. Cada um dos 150 inimigos no grafico de cena correspondem a um unico caminho pelo grafico de cena. Para cada possivel caminho da raiz do imigo, existe uma instancia do inimigo na cena. Em outras palavrasm para identificar unicamente uma isntancia, voce precisa de uma lista dos nodes que começam na folha e vão até a raiz.

A informação administrativa no nodepath é uma lista de nodes. Você pode conseguir qualquer node da lista, usando NodePath:;node(i). O primerio, node(0) é um node para onde o NodePath aponta.