osgEarth示例分析——osgearth_shadercomp

前言

osgearth_shadercomp示例,这是osgEarth着色器合成框架的一组单元测试。主要分析OpenGL着色器语言和方法的使用。是OpenGL与osgearth的结合。通过OpenGL的着色器设置地球的各种颜色状态。

执行命令

// 每一条命令,测试出一种结果
osgearth_shadercompd.exe --test1

osgearth_shadercompd.exe --test2

osgearth_shadercompd.exe --test3

osgearth_shadercompd.exe --test4

osgearth_shadercompd.exe --test5

osgearth_shadercompd.exe --test6

osgearth_shadercompd.exe --test7

osgearth_shadercompd.exe --test8

osgearth_shadercompd.exe --test9

执行效果

test1:添加雾特效

test2:回调方法测试,当窗口宽度大于1000时,地球是红色,否则原色。

 

 test3:LOD参数,各种调节,好像并没有起作用。

 test4:频繁的安装卸载着色器程序,球上的红色一闪一闪的。

test5:两个三角形

test6:左下角4个相机

 

test7:注入,也就是将绘制的内容写入 out.osg 文件中。

test8:读取 out.osg 文件,并展示。

 test9: 红色方块,像瓦片图一样,一级一级的添加。

 代码分析

#include <osg/Notify>
#include <osg/CullFace>
#include <osgDB/ReadFile>
#include <osgDB/WriteFile>
#include <osgGA/StateSetManipulator>
#include <osgViewer/Viewer>
#include <osgViewer/ViewerEventHandlers>
#include <osgEarthUtil/EarthManipulator>
#include <osgEarth/VirtualProgram>
#include <osgEarth/Registry>
#include <osgEarth/Capabilities>
#include <osgEarth/ShaderUtils>
#include <osgEarth/FileUtils>
#include <osgEarth/GLUtils>
#include <osgEarthUtil/Controls>

using namespace osgEarth;
using namespace osgEarth::Util::Controls;


// 根据输入参数不同,执行不同的测试程序,共9个测试程序
int usage( const std::string& msg )
{    
    OE_NOTICE
        << msg << "\n\n"
        << "USAGE: osgearth_shadercomp \n"
        << "           [--test1]    : Run the function injection test \n"
        << "           [--test2]    : Run the accept callback test \n"
        << "           [--test3]    : Run the shader LOD test \n"
        << "           [--test4]    : Run the memory test \n"
        << "           [--test5]    : Run the Program state set test \n"
        << "           [--test6]    : Run the 2-camera test \n"
        << "           [--test7]    : Run the geometry shader injection test \n"
        << "           [--test8]    : Run the VP serialization test \n"
        << "           [--test9]    : Run the 64-bit shader test \n"
        << std::endl;

    return -1;
}

//Utility: 绘制一个三角形节点,作为一个最小单元
osg::Geode* makeGeom( float v )
{
    osg::Geode* geode = new osg::Geode();
    osg::Geometry* geom = new osg::Geometry();
    osg::Vec3Array* verts = new osg::Vec3Array();
    verts->push_back( osg::Vec3(v-1, 0, 0) );
    verts->push_back( osg::Vec3(v+1, 0, 0) );
    verts->push_back( osg::Vec3(  v, 0, 2) );
    geom->setVertexArray( verts );
    geom->setUseDisplayList(false);
    geom->setUseVertexBufferObjects(true);
    osg::Vec4Array* colors = new osg::Vec4Array(osg::Array::BIND_OVERALL);
    colors->push_back( osg::Vec4(0,0,1,1) );
    geom->setColorArray(colors);
    geom->addPrimitiveSet(new osg::DrawArrays(GL_TRIANGLES,0,3));
    geode->addDrawable(geom);
    return geode;
}

//-------------------------------------------------------------------------

// Simple function injection test -- turns the earth gray with a haze.
// 简单的功能注入测试——使大地灰蒙蒙的。
namespace TEST_1
{
	// 顶点着色器
    char s_hazeVertShader[] =
        "#version " GLSL_VERSION_STR "\n"
        "out vec3 v_pos; \n"
        "void setup_haze(inout vec4 VertexVIEW) \n"
        "{ \n"
        "    v_pos = vec3(VertexVIEW); \n"
        "} \n";

