OSG入坑之路

c/c++

浏览数:157

2019-3-28

AD:资源代下载服务

所谓“入坑”,只不过为自己的不熟练找借口而已。
现在学习OSG已经三周,跟着书、视频做些简简单单的小demo而已,有时候真是感觉自己无从下手。
不晓得的自己算不算是转行,之前研究点云数据处理,有一点点opengl的基础,虽说工作进入某高校,项目是某沉浸式VR系统开发,目前为止也止有我一个人在搞!
。。。。
有些扯远了,上述纯属瞎扯淡,毫无逻辑。。。。。。

断断续续的用了大半个月的osg,给我的感觉就是:出了问题不知如何下手处理!比如对某图形节点设置状态,但是可视化出来却没有自己想要的效果,程序也不报错。
之所以会有上述烦恼,可能原因在于自己对opengl和计算机图形学并不深入了解,理论上的匮乏造成了实际问题无法有效解决,这是其一;其二,或许是个人经验不够,思考不多,在学校习惯了被老师同学指点,自己缺不去深入考虑,经验需要积累,在以后工作中要多思考,多感悟,多总结,不怕错
坏了,又有些扯远了,还是回到osg吧!记性不好,就多记录一下自己的小收获吧!

一、 渲染状态(render state)

osg中,当设置某节点的渲染状态时,该状态会赋予当前节点及其子节点,因此,若要实现多节点多状态渲染时,一定注意节点之间的父子关系,最好一个节点设置一个自己想要的状态,除非父节点及其子节点的渲染状态一样。
渲染状态的管理通过osg::StateSet管理,可以将其添加到任意的节点(node、group、geode等)和DrawAble类。如要设置渲染状态的值,需要:

  1. 得到某节点的stateset实例;
  2. 设置该实例的渲染属性(attribute)和模式(mode)
    例:osg::StateSet *state = obj->getOrCreatStateSet() ;其中,obj可以是节点或Drawable实例,且:getOrCreatStateSet()方法是在osg::Node中声明的,这就意味着geode或group都可以使用。
state>setMode(GL_LIGHTING,osg::StateAttribute::OFF);//关闭灯光状态
state->setTextureAttributeAndModes(0,texture,osg::StateAttribute::ON);//设置纹理

//开启shader
osg::ref_ptr<osg::Program> shaderProg = new osg::Program;
shaderProg->addShader(new osg::Shader(osg::Shader::VERTEX,vertexShader));
shaderProg->addShader(new osg::Shader(osg::Shader::FRAGMENT,fragShader));
state->setAttributeAndModes(shaderProg,osg::StateAttribute::ON);  //设置渲染属性和模式

->setMode(GL_DEPTH_TEST,osg::StateAttribute::OFF);//管理深度测试

//设置渲染顺序,第一个参数越小,渲染越靠前,默认第一个参数为 -1
state->setRenderBinDetails(10, "RenderBin"); //默认渲染排序
state->setRenderBinDetails(100,"DepthSortedBin");  //由远到近
state->setRenderBinDetails(1000,"TraversalOrderBin"); //按遍历顺序

//开启混合透明度
state->setMode(GL_BLEND,osg::StateAttribute::ON);  //设置渲染模式

等等等等

二、 geometry和geode

显然,geode是几何节点,且是叶节点,geometry类管理osg中各种各样的几何体。
个人总结:在使用geode画osg自带的几何图形时,总是:

  1. 声明geode节点
  2. 创建几何对象
  3. 设置几何对象的参数
  4. 申请一个osg::ShapeDrawable
  5. geode->addDrawable
    列子:
