第22.5节 性能篇-设计一个简单的拣选策略

天下武功,唯快不破

最近网友问了关于点云、倾斜摄影数据的性能优化问题。本来想刀枪剑戟、斧钺勾叉给弄了,但是后来想性能其实是个系统问题,要在第22节分成数小节扎扎实实的讲一讲。

鸣谢

非常感谢王锐王大神的cookbook,我准备主要参考它里面关于性能的一章。也就是第8章。

本节资源

本文集包括本节所有资源包括模型代码都在此下载,按节的序号有文件或文件夹:

注意: 务必使用浏览器打开:
【击此打开网盘资源链接】

策略描述

本节为提高效率提出了一个新的策略,那就是自己写一个显而易见的拣选算法。本节是这么打算的,首先我们要建一个迷宫,如下图:
在这里插入图片描述

这个迷营是依照maze.txt来建的在这里插入图片描述
建的策略是maze.txt是0的地方,我们就在地板上放一个面,是1的地方我们就放一个盒子。盒子就像墙一样就档住了东西。看起来是这样的:
在这里插入图片描述

那么我们就可以开始走迷营,操作是这样的,鼠标滚轮向前就是向前走,向后就是向后走,按住鼠标划动可以拐弯。

现在在迷营范围内随机的放20000辆车那么这20000辆需要不需要在视品中显示按说我们不用管,让osg默认来拣选就行了。但是因为我们知道车的位置假如在墙里面,那么就不需要显示,因此我们可以手动写一个cullCallback,在这个callback中判断卡车的位置是不是在墙里面,如果是在墙里面,则不需要显示它。或卡车的位置与视点的位置连线的中点在墙里面,也不需要显示它,因为虽然在地板上但是视点会被墙档住了。我们认为因为我们了解迷宫这回事,它是特殊的,所以我们可以针对这个特性自己写一个上面的拣选算法,更加高效。

这就是本节:设计一个简单的拣选策略来提升效率的原因,本节优化前后对比如下:
在这里插入图片描述
可以看到拣选在优化前是187ms,优化后变成了127ms(可能判断中点这个算法有点问题,还误裁了一些车,所以Draw与GPU时间也变短了,可以把中点的判断取消息掉大伙再试一试,大概就是这么个思想)

具体实现

1、创建地板函数,创建了一个角在(-0.5, -0.5, 0)的,边长是1的地板,创建完后其范围就是[-0.5, 0.5]

osg::Geode* getOrCreatePlane()
{
    static osg::ref_ptr<osg::Geode> s_quad;
    if ( !s_quad )
    {
        osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
        texture->setImage( osgDB::readImageFile("Images/skin.tga") );
        
        osg::ref_ptr<osg::Drawable> drawable = osg::createTexturedQuadGeometry(
            osg::Vec3(-0.5f,-0.5f, 0.0f), osg::X_AXIS, osg::Y_AXIS );
        drawable->getOrCreateStateSet()->setTextureAttributeAndModes( 0, texture.get() );
        
        s_quad = new osg::Geode;
        s_quad->addDrawable( drawable.get() );
    }
    return s_quad.get();
}

2、创建盒子函数,创建完,盒子的位置在(0.0f, 0.0f, 0.5f),长度是1,坐标范围也是从[-0.5, 0.5],这样与地板以及盒子自己就是相连的。

osg::Geode* getOrCreateBox()
{
    static osg::ref_ptr<osg::Geode> s_box;
    if ( !s_box )
    {
        osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
        texture->setImage( osgDB::readImageFile("Images/Brick-Std-Orange.TGA") );
        
        osg::ref_ptr<osg::Drawable> drawable = new osg::ShapeDrawable(
            new osg::Box(osg::Vec3(0.0f, 0.0f, 0.5f), 1.0f) );
        drawable->getOrCreateStateSet()->setTextureAttributeAndModes( 0, texture.get() );
        
        s_box = new osg::Geode;
        s_box->addDrawable( drawable.get() );
    }
    return s_box.get();
}

3、创建迷宫,从maze.txt中读取01的值,其值存放在全局变量g_mazeMap中,其存放的是[行, 列, 值],这个全局变量很关键,给定一个世界坐标pos,可以在其中查到其是地板还是盒子。0是地板,1是盒子。

