OpenSceneGraph: Animação processual de atributos de geometria e estado

imagem

1. Introdução


Falando sobre técnicas de programação específicas para OSG , a última vez que falamos sobre o mecanismo de retorno de chamada e sua implementação no mecanismo. É hora de examinar as possibilidades que esse mecanismo oferece para gerenciar o conteúdo de uma cena tridimensional.

Se falamos sobre animação de objetos, o OSG fornece ao desenvolvedor duas opções para sua implementação:

  1. Animação processual implementada programaticamente através da transformação de objetos e seus atributos
  2. Exporte a animação e controle-a de um editor 3D a partir do código do aplicativo

Para começar, considere a primeira possibilidade, como a mais óbvia. Definitivamente falaremos sobre o segundo um pouco mais tarde.

1. Animação morphing processual


Ao percorrer o gráfico de cena, o OSG transfere dados para o pipeline OpenGL, que é executado em um encadeamento separado. Esse encadeamento deve ser sincronizado com outros encadeamentos de processamento em cada quadro. Caso contrário, o método frame () será concluído antes do processamento dos dados da geometria. Isso levará a um comportamento imprevisível do programa e a falhas. O OSG oferece uma solução para esse problema na forma do método setDataVariance () da classe osg :: Object, que é a base para todos os objetos de cena. Você pode definir três modos de processamento para objetos

  1. NÃO ESPECIFICADO (por padrão) - OSG determina independentemente a ordem de processamento do objeto.
  2. ESTÁTICO - o objeto é imutável e a ordem de seu processamento não é importante. Acelera significativamente a renderização.
  3. DINÂMICO - o objeto deve ser processado antes do início da renderização.

Essa configuração pode ser definida a qualquer momento chamando

node->setDataVariance( osg::Object::DYNAMIC ); 

A prática geralmente aceita é modificar a geometria "on the fly", ou seja, alterar as coordenadas de vértices, cores normais e texturas dinamicamente em cada quadro, obtendo geometria mutável. Essa técnica é chamada de animação transformada. Nesse caso, a ordem de processamento da geometria é decisiva - todas as suas alterações devem ser recalculadas antes do início do desenho. Para ilustrar esse truque, modificamos levemente o exemplo quadrado colorido, forçando um de seus vértices a girar em torno do eixo X.

Exemplo de Animquad
main.h

 #ifndef MAIN_H #define MAIN_H #include <osg/Geometry> #include <osg/Geode> #include <osgViewer/Viewer> #endif 

main.cpp

 #include "main.h" //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ osg::Geometry *createQuad() { osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array; vertices->push_back( osg::Vec3(0.0f, 0.0f, 0.0f) ); vertices->push_back( osg::Vec3(1.0f, 0.0f, 0.0f) ); vertices->push_back( osg::Vec3(1.0f, 0.0f, 1.0f) ); vertices->push_back( osg::Vec3(0.0f, 0.0f, 1.0f) ); osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array; normals->push_back( osg::Vec3(0.0f, -1.0f, 0.0f) ); osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array; colors->push_back( osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f) ); colors->push_back( osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f) ); colors->push_back( osg::Vec4(0.0f, 0.0f, 1.0f, 1.0f) ); colors->push_back( osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f) ); osg::ref_ptr<osg::Geometry> quad = new osg::Geometry; quad->setVertexArray(vertices.get()); quad->setNormalArray(normals.get()); quad->setNormalBinding(osg::Geometry::BIND_OVERALL); quad->setColorArray(colors.get()); quad->setColorBinding(osg::Geometry::BIND_PER_VERTEX); quad->addPrimitiveSet(new osg::DrawArrays(GL_QUADS, 0, 4)); return quad.release(); } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ class DynamicQuadCallback : public osg::Drawable::UpdateCallback { public: virtual void update(osg::NodeVisitor *, osg::Drawable *drawable); }; //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ void DynamicQuadCallback::update(osg::NodeVisitor *, osg::Drawable *drawable) { osg::Geometry *quad = static_cast<osg::Geometry *>(drawable); if (!quad) return; osg::Vec3Array *vertices = static_cast<osg::Vec3Array *>(quad->getVertexArray()); if (!vertices) return; osg::Quat quat(osg::PI * 0.01, osg::X_AXIS); vertices->back() = quat * vertices->back(); quad->dirtyDisplayList(); quad->dirtyBound(); } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ int main(int argc, char *argv[]) { (void) argc; (void) argv; osg::Geometry *quad = createQuad(); quad->setDataVariance(osg::Object::DYNAMIC); quad->setUpdateCallback(new DynamicQuadCallback); osg::ref_ptr<osg::Geode> root = new osg::Geode; root->addDrawable(quad); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run(); } 


