leelongcrazy's blog

osg use

Use OSG

简介

OpenSceneGraph是一个开源的,跨平台的高性能 3D 图形工具包,它完全用标准 C++ 和 OpenGL 编写,可以用来进行三维仿真,OSG包含的多线程技术,PagedLOD技术,可以方便的处理大数据的三维模型的可视化及其调度。

一些术语:

场景图形-SceneGraph;场景子树-Subgraph;节点-Node;摄像机-Camera;渲染器-Renderer;

窗口-Window;视口-Viewport;场景-Scene;视图-View;视景器-Viewer;漫游器-Manipulator;

访问器-Visitor;回调-Callback;事件-Event;更新-Update;筛选-Cull;绘制-Draw。

OSG 运行框架

img

模型的移动,旋转,缩放

通过对矩阵操作实现,osg中有一个专门处理的类,osg::MatrixTransform类

还有一个类osg::Matrixd,被当做参数传入osg::MatrixTransform,以实现模型的矩阵变换

osg::ref_ptr<osg::MatrixTransform> mt = new osg::MatrixTransform;
osg::ref_ptr<osg::Node> cow = osgDB::readNodeFile("cow.osg");
mt.addChild(cow);
// 向X轴移动30, 绕X轴旋转90度,缩放到原来的0.2倍
mt->setMatrix(osg::Matrix::translate(osg::Vec3(30, 0, 0)) *  // 平移矩阵
             osg::Matrix::rotate(90, osg::Vec3d(1, 0, 0)) *  // 旋转矩阵 
             osg::Matrix::scale(osg::Vec3(0.2, 0.2, 0.2)) ); // 缩放矩阵
// 矩阵相乘不满足交换律,前后顺序不能颠倒

osg::Referenced 内存管理的类,继承它可以使用超级指针管理内存 osg::View 管理主相机和从相机 osgViewer::View 包括事件管理,场景管理,比如setSceneData(),addEventHandler(), setCameraManipulator()

OSG 裁剪

裁剪技术:

  • 背面裁剪
  • 视锥体裁剪
  • 近平面裁剪
  • 远平面裁剪
  • 视锥体侧面裁剪
  • 细节裁剪
  • 遮挡裁剪
  • 聚集裁剪

osgViewer

  • setSceneData(osg::Node* node)
  • run()
  • realize()

OSG 字段

对象类:osg::Object

节点类:osg::Node

LOD类:osg::LOD

分页数据类:osg::PagedLOD

几何节点类:osg::Geode

组节点类:osg::Group

状态类:osg::StateSet

属性类:osg::StateAttribute

材质类:osg::Material

纹理类:osg::Texture, osg::Texture2D

可绘制类:osg::Drawable

几何体类:osg::Geometry

缓存数据类:osg::BufferData

片元集合类:osg::PrimitiveSet

图片类:osg::Image

缓存对象类: osg::BufferObject

元素缓存对象类:osg::ElementBufferObject

顶点缓存对象类:osg::VertexBufferObject

绘制对象索引类:osg::DrawElementsUnit

数组类:osg::Array, Vec2Array, Vec3Array

osg节点的方法

# osg::Node()
Node() //默认构造函数
void dirtyBound() //提示更新节点的包围体
const BoundingSphere &getBound() const //获取节点的包围体
BoundingSphere computeBound() const //虚函数,计算节点包围体
const ParentList getParents() const //获取节点的父节点列表
const Group *getParent(unsigned int i) const //获取制定的父节点
unsigned int getNumParents() const //获取父节点的数目
void addParent(Group *node) //为当前节点追加一个父节点
void removeParent(Group *node) //删除当前节点的某个父节点

void accept(NodeVisitor &nv) //接受一个访问器

void ascend(NodeVisitor &nv) //虚函数。向上一级节点推进访问器
void traverse(NodeVisitor &nv) //虚函数。向下一级节点推进访问器

//例:获取和操作节点的每一个父节点的方法
for (unsigned int i = 0; i<getNumParents(); ++i)
{
    const osg::Group *parent = node.getParent(i);
    /*执行parent对象的操作*/
}

