OpenSceneGraph:几何和状态属性的过程动画

图片

引言


在谈到OSG特有的编程技术时,我们最后一次讨论了回调机制及其在引擎中的实现。 现在是时候看看这种机制为管理三维场景的内容提供的可能性了。

如果我们谈论对象动画,则OSG为开发人员提供了两种实现方式:

  1. 通过对象及其属性的转换以编程方式实现过程动画
  2. 从3D编辑器导出动画并从应用程序代码对其进行管理

首先,考虑第一种可能性,最明显的可能性。 我们一定会稍后再讨论第二个。

1.程序变形动画


遍历场景图时,OSG会将数据传输到OpenGL管道,该管道在单独的线程中运行。 该线程必须与每个帧中的其他处理线程同步。 否则,可能会导致frame()方法在处理几何数据之前完成。 这将导致不可预测的程序行为并崩溃。 OSG以osg :: Object类的setDataVariance()方法的形式提供了此问题的解决方案,它是所有场景对象的基础。 您可以为对象设置三种处理模式

  1. 未定义(默认)-OSG独立确定对象的处理顺序。
  2. 静态-对象是不可变的,其处理顺序并不重要。 显着加快了渲染速度。
  3. 动态-必须在开始渲染之前处理对象。

可以随时通过以下方式设置此设置:

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

普遍接受的做法是“动态”修改几何形状,即在每帧中动态更改顶点,颜色法线和纹理的坐标,以获得可变的几何形状。 此技术称为变形动画。 在这种情况下,几何图形的处理顺序是决定性的-必须在绘制开始之前重新计算其所有更改。 为了说明这一技巧,我们对彩色方形示例进行了一些修改,迫使其顶点之一绕X轴旋转。

Animquad示例
主文件

 #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(); } 


我们将在单独的函数中创建一个正方形

 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(); } 

原则上不需要描述,因为我们已经反复进行了此类操作。 要修改此正方形的顶点,我们编写DynamicQuadCallback类,从osg :: Drawable :: UpdateCallback继承它

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

覆盖其中的update()方法

 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(); } 

这里我们得到一个指向几何对象的指针

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

我们从几何体中读取顶点列表(或更确切地说是指向顶点的列表)

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

要获取数组中的最后一个元素(最后一个顶点),osg :: Array类提供了back()方法。 为了执行顶点相对于X轴的旋转,我们引入四元数

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

也就是说,我们设置一个四元数,该四元数实现绕X轴旋转0.01 * Pi的角度。 通过将四元数乘以定义顶点坐标的向量来旋转顶点

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

最后两个调用重新计算了显示列表和修改后的几何图形的尺寸平行六面体

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

在main()函数的主体中,我们创建一个正方形,为其设置动态绘制模式,并添加一个修改几何的回调

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

我将不加选择地创建根节点并启动查看器,因为我们已经以不同的方式完成了至少二十次。 因此,我们拥有最简单的变形动画



现在尝试删除(注释)setDataVariance()调用。 在这种情况下,也许我们看不到任何犯罪行为-默认情况下,OSG尝试自动确定何时更新几何数据,并尝试与渲染同步。 然后尝试将模式从“动态”更改为“静态”,您将看到图像显示不流畅,出现明显的抖动,错误和警告,例如

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

如果不执行dirtyDisplayList()方法,则OpenGL将忽略几何中的所有更改,并将使用最开始创建的显示列表来创建用于渲染的正方形。 删除此呼叫,您将看到没有动画。

如果不调用dirtyBound()方法,将不会重新计算边界框,并且OSG将错误地修剪不可见的面。

2.运动插补的概念


假设从A站到B站的火车需要15分钟的路程。 我们如何通过更改回调中火车的位置来模拟这种情况? 最简单的方法是将车站A的位置与时间0相关联,将车站B的位置与15分钟相关联,并在这些时间之间均匀地移动火车。 这种最简单的方法称为线性插值。 在线性插值中,用公式描述指定中间点位置的向量

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

其中p0是起点; p1是终点; t是一个从0到1均匀变化的参数。但是,火车的运动要复杂得多:它离开站A,加速,然后以恒定的速度运动,然后减速,停在站B。这样的过程不再能够描述线性插值和看起来不自然。

OSG为开发人员提供了osgAnimation库,该库包含许多标准插值算法,用于平滑地动画场景对象的运动。 这些函数中的每一个通常都有两个参数:参数的初始值(通常为0)和参数的最终值(通常为1)。 这些功能可以应用于运动的开始(InMotion),运动的结束(OutMotion)或运动的开始和结束(InOutMotion)