Vamos criar um quadrado em uma função separada

 osg::Geometry *createQuad() { osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array; vertices->push_back( osg::Vec3(0.0f, 0.0f, 0.0f) ); vertices->push_back( osg::Vec3(1.0f, 0.0f, 0.0f) ); vertices->push_back( osg::Vec3(1.0f, 0.0f, 1.0f) ); vertices->push_back( osg::Vec3(0.0f, 0.0f, 1.0f) ); osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array; normals->push_back( osg::Vec3(0.0f, -1.0f, 0.0f) ); osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array; colors->push_back( osg::Vec4(1.0f, 0.0f, 0.0f, 1.0f) ); colors->push_back( osg::Vec4(0.0f, 1.0f, 0.0f, 1.0f) ); colors->push_back( osg::Vec4(0.0f, 0.0f, 1.0f, 1.0f) ); colors->push_back( osg::Vec4(1.0f, 1.0f, 1.0f, 1.0f) ); osg::ref_ptr<osg::Geometry> quad = new osg::Geometry; quad->setVertexArray(vertices.get()); quad->setNormalArray(normals.get()); quad->setNormalBinding(osg::Geometry::BIND_OVERALL); quad->setColorArray(colors.get()); quad->setColorBinding(osg::Geometry::BIND_PER_VERTEX); quad->addPrimitiveSet(new osg::DrawArrays(GL_QUADS, 0, 4)); return quad.release(); } 

uma descrição da qual, em princípio, não é necessária, uma vez que realizamos tais ações repetidamente. Para modificar os vértices desse quadrado, escrevemos a classe DynamicQuadCallback, herdando-a de osg :: Drawable :: UpdateCallback

 class DynamicQuadCallback : public osg::Drawable::UpdateCallback { public: virtual void update(osg::NodeVisitor *, osg::Drawable *drawable); }; 

substituindo o método update () nele

 void DynamicQuadCallback::update(osg::NodeVisitor *, osg::Drawable *drawable) { osg::Geometry *quad = static_cast<osg::Geometry *>(drawable); if (!quad) return; osg::Vec3Array *vertices = static_cast<osg::Vec3Array *>(quad->getVertexArray()); if (!vertices) return; osg::Quat quat(osg::PI * 0.01, osg::X_AXIS); vertices->back() = quat * vertices->back(); quad->dirtyDisplayList(); quad->dirtyBound(); } 

Aqui temos um ponteiro para um objeto de geometria

 osg::Geometry *quad = static_cast<osg::Geometry *>(drawable); 

lemos da geometria uma lista de vértices (ou melhor, um ponteiro para ela)

 osg::Vec3Array *vertices = static_cast<osg::Vec3Array *>(quad->getVertexArray()); 

Para obter o último elemento (último vértice) na matriz, a classe osg :: Array fornece o método back (). Para executar a rotação do vértice em relação ao eixo X, introduzimos o quaternion

 osg::Quat quat(osg::PI * 0.01, osg::X_AXIS); 

isto é, definimos um quaternion que implementa uma rotação em torno do eixo X por um ângulo de 0,01 * Pi. Gire o vértice multiplicando o quaternion por um vetor que define as coordenadas do vértice

 vertices->back() = quat * vertices->back(); 

As duas últimas chamadas recontam a lista de exibição e o paralelepípedo dimensional para a geometria modificada

 quad->dirtyDisplayList(); quad->dirtyBound(); 

