📜 ⬆️ ⬇️

OpenSceneGraph: Procedural animation of geometry and state attributes

image

Introduction


Speaking about programming techniques specific to OSG , last time we talked about the callback mechanism (Callback) and its implementation in the engine. It is time to look at what opportunities the use of this mechanism gives us to control the contents of the three-dimensional scene.

If we talk about the animation of objects, OSG provides the developer with two possibilities for its implementation:

  1. Procedural animation implemented programmatically through the transformation of objects and their attributes
  2. Exporting animation from a 3D editor and managing it from application code

For a start, consider the first possibility as the most obvious. About the second we will definitely talk later.

1. Procedural morph animation


When traversing a scene graph, OSG transfers data to the OpenGL pipeline, which runs in a separate thread. This thread must be synchronized with other processing threads in each frame. Failure to comply with this requirement may cause the frame () method to complete before processing geometry data. This will lead to unpredictable program behavior and crashes. OSG offers a solution to this problem in the form of the setDataVariance () method of the osg :: Object class, which is the base for all objects in the scene. You can set three modes of processing objects

  1. UNSPECIFIED (by default) - OSG independently determines the processing order of the object.
  2. STATIC - the object is immutable and the order of its processing is not important. Significantly accelerates rendering.
  3. DYNAMIC - the object must be processed before the start of the drawing.

This setting can be set at any time by calling.

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

It is generally accepted practice of modifying geometry on the fly, that is, changing the coordinates of vertices, normals of colors and textures dynamically in each frame, obtaining a modified geometry. This technique is called morph animation. In this case, the order of processing geometry is decisive - all its changes must be recalculated before the drawing starts. To illustrate this technique, let's slightly change the example with a colored square, causing one of its vertices to rotate around the X axis.

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


Creating a square will make a separate function

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

a description of which, in principle, is not required, since we have done this many times. To modify the vertices of this square, write the class DynamicQuadCallback, inheriting it from osg :: Drawable :: UpdateCallback

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

overriding the update () method

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

Here we get a pointer to a geometry object.

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

we read a list of vertices from geometry (or rather, a pointer to it)

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

To get the last element (the last vertex) in the array, the osg :: Array class provides the back () method. To perform the rotation of the vertex relative to the X axis, enter the quaternion

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

that is, we specified a quaternion that implements a rotation around the X axis at an angle of 0.01 * Pi. We rotate the vertex by multiplying the quaternion by the vector that specifies the coordinates of the vertex

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

The last two calls recalculate the display list and dimensional parallelepiped for modified geometry

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

In the body of the main () function, we create a square, set a dynamic drawing mode for it, and add a callback that modifies the geometry.

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

I will leave the creation of the root node and the launch of the viewer indiscriminately, since we have already done this at least twenty times in different versions. As a result, we have the simplest morph animation.



Now try to remove (comment) the setDataVariance () call. Perhaps we will not see anything criminal in this case - by default, OSG tries to automatically determine when to update the geometry data, trying to synchronize with the drawing. Then try changing the mode from DYNAMIC to STATIC and you will see that the image is not rendered smoothly, with noticeable jerks, errors and warnings like this

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

If you do not execute the dirtyDisplayList () method, OpenGL will ignore all changes to the geometry and will use the display list created at the very beginning when creating the square to draw. Delete this call and see that there is no animation.

Without calling the dirtyBound () method, the bounding box will not be recalculated and OSG will incorrectly cut the invisible faces.

2. The concept of motion interpolation


Suppose a train from station A to station B spends 15 minutes on this movement. How can you simulate this situation by changing the position of the train in the callback? The easiest way is to correlate the position of station A with time 0, and station B with time 15 minutes and move the train evenly between these times. Such a simple approach is called linear interpolation. With linear interpolation, the vector defining the position of the intermediate point is described by the formula

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