void setUpdateCallback(NodeCallback *) //设置节点的更新回调
void addUpdateCallback(NodeCallback *)
NodeCallback *getUpdateCallback() //获取节点的更新回调
void setEventCallback(NodeCallback *) //设置节点的交互事件回调
NodeCallback *getEventCallback() //获取节点的交互事件回调
osg::Geode()
Geode() //默认构造函数
bool addDrawable(Drawable *) //从叶节点追加一个可绘制体对象
boolremoveDrawable(Drawable *) //从叶节点删除一个可绘制体对象
virtual bool removeDrawables(unsigned int i, unsigned int numToRemove)//从制定索引位置开始,删除制定数目的可绘制体
bool replaceDrawable(Drawable *origDraw, Drawable *newDraw) //将当前节点中包含的一个可绘制体替换为新的可绘制体
unsigned int getNumDrawables() const //获取可绘制体的数目
Drawable *getDrawables(unsigned int i) //获取一个指定位置的可绘制体
unsigned int getDrawableIndex(const Drawable *) const //获取一个可绘制体在叶节点的索引位置
const DrawableList &getDrawableList() const //获取可绘制体的列表

//例:在可绘制体drawable1和drawable2中加入一个叶节点,然后从叶节点中获取并操作索引位置为i的可绘制体
osg::ref_ptr<osg::Geode> geode = new osg::Geode;
geode->addDrawable(drawable1);
geode->addDrawable(drawable2);
osg::Drawable *drawable = geode->getDrawable(i);
osg::Group()
Group() //默认构造函数
bool addChild(Node *child) //追加一个子节点
bool removeChild(Node *child) //删除一个子节点
bool removeChildren(unsigned int pos, unsigned int numToRemove) //从指引索引位置开始,删除制定数目的子节点
bool insertChild(unsigned int index, Node *child) //向指定索引位置插入一个子节点
bool replaceChild(Node *origChild, Node *newChild) //将当前节点中包含一个子节点替换为新的子节点
unsigned int getNumChildren() const //获取子节点的数目
Node *getChild(unsigned int i) //获取一个指定位置的子节点
unsigned int getChildIndex(const Node *node) const //获取一个子节点的索引位置
osg::NodeVisitor()
NodeVisitor(TraversalMode tm) //构造函数。传入参数为节点树的遍历方式:TRAVERSE_NODE仅当前节点;TRAVERSE_PARENTS向当前节点的父节点遍历;TRAVERSE_ALL_CHILDREN向子节点遍历
void traverse(Node &node) //向下一个需要访问的节点推进
//虚函数。访问各种类型的节点,并执行访问器中自定义的节点操作
void apply(Node &node)
void apply(Node &node)
void apply(Node &node)
osg::NodeCallback()
NodeCallback() //默认构造函数
void operator()(Node *node, NodeVisitor *nv) //虚函数。当回调动作发生时,将会执行这一操作符的内容,并将节点和访问器对象作为参数传入
void addNestedCallback(NodeCallback *) //添加一个临近回调,其内容将在节点回调的执行过程中被依次调用
void removeNestedCallback(NodeCallback *) //移除一个临近回调
void setNestedCallback(NodeCallback *) //直接设置一个临近回调
NodeCallback *getNestedCallback(NodeCallback *) //直接获取一个临近回调
osg::Transform()
Transform() //默认构造函数
void setReferenceFrame(ReferenceFrame rf) //设置节点所用的参考坐标系
ReferenceFrame getReferenceFrame() const //获取节点所用的参考坐标系
bool computerLocalToWorldMatrix(Matrix &matrix, NodeVisitor *nv) const //虚函数。计算从局部坐标系到世界坐标系的级联矩阵,保存到matrix变量中
bool computerWorldToLocalMatrix(Matrix &matrix, NodeVisitor *nv) const //虚函数。计算从世界坐标系到局部坐标系的级联矩阵,保存到matrix变量
osg::MatrixTransform()
MatrixTransform() //默认构造函数
void setMatrix(const Matrix &mat) //设置空间变换矩阵的内容
const Matrix &getMatrix() const //获取空间变换矩阵的内容
void preMult(const Matrix &mat) //递乘,比较类似于++a
void postMult(const Matrix &mat) //递乘,比较类似于a++
const Matrix &getInverseMatrix() const //得到逆矩阵
osg::PositionAttitudeTransform()
PositionAttitudeTransform() //默认构造函数
void setPosition(const VEC3D &) //设置空间中平移的距离
const Vec3d &getPosition() const //获取空间中平移的距离
void setAttitude(const Quat &) //设置空间中旋转的四元数
const Quat &getAttitude() const //获取空间中旋转的四元数
void setScale(const Vec3d &) //设置空间缩放的倍数
const Vec3d& getScale() const //获取空间缩放的倍数
void setPivotPoint(const Vec3d&) //设置空间旋转与缩放的轴心位置
const Vec3d& getPivotPoint() const //获取空间旋转与缩放的轴心位置
osg::Switch
Switch() //默认构造函数
bool addChild(Node *child, bool) //添加一个子节点,同时设置其开关值
void setValue(unsigned int pos, bool) //设置指定索引pos位置子节点的开关值
bool getValue(unsigned int pos) const //获取指定索引pos位置子节点的开关值
void setNewChildDefaultValue(bool value) //设置新加节点的初始值
bool getNewChildDefaultValue() const //得到新加节点的初始值
void setChildValue(const Node *child, bool value) //设置child的值
bool getChildValue(const Node *child) const //得到child的值
bool setAllChildrenOff() //设置所有子节点不显示
bool setAllChildrenOn() //设置所有子节点显示
bool setSingleChildOn(unsigned int pos) //设置索引pos单个节点显示
osg::LOD
LOD() //默认构造函数
bool addChild(Node *child, float min, float max) //添加一个子节点,并设置其对应的观察范围
void setRange(unsigned int childNo, float min, float max) //设置指定位置的子节点对应的观察范围
float getMinRange(unsigned int childNo) const //获取某个子节点对应的观察最小值
float getMaxRange(unsigned int childNo) const //获取某个子节点对应的观察最大值
const RangeList &getRangeList() const //获取所有子节点观察范围的列表
osg::PagedLOD