No corpo da função main (), criamos um quadrado, configuramos o modo de desenho dinâmico para ela e adicionamos um retorno de chamada modificando a geometria

 osg::Geometry *quad = createQuad(); quad->setDataVariance(osg::Object::DYNAMIC); quad->setUpdateCallback(new DynamicQuadCallback); 

Vou deixar indiscriminadamente criar o nó raiz e iniciar o visualizador, como já fizemos isso pelo menos vinte vezes de maneiras diferentes. Como resultado, temos a animação morphing mais simples



Agora tente remover (comentar) a chamada setDataVariance (). Talvez não veremos nada de criminoso nesse caso - por padrão, o OSG tenta determinar automaticamente quando atualizar os dados da geometria, tentando sincronizar com a renderização. Em seguida, tente alterar o modo de DINÂMICO para ESTÁTICO e você verá que a imagem não é renderizada sem problemas, com reflexos visíveis, erros e avisos como este

 Warning: detected OpenGL error 'invalid value' at after RenderBin::draw(..) 

Se você não executar o método dirtyDisplayList (), o OpenGL ignorará todas as alterações na geometria e usará a lista de exibição criada no início para criar o quadrado para renderização. Exclua esta chamada e você verá que não há animação.

Sem chamar o método dirtyBound (), a caixa delimitadora não será recalculada e o OSG cortará incorretamente as faces invisíveis.

2. O conceito de interpolação de movimento


Suponha que um trem que vai da estação A à estação B leve 15 minutos para viajar. Como podemos simular essa situação alterando a posição do trem no retorno de chamada? A maneira mais fácil é correlacionar a posição da estação A com o tempo 0 e da estação B com 15 minutos e mover o trem uniformemente entre esses horários. Essa abordagem mais simples é chamada interpolação linear. Na interpolação linear, um vetor especificando a posição de um ponto intermediário é descrito pela fórmula

 p = (1 - t) * p0 + t * p1 

onde p0 é o ponto de partida; p1 é o ponto final; t é um parâmetro que varia uniformemente de 0 a 1. No entanto, o movimento do trem é muito mais complicado: sai da estação A, acelera, depois se move a uma velocidade constante e depois desacelera, parando na estação B. Esse processo não é mais capaz de descrever interpolação linear e Parece antinatural.

O OSG fornece ao desenvolvedor a biblioteca osgAnimation, que contém vários algoritmos de interpolação padrão usados ​​para animar suavemente o movimento de objetos de cena. Cada uma dessas funções geralmente possui dois argumentos: o valor inicial do parâmetro (geralmente 0) e o valor final do parâmetro (geralmente 1). Essas funções podem ser aplicadas ao início do movimento (InMotion), ao final do movimento (OutMotion) ou ao início e fim do movimento (InOutMotion)

Tipo de movimentona aulafora da aulaclasse de entrada / saída
Interpolação linearLinearMotion--
Interpolação quadráticaInQuadMotionOutQuadMotionInOutQuadMotion
Interpolação cúbicaInCubicMotionOutcubicmotionInOutCubicMotion
Interpolação de 4 ordensInQuartMotionOutQuartMotionInOutQuartMotion
Interpolação de efeito de rejeiçãoInBounceMotionOutBounceMotionInOutBounceMotion
Interpolação de rebote elásticoInElasticMotionOutElasticMotionInOutElasticMotion
Interpolação sinusoidalInSineMotionOutsinemotionInOutSineMotion
Interpolação inversaInbackmotionOutbackmotionInOutBackMotion
Interpolação circularInCircMotionOutcircmotionInOutCircMotion
Interpolação exponencialInExpoMotionOutexpomotionInOutExpoMotion

Para criar uma interpolação linear do movimento de um objeto, escrevemos esse código

 osg::ref_ptr<osgAnimation::LinearMotion> motion = new osgAnimation::LinearMotion(0.0f, 1.0f); 

3. Animação de nós de transformação


A animação de trajetória é o tipo mais comum de animação em aplicativos gráficos. Essa técnica pode ser usada para animar o movimento de um carro, o voo de um avião ou o movimento da câmera. A trajetória é predefinida, com todas as posições, rotações e mudanças de escala em pontos-chave no tempo. Quando o ciclo de simulação começa, o estado do objeto é recalculado em cada quadro, usando interpolação linear para posição e escala e interpolação linear esférica para quaternions de rotação. Para fazer isso, use o método interno slerp () da classe osg :: Quat.