where p0 is the starting point; p1 is the end point; t is a parameter that varies uniformly from 0 to 1. However, the movement of the train is much more difficult: after leaving station A it accelerates, then moves at a constant speed, and then slows down, stopping at station B. Linear interpolation is no longer able to describe this process looks unnatural.

OSG provides the developer with the osgAnimation library containing a number of standard interpolation algorithms used to smoothly animate the movement of scene objects. Each of these functions usually has two arguments: the initial value of the parameter (usually 0) and the final value of the parameter (usually 1). These functions can be applied to the starting part of the movement (InMotion), to the ending area (OutMotion) or to the starting and ending part of the movement (InOutMotion)

Movement typein-classout classin / out class
Linear interpolationLinearmotion--
Quadratic interpolationInquadmotionOutQuadMotionInOutQuadMotion
Cubic interpolationIncubicmotionOutCubicMotionInOutCubicMotion
4-order interpolationInquartmotionOutQuartMotionInOutQuartMotion
Interpolation with rebound effectInBounceMotionOutbouncemotionInOutBounceMotion
Interpolation with elastic reboundInElasticMotionOutElasticMotionInOutElasticMotion
Sinusoidal interpolationInSineMotionOutSineMotionInOutSineMotion
Interpolation inverse functionInBackMotionOutBackMotionInOutBackMotion
Circular interpolationIncircmotionOutcircmotionInOutCircMotion
Exponential interpolationInExpoMotionOutExpoMotionInOutExpoMotion

To create a linear interpolation of the motion of an object, we write such code.

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

3. Animation of transformation nodes


Path motion animation is the most common type of animation in graphics applications. This technique can be used to animate a car’s movement, an airplane’s flight, or a camera’s motion. The trajectory is set in advance, with all positions, turns and scale changes at key points in time. At the start of the simulation cycle, the state of the object is recalculated in each frame, using linear interpolation for position and scaling and spherical linear interpolation for rotation quaternions. For this, the internal method slerp () of the class osg :: Quat is used.

OSG provides the osg :: AnimationPath class for describing a time-varying trajectory. The method of this class insert () is used to add control points to the trajectory corresponding to specific points in time. The control point is described by the class osg :: AnimationPath :: ControlPoint, whose constructor takes a position as parameters, and, optionally, object rotation and scaling parameters. for example

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

Here t1, t2 - time points in seconds; rot1 is the rotation parameter at time t1, described by the quaternion osg :: Quat.

You can control the looping of the animation through the setLoopMode () method. The default mode is LOOP - the animation will be continuously repeated. Other possible values: NO_LOOPING - play the animation once and SWING - loop to play the movement in the forward and reverse directions.

After all the initializations have been completed, we attach the osg :: AnimationPath object to the built-in osg :: AnimationPathCallback object, which is derived from the osg :: NodeCallback class.

4. An example of the animation of the movement along the trajectory.


Now we make our Cessna move in a circle with a center at the point (0,0,0). The position of the aircraft on the trajectory will be calculated by linear interpolation of the position and orientation between the key frames.

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


We start by creating the trajectory of the aircraft, putting this code into a separate function

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

As parameters, the function takes the radius of the circle, in which the plane moves and the time during which it takes one turn. Inside the function, create an object of the trajectory and enable the mode of cyclic animation repetition

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

Following code

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

calculates trajectory approximation parameters. We divide the entire trajectory into numSamples of rectilinear sections, and calculate the change in the angle of rotation of the aircraft around the vertical axis (yaw) delta_yaw and the change in time delta_time as it moves from section to section. Now create the necessary control points.

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

In the cycle, all trajectories from the first to the last are sorted. Each control point is characterized by a yaw angle.

 double yaw = delta_yaw * i; 

the position of the center of mass of the aircraft in space

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

Rotate the aircraft to the desired yaw angle (relative to the vertical axis) set by quaternion

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

and then add the calculated parameters to the list of control points of the trajectory

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

In the main program, we draw attention to the nuance in specifying the name of the aircraft model file when loading

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