机芯类型在课堂上课外进/出班
线性插补线性运动----
二次插值InQuadMotion超越运动InOutQuadMotion
三次插值立方运动出轨运动InOutCubicMotion
4阶插补InQuartMotionOutQuartMotionInOutQuartMotion
弹跳效果插值InBounceMotionOutBounceMotionInOutBounceMotion
弹性回弹插值弹性运动外弹性运动InOutElasticMotion
正弦插值InSineMotion超越运动InOutSineMotion
反插值内向运动内向运动InOutBackMotion
圆弧插补循环运动外循环InOutCircMotion
指数插值InExpoMotion外展InOutExpoMotion

为了创建对象运动的线性插值,我们编写了这样的代码

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

3.转换节点的动画


轨迹动画是图形应用程序中最常见的动画类型。 此技术可用于为汽车的运动,飞机的飞行或相机的运动设置动画。 轨迹是预定义的,所有位置,旋转和比例在关键时间点都会发生变化。 当模拟周期开始时,将在每个帧中重新计算对象的状态,其中线性插值用于位置和缩放,球形线性插值用于旋转四元数。 为此,请使用osg :: Quat类的slerp()内部方法。

OSG提供了osg :: AnimationPath类来描述随时间变化的路径。 此类的方法insert()用于将与某些时间点相对应的控制点添加到轨迹中。 控制点由osg :: AnimationPath :: ControlPoint类描述,该类的构造函数将位置作为参数,并可选地将对象旋转和缩放参数作为参数。 举个例子

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

这里的t1,t2是以秒为单位的时间瞬间; rot1是时间t1处的旋转参数,由osg :: Quat四元数描述。

可以通过setLoopMode()方法控制动画循环。 默认情况下,“ LOOP”模式处于打开状态-动画将连续重复。 其他可能的值:NO_LOOPING-播放动画一次,SWING-循环向前和向后的运动。

在完成所有初始化之后,我们将osg :: AnimationPath对象附加到osg :: AnimationPathCallback内置对象,该对象是从osg :: NodeCallback类派生的。

4.沿路径运动的动画示例


现在,我们将使cessna绕圆心移动,圆心的中心为(0,0,0)。 飞机在轨迹上的位置将通过线性插值关键帧之间的位置和方向来计算。

Animcessna示例
主文件

 #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(); } 


我们首先创建飞机的轨迹,然后将此代码纳入一个单独的函数中

 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(); } 

作为参数,该函数采用平面沿其移动的圆的半径以及旋转一圈的时间。 在函数内部,创建轨迹对象并打开动画循环模式

 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); 

计算轨迹的近似参数。 我们将整个轨迹划分为直线部分的numSamples,并计算平面绕垂直轴(yaw)delta_yaw的旋转角度的变化以及从一个部分移动到另一个部分时时间delta_time的变化。 现在创建必要的控制点

 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)); } 

在循环中,将对轨迹的所有部分进行排序。 每个控制点都以偏航角为特征

 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)); 

在主程序中,我们在启动时指出飞机模型文件的名称时要注意细微差别

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

-在文件名中添加了后缀“ .0,0,90.rot”。 从OSG中使用的文件中加载几何图形的机制允许您指定加载后模型的初始位置和方向。 在这种情况下,我们希望模型在加载后绕Z轴旋转90度。

接下来,创建根节点,即转换节点,并将模型对象作为子节点添加到该根节点中。

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

现在创建一个轨迹动画回调,向其添加由createAnimationPath()函数创建的路径。

 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(); 

获取飞机动作动画



认为您在此示例中发现了什么奇怪的东西? 以前,例如,在程序中,当渲染到纹理时,您显式更改了转换矩阵,以实现模型在空间中的位置更改。 在这里,我们只是创建一个转换节点,并且在代码中没有任何明确的矩阵分配。

秘密在于特殊的osg :: AnimationPathCallback类可以完成此工作。 根据对象在路径上的当前位置,它计算转换矩阵并将其自动应用于与其相连的转换节点,从而使开发人员免于进行一系列常规操作。

应当注意,将osg :: AnimationPathCallback附加到其他类型的节点不仅无效,而且可能导致未定义的程序行为。 重要的是要记住,此回调仅影响转换节点。

5.软件控制动画


osg :: AnimationPathCallback类提供了在程序执行期间控制动画的方法。

  1. reset()-重设动画并首先播放。
  2. setPause()-暂停动画。 将布尔值作为参数
  3. setTimeOffset()-设置动画开始之前的时间偏移。
  4. setTimeMultiplier()-设置动画加速/减速的时间因子。

例如,要从暂停和重置中删除动画,我们执行以下代码

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

并以双倍加速启动程序后从第四秒开始动画,例如

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

6. OpenGL中渲染图元的顺序