O OSG fornece a classe osg :: AnimationPath para descrever um caminho variável no tempo. O método desta classe insert () é usado para adicionar pontos de controle correspondentes a determinados pontos no tempo à trajetória. O ponto de controle é descrito pela classe osg :: AnimationPath :: ControlPoint, cujo construtor assume a posição como parâmetros e, opcionalmente, os parâmetros de rotação e dimensionamento de objetos. Por exemplo

 osg::ref_ptr<osg::AnimationPath> path = new osg::AnimationPath; path->insert(t1, osg::AnimationPath::ControlPoint(pos1, rot1, scale1)); path->insert(t2, ...); 

Aqui t1, t2 são instantes de tempo em segundos; rot1 é o parâmetro de rotação no tempo t1, descrito pelo quaternion osg :: Quat.

É possível controlar loops de animação através do método setLoopMode (). Por padrão, o modo LOOP está ativado - a animação será repetida continuamente. Outros valores possíveis: NO_LOOPING - reproduz a animação uma vez e SWING - faz um loop do movimento nas direções para frente e para trás.

Após a conclusão de toda a inicialização, anexamos o objeto osg :: AnimationPath ao objeto interno osg :: AnimationPathCallback, derivado da classe osg :: NodeCallback.

4. Um exemplo de uma animação de movimento ao longo de um caminho


Agora faremos nossa cessna se mover em círculo com o centro no ponto (0,0,0). A posição da aeronave na trajetória será calculada interpolando linearmente a posição e a orientação entre os quadros principais.

Exemplo de Animcessna
main.h

 #ifndef MAIN_H #define MAIN_H #include <osg/AnimationPath> #include <osg/MatrixTransform> #include <osgDB/ReadFile> #include <osgViewer/Viewer> #endif 

main.cpp

 #include "main.h" //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ osg::AnimationPath *createAnimationPath(double radius, double time) { osg::ref_ptr<osg::AnimationPath> path = new osg::AnimationPath; path->setLoopMode(osg::AnimationPath::LOOP); unsigned int numSamples = 32; double delta_yaw = 2.0 * osg::PI / (static_cast<double>(numSamples) - 1.0); double delta_time = time / static_cast<double>(numSamples); for (unsigned int i = 0; i < numSamples; ++i) { double yaw = delta_yaw * i; osg::Vec3d pos(radius * sin(yaw), radius * cos(yaw), 0.0); osg::Quat rot(-yaw, osg::Z_AXIS); path->insert(delta_time * i, osg::AnimationPath::ControlPoint(pos, rot)); } return path.release(); } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ int main(int argc, char *argv[]) { (void) argc; (void) argv; osg::ref_ptr<osg::Node> model = osgDB::readNodeFile("../data/cessna.osg.0,0,90.rot"); osg::ref_ptr<osg::MatrixTransform> root = new osg::MatrixTransform; root->addChild(model.get()); osg::ref_ptr<osg::AnimationPathCallback> apcb = new osg::AnimationPathCallback; apcb->setAnimationPath(createAnimationPath(50.0, 6.0)); root->setUpdateCallback(apcb.get()); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run(); } 


Começamos criando a trajetória da aeronave, levando esse código para uma função separada

 osg::AnimationPath *createAnimationPath(double radius, double time) { osg::ref_ptr<osg::AnimationPath> path = new osg::AnimationPath; path->setLoopMode(osg::AnimationPath::LOOP); unsigned int numSamples = 32; double delta_yaw = 2.0 * osg::PI / (static_cast<double>(numSamples) - 1.0); double delta_time = time / static_cast<double>(numSamples); for (unsigned int i = 0; i < numSamples; ++i) { double yaw = delta_yaw * i; osg::Vec3d pos(radius * sin(yaw), radius * cos(yaw), 0.0); osg::Quat rot(-yaw, osg::Z_AXIS); path->insert(delta_time * i, osg::AnimationPath::ControlPoint(pos, rot)); } return path.release(); } 

