最近项目中需要利用osg重建三维曲面,所以学习了一下。
第一,我先用的狄洛尼三角形的方法,即osgUtil::DelaunayTriangulator,用这种方法的特点是:
1.首先必须给其一个存储三维点集的数组,该方法会对这些杂乱无章的散点自动排序,然后就利用这些排好序的,符合三角网构建规则的散点去构建三角网,需要注意的是经过dt->setInputPointArray(coords);这句话以后,数组coords的值的顺序已经发生改变,不再是原来的coords。
2.再给其贴纹理的时候,必须要首先设置一个颜色数组给它,需要注意的是,纹理的坐标都是0~1范围的,而且是二维的(x,y),所以必须将coords的坐标的x和y值一 一 映射到0~1的范围。
3.必须输出法向量,并用该法向量数组给对应的geometry赋值
具体代码如下:
//创建Delaunay三角网对象
osg::ref_ptr<osgUtil::DelaunayTriangulator> dt = new osgUtil::DelaunayTriangulator();
dt->setInputPointArray(coords);//赋给它三维点集数组
dt->setOutputNormalArray(normals);//输出法向量
//生成三角网
dt->triangulate();
osg::ref_ptr<osg::Geometry> geometry = new osg::Geometry();
//设置坐标
geometry->setVertexArray(coords.get());
//设置描述
geometry->addPrimitiveSet(dt->getTriangles());
//设置法线
geometry->setNormalArray(normals.get());
geometry->setNormalBinding(osg::Geometry::BIND_PER_PRIMITIVE);
//设置纹理坐标(纹理填充)
osg::ref_ptr<osg::Vec2Array> texCoords = ComputerTextureCoords(*(coords.get()));//得到一 一映射后的范围在0~1的二维纹理数组
geometry->setTexCoordArray(0,texCoords.get());
//尝试颜色填充
// osg::ref_ptr<osg::Vec4Array> vextexColorArray = ComputePerVertexColor(*(coords.get()),getOSGColorTable());
// geometry->setColorArray(vextexColorArray );
// geometry->setColorBinding(osg::Geometry::BIND_PER_VERTEX);
//准备纹理图像
//生成一副QImage,给其每个像素用事先定义好的颜色赋值。最后保存成png图像,保存方式就是image.save(strPath)参数是保存全文件名,包括路径和后缀。
QImage image(xCount,yCount,QImage::Format_Indexed8);
QVector<QRgb> colorTable = getColorTable();
image.setColorTable(colorTable);
InterpolateAndDrawImage(vecZs,&image,xCount,yCount,xCount,yCount);
QString strName = ::GetImagePath() + pContourData->GetName() +".png";
image.save(strName);//保存成png图像
//开始用png图像生成纹理
osg::ref_ptr<osg::Image> texImage = osgDB::readImageFile(strName.toStdString());
osg::ref_ptr<osg::Texture2D> tex = new osg::Texture2D;
tex->setImage(texImage.get());
tex->setDataVariance(osg::Object::DYNAMIC);
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();
stateset->setTextureAttributeAndModes(0,tex.get(),osg::StateAttribute::ON);
osg::ref_ptr<osg::Geode> geode = new osg::Geode();
geode->addDrawable(geometry.get());
geode->setStateSet(stateset.get());
//设置矩阵变换矩阵
m_pRootSwitch->addChild(geode);
这样利用三角面片重建三维曲面,并且给其贴纹理渲染的效果就已经出来了,但是需要注意的是如果点集是规则网格数据,这种方式的构建就不适合了,应该用四角面片osg::HeightField的方式。
二、四边形面片
这种方式构建的特点如下:
1必须给其分配一个行列号,即构建出的网格有多少行多少列,利用allocate(unsigned int rownum,unsigned int columnnum)函数来分配。
2.必须给其指定一个初始位置,用setOrigin((const osg::Vec3& origin)来指定,注意三维顶点数组的z轴设为0即可,点集合中最小的x、y即可。
3.必须给其指定x和y方向每行每列之间的间隔用setXInterval(float dx );setYInterval(float dy)来指定。
4.有了初始位置,行列号,和间隔,就可以算出四边形面片每个顶点的位置了,然后在每个位置上设置高程值即可,用setHeight(float height)
具体实现过程如下,纹理渲染过程与三角面片类似,只是少了一个步骤就是:不需要给其设置纹理坐标,跟简单容易。
osg::ref_ptr<osg::HeightField> pHeightField = new osg::HeightField();
pHeightField->allocate(xCount,yCount);
pHeightField->setOrigin(osg::Vec3(xMin,yMin,0));
pHeightField->setXInterval( xdelta );
pHeightField->setYInterval( ydelta );
float x,y;
for(int i = 0; i < yCount; i++)
{
for(int j = 0; j < xCount; j++)
{
x = xMin + j * xdelta;
y = yMin + i * ydelta;
double z = pInterpolater->GetInterpolatedZ(x,y,input.begin(),input.end());
vecZs.push_back(-z+zMin);
coords->push_back(GeoToGeoNormal(osg::Vec3f(x,y,0)));
pHeightField->setHeight(j,yCount-i-1,-z);//循环得到每个顶点,然后为其设置z值
}
}
///绘制纹理图像
QImage image(xCount,yCount,QImage::Format_Indexed8);
QVector<QRgb> colorTable = getColorTable();
image.setColorTable(colorTable);
InterpolateAndDrawImage(vecZs,&image,xCount,yCount,xCount,yCount);
QString strName = ::GetImagePath() + pContourData->GetName() +".png";
image.save(strName);
osg::ref_ptr<osg::Image> texImage = osgDB::readImageFile(strName.toStdString());
osg::ref_ptr<osg::Texture2D> tex = new osg::Texture2D;
tex->setImage(texImage.get());
tex->setDataVariance(osg::Object::DYNAMIC);
osg::ref_ptr<osg::StateSet> stateset = new osg::StateSet();
stateset->setTextureAttributeAndModes(0,tex.get(),osg::StateAttribute::ON);
osg::ref_ptr<osg::Geode> geode = new osg::Geode();
geode->addDrawable(new osg::ShapeDrawable(pHeightField.get()));
geode->setStateSet(stateset.get());
//附录:贴纹理用到的三个函数:
第一个将真实顶点坐标一 一映射到(0~1)的范围
osg::ref_ptr<osg::Vec2Array> COSG3DSurfaceNode::ComputerTextureCoords( const osg::Vec3Array & vp)
{
osg::ref_ptr<osg::Vec2Array> texCoords = new osg::Vec2Array();
int nSize = vp.size();
float maxX = vp[0].x();
float minX = maxX;
float maxY = vp[0].y();
float minY = maxY;
for (int i=0;i<nSize;i++)
{
maxX = maxX<vp[i].x()?vp[i].x():maxX;
minX = minX>vp[i].x()?vp[i].x():minX;
maxY = maxY<vp[i].y()?vp[i].y():maxY;
minY = minY>vp[i].y()?vp[i].y():minY;
}
for(int i = 0;i< nSize; i++)
{
float xValue = 1-(maxX-vp[i].x())/(maxX - minX);
//float yValue = 1-(maxY-vp[i].y())/(maxY - minY);
float yValue = 1-(vp[i].y()-minY)/(maxY - minY);
texCoords->push_back(osg::Vec2(xValue,yValue));
}
return texCoords;
}
第二,由颜色配置文件得到颜色数组
QVector<QRgb> COSG3DSurfaceNode::getColorTable()
{
QVector<QRgb> table;
QString colotpath = ::GetImagePath() + "colorbar.txt";
QFile file(colotpath);
if (!file.open(QIODevice::ReadOnly))
{
assert(false);
}
QTextStream WXStream(&file);
for (int i=0;i<32;i++)
{
float RColor(0.0),GColor(0.0),BColor(0.0),AColor(1.0);
osg::Vec4 Vcolor;
WXStream>>RColor>>GColor>>BColor;
table.push_back(qRgb(RColor*255,GColor*255,BColor*255));
}
return table;
}
第三、生成纹理图像,根据颜色配置文件对图像像素 一 一进行赋值
void COSG3DSurfaceNode::InterpolateAndDrawImage(const std::vector<float>& vecData,QImage* pImage,int xCount,int yCount,int imageSizeX,int imageSizeY)
{
assert(xCount > 0);
assert(yCount > 0);
assert(vecData.size() == xCount * yCount);
float min = vecData[0];
float max = vecData[0];
for(int i = 0; i < vecData.size() ; i++)
{
if(max < vecData[i]) max = vecData[i];
if(min > vecData[i]) min = vecData[i];
}
double colorFactor = getColorTable().size() / (max - min);
/// todo 图片插值
for(int i = 0; i < imageSizeX; i++)
{
for(int j = 0; j < imageSizeY; j++)
{
pImage->setPixel(i,j,(vecData[j*xCount + i] - min) * colorFactor );
}
}
pImage->save(::GetImagePath() + "123.png");
}
m_pRootSwitch->addChild(geode);
最终效果图: