1、理论知识
OSG最终绘制的都是osg::Geometry,然后Geometry会存放在Node里。比如我们要绘制10条线,那么我们有两种方法,一种方法是定义10个Geometry,每个Geometry定义两个顶点绘制1条线。另一个方法是我们定义1个Geometry,里面放20个顶点,一次性绘制10条线。这两种方法的性能差异很大。
方法一因为定义了10个Geometry,那么CPU端就会做Event、Cull、Update、Draw都会针对10个结点来做,很花性能。GPU也会接受10次来自CPU端的数据,之间通信也会增加,GPU端的绘制工作量也会增加变缓。如下是cookbook8,绘制了300x300=90000个Geometry。每个Geometry只有4个顶点时的数据绘制效率:
而方法二是只定义1个Geometry,压入了300x300x4=36万个顶点,也是90000个四边形,一个四边形是4个顶点。绘制的结果是一模一样的,但是其性能就会大幅提升:
总之来说,将多个Geometry合并成一个Geometry几乎是业界普遍认为最简单、最有效、最普遍、最应该使用的优化方法。也就是CPU尽量少次多量的向GPU提交数据。
2、附上代码如下
// osgPro221.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#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)
{
//创建Viewer对象,场景浏览器
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
osg::ref_ptr<osg::Group> root = new osg::Group();
//添加到场景
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;
}
通过定义 MERGE_GEOMETRY 来开启优化。
3、OSG中的合并工具
OSG有个类叫做osgUtil::Optimizer,大家打开后就发现里面有好多的优化都在里面,其中就有:MERGE_GEOMETRY,当我们使用如下语句时,正常的就会将结点的Geometry进行合并:
osgUtil::Optimizer optimizer;
optimizer.optimize(loadedModel.get());
看其内部使用了一个工具类的方法叫做:
MergeGeometryVisitor mgv(this);
mgv.setTargetMaximumNumberOfVertices(10000);
node->accept(mgv);
它的思想也很简单,先把Group给一级一级合,然后多个Geometry合成一样。能合并的PrimitiveSet也者合并。
osgUtil::Optimizer只能进行一些通用的合并。我们理解了思想之后就可以手动的或者自己针对自己的场景写个性化的工具,以免不想合的被合了,想合的合不了。