- a certain suffix ".0,0,90.rot" was added to the file name. The mechanism for loading geometry from a file, used in OSG, thus allows you to specify the initial position and orientation of the model after loading. In this case, we want the model to be rotated 90 degrees around the Z axis when loaded.

Next, a root node is created, which is a transformation node, and the model object is added to it as a child node

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

Now we create a callback for trajectory animation, adding to it the path created by the createAnimationPath () function

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

Attach this callback to the transformation node.

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

Initialization and launch of the viewer is performed as usual.

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

Get the animation of the aircraft



Think you did not find anything strange in this example? Earlier, for example in the program, when rendering to a texture was performed, you changed the transformation matrix explicitly to achieve a change in the position of the model in space. Here we only create a transformation node and nowhere in the code does the explicit assignment of the matrix occur.

The secret is that this work is performed by a special class osg :: AnimationPathCallback. In accordance with the current position of the object on the trajectory, it calculates the transformation matrix and automatically applies it to the transformation node to which it is attached, saving the developer from a heap of routine operations.

It should be noted here that attaching osg :: AnimationPathCallback to other types of nodes will not only have no effect, but may also lead to undefined program behavior. It is important to remember that this callback affects only the transformation nodes.

5. Software control animation


The osg :: AnimationPathCallback class provides methods for controlling animation during program execution.

  1. reset () - reset the animation and play it first.
  2. setPause () - pauses the animation. It takes a boolean value as a parameter.
  3. setTimeOffset () - sets the offset in time to the beginning of the animation.
  4. setTimeMultiplier () - sets the time multiplier to speed up / slow down animation.

For example, to remove the animation from a pause and reset, execute such code

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

and to start the animation from the fourth second after launching the program with double acceleration, such code

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

6. The order of rendering primitives in OpenGL


OpenGL stores vertex and primitive data in various buffers, such as color buffer (depth buffer), depth buffer (stencil buffer), and so on. In addition, it does not overwrite the vertices and triangular faces already sent to its pipeline. This means that OpenGL creates new geometry, regardless of how the existing geometry was created. This means that the order in which primitives are sent to the rendering pipeline significantly affects the final result that we see on the screen.

Based on data from the depth buffer, OpenGL will correctly draw opaque objects, sorting the pixels according to their distance from the observer. However, when using the color mixing technique, for example, when implementing transparent and translucent objects, a special operation of updating the color buffer will be performed. New and old pixels of the image are mixed, taking into account the value of the alpha channel (the fourth component of color). This leads to the fact that the rendering order of translucent (translucent) and opaque (opaque) faces affects the final result.



in the figure, in the situation on the left, opaque and then transparent objects were sent to the conveyor first, which led to the correct color shift in the buffer and the correct display of the faces. In the right situation, transparent objects were first drawn, and then opaque, which led to incorrect display.

The setRenderingHint () method of the osg :: StateSet class specifies OSG for the required order of rendering nodes and geometric objects, if it is necessary to do this explicitly. This method simply indicates whether or not translucent edges should be taken into account when rendering, thereby ensuring that if there are translucent edges in the scene, opaque and then transparent edges will be drawn first, taking into account the distance of the faces from the observer. To inform the engine that this node is opaque, use such code

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

or contains transparent faces

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

7. An example of the implementation of translucent objects


Let us try to illustrate all the above theoretical introduction with a concrete example of the implementation of a semi-transparent object.

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


For the most part, the code presented here contains nothing new: two geometric objects are created — a textured square and a hang glider, whose model is loaded from a file. However, we apply white translucent color to all the vertices of the square.

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

- the value of the alpha channel is 0.5, which, when mixed with the texture colors, should give the effect of a translucent object. In addition, the color blending function should be set to handle transparency.

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

passing it to the OpenGL state machine

 stateset->setAttributeAndModes(blendFunc); 

When compiling and running this program, we get the following result