//画个圆柱
osg::TessellationHints *hins = new osg::TessellationHints;
hins->setDetailRatio(1.0f);//设置圆柱的精度为0.1,值越小,精度越小
osg::ref_ptr<osg::Cylinder> cy = new osg::Cylinder;  //圆柱
osg::ref_ptr<osg::ShapeDrawable> sd = new osg::ShapeDrawable(cy); //直接用几何对象初始化shapedrawable实例
cy->setCenter(osg::Vec3(400.0,300,0));
cy->setHeight(0.1);
cy->setRadius(150);
sd->setTessellationHints(hins);
geode->addDrawable(sd);  //必不可少(到这里才真正绘制)
/*以上啰嗦代码当然可以这样写:*/
geode->addDrawable(new osg::ShapeDrawable(osg::Cylinder(center),Radius,Height),teseel);

//画个盒子
osg::ref_ptr<osg::TessellationHints> hints = new osg::TessellationHints;
//设置精度
hints->setDetailRatio(0.1);
osg::ref_ptr<osg::ShapeDrawable> shape  = new osg::ShapeDrawable(new osg::Box(osg::Vec3(x,y,z),长,宽,高),hints.get());
geode->addDrawable(shape);

TessellationHints(精度)参数对圆柱几何的影响以及shapedrawable继承关系:
!

自定义几何体时:

  1. 申请geometry对象,自定义绘制的顶点、法向量、颜色等(如果需要的话)数组
  2. 将自定的各种数组传递给geode,并设置绑定方式。
  3. 设置各个顶点之间的关联方式(也就是绘制什么图形)
  4. 将geometry对象添加到geode->addDrawable(geometry);
    在第三步骤中,geom->addPrimitiveSet();函数需要一个Drawarrays指针和原始点的连接方式
    osg::ref_ptr<osg::Geode> geo = new osg::Geode;
    osg::ref_ptr<osg::Geometry> geom = new osg::Geometry();
    osg::ref_ptr<osg::Vec3Array> vex = new osg::Vec3Array;
    osg::ref_ptr<osg::Vec4Array> color = new osg::Vec4Array;
    osg::ref_ptr<osg::Vec3Array> normal = new osg::Vec3Array;
    osg::ref_ptr<osg::LineWidth> width = new osg::LineWidth;  //线宽
    
//设置法向量及其绑定方式
    geom->setNormalArray(normal,osg::Array::Binding::BIND_OVERALL);
    normal->push_back(osg::Vec3(0.0,-1.0,0.0));

 //设置顶点
    vex->push_back(osg::Vec3(-10.5,5,-10.0));
    vex->push_back(osg::Vec3(10.5,5,-10.0));
    vex->push_back(osg::Vec3(10.0,5,10.0));
    vex->push_back(osg::Vec3(-10.0,5,10.0));
    geom->setVertexArray(vex.get());
    
//设置颜色
    color->push_back(osg::Vec4(0.1,0.2,0.3,0.5));
    color->push_back(osg::Vec4(1.1,0.9,0.3,0.50));
    color->push_back(osg::Vec4(0.2,0.5,0.3,0.50));
    color->push_back(osg::Vec4(0.4,0.2,0.7,0.50));
    geom->setColorArray(color);
    geom->setColorBinding(osg::Geometry::AttributeBinding::BIND_PER_VERTEX); //设置纹理绑定方式
    osg::ref_ptr<osg::TessellationHints> hints = new osg::TessellationHints;
    hints->setDetailRatio(0.1);
//设置透明度
    geom->getOrCreateStateSet()->setMode(GL_BLEND,osg::StateAttribute::ON);
    

    /*osg::DrawArrays相当于对opengl中glDrawarray的封装*/
    geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::Mode::LINE_LOOP,0,4)); //设置顶点的关联方式(此处,以线段的方式连接)

    //设置线宽
    width->setWidth(20.0);
    geom->getOrCreateStateSet()->setAttributeAndModes(width);

    geo->addDrawable(geom.get());

geom->addPrimitiveSet(new osg::DrawArrays(osg::PrimitiveSet::Mode::LINE_LOOP,0,4));的基本图元及drawable继承方式:

三、Camera

