网站建设表格,做网站ps的图片,网站的验证码是怎么做的,好用的海报设计网站1 OSG基础知识 OSG是Open Scene Graphic 的缩写#xff0c;OSG于1997年诞生于以为滑翔机爱好者之手#xff0c;Don burns 为了对滑翔机的飞行进行模拟#xff0c;对openGL的库进行了封装#xff0c;osg的雏形就这样诞生了#xff0c;1998年Don burns 遇到了同样喜欢滑翔… 1 OSG基础知识 Ø OSG是Open Scene Graphic 的缩写OSG于1997年诞生于以为滑翔机爱好者之手Don burns 为了对滑翔机的飞行进行模拟对openGL的库进行了封装osg的雏形就这样诞生了1998年Don burns 遇到了同样喜欢滑翔机和计算机图形学的Robert Osfield 从此Robert Osfield加入了osg小组的开发并一直担任开发小组的组长。 Ø OSG不但有openGL的跨平台的特性和较高的渲染性能还提供了一系列可供3D程序开发者使用的功能接口包括2D和3D数据文件的加载、纹理字体支持、细节层次LOD控制、多线程数据分页处理等。OSG广泛应用于飞行仿真等领域包括Flightgear及美国军方投资的仿真项目Delta3d等 1.1 计算机绘图的基本知识 Ø 首先要先回顾一下在显示世界中我们是如何作画的。 Ø 在现实世界中绘制一副画我们需要的东西就是彩笔、白纸。通过选择不同颜色的彩笔在白纸上移动就可以将白纸上的不同的点描绘上不同的颜色而所有这些点连接起来从人的宏观视野看来就构成了一副对人有意义的画作。 Ø 类比到计算机的实际中来。在计算机的世界里。作画的过程又是怎样的呢 Ø 同样绘制虚拟的图像也需要“彩笔”和“白纸”。在计算机的世界里“彩笔”就是Direct3D之类的绘图API函数而“白纸”就是存储数据的内存。我们在内存中划分出一块区域其中的数据就是对一个真实世界的模拟。一个数据就描述真实世界中一个点的属性。在我们作画前他们都只有一个初始值就像白纸在作画前只有白色一样。而在作画后每一个数据都有了独特的意义将整片数据连接在一起看就是一副有意义的图景。作画的过程就是对内存中的每一个数据进行赋值的过程相当于用彩笔给白纸上的一个点进行着色。选择不同的API函数可以画出不同的形状。 1.2 OSG程序框架 Ø 一个最简单的OSG程序如下所示当然在如果是在VS下面进行编辑的话要进行一些设置要设置OSG的lib和include目录。 1 #includeosgDB/ReadFile2 3 #includeosgViewer/Viewer4 5 void main()6 7 {8 9 osgViewer::Viewer viewer;
10
11 viewer.setSceneData(osgDB::readNodeFile(glider.osg));
12
13 viewer.realize();
14
15 viewer.run();
16
17 }
18 osgViewer::Viewer viewer 申请了一个viewer可以理解为申请一个观察器该观察可以查看模型 viewer.setSceneData(osgDB::readNodeFile(glider.osg)) 这里是设置观察器Viewer中的数据换句话说有了观察器就可以添加模型了 viewer.realize() 这个语句表达的意思非常多事实上可以定位到Viewer.cpp的realize函数会发现里面的操作非常多可以理解为这是在渲染前的最后一步会检查和设置图形上下文屏幕啊什么的会让你以前的设置对Viewer的设置都生效。 viewer.run(); 这一句的意思就是渲染了如果要解释它的意思的话可以用下面的几个语句来替代 while(!viewer.done()){viewer.frame();}.意思也就是说只要viewer没有结束那么就绘制它的每一个帧frame。 1.3 OSG简单模型控制 1.3.1 添加模型 在OSG当中模型是使用osg::Group和osg::Node来装载在一起的比如同时需要加入两个模型模型A了模型BAB各自是一个NODE那么可以使用以下语句来做到首先使用一个Group,然后Group-addChild(A),同样之后要Group-addChild(B)。然后再把Group添加到viewer当中就可以了。如图3.1所示AB之间的关系。在这里要申明的是NODE是Group的父类在类中都有相应的方法可以转到对方故Node与Group 是通用的Node也可以被当作Group来用。 图 31 AB都加入到Group当中 简单示例代码如下 1 #includeosgDB/ReadFile2 3 #includeosgViewer/Viewer4 5 #includeosg/Node6 7 void main()8 9 {
10
11 osgViewer::Viewer viewer;
12
13 osg::Group * rootnew osg::Group();
14
15 root-addChild(osgDB::readNodeFile(glider.osg));
16
17 root-addChild(osgDB::readNodeFile(osgcool.osg));
18
19 viewer.setSceneData(root);
20
21 viewer.realize();
22
23 viewer.run();
24
25 }
26 则运行结果为 图 32示例运行结果 1.3.2 删除结点 如果我们不需要某个结点了比如图3.2我们看那个小飞机很不爽我们想把它从场景中删除掉。不知道于某种目的反正现在要删除掉可能是开始想看见它现在不想看见它了。可以通过removeChild方法除多个孩子也可以通过removeChildren方法里面的参数有些需要索引值有些需要结点本身的指针读者可以自己尝试。这里要注意的是如果要删除一个结点那么该结点下的所有结点都会被删除。如果一个结点被加入到一个组中两次那么这两次是分别存在的删除一次还有另一次。删除操作不能说不是个危险的操作有些时候尤其在有移动结点等等混在一起时删除操作有时候会发生一些比较奇怪的现象。在内存映象当中如果一个模型被读取一次而用了多次那么所占用的空间是不会改变的。 1.3.3 隐藏模型与结点开关 Ø 隐藏模型 隐藏模型其实模型仍在渲染当中因此损耗并未减少只不过隐藏了而已隐藏的确不是个什么好操作但是有时候对小模型确实也很实用。node-setNodeMask可以设置隐藏与显示。 Ø 节点开关 在OSG当中专门有一个类来负责打开与关闭结点该类名为osg::Switch里面有相应的方法来控制它所管理的结点的打开与关闭。 两个方法都能控制模型的显示和隐藏区别在于隐藏模型方法不会让模型在内存中消失这样对于小的物体频繁的调用会节省一些时间而对于有些大的模块在用一次以后可能很久再用第二次这个时候用节点开关可以将模型销毁再次使用再调入内存以防止占用更多的资源。 1.3.4 超级指针 超级指针的机制其实就是引用一个计数器这个计数器会计算这个箱子被引用的次数被别人引用一次这个计数器增加一别人不用一次即释放一次则计数器减一。当减至0时内存放掉不用。 们来看使用一个Node的三种方法,对比一下 Ø //方法一最好的方法,十分安全也是OSG中最常用的方法多少版本它都没变 osg::ref_ptrosg::NodeaNode(new osg::Node()); group-addChild(aNode.get()); Ø //方法二也是非常好的方法有时候不适用但也十分安全 group-addChild(new osg::Node()); Ø //方法三非常危险但是令许多人无故铤而走险的方法 osg::Node*anotherNodenew osg::Node(); group-addChild(anotherNode); 方法一在new::Node()时申请了一个Node的资源这时在堆内引用该Node的计算器会被置1。在group-addChild(aNode.get())时又引用了一次会再加1。在这两次引用都结束时Node的资源就会被释放。 方法二这个方法也是很实用的但是无法引出Node的指针也许在别处可以用到事实上会经常用到。如果已经这样做了得到Node指针也不是不可以的可以使用NodeVisitor来得到Node的指针也可以使用findChild方法来做这件事。 方法三这个应该是最常用但是最烂的方法了原因在于如果在osg::Node*antherodenew osg::Node()之后发生了错误抛出了异常谁来释放Node所占用的资源呢。而这个异常在后面被捕获程序正常的走下去而内存却没有被正常的放掉。 1.3.5 移动/旋转/缩放模型 移动/旋转/缩放其实都是对矩阵进行操作在OSG当中矩阵可以当作一个特殊的结点加入到root当中而矩阵下也可以另入结点而加入的结点就会被这个矩阵处理过比如移动过/旋转过/缩放过。在OSG中控制矩阵的类为osg::MatrixTransform。 Ø 移动 osg::Matrix::translate Ø 旋转 osg::Matrix::rotate Ø 缩放 osg::Matrix::scale 1.4 基本几何图元 1.4.1 基本绘制方法 首先来看一些OSG中的最基本的绘制路数。如果我们要绘制一个正方形绘制有色彩未贴图。首先我们必须要申请一个osg::Geometry,把这个Geometry加入到Geode就可以了。在这个Geometry中要设置一些元素最基本的是顶点Vertex,颜色color,以及顶点的关联方式和法线normal.就可以了。如图3.3所示。 图 33几何体绘制过程 1.4.2 可绘制的图元 所有可绘制的图元包括 Ø POINTS[点] Ø LINES[线] Ø LINE_STRIP[线带] Ø LINE_LOOP[闭合线段] Ø TRIANGLES[三角形] Ø TRIANGLE_STRIP[三角带] Ø TRIANGLE_FAN[三角扇] Ø QUADS[四方块] Ø QUAD_STRIP[四方块带] Ø POLYGON[多边形] 在OSG中设置直线线宽的专门有一个函数来管理叫做LineWidth它本身属于状态与属性类别中的类。事实上也是从那里派生而来。所有设置状态的操作都与此类似。 1.4.3 内置几何类型 如同OpenGL一样OSG同样有一套内置几何类型这些几何类型都在类osg::Shape中这些shape本身都可以本当成一个Draw结点加入到geode中然后再人geode中添加到root里进行渲染。形状共有九种分别为osg::Box[盒子]osg::Capsule[胶囊形]osg::CompositeShape[组合型]osg::Cone[圆锥形]osg::Cylinder[圆柱形]osg::HeightField[高程形]osg::InfinitePlane[有限面]osg::Sphere[球形]osg::TriangleMesh[三角蒙皮]。 内置几何类型的渲染过程,如图4.5所示 图 34基本几何图元的添加过程 这里要注意的是一般的形状态都有特定的因素比如Box有长宽圆有半径以及各个图形所画的精细度都需要指明这些精细度在球这样的形状上意义还是十分巨大的。在OSG中有专门指明精细度的类名为osg::TessellationHints。以球为例只需要规定圆心半径和精细度就可以画出该球。 1.5 交互 1.5.1 交互过程 viewer的主要的功能是控制场景它是场景的核心类如果能响应键盘时得到viewer那么也可以从键盘的响应中控制整个场景。viewer中有一个方法名为addEventHandler就是专门做这件事情的。他会加入一个事件处理器。于是我们就想一定要自己写一个事件处理器才行这就必须要了解事件处理器的格式只要有一个接口就可以了解它的格式这个接口就是osgGA::GUIEventHandler于是我们可以写一个类A从该类公有派生出来即class Apublic osgGA::GUIEventHandler,在里面处理好各种操作然后加入到viewer当中即viewer.addEventHadler(new A(里面可以有参数));这样就可以完成操作。 假如类A是一个事件处理类那么加入类A可以这样理解如图3.5 图 35事件A控制场景过程 1.5.2 事件类型与响应 代码 值 事件类型 NONE 0 无事件。 PUSH 1 鼠标某键按下在上面代码28行有用到。 RELEASE 2 鼠标某键弹起。 DOUBLECLICK 4 鼠标某键双击。 DRAG 8 鼠标某键拖动。 MOVE 16 鼠标移动。 KEYDOWN 32 键盘上某键按下。 KEYUP 64 键盘上某键弹起。 FRAME 128 应该是鼠标每帧。没用过。 RESIZE 256 窗口大小改变时会有的事件。 SCROLL 512 鼠标轮滚动。 PEN_PRESSURE 1024 手写板的某事件 PEN_PROXIMITY_ENTER 2048 手写板的某事件 PEN_PROXIMITY_LEAVE 4096 手写板的某事件 CLOSE_WINDOWS 8192 关闭窗口。 QUIT_APPLICATION 16384 退出程序。 USER 32768 用户定义。 至于为什么都用2的N次方主要是因为它的二进制编码只有一位是一判断事件时很好判断只要年哪位是一就可以了。 1.5.3 PICK pick主要是通过鼠标的点击来拾取一些物体或者判断鼠标所点击的位置在哪里。Pick实现的思路如下图所示 图 36pick事件流程 判断射线与viewer中物体相交的方法为发出射线并相交。在OSG中有库函数osgViewer::View::computeIntersections他共有三个参数第一个是x屏幕坐标第二个是Y屏幕坐标第三个是存放被交的结点以及相交的坐标结点路径等等相关信息。 判断相交结点为我想要的那个结点只需要判断存放相交射线交场景的结果集中有没有要用的结点就可以了。 1.6 漫游 1.6.1 MatrixManipulator 场景的核心管理器是viewer而漫游必须响应事件比如鼠标动了场景也在动。响应事件的类是osgGA::GUIEventHandler。我们想把响应事件的类派生一个新类出来这个类专门用来根据响应控制viewer。这个类就是osgGA::MatrixManipulator这个类有一些设置矩阵的公共接口有了这些接口就可以有效的控制viewer了根据不同的习惯大家还会设置不同的控制方式如同OSG自带的几个操作器操作都不尽相同。来看一下漫游的主要流程如图6.1 图 37一般的场景操作器 操作器必须从osgGA::MatrixManipulator派生而来。osgGA::MatrixManipulator有四个可以控制场景的重要接口 1 virtual void setByMatrix(const osg::Matrixdmatrix)0
2
3 virtual void setByInverseMatrix(const osg::Matrixdmatrix)0
4
5 virtual osg::Matrixd getMatrix()const0
6
7 virtual osg::Matrixd getInverseMatrix()const0
8 四个矩阵接口可以有效的向viewer来传递矩阵的相关信息。 1.6.2 碰撞检测 最简单的碰撞检测如下图所示 图 38简单的碰撞检测原理图 TravelManipulator.dll中用到的就是如图所示的原理黑三角形代表没有移动之的位置控制移动的函数是ChangePosition(osg::Vec3delta)参数意思是要移动的相对于当前点的增量在黑三角形没有移动时该函数在计算时先假设一点newPosition为移动后的点而后通过连接这两个点而后通过判断与场景的模型是否有交点来判定这个移动可不可以执行如图所示两者之间有个大盒子是穿不过去的所以只有保持在原地。就算没有这个盒子移动后的新点又与地面在某种程序上有一个交点这证明移动是不可行的。这可以防止用户穿过地板到达地下去。 1.6.3 路径漫游 使用path文件的方法如下面示例 1 #includeosgDB/ReadFile2 3 #includeosgViewer/Viewer4 5 #includeosg/Node6 7 #includeosgGA/AnimationPathManipulator8 9 void main()
10
11 {
12
13 osgViewer::Viewer viewer;
14
15 viewer.setSceneData(osgDB::readNodeFile(glider.osg));
16
17 //申请一个操作器参数为一个path文件。
18
19 osg::ref_ptrosgGA::AnimationPathManipulatorampnew osgGA::AnimationPathManipulator(glider.path);
20
21 //选择使用这个操作器。
22
23 viewer.setCameraManipulator(amp.get());
24
25 viewer.realize();
26
27 viewer.run();
28
29 }
30
31 我们可以用路径编辑器编辑path文件或者可以控制程序中的某个物体的运动轨迹然后保存为path文件。 1.7 更新回调 回调的意思就是说你可以规定在某件事情发生时启动一个函数这个函数可能做一些事情。这个函数就叫做回调函数我们可以使用已有回调函数或者自定义回调函数。 Ø 使用已有回调 已有的回调的类型有很多种一般很容易就想到的是UpdateCallBack或者EventCallBack等 Ø 自定义回调 自定义回调为从一个回调类型派出生自己的回调然后具有该种回调的特点等等。 NodeVisitor是一个极有用的类可以访问结点序列使用的方法大同小异NodeVisitor的工作流程如下图所示 图 39NodeVisitor工作流程 在主结点accept之后结点数据立即传至NodeVisitor中去应用apply函数可以将数据定任一些操作更多的操作还是需要硬性的制做与调用。 1.8 粒子系统初步 在OSG中提供有专门的粒子系统工具名字空间为osgParticleOSG对经常使用的粒子模拟都做了专门的类如ExplosionEffect用于暴炸的模拟FireEffect用于火的模拟ExplosionDebrisEffect用于爆炸后四散的颗粒模拟等等。 在OSG中使用粒子系统一般要经历以下几个步骤 第一步确定意图包括粒子的运动方式等等诸多方面。第二步建立粒子模版按所需要的类型确定粒子的角度该角度一经确定由于粒子默认使用有Billboard所以站在任何角度看都是一样的形状圆形多边形等等生命周期等。第三步建立粒子系统设置总的属性第四步设置发射器发射器形状发射粒子的数目变化等第五步设置操作旋转度风力等等因素。第六步加入结点更新。下图描述了各个部分是协调工作的方式 图 310粒子系统各个部分是协调工作的方式 上图中各个部分所对应的类如下图所示 图 311粒子系统各部分对应的类 1.9 视口LODImposter 1.9.1 多视口 多视口的原理是自己创建所有的相机包括主相机这样我们可以随意的添加相机。 首先我们要创建视口必须有以下几件东西第一了解整个屏幕的分辩率有多大这样可以分辩视口的大小好分割开来。第二上下文。我们必须自己手动的打开设置上下文。每个视口的数据也不一定非要与主视口的相同。但是矩阵一般是同步的。也就是说主视口里有栋楼从视口里可以是平面图什么的。了解整个屏幕的分辩率可以用这个类osg::GraphicsContext::WindowingSystemInterface意思是说系统接口可以获得当前环境的各种信息。有一方法叫getScreenResolution可以得到分辩率。之后上下文了osg::GraphicsContext里面可以设置窗口大小缓存什么的大多数的东西都在这里面设置。 1.9.2 LOD LOD即level of details LOD比起PagedLOD而言并非十分的常用有个地方用的特别多那就是把一个好好的模型加一个视矩压成一个模型这个模型比以前的看来就是多了个视矩的控制远了看不见近了能看见。 在模型中加LOD头结点的方式如下所示 1 #includeosgDB/Registry2 3 #includeosgDB/ReadFile4 5 #includeosgDB/ReaderWri ter6 7 #includeosgDB/Wri teFile8 9 #includeosg/Node
10
11 #includeosgViewer/Viewer
12
13 int main()
14
15 {
16
17 osgViewer::Viewer viewer;
18
19 //读取模型
20
21 osg::Node*nodeosgDB::readNodeFile(fountain.osg);
22
23 //隐藏结点
24
25 node-asGroup()-getChild(0)-setNodeMask(0);
26
27 viewer.setSceneDa ta(node);
28
29 //输出结点到free.os g中
30
31 osgDB::writeNodeFile(*(viewer.getSceneData()),free.osg,osgDB::Registry
32
33 ::instance()-getOptions());
34
35 return 0;
36
37 }
38
39 1.9.3 Imposter 用动态图片来替换场景的实用技术imposter.可以把它法做LOD一样使用只不过它 不是变模型变没有而是使它换成一张图 示例代码如下设置一个视矩超过这个视距模型会变为一张动态图 1 #includeosgViewer/Viewer2 3 #includeosgGA/TrackballManipulator4 5 #includeosgSim/Impostor6 7 #includeosgDB/ReadFile8 9 int main(inta rgc,cha r**a rgv)
10
11 {
12
13 //申请viewer
14
15 osgViewer::Viewer viewer;
16
17 //读取模型
18
19 osg::Node*nodeosgDB::readNodeFile(ceep.ive);
20
21 //申请一个i mpos tor结点
22
23 osgSim::Impos tor*simnew osgSim::Impostor;
24
25 //在到之内显示模型之外显示贴图
26
27 sim-addChild(node,0,50000);
28
29 sim-setImpostorThreshold(1000);
30
31 osg::Group*rootnew osg::Group;
32
33 root-addChild(sim);
34
35 viewer.setSceneData(root);
36
37 viewer.realize();
38
39 viewer.run();
40
41 return 0;
42
43 }
44
45 1.10 文字模型阴影 1.10.1 HUD HUD即head up display 文字在3D场景中显示往往要经历以下几步读取字体点阵信息-转化为图像-反走样-最终图像。在反走样期间可以处理可种模糊效果在最终图像形成时可以设置如何摆放。OSG中有一个TEXT类提供可很多文字显示的方法比如 void setFont(Font*font0)//设置/得到字体如setFont(fonts/SIMYOU.TTF);void setFont(const std::stringfontfile)const Font*getFont()const//设置/得到字体显示的宽高void setFontResolution(unsigned int width,unsigned int height)unsigned int getFontWidth()constunsigned int getFontHeight()const//设置/得到文字的具体内容 等等可以很方便的调用 1.10.2 阴影 OSG对阴影的支持也相当的好可以很容易的写出简单的阴影效果可以参考例子osgShadow OSG有一个专门的shadow类来支持阴影效果提供了很多接口如 1 void setBackdropType(BackdropType type)2 3 //说明设置阴影类型。4 5 void setBackdropOffset(float offset0.07f)6 7 void setBackdropOffset(float horizontal,float vertical)8 9 //说明设置阴影的离开程度与方向
10
11 void setBackdropColor(const osg::Vec4color)
12
13 //说明设置阴影颜色
14
15 void setColorGradientMode(ColorGradientMode mode)
16
17 //说明设置颜色映射方式可以得到渐变效果
18 等等 PS本文为几个月前整理参考书FreeSouth的《QpenSceneGraph程序设计》