
1. Introdução
Uma das tarefas mais interessantes resolvidas por meio de gráficos tridimensionais é a criação de "grandes mundos" - cenas longas que contêm um grande número de objetos com a possibilidade de movimento ilimitado pelo palco. A solução para esse problema está nas limitações compreensíveis inerentes ao hardware do computador.
Um exemplo típico: o "grande mundo" ao visualizar a ferrovia no mecanismo OSG. Tudo o que falta são os langoliers que devoram o mundo atrás do trem ...Nesse sentido, surge a necessidade de gerenciar recursos de aplicativos, que se resumem a uma solução óbvia: carregar apenas os recursos (modelos, texturas etc.) necessários para formar uma cena no momento atual com a posição atual do observador; redução dos níveis de detalhe de objetos remotos; descarregando objetos que não são mais necessários da memória do sistema. Na maioria das vezes, gráficos e mecanismos de jogos fornecem um certo conjunto de ferramentas para resolver esses problemas. Hoje, examinamos quais deles estão disponíveis no OpenSceneGraph.
1. Usando níveis de detalhe (LOD)
A técnica de usar níveis de detalhe permite exibir o mesmo objeto com mais ou menos detalhes, dependendo da distância dele até o observador. O uso dessa técnica baseia-se na simples consideração de que os pequenos detalhes de um modelo tridimensional são indistinguíveis a uma grande distância, o que significa que não há necessidade de desenhá-los. Por um lado, essa técnica permite reduzir o número total de primitivas geométricas exibidas no buffer de quadros e, por outro lado, não perder o alcance da exibição dos objetos de cena, o que é muito útil ao criar grandes mundos abertos.
O OSG fornece ferramentas para implementar essa técnica por meio da classe osg :: LOD, herdada do mesmo osg :: Group. Esta classe permite representar o mesmo objeto em vários níveis de detalhe. Cada nível de detalhe é caracterizado por uma distância mínima e máxima para o observador, em cuja observância a exibição do objeto é alterada nesse nível de detalhe.
osg :: LOD permite que você especifique esse intervalo imediatamente ao definir um nó filho ou mais tarde, usando os métodos setRange ()
osg::ref_ptr<osg::LOD> lodNode = new osg::LOD; lodNode->addChild(node2, 500.0f, FLT_MAX); lodNode->addChild(node1); . . . lodNode->setRange(1, 0.0f, 500.0f);
Continuamos atormentando Cessna e ilustrando a técnica descrita com um exemplo
Exemplo de Lodmain.h #ifndef MAIN_H #define MAIN_H #include <osg/LOD> #include <osgDB/ReadFile> #include <osgUtil/Simplifier> #include <osgViewer/Viewer> #endif
main.h #include "main.h" int main(int argc, char *argv[]) { (void) argc; (void) argv; osg::ref_ptr<osg::Node> modelL3 = osgDB::readNodeFile("../data/cessna.osg"); osg::ref_ptr<osg::Node> modelL2 = dynamic_cast<osg::Node *>(modelL3->clone(osg::CopyOp::DEEP_COPY_ALL)); osg::ref_ptr<osg::Node> modelL1 = dynamic_cast<osg::Node *>(modelL3->clone(osg::CopyOp::DEEP_COPY_ALL)); osgUtil::Simplifier simplifer; simplifer.setSampleRatio(0.5f); modelL2->accept(simplifer); simplifer.setSampleRatio(0.1f); modelL1->accept(simplifer); osg::ref_ptr<osg::LOD> root = new osg::LOD; root->addChild(modelL1.get(), 200.0f, FLT_MAX); root->addChild(modelL2.get(), 50.0f, 200.0f); root->addChild(modelL3.get(), 0.0f, 50.0f); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run(); }
Primeiro, carregue o modelo
osg::ref_ptr<osg::Node> modelL3 = osgDB::readNodeFile("../data/cessna.osg");
Agora você precisa gerar vários modelos (nos limitaremos a dois exemplos), com um nível de detalhe mais baixo. Para fazer isso, copie o nó carregado duas vezes, usando a técnica da chamada cópia "profunda" da classe, para o nó implementado pelo método clone ()
osg::ref_ptr<osg::Node> modelL2 = dynamic_cast<osg::Node *>(modelL3->clone(osg::CopyOp::DEEP_COPY_ALL)); osg::ref_ptr<osg::Node> modelL1 = dynamic_cast<osg::Node *>(modelL3->clone(osg::CopyOp::DEEP_COPY_ALL));
Agora reduzimos a geometria desses modelos usando a classe osgUtil :: Simplifer. O grau de simplificação do modelo é definido pelo método setSampleRatio () dessa classe - quanto menor o parâmetro passado, menos detalhado o modelo será após a aplicação do procedimento de redução
osgUtil::Simplifier simplifer; simplifer.setSampleRatio(0.5f); modelL2->accept(simplifer); simplifer.setSampleRatio(0.1f); modelL1->accept(simplifer);
Quando temos modelos de diferentes níveis de detalhe, podemos carregá-los no nó raiz, criado como um ponteiro inteligente para osg :: LOD. Para cada nível de detalhe, defina a distância de exibição desse nível
osg::ref_ptr<osg::LOD> root = new osg::LOD; root->addChild(modelL1.get(), 200.0f, FLT_MAX); root->addChild(modelL2.get(), 50.0f, 200.0f); root->addChild(modelL3.get(), 0.0f, 50.0f);
FLT_MAX significa, de alguma maneira, uma distância "infinitamente" grande para o observador. Depois de iniciar o visualizador, obtemos a seguinte imagem
Nível de detalhe 3