	// 片段着色器
    char s_hazeFragShader[] =
        "#version " GLSL_VERSION_STR "\n"
        "in vec3 v_pos; \n"
        "void apply_haze(inout vec4 color) \n"
        "{ \n"
        "    float dist = clamp( length(v_pos)/1e7, 0.0, 0.75 ); \n"
        "    color = mix(color, vec4(0.5, 0.5, 0.5, 1.0), dist); \n"
        "} \n";

    osg::StateAttribute* createHaze()
    {
		// 着色器程序
        osgEarth::VirtualProgram* vp = new osgEarth::VirtualProgram();
        vp->setFunction( "setup_haze", s_hazeVertShader, osgEarth::ShaderComp::LOCATION_VERTEX_VIEW );
        vp->setFunction( "apply_haze", s_hazeFragShader, osgEarth::ShaderComp::LOCATION_FRAGMENT_LIGHTING );
        vp->setShaderLogging(true, "shaders.txt");
        return vp;
    }

    osg::Group* run( osg::Node* earth )
    {   
        osg::Group* g = new osg::Group();
        g->addChild( earth );
        g->getOrCreateStateSet()->setAttributeAndModes( createHaze(), osg::StateAttribute::ON );
        return g;
    }
}

//-------------------------------------------------------------------------

// Tests the VirtualProgram's ShaderComp::AcceptCallback
// 测试着色器程序,接收回调方法
namespace TEST_2
{
    const char* fragShader =
        "#version 110\n"
        "void make_it_red(inout vec4 color) {\n"
        "    color.r = 1.0;\n"
        "}\n";

    struct Acceptor : public ShaderComp::AcceptCallback
    {
        // Return true to activate the shader function.
        bool operator()(const osg::State& state)
        {
            osg::Camera* camera = *state.getGraphicsContext()->getCameras().begin();
            if (!camera) return false;
            osg::Viewport* viewport = camera->getViewport();
            if (!viewport) return false;
            return viewport->width() > 1000;
        }
    };

    osg::Group* run(osg::Node* node)
    {
        VirtualProgram* vp = VirtualProgram::getOrCreate(node->getOrCreateStateSet());
        vp->setFunction("make_it_red", fragShader, ShaderComp::LOCATION_FRAGMENT_LIGHTING, new Acceptor());
        
        osg::Group* g = new osg::Group();
        g->addChild( node );
        return g;
    }
}

//-------------------------------------------------------------------------

// Tests the VirtualProgram's min/max range for functions (shader LOD)
// 测试着色器程序的LOD方法
namespace TEST_3
{
    const char* fragShader =
        "#version 110\n"
        "void make_it_red(inout vec4 color) {\n"
        "    color.r = 1.0;\n"
        "}\n";

    osg::Group* run(osg::Node* node)
    {
		// 获取椭球的赤道半径
        float radius = osgEarth::SpatialReference::get("wgs84")->getEllipsoid()->getRadiusEquator();

		// 着色器程序
        VirtualProgram* vp = VirtualProgram::getOrCreate(node->getOrCreateStateSet());

        // Install the shader function:
        vp->setFunction("make_it_red", fragShader, ShaderComp::LOCATION_FRAGMENT_LIGHTING);

        // Set a maximum LOD range for the above function:设置LOD范围
        vp->setFunctionMinRange( "make_it_red", 5000000 );// 50万米
        vp->setFunctionMaxRange( "make_it_red", 10000000 );// 100万米

        osg::Group* g = new osg::Group();

        // Install a callback that will convey the LOD range to the shader LOD.
        g->addCullCallback( new RangeUniformCullCallback() );

        g->addChild( node );
        return g;
    }
}
//-------------------------------------------------------------------

// Tests memory management by installing and uninstalling shader
// functions every frame.
// 测试缓存功能。通过每隔一帧进行安装/卸载
namespace TEST_4
{
    const char* fragShader =
        "#version 110\n"
        "void make_it_red(inout vec4 color) {\n"
        "    color.r *= 1.5;\n"
        "}\n";

    struct Acceptor : public ShaderComp::AcceptCallback
    {
        osg::ref_ptr<osg::Array> _a;

        Acceptor()
        {
            _a = new osg::Vec4Array(1024000);
        }

        bool operator()(const osg::State& state)
        {
            return true;
        }
    };

    struct MyGroup : public osg::Group
    {
        bool _toggle;

        MyGroup() : _toggle(false)
        {
            this->setNumChildrenRequiringUpdateTraversal(1);
        }

