最近参与一个智慧城市类的项目的开发,城市的数据通过专业设备扫描为osgb格式的数据,我负责将osgb数据加载并在ue里显示,此系列文章用于记录开发历程。
一:环境安装
下载链接:https://freesouth.blog.csdn.net/article/details/121093781 在网上找到了别人编译好的第三方库,而且这个网盘里还有比较基础的接入教程,介绍的比较详细了(注意按照视频里面的步骤配置环境变量),接下来我只介绍关键步骤。
在项目根目录下创建ThirdParty文件夹,创建OSG文件夹,将编译好的第三方库的dll,lib和头文件全都复制进来。
接下来在Build里引入dll
string OSGBPath = Path.Combine(ModuleDirectory, "../../ThirdParty/OSG");
string IncDir = Path.Combine(OSGBPath, "include");
string LibDir = Path.Combine(OSGBPath, "lib");
PublicIncludePaths.Add(IncDir);
foreach (string file in Directory.GetFiles(LibDir))
{
if(!file.Contains("pkgconfig"))
{
PublicAdditionalLibraries.Add(Path.Combine(LibDir, file));
}
}
之后将编译好的bin文件夹下面的文件全部复制到工程的Binary中去,这步必做,不复制虽然会过编译但是打开引擎加载到75%会跳出警告并闪退,我有尝试在build文件里去包含dll,但是不得行,会闪退(目前还不知原因); ok,到这一步,敲两行代码验证下是否接入成功:
osg::ref_ptr<osg::Node> node = osgDB::readNodeFile("D:\\OSGSetting\\OpenSceneGraph-Data\\glider.OSG");
如果你发现地址没给错,命令行读取osg文件没问题,但是node一直返回为空指针,那么你需要把项目的Binary的地址添加到Path变量里去。
二:读取OSG模型信息
参考文章:https://blog.csdn.net/qq_31709249/article/details/94357183
首先我们定义了这两个类;在OSGeom储存了一个drawable的顶点,法线,UV坐标和三角形索引。OSGNodeVisiter是继承于osg::NodeVisiter的,用于储存所有drawble数据。
class OSGGeom
{
public:
TArray<FVector> vertexArray;
TArray<FVector> normalArray;
TArray<FVector2D> textCoordArray;
TArray<int> triangleArray;
public:
OSGGeom()
{
vertexArray = TArray<FVector>();
normalArray = TArray<FVector>();
textCoordArray = TArray<FVector2D>();
triangleArray = TArray<int>();
}
};
class OSGNodeVisiter :public osg::NodeVisitor
{
public:
TArray<OSGGeom*> NodeGeoms;
public:
virtual void apply(osg::Geode& node) override;
OSGNodeVisiter()
{
NodeGeoms = TArray<OSGGeom*>();
}
};
定义继承于AttributeVisiter的OSGAttributeVisiter 类,用于记录drawble的顶点,法线和UV坐标 定义模板类TriangleIndex,用于访问三角形索引。
class OSGAttributeVisiter :public osg::Drawable::AttributeFunctor
{
public:
TArray<FVector> vertexArray;
TArray<FVector> normalArray;
TArray<FVector2D> textCoordArray;
virtual void apply(osg::Drawable::AttributeType, unsigned, osg::Vec2*) override;
virtual void apply(osg::Drawable::AttributeType, unsigned, osg::Vec3*) override;
};
class TriangleIndex
{
public:
TArray<int> triangleIndexes;
int triangleNum;
TriangleIndex(){};
~TriangleIndex(){};
void operator()(const unsigned int& v1, const unsigned int& v2, const unsigned int& v3);
};
OSGNodevisiter是继承于Nodevisiter的,当调用node->accept(visitor)时,最终会调用visitor的apply
osg::ref_ptr<osg::Node> node = osgDB::readNodeFile("D:\\OSGSetting\\OpenSceneGraph-Data\\glider.OSG");
OSGNodeVisiter aNodeVisiter = OSGNodeVisiter();
node->accept(aNodeVisiter);
在OSGNodevisiter的apply函数里,我们实例化一个AttributeFUnctor和osg::TriangleIndexFunctor ,因为OSG顶点数据的范围与UE不同,不符合我们的需求,所以我们暂且先把顶点坐标都乘1000.
void OSGNodeVisiter:: apply(osg::Geode& node)
{
const float multiNum = 1000;
for (size_t i = 0; i < node.getNumDrawables(); i++)
{
osg::ref_ptr<osg::Drawable> drawable = node.getDrawable(i);
OSGAttributeVisiter functor;
drawable->accept(functor);
osg::TriangleIndexFunctor<TriangleIndex> triangleIndex;
drawable->accept(triangleIndex);
OSGGeom*newGeom = new OSGGeom;
for (FVector vValue : functor.vertexArray)
{
newGeom->vertexArray.Add(vValue* multiNum);
}
for (FVector nValue : functor.normalArray)
{
newGeom->normalArray.Add(nValue);
}
for (FVector2D texValue : functor.textCoordArray)
{
newGeom->textCoordArray.Add(texValue);
}
for (int triValue : triangleIndex.triangleIndexes)
{
newGeom->triangleArray.Add(triValue);
}
NodeGeoms.Add(newGeomptr);
}
}
父类AttributeFunctor中的apply是多态的,如果数据类型是一样的,那么可以通过type来分别获取的是什么数据
所以我们在我们定义的OSGNodevisiter根据我们的需求来重写apply:
void OSGAttributeVisiter::apply(osg::Drawable::AttributeType type, unsigned size, osg::Vec2* front)
{
UE_LOG(LogTemp, Warning, TEXT("OSGAttributeVisiter::applyVec2 %d"), type);
if (type == osg::Drawable::TEXTURE_COORDS_0)
{
for (unsigned i = 0; i < size; i++)
{
osg::Vec2 vec = *(front + i);
double X = vec._v[0];
double Y = vec._v[1];
textCoordArray.Add(FVector2D(X, Y));
}
}
}
void OSGAttributeVisiter::apply(osg::Drawable::AttributeType type, unsigned size, osg::Vec3* front)
{
if (type == osg::Drawable::VERTICES)
{
for (unsigned i = 0; i < size; i++)
{
osg::Vec3 vec = *(front + i);
double X = vec._v[0];
double Y = vec._v[1];
double Z = vec._v[2];
vertexArray.Add(FVector(X, Y, Z));
}
}
else if (type == osg::Drawable::NORMALS)
{
for (unsigned i = 0; i < size; i++)
{
osg::Vec3 vec = *(front + i);
double X = vec._v[0];
double Y = vec._v[1];
double Z = vec._v[2];
normalArray.Add(FVector(X, Y, Z));
}
}
}
至此我们就把这个osg模型的基本数据读出来啦
三:用ProceduralMesh显示
首先,因为这个项目里以后会读取多个osg模型,所以我定义了一个OSGGridHolderc++类,用ProceduralMeshComponent负责绘制网格体。 法线数据虽然我们已经得到了,但是这里我选择用UKismetProceduralMeshLibrary::CalculateTangentsForMesh去计算法线和切线,因为这个Glider是没有纹理坐标的,所以我这里UV坐标就随便赋值使程序正常运行即可。 主要代码如下:
void AOSGBGridHolder::DrawOSGBObject(const TArray<OSGGeom*>& Geoms)
{
for (int i = 0; i < Geoms.Num(); i++)
{
OSGGeom* Geom = Geoms[i];
if (Geom)
{
TArray<FVector> Verteces = Geom->vertexArray;
TArray<FVector> Normals = Geom->normalArray;
TArray<FVector2D> UV;
if (!Geom->textCoordArray.IsEmpty())
{
UV = Geom->textCoordArray;
}
else
{
for (int j = 0; j < Verteces.Num(); j++)
{
UV.Add(FVector2D(1, 1));
}
}
TArray<int> Triangles = Geom->triangleArray;
FString compName = FString("bp_RoadMeshComp") + FString::FromInt(i);
UProceduralMeshComponent* newPComp = NewObject<UProceduralMeshComponent>(this, *compName);
newPComp->AttachToComponent(RootComponent, FAttachmentTransformRules::KeepWorldTransform);
newPComp->CreationMethod = EComponentCreationMethod::Instance;
newPComp->RegisterComponent();
TArray<FProcMeshTangent> tangents;
TArray<FVector> normals;
TArray<FColor> vertexColors;
UKismetProceduralMeshLibrary::CalculateTangentsForMesh(Verteces, Triangles, UV, normals, tangents);
newPComp->CreateMeshSection(0, Verteces, Triangles, normals, UV, TArray<FVector2D>(), TArray<FVector2D>(), TArray<FVector2D>(), vertexColors, tangents, false);
PMeshArray.Add(newPComp);
}
}
}
最终成功将模型读取到场景中来:
|