Nível de detalhe 2

Nível de detalhe 1

Pode-se observar que, quando a câmera é afastada do objeto, os detalhes da geometria exibida diminuem. Usando esta técnica, você pode obter alto realismo da cena com baixo consumo de recursos.
2. Técnica de carregamento em segundo plano para nós de cena
O mecanismo OSG fornece as classes osg :: ProxyNode e osg :: PagedLOD, projetadas para equilibrar a carga ao renderizar a cena. Ambas as classes herdam de osg :: Group.
Um nó do tipo osg :: ProxyNode reduz o tempo de inicialização do aplicativo antes da renderização, se a cena tiver um grande número de modelos carregados do disco e exibidos. Funciona como uma interface para arquivos externos, permitindo o carregamento diferido de modelos. Para adicionar nós filhos, use o método setFileName () (em vez de addChild) para definir o nome do arquivo de modelo no disco e carregá-lo dinamicamente.
O nó osg :: PagedNode herda os métodos osg :: LOD e carrega e descarrega níveis de detalhes de maneira a evitar sobrecarregar o pipeline OpenGL e garantir a renderização suave da cena.
3. Carregamento dinâmico (tempo de execução) do modelo
Vamos ver como ocorre o processo de carregamento do modelo usando osg :: ProxyNode.
Exemplo de Proxynodemain.h #ifndef MAIN_H #define MAIN_H #include <osg/ProxyNode> #include <osgViewer/Viewer> #endif
main.cpp #include "main.h" int main(int argc, char *argv[]) { (void) argc; (void) argv; osg::ref_ptr<osg::ProxyNode> root = new osg::ProxyNode; root->setFileName(0, "../data/cow.osg"); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run(); }
O processo de download aqui é um pouco diferente
osg::ref_ptr<osg::ProxyNode> root = new osg::ProxyNode; root->setFileName(0, "../data/cow.osg");
Em vez de carregar explicitamente o modelo de vaca, indicamos ao nó raiz o nome do arquivo em que o modelo e o índice do nó filho estão localizados, onde esse modelo deve ser colocado após o carregamento. Ao executar o programa, obtemos esse resultado