        void traverse(osg::NodeVisitor& nv)
        {
            if (nv.getVisitorType() == nv.UPDATE_VISITOR)
            {
                if ( (nv.getFrameStamp()->getFrameNumber() % 4) == 0 )// 通过修改4,可以调整频率
                {
                    _toggle = !_toggle;

                    VirtualProgram* vp = VirtualProgram::getOrCreate(this->getOrCreateStateSet());
                    if ( _toggle )
                    {
                        vp->setFunction(
                            "make_it_red", fragShader,
                            osgEarth::ShaderComp::LOCATION_FRAGMENT_COLORING,
                            new Acceptor() );
                    }
                    else
                    {
                        vp->removeShader("make_it_red");// 移除
                    }
                }
            }
            osg::Group::traverse(nv);
        }
    };

    osg::Group* run(osg::Node* node)
    {
        MyGroup* g = new MyGroup();
        g->addChild( node );
        return g;
    }
}

//-------------------------------------------------------------------------
// 运行程序状态集测试。
namespace TEST_5
{
    char s_vert[] =
        "#version " GLSL_VERSION_STR "\n"
        "void main() { \n"
        "    gl_Position = gl_ModelViewProjectionMatrix * gl_Vertex; \n"
        "} \n";
    char s_frag[] =
        "#version " GLSL_VERSION_STR "\n"
        "void main() { \n"
        "    gl_FragColor = vec4(1.0,0.0,0.0,1.0); \n"
        "} \n";
    char s_vp[] =
        "#version " GLSL_VERSION_STR "\n"
        "void test( inout vec4 color ) { color = vec4(1.0,0.0,0.0,1.0); } \n";

    osg::Group* run()
    {
        osg::Node* n1 = makeGeom( -5 );
        osg::Node* n2 = makeGeom(  5 );

        VirtualProgram* vp = new VirtualProgram();
        vp->setFunction("test", s_vp, ShaderComp::LOCATION_FRAGMENT_LIGHTING);
        n1->getOrCreateStateSet()->setAttributeAndModes( vp, 1 );

        osg::Group* root = new osg::Group();
        root->getOrCreateStateSet()->setRenderBinDetails( 0, "TraversalOrderBin" );
        GLUtils::setLighting(root->getOrCreateStateSet(), 0);

        root->addChild( n1 );// 仅对n1节点做
        root->addChild( n2 );

        return root;
    }
}

//-------------------------------------------------------------------------

// Tests the VirtualProgram's ShaderComp::AcceptCallback with multiple cameras
// in order to vertify that the State Stack Memory is being properly disabled
// when Accept Callbacks are in play.
// 测试着色器程序,接收多相机回调,
// 来证明 当Accept Callbacks正在播放时,状态堆栈内存是否被正确禁用。
namespace TEST_6
{
    const char* fragShader =
        "#version 110\n"
        "void make_it_red(inout vec4 color) {\n"
        "    color.r = 1.0;\n"
        "}\n";
    const char* fragShader2 =
        "#version 110\n"
        "void make_it_blue(inout vec4 color) {\n"
        "    color.b = 1.0;\n"
        "}\n";

    // This acceptor will only include the fragment shader snippet above
    // when the camera's viewport.x == 0. Normally the Program will only
    // be applied once in succession. 
    struct Acceptor : public ShaderComp::AcceptCallback
    {
        Acceptor() : _fn(0) { }

        // Return true to activate the shader function.
        bool operator()(const osg::State& state)
        {
            const osg::Viewport* vp = state.getCurrentViewport();
            return vp && vp->x() == 0.0;
        }

        unsigned _fn;
    };