当然,上一篇入门文章viewer::run()多多少少也和camera有关。现在算是进一步吧。
先看看camera的继承关系图

很明显,camera继承自node、group、transform,也就是说他们具有的属性,camera也具有。

1. 裁剪面

就像上篇文章所述,osgviewer->run()会自动设置一个场景漫游器,该漫游器包含了透视投影矩阵、视口大小、屏幕宽高比以及远近裁剪面、摄像机位置等等参数,如果想要修改远近裁剪面应该首先关闭osg的自动判断远近裁剪面的函数:

viewer->getCamera()->setComputeNearFarMode(osgUtil::CullVisitor::DO_NOT_COMPUTE_NEAR_FAR);
viewer->getCamera()->setProjectionMatrixAsPerspective(fovy,aspectRatio,zNear,zFarSurface);

osgUtil::CullVisitor是osg的场景拣选访问器。

2. 获取自己设备的图形环境、HUD、RTT:

osg::GraphicsContext::WindowingSystemInterface *wsi = osg::GraphicsContext::getWindowingSystemInterface();
    if (!wsi)
        return ;
    unsigned int height,width;
    wsi->getScreenResolution(osg::GraphicsContext::ScreenIdentifier(0),width,height);  //获取屏幕的长宽
    //设置图形环境特性
    osg::ref_ptr<osg::GraphicsContext::Traits> traits  = new osg::GraphicsContext::Traits();
    traits->x = 0;
    traits->y = 0;
    traits->width = width;
    traits->height = height;
    traits->windowDecoration = false; //窗口修饰 关
    traits->doubleBuffer = true; //是否支持双缓存
    traits->sharedContext = false;

    osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits);
    if (!gc.valid())
    {
        return ;
    }
    gc->setClearMask(GL_COLOR_BUFFER_BIT);
    gc->setClearColor(osg::Vec4(0.5,0.5,0.5,1.0)); //设置全局上下文的背景色
    

     osg::ref_ptr<osg::Camera> master = new osg::Camera;
     master->setGraphicsContext(gc);

    viewer->addSlave(master); //将相机添加到场景中。

在HUD和RTT中,创建相机是非常重要的,HUD的相机要最后渲染,防止被覆盖
HUD相机:

osg::Camera *CreatTextHUD()
{
    osg::ref_ptr<osg::Camera> camera = new osg::Camera;
    camera->setViewMatrix(osg::Matrix::identity()); //设置视图矩阵为单位矩阵
    camera->setAllowEventFocus(false); //不响应其他鼠标事件
    camera->setRenderOrder(osg::Camera::POST_RENDER);//最后渲染
    camera->setClearMask(GL_DEPTH_BUFFER_BIT);
    camera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);//设置参考帧 为绝对坐标系
    camera->setProjectionMatrixAsOrtho2D(0,1024,0,768); //设置二维正交投影

    osg::ref_ptr<osg::Geode> geode = new osg::Geode;
    geode->getOrCreateStateSet()->setMode(GL_LIGHTING,osg::StateAttribute::OFF);//关闭灯光状态
    osg::ref_ptr<osgText::Text> text = new osgText::Text;
    geode->addDrawable(text);

    text->setPosition(osg::Vec3(0,0,0));
    text->setFont("simsun.ttc"); //设置为宋体
    text->setText(L"宋体 测试");  //一定不要忘记加“L”字符
    text->setCharacterSize(50);

    camera->addChild(geode);

    return camera.release();
}

RTT相机

