osg 入门知识点
osg第一天 – 认识osg
osg 坐标系
osg 坐标系运用的时 右手坐标系
osg坐标系系统分为三种:
- 世界坐标
世界坐标系为所有对象的位置提供一个绝对的参考标准;
世界坐标系也被广泛地成为全局坐标系或宇宙坐标系;
一般用于:绘制体、几何体- 局部坐标
物体坐标系是针对某一特定的物体而建立的独立坐标系;
每一个物体都有自己的坐标系;
物体坐标系对于描述特定的物体非常方便;
一般用于:顶点、法线等- 相机坐标
摄像机坐标系可以被看作是一种特殊的物体坐标系,该物体坐标系就定义在摄像机的屏幕可视区域。
osg点和向量
概念:
向量一般是指,一个带有方向和大小的量;
一般有二维向量、三维向量、三维向量等等, n维向量;
在osg中分别用 vec2、vec3、 vec4 来表示;
点:
在三维中,点一般用vec3(x, y, z) 来表示;
向量的基本接口功能(点乘、叉乘、求模)
点乘: n1·n2
点乘 又名 “ 内积 ” 和 “ 数量积 ”;
用法:n1^n2
公式: n1[0] * n2[0] + n1[1] * n2[1] + n1[2] * n2[2]
( n1、n2 代表三维向量,下标123 分别代表 osg坐标的 x y z )
作用:一般用来计算两个向量的夹角
叉乘: n1^n2
叉乘 又名 “ 向量积 ” 、“ 外积 ” 和 “ 叉积 ”;
公式:
x = ay * bz - az * by
y = -( ax*bz - az * bx )
z = ax * by - ay * bx
( a、b 代表三维向量,x y z 分别代表 osg坐标的 x y z )
作用:
在三维几何中,向量a和向量b的叉乘结果是一个向量,更为熟知的叫法是法向量,该向量垂直于a和b向量构成的平面。
在3D图像学中,叉乘的概念非常有用,可以通过两个向量的叉乘,生成第三个垂直于a,b的法向量,从而构建X、Y、Z坐标系。
在二维空间中,叉乘还有另外一个几何意义就是:aXb等于由向量a和向量b构成的平行四边形的面积。
求模:
公式: n[0] * n[0] + n[1] * n[1] + n[2] * n[2] 的平方根
( n代表三维向量)
作用:
求距离
osg矩阵
左乘和右乘
( 左乘 )点和向量在左边,向右乘
( 右乘 )点和向量在右边,向左乘
Matrix 采用了左乘操作
OpenGL 采用了右乘操作
矩阵变换中的级联顺序
矩阵变换顺序执行 SRT 原则
(一般先缩小和旋转,最后平移)
四元数
四元数是通过使用四个数来表达方位,其目的是避免旋转过程中的万向锁问题。所以在3D中,四元数的主要用途即是用于旋转。
四元数转矩阵
osg::Matrix mat;
osg::Quat qat;
qat.get(mat);
欧拉角转四元数
osg::Quat HPRToQuat(double heading, double pitch, double roll)
{
osg::Quat q(
roll, osg::Vec3d(0.0, 1.0, 0.0),
pitch, osg::Vec3d(1.0, 0.0, 0.0),
heading, osg::Vec3d(0.0, 0.0, 1.0)
);
return q;
}
osg第二天 – osg场景图
智能指针(osg::ref_ptr)
为 osg 执行内存管理
通过智能指针的方式让osg自己处理对象的销毁操作
get() 和 release() 的区别
get()
- 获取原始指针
release()
- 获取原始指针后,并释放智能指针
观察指针(osg::observer_ptr)
观察指针也属于智能指针;
osg::ref_ptr 是强指针类型
osg::observer_ptr 是弱指针类型
观察指针没有引用计数功能,仅仅记录该对象的地址
智能指针和观察指针的用处
在接收别人的指针时,用普通指针,避免智能指针给析构掉
如果接受的指针暂时不用,就用观察者指针接收
自己管理的指针一般用智能指针
常用的osg节点类
Referenced
通用的基类,所有节点均继承于Referenced, 它包含了一个整形的引用计数器;
Node
节点
Group
- 组节点,可以添加任意数量的子节点,子节点本身也可以继续分发子节点
- 根节点一般也用Group, 一个场景一般只有一个根节点
Geode
- 叶子节点,用来保存几何信息以便渲染
- geode不会再有叶子节点
- geode 是 “ geometry node ” 的简称
- Geode 提供了 addDrawable() 的方法用来关联程序中的几何信息
Drawable
- 绘制体,用于保存要渲染的数据;
- 无法直接实例化
Geometry
几何
- 指定顶点、法线和颜色
- 指定数据绘制的方式
- 将几何图形添加到叶子节点中
Transform
变换节点
osg 通过 osg::Transform 家族来实现几何数据的变换
MatrixTransform
矩阵变换节点
LOD
细节层次节点可以根据观察者的距离调用不同的子节点
AutoTransform
自动变换节点, 继承自Transform, 可以自动的调整大小和角度
void setAutoRotateMode(AutoRotateMode mode)
enum osg::AutoTransform::AutoRotateMode
Enumerator:
NO_ROTATION
ROTATE_TO_SCREEN // 自动朝向屏幕
ROTATE_TO_CAMERA // 自动朝向摄像机
PositionAttitudeTransform
位置变换节点, 继承自Transform,
主要作用是提供 模型的位置变换、大小缩放、原点位置的设置以及坐标系的变化
主要函数:
- void setPosition(const Vec3d &pos); // 设置位置
- const Vec3d &getPosition() const; // 得到位置
- void setAttitude(const Quat &quat) // 设置姿态
- const Quat &getAttitude() const // 得到姿态
Switch
开关节点,可以渲染或跳过指定的子节点
osg第三天 – 图元
顶点属性
- 几何体是由多个点组成的
- 顶点是属性定义每个点的信息:
– 顶点坐标
– 顶点颜色
– 顶点法线
– 纹理坐标
(其中, 顶点坐标是必须要的, 其他属性可选)
对于颜色、法线等,提供了绑定方式。 包括;
1、 绑定每个顶点( BING_PER_VERTEX )
2、 绑定图元组 ( BING_PER_PRIMITIVE_SET )
3、 绑定所有 ( BING_OVERALL )
图元类型
osg::PrimitiveSet 类, 该类主要松散封装了OpenGL的绘制基元;
类型 | 功能 | 功能描述 | 连线顺序 |
---|---|---|---|
POINTS | 绘制点 | 绘制用户指定的所有顶点 | 0、1、2、3、4、5 |
LINES | 绘制直线 | 直线的起点、终点由数组中先后相邻的两个点决定,用户提供的点不止两个时,将尝试继续绘制新的直线。 | 01、23、45 |
LINE_STRIP | 绘制多段直线 | 多段直线的第一段由数组的前两个点决定,其余段的起点位置为上一段的终点坐标,而终点位置由数组中随后的点决定。 | 012345 |
LINE_LOOP | 绘制封闭直线 | 绘制方式与多端直线相同,但是最后将自动封闭该直线。 | 0123450 |
TRIANGLES | 绘制三角形 | 三角形的三个顶点由数组相邻的三个点决定;用户提供的点不止三个时,将尝试继续绘制新的三角形。( 方向为逆时针时,朝向屏幕面是正面 ) | 012、345 |
TRIANGLE_STRIP | 绘制多段三角形 | 第一段三角形的由数组的前三个点决定; 其余段的三角形绘制,起始边由上一段三角形的后两个点决定,第三点由数组中随后的一点决定。 | 012、213、234、435、456 |
TRIANGLE_FAN | 绘制三角形扇面 | 第一段三角形的由数组中的前三个点决定;其余段三角形的绘制, 起始边由整个数组的第一点和上一段三角形的最后一个点决定,第三点由数组中随后的一点决定。 | 012、023、034、045、056 |
QUADS | 绘制四边形 | 四边形的四个顶点由数组中相邻的四个顶点决定,并按照逆时针的顺序进行绘制;用户提供的点不止四个时,将尝试继续绘制新的四边形。 | |
QUAD_STRIP | 绘制多段四边形 | 第一段四边形的起始边由数组中的前两个点决定,边的矢量方向由这两点的延伸方向决定;起始边的对边由其后的两个点决定,如果起始边和对边的矢量方向不同,那么四边形将会扭曲;其余段四边形的绘制,起始边由上一段决定,其对边由随后的两点及其延伸方向决定。 | |
POLYGON | 绘制任意多边形 | 根据用户提供的顶点数量,绘制多边形。 | 0123450 |
图元组
作用:
图元组用来告诉OSG, 在绘制图形时,使用哪些顶点、使用哪个图元对顶点进行组合。
图元组分两种:
- DrawArrays
DrawArrays 直接告诉 OSG 使用的顶点在顶点数组中的起点、终点、使用的图元属性,该方式效率高,
但对于复杂的模型需要传入较多的点时比较麻烦。- DrawElements
DrawElements 告诉 OSG 使用顶点的索引值数组和图元,该方式不需要传入重复的点,使用简单,
但由于内部需要复制数据,因此效率不如第一种。
在osg中,有三种生成几何体的手段:
- 松散封装的OpenGLh绘制基元osg::Geometry
- 使用OSG中预定义的基本几何体
- 从文件中导入场景模型
osg第四天 – 图元纹理、属性
StateSet 状态集
为什么引用状态集?
由于 OpenGL 状态集的存在,需要手动保存原始状态,在绘制完恢复原始状态,给使用者带来较大困难,OSG 为了解决这个问题,引用了 OSG 状态集( osg::StateSet )。
了解状态集
状态集中保存了用户修改的状态信息,在退出时自动恢复状态
状态集提供了两个接口,设置状态属性或状态
- setAttributeAndModes()
- setMode()
osg 的状态较多,例如 Txxture2D、Point、LineWidth 等都为一种属性。
纹理贴图
纹理概念:
- 纹理 ( Texture ) 是一种状态属性,其特点与以他状态相同;
- 纹理的内部时数组的概念,保存了图像的像素值,可贴在几何体上,使几何体更真实;
- 其中,二维纹理 ( Texture2D ) 是最常用的纹理;
二维纹理:
- 对于二维纹理,在内部其横纵坐标被归一化为 ( 0~1 ) ;
- 为了区别于世界空间的坐标系,其坐标不使用 x 、y 表示, 而是 s 、t 。
纹理坐标:
- 纹理坐标是一种顶点属性,表示该顶点使用纹理的那个像素值
- 纹理贴图必须设置纹理坐标,否则无法贴图
纹理环绕模式:
纹理环绕模式为了解决纹理坐标超过纹理范围时该怎样获取像素值的情况,规定几种模式:
- CLAMP ----------// 截取
- CLAMP_TO_EDGE ---------- // 边框始终被忽略
- CLAMP_TO_BORDER ---------- // 它使用的纹理取自图像的边框,没有边框就使用常量边框颜色
- REPEAT -------- // 纹理重复映射
- MIRROR -------- // 纹理镜像的重复映射
纹理环绕模式通过setWrap设置,第一个参数表示纹理坐标轴STR,第二个参数为选择的环绕模式
纹理的过滤方法:
- MIN_FILTER -------- // 用于缩小
- MAG_FILTER --------- // 用于放大
纹理采样:
- 在几何体显示在屏幕上时,显示的像素与纹理的像素位置不能完全对应, 总会有些差值;
- OpenGL 内部通过纹理采样来获取纹理像素值;
多重纹理:
- 为了展现几何体真实效果,有时需要多个纹理; 例如:墙壁的污渍纹理
- 几何体可以有多重纹理,内部通过纹理单元来进行切换
- 固定线管下,OpenGL 支持 8 个纹理单元
- 当我们需要向某个纹理单元设置纹理时,需要设置该纹理单元对应的纹理坐标。
osg第五天 – 时间响应和碰撞检测
OSG 中主要使用 osgGA 库来处理用户的交互动作,GA 的全称是 GUI Abstraction, 即图形接口抽象层;
事件适配器 GUIEventAdapter
- 在用户端,通常使用 GUIEventAdapter 类作为系统交互事件和 OSG 交互时间的适配接口,
- 它定义了一个且仅有一个人机交互事件,
- 基本包括了常见窗口系统中的鼠标、键盘、甚至触摸板的操作。
动作适配器 GUIActionAdapter
- 用户向系统传递请求通过 GUIActionAdapter 类来实现。
- 常见的用户请求包括窗口的刷新以及鼠标坐标的位置。
碰撞检测实战(类似于点击事件)
- 继承osgGA::GUIEventHandler
- 重写handle 方法
- 调用
// CPickHandle.h
class CPickHandle :public osgGA::GUIEventHandler
{
// 重写handle, 参数为 事件适配器 和 动作适配器
bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa);
// 拾取
void pick(osg::ref_ptr<<osgViewer::View> view, float x, float y);
}
// CPickHandle.cpp
// 事件处理函数
bool CPickHandle::handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa)
{
// 把动作适配器转化为 view
osg::ref_ptr<osgViewer::View> view = dynamic_cast<osgViewer::View*> (&aa);
if(!view)
{
return false;
}
switch(ea.getEventType())
{
// 鼠标按下
case(osgGA::GUIEventAdapter::PUSH):
{
m_fx = ea.getX();
m_fy = ea.getY();
break;
}
// 鼠标释放
case(osgGA::GUIEventAdapter::RELEASE):
{
if(m_fx == ea.getX() && m_fy = ea.getY())
{
pick(view.get(), ea.getX(), ea.getY());
}
break;
}
default:
break;
}
}
// 拾取
void CPickHandle::pick(osg::ref_ptr<osgViewer::View> view, float x, float y)
{
// 线段相交检测平台
osgUtil::LineSegmentIntersector::Intersections intersection;
if(view->computeIntersections(x, y, intersection))
{
osgUtil::LineSegmentIntersector::Intersections::iterator it = intersection.begin();
// 操作节点
......
......
}
}
osg第六天 – 遍历
NodeVisitor 类
- NodeVisitor 类是 OSG 对于访问器设计思想的具体实现。
- 从本质上说,NodeVisitor 类遍历了一个场景图形并为每一个被访问节点调用特定的函数
- NodeVisitor 在 OSG 的应用中无处不在
osg遍历实例
osg遍历的使用实例: 遍历显示节点信息
1、继承 osg::NodeVisitor
2、重载 apply() 函数
3、调用函数 accept() 方法
// CFindNode.h
// 继承osg::NodeVisitor
class CFindNode :public osg::NodeVisitor
{
public:
CFindNode();
virtual void apply(osg::Node& node);
osg::Node* getNode();
protected:
osg::ref_ptr<osg::Node> _node;
}
// CFindNode.cpp
CFindNode::CFindNode() :osg::NodeVisitor(osg::NodeVisitor::TRAVERSE_ALL_CHILDREN)
{
}
// 重载 apply() 函数
void CFindNode::apply(osg::Node& node)
{
if(node.getName() == "Ball")
{
_node = &node;
}
traverse(node);
}
osg::Node* CFindNode::getNode()
{
return _node.get();
}
// main.cpp
osg::ref_ptr<osgViewer::Viewer> rpViewer = new osgViewer::Viewer;
CFindNode cfd;
// 调用函数 accept() 方法
osg::ref_ptr<osg::Group> group = new osg::Group();
group->accept(cfd);
osg第七天 – 回调
使用回调实现地球的公转自转效果
osg回调的使用实例: ----使用回调实现 地球的公转和自转
- 继承 NodeCallBack 类
- 重写操作符 NodeCallback::operator()
- 通过 setXXXCallback 或 addXXXCallback 注册到节点对象
// CPlanetRotate.h
// 继承 NodeCallBack 类
class CPlanetRotate :public osg::NodeCallBack
{
public:
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv);
}
// CPlanetRotate.cpp
void CPlanetRotate::operator(osg::Node* node, osg::NodeVisitor* nv)
{
// 转为矩阵变换节点
if(osg::ref_ptr<osg::MatrixTransform> rpMt = dynamic_cast<osg::MatrixTransform*>(node))
{
if(rpMt.valid())
{
osg::Matrix mtRotate,MT,MR;
MR.makeRotate(m_dAngle, osg::Z_AXIS); // 自转
MT.makeTranslate(osg::Vec3(getRevolutionRadius(), 0, 0)); // 位移
mtRotate.makeRotate(m_dAngle, osg::Z_AXIS); // 公转
rpMt.setMatrix(MR * MT * mtRotate); // 设置矩阵 (括号里的顺序不可改变)
m_dAngle += 0.01;
}
}
traverse(node, nv); // 继续遍历
}
// main.cpp
m_pCPlanetRotate = new CPlanetRotate(); // 创建对象
trans->setUpdateCallBack(m_pCPlanetRotate); // 调用回调
使用粒子效果绘制行星尾迹
实现粒子效果
// Star.cpp
osg::ref<osg::Geode> Star::buildTail(osg::Vec3 position, osg::MatrixTransform* scalar)
{
// 参数为位置和大小
osg::ref_ptr<osgParticle::FireEffect> fire = new osgParticle::FireEffect(position,10);
fire->setUserLocalParticleSystem(false);
// 设置粒子喷射时间为--无限
fire->getEmitter()->setEnabless(true);
fire->getEmitter()->setLifeTime(1);
scalar->addChild(fire);
osg::ref_ptr<osg::Geode> geode = new osg::Geode;
geode->addDrawable(fire->getParticleSystem());
return geode;
}
添加粒子
// main.cpp
osg::ref_ptr<osg::Geode> tail = bulidTail(osg::Vec3(0,0,0), trans);
group->addChild(tail);
osg第八天 – 相机及其应用
HUD相机
HUD相机 全称为 head up display : 意思为 “ 头显示 ”
( 用于常显在屏幕坐标系的固定位置来展示信息)
HUD相机实例
osg HUD相机使用实例:
- 创建HUD相机
- 把相机添加到视图
// CHUDProvider.h
class CHUDProvider
{
public:
osg::Camera* createHUD(); // 创建HUD相机
};
// CHUDProvider.cpp
osg::Camera* CHUDProvider::createHUD()
{
osg::Camera* pCamera = new osg::Camera;
// 设置投影矩阵内容
pCamera->setProjectionMatrix(osg::Matrix::ortho2D(0, 1580, 0, 1024));
// 设置视图矩阵内容
pCamera->setReferenceFrame(osg::Transform::ABSOLUTE_RF);
pCamera->setViewMatrix(osg::Matrix:identity());
// 清理深度缓冲
pCamera->setClearMask(GL_DEPTH_BUFFER_BIT);
// 设置相机的渲染顺序
pCamera->setRenderOrder(osg::Camera::POST_RENDER);
// 设置相机是否接受图形设备传递的人机交互事件
pCamera->setAllowEventFocus(false);
{
osg::Geode* pGeode = new osg::Geode();
osg::StateSet* pStateset = pGeode->getOrCreateStateSet();
pStateset->setMode(GL_LIGHTING, osg::StateAttribute::OFF);
std::string strTimeFont("fonts/arial.ttf");
float fFontSize = 25.0;
osg::Vec3 position(150.0f, 800.0f, 0.0f);
// 文字
{
osgText::Text* pText = new osgText::Text; // 新建文本字节
pGeode->addDrawable(pText); // 添加文字节点到几何节点中
pText->setFont(strTimeFont); // 设置字体
pText->setPosition(position); // 设置文字节点位置
pText->setText("显示文字内容"); // 设置文字内容
}
pCamera->addChild(pGeode);
}
return pCamera;
}
// main.cpp
CHUDProvider cHUDProvider = new CHUDPrivider(); // 创建HUD对象
osg::Camera* camera = cHUDProvider.createHUD(); // 创建HUD相机
group->addChild(camera); // 将HUD相机添加到节点中
注意
HUD显示文字时,需注意以下几点:
- 渲染顺序设置为POST,否则可能被其他图形所覆盖
- 注意关闭光照和深度
- 投影矩阵通常设置为屏幕尺寸大小
osg第九天 – 漫游
漫游的使用
1、继承 osg::CameraManipulator
2、重写四个虚函数 (其中两个set方法,实现可为空)
virtual void setByMatrix(const osg::Matrixd& matrix);
virtual void setByInverseMatrix(const osg::Matrixd& matrix);
virtual osg::Matrixd getMatrix() const;
virtual osg::Matrixd getInverMatrix() const;
3、重写事件处理
bool handle(const osgGA::GUIEventAdapter& ea, osgGA::GUIActionAdapter& aa);
4、调用
osg::ref_ptr<CDriveManupulator> rpDrive = new CDriverManupulator();
rpViewer->setCameraManipulator(rpDrive);