	// 两个着色器,每个着色器渲染的场景,各设置两个相机,所以共4个相机
    osg::Group* run(osg::Node* node)
    {
        osg::Group* group1 = new osg::Group();
        VirtualProgram* vp1 = VirtualProgram::getOrCreate(group1->getOrCreateStateSet());
        vp1->setFunction("make_it_red", fragShader, ShaderComp::LOCATION_FRAGMENT_LIGHTING, new Acceptor());
        vp1->setAcceptCallbacksVaryPerFrame(true);
        group1->addChild( node );

		// osg相机的坐标原点在 viewer窗口的左下角
		// 将所有相机的宽都比高大20,且cam1与2在底层,cam3和4在上层
		osg::Camera* cam1 = new osg::Camera();
        cam1->setViewport(0, 0, 220, 200);
        cam1->addChild( group1 );

        osg::Camera* cam2 = new osg::Camera();
        cam2->setViewport(221, 0, 220, 200);
        cam2->addChild( group1 );        

        osg::Group* group2 = new osg::Group();
        VirtualProgram* vp2 =  VirtualProgram::getOrCreate(group2->getOrCreateStateSet());
        vp2->setFunction("make_it_blue", fragShader2, ShaderComp::LOCATION_FRAGMENT_LIGHTING);
        group2->addChild( node );

        osg::Camera* cam3 = new osg::Camera();
        cam3->setViewport(0, 221, 220, 200);
        cam3->addChild( group2 );

        osg::Camera* cam4 = new osg::Camera();
        cam4->setViewport(221, 221, 220, 200);
        cam4->addChild( group2 );
        
        osg::Group* g = new osg::Group();
        g->addChild( cam1 );
        g->addChild( cam2 );
        g->addChild( cam3 );
        g->addChild( cam4 );

        return g;
    }
}

//-------------------------------------------------------------------------
// 运行几何着色器注入测试。
namespace TEST_7
{
    const char* vert =
        "#version 120\n"
        "out float oe_red; \n"
        "void myVertShader(inout vec4 vertex) { \n"
        "    oe_red = 1.0; \n"
        "} \n";

    const char* geom =
        "#version 330\n"
        "#pragma vp_name ShaderComp Test 7 Geom Shader (Triangle Viewer)\n"

        "layout(triangles) in; \n"
        "layout(triangle_strip) out; \n"
        "layout(max_vertices = 3) out; \n"
        
        "void VP_LoadVertex(in int); \n"
        "void VP_EmitVertex(); \n"

        "uniform float osg_FrameTime; \n"

        "void myGeomShader() \n"
        "{ \n"
        "    float strength = 0.25 + sin(osg_FrameTime*2.0)*0.25; \n"
        "    vec4 cen = (gl_in[0].gl_Position + gl_in[1].gl_Position + gl_in[2].gl_Position)/3.0; \n"        
        "    for(int i=0; i < 3; ++i ) \n"
        "    { \n"
        "        VP_LoadVertex(i); \n"
        "        vec4 pos = gl_in[i].gl_Position; \n"
        "        pos += vec4(normalize(cen.xyz-pos.xyz) * distance(cen, pos) * strength, 0.0); \n"
        "        gl_Position = pos; \n"
        "        VP_EmitVertex(); \n"
        "    } \n"
        "    EndPrimitive(); \n"
        "} \n";

    const char* frag =
        "#version 120\n"
        "in float oe_red; \n"
        "void myFragShader(inout vec4 color) \n"
        "{ \n"
        "    // nop\n"
        "} \n";

	// 创建着色器程序
    osg::StateAttribute* createVP()
    {
        osgEarth::VirtualProgram* vp = new osgEarth::VirtualProgram();
        vp->setFunction( "myVertShader", vert, osgEarth::ShaderComp::LOCATION_VERTEX_MODEL );
        vp->setFunction( "myGeomShader", geom, osgEarth::ShaderComp::LOCATION_GEOMETRY );
        vp->setFunction( "myFragShader", frag, osgEarth::ShaderComp::LOCATION_FRAGMENT_COLORING );
        vp->setShaderLogging(true, "test7.glsl");// 输出此文件
        return vp;
    }

    osg::Group* run( osg::Node* earth )
    {   
        osg::Group* g = new osg::Group();
        g->addChild( earth );
        g->getOrCreateStateSet()->setAttribute( createVP() );
        g->getOrCreateStateSet()->setAttributeAndModes(new osg::CullFace(osg::CullFace::BACK));
        return g;
    }
}

//-------------------------------------------------------------------------

// Serialization test.序列化测试
namespace TEST_8
{
    osg::Node* run()
    {
		// 将第五个测试生成的对象,保存,然后再读出展示
        osg::ref_ptr<osg::Node> node = TEST_5::run();
        osgDB::writeNodeFile(*node, "out.osgt");
        OE_NOTICE << "Wrote to out.osgt" << std::endl;

        node = 0L;
        node = osgDB::readRefNodeFile("out.osgt");
        if (!node.valid()) {
            OE_WARN << "Readback failed!!" << std::endl;
            exit(0);
        }

        return node.release();
    }
}