Como parâmetros, a função leva o raio do círculo ao longo do qual o avião se move e o tempo durante o qual ele fará uma revolução. Dentro da função, crie um objeto de trajetória e ative o modo de loop de animação

 osg::ref_ptr<osg::AnimationPath> path = new osg::AnimationPath; path->setLoopMode(osg::AnimationPath::LOOP); 

Código a seguir

 unsigned int numSamples = 32; double delta_yaw = 2.0 * osg::PI / (static_cast<double>(numSamples) - 1.0); double delta_time = time / static_cast<double>(numSamples); 

calcula os parâmetros de aproximação da trajetória. Dividimos toda a trajetória em vários exemplos de seções retas e calculamos a mudança no ângulo de rotação do plano em torno do eixo vertical (guinada) delta_yaw e a mudança no tempo delta_time ao passar de uma seção para outra. Agora crie os pontos de controle necessários

 for (unsigned int i = 0; i < numSamples; ++i) { double yaw = delta_yaw * i; osg::Vec3d pos(radius * sin(yaw), radius * cos(yaw), 0.0); osg::Quat rot(-yaw, osg::Z_AXIS); path->insert(delta_time * i, osg::AnimationPath::ControlPoint(pos, rot)); } 

No ciclo, todas as seções da trajetória do primeiro ao último são classificadas. Cada ponto de controle é caracterizado por um ângulo de guinada

 double yaw = delta_yaw * i; 

a posição do centro de massa da aeronave no espaço

 osg::Vec3d pos(radius * sin(yaw), radius * cos(yaw), 0.0); 

A rotação da aeronave para o ângulo de guinada desejado (em relação ao eixo vertical) é definida pelo quaternion

 osg::Quat rot(-yaw, osg::Z_AXIS); 

e adicione os parâmetros calculados à lista de pontos de controle do caminho

 path->insert(delta_time * i, osg::AnimationPath::ControlPoint(pos, rot)); 

No programa principal, prestamos atenção às nuances na indicação do nome do arquivo de modelo da aeronave no momento da inicialização

 osg::ref_ptr<osg::Node> model = osgDB::readNodeFile("../data/cessna.osg.0,0,90.rot"); 

- um sufixo ".0,0,90.rot" foi adicionado ao nome do arquivo. O mecanismo para carregar a geometria de um arquivo usado no OSG permite especificar a posição inicial e a orientação do modelo após o carregamento. Nesse caso, queremos que o modelo seja girado 90 graus ao redor do eixo Z após o carregamento.

Em seguida, o nó raiz é criado, que é o nó de transformação, e o objeto de modelo é adicionado a ele como um nó filho

 osg::ref_ptr<osg::MatrixTransform> root = new osg::MatrixTransform; root->addChild(model.get()); 

Agora crie um retorno de chamada de animação de trajetória, adicionando a ele o caminho criado pela função createAnimationPath ()

 osg::ref_ptr<osg::AnimationPathCallback> apcb = new osg::AnimationPathCallback; apcb->setAnimationPath(createAnimationPath(50.0, 6.0)); 

Anexe esse retorno de chamada ao nó de transformação

 root->setUpdateCallback(apcb.get()); 

O visualizador é inicializado e iniciado como de costume.

 osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run(); 

Obter uma animação em movimento de avião



Acha que não encontrou nada de estranho neste exemplo? Anteriormente, por exemplo, em um programa ao renderizar em uma textura, você alterava explicitamente a matriz de transformação para obter uma alteração na posição do modelo no espaço. Aqui, apenas criamos um nó de transformação e no código não há atribuição de matriz explícita em lugar algum.

O segredo é que a classe osg :: AnimationPathCallback faz esse trabalho. De acordo com a posição atual do objeto no caminho, ele calcula a matriz de transformação e a aplica automaticamente ao nó de transformação ao qual está conectado, salvando o desenvolvedor de várias operações de rotina.

Deve-se observar que anexar osg :: AnimationPathCallback a outros tipos de nós não terá apenas efeito, mas também poderá levar ao comportamento indefinido do programa. É importante lembrar que esse retorno de chamada afeta apenas os nós de transformação.

