立方图纹理是一种特殊的技术,它是一个由 6 幅二维图像构成的、以原点为中心的纹理立方体。对于每个片段而言,纹理坐标(S,T,R)都被当作一个方向向量来看待,每个纹理单元表示从原点所看到的纹理立方体的东西。立方图纹理应用非常广泛,可以利用它来实现环境贴图、反射和光照等效果。常见的就是三维场景中的天空盒了。
立方图纹理的功能与其他的纹理操作是相互独立的,不要认为它是一种特殊的纹理。因此,立方图纹理可以使用其他标准的纹理特性,如多重纹理、纹理边框等。但需要注意的是,立方图纹理渲染时需要很大的内存,是通常二维纹理的 6 倍左右。在为其指定纹理数据和创建纹理对象时,应该把它作为一个整体来处理,而不是作为单个的面来指定。
立方图纹理技术实现需要的主要类是 osg::TextureCubeMap,它继承自 osg::Texture 类,封装了OpenGL 中的立方图纹理函数的功能。从继承关系中可以看出,osg::TextureCubeMap 同样继承自 osg::StateAttribute。因此,可以使用设置渲染属性的方式来启用立方图纹理,代码如下:
//设置立方图纹理
osg::TextureCubeMap* skymap = osg::TextureCubeMap();
stateset->setTextureAttributeAndModes(0,skymap,osg::StateAttribute::ON| osg::StateAttribute::OVERRIDE);
创建天空盒的方法有很多,可以直接绘制一个,当然效果是非常不好的。实际中,经常使用的天空盒包括两种,即方体天空盒和球体天空盒。更多的是球体天空盒的应用,看起来更真实,更能描述真实的天空效果。
创建球体天空盒的主要步骤如下:
(1)创建立方图纹理对象,读取立方图纹理贴图。
(2)设置自动生成纹理坐标。
(3)设置纹理矩阵。
(4)设置立方图纹理。
(5)设置矩阵变换节点,以实现合适的矩阵变换。
(6)关闭光照并关闭背面剔除,设置渲染顺序,加入到叶节点中绘制。
在上面的步骤中有几点需要注意的地方:
读取立方图纹理贴图时,需要特别注意的是,纹理贴图要与立方体的各个面(+X,-X,+Y,-Y,+Z,-Z)一一对应,否则,生成的天空盒会非常难看。
POSITIVE_X=0, //Left X 正方向
NEGATIVE_X=1, //Right X 负方向
POSITIVE_Y=2, //Front Y 正方向
NEGATIVE_Y=3, //Back Y 负方向
POSITIVE_Z=4, //Up Z 正方向
NEGATIVE_Z=5 //Down Z 负方向
一定要设置关闭背面剔除及光照,并设置正确的渲染顺序。为了实现更真实的效果,通常天空盒只会根据视点做适当的变换,这就需要继承变换类实现一个新的变换类。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include //事件监听
#include //事件响应类,对渲染状态进行控制
#include //简化几何体
#include
#include
#include
#include
#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")
//创建二维纹理属性
osg::ref_ptr<osg::StateSet> createTexture1DState()
{
osg::ref_ptr<osg::Image> image = osgDB::readImageFile("zhutie.png");
//创建二维纹理
osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
//设置边界处理
texture->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::REPEAT);
texture->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::REPEAT);
//设置滤波
texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
//texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::NEAREST);
//设置贴图
texture->setImage(image.get());
//设置自动纹理坐标,并指定相关的平面
osg::ref_ptr<osg::TexGen> texgen = new osg::TexGen;
texgen->setMode(osg::TexGen::OBJECT_LINEAR);
texgen->setPlane(osg::TexGen::S, osg::Plane(0.0f, 0.0f, 1.0f, 0.0f));
//创建属性集
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
//启用二维纹理
stateset->setTextureAttribute(0, texture.get(), osg::StateAttribute::OVERRIDE);
stateset->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
//启用纹理坐标生成器
stateset->setTextureAttribute(0, texgen.get(), osg::StateAttribute::OVERRIDE);
stateset->setTextureMode(0, GL_TEXTURE_GEN_S, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
stateset->setTextureMode(0, GL_TEXTURE_GEN_T, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
return stateset.get();
}
//读取立方图
osg::ref_ptr<osg::TextureCubeMap> readCubeMap()
{
osg::ref_ptr<osg::TextureCubeMap> cubemap = new osg::TextureCubeMap;
osg::ref_ptr<osg::Image> imagePosX = osgDB::readImageFile("skybox\\right.jpg");
osg::ref_ptr<osg::Image> imageNegX = osgDB::readImageFile("skybox\\left.jpg");
osg::ref_ptr<osg::Image> imagePosY = osgDB::readImageFile("skybox\\down.jpg");
osg::ref_ptr<osg::Image> imageNegY = osgDB::readImageFile("skybox\\up.jpg");
osg::ref_ptr<osg::Image> imagePosZ = osgDB::readImageFile("skybox\\front.jpg");
osg::ref_ptr<osg::Image> imageNegZ = osgDB::readImageFile("skybox\\back.jpg");
if (imagePosX.get() && imageNegX.get() && imagePosY.get() && imageNegY.get() && imagePosZ.get() && imageNegZ.get())
{
//设置立方图的六个面的贴图
cubemap->setImage(osg::TextureCubeMap::POSITIVE_X, imagePosX.get());
cubemap->setImage(osg::TextureCubeMap::NEGATIVE_X, imageNegX.get());
cubemap->setImage(osg::TextureCubeMap::POSITIVE_Y, imagePosY.get());
cubemap->setImage(osg::TextureCubeMap::NEGATIVE_Y, imageNegY.get());
cubemap->setImage(osg::TextureCubeMap::POSITIVE_Z, imagePosZ.get());
cubemap->setImage(osg::TextureCubeMap::NEGATIVE_Z, imageNegZ.get());
//设置纹理环绕模式
cubemap->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
cubemap->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
cubemap->setWrap(osg::Texture::WRAP_R, osg::Texture::CLAMP_TO_EDGE);
//设置滤波:线形和mipmap
cubemap->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR);
cubemap->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
}
return cubemap.get();
}
//更新立方体图纹理
struct TexMatCallback : public osg::NodeCallback
{
public:
TexMatCallback(osg::TexMat& tm) :
_texMat(tm)
{
//
}
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
osgUtil::CullVisitor* cv = dynamic_cast<osgUtil::CullVisitor*>(nv);
if (cv)
{
//得到模型视图矩阵并设置旋转角度
const osg::Matrix& MV = *(cv->getModelViewMatrix());
const osg::Matrix R = osg::Matrix::rotate(osg::DegreesToRadians(112.0f), 0.0f, 0.0f, 1.0f) *
osg::Matrix::rotate(osg::DegreesToRadians(90.0f), 1.0f, 0.0f, 0.0f);
osg::Quat q = MV.getRotate();
const osg::Matrix C = osg::Matrix::rotate(q.inverse());
//设置纹理矩阵
_texMat.setMatrix(C * R);
}
traverse(node, nv);
}
//纹理矩阵
osg::TexMat& _texMat;
};
//一个变换类,使天空盒绕视点旋转
class MoveEarthySkyWithEyePointTransform : public osg::Transform
{
public:
//局部矩阵计算成世界矩阵
virtual bool computeLocalToWorldMatrix(osg::Matrix& matrix, osg::NodeVisitor* nv) const
{
osgUtil::CullVisitor* cv = dynamic_cast<osgUtil::CullVisitor*>(nv);
if (cv)
{
osg::Vec3 eyePointLocal = cv->getEyeLocal();
matrix.preMult(osg::Matrix::translate(eyePointLocal));
}
return true;
}
//世界矩阵计算为局部矩阵
virtual bool computeWorldToLocalMatrix(osg::Matrix& matrix, osg::NodeVisitor* nv) const
{
osgUtil::CullVisitor* cv = dynamic_cast<osgUtil::CullVisitor*>(nv);
if (cv)
{
osg::Vec3 eyePointLocal = cv->getEyeLocal();
matrix.postMult(osg::Matrix::translate(-eyePointLocal));
}
return true;
}
};
//创建天空盒
osg::ref_ptr<osg::Node> createSkyBox()
{
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();
//设置纹理映射方式,指定为替代方式,即纹理中的颜色代替原来的颜色
osg::ref_ptr<osg::TexEnv> te = new osg::TexEnv;
te->setMode(osg::TexEnv::REPLACE);
stateset->setTextureAttributeAndModes(0, te.get(), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
//自动生成纹理坐标,反射方式(REFLECTION_MAP)
/*
NORMAL_MAP 标准模式-立方图纹理
REFLECTION_MAP 反射模式-球体纹理
SPHERE_MAP 球体模型-球体纹理
*/
osg::ref_ptr<osg::TexGen> tg = new osg::TexGen;
tg->setMode(osg::TexGen::NORMAL_MAP);
stateset->setTextureAttributeAndModes(0, tg.get(), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
//设置纹理矩阵
osg::ref_ptr<osg::TexMat> tm = new osg::TexMat;
stateset->setTextureAttribute(0, tm.get());
//设置立方图纹理
osg::ref_ptr<osg::TextureCubeMap> skymap = readCubeMap();
stateset->setTextureAttributeAndModes(0, skymap.get(), osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
stateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
stateset->setMode(GL_CULL_FACE, osg::StateAttribute::OFF);
//将深度设置为远平面
osg::ref_ptr<osg::Depth> depth = new osg::Depth;
depth->setFunction(osg::Depth::ALWAYS);
depth->setRange(1.0, 1.0);//远平面
stateset->setAttributeAndModes(depth, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
//设置渲染顺序为-1,先渲染
stateset->setRenderBinDetails(-1, "RenderBin");
osg::ref_ptr<osg::Drawable> drawable = new osg::ShapeDrawable(new osg::Sphere(osg::Vec3(0.0f, 0.0f, 0.0f), 1));
//把球体加入到叶节点
osg::ref_ptr<osg::Geode> geode = new osg::Geode;
geode->setCullingActive(false);
geode->setStateSet(stateset.get());
geode->addDrawable(drawable.get());
//设置变换
osg::ref_ptr<osg::Transform> transform = new MoveEarthySkyWithEyePointTransform();
transform->setCullingActive(false);
transform->addChild(geode.get());
osg::ref_ptr<osg::ClearNode> clearNode = new osg::ClearNode;
clearNode->setCullCallback(new TexMatCallback(*tm));
clearNode->addChild(transform.get());
return clearNode.get();
}
int main()
{
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
osg::ref_ptr<osg::Group> rootnode = new osg::Group();
osg::ref_ptr<osg::Node> node = osgDB::readNodeFile("cessna.osg");
//自动生成纹理坐标属性
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();
stateset = createTexture1DState();
node->setStateSet(stateset.get());
rootnode->addChild(node.get());
//加入天空盒
rootnode->addChild(createSkyBox());
//优化场景数据
osgUtil::Optimizer optimzer;
optimzer.optimize(rootnode.get());
//方便查看在多边形之间切换,以查看三角网
viewer->addEventHandler(new osgGA::StateSetManipulator(viewer->getCamera()->getOrCreateStateSet()));
viewer->addEventHandler(new osgViewer::StatsHandler());
viewer->addEventHandler(new osgViewer::WindowSizeHandler());
viewer->setSceneData(rootnode.get());
viewer->setUpViewInWindow(600, 600, 1000, 800);
return viewer->run();
}
为了实现使用纹理贴图生成模型的轮廓线或者有光泽的模型对任意环境的反射,并不需要显示的指定纹理坐标,可以利用自动生成纹理坐标的机制处理。通常情况下,有多种自动生成纹理坐标的方法,不同的生成纹理坐标的方法有不同的用途。
下面列举几种自动生成纹理坐标的模式:
OBJECT_LINEAR= GL_OBJECT_LINEAR。当纹理图像与移动的物体保持固定时,在物体坐标中指定参考平面是最为合适的。此时,可以使用 OBJECT_LINEAR 来实现纹理图像映射。
EYE_LINEAR= GL_EYE_LINEAR。为了实现移动物体的动态轮廓线,可以在视觉坐标中指定使用 EYE_LINEAR,在石油或煤矿的演示中经常用到。
SPHERE_MAP= GL_SPHERE_MAP。用于球体环境贴图。
NORMAL_MAP= GL_NORMAL_MAP_ARB。用于立方图纹理。
REFLECTION_MAP= GL_REFLECTION_MAP_ARB。用于球体环境纹理。
osg::TexGen 直接继承自状态属性类,因此可以像 osg::Texture2D 一样直接指定属性使用。例如:
//设置自动纹理坐标,并指定相关的平面
osg::TexGen* texgen = new osg::TexGen;
texgen->setMode(osg::TexGen::OBJECT_LINEAR);
texgen->setPlane(osg::TexGen::S,osg::Plane(0.0f,0.0f,1.0f,0.0f));
//启用纹理坐标生成器
stateset->setTextureAttribute(0,texgen,osg::StateAttribute::OVERRIDE);
// TestOSGProject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include //事件监听
#include //事件响应类,对渲染状态进行控制
#include //简化几何体
#include
#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")
//创建二维纹理属性
osg::ref_ptr<osg::StateSet> createTexture1DState()
{
osg::ref_ptr<osg::Image> image = osgDB::readImageFile("flycolor.png");
//创建二维纹理
osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D;
//设置边界处理
texture->setWrap(osg::Texture2D::WRAP_S, osg::Texture2D::CLAMP_TO_EDGE);
texture->setWrap(osg::Texture2D::WRAP_T, osg::Texture2D::CLAMP_TO_EDGE);
//设置滤波
texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR);
//texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::NEAREST);
//设置贴图
texture->setImage(image.get());
//设置自动纹理坐标,并指定相关的平面
osg::ref_ptr<osg::TexGen> texgen = new osg::TexGen;
texgen->setMode(osg::TexGen::OBJECT_LINEAR);
texgen->setPlane(osg::TexGen::S, osg::Plane(0.0f, 0.0f, 1.0f, 0.0f));
//创建属性集
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet;
//启用二维纹理
stateset->setTextureAttribute(0, texture.get(), osg::StateAttribute::OVERRIDE);
stateset->setTextureMode(0, GL_TEXTURE_2D, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
//启用纹理坐标生成器
stateset->setTextureAttribute(0, texgen.get(), osg::StateAttribute::OVERRIDE);
stateset->setTextureMode(0, GL_TEXTURE_GEN_S, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
stateset->setTextureMode(0, GL_TEXTURE_GEN_T, osg::StateAttribute::ON | osg::StateAttribute::OVERRIDE);
return stateset.get();
}
int main()
{
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
osg::ref_ptr<osg::Group> root = new osg::Group();
osg::ref_ptr<osg::Node> node = osgDB::readNodeFile("cessna.osg");
//自动生成纹理坐标属性
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();
stateset = createTexture1DState();
node->setStateSet(stateset.get());
root->addChild(node.get());
//优化场景数据
osgUtil::Optimizer optimizer;
optimizer.optimize(root.get());
//方便查看在多边形之间切换,以查看三角网
viewer->addEventHandler(new osgGA::StateSetManipulator(viewer->getCamera()->getOrCreateStateSet()));
viewer->addEventHandler(new osgViewer::StatsHandler());
viewer->addEventHandler(new osgViewer::WindowSizeHandler());
viewer->setSceneData(root.get());
viewer->setUpViewInWindow(600, 600, 1000, 800);
return viewer->run();
}
在很多时候,直接指定纹理坐标是非常不方便的,如曲面纹理坐标,只有少数的曲面(如圆锥、圆柱等)可以在不产生扭曲的情况下映射到平面上,其他的曲面在映射到表面时都会产生一定程度的扭曲。一般而言,曲面表面的曲率越大,纹理所需要的扭曲度就越大。这时,直接指定纹理坐标可能是一件非常困难的事情了。
下面示例程序通过一个纹理坐标生成器(继承自 osg::NodeVisitor 访问器)遍历模型的所有顶点及法线,然后根据顶点、法线及一定的比例来确定纹理坐标。
// TestOSGProject.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include //事件监听
#include //事件响应类,对渲染状态进行控制
#include //简化几何体
#include
#include
#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")
//纹理坐标生成器,继承自NodeVisitor
class TexCoordGenerator : public osg::NodeVisitor
{
public:
//遍历所有的子节点
TexCoordGenerator() : NodeVisitor(NodeVisitor::TRAVERSE_ALL_CHILDREN)
{
//
}
void apply(osg::Geode& geode)
{
//通过包围盒来确定合适的比例
const osg::BoundingSphere& bsphere = geode.getBound();
float scale = 10;
if (bsphere.radius() != 0)
{
scale = 5 / bsphere.radius();
}
//遍历所有几何体,并设置纹理坐标
for (unsigned i = 0; i < geode.getNumDrawables(); ++i)
{
osg::Geometry* geo = dynamic_cast<osg::Geometry*>(geode.getDrawable(i));
if (geo)
{
osg::Vec2Array* tc = generate_coords(geo->getVertexArray(), geo->getNormalArray(), scale);
geo->setTexCoordArray(0, tc);
}
}
NodeVisitor::apply(geode);
}
protected:
//计算纹理坐标
osg::Vec2Array* generate_coords(osg::Array* vx, osg::Array* nx, float scale)
{
osg::Vec2Array* v2a = dynamic_cast<osg::Vec2Array*>(vx);
osg::Vec3Array* v3a = dynamic_cast<osg::Vec3Array*>(vx);
osg::Vec4Array* v4a = dynamic_cast<osg::Vec4Array*>(vx);
osg::Vec2Array* n2a = dynamic_cast<osg::Vec2Array*>(nx);
osg::Vec3Array* n3a = dynamic_cast<osg::Vec3Array*>(nx);
osg::Vec4Array* n4a = dynamic_cast<osg::Vec4Array*>(nx);
osg::ref_ptr<osg::Vec2Array> tc = new osg::Vec2Array;
for (unsigned i = 0; i < vx->getNumElements(); ++i) {
osg::Vec3 P;
if (v2a) P.set((*v2a)[i].x(), (*v2a)[i].y(), 0);
if (v3a) P.set((*v3a)[i].x(), (*v3a)[i].y(), (*v3a)[i].z());
if (v4a) P.set((*v4a)[i].x(), (*v4a)[i].y(), (*v4a)[i].z());
osg::Vec3 N(0, 0, 1);
if (n2a) N.set((*n2a)[i].x(), (*n2a)[i].y(), 0);
if (n3a) N.set((*n3a)[i].x(), (*n3a)[i].y(), (*n3a)[i].z());
if (n4a) N.set((*n4a)[i].x(), (*n4a)[i].y(), (*n4a)[i].z());
int axis = 0;
if (N.y() > N.x() && N.y() > N.z()) axis = 1;
if (-N.y() > N.x() && -N.y() > N.z()) axis = 1;
if (N.z() > N.x() && N.z() > N.y()) axis = 2;
if (-N.z() > N.x() && -N.z() > N.y()) axis = 2;
osg::Vec2 uv;
switch (axis) {
case 0: uv.set(P.y(), P.z()); break;
case 1: uv.set(P.x(), P.z()); break;
case 2: uv.set(P.x(), P.y()); break;
default:;
}
tc->push_back(uv * scale);
}
return tc.release();
}
};
//创建二维纹理状态对象
osg::ref_ptr<osg::StateSet> createTexture2DState(osg::ref_ptr<osg::Image> image)
{
//创建状态集对象
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();
//创建二维纹理对象
osg::ref_ptr<osg::Texture2D> texture = new osg::Texture2D();
texture->setDataVariance(osg::Object::DYNAMIC);
//设置贴图
texture->setImage(image.get());
texture->setFilter(osg::Texture::MIN_FILTER, osg::Texture::LINEAR_MIPMAP_LINEAR);
texture->setFilter(osg::Texture::MAG_FILTER, osg::Texture::LINEAR);
texture->setWrap(osg::Texture::WRAP_S, osg::Texture::CLAMP_TO_EDGE);
texture->setWrap(osg::Texture::WRAP_T, osg::Texture::CLAMP_TO_EDGE);
stateset->setTextureAttributeAndModes(0, texture.get(), osg::StateAttribute::ON);
return stateset.get();
}
int main()
{
osg::ref_ptr<osgViewer::Viewer> viewer = new osgViewer::Viewer();
osg::ref_ptr<osg::Group> root = new osg::Group();
//读取贴图文件
osg::ref_ptr<osg::Image> image = osgDB::readImageFile("flycolor.png");
osg::ref_ptr<osg::Node> node = osgDB::readNodeFile("dumptruck.osg");
//计算纹理坐标
TexCoordGenerator tcg;
node->accept(tcg);
//创建状态集对象
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();
stateset = createTexture2DState(image.get());
//使用二维纹理
node->setStateSet(stateset.get());
root->addChild(node.get());
//优化场景数据
osgUtil::Optimizer optimizer;
optimizer.optimize(root.get());
//方便查看在多边形之间切换,以查看三角网
viewer->addEventHandler(new osgGA::StateSetManipulator(viewer->getCamera()->getOrCreateStateSet()));
viewer->addEventHandler(new osgViewer::StatsHandler());
viewer->addEventHandler(new osgViewer::WindowSizeHandler());
viewer->setSceneData(root.get());
viewer->setUpViewInWindow(600, 600, 1000, 800);
return viewer->run();
}