OSG data loading performance optimization: merging geometry

1. Theoretical knowledge

OSG finally draws osg::Geometry, and then the Geometry will be stored in the Node. For example, if we want to draw 10 lines, we have two methods. One method is to define 10 Geometry, and each Geometry defines two vertices to draw one line. Another method is to define a Geometry with 20 vertices and draw 10 lines at one time. The performance of the two methods varies greatly.

Because 10 geometries are defined in method 1, the CPU will do Event, Cull, Update and Draw for 10 nodes, which takes a lot of performance. The GPU will also accept data from the CPU for 10 times, the communication between them will increase, and the rendering workload on the GPU will increase and slow down. The following is cookbook8, drawing 300x300=90000 Geometry. Data rendering efficiency when each Geometry has only 4 vertices:

The second method is to define only one Geometry, press in 300x300x4=360000 vertices, which is also 90000 quadrilaterals, and a quadrilateral is 4 vertices. The results are as like as two peas, but their performance will be greatly improved.

In short, merging multiple geometries into one Geometry is almost the simplest, most effective, most common and most appropriate optimization method in the industry. That is, the CPU submits data to the GPU as many times as possible.

2. The attached code is as follows

// osgPro221.cpp: this file contains the "main" function. Program execution will begin and end here.
//
#include <windows.h>
#include <iostream>

/* -*-c++-*- OpenSceneGraph Cookbook
 * Chapter 8 Recipe 1
 * Author: Wang Rui <wangray84 at gmail dot com>
*/

#include <osg/Geometry>
#include <osg/Group>
#include <osgDB/ReadFile>
#include <osgViewer/ViewerEventHandlers>
#include <osgViewer/Viewer>

#pragma comment(lib, "OpenThreadsd.lib")
#pragma comment(lib, "osgd.lib")
#pragma comment(lib, "osgDBd.lib")
#pragma comment(lib, "osgUtild.lib")
#pragma comment(lib, "osgGAd.lib")
#pragma comment(lib, "osgViewerd.lib")
#pragma comment(lib, "osgTextd.lib")

float randomValue(float min, float max)
{
    return (min + (float)rand() / (RAND_MAX + 1.0f) * (max - min));
}

osg::Vec3 randomVector(float min, float max)
{
    return osg::Vec3(randomValue(min, max),
        randomValue(min, max),
        randomValue(min, max));
}

osg::Matrix randomMatrix(float min, float max)
{
    osg::Vec3 rot = randomVector(-osg::PI, osg::PI);
    osg::Vec3 pos = randomVector(min, max);
    return osg::Matrix::rotate(rot[0], osg::X_AXIS, rot[1], osg::Y_AXIS, rot[2], osg::Z_AXIS) *
        osg::Matrix::translate(pos);
}

#define MERGE_GEOMETRY  // Comment this to disable merging geometries

#ifndef MERGE_GEOMETRY

osg::Node* createTiles(unsigned int cols, unsigned int rows)
{
    osg::ref_ptr<osg::Geode> geode = new osg::Geode;
    for (unsigned int y = 0; y < rows; ++y)
    {
        for (unsigned int x = 0; x < cols; ++x)
        {
            osg::Vec3 center((float)x, 0.0f, (float)y);

            osg::ref_ptr<osg::Vec3Array> va = new osg::Vec3Array(4);
            (*va)[0] = center + osg::Vec3(-0.45f, 0.0f, -0.45f);
            (*va)[1] = center + osg::Vec3(0.45f, 0.0f, -0.45f);
            (*va)[2] = center + osg::Vec3(0.45f, 0.0f, 0.45f);
            (*va)[3] = center + osg::Vec3(-0.45f, 0.0f, 0.45f);

            osg::ref_ptr<osg::Vec3Array> na = new osg::Vec3Array(1);
            na->front() = osg::Vec3(0.0f, -1.0f, 0.0f);

            osg::ref_ptr<osg::Vec4Array> ca = new osg::Vec4Array(1);
            ca->front() = osg::Vec4(randomVector(0.0f, 1.0f), 1.0f);

            osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;
            geom->setVertexArray(va.get());
            geom->setNormalArray(na.get());
            geom->setNormalBinding(osg::Geometry::BIND_OVERALL);
            geom->setColorArray(ca.get());
            geom->setColorBinding(osg::Geometry::BIND_OVERALL);
            geom->addPrimitiveSet(new osg::DrawArrays(GL_QUADS, 0, 4));
            geode->addDrawable(geom.get());
        }
    }
    return geode.release();
}