5. Animação de controle de software


A classe osg :: AnimationPathCallback fornece métodos para controlar a animação durante a execução do programa.

  1. reset () - redefine a animação e reproduza-a primeiro.
  2. setPause () - pausa a animação. Toma um valor booleano como parâmetro
  3. setTimeOffset () - define o deslocamento de tempo antes do início da animação.
  4. setTimeMultiplier () - define o fator de tempo para aceleração / desaceleração da animação.

Por exemplo, para remover a animação de pausar e redefinir, executamos esse código

 apcb->setPause(false); apcb->reset(); 

e iniciar a animação a partir do quarto segundo após iniciar o programa com dupla aceleração, esse código

 apcb->setTimeOffset(4.0f); apcb->setTimeMultiplier(2.0f); 

6. A ordem de renderização de primitivas no OpenGL


O OpenGL armazena dados de vértice e primitivos em vários buffers, como um buffer de cores, um buffer de profundidade, um buffer de estêncil e assim por diante. Além disso, ele não substitui os vértices e as faces triangulares já enviadas ao seu pipeline. Isso significa que o OpenGL cria uma nova geometria, independentemente de como a geometria existente foi criada. Isso significa que a ordem na qual as primitivas são enviadas para o pipeline de renderização afeta significativamente o resultado final que vemos na tela.

Com base nos dados do buffer de profundidade, o OpenGL desenha corretamente objetos opacos, classificando os pixels de acordo com a distância do observador. No entanto, ao usar a técnica de mistura de cores, por exemplo, ao implementar objetos transparentes e translúcidos, uma operação especial será executada para atualizar o buffer de cores. Os pixels novos e antigos da imagem são misturados, levando em consideração o valor do canal alfa (quarto componente de cor). Isso leva ao fato de que a ordem de renderização das bordas translúcidas e opacas afeta o resultado final



Na figura, na situação à esquerda, primeiro objetos opacos e depois transparentes foram enviados para o pipeline, o que levou à mudança correta no buffer de cores e à exibição correta de faces. Na situação certa, os primeiros objetos transparentes foram desenhados e depois opacos, o que levou a uma exibição incorreta.

O método setRenderingHint () da classe osg :: StateSet indica ao OSG a ordem de renderização necessária de nós e objetos geométricos, se isso precisar ser feito explicitamente. Esse método simplesmente indica se as faces translúcidas devem ou não ser levadas em consideração durante a renderização, garantindo assim que, se houver faces translúcidas na cena, as faces opacas e transparentes serão desenhadas primeiro, levando em consideração a distância do observador. Para informar o mecanismo que este nó é opaco, usamos este código

 node->getOrCreateStateSet()->setRenderingHint(osg::StateSet::OPAQUE_BIN); 

ou contém bordas transparentes

 node->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); 

7. Um exemplo da implementação de objetos translúcidos


Vamos tentar ilustrar toda a introdução teórica acima com um exemplo concreto da implementação de um objeto translúcido.

Exemplo de transparência
main.h

 #ifndef MAIN_H #define MAIN_H #include <osg/BlendFunc> #include <osg/Texture2D> #include <osg/Geometry> #include <osgDB/ReadFile> #include <osgViewer/Viewer> #endif 