DatabasePager::setTargetMaximumNumberOfPageLOD函数或环境变量OSG_MAX_PAGEDLOD就是干这个的。他告诉DatabasePager我的电脑内存有限只能容纳指定数量的 PagedLOD ,超出这个数的过期 PagedLOD 就让他滚蛋吧。

数据的动态调度可以使用多线程的工作方式,使数据的动态调度和场景的实时绘制同时进行

深度复制节点数据
osg::ref_ptr<osg::Node> deepnode = (osg::Node*)(item->clone(osg::CopyOp::DEEP_COPY_ALL));

osg::Camera

osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
traits->x = 300;
traits->y = 100;
traits->width = 200;
traits->height = 200;
// 设置窗口的特性。其中X,Y表示的是所建立的窗口左上角,在屏幕上的位置 原点在屏幕的左上角,向右为X轴正方向,向下为Y轴正方向。
getCamera()->setViewport(new osg::Viewport(0,0,w,h)); // 设置视窗的坐标,坐标原点在窗口左下角,水平向右为X轴正方向,向上为Y轴正方向;
getCamera()->setProjectionMatrixAsPerspective(30.0f, static_cast<double>(w)/static_cast<double>(h), 1.0f, 10000.0f);
getCamera()->setGraphicsContext(getGraphicsWindow());
getCamera()->setDrawBuffer(GL_BACK);
getCamera()->setReadBuffer(GL_BACK);
setThreadingModel(osgViewer::Viewer::SingleThreaded);
osg::ref_ptr<osg::GraphicsContext::Traits> traits = new osg::GraphicsContext::Traits;
traits->screenNum = multipleScreens ? i / 3 : 0;
traits->x = (i*width)/numCameras;
traits->y = 0;
traits->width = width/numCameras-1;
traits->height = height;
traits->windowDecoration = true;
traits->doubleBuffer = true;
traits->sharedContext = 0;
traits->readDISPLAY();
traits->setUndefinedScreenDetailsToDefaultScreen();

osg::ref_ptr<osg::GraphicsContext> gc = osg::GraphicsContext::createGraphicsContext(traits.get());


osg::ref_ptr<osg::Camera> camera = new osg::Camera;
camera->setGraphicsContext(gc.get());
camera->setViewport(new osg::Viewport((i*width)/numCameras,(i*height)/numCameras, width/numCameras, height/numCameras));
GLenum buffer = traits->doubleBuffer ? GL_BACK : GL_FRONT;
camera->setDrawBuffer(buffer);
camera->setReadBuffer(buffer);