#else

osg::Node* createTiles(unsigned int cols, unsigned int rows)
{
    unsigned int totalNum = cols * rows, index = 0;
    osg::ref_ptr<osg::Vec3Array> va = new osg::Vec3Array(totalNum * 4);
    osg::ref_ptr<osg::Vec3Array> na = new osg::Vec3Array(totalNum);
    osg::ref_ptr<osg::Vec4Array> ca = new osg::Vec4Array(totalNum);

    osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;
    geom->setVertexArray(va.get());
    geom->setNormalArray(na.get());
    geom->setNormalBinding(osg::Geometry::BIND_PER_PRIMITIVE_SET);
    geom->setColorArray(ca.get());
    geom->setColorBinding(osg::Geometry::BIND_PER_PRIMITIVE_SET);

    for (unsigned int y = 0; y < rows; ++y)
    {
        for (unsigned int x = 0; x < cols; ++x)
        {
            unsigned int vIndex = 4 * index;
            osg::Vec3 center((float)x, 0.0f, (float)y);
            (*va)[vIndex + 0] = center + osg::Vec3(-0.45f, 0.0f, -0.45f);
            (*va)[vIndex + 1] = center + osg::Vec3(0.45f, 0.0f, -0.45f);
            (*va)[vIndex + 2] = center + osg::Vec3(0.45f, 0.0f, 0.45f);
            (*va)[vIndex + 3] = center + osg::Vec3(-0.45f, 0.0f, 0.45f);

            (*na)[index] = osg::Vec3(0.0f, -1.0f, 0.0f);
            (*ca)[index] = osg::Vec4(randomVector(0.0f, 1.0f), 1.0f);
            geom->addPrimitiveSet(new osg::DrawArrays(GL_QUADS, vIndex, 4));
            index++;
        }
    }

    osg::ref_ptr<osg::Geode> geode = new osg::Geode;
    geode->addDrawable(geom.get());
    return geode.release();
}

#endif

int main(int argc, char** argv)
{
	//Create Viewer objects, scene browser
	osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();

	osg::ref_ptr<osg::Group> root = new osg::Group();

	//Add to scene
	root->addChild(createTiles(300, 300));

    viewer->setSceneData(root.get());

    viewer->addEventHandler(new osgViewer::StatsHandler);
    viewer->addEventHandler(new osgViewer::WindowSizeHandler());

	viewer->realize();

	viewer->run();

	return 0;
}

By defining MERGE_GEOMETRY to enable optimization.

3. Merge tool in OSG

OSG has a class called osgUtil::Optimizer. After you open it, you will find that there are many optimizations in it, including MERGE_GEOMETRY, when we use the following statement, the geometry of nodes will be merged normally:

 osgUtil::Optimizer optimizer;
 optimizer.optimize(loadedModel.get());

A tool class method is used internally, which is called:

MergeGeometryVisitor mgv(this);
mgv.setTargetMaximumNumberOfVertices(10000);
node->accept(mgv);

Its idea is also very simple. First, Group is combined one level at a time, and then multiple geometries are combined into the same. Primitivesets that can be merged are also merged.

osgUtil::Optimizer can only perform some general merges. After we understand the idea, we can write personalized tools manually or for our own scenes, so as not to close what we don't want to close, and what we want to close can't be closed.

Keywords: osg

Added by WildcatRudy on Sat, 22 Jan 2022 00:18:48 +0200