PCX是Unity开源的点云绘制,操作简单,拖入ply文件即可得到点云prefab,但是只支持binary_little_endian编码的ply格式点云。
我手上只有一些xyzrgb并且是txt格式的点云文件需要显示,网上其他的一些mesh绘制需要考虑文件位置及绘制点数问题,所以参考PCX的部分源码。
直接放上源码吧
IndexFormat.UInt32可以绘制40亿个顶点,但有些设备不支持该格式
本段代码需将txt后缀变为point,且点数数据格式为x y z r g b。
替换的后缀代码中可自由替换,数据格式可调整代码读取不同格式点云
// An highlighted block
using System.Collections.Generic;
using UnityEngine;
using System.IO;
using System.Linq;
using UnityEngine.Rendering;
using UnityEditor;
using System;
#if UNITY_2020_2_OR_NEWER
using UnityEditor.AssetImporters;
#else
using UnityEditor.Experimental.AssetImporters;
#endif
//ScriptedImporter 资产导入脚本
//[ScriptedImporter(1, "point")] 导入文件类型
namespace pointcould_display
{
[ScriptedImporter(1, "point")]
public class meshrenderpcl : ScriptedImporter
{
#region ScriptedImporter implementation
//int numPoints = 60000;
//读取点云xyz列表,颜色列表
List<Vector3> points = new List<Vector3>();
List<Color> colors = new List<Color>();
//点云数量,生成mesh时需要判断点云数量使用不同网格索引缓冲区
int num = 0;
//导入重写虚函数OnImportAsset
public override void OnImportAsset(AssetImportContext context)
{
//读取点云数据
//context.assetPath 导入文件的路径
ReadPointColudData(context.assetPath);
//var data = ImportAsPointCloudData(context.assetPath,points,colors);
//生成点云
var gameObject = new GameObject();
var mesh = CreateMesh(context.assetPath, points, colors, num);
//设置mesh
var meshFilter = gameObject.AddComponent<MeshFilter>();
meshFilter.sharedMesh = mesh;
//设置材质
var meshRenderer = gameObject.AddComponent<MeshRenderer>();
meshRenderer.sharedMaterial = GetDefaultMaterial();
//在导入事件期间定义脚本化导入器的导入上下文。、
//AddObjectToAsset 向导入操作的结果添加对象。
//SetMainObject 设置导入的主要对象。
context.AddObjectToAsset("prefab", gameObject);
if (mesh != null) context.AddObjectToAsset("mesh", mesh);
context.SetMainObject(gameObject);
}
#endregion
void ReadPointColudData(string fileAddress)
{
//读取点云数据
FileInfo fInfo0 = new FileInfo(fileAddress);
string s = "";
StreamReader r;
if (fInfo0.Exists)
{
r = new StreamReader(fileAddress);
}
else
{
Debug.LogError("NO THIS FILE!");
return;
}
// 将文本中的点云数据读入分别存到xyz数组和rgb数组中
while ((s = r.ReadLine()) != null)
{
string[] words = s.Split(" "[0]);
Vector3 xyz = new Vector3(float.Parse(words[0]), float.Parse(words[1]), float.Parse(words[2]));
points.Add(xyz);
if(words.Length < 4)
{
List<String> b = words.ToList();
b.Add("255");
b.Add("255");
b.Add("255");
words = b.ToArray();
}
Color colorRGB = new Color(float.Parse(words[3] != null ? words[3] : "255") / 255.0f, float.Parse(words[4] != null ? words[4] : "255") / 255.0f, float.Parse(words[5] != null ? words[5] : "255") / 255.0f);
colors.Add(colorRGB);
//Debug.Log(xyz.ToString() + "," + colorRGB.ToString());
}
//总顶点数
num = points.Count;
}
Mesh CreateMesh(string path, List<Vector3> points, List<Color> colors, int pointsNum)
{
Mesh mesh = new Mesh();
//获取名称
mesh.name = Path.GetFileNameWithoutExtension(path);
//网格索引缓冲区数据的格式。16为只能65535个顶点,32为更大
mesh.indexFormat = pointsNum > 65535 ?
IndexFormat.UInt32 : IndexFormat.UInt16;
//设置网格顶点及颜色
mesh.SetVertices(points);
mesh.SetColors(colors);
//为子网格设置索引缓冲区。
//子网格表示使用单个 Material 渲染的三角形(或具有不同 MeshTopology 的索引)的列表。
//当网格与具有多个材质的 Renderer 一起使用时,应确保每个材质有一个子网格。
mesh.SetIndices(
Enumerable.Range(0, pointsNum).ToArray(),
MeshTopology.Points, 0
);
//将以前进行的网格修改上传到图形 AP
//从代码创建或修改网格(使用 vertices、normals、triangles 等属性)时,
//网格数据在内部标记为“已修改”,会在下次渲染网格时发送给图形 API。
mesh.UploadMeshData(true);
return mesh;
}
static Material GetDefaultMaterial()
{
//? 1.定义数据类型可为空 2.用于判断对象是否为空,如果对象为空,则无论该对象调用什么皆不会抛出异常,直接返回null(C#6.0)
//?? 可用于判断一个变量在为null时返回一个指定的值
// Via package manager
var path_prj = "Assets/Scripts/Default_Point.mat";
return AssetDatabase.LoadAssetAtPath<Material>(path_prj);
}
}
}
注意1:将文件拖入即可,但是unity有txt导入功能的,所以要修改后缀,修改的后缀可自行更换,[ScriptedImporter(1, “point”)]即将txt改为point,可以修改point为其他后缀
注意2:脚本创建后放入Editor文件夹,因为导入是属于Editor模块,因为Mesh已经上传过GUI了,不需要考虑点云文件打包
附:PCX mat放入代码中的mat位置及几个标准点云
链接: PCX相关下载 使用方法:新建Scripts文件夹,先将链接中的unity文件夹中的Editor和Shader文件夹放入,再将Default_Point.mat放入,防止先放入point文件没有读取导入和找不到Shader和材质,最后再将x y z r g b格式的后缀为.point的文件放入,unity文件夹中有几个标准点云文件