viewer.addSlave(camera.get(), osg::Matrixd(), osg::Matrixd::scale(aspectRatioScale,1.0,1.0));
// 得到相机的默认参数设置
osg::Vec3d eye, center, up;
viewer->getCamera()->getViewMatrixAsLookAt(eye, center, up);
// 修改相机参数
eye = osg::Vec3d(0.0, -10.0, 0.0);
center = osg::Vec3d(0.0, 0.0, 0.0);
up = osg::Vec3d(0.0, 0.0, 1.0);
// 将参数设置给相机
viewer->getCamera()->setViewMatrixAsLookAt(eye, center, up);
// 仿真循环
viewer->frame();

setViewMatrixAsLookAt: 在世界坐标系中设置相机的位置姿态信息

setViewMatrix: 设置相机坐标系下,设置三维世界的位置。

修改视图背景颜色

viewer->getCamera()->setClearColor(osg::vec4(1, 1, 1, 0)); // 参数RGB和透明度

OSG中漫游器

什么是漫游器?

  • 漫游器就是观察者的视角位置的变换,而不是被观察的视界的物体
  • osg中的漫游器,设置的是相机在世界坐标中的位置姿态矩阵。

漫游器中有的参数:

_center     漫游器中心
_rotation   旋转姿态
_distance   相机到中心的距离,也可以理解为旋转半径

osgGA库中所有漫游器的基类是CameraManipulator

osgGA::CameraManipulator中提到了另一个函数getCoordinateFrame,这个函数是用来获取相机坐标系框架,通过它可以得到当前相机坐标系的坐标轴向。

通过osgGA::CameraManipulator提供的另外三个函数:

        osg::Vec3d getSideVector(const osg::CoordinateFrame& cf) const { return osg::Vec3d(cf(0,0),cf(0,1),cf(0,2)); }
        osg::Vec3d getFrontVector(const osg::CoordinateFrame& cf) const { return osg::Vec3d(cf(1,0),cf(1,1),cf(1,2)); }
        osg::Vec3d getUpVector(const osg::CoordinateFrame& cf) const { return osg::Vec3d(cf(2,0),cf(2,1),cf(2,2)); }

可以得到当前相机坐标的右、前、上的方向,可以方便地计算角色向前移动、向右移动、旋转等操作。

void setByMatrix(const osg::Matrix&) // 虚函数 设置相机的位置姿态矩阵,或者直接设置相机的观察矩阵

void setByInverseMatrix(const osg::Matrixd&)

osg::Matrixd getMatrix() const // 虚函数,获取相机的位置姿态矩阵,或者相机的观察矩阵,通常由OSG系统内部获取并实时调整场景的漫游方式
osg::Matrixd getInverseMatrix() const  // 相机坐标系和世界坐标系之间的变换矩阵

void home(const GUIEventAdapter&,GUIActionAdapter&) // 回到场景的初始位置,何谓初始位置是由漫游器自己定义
bool handle(const GUIEventAdapter& ea,GUIActionAdapter&,GUIActionAdapter& us) // 根据用户的交互动作(交互事件和反馈动作),调整漫游方式和漫游器的位置、姿态。

用户可以在handle中设置交互逻辑,在帧事件中改变速度、方向、姿态。

viewer->setCameraManipulator() 设置漫游器

漫游器的更新

在viewer类中updateTraverseal函数中有如下代码:

 if (_cameraManipulator.valid())
{
    setFusionDistance( getCameraManipulator()->getFusionDistanceMode(),
                        getCameraManipulator()->getFusionDistanceValue() );

    _cameraManipulator->updateCamera(*_camera);
}

// updateCamera()实际实现
 /** update the camera for the current frame, typically called by the viewer classes. 
            Default implementation simply set the camera view matrix. */
virtual void updateCamera(osg::Camera& camera) {                                                camera.setViewMatrix(getInverseMatrix()); 
}

关于漫游器,观察矩阵是姿态矩阵的逆?

可以想象一下:观察者在世界中的运动,可以理解为世界在观察者眼中的逆运动