//-------------------------------------------------------------------------

// Double-precision GLSL Test.
//
// This test will turn terrain verts RED if their distance from the earth's
// center exceeds a certain number. Zoom down to a region where there is a
// boundary between red verts and normally-colored verts. If you use the 32-bit
// version of the vertex shader, small movements in the camera will cause 
// red triangles to jump in and out as the world vertex position overflows
// the 32-bit values. If you use the 64-bit version, this no longer happens.
//
// For the 64-bit version, the OSG built-in uniform osg_ViewMatrixInverse is
// insufficient (since it's only single-precision). So we have to install a
// 64-bit version using a cull callback.
// 运行64位着色器测试
namespace TEST_9
{
    osg::Node* run(osg::Node* earthfile)
    {
        // 32-bit vertex shader, for reference only. This shader will exceed
        // the single-precision capacity and cause "jumping verts" at the 
        // camera make small movements.
        const char* vs32 =
            "#version 330 \n"
            "uniform mat4 osg_ViewMatrixInverse; \n"
            "flat out float isRed; \n"

            "void vertex(inout vec4 v32) \n"
            "{ \n"
            "    vec4 world = osg_ViewMatrixInverse * v32; \n"
            "    world /= world.w; \n"
            "    float len = length(world); \n"

            "    const float R = 6371234.5678; \n"
            
            "    isRed = 0.0; \n"
            "    if (len > R) \n"
            "        isRed = 1.0;"

            "}\n";

        // 64-bit vertex shader. This shader uses a double-precision inverse
        // view matrix and calculates the altitude all in double precision;
        // therefore the "jumping verts" problem in the 32-bit version is 
        // resolved. (Mostly-- you will still see the jumping if you view the 
        // earth from orbit, because the 32-bit vertex itself is very far from
        // the camera in view coordinates. If that is an issue, you need to pass
        // in 64-bit vertex attributes.)
        const char* vs64 = 
            "#version 330 \n"
            "#extension GL_ARB_gpu_shader_fp64 : enable \n"
            "uniform dmat4 u_ViewMatrixInverse64; \n"            // must use a 64-bit VMI.
            "flat out float isRed; \n"
            "flat out double vary64; \n"                         // just to test shadercomp framework

            "void vertex(inout vec4 v32) \n"
            "{ \n"
            "    dvec4 v64 = dvec4(v32); \n"                     // upcast to 64-bit, no precision loss
                                                                 // unless camera is very far away

            "    dvec4 world = u_ViewMatrixInverse64 * v64; \n"  // xform into world coords
            "    world /= world.w; \n"                           // divide by w
            "    double len = length(world.xyz); \n"             // get double-precision vector length.

            "    const double R = 6371234.5678; \n"              // arbitrary earth radius threshold
            
            "    isRed = (len > R) ? 1.0 : 0.0; \n"
            "}\n";

        // frag shader: color the terrain red if the incoming varying is non-zero.
        const char* fs =
            "#version 330 \n"
            "#extension GL_ARB_gpu_shader_fp64 : enable \n"
            "flat in float isRed; \n"
            "flat in double vary64; \n"
            "void fragment(inout vec4 color) \n"
            "{ \n"
            "    if (isRed > 0.0f) { \n"
            "        color.r = 1.0; \n"
            "        color.gb *= 0.5; \n"
            "    } \n"
            "} \n";

        // installs a double-precision inverse view matrix for our shader to use.
        struct VMI64Callback : public osg::NodeCallback
        {
            void operator()(osg::Node* node, osg::NodeVisitor* nv)
            {
                osgUtil::CullVisitor* cv = dynamic_cast<osgUtil::CullVisitor*>(nv);

                osg::Uniform* u = new osg::Uniform(osg::Uniform::DOUBLE_MAT4, "u_ViewMatrixInverse64");
                u->set(cv->getCurrentCamera()->getInverseViewMatrix());
                
                osg::ref_ptr<osg::StateSet> ss = new osg::StateSet();
                ss->addUniform(u);
                cv->pushStateSet(ss.get());

                traverse(node, nv);

                cv->popStateSet();
            }
        };
        earthfile->setCullCallback(new VMI64Callback());

        osg::StateSet* ss = earthfile->getOrCreateStateSet();
        VirtualProgram* vp = VirtualProgram::getOrCreate(ss);
        vp->setFunction("vertex",   vs64, ShaderComp::LOCATION_VERTEX_VIEW);
        vp->setFunction("fragment", fs,   ShaderComp::LOCATION_FRAGMENT_COLORING);


        return earthfile;
    }
}

