Skip to content

Commit 3bff1de

Browse files
committed
Support arbitrary PLY file properties order, do not require normals or SH (#108, #170)
Apparently some tools do or used to do 3DGS PLY files in slightly different properties order than what was used in original 3DGS paper. Support arbitrary order, and shuffle data into what we expect. Also, do not require normals (which are unused), or SHs besides the base color.
1 parent 540df1b commit 3bff1de

File tree

2 files changed

+135
-22
lines changed

2 files changed

+135
-22
lines changed

package/Editor/Utils/GaussianFileReader.cs

Lines changed: 125 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,19 @@
11
using System.Collections.Generic;
22
using System.Globalization;
33
using System.IO;
4+
using System.Linq;
45
using GaussianSplatting.Runtime;
56
using Unity.Burst;
67
using Unity.Collections;
78
using Unity.Collections.LowLevel.Unsafe;
89
using Unity.Jobs;
910
using Unity.Mathematics;
1011
using UnityEngine;
12+
using UnityEngine.Assertions;
1113

1214
namespace GaussianSplatting.Editor.Utils
1315
{
14-
// input file splat data is expected to be in this format
16+
// input file splat data is read into this format
1517
public struct InputSplatData
1618
{
1719
public Vector3 pos;
@@ -44,16 +46,14 @@ public static unsafe void ReadFile(string filePath, out NativeArray<InputSplatDa
4446
{
4547
if (isPLY(filePath))
4648
{
47-
NativeArray<byte> verticesRawData;
48-
PLYFileReader.ReadFile(filePath, out var splatCount, out var splatStride, out List<string> _, out verticesRawData);
49-
if (UnsafeUtility.SizeOf<InputSplatData>() != splatStride)
50-
throw new IOException($"PLY vertex size mismatch, expected {UnsafeUtility.SizeOf<InputSplatData>()} but file has {splatStride}");
51-
52-
// reorder SHs
53-
NativeArray<float> floatData = verticesRawData.Reinterpret<float>(1);
54-
ReorderSHs(splatCount, (float*)floatData.GetUnsafePtr());
55-
56-
splats = verticesRawData.Reinterpret<InputSplatData>(1);
49+
NativeArray<byte> plyRawData;
50+
List<(string, PLYFileReader.ElementType)> attributes;
51+
PLYFileReader.ReadFile(filePath, out var splatCount, out var vertexStride, out attributes, out plyRawData);
52+
string attrError = CheckPLYAttributes(attributes);
53+
if (!string.IsNullOrEmpty(attrError))
54+
throw new IOException($"PLY file is probably not a Gaussian Splat file? Missing properties: {attrError}");
55+
splats = PLYDataToSplats(plyRawData, splatCount, vertexStride, attributes);
56+
ReorderSHs(splatCount, (float*)splats.GetUnsafePtr());
5757
LinearizeData(splats);
5858
return;
5959
}
@@ -68,6 +68,120 @@ public static unsafe void ReadFile(string filePath, out NativeArray<InputSplatDa
6868
static bool isPLY(string filePath) => filePath.EndsWith(".ply", true, CultureInfo.InvariantCulture);
6969
static bool isSPZ(string filePath) => filePath.EndsWith(".spz", true, CultureInfo.InvariantCulture);
7070

71+
static string CheckPLYAttributes(List<(string, PLYFileReader.ElementType)> attributes)
72+
{
73+
string[] required = { "x", "y", "z", "f_dc_0", "f_dc_1", "f_dc_2", "opacity", "scale_0", "scale_1", "scale_2", "rot_0", "rot_1", "rot_2", "rot_3" };
74+
List<string> missing = required.Where(req => !attributes.Contains((req, PLYFileReader.ElementType.Float))).ToList();
75+
if (missing.Count == 0)
76+
return null;
77+
return string.Join(",", missing);
78+
}
79+
80+
static unsafe NativeArray<InputSplatData> PLYDataToSplats(NativeArray<byte> input, int count, int stride, List<(string, PLYFileReader.ElementType)> attributes)
81+
{
82+
NativeArray<int> fileAttrOffsets = new NativeArray<int>(attributes.Count, Allocator.Temp);
83+
int offset = 0;
84+
for (var ai = 0; ai < attributes.Count; ai++)
85+
{
86+
var attr = attributes[ai];
87+
fileAttrOffsets[ai] = offset;
88+
offset += PLYFileReader.TypeToSize(attr.Item2);
89+
}
90+
91+
string[] splatAttributes =
92+
{
93+
"x",
94+
"y",
95+
"z",
96+
"nx",
97+
"ny",
98+
"nz",
99+
"f_dc_0",
100+
"f_dc_1",
101+
"f_dc_2",
102+
"f_rest_0",
103+
"f_rest_1",
104+
"f_rest_2",
105+
"f_rest_3",
106+
"f_rest_4",
107+
"f_rest_5",
108+
"f_rest_6",
109+
"f_rest_7",
110+
"f_rest_8",
111+
"f_rest_9",
112+
"f_rest_10",
113+
"f_rest_11",
114+
"f_rest_12",
115+
"f_rest_13",
116+
"f_rest_14",
117+
"f_rest_15",
118+
"f_rest_16",
119+
"f_rest_17",
120+
"f_rest_18",
121+
"f_rest_19",
122+
"f_rest_20",
123+
"f_rest_21",
124+
"f_rest_22",
125+
"f_rest_23",
126+
"f_rest_24",
127+
"f_rest_25",
128+
"f_rest_26",
129+
"f_rest_27",
130+
"f_rest_28",
131+
"f_rest_29",
132+
"f_rest_30",
133+
"f_rest_31",
134+
"f_rest_32",
135+
"f_rest_33",
136+
"f_rest_34",
137+
"f_rest_35",
138+
"f_rest_36",
139+
"f_rest_37",
140+
"f_rest_38",
141+
"f_rest_39",
142+
"f_rest_40",
143+
"f_rest_41",
144+
"f_rest_42",
145+
"f_rest_43",
146+
"f_rest_44",
147+
"opacity",
148+
"scale_0",
149+
"scale_1",
150+
"scale_2",
151+
"rot_0",
152+
"rot_1",
153+
"rot_2",
154+
"rot_3",
155+
};
156+
Assert.AreEqual(UnsafeUtility.SizeOf<InputSplatData>() / 4, splatAttributes.Length);
157+
NativeArray<int> srcOffsets = new NativeArray<int>(splatAttributes.Length, Allocator.Temp);
158+
for (int ai = 0; ai < splatAttributes.Length; ai++)
159+
{
160+
int attrIndex = attributes.IndexOf((splatAttributes[ai], PLYFileReader.ElementType.Float));
161+
int attrOffset = attrIndex >= 0 ? fileAttrOffsets[attrIndex] : -1;
162+
srcOffsets[ai] = attrOffset;
163+
}
164+
165+
NativeArray<InputSplatData> dst = new NativeArray<InputSplatData>(count, Allocator.Persistent);
166+
ReorderPLYData(count, (byte*)input.GetUnsafeReadOnlyPtr(), stride, (byte*)dst.GetUnsafePtr(), UnsafeUtility.SizeOf<InputSplatData>(), (int*)srcOffsets.GetUnsafeReadOnlyPtr());
167+
return dst;
168+
}
169+
170+
[BurstCompile]
171+
static unsafe void ReorderPLYData(int splatCount, byte* src, int srcStride, byte* dst, int dstStride, int* srcOffsets)
172+
{
173+
for (int i = 0; i < splatCount; i++)
174+
{
175+
for (int attr = 0; attr < dstStride / 4; attr++)
176+
{
177+
if (srcOffsets[attr] >= 0)
178+
*(int*)(dst + attr * 4) = *(int*)(src + srcOffsets[attr]);
179+
}
180+
src += srcStride;
181+
dst += dstStride;
182+
}
183+
}
184+
71185
[BurstCompile]
72186
static unsafe void ReorderSHs(int splatCount, float* data)
73187
{

package/Editor/Utils/PLYFileReader.cs

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -11,18 +11,18 @@ namespace GaussianSplatting.Editor.Utils
1111
{
1212
public static class PLYFileReader
1313
{
14-
public static void ReadFileHeader(string filePath, out int vertexCount, out int vertexStride, out List<string> attrNames)
14+
public static void ReadFileHeader(string filePath, out int vertexCount, out int vertexStride, out List<(string, ElementType)> attrs)
1515
{
1616
vertexCount = 0;
1717
vertexStride = 0;
18-
attrNames = new List<string>();
18+
attrs = new List<(string, ElementType)>();
1919
if (!File.Exists(filePath))
2020
return;
2121
using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
22-
ReadHeaderImpl(filePath, out vertexCount, out vertexStride, out attrNames, fs);
22+
ReadHeaderImpl(filePath, out vertexCount, out vertexStride, out attrs, fs);
2323
}
2424

25-
static void ReadHeaderImpl(string filePath, out int vertexCount, out int vertexStride, out List<string> attrNames, FileStream fs)
25+
static void ReadHeaderImpl(string filePath, out int vertexCount, out int vertexStride, out List<(string, ElementType)> attrs, FileStream fs)
2626
{
2727
// C# arrays and NativeArrays make it hard to have a "byte" array larger than 2GB :/
2828
if (fs.Length >= 2 * 1024 * 1024 * 1024L)
@@ -31,7 +31,7 @@ static void ReadHeaderImpl(string filePath, out int vertexCount, out int vertexS
3131
// read header
3232
vertexCount = 0;
3333
vertexStride = 0;
34-
attrNames = new List<string>();
34+
attrs = new List<(string, ElementType)>();
3535
const int kMaxHeaderLines = 9000;
3636
for (int lineIdx = 0; lineIdx < kMaxHeaderLines; ++lineIdx)
3737
{
@@ -51,32 +51,31 @@ static void ReadHeaderImpl(string filePath, out int vertexCount, out int vertexS
5151
_ => ElementType.None
5252
};
5353
vertexStride += TypeToSize(type);
54-
attrNames.Add(tokens[2]);
54+
attrs.Add((tokens[2], type));
5555
}
5656
}
57-
//Debug.Log($"PLY {filePath} vtx {vertexCount} stride {vertexStride} attrs #{attrNames.Count} {string.Join(',', attrNames)}");
5857
}
5958

60-
public static void ReadFile(string filePath, out int vertexCount, out int vertexStride, out List<string> attrNames, out NativeArray<byte> vertices)
59+
public static void ReadFile(string filePath, out int vertexCount, out int vertexStride, out List<(string, ElementType)> attrs, out NativeArray<byte> vertices)
6160
{
6261
using var fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
63-
ReadHeaderImpl(filePath, out vertexCount, out vertexStride, out attrNames, fs);
62+
ReadHeaderImpl(filePath, out vertexCount, out vertexStride, out attrs, fs);
6463

6564
vertices = new NativeArray<byte>(vertexCount * vertexStride, Allocator.Persistent);
6665
var readBytes = fs.Read(vertices);
6766
if (readBytes != vertices.Length)
6867
throw new IOException($"PLY {filePath} read error, expected {vertices.Length} data bytes got {readBytes}");
6968
}
7069

71-
enum ElementType
70+
public enum ElementType
7271
{
7372
None,
7473
Float,
7574
Double,
7675
UChar
7776
}
7877

79-
static int TypeToSize(ElementType t)
78+
public static int TypeToSize(ElementType t)
8079
{
8180
return t switch
8281
{

0 commit comments

Comments
 (0)