main.cpp

 #include "main.h" int main(int argc, char *argv[]) { (void) argc; (void) argv; osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array; vertices->push_back( osg::Vec3(-0.5f, 0.0f, -0.5f) ); vertices->push_back( osg::Vec3( 0.5f, 0.0f, -0.5f) ); vertices->push_back( osg::Vec3( 0.5f, 0.0f, 0.5f) ); vertices->push_back( osg::Vec3(-0.5f, 0.0f, 0.5f) ); osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array; normals->push_back( osg::Vec3(0.0f, -1.0f, 0.0f) ); osg::ref_ptr<osg::Vec2Array> texcoords = new osg::Vec2Array; texcoords->push_back( osg::Vec2(0.0f, 0.0f) ); texcoords->push_back( osg::Vec2(0.0f, 1.0f) ); texcoords->push_back( osg::Vec2(1.0f, 1.0f) ); texcoords->push_back( osg::Vec2(1.0f, 0.0f) ); osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array; colors->push_back( osg::Vec4(1.0f, 1.0f, 1.0f, 0.5f) ); osg::ref_ptr<osg::Geometry> quad = new osg::Geometry; quad->setVertexArray(vertices.get()); quad->setNormalArray(normals.get()); quad->setNormalBinding(osg::Geometry::BIND_OVERALL); quad->setColorArray(colors.get()); quad->setColorBinding(osg::Geometry::BIND_OVERALL); quad->setTexCoordArray(0, texcoords.get()); quad->addPrimitiveSet(new osg::DrawArrays(GL_QUADS, 0, 4)); osg::ref_ptr<osg::Geode> geode = new osg::Geode; geode->addDrawable(quad.get()); osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D; osg::ref_ptr<osg::Image> image = osgDB::readImageFile("../data/Images/lz.rgb"); texture->setImage(image.get()); osg::ref_ptr<osg::BlendFunc> blendFunc = new osg::BlendFunc; blendFunc->setFunction(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); osg::StateSet *stateset = geode->getOrCreateStateSet(); stateset->setTextureAttributeAndModes(0, texture.get()); stateset->setAttributeAndModes(blendFunc); osg::ref_ptr<osg::Group> root = new osg::Group; root->addChild(geode.get()); root->addChild(osgDB::readNodeFile("../data/glider.osg")); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run(); } 


Na maioria das vezes, o código mostrado aqui não contém nada de novo: dois objetos geométricos são criados - um quadrado texturizado e uma asa delta, cujo modelo é carregado a partir de um arquivo. No entanto, aplicamos uma cor translúcida branca a todos os vértices do quadrado

 colors->push_back( osg::Vec4(1.0f, 1.0f, 1.0f, 0.5f) ); 

- o valor do canal alfa é 0,5, que, quando misturado com cores de textura, deve dar o efeito de um objeto translúcido. Além disso, a função de mistura de cores deve ser definida para o processamento de transparência.

 osg::ref_ptr<osg::BlendFunc> blendFunc = new osg::BlendFunc; blendFunc->setFunction(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); 

passando para a máquina de estado OpenGL

 stateset->setAttributeAndModes(blendFunc); 

Ao compilar e executar este programa, obtemos o seguinte resultado



Pare com isso! E onde está a transparência? O fato é que esquecemos de dizer ao mecanismo que as bordas transparentes devem ser processadas, o que é facilmente resolvido chamando

 stateset->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); 

após o qual obtemos o resultado que precisamos - a asa delta brilha através de um quadrado texturizado translúcido



Os parâmetros das funções de mistura GL_SRC_ALPHA e GL_ONE_MINUS_SRC_ALPHA significam que o pixel da tela resultante ao desenhar uma face translúcida terá componentes de cores calculados pela fórmula

 R = srcR * srcA + dstR * (1 - srcA) G = srcG * srcA + dstG * (1 - srcA) B = srcB * srcA + dstB * (1 - srcA) 

onde [srcR, srcG, srcB] são os componentes de cor da textura quadrada; [dstR, dstG, dstB] — , , . srcA - .

seRenderingHint() , , . , .

8.


. . , .

.

. , - , , 1 — . , 0 1 .

fading-in
main.h

 #ifndef MAIN_H #define MAIN_H #include <osg/Geode> #include <osg/Geometry> #include <osg/BlendFunc> #include <osg/Material> #include <osgAnimation/EaseMotion> #include <osgDB/ReadFile> #include <osgViewer/Viewer> #endif 

