第22.8节 性能篇-多实例渲染点云

天下武功,唯快不破

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

鸣谢

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

本节资源

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

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

本节目标

本节要渲染64233个点,存在data.txt中。一行存一个,一行五个数,分别是位置XYZ,以及密度、亮度。

采用技法

1、 本来我们直接在一个Geometry中渲染64233个点就可以了,位置有,亮度有。但是这样会很慢。

2、 我们采用多实例的渲染方法,把64233个点使用纹理存起来,只需要存位置XYZ和亮度就可以了,正好使用RGBA四个通道。一个512×512可以代表262144个顶点,真是足够了。我们来推一推,一张2048×2048的纹理可以存4194304个顶点。

具体实现

1、 使用多实例非常简单,只需要在addPrimitiveSet的时候,把DrawArrays的最后一个参数设置成实例的个数,如下:

    osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;
    geom->setUseDisplayList(false);
    geom->setUseVertexBufferObjects(true);
    geom->setVertexArray(new osg::Vec3Array(1));
    geom->addPrimitiveSet(new osg::DrawArrays(GL_POINTS, 0, 1, numInstances));

上面是建立了一个多实例的geometry,假如最后一句不要numInstances或将其置成默认值0,则就是一个普通的绘制一个点的geometry,加上numInstances就会绘制numInstances个点。这里numInstances是512×512。那么每个顶点的位置颜色该怎么确定呢? 我们要先把其存在纹理当中。

2. 申请一个纹理,把data.txt中的数据存里面:

    std::ifstream is(file.c_str());
    if (!is) return NULL;

    osg::ref_ptr<osg::Image> image = new osg::Image;
    image->allocateImage(w, h, 1, GL_RGBA, GL_FLOAT);

    unsigned int density, brightness;
    osg::BoundingBox boundBox;
    float* data = (float*)image->data();
    while (!is.eof())
    {
        osg::Vec3 pos;
        is >> pos[0] >> pos[1] >> pos[2] >> density >> brightness;
        boundBox.expandBy(pos);

        *(data++) = pos[0];
        *(data++) = pos[1];
        *(data++) = pos[2];
        *(data++) = brightness / 255.0;
    }

3、 最后一步,在shader中,每个顶点按照gl_InstanceID内置变量进行区分,也就是根据gl_InstanceID来取纹理中的像素,转成位置和亮度,赋给点的位置即可:

    "uniform sampler2D defaultTex;\\n"
    "uniform int width;\\n"
    "uniform int height;\\n"
    "varying float brightness;\\n"
    "void main()\\n"
    "{\\n"
    "    float r = float(gl_InstanceID) / float(width);\\n"
    "    vec2 uv = vec2(fract(r), floor(r) / float(height));\\n"
    "    vec4 texValue = texture2D(defaultTex, uv);\\n"
    "    vec4 pos = gl_Vertex + vec4(texValue.xyz, 1.0);\\n"
    "    brightness = texValue.a;\\n"
    "    gl_Position = gl_ModelViewProjectionMatrix * pos;\\n"
    "}\\n"

上面的width和height都是512, 512, gl_InstanceID是从0到512×512,使用
" float r = float(gl_InstanceID) / float(width);\\n"
" vec2 uv = vec2(fract( r ), floor( r ) / float(height));\\n"
就得到了当前点的位置在纹理中的uv,取出来给gl_Position用来计算即可。

这种渲染点云的方法是比较快的,假如点云数量再大,比如上万亿级别的点云,则要再想办法。这种处理个百万级别的,问题是不大的。

以下是所有代码


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

#include <osg/Point>
#include <osg/Group>
#include <osgDB/ReadFile>
#include <osgViewer/ViewerEventHandlers>
#include <osgViewer/Viewer>
#include <fstream>
#include <iostream>


