以下内容来自:
1、《OpenSceneGraph三维渲染引擎编程指南》肖鹏 刘更代 徐明亮 清华大学出版社
2、《OpenSceneGraph三维渲染引擎设计与实践》王锐 钱学雷 清华大学出版社
3、自己的总结
OSG中类的继承关系等见OSG学习:OSG组成(二)——场景树。
创建C++项目后,首先需要配置OSG环境,具体步骤看OSG学习:WIN10系统下OSG+VS2017编译及运行第六步:新建OSG项目测试。
绘制一个人字顶的简易房屋:
// stdafx.h
#include <osg/Geode>
#include <osg/Geometry>
#include <osgViewer/Viewer>
#include <osgDB/ReadFile>
//osgUtil工具类库,提供通用的共用类,用于操作场景图形及内容,如更新、裁剪、遍历、数据统计及场景图的一些优化。包括Delaunay三角面绘制功能、法线生成功能等。
//SmoothingVisitor 生成法线
#include <osgUtil/SmoothingVisitor>
#include <osg/Texture2D>
//.cpp
#include "stdafx.h"
/*创建房屋墙体部分
由于房屋为人字顶,因此由10个顶点组成,每个顶点都有对应的法线和纹理坐标,以便正确地实现光照和纹理贴图效果
使用QUAD_STRIP的方式将顶点连接为四边形条带图元
*/
osg::Drawable *createHouseWall()
{
//创建顶点数组,逆时针添加
osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
//添加数据
vertices->push_back(osg::Vec3(0.0, 0.0, 4.0)); //0
vertices->push_back(osg::Vec3(0.0, 0.0, 0.0)); //1
vertices->push_back(osg::Vec3(6.0, 0.0, 4.0)); //2
vertices->push_back(osg::Vec3(6.0, 0.0, 0.0)); //3
vertices->push_back(osg::Vec3(6.0, 4.0, 4.0)); //4
vertices->push_back(osg::Vec3(6.0, 4.0, 0.0)); //5
vertices->push_back(osg::Vec3(0.0, 4.0, 4.0)); //6
vertices->push_back(osg::Vec3(0.0, 4.0, 0.0)); //7
vertices->push_back(osg::Vec3(0.0, 0.0, 4.0)); //8
vertices->push_back(osg::Vec3(0.0, 0.0, 0.0)); //9
//创建顶点法线数组
osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array(10);
//添加数据
(*normals)[0].set(osg::Vec3(-0.707, -0.707, 0.0)); //0
(*normals)[1].set(osg::Vec3(-0.707, -0.707, 0.0)); //1
(*normals)[2].set(osg::Vec3(0.707, -0.707, 0.0)); //2
(*normals)[3].set(osg::Vec3(0.707, -0.707, 0.0)); //3
(*normals)[4].set(osg::Vec3(0.707, 0.707, 0.0)); //4
(*normals)[5].set(osg::Vec3(0.707, 0.707, 0.0)); //5
(*normals)[6].set(osg::Vec3(-0.707, 0.707, 0.0)); //6
(*normals)[7].set(osg::Vec3(-0.707, 0.707, 0.0)); //7
(*normals)[8].set(osg::Vec3(-0.707, -0.707, 0.0)); //8
(*normals)[9].set(osg::Vec3(-0.707, -0.707, 0.0)); //9
//或者按下面方式添加数据 set重载
//(*normals)[0].set(-0.707, -0.707, 0.0); //0
//(*normals)[1].set(-0.707, -0.707, 0.0); //1
//(*normals)[2].set(0.707, -0.707, 0.0); //2
//(*normals)[3].set(0.707, -0.707, 0.0); //3
//(*normals)[4].set(0.707, 0.707, 0.0); //4
//(*normals)[5].set(0.707, 0.707, 0.0); //5
//(*normals)[6].set(-0.707, 0.707, 0.0); //6
//(*normals)[7].set(-0.707, 0.707, 0.0); //7
//(*normals)[8].set(-0.707, -0.707, 0.0); //8
//(*normals)[9].set(-0.707, -0.707, 0.0); //9
//或者按下面的方式进行定义并添加数据 动态数组
//osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array;
添加数据
//normals->push_back(osg::Vec3(-0.707, -0.707, 0.0)); //0
//normals->push_back(osg::Vec3(-0.707, -0.707, 0.0)); //1
//normals->push_back(osg::Vec3(0.707, -0.707, 0.0)); //2
//normals->push_back(osg::Vec3(0.707, -0.707, 0.0)); //3
//normals->push_back(osg::Vec3(0.707, 0.707, 0.0)); //4
//normals->push_back(osg::Vec3(0.707, 0.707, 0.0)); //5
//normals->push_back(osg::Vec3(-0.707, 0.707, 0.0)); //6
//normals->push_back(osg::Vec3(-0.707, 0.707, 0.0)); //7
//normals->push_back(osg::Vec3(-0.707, -0.707, 0.0)); //8
//normals->push_back(osg::Vec3(-0.707, -0.707, 0.0)); //9
//创建纹理坐标 也可以用和上面相同的方式定义
osg::ref_ptr<osg::Vec2Array> texcoords = new osg::Vec2Array(10);
//添加数据
(*texcoords)[0].set(0.0, 1.0);
(*texcoords)[1].set(0.0, 0.0);
(*texcoords)[2].set(0.3, 1.0);
(*texcoords)[3].set(0.3, .0);
(*texcoords)[4].set(0.5, 1.0);
(*texcoords)[5].set(0.5, 0.0);
(*texcoords)[6].set(0.8, 1.0);
(*texcoords)[7].set(0.8, 0.0);
(*texcoords)[8].set(1.0, 1.0);
(*texcoords)[9].set(1.0, 0.0);
//创建一个几何对象
osg::ref_ptr<osg::Geometry> houseWall = new osg::Geometry;
//设置顶点数据、纹理坐标、法线数组
houseWall->setVertexArray(vertices.get());
houseWall->setTexCoordArray(0, texcoords.get());
houseWall->setNormalArray(normals.get());
//设置法线的绑定方式为每个属性与一个图元组相绑定,该方法自动设置使用glBegin()/glEnd()的慢速通道进行绘制
houseWall->setNormalBinding(osg::Geometry::BIND_PER_VERTEX);
//添加图元,多段四边形条带,即一系列四边形
houseWall->addPrimitiveSet(new osg::DrawArrays(osg::DrawArrays::QUAD_STRIP, 0, 10));
//设置纹理贴图
//C++项目的当前目录为vcproj工程文件目录
houseWall->getOrCreateStateSet()->setTextureAttributeAndModes(0, new osg::Texture2D(osgDB::readImageFile("../wall.bmp")));
return houseWall.release();
}
/*创建人字顶部分
人字顶由6个顶点组成
使用颜色数组替代纹理,表达顶面的绘制效果
*/
osg::Drawable *createHouseRoof()
{
//创建顶点数组,逆时针添加
osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
//添加数据
vertices->push_back(osg::Vec3(-0.2, -0.5, 3.5)); //0
vertices->push_back(osg::Vec3(6.2, -0.5, 3.5)); //1
vertices->push_back(osg::Vec3(0.8, 2.0, 6.0)); //2
vertices->push_back(osg::Vec3(5.2, 2.0, 6.0)); //3
vertices->push_back(osg::Vec3(-0.2, 4.5, 3.5)); //4
vertices->push_back(osg::Vec3(6.2, 4.5, 3.5)); //5
//绘图基元为多段四边形条带
osg::ref_ptr<osg::DrawArrays> roof = new osg::DrawArrays(osg::DrawArrays::QUAD_STRIP, 0, 6);
//绘图基元为三角形
osg::ref_ptr<osg::DrawElementsUInt> roofSide = new osg::DrawElementsUInt(osg::DrawElementsUInt::TRIANGLES, 6);
(*roofSide)[0] = 0;
(*roofSide)[1] = 2;
(*roofSide)[2] = 4;
(*roofSide)[3] = 5;
(*roofSide)[4] = 3;
(*roofSide)[5] = 1;
//创建屋顶颜色数组
osg::ref_ptr<osg::Vec4Array> colors = new osg::Vec4Array;
//添加数据
colors->push_back(osg::Vec4(0.25, 0.0, 0.0, 1.0));
//创建一个几何体对象
osg::ref_ptr<osg::Geometry> houseRoof = new osg::Geometry;
//设置顶点数据、颜色数组
houseRoof->setVertexArray(vertices.get());
houseRoof->setColorArray(colors.get());
//设置颜色的绑定方式为一个属性与所有顶点绑定
houseRoof->setColorBinding(osg::Geometry::BIND_OVERALL);
//添加图元
houseRoof->addPrimitiveSet(roof.get());
houseRoof->addPrimitiveSet(roofSide.get());
//由于顶面的法线计算比较复杂,这里使用OSG自带的快速法线生成工具osgUtil::SmoothingVisitor
osgUtil::SmoothingVisitor smv;
smv.smooth(*houseRoof);
return houseRoof.release();
}
int main(int argc, char **argv)
{
//将房屋和墙体整合到一个叶节点中进行渲染
osg::ref_ptr<osg::Geode> geode = new osg::Geode;
geode->addDrawable(createHouseWall());
geode->addDrawable(createHouseRoof());
osgViewer::Viewer viewer;
viewer.setSceneData(geode.get());
return viewer.run();
}
运行程序,可得到图一的房屋,及图二的控制台界面显示:
对比上一个例子:几何图像的绘制——四边形,可以看到这个例子在结构和定义上都进行了一定的改变:
1)定义数组数据和添加数据的方法有两种:
动态数组:
osg::ref_ptr<osg::Vec3Array> vertices = new osg::Vec3Array;
静态数组:
osg::ref_ptr<osg::Vec3Array> normals = new osg::Vec3Array(10);
它们对应的添加数据的方法也不同:
动态数组添加方式有两种,set具有函数重载的属性:
(*normals)[0].set(osg::Vec3(-0.707, -0.707, 0.0));
(*normals)[0].set(-0.707, -0.707, 0.0);
静态数组的添加方式为:
normals->push_back(osg::Vec3(0.707, -0.707, 0.0));
对于动态数组与静态数组的优缺点,我不懂C,但结合我对C#的理解,我认为在C中:
对于已知长度,且长度不会改变的数据来说,创建静态数组更好,因为静态数组创建方便,引用简单,不需要释放;
而对于动态数组,虽然使用灵活,能根据需要动态分配大小,但创建麻烦,未避免内存泄漏必须由程序员自己释放(C#有自动回收机制CC,但并不是用完立即回收,而是它觉得该回收时才回收,因此建议也手动释放),如果是固定长度则没必要使用它。
2)在几何图像的绘制——四边形中,绘制几何体的步骤是:创建叶节点——创建几何体对象——创建顶点、添加数据、设置为几何体属性————创建其他相关数据并设置为几何体属性——在几何体上添加图元——将几何体添加到叶节点上;
而在这个几何图形中,绘制几何体的步骤是:创建顶点、添加数据——创建其他顶点并添加数据——创建几何体对象——把前面的数据依次设置为几何体属性——在几何体上添加图元;在总函数上创建叶节点——把几何体添加到叶节点上。
从上面两个步骤来看,第二个步骤要更符合结构组成一些,当然,到底使用什么步骤自己决定。