漫游器设置的是相机在世界坐标中的位置姿态矩阵;而相机的观察矩阵表达的是世界在观察坐标系下的位置姿态。这两个矩阵互为逆矩阵,即,相机在世界中的姿态,就是世界在相机中的姿态的逆过程。

基础 MVPW

M: model 模型

模型顶点在三维场景中的坐标位置 worldPos = pos * matrix

  • pos 为模型的顶点位置
  • matrix: 变换矩阵

V ViewMatrix 观察矩阵

作用:将世界坐标转换为相机坐标。在相机坐标系下,以相机为参考,其位置为原点,world * viewMatrix可以获得模型顶点在相机坐标下的位置

P projectMatrix 投影矩阵

投影的作用:将三维坐标信息转化到二维坐标中。

两种方式:

  • 透视投影
  • 正交投影
// OSG中设置正交投影的接口
Camera::setProjectionMatrixAsOrtho(double left, double right,
double bottom, double top, double zNear, double zFar);
// left表示视景体左面的坐标,right表示右面的坐标,bottom表示下面的,top表示上面的。

// 透视投影设置接口如下
Camera::setProjectionMatrixAsPerspective(double fovy, double aspectRatio, double zNear, double zFar);
// fovy——相机的角度大小,视角小就是焦距大(长焦),视角大就是焦距小(广角);
// aspect——实际窗口的纵横比,即width(窗口宽度)/ height(窗口高度);
// zNear——近处裁面,如果物体的位置到相机距离小于近裁剪面,该物体会被相机裁剪掉;
// zFar——远处的裁面,如果物体的位置到相机距离大于远裁剪面,该物体会被相机裁剪掉。

窗口矩阵变化 Windows

窗口矩阵变换的主要作用:将视口裁剪后的结果映射到屏幕中,通过viewport获取屏幕显示区域的大小,将帧缓冲区域内的数据转变为能显示在屏幕上的像素。

osg内置shader变量

uniform int osg_FrameNumber:当前OSG程序运行的帧数
uniform float osg_FrameTime:当前OSG程序的运行总时间
uniform float osg_DeltaFrameTime:当前OSG程序运行每帧的间隔时间
uniform mat4 osg_ViewMatrix:当前OSG摄像机的观察矩阵
uniform mat4 osg_ViewMatrixInverse:当前OSG摄像机观察矩阵的逆矩阵
uniform mat4 osg_ModelViewMatrix:内置gl_ModelViewMatrix
uniform mat4 osg_ModelViewProjectionMatrix:内置gl_ModelViewProjectionMatrix
uniform mat4 osg_ProjectionMatrix:内置gl_ProjectionMatrix
uniform mat3 osg_NormalMatrix:内置gl_NormalMatrix

attribute:应用程序与顶点着色器的接口,使用顶点属性定义函数进行定义;
uniform:应用程序与所有着色器的接口,定义不随顶点变化的“一致变量”;
varying:着色器之间的“易变变量”接口,用于传递插值得到的顶点数据;
const:用于声明常量数据;
in:作为函数形参进行传递,函数返回时不保留改变,只保留传入值;
out:作为函数形参进行传递,本身未定义,函数返回时保留改变值;
inout:作为函数形参进行传递,可以定义传入值,也会保留返回时的改变值。
uniform mat4 gl_NormalMatrix:法线变换矩阵;
uniform mat4 gl_ModelViewMatrix:模型视点变换矩阵;
attribute vec4 gl_Vertex:顶点坐标属性;
attribute vec4 gl_MultiTexCoord0:纹理单元0的纹理坐标属性;
varying vec4 gl_TexCoord[0]:纹理单元0的实际纹理坐标。

相关文档

osg 几何体绘制

https://blog.csdn.net/hudfang/article/details/46724605

圆点绘制参考

https://developer.aliyun.com/article/781532

OpenGL中的功能与OSG对应功能

https://blog.csdn.net/hankern/article/details/84051611

OSG3

https://github.com/mylxiaoyi/osg3/tree/master/source

使用技巧

总之来说,将多个Geometry合并成一个Geometry几乎是业界普遍认为最简单、最有效、最普遍、最应该使用的优化方法。也就是我们追究求的就是CPU尽量少次多量的向GPU提交数据。

IT Code, Osg