Stop! And where is the transparency? The thing is, we forgot to indicate to the engine that transparent edges should be processed, which can be easily solved by calling

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

after which we get the result we need - the glider wing shines through a semi-transparent textured square



The parameters of the blending function GL_SRC_ALPHA and GL_ONE_MINUS_SRC_ALPHA mean that the resulting screen pixel when drawing a translucent face will have color components calculated by the formula

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

where [srcR, srcG, srcB] are the components of the texture color of the square;[dstR, dstG, dstB] are the components of the color of each pixel of that area on which a translucent face is superimposed, taking into account the fact that the background and opaque edges of the glider wing are already drawn on this spot. By srcA I mean the alpha component of the color of the square.

The seRenderingHint () method perfectly arranges primitive drawing, but it is not very efficient to use, since sorting transparent objects by depth when rendering a frame is quite a resource-intensive operation. Therefore, the developer should take care of the order of drawing the edges on their own, if possible at the preliminary stages of the preparation of the scene.

8. Animation of state attributes


With the help of animation you can control and state attributes. A whole range of visual effects can be generated by changing the properties of one or more rendering attributes. Such an animation that changes the state of the rendering attributes is easy to implement through callbacks when the scene is updated.

Classes of standard interpolations can also be used to define the function for changing attribute parameters.

We already have experience in creating translucent objects. We know that if the alpha component of the color is zero, we get a fully transparent object, with a value of 1 - completely opaque. It is clear that by varying this parameter from 0 to 1 in time, you can get the effect of the gradual appearance or disappearance of the object. We illustrate this with a specific example.

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


We start by creating a callback handler for changing the alpha channel value over time.

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

The protected parameter _motion will determine the function by which the alpha value changes over time. For this example, we choose an approximation by a cubic spline, specifying it immediately, in the class constructor

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

This dependence can be illustrated by such a curve.



In the constructor of the InOutCubicMotion object, we determine the limits of variation of the approximated value from 0 to 1. Next, we redefine the operator () for this class in this way

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

We get a pointer to the material

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

The abstract value of the attribute comes to callback, however we attach this handler to the material, so the pointer to the material will come, so we boldly convert the state attribute to the pointer to the material. Next, we set the time interval for the update of the approximating function - the larger it is, the faster the parameter will change within the specified range

 _motion->update(0.0005f); 

Read the value of the approximating function

 float alpha = _motion->getValue(); 

and assign the material a new diffuse color

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

Now we create the scene in the function main (). I think you are tired every time building a square on the vertices, so simplify the task - we generate a square polygon using the standard OSG function

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

The first parameter is the point from which the lower left corner of the square will be built, the other two parameters define the coordinates of the diagonals. Having dealt with the square, create a material for it

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

We specify the color parameters of the material. Ambient color is a parameter characterizing the color of the material in a shaded area that is not available for color sources. Diffuse color is a material's own color, which characterizes the ability of a surface to disperse the color falling on it, that is, what we used to call color in everyday life. The FRONT_AND_BACK parameter indicates that this color attribute is assigned to both the front and back sides of the geometry faces.

Assign a handler created by us to the material.

 material->setUpdateCallback(new AlphaFadingCallback); 

Assign the created material to the square

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

and set other attributes - the color mixing function and indicate that this object has transparent edges

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

Finish shaping the scene and launch the viewer.

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

We get the result in the form of a square smoothly appearing in the scene.



Instead of a conclusion: a small note about dependencies


Surely your example is not compiled, giving an error at the build stage. This is not accidental - pay attention to the line in the header file main.h

 #include <osgAnimation/EaseMotion> 

The OSG header catalog, from which the header file is taken, usually points to the library that contains the implementation of the functions and classes described in the header. Therefore, the appearance of the osgAnimation / directory should suggest that the library of the same name should be added to the link list of the build script of the project, like this (taking into account the paths to the libraries and the build version)

 LIBS += -losgAnimation 

To be continued...

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