Pode-se ver que o ponto de vista não foi escolhido da melhor maneira - a câmera repousa diretamente no lado espelho da vaca. Isso aconteceu porque o modelo foi carregado após o início da renderização e a inicialização da câmera, quando o nó 0 ainda não estava visível. O visualizador simplesmente não conseguiu calcular os parâmetros necessários da câmera. No entanto, o modelo foi carregado e podemos configurar o modo de exibição manipulando o mouse

O que acontece no exemplo acima? osg :: ProxyNode e osg :: PagedLOD funcionam neste caso como contêineres. O gerenciador de dados interno do OSG enviará solicitações e carregará dados no gráfico de cena conforme a necessidade de arquivos de modelo e níveis de detalhe.
Esse mecanismo funciona em vários fluxos de segundo plano e controla o carregamento de dados estáticos localizados em arquivos em disco e dados dinâmicos gerados e adicionados durante a execução do programa.
O mecanismo processa automaticamente os nós que não são exibidos na viewport atual e os remove do gráfico de cena quando a renderização é sobrecarregada. No entanto, esse comportamento não afeta os nós osg :: ProxyNode.
Como o nó proxy, a classe osg :: PagedLOD também possui um método setFileName () para especificar o caminho para o modelo carregado, no entanto, você deve definir o intervalo de visibilidade para ele, assim como para o nó osg :: LOD. Desde que tenhamos um arquivo cessna.osg e um modelo low-poly de nível L1, podemos organizar o nó paginado da seguinte maneira
osg::ref_ptr<osg::PagedLOD> pagedLOD = new osg::PagedLOD; pagedLOD->addChild(modelL1, 200.0f, FLT_MAX ); pagedLOD->setFileName( 1, "cessna.osg" ); pagedLOD->setRange( 1, 0.0f, 200.0f );
Você precisa entender que o nó modelL1 não pode ser descarregado da memória, pois este é um nó não proxy filho normal.
Ao renderizar, a diferença entre osg :: LOD e osg :: PagedLOD não é visível do lado de fora, se você usar apenas um nível de detalhe do modelo. Uma idéia interessante seria organizar um grande cluster de modelos Cessna usando a classe osg :: MatrixTransform. Para isso, você pode usar, por exemplo, uma função
osg::Node* createLODNode( const osg::Vec3& pos ) { osg::ref_ptr<osg::PagedLOD> pagedLOD = new osg::PagedLOD; … osg::ref_ptr<osg::MatrixTransform> mt = new osg::MatrixTransform; mt->setMatrix( osg::Matrix::translate(pos) ); mt->addChild( pagedLOD.get() ); return mt.release(); }
Um exemplo de programa que implementa carregamento em segundo plano de 10.000 aeronaves
main.h #ifndef MAIN_H #define MAIN_H #include <osg/PagedLOD> #include <osg/MatrixTransform> #include <osgViewer/Viewer> #endif
main.cpp #include "main.h"
Supõe-se que a aeronave esteja localizada em um avião com um intervalo de 50 unidades de coordenadas. Ao carregar, veremos que apenas as cessões que entram no quadro são carregadas. Os planos que desaparecem do quadro desaparecem da árvore da cena.

Conclusão
Esta lição da série OpenSceneGraph será a última no formato Como. No curso de doze artigos, foi possível ajustar os princípios básicos de trabalho e uso do OpenSceneGraph na prática. Eu realmente espero que esse mecanismo fique mais claro para o desenvolvedor que fala russo.
Isso não significa que estou encerrando o tópico do OpenSceneGraph sobre o recurso; pelo contrário, está planejado dedicar artigos futuros a técnicas e métodos mais avançados de uso do OSG no desenvolvimento de aplicativos gráficos. Mas, para isso, é necessário acumular bom material e processar muitas fontes de língua inglesa, e isso leva tempo.
Mas não digo adeus, obrigado pela atenção e até
breve!