//-------------------------------------------------------------------------

int main(int argc, char** argv)
{
    osg::ArgumentParser arguments(&argc,argv);
    osgViewer::Viewer viewer(arguments);

    bool test1 = arguments.read("--test1");
    bool test2 = arguments.read("--test2");
    bool test3 = arguments.read("--test3");
    bool test4 = arguments.read("--test4");
    bool test5 = arguments.read("--test5");
    bool test6 = arguments.read("--test6");
    bool test7 = arguments.read("--test7");
    bool test8 = arguments.read("--test8");
    bool test9 = arguments.read("--test9");
    bool ok    = test1 || test2 || test3 || test4 || test5 || test6 || test7 || test8||test9;

    bool ui = !arguments.read("--noui");// 此参数不需要输入

    if ( !ok )
    {
        return usage("");
    }

    osg::Group* root = new osg::Group();
    viewer.setSceneData( root );
    
    LabelControl* label = new LabelControl();
    if ( ui )
    {
        // add a canvas: 添加画布
        ControlCanvas* canvas = new ControlCanvas();
        root->addChild( canvas );

        // and a label: 添加标签
        canvas->addControl(label);
    }

	// 1 2 3 4 6 需要earth文件
    if ( test1 || test2 || test3 || test4 || test6 )
    {
        osg::ref_ptr<osg::Node> earthNode = osgDB::readRefNodeFile( "F:\\osgData\\Data\\earth_image\\china-simple.earth" );
        if (!earthNode.valid())
        {
            return usage( "Unable to load earth model." );
        }

        if ( test1 )
        {
            root->addChild( TEST_1::run(earthNode.get()) );
            if (ui) label->setText( "Function injection test: the map appears hazy at high altitude." );
        }
        else if ( test2 )
        {
            root->addChild( TEST_2::run(earthNode.get()) );
            if (ui) label->setText( "Accept callback test: the map turns red when viewport width > 1000" );
        }
        else if ( test3 )
        {
            root->addChild( TEST_3::run(earthNode.get()) );
            if (ui) label->setText( "Shader LOD test: the map turns red between 500K and 1M meters altitude" );
        }
        else if ( test4 )
        {
            root->addChild( TEST_4::run(earthNode.get()) );
            if (ui) label->setText("Memory management test; monitor memory for stability");
        }
        else if ( test6 )
        {
            root->addChild( TEST_6::run(earthNode.get()) );
            if (ui) label->setText("State Memory Stack test; top row, both=blue. bottom left=red, bottom right=normal.");
        }
        
        viewer.setCameraManipulator( new osgEarth::Util::EarthManipulator() );
    }
    else if ( test5 )
    {
        osgEarth::Registry::instance()->getCapabilities();
        root->addChild( TEST_5::run() );
        if (ui) label->setText( "Leakage test: red tri on the left, blue on the right." );
    }
    else if ( test7 )
    {
        root->addChild( TEST_7::run( osgDB::readNodeFiles(arguments) ) );
        if (ui) label->setText("Geometry Shader Injection Test.");
    }
    else if (test8)
    {
        root->addChild( TEST_8::run() );
        if (ui) label->setText("Serialization test");
    }
    else if (test9)
    {
        osg::ref_ptr<osg::Node> earthNode = osgDB::readRefNodeFile( "F:\\osgData\\Data\\earth_image\\china-simple.earth" );
        if (!earthNode.valid())
        {
            return usage( "Unable to load earth model." );
        }
        
        root->addChild(TEST_9::run(earthNode.get()));
        if (ui) label->setText("DP Shader Test - see code comments");
        viewer.setCameraManipulator( new osgEarth::Util::EarthManipulator() );
    }


    // add some stock OSG handlers:
    viewer.addEventHandler(new osgViewer::StatsHandler());
    viewer.addEventHandler(new osgViewer::WindowSizeHandler());
    viewer.addEventHandler(new osgViewer::ThreadingHandler());
    viewer.addEventHandler(new osgGA::StateSetManipulator(viewer.getCamera()->getOrCreateStateSet()));

    return viewer.run();
}

 

 

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值