const char* vertCode = {
    "uniform sampler2D defaultTex;\\n"
    "uniform int width;\\n"
    "uniform int height;\\n"
    "varying float brightness;\\n"
    "void main()\\n"
    "{\\n"
    "    float r = float(gl_InstanceID) / float(width);\\n"
    "    vec2 uv = vec2(fract(r), floor(r) / float(height));\\n"
    "    vec4 texValue = texture2D(defaultTex, uv);\\n"
    "    vec4 pos = gl_Vertex + vec4(texValue.xyz, 1.0);\\n"
    "    brightness = texValue.a;\\n"
    "    gl_Position = gl_ModelViewProjectionMatrix * pos;\\n"
    "}\\n"
};

const char* fragCode = {
    "varying float brightness;\\n"
    "void main()\\n"
    "{\\n"
    "    gl_FragColor = vec4(brightness, brightness, brightness, 1.0);\\n"
    "}\\n"
};

osg::Geometry* createInstancedGeometry(osg::Image* img, unsigned int numInstances)
{
    osg::ref_ptr<osg::Geometry> geom = new osg::Geometry;
    geom->setUseDisplayList(false);
    geom->setUseVertexBufferObjects(true);
    geom->setVertexArray(new osg::Vec3Array(1));
    geom->addPrimitiveSet(new osg::DrawArrays(GL_POINTS, 0, 1, numInstances));

    osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
    texture->setImage(img);
    texture->setInternalFormat(GL_RGBA32F_ARB);
    texture->setFilter(osg::Texture2D::MIN_FILTER, osg::Texture2D::LINEAR);
    texture->setFilter(osg::Texture2D::MAG_FILTER, osg::Texture2D::LINEAR);
    geom->getOrCreateStateSet()->setTextureAttributeAndModes(0, texture.get());
    geom->getOrCreateStateSet()->addUniform(new osg::Uniform("defaultTex", 0));
    geom->getOrCreateStateSet()->addUniform(new osg::Uniform("width", (int)img->s()));
    geom->getOrCreateStateSet()->addUniform(new osg::Uniform("height", (int)img->t()));

    osg::ref_ptr<osg::Program> program = new osg::Program;
    program->addShader(new osg::Shader(osg::Shader::VERTEX, vertCode));
    program->addShader(new osg::Shader(osg::Shader::FRAGMENT, fragCode));
    geom->getOrCreateStateSet()->setAttributeAndModes(program.get());
    return geom.release();
}

osg::Geometry* readPointData(const std::string& file, unsigned int w, unsigned int h)
{
    std::ifstream is(file.c_str());
    if (!is) return NULL;

    osg::ref_ptr<osg::Image> image = new osg::Image;
    image->allocateImage(w, h, 1, GL_RGBA, GL_FLOAT);

    unsigned int density, brightness;
    osg::BoundingBox boundBox;
    float* data = (float*)image->data();
    while (!is.eof())
    {
        osg::Vec3 pos;
        is >> pos[0] >> pos[1] >> pos[2] >> density >> brightness;
        boundBox.expandBy(pos);

        *(data++) = pos[0];
        *(data++) = pos[1];
        *(data++) = pos[2];
        *(data++) = brightness / 255.0;
    }

    osg::ref_ptr<osg::Geometry> geom = createInstancedGeometry(image.get(), w * h);
    geom->setInitialBound(boundBox);
    geom->getOrCreateStateSet()->setAttributeAndModes(new osg::Point(5.0f));
    return geom.release();
}

int main(int argc, char** argv)
{
    osg::ref_ptr<osg::Geode> geode = new osg::Geode;
    geode->addDrawable(readPointData("data.txt", 512, 512));

    osg::ref_ptr<osg::Group> root = new osg::Group;
    root->addChild(geode.get());

    osgViewer::Viewer viewer;
    viewer.setSceneData(root.get());
    viewer.addEventHandler(new osgViewer::StatsHandler);
    return viewer.run();
}

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

昵称

取消
昵称表情代码图片