1. 简介
osgLight示例演示了osg如何使用光照。
2. 描述
首先看一下osgLight的运行效果图,如下图所示:
3. 创建立方体
3.1 绘制一个面
首先是创建场景中的立方体,在创建中需要创建立方体的每一个面,实现在函数 createWall中,createWall的函数原型如下
osg::Geometry* createWall(const osg::Vec3& v1,const osg::Vec3& v2,const osg::Vec3& v3,osg::StateSet* stateset)
调用它的代码如下所示:
// create front side.
geode->addDrawable(createWall(bb.corner(0),
bb.corner(4),
bb.corner(1),
wall));
可以知道它是通过某个节点的包围盒来创建的,那么包围盒的corner分别是哪些点呢?也就是说这里的corner(0)到corner(7)分别指的是包围盒的哪些。查看源码可以得知:
//BoundingBox源码
inline const vec_type corner(unsigned int pos) const
{
return vec_type(pos&1?_max.x():_min.x(),pos&2?_max.y():_min.y(),pos&4?_max.z():_min.z());
}
也就是说corner(0)到corner(7)遵循x、y、z从小到大排列,同时x优先y优先z的顺序,具体来说corner(0-7)如下图所示:
通过上图以及createWall被调用的情况(前面是 0、4、1),我们可以知道该函数 createWall中的前三个参数 v1、v2、v3分别指的是一个四边形的 左下点、左上点和右下点。(如果这点不清除那么代码阅读起来会有很大的障碍)通过阅读createWall的代码可以知道,该函数主要是用来绘制立方体的一个面,这个面被细分成100x100个四边形小方格来绘制。
3.2 绘制立方体
通过6个面的绘制组装成一个立方体,立方体绘制的代码如下:
osg::StateSet* rootStateSet = new osg::StateSet;
root->setStateSet(rootStateSet);
osg::StateSet* wall = new osg::StateSet;
wall->setMode(GL_CULL_FACE,osg::StateAttribute::ON);
osg::StateSet* floor = new osg::StateSet;
floor->setMode(GL_CULL_FACE,osg::StateAttribute::ON);
osg::StateSet* roof = new osg::StateSet;
roof->setMode(GL_CULL_FACE,osg::StateAttribute::ON);
osg::Geode* geode = new osg::Geode;
// create front side.
geode->addDrawable(createWall(bb.corner(0),
bb.corner(4),
bb.corner(1),
wall));
// right side
geode->addDrawable(createWall(bb.corner(1),
bb.corner(5),
bb.corner(3),
wall));
// left side
geode->addDrawable(createWall(bb.corner(2),
bb.corner(6),
bb.corner(0),
wall));
// back side
geode->addDrawable(createWall(bb.corner(3),
bb.corner(7),
bb.corner(2),
wall));
// floor
geode->addDrawable(createWall(bb.corner(0),
bb.corner(1),
bb.corner(2),
floor));
// roof
geode->addDrawable(createWall(bb.corner(6),
bb.corner(7),
bb.corner(4),
roof));
绘制六个面(设置了面的拣选,让背面被剔除掉),这些面绘制顺序是让背面朝向外,设置之后可以看到立方体内部。
3.3 绘制光源
场景中的光源绘制是在createLights函数中完成的,这个函数绘制了两个光源,一个光源固定在立方体的左上角(corner(4))的位置,另一个光源是一个移动的光源,移动的路径是立方体的8个角点组成的。
3.4 模型动画
osgLight中添加了一个模型,通过该模型计算出一个包围盒,模型中添加了动画效果,主要是通过ModelTransformCallback实现的。实现如下:
class ModelTransformCallback : public osg::NodeCallback
{
public:
ModelTransformCallback(const osg::BoundingSphere& bs)
{
_firstTime = 0.0;
_period = 4.0f;
_range = bs.radius()*0.5f;
}
virtual void operator()(osg::Node* node, osg::NodeVisitor* nv)
{
osg::PositionAttitudeTransform* pat = dynamic_cast<osg::PositionAttitudeTransform*>(node);
const osg::FrameStamp* frameStamp = nv->getFrameStamp();
if (pat && frameStamp)
{
if (_firstTime==0.0)
{
_firstTime = frameStamp->getSimulationTime();
}
double phase = (frameStamp->getSimulationTime()-_firstTime)/_period;
phase -= floor(phase);
phase *= (2.0 * osg::PI);
osg::Quat rotation;
rotation.makeRotate(phase,1.0f,1.0f,1.0f);
pat->setAttitude(rotation);
pat->setPosition(osg::Vec3(0.0f,0.0f,sin(phase))*_range);
}
// must traverse the Node's subgraph
traverse(node,nv);
}
double _firstTime;
double _period;
double _range;
};
主要是修改位置姿态节点osg::PositionAttitudeTransform类,该类与osg::MatrixTransform类似,主要区别在于osg::PositionAttitudeTransform比较直观,通过设置缩放、平移量和旋转实现。它的矩阵 Matrix = SRT(顺序是缩放、旋转、平移),在NodeCallback中一直去修改平移和旋转实现动画的效果。
osg::MatrixTransform和osg::PositionAttitudeTransform的几点区别:
1. osg::MatrixTransform按照我们给的顺序组合各种变换,比如我们先定义平移、在定义旋转和缩放,那么MatrixTransform的矩阵是
M = TRS,也就是所它会严格按照我们指定的顺序执行操作。指定的顺序不同时产生的结果差异巨大。
2. osg::PositionAttitudeTransform同时几个接口来获得平移、旋转和缩放矩阵,分别是:
1.setPosition指定平移量
2.setAttitude指定旋转量
3.setScale指定缩放量
不管我们设置的顺序如何,它的结果矩阵M=SRT(先缩放再旋转最后平移)总是按固定的顺序进行。除此之外osg::PositionAttitudeTransform还可以设置旋转和缩放的轴心位置(一般来说旋转和缩放是以原点为轴心进行的),但是PositionAttitudeTransform提供了让我们以其他位置为轴心进行旋转和缩放。
- 如果我们想用MatrixTranform实现与PositionAttitudeTransform同样的效果,那么就必须设置三个矩阵相乘的顺序是SRT。如果PositionAttitudeTransform设置了轴心位置,那么在作平移和旋转变换的时候还需要注意应该先平移到轴心位置,再旋转(和缩放),再平移回去。