void CreatRTT(osgViewer::Viewer *viewer)
{
    osg::ref_ptr<osg::Group> group = new osg::Group;
    osg::ref_ptr<osg::Node> node = osgDB::readNodeFile("nathan.osg");
    group->addChild(node);

    //设置图形上下文
    osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
    traits->x = 0;
    traits->y = 0;
    traits->width = 800;
    traits->height = 600;
    traits->sharedContext = false;
    traits->doubleBuffer = true;
    traits->windowDecoration = false;
    
    osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits);

    //创建主相机
    osg::ref_ptr<osg::Camera> master = new osg::Camera;
    master->setGraphicsContext(gc);
    master->setViewport(0,0,800,600);
    viewer->addSlave(master);

    //创建rtt相机
    osg::ref_ptr<osg::Camera> rttCamera = new osg::Camera;
    rttCamera->setRenderOrder(osg::Camera::PRE_RENDER);
    rttCamera->setGraphicsContext(gc);
    rttCamera->setViewport(0,0,800,600);
    rttCamera->addChild(node);

    //添加相机并设置瞄准镜的放大倍数为8倍,最后false表示:该添加入的相机不接受主窗口任何内容。
    viewer->addSlave(rttCamera,osg::Matrix::scale(8,8,8),osg::Matrix::identity(),false); 

    osg::Texture2D *t2d = new osg::Texture2D;
    t2d->setInternalFormat(GL_RGBA);
    rttCamera->attach(osg::Camera::COLOR_BUFFER,t2d);
    
    group->addChild(CreatHUD(t2d));
    viewer->setSceneData(group);
    
    return ;
}

上边的函数其实是创建了一个瞄准镜的效果:

自我感觉(或许并不对,仅限于目前认知水平):osg添加多相机主要功能是为了多窗口多视图显示。一个相机能够渲染多个视图,一个场景能够添加多个相机,多个相机能够实现从不同角度、方位、视角观察同时观察同一个模型。

3. 两个小函数:

viewer->setCameraManipulator(osgGA::CameraMaipulator *),和viewer->addEventHandler(osgGA::EventHandler *),前者的函数参数是osgGA::CameraMaipulator *类型,后者参数为osgGA::EventHandler *类型,且osgGA::CameraMaipulator *是继承自osgGA::EventHandler *的。
对比:

  1. 前者的主要作用是利用外部设备等影响场景中的主相机位置以及相应事件。事件相应的主要目的是为了修改相机参数矩阵,以实现漫游的目的。设置漫游的本质就是修改主相机的各种参数矩阵,并将修改后的参数返回场景中的主相机(影响的最顶层的相机节点).
  2. 后者顾名思义,就是为了相应各种键盘、鼠标等外部设备事件事件,当然可以影响主场景中的相机。其实本质上):从类的继承关系来看,漫游器是从事件处理类中继承而来,如果事件处理函数个人写得足够好、足够丰富,应该能够替代漫游器的,这也是为什么两者在很多时候添加自定义类时程序并不报错,但就是达不到预想效果的原因。
    进一步讲,漫游器是对事件处理的更进一步的丰富,其”丰富”的主要内容应该是矩阵的自动返回操作,从而控制相机,其矩阵自动返回的主要函数是:
virtual void setByMatrix(const osg::Matrixd& matrix) = 0;         //设置相机的位置姿态矩阵  
virtual void setByInverseMatrix(const osg::Matrixd& matrix) = 0;  //设置相机的视图矩阵  
virtual osg::Matrixd getMatrix() const = 0;                       //获取相机的姿态矩阵  
virtual osg::Matrixd getInverseMatrix() const = 0;  

这也就是为什么自定义的漫游器必须重载这四个函数的原因所在。(事件处理不需要重载这几个函数)
关系:
如果总结为一句话,应该是:CameraManipulator是EventHanler的子类,EventHandler本身也可以实现漫游功能,只不过osg开发者为了更加丰富便捷的控制事件与漫游,从而单独设计了一个漫游操作器,以便使用者能够更加轻松方便的控制自己的场景。

清除camera中的深度缓存:camera->setclearMask(GL_COLOR_BUFFER_BIT | GL_DEPTH)

:以上内容只是自己在比较皮毛的层面的记录,并没有过多的深入源码解析,内容或许有误,恳请指正留言,不胜感激!
:有做点云处理研究的朋友也可多多交流。