OpenGL将顶点和基本数据存储在各种缓冲区中,例如颜色缓冲区,深度缓冲区,模板缓冲区等。 此外,他不会覆盖已经发送到管道的顶点和三角形面。 这意味着,无论如何创建现有几何图形,OpenGL都会创建一个新的几何图形。 这意味着将基元发送到渲染管道的顺序会显着影响我们在屏幕上看到的最终结果。

基于深度缓冲区数据,OpenGL将正确绘制不透明对象,并根据像素与观察者之间的距离对其进行排序。 但是,当使用颜色混合技术时,例如,当实现透明和半透明的对象时,将执行特殊操作以更新颜色缓冲区。 考虑到alpha通道的值(第四种颜色分量),图像的新像素和旧像素将混合在一起。 这导致以下事实:半透明和不透明边缘的渲染顺序会影响最终结果



在图中,在左侧情况下,首先将不透明的对象然后是透明的对象发送到管道,这导致颜色缓冲区中的正确偏移和面部的正确显示。 在正确的情况下,先绘制透明对象,然后绘制不透明对象,这将导致显示不正确。

osg :: StateSet类的setRenderingHint()方法向OSG指示节点和几何对象的所需渲染顺序(如果必须显式完成)。 此方法仅指示在渲染时是否应考虑半透明的面部,从而确保在场景中存在半透明的面部时,考虑到观察者的距离,首先绘制不透明然后透明的面部。 为了通知引擎该节点是不透明的,我们使用以下代码

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

或包含透明边缘

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

7.半透明对象的实现示例


让我们尝试以实现透明对象的具体示例来说明所有上述理论介绍。

透明度示例
主文件

 #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(); } 


在大多数情况下,此处显示的代码不包含任何新内容:创建了两个几何对象-带纹理的正方形和悬挂式滑翔机,其模型是从文件中加载的。 但是,我们将白色半透明颜色应用于正方形的所有顶点

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

-Alpha通道值为0.5,当与纹理颜色混合时,应具有半透明对象的效果。 此外,应设置颜色混合功能以进行透明度处理。

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

将其传递给OpenGL状态机

 stateset->setAttributeAndModes(blendFunc); 

编译并运行该程序时,我们得到以下结果



别说了 透明度在哪里? 问题是我们忘记告诉引擎应该处理透明边,这很容易通过调用来解决

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

在此之后,我们得到了所需的结果-悬挂式滑翔机翼通过半透明的纹理正方形发光



GL_SRC_ALPHA和GL_ONE_MINUS_SRC_ALPHA混合函数的参数意味着,绘制半透明面时所得的屏幕像素将具有通过公式计算的颜色分量

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

其中[srcR,srcG,srcB]是正方形纹理的颜色分量;[dstR,dstG,dstB]-假定已经在此位置绘制了滑翔机翼的背景和不透明边缘,则在该部分上叠加了半透明面的每个像素的颜色分量。srcA是指正方形颜色的alpha分量。

seRenderingHint()方法可以完美地安排基元的呈现,但是使用它的效率不是很高,因为在呈现帧时按深度对透明对象进行排序是一项非常耗费资源的操作。因此,如果可能的话,在场景准备的初始阶段,开发人员应注意自己绘制面孔的顺序。

8.状态属性的动画


使用动画,您还可以控制状态属性。通过更改一个或多个渲染属性的属性,可以生成整个视觉效果。在场景更新时,通过回调机制很容易实现这种改变渲染属性状态的动画。

标准插值的类别也可以用于指定更改属性参数的功能。

我们已经具有创建半透明对象的经验。我们知道,如果颜色的alpha分量为零,我们将得到一个完全透明的对象,其值为1-完全不透明。显然,通过将该参数从0改变为1,可以得到物体逐渐出现或消失的效果。我们用一个具体的例子来说明这一点。

淡入示例
主文件

 #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(); } 


我们首先创建一个回调处理程序,以随着时间的推移更改Alpha通道的值

 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将确定Alpha值随时间变化的功能。对于此示例,我们选择三次样条曲线逼近,并在类构造函数中立即进行设置

 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); 

属性的抽象值用于回调,但是我们将此处理程序附加到物料,因此它是即将出现的物料的指针,因此我们可以安全地将state属性转换为物料的指针。接下来,我们设置近似函数的更新间隔-间隔越大,参数在指定范围内变化的速度越快

 _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)); 

我们指出材料的颜色选项。环境颜色是表征阴影区域中材料颜色的参数,颜色源无法访问。漫反射颜色是材料本身的颜色,它表征了表面扩散掉其上的颜色的能力,即我们在日常生活中通常所说的颜色。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 

待续...

Source: https://habr.com/ru/post/zh-CN437724/


All Articles