osg::Node* createMaze( const std::string& file )
{
    std::ifstream is( file.c_str() );
    if ( is )
    {
        std::string line;
        int col = 0, row = 0;
        while ( std::getline(is, line) )
        {
            std::stringstream ss(line);
            while ( !ss.eof() )
            {
                int value = 0; ss >> value;
                g_mazeMap[CellIndex(col, row)] = value;
                col++;
            }
            col = 0;
            row++;
        }
    }
    
    osg::ref_ptr<osg::Group> mazeRoot = new osg::Group;
    for ( CellMap::iterator itr=g_mazeMap.begin(); itr!=g_mazeMap.end(); ++itr )
    {
        const CellIndex& index = itr->first;
        osg::ref_ptr<osg::MatrixTransform> trans = new osg::MatrixTransform;
        trans->setMatrix( osg::Matrix::translate(index.first, index.second, 0.0f) );
        mazeRoot->addChild( trans.get() );
        
        int value = itr->second;
        if ( !value )  // Ground
            trans->addChild( getOrCreatePlane() );
        else  // Wall
            trans->addChild( getOrCreateBox() );
    }
    return mazeRoot.release();
}

4、操作器中的碰撞检测,本节使用了一个比较难用的操作器FirstPersonManipulator,它是使用鼠标滚轮的滚动来操作前进后退的。我们在其handle中来判断是不是撞到迷宫的墙壁上了,判断的方法也很简单,先把这一步走之前的位置存下来osg::Matrix lastMatrix = getMatrix();,然后再走:bool ok = osgGA::FirstPersonManipulator::handle(ea, aa);再把走之后的位置取出来
osg::Matrix matrix = getMatrix();
osg::Vec3 pos = matrix.getTrans();
如果走之后的位置在g_mazeMap中一查是个1则代表是迷营,就还把位置赋成走之前的lastMatrix 。代码如下:


    virtual bool handle( const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa )
    {
        osg::Matrix lastMatrix = getMatrix();
        bool ok = osgGA::FirstPersonManipulator::handle(ea, aa);
        
        if ( ea.getEventType()==osgGA::GUIEventAdapter::FRAME ||
             ea.getEventType()==osgGA::GUIEventAdapter::SCROLL )
        {
            osg::Matrix matrix = getMatrix();
            osg::Vec3 pos = matrix.getTrans();
            if ( pos[2]!=0.5f )  // Fix the player height
            {
                pos[2] = 0.5f;
                matrix.setTrans( pos );
                setByMatrix( matrix );
            }
            
            CellIndex index(int(pos[0] + 0.5f), int(pos[1] + 0.5f));
            CellMap::iterator itr = g_mazeMap.find(index);
            if ( itr==g_mazeMap.end() )  // Outside the maze
                setByMatrix( lastMatrix );
            else if ( itr->second!=0 )  // Don't intersect with walls
                setByMatrix( lastMatrix );
        }
        return ok;
    }

5、最关键的cullCallback来了,它的核心算法是这样的:
先求出眼睛的位置,再求出卡车的位置,只有当卡车的位置在地板上且眼睛与卡车连线的中点的位置也在地板上才说明卡车在地板上且没有被墙档住,这是个小算法,算不上精确。getCellIndex返回true代表在地板上,false表示不在地板上。

void MazeCullCallback::operator()( osg::Node* node, osg::NodeVisitor* nv )
{
    //眼晴的位置,
    osg::Vec3 eye = nv->getEyePoint();
    //卡车的位置
    osg::Vec3 center = node->getBound().center();

    //将其换算成世界坐标,卡车矩阵以上不再有矩阵,不需要计算世界坐标了且
    //矩阵的相乘非常耗时
    //osg::Matrix l2w = osg::computeLocalToWorld( node->getParentalNodePaths()[0] );
    //eye = eye * l2w; center = center * l2w;

    //在世界坐标下计算
    CellIndex indexNode;
    
    if (getCellIndex(indexNode, center)&& getCellIndex(indexNode, (center+ eye)*0.5))
    {
        traverse( node, nv );
    }
    // We don't traverse if the node is not visible in maze
}

注意 王大神写这个例子的年代,OSG的拣选还不咋地,现在已经优化的相当了得,因此我们求眼睛和卡车的位置如果考虑其上还有矩阵,那么就需要使用上面注释掉的这两句:
//将其换算成世界坐标,卡车矩阵以上不再有矩阵,不需要计算世界坐标了且
//矩阵的相乘非常耗时
//osg::Matrix l2w = osg::computeLocalToWorld( node->getParentalNodePaths()[0] );
//eye = eye * l2w; center = center * l2w;

就因为增加了这几句矩阵的运算,导致我们手工写的cull算法性能还不如OSG自带的。因此在知道其上已经没有矩阵的情况下,我把这两句省了,本节的性能提升才得以真的提升。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片