main.cpp

 #include "main.h" //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ class AlphaFadingCallback : public osg::StateAttributeCallback { public: AlphaFadingCallback() { _motion = new osgAnimation::InOutCubicMotion(0.0f, 1.0f); } virtual void operator() (osg::StateAttribute* , osg::NodeVisitor*); protected: osg::ref_ptr<osgAnimation::InOutCubicMotion> _motion; }; //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ void AlphaFadingCallback::operator()(osg::StateAttribute *sa, osg::NodeVisitor *nv) { (void) nv; osg::Material *material = static_cast<osg::Material *>(sa); if (material) { _motion->update(0.0005f); float alpha = _motion->getValue(); material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4(0.0f, 1.0f, 1.0f, alpha)); } } //------------------------------------------------------------------------------ // //------------------------------------------------------------------------------ int main(int argc, char *argv[]) { (void) argc; (void) argv; osg::ref_ptr<osg::Drawable> quad = osg::createTexturedQuadGeometry( osg::Vec3(-0.5f, 0.0f, -0.5f), osg::Vec3(1.0f, 0.0f, 0.0f), osg::Vec3(0.0f, 0.0f, 1.0f)); osg::ref_ptr<osg::Geode> geode = new osg::Geode; geode->addDrawable(quad.get()); osg::ref_ptr<osg::Material> material = new osg::Material; material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4(0.0f, 0.0f, 0.0f, 1.0f)); material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4(0.0f, 1.0f, 1.0f, 0.5f)); material->setUpdateCallback(new AlphaFadingCallback); geode->getOrCreateStateSet()->setAttributeAndModes(material.get()); geode->getOrCreateStateSet()->setAttributeAndModes(new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); geode->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); osg::ref_ptr<osg::Group> root = new osg::Group; root->addChild(geode.get()); root->addChild(osgDB::readNodeFile("../data/glider.osg")); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run(); } 


- -

 class AlphaFadingCallback : public osg::StateAttributeCallback { public: AlphaFadingCallback() { _motion = new osgAnimation::InOutCubicMotion(0.0f, 1.0f); } virtual void operator() (osg::StateAttribute* , osg::NodeVisitor*); protected: osg::ref_ptr<osgAnimation::InOutCubicMotion> _motion; }; 

_motion , . , ,

 AlphaFadingCallback() { _motion = new osgAnimation::InOutCubicMotion(0.0f, 1.0f); } 





InOutCubicMotion 0 1. operator()

 void AlphaFadingCallback::operator()(osg::StateAttribute *sa, osg::NodeVisitor *nv) { (void) nv; osg::Material *material = static_cast<osg::Material *>(sa); if (material) { _motion->update(0.0005f); float alpha = _motion->getValue(); material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4(0.0f, 1.0f, 1.0f, alpha)); } } 



 osg::Material *material = static_cast<osg::Material *>(sa); 

callback , , , . — ,

 _motion->update(0.0005f); 



 float alpha = _motion->getValue(); 



 material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4(0.0f, 1.0f, 1.0f, alpha)); 

main(). , — OSG

 osg::ref_ptr<osg::Drawable> quad = osg::createTexturedQuadGeometry( osg::Vec3(-0.5f, 0.0f, -0.5f), osg::Vec3(1.0f, 0.0f, 0.0f), osg::Vec3(0.0f, 0.0f, 1.0f)); 

, , . ,

 osg::ref_ptr<osg::Material> material = new osg::Material; material->setAmbient(osg::Material::FRONT_AND_BACK, osg::Vec4(0.0f, 0.0f, 0.0f, 1.0f)); material->setDiffuse(osg::Material::FRONT_AND_BACK, osg::Vec4(0.0f, 1.0f, 1.0f, 0.5f)); 

. Ambient color — , , . Diffuse color — , , , . FRONT_AND_BACK , , .



 material->setUpdateCallback(new AlphaFadingCallback); 



 geode->getOrCreateStateSet()->setAttributeAndModes(material.get()); 

— ,

 geode->getOrCreateStateSet()->setAttributeAndModes(new osg::BlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA)); geode->getOrCreateStateSet()->setRenderingHint(osg::StateSet::TRANSPARENT_BIN); 



 osg::ref_ptr<osg::Group> root = new osg::Group; root->addChild(geode.get()); root->addChild(osgDB::readNodeFile("../data/glider.osg")); osgViewer::Viewer viewer; viewer.setSceneData(root.get()); return viewer.run(); 





:


, . – main.h

 #include <osgAnimation/EaseMotion> 

OSG, , , , . osgAnimation/ , , ( )

 LIBS += -losgAnimation 

Para continuar ...

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


All Articles