├── Content
├── Materials
│ ├── M_GaussianSplatting.uasset
│ └── M_GaussianSplattingPoint.uasset
├── Niagara
│ ├── NE_GaussianSplatting.uasset
│ ├── NS_GaussianSplattingPointCloud.uasset
│ └── NS_GaussianSplattingPointCloudDebug.uasset
└── Tools
│ ├── BP_GSDebugTool.uasset
│ ├── BP_PointCloudsDirLoader.uasset
│ └── BP_PointCloudsImportTool.uasset
├── GaussianSplattingForUnrealEngine.uplugin
├── LICENSE
├── README.md
├── Resources
├── 123Tes1tz1.gif
├── 3dgs.gif
├── Capture.svg
├── GaussianSplatting.svg
├── SparseReconstruction.svg
├── image-20250125114152866.png
├── image-20250125114302564.png
├── image-20250125123542864.png
├── image-20250125123858563.png
├── image-20250125124847911.png
├── image-20250125125848081.png
├── image-20250125131850213.png
├── image-20250125132939563.png
├── image-20250125133400148.png
├── image-20250125134459423.png
├── image-20250125141645234.png
├── image-20250125141810400.png
├── image-20250125141841703.png
├── image-20250125141852531.png
├── image-20250125141854448.png
├── image-20250125142437998.png
├── image-20250125143147405.png
├── image-20250125143907659.png
├── image-20250125144933867.png
├── image-20250125145736912.png
├── image-20250425144116238.png
├── image-20250425144806596.png
└── image-20250425145537057.png
├── Scripts
├── clip_model.py
├── gaussian_splatting_helper.py
├── gaussian_splatting_helper.pyproj
├── gaussian_splatting_helper.sln
├── make_depth_scale.py
└── read_write_model.py
└── Source
├── GaussianSplattingEditor
├── GaussianSplattingEditor.build.cs
├── Private
│ ├── GaussianSplattingEdMode.cpp
│ ├── GaussianSplattingEdMode.h
│ ├── GaussianSplattingEditorLibrary.cpp
│ ├── GaussianSplattingEditorModule.cpp
│ ├── GaussianSplattingEditorSettings.cpp
│ ├── GaussianSplattingEditorSettings.h
│ ├── GaussianSplattingEditorStyle.cpp
│ ├── GaussianSplattingEditorStyle.h
│ ├── GaussianSplattingHLODBuilder.cpp
│ ├── GaussianSplattingHLODBuilder.h
│ ├── GaussianSplattingPointCloudAssetFactory.cpp
│ ├── GaussianSplattingPointCloudAssetFactory.h
│ ├── GaussianSplattingPointCloudEditor.cpp
│ ├── GaussianSplattingPointCloudEditor.h
│ ├── GaussianSplattingStep.cpp
│ ├── GaussianSplattingStep.h
│ ├── SGaussianSplattingEdModePanel.cpp
│ ├── SGaussianSplattingEdModePanel.h
│ ├── SGaussianSplattingPointCloudEditorViewport.cpp
│ ├── SGaussianSplattingPointCloudEditorViewport.h
│ ├── SGaussianSplattingPointCloudEditorViewportClient.cpp
│ ├── SGaussianSplattingPointCloudEditorViewportClient.h
│ ├── SGaussianSplattingPointCloudFeatureEditor.cpp
│ └── SGaussianSplattingPointCloudFeatureEditor.h
└── Public
│ ├── GaussianSplattingEditorLibrary.h
│ └── GaussianSplattingEditorModule.h
└── GaussianSplattingRuntime
├── GaussianSplattingRuntime.build.cs
├── Private
├── Compression
│ ├── LICENSE
│ ├── Spz.cpp
│ └── Spz.h
├── GaussianSplattingPointCloud.cpp
├── GaussianSplattingPointCloudActor.cpp
├── GaussianSplattingPointCloudDataInterface.cpp
└── GaussianSplattingRuntimeModule.cpp
└── Public
├── GaussianSplattingPointCloud.h
├── GaussianSplattingPointCloudActor.h
├── GaussianSplattingPointCloudDataInterface.h
└── GaussianSplattingRuntimeModule.h
/Content/Materials/M_GaussianSplatting.uasset:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Content/Materials/M_GaussianSplatting.uasset
--------------------------------------------------------------------------------
/Content/Materials/M_GaussianSplattingPoint.uasset:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Content/Materials/M_GaussianSplattingPoint.uasset
--------------------------------------------------------------------------------
/Content/Niagara/NE_GaussianSplatting.uasset:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Content/Niagara/NE_GaussianSplatting.uasset
--------------------------------------------------------------------------------
/Content/Niagara/NS_GaussianSplattingPointCloud.uasset:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Content/Niagara/NS_GaussianSplattingPointCloud.uasset
--------------------------------------------------------------------------------
/Content/Niagara/NS_GaussianSplattingPointCloudDebug.uasset:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Content/Niagara/NS_GaussianSplattingPointCloudDebug.uasset
--------------------------------------------------------------------------------
/Content/Tools/BP_GSDebugTool.uasset:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Content/Tools/BP_GSDebugTool.uasset
--------------------------------------------------------------------------------
/Content/Tools/BP_PointCloudsDirLoader.uasset:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Content/Tools/BP_PointCloudsDirLoader.uasset
--------------------------------------------------------------------------------
/Content/Tools/BP_PointCloudsImportTool.uasset:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Content/Tools/BP_PointCloudsImportTool.uasset
--------------------------------------------------------------------------------
/GaussianSplattingForUnrealEngine.uplugin:
--------------------------------------------------------------------------------
1 | {
2 | "FileVersion": 3,
3 | "Version": 1,
4 | "VersionName": "1.0",
5 | "FriendlyName": "Gaussian Splatting For Unreal Engine",
6 | "Description": "",
7 | "Category": "Other",
8 | "CreatedBy": "Italink",
9 | "CreatedByURL": "",
10 | "DocsURL": "",
11 | "MarketplaceURL": "",
12 | "SupportURL": "",
13 | "CanContainContent": true,
14 | "IsBetaVersion": false,
15 | "IsExperimentalVersion": false,
16 | "Installed": false,
17 | "SupportedTargetPlatforms": [
18 | "Win64",
19 | "Linux"
20 | ],
21 | "Modules": [
22 | {
23 | "Name": "GaussianSplattingRuntime",
24 | "Type": "Runtime",
25 | "LoadingPhase": "PostConfigInit"
26 | },
27 | {
28 | "Name": "GaussianSplattingEditor",
29 | "Type": "Editor",
30 | "LoadingPhase": "Default"
31 | }
32 | ],
33 | "Plugins": [
34 | {
35 | "Name": "Niagara",
36 | "Enabled": true
37 | }
38 |
39 | ]
40 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2023 Italink
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ---
2 | comments: true
3 | ---
4 | # Gaussian Splatting For Unreal Engine
5 | **GaussianSplattingForUnrealEngine** 是一个虚幻引擎插件,它可以轻易将虚幻中的图形转换为高质量的3D高斯点云:
6 |
7 | 
8 |
9 | 
10 |
11 | 如果还不了解什么是3DGS,可以查看这篇文档:
12 |
13 | - https://italink.github.io/ModernGraphicsEngineGuide/04-UnrealEngine/16.3DGaussianSplatting/
14 |
15 | 该插件支持以下功能:
16 |
17 | - 提供简单易用的编辑器工具来完成:
18 | - 图形捕获
19 | - 稀疏点云重建
20 | - 高斯训练
21 | - 支持高斯点云(`*.ply`)的导入,在虚幻中以 **GPU粒子** 或 **静态网格体** 为载体进行渲染
22 | - 让3D高斯更接近工业生产,提供了一些非常有用的机制:
23 | - 精细化的深度修剪:有效剔除漂浮噪点
24 | - 基于屏幕尺寸的LOD策略:基于高斯点的特征,高效调控粒子的数量和内存
25 |
26 | 3D高斯具有如下优缺点:
27 |
28 | - 优点:
29 | - 基于【点】表达相较于三角形,更适用于制作粒子特效
30 | - 基于图像特征识别的重建,相较于传统的网格简化,通常具有更好的简化效果,非常适合用于制作大范围区域的视觉代理
31 | - 无纹理数据,仅存在基于特征的顶点数据,可微的特性也让它可以进行自由裁剪和压缩
32 | - 缺点:
33 | - 使用半透明叠加的方式进行渲染,需要对图元进行半透明排序,且 OverDraw 较高
34 | - 仅还原了物体在视觉上的颜色表达,无法或很难还原物体实际的物理属性,因此不能营造动态的光影效果
35 |
36 | ## 环境要求
37 |
38 | - **Unreal Engine 5.5 +**
39 | - **Colmap** :https://github.com/colmap/colmap
40 | - **Gaussian Splatting** :https://github.com/graphdeco-inria/gaussian-splatting
41 |
42 | > 该插件的执行建立在上述仓库的命令行接口之上,请确保搭建正确的运行环境。
43 |
44 | ## 使用手册
45 |
46 | - [此处](https://drive.google.com/file/d/1gyKlCQacUsZUX6rXSKW1joyVxIYsBnHM/view?usp=drive_link)可以获取测试所生成的高斯点云。
47 | - 如果厌倦密集的文字说明,[此处](https://www.bilibili.com/video/BV1GUwNeYE6c)还有一个不那么友好的视频演示。
48 |
49 | ### 安装插件
50 |
51 | - 克隆最新的插件代码到项目工程的`Plugins`目录下:
52 |
53 | - 安装完成之后可以在虚幻引擎的编辑器模式中找到 **Gaussian Splatting Mode** :
54 |
55 | 
56 |
57 | - 首次执行需要在编辑器面板的【Settings】页面指定相关的目录:
58 |
59 | 
60 |
61 | ### 执行相机捕获
62 |
63 | - 该步骤的目标在于构建一组相机阵列,生成稀疏重建和高斯训练所需的数据集。
64 |
65 | 
66 |
67 | #### 构建相机阵列
68 |
69 | 构建相机阵列的方式有三种:
70 |
71 | - **Select** :通过选中物体来确定需要捕获的场景图形
72 | - **Locate** :通过一个 `LocateActor` 来锚定捕获的中心点,再通过设置 `HiddenActors` 来排除不需要捕获的场景图形
73 | - **Custom** :以自定义的方式添加相机位,再通过设置 `HiddenActors` 来排除不需要捕获的场景图形
74 |
75 | **Select** 模式适用于单个或少量图形的捕获,我们可以在场景中按住`Ctrl`键来多选图形,也可以在场景大纲中进行选择,相机阵列将会以选中图形的包围盒来自动更新相机位置:
76 |
77 | 
78 |
79 | - `Camera Mode`:可以选择上半球或者全球捕获
80 | - `Frame XY` :相机矩阵的尺寸,`12` 意味着 `144 (12 x 12)` 个相机位。通常情况下,100+个机位足以用于构建高斯,但如果捕获的场景有较大的区域或者细节,可以尝试增加到300以上。
81 |
82 | - `Capture Distance Scale`:用于动态伸缩相机的距离,通常它的调整要打开 `Render Target` 的编辑视图,来查看捕获的RT是否涵盖图形的全部区域,可以通过以下手段来调整当前预览的机位:
83 | - 点击按钮【Prev Camera】和【Next Camera】
84 | - 在场景直接选中 相机 模型
85 | - 在场景大纲中,ScreenCaptureActor的子列表中选中相机机位
86 |
87 | - `Scene Capture`:用于捕获图形的虚幻组件,点击右侧的箭头可以选中它,对参数进行微调
88 | - `Render Target Resolution`:捕获的图像尺寸,colmap 建议图像尺寸不超过 `3200`, gs 建议图像尺寸不超过 `1600`,这个界限是可以突破的,但较大的图像尺寸在训练时通常需要占用非常多的显存
89 | - `Capture Depth`:是否需要捕获深度图,通常使用深度图可以更好的修剪高斯训练产生的浮空噪点
90 | - `Show Flag Setting`:暴露 `Screen Capture` 显示标识的设置,显示标识将影响图像的捕获效果,常用的有:
91 | - Dynamic Shadow: 动态阴影开关
92 | - Ambient Occlusion: 环境遮蔽开关
93 | - Translucency: 半透明开关
94 |
95 | **Locate** 模式适用于大范围场景的捕获,切换到 **Locate** 模式,将在场景中场景一个 Locate Actor,在编辑器面板上点击`Locate Actor`右侧的箭头可以选中它,调整 Locate Actor 的位置和缩放可以控制相机阵列的位置:
96 |
97 | 
98 |
99 | - `Hidden Actors`:需要排除的图形
100 |
101 | **Custom** 模式用来自定义相机机位,切换到 **Custom** 模式,编辑器面板中显示 `Camera Actors` 选项,点击 【+】新增时会在当前编辑器视口位置添加新的机位:
102 |
103 | 
104 |
105 | 在构建好相机阵列和设置好捕获配置后,点击【Capture】按钮,将在插件的工作目录下生成数据集,点击右侧的【Browse】 图标,可以浏览捕获结果:
106 |
107 | - `depths`:深度图
108 | - `images`:原始图像
109 | - `masks`:遮罩
110 | - `cameras.txt`:相机的位置信息
111 |
112 | 
113 |
114 | 确保捕获的原始图像没有太大问题,就可以进行下一个步骤。
115 |
116 | 
117 |
118 | ### 执行稀疏重建
119 |
120 | - 该步骤本质上是在调用 [colmap](https://github.com/colmap/colmap) 的命令行程序来完成稀疏点云的重建。
121 |
122 | 
123 |
124 | 通常情况下,只需要点击 【Reconstruction】来执行稀疏重建,控制台可以观察到执行的日志,在结束后点击 【Colmap View】 可以查看重建的结果是否存在明显异常,如果不存在明显异常就可以进行下一步骤。
125 |
126 | 
127 |
128 | 很少情况下会出现异常情况,但为了以防万一,增加了 colmap 各个阶段执行的扩展参数,你可以依据以下文档来调整测试:
129 |
130 | - https://colmap.github.io/
131 | - https://github.com/mwtarnowski/colmap-parameters
132 | - https://github.com/colmap/colmap/issues
133 |
134 | 也可以点击 【Colmap Edit】在 Colmap GUI中手动进行稀疏重建,只需将最终的结果导出到工作目录的`./sparse/0/`中:
135 |
136 | - 通常是 `{PluginDir}/WorkHome/GaussianSplattingEditor/sparse/0/`
137 |
138 | 
139 |
140 | ### 执行高斯溅射
141 |
142 | - 该步骤本质上是在调用 [gaussian-splatting](https://github.com/graphdeco-inria/gaussian-splatting) 提供的算法来完成3D高斯的训练。
143 |
144 | 
145 |
146 | - `Resolution`:参与高斯训练时对图像分辨率的缩放系数,支持 `1`、`2`、`4`、`8`, `1` 表示不缩放, `2` 表示缩放为原图像的 `1/2`
147 | - `Iterations`:高斯训练的迭代次数,通常 `7000 ` 次迭代能得到一个基本可观察的结果,`30000` 次迭代能得到更好的效果。
148 | - `Advanced`:高斯训练的高级参数,详细配置请参阅 [gaussian-splatting](https://github.com/graphdeco-inria/gaussian-splatting) 的 `README.md`
149 |
150 | 点击【Train】将开始执行高斯训练,在此之前,插件会执行一次深度修剪,执行完成后,将应用 Ouput 类别下的编辑器设置,对高斯点云进行后处理:
151 |
152 | - `Output Type`:生成结果在虚幻引擎中的存储载体,可以是 Niagara粒子 或者 静态网格体
153 | - `Clipping By Bound`:是否要使用捕获图像时的包围盒对点云进行裁剪
154 | - `Distance Of Observation`:将以该值作为观察距离来评估每个高斯点的屏幕尺寸,用于生成后的裁剪,如果为0,则表示不进行裁剪
155 | - `Min Screen Size Of Observation`: 裁剪掉屏幕尺寸小于该值的高斯点
156 |
157 | > 点击【Reload】按钮将重新执行后处理过程。
158 |
159 | 最终执行结束将在【Output】框中展示最终生成的结果,点击【Export】按钮可以将结果转储到项目目录中:
160 |
161 | 
162 |
163 | 导致后直接点击资产可以打开一个简易的高斯点云编辑器:
164 |
165 | 
166 |
167 | 下侧的柱状图表示的高斯点的尺寸分布,目前支持的操作有:
168 |
169 | - 鼠标在柱状区域拖拽可以进行选择高斯点
170 | - 键盘按住 `Ctrl` + `Alt` 可以在视口中拖拽鼠标框选高斯点
171 | - 按下`Delete` 可以删除当前选中的高斯点
172 | - `Ctrl + Z` / `Ctrl + Y` 为撤销重做
173 |
174 | 高斯点云资产拖拽到场景会生成一个NiagaraActor,也可以导出独立的 Niagara System:
175 |
176 | 
177 |
178 | ## LOD
179 |
180 | 粒子形式的高斯点云可以自动剔除掉小于某个屏幕尺寸的高斯点,以此来减少系统的调度和内存的开销:
181 |
182 | 
183 |
184 | 可以在粒子系统的参数面板来修改 `MinFeatureScreenSize` 来修改单个粒子系统剔除的阈值
185 |
186 | 
187 |
188 | 也可以使用控制台参数来进行全局的调整:
189 |
190 | - `r.GaussianSplatting.ScreenSizeBias`:屏幕尺寸计算时的偏移,默认为 `0`
191 | - `r.GaussianSplatting.ScreenSizeScale`:屏幕尺寸计算时的缩放系数,默认为 `1`
192 |
193 | ## 自定义高斯算法
194 |
195 | 虚幻引擎提供了非常便利的编辑器,借助此插件可以快速的生成一些合成数据集,如果有算法定制化或测试的需求,在插件目录的 `/WorkHome/Scripts/.`下找到该插件所使用的 `python` 脚本:
196 |
197 | 
198 |
199 | 实际上编辑器中的绝大数操作,都是在通过命令行参数去执行 `gaussian_splatting_helper.py`,可以按需对其进行调整。
200 |
--------------------------------------------------------------------------------
/Resources/123Tes1tz1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Resources/123Tes1tz1.gif
--------------------------------------------------------------------------------
/Resources/3dgs.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Resources/3dgs.gif
--------------------------------------------------------------------------------
/Resources/Capture.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Resources/GaussianSplatting.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Resources/SparseReconstruction.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/Resources/image-20250125114152866.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Resources/image-20250125114152866.png
--------------------------------------------------------------------------------
/Resources/image-20250125114302564.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Resources/image-20250125114302564.png
--------------------------------------------------------------------------------
/Resources/image-20250125123542864.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Resources/image-20250125123542864.png
--------------------------------------------------------------------------------
/Resources/image-20250125123858563.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Resources/image-20250125123858563.png
--------------------------------------------------------------------------------
/Resources/image-20250125124847911.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Resources/image-20250125124847911.png
--------------------------------------------------------------------------------
/Resources/image-20250125125848081.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Resources/image-20250125125848081.png
--------------------------------------------------------------------------------
/Resources/image-20250125131850213.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Resources/image-20250125131850213.png
--------------------------------------------------------------------------------
/Resources/image-20250125132939563.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Resources/image-20250125132939563.png
--------------------------------------------------------------------------------
/Resources/image-20250125133400148.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Resources/image-20250125133400148.png
--------------------------------------------------------------------------------
/Resources/image-20250125134459423.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Resources/image-20250125134459423.png
--------------------------------------------------------------------------------
/Resources/image-20250125141645234.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Resources/image-20250125141645234.png
--------------------------------------------------------------------------------
/Resources/image-20250125141810400.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Resources/image-20250125141810400.png
--------------------------------------------------------------------------------
/Resources/image-20250125141841703.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Resources/image-20250125141841703.png
--------------------------------------------------------------------------------
/Resources/image-20250125141852531.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Resources/image-20250125141852531.png
--------------------------------------------------------------------------------
/Resources/image-20250125141854448.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Resources/image-20250125141854448.png
--------------------------------------------------------------------------------
/Resources/image-20250125142437998.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Resources/image-20250125142437998.png
--------------------------------------------------------------------------------
/Resources/image-20250125143147405.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Resources/image-20250125143147405.png
--------------------------------------------------------------------------------
/Resources/image-20250125143907659.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Resources/image-20250125143907659.png
--------------------------------------------------------------------------------
/Resources/image-20250125144933867.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Resources/image-20250125144933867.png
--------------------------------------------------------------------------------
/Resources/image-20250125145736912.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Resources/image-20250125145736912.png
--------------------------------------------------------------------------------
/Resources/image-20250425144116238.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Resources/image-20250425144116238.png
--------------------------------------------------------------------------------
/Resources/image-20250425144806596.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Resources/image-20250425144806596.png
--------------------------------------------------------------------------------
/Resources/image-20250425145537057.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/Italink/GaussianSplattingForUnrealEngine/2efdeb4a26c075e47d7d86709e065db4bb1c0a11/Resources/image-20250425145537057.png
--------------------------------------------------------------------------------
/Scripts/clip_model.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import argparse
3 | import cv2
4 | import math
5 | from plyfile import PlyData, PlyElement
6 | from read_write_model import *
7 | from PIL import Image
8 | from scipy.ndimage import binary_dilation
9 | import matplotlib.pyplot as plt
10 |
11 | # from vispy import app, scene
12 | # from vispy.scene import visuals
13 |
14 | C0 = 0.28209479177387814
15 |
16 | def SH2RGB(sh):
17 | return sh * C0 + 0.5
18 |
19 | def getWorld2View(R, t):
20 | Rt = np.zeros((4, 4))
21 | Rt[:3, :3] = R
22 | Rt[:3, 3] = t
23 | Rt[3, 3] = 1.0
24 | return np.float32(Rt)
25 |
26 | def getProjectionMatrix(znear, zfar, fovX, fovY):
27 | tanHalfFovY = math.tan((fovY / 2))
28 | tanHalfFovX = math.tan((fovX / 2))
29 |
30 | top = tanHalfFovY * znear
31 | bottom = -top
32 | right = tanHalfFovX * znear
33 | left = -right
34 |
35 | P = np.zeros((4, 4))
36 |
37 | z_sign = 1.0
38 |
39 | P[0, 0] = 2.0 * znear / (right - left)
40 | P[1, 1] = 2.0 * znear / (top - bottom)
41 | P[0, 2] = (right + left) / (right - left)
42 | P[1, 2] = (top + bottom) / (top - bottom)
43 | P[3, 2] = z_sign
44 | P[2, 2] = z_sign * zfar / (zfar - znear)
45 | P[2, 3] = -(zfar * znear) / (zfar - znear)
46 | return P
47 |
48 | def focal2fov(focal, pixels):
49 | return
50 |
51 | def clip_test(pts, image_meta, camera_intrinsics, discarded_indices, mask_image, debug_view):
52 | pts_homogeneous = np.hstack((pts, np.ones((pts.shape[0], 1))))
53 | # 计算旋转矩阵
54 | R = qvec2rotmat(image_meta.qvec)
55 | # 平移向量
56 | t = image_meta.tvec
57 | # 世界坐标系到相机坐标系的变换矩阵
58 | ViewMat = getWorld2View(R, t)
59 | # 获取相机内参
60 | f, cx, cy = camera_intrinsics.params
61 |
62 | image_width = 2 * cx
63 | image_height = 2 * cy
64 |
65 | # 计算水平和垂直视场角
66 | fovX = 2 * math.atan(image_width / (2 * f))
67 | fovY = 2 * math.atan(image_height / (2 * f))
68 | # 计算投影矩阵
69 | ProjectionMatrix = getProjectionMatrix(0.01, 10000, fovX, fovY)
70 |
71 | # 将点云从世界坐标系转换到相机坐标系
72 | camera_points_homogeneous = np.dot(pts_homogeneous, ViewMat.T)
73 | # 将相机坐标系下的点投影到裁剪空间
74 | clip_points_homogeneous = np.dot(camera_points_homogeneous, ProjectionMatrix.T)
75 |
76 | # 透视除法
77 | clip_points = clip_points_homogeneous[:, :3] / clip_points_homogeneous[:, 3, np.newaxis]
78 |
79 | # 提取裁剪空间的深度信息
80 | clip_depths = clip_points[:, 2]
81 |
82 | # 转换到图像空间
83 | image_points = np.zeros((clip_points.shape[0], 2))
84 | image_points[:, 0] = (clip_points[:, 0] + 1) * cx
85 | image_points[:, 1] = (clip_points[:, 1] + 1) * cy
86 |
87 | original_image_name = image_meta.name
88 |
89 | file_name, file_ext = os.path.splitext(original_image_name)
90 | image_width = int(camera_intrinsics.width)
91 | image_height = int(camera_intrinsics.height)
92 |
93 | indices = np.arange(len(image_points))
94 | x, y = image_points[:, 0].astype(int), image_points[:, 1].astype(int)
95 |
96 | valid_indices = (x >= 0) & (x < image_width) & (y >= 0) & (y < image_height) & (clip_depths >= 0) & (clip_depths <= 1)
97 | valid_x = x[valid_indices]
98 | valid_y = y[valid_indices]
99 | valid_indices_subset = indices[valid_indices]
100 |
101 | in_black_mask = mask_image[valid_y, valid_x] == 0
102 | black_mask_indices = valid_indices_subset[in_black_mask]
103 |
104 | # 统计 black_mask_indices 中每个索引的计数
105 | unique_black_mask_indices, black_counts = np.unique(black_mask_indices, return_counts=True)
106 | # 统计 valid_indices_subset 中每个索引的计数
107 | unique_valid_indices, valid_counts = np.unique(valid_indices_subset, return_counts=True)
108 |
109 | # 初始化或更新 discarded_indices
110 | all_indices = np.unique(np.concatenate([unique_black_mask_indices, unique_valid_indices]))
111 | for idx in all_indices:
112 | black_count = black_counts[unique_black_mask_indices == idx][0] if idx in unique_black_mask_indices else 0
113 | valid_count = valid_counts[unique_valid_indices == idx][0] if idx in unique_valid_indices else 0
114 |
115 | if idx not in discarded_indices:
116 | discarded_indices[idx] = [black_count, valid_count, 0]
117 | else:
118 | discarded_indices[idx][0] += black_count
119 | discarded_indices[idx][1] += valid_count
120 | discarded_indices[idx][2] = discarded_indices[idx][0] / discarded_indices[idx][1]
121 |
122 | def clip(base_dir, ply_path, out_ply_path, mask_dir, mask_use_count = 10, mask_dilation = 5, mask_clip_threshold = 0.8, size_clip_threshold = 0.95, distance_of_observation = 25600, min_screen_size_of_observation = 0.01, debug_view = False):
123 | cameras, images, points3d = read_model(os.path.join(base_dir, "sparse", "0"), ext=f".bin")
124 | plydata = PlyData.read(ply_path)
125 | vertices = plydata['vertex']
126 | pts = np.vstack([vertices['x'], vertices['y'], vertices['z']]).T
127 | # shs = np.vstack([vertices['f_dc_0'], vertices['f_dc_1'], vertices['f_dc_2']]).T
128 | # colors = SH2RGB(shs)
129 | scales = np.vstack([np.exp(vertices['scale_0']), np.exp(vertices['scale_1']), np.exp(vertices['scale_2'])]).T
130 | sizes = np.linalg.norm(scales, axis=1) * 2
131 | sizes_ue = sizes * 100
132 | pts_count = len(pts)
133 |
134 | fov = 90.0
135 | half_fov_rad = fov * math.pi / 360.0
136 | screen_multiple = 1920.0 / 1080.0 / math.tan(half_fov_rad)
137 | min_object_size = min_screen_size_of_observation * distance_of_observation / screen_multiple
138 |
139 | size_percentile = np.percentile(sizes_ue, size_clip_threshold * 100)
140 | indices_below_threshold = np.where(sizes_ue < 5 * size_percentile)[0]
141 | clip_count_by_size_percentile = pts_count - len(indices_below_threshold)
142 |
143 | indices_above_min = np.where(sizes_ue > min_object_size)[0]
144 | observable_indices = np.intersect1d(indices_above_min, indices_below_threshold)
145 | clip_count_by_observate = pts_count - len(observable_indices) - clip_count_by_size_percentile
146 |
147 | property_names = [prop.name for prop in vertices.properties]
148 | filtered_properties = {}
149 | for prop_name in property_names:
150 | prop_data = vertices[prop_name]
151 | filtered_properties[prop_name] = prop_data[observable_indices]
152 |
153 | new_vertex_data = np.empty(len(filtered_properties[property_names[0]]), dtype=vertices.data.dtype)
154 | for prop_name in property_names:
155 | new_vertex_data[prop_name] = filtered_properties[prop_name]
156 | vertices = PlyElement.describe(new_vertex_data, 'vertex')
157 | pts = np.vstack([vertices['x'], vertices['y'], vertices['z']]).T
158 |
159 | discarded_indices = {}
160 | all_indices = np.arange(len(vertices))
161 | step = max(1, len(images) / mask_use_count)
162 | image_keys = list(images.keys())
163 | for i in range(0, len(images), int(step)):
164 | if i < len(image_keys):
165 | image_meta = images[image_keys[i]]
166 | camera_intrinsics = cameras[image_meta.camera_id]
167 | original_image_name = image_meta.name
168 | file_name, file_ext = os.path.splitext(original_image_name)
169 | mask_file_path = os.path.join(mask_dir, file_name + ".png")
170 | if not os.path.exists(mask_file_path):
171 | return None
172 | mask_image = Image.open(mask_file_path)
173 | mask_image = np.array(mask_image.convert("L"))
174 | structuring_element = np.ones((2 * mask_dilation + 1, 2 * mask_dilation + 1))
175 | dilated_mask = binary_dilation(mask_image, structure=structuring_element)
176 | dilated_mask = (dilated_mask * 255).astype(np.uint8)
177 | clip_test(pts, image_meta, camera_intrinsics, discarded_indices, dilated_mask, debug_view)
178 |
179 | valid_indices = [idx for idx in all_indices if idx not in discarded_indices or discarded_indices[idx][2] < 1 - mask_clip_threshold]
180 | valid_indices = np.array(valid_indices)
181 | clip_count_by_mask = len(all_indices) - len(valid_indices)
182 |
183 | print(f"clip begin : {pts_count}")
184 | print(f"clip by size percentile : {clip_count_by_size_percentile} \t[{size_clip_threshold}] ")
185 | print(f"clip by size observate : {clip_count_by_observate} \t[{distance_of_observation}:{min_screen_size_of_observation}] ")
186 | print(f"clip by size masks : {clip_count_by_mask} \t[{mask_clip_threshold}] ")
187 | print(f"clip end : {len(valid_indices)} ", flush = True)
188 |
189 | if len(valid_indices) == 0 :
190 | return
191 | property_names = [prop.name for prop in vertices.properties]
192 | filtered_properties = {}
193 | for prop_name in property_names:
194 | prop_data = vertices[prop_name]
195 | filtered_properties[prop_name] = prop_data[valid_indices]
196 |
197 | new_vertex_data = np.empty(len(filtered_properties[property_names[0]]), dtype=vertices.data.dtype)
198 | for prop_name in property_names:
199 | new_vertex_data[prop_name] = filtered_properties[prop_name]
200 | vertices = PlyElement.describe(new_vertex_data, 'vertex')
201 |
202 | output_dir = os.path.dirname(out_ply_path)
203 | if not os.path.exists(output_dir):
204 | os.makedirs(output_dir)
205 |
206 | new_plydata = PlyData([vertices] + [el for el in plydata.elements if el.name != 'vertex'])
207 | new_plydata.write(out_ply_path)
208 |
209 | def hlod_clip_workflow():
210 | search_dir = r"D:\ProjectTitan\Plugins\GaussianSplattingForUnrealEngine_Private\Work\TitanMain\CitySample_HLOD0_3DGS"
211 |
212 | found_files = []
213 |
214 | for root, dirs, files in os.walk(search_dir):
215 | for file in files:
216 | if file == "point_cloud.ply":
217 | file_path = os.path.join(root, file)
218 | found_files.append(file_path)
219 |
220 | files_count = len(found_files)
221 | for i in range(0, files_count, 1):
222 | ply_path = found_files[i]
223 | file_name, file_ext = os.path.splitext(ply_path)
224 | output_ply_path = file_name + '_clipped' + file_ext
225 | base_dir = os.path.dirname(os.path.dirname(os.path.dirname(os.path.dirname(ply_path))))
226 | mask_dir = os.path.join(base_dir, "masks")
227 | mask_dilation = 5
228 | mask_use_count = 10
229 | mask_clip_threshold = 0.9
230 | size_clip_threshold = 0.98
231 | distance_of_observation = 25600
232 | min_screen_size_of_observation = 0.005
233 | print(f"-------[{i}/{files_count}]{ply_path}", flush = True)
234 | clip(base_dir, ply_path, output_ply_path, mask_dir, mask_use_count, mask_dilation, mask_clip_threshold, size_clip_threshold, distance_of_observation, min_screen_size_of_observation, False)
235 | print(f"", flush = True)
236 |
237 |
238 | if __name__ == '__main__':
239 | parser = argparse.ArgumentParser()
240 | parser.add_argument('--base_dir', default="F:/UnrealProjects/CitySample2/Plugins/GaussianSplattingForUnrealEngine_Private/WorkHome/Cache/Big_City_LVL/CitySample_HLOD0_3DGS/Big_City_LVL_MainGrid_L0_X-9_Y-9/Cluster0")
241 | parser.add_argument('--ply_path', default="F:/UnrealProjects/CitySample2/Plugins/GaussianSplattingForUnrealEngine_Private/WorkHome/Cache/Big_City_LVL/CitySample_HLOD0_3DGS/Big_City_LVL_MainGrid_L0_X-9_Y-9/Cluster0/output/point_cloud/iteration_7000/point_cloud.ply")
242 | parser.add_argument('--output_ply_path', default="F:/UnrealProjects/CitySample2/Plugins/GaussianSplattingForUnrealEngine_Private/WorkHome/Cache/Big_City_LVL/CitySample_HLOD0_3DGS/Big_City_LVL_MainGrid_L0_X-9_Y-9/Cluster0/output/point_cloud/iteration_7000/point_cloud_clipped.ply")
243 | parser.add_argument('--mask_dir', default="F:/UnrealProjects/CitySample2/Plugins/GaussianSplattingForUnrealEngine_Private/WorkHome/Cache/Big_City_LVL/CitySample_HLOD0_3DGS/Big_City_LVL_MainGrid_L0_X-9_Y-9/Cluster0/masks")
244 | parser.add_argument('--mask_use_count', default=5)
245 | parser.add_argument('--mask_dilation', default=5)
246 | parser.add_argument('--mask_clip_threshold', default=0.9)
247 | parser.add_argument('--size_clip_threshold', default=0.98)
248 | parser.add_argument('--distance_of_observation', default=25600)
249 | parser.add_argument('--min_screen_size_of_observation', default=0.005)
250 | args = parser.parse_args()
251 | # clip(args.base_dir, args.ply_path, args.output_ply_path, args.mask_dir, args.mask_use_count, args.mask_dilation, args.mask_clip_threshold, args.size_clip_threshold, args.distance_of_observation, args.min_screen_size_of_observation, True)
252 | hlod_clip_workflow()
253 |
254 |
255 |
256 |
257 |
258 |
259 |
260 |
--------------------------------------------------------------------------------
/Scripts/gaussian_splatting_helper.py:
--------------------------------------------------------------------------------
1 | #-*- coding: utf-8 -*-
2 |
3 | from ast import arg
4 | import subprocess
5 | import os
6 | import sys
7 | import argparse
8 | import shutil
9 |
10 | def printImmediately(*args, **kwargs):
11 | print(*args, **kwargs, flush = True)
12 |
13 | class GaussianSplattingHelper:
14 | def __init__(self):
15 | parser = argparse.ArgumentParser(description='Gaussian Splatting Helper')
16 | parser.add_argument("workDir", help= "work directory");
17 | parser.add_argument('-s', '--sparse', action='store_true', help='execute sparse reconstruction')
18 | parser.add_argument('-v', '--view', action='store_true', help='execute colmap view')
19 | parser.add_argument('-d', '--edit', action='store_true', help='execute colmap edit')
20 | parser.add_argument('-c', '--colmap', help='')
21 | parser.add_argument('-g', '--gaussian', help='')
22 | parser.add_argument('-e', '--extractor', help='')
23 | parser.add_argument('-mat', '--matcher', help='')
24 | parser.add_argument('-map', '--mapper', help='')
25 | parser.add_argument('-a', '--aligner', help='')
26 | parser.add_argument('-t', '--train', help='')
27 | parser.add_argument('--clip', action='store_true', help='execute clip ')
28 | parser.add_argument('--clip_threshold',type=float, help='', default = 0.8)
29 | parser.add_argument('--mask_dilation',type=int, help='', default = 100)
30 | parser.add_argument('--ply', help='')
31 |
32 | self.args = parser.parse_args()
33 | self.scriptDir = os.path.dirname(os.path.abspath(__file__))
34 | os.chdir(self.args.workDir)
35 | if self.args.sparse:
36 | self.executeSparseReconstruction()
37 | if self.args.view:
38 | self.executeColmapView()
39 | if self.args.edit:
40 | self.executeColmapEdit()
41 | if self.args.gaussian and len(self.args.gaussian) > 0:
42 | self.executeGaussianSplatting()
43 | if self.args.clip:
44 | self.executeGaussianSplattingClip()
45 |
46 | def runCommand(self, command, env_ext = {}):
47 | env = os.environ.copy();
48 | env.update(env_ext)
49 | printImmediately("python run command: ", command)
50 | process = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL, cwd=self.args.workDir , env=env, shell=True, universal_newlines=True)
51 | while True:
52 | output = process.stdout.readline()
53 | if output == '' and process.poll() is not None:
54 | break
55 | if output:
56 | printImmediately(output.strip())
57 | sys.stdout.flush()
58 | if process.returncode!= 0:
59 | printImmediately(f"Command '{command}' failed with return code {process.returncode}", file=sys.stderr)
60 |
61 | def executeSparseReconstruction(self):
62 | if os.path.exists("./sparse"):
63 | shutil.rmtree("./sparse")
64 | os.makedirs("./images", exist_ok=True)
65 | os.makedirs("./sparse/0", exist_ok=True)
66 | if os.path.exists("./database.db"):
67 | os.remove("./database.db")
68 | command = f"{self.args.colmap} feature_extractor --database_path ./database.db --image_path ./images --ImageReader.camera_model SIMPLE_PINHOLE"
69 | if os.path.exists("./masks") :
70 | command += " --ImageReader.mask_path ./masks "
71 | if self.args.extractor:
72 | command += str(self.args.extractor);
73 | self.runCommand(command)
74 | command = f"{self.args.colmap} exhaustive_matcher --database_path ./database.db "
75 | if self.args.matcher:
76 | command += str(self.args.matcher);
77 | self.runCommand(command)
78 |
79 | # self.runCommand(f"{self.args.colmap} point_triangulator --database_path database.db --image_path images --input_path ./text --output_path ./pointtriangulator --Mapper.fix_existing_images 1 --Mapper.ba_refine_focal_length 0")
80 |
81 | command = f"{self.args.colmap} mapper --database_path ./database.db --image_path ./images --output_path ./sparse --Mapper.fix_existing_images 1 "
82 | if self.args.mapper:
83 | command += str(self.args.mapper);
84 | self.runCommand(command)
85 |
86 | command = f"{self.args.colmap} model_aligner --input_path ./sparse/0 --output_path ./sparse/0 --ref_images_path ./cameras.txt --ref_is_gps 0 --alignment_type custom --alignment_max_error 3 "
87 | if self.args.aligner:
88 | command += str(self.args.aligner);
89 | self.runCommand(command)
90 |
91 | def executeColmapView(self):
92 | colmap_executable_path = self.args.colmap
93 | colmap_directory = os.path.dirname(os.path.dirname(colmap_executable_path))
94 | plugins_path = os.path.join(colmap_directory, "plugins")
95 | env = {
96 | "QT_PLUGIN_PATH" :plugins_path
97 | }
98 | command = f"{self.args.colmap} gui --database_path ./database.db --image_path ./images --import_path ./sparse/0"
99 | self.runCommand(command, env)
100 |
101 | def executeColmapEdit(self):
102 | colmap_executable_path = self.args.colmap
103 | colmap_directory = os.path.dirname(os.path.dirname(colmap_executable_path))
104 | plugins_path = os.path.join(colmap_directory, "plugins")
105 | env = {
106 | "QT_PLUGIN_PATH" :plugins_path
107 | }
108 | command = f"{self.args.colmap} gui --database_path ./database.db --image_path ./images "
109 | if os.path.exists("./masks") :
110 | command += " --ImageReader.mask_path ./masks "
111 | self.runCommand(command, env)
112 |
113 | def executeGaussianSplatting(self):
114 | command = "conda activate gaussian_splatting"
115 | if os.path.exists("./depths"):
116 | command += f"&& python {self.scriptDir}/make_depth_scale.py --base_dir . --depths_dir ./depths && python {self.args.gaussian}/train.py -s . -m ./output --depths ./depths "
117 | else:
118 | command += f"&& python {self.args.gaussian}/train.py -s . -m ./output "
119 | if self.args.train:
120 | command += str(self.args.train);
121 | self.runCommand(command)
122 |
123 | if __name__ == "__main__":
124 | GaussianSplattingHelper = GaussianSplattingHelper()
125 |
--------------------------------------------------------------------------------
/Scripts/gaussian_splatting_helper.pyproj:
--------------------------------------------------------------------------------
1 |
2 |
3 | Debug
4 | 2.0
5 | a7e3a740-ed32-4e86-bd4d-81b3cbd44531
6 | .
7 | gaussian_splatting_helper.py
8 |
9 |
10 | .
11 | .
12 | GaussianSplattingHelper
13 | GaussianSplattingHelper
14 | Standard Python launcher
15 | F:/UnrealProjects/ItaDev/Plugins/GaussianSplattingForUnrealEngine/WorkHome/Cache/GaussianSplattingEditor --gaussian D:/gaussian-splatting --train="--resolution 1"
16 | False
17 |
18 |
19 | true
20 | false
21 |
22 |
23 | true
24 | false
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
35 |
36 |
37 |
38 |
39 |
40 |
--------------------------------------------------------------------------------
/Scripts/gaussian_splatting_helper.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.12.35527.113 d17.12
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{888888A0-9F3D-457C-B088-3A5042F75D52}") = "gaussian_splatting_helper", "gaussian_splatting_helper.pyproj", "{A7E3A740-ED32-4E86-BD4D-81B3CBD44531}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {A7E3A740-ED32-4E86-BD4D-81B3CBD44531}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {A7E3A740-ED32-4E86-BD4D-81B3CBD44531}.Release|Any CPU.ActiveCfg = Release|Any CPU
16 | EndGlobalSection
17 | GlobalSection(SolutionProperties) = preSolution
18 | HideSolutionNode = FALSE
19 | EndGlobalSection
20 | EndGlobal
21 |
--------------------------------------------------------------------------------
/Scripts/make_depth_scale.py:
--------------------------------------------------------------------------------
1 | import numpy as np
2 | import argparse
3 | import cv2
4 | from joblib import delayed, Parallel
5 | import json
6 | from read_write_model import *
7 |
8 | def get_scales(key, cameras, images, points3d_ordered, args):
9 | image_meta = images[key]
10 | cam_intrinsic = cameras[image_meta.camera_id]
11 |
12 | pts_idx = images_metas[key].point3D_ids
13 |
14 | mask = pts_idx >= 0
15 | mask *= pts_idx < len(points3d_ordered)
16 |
17 | pts_idx = pts_idx[mask]
18 | valid_xys = image_meta.xys[mask]
19 |
20 | if len(pts_idx) > 0:
21 | pts = points3d_ordered[pts_idx]
22 | else:
23 | pts = np.array([0, 0, 0])
24 |
25 | R = qvec2rotmat(image_meta.qvec)
26 | pts = np.dot(pts, R.T) + image_meta.tvec
27 |
28 | invcolmapdepth = 1. / pts[..., 2]
29 | n_remove = len(image_meta.name.split('.')[-1]) + 1
30 | invmonodepthmap = cv2.imread(f"{args.depths_dir}/{image_meta.name[:-n_remove]}.png", cv2.IMREAD_UNCHANGED)
31 |
32 | if invmonodepthmap is None:
33 | return None
34 |
35 | if invmonodepthmap.ndim != 2:
36 | invmonodepthmap = invmonodepthmap[..., 0]
37 |
38 | invmonodepthmap = invmonodepthmap.astype(np.float32) / (2**16)
39 | s = invmonodepthmap.shape[0] / cam_intrinsic.height
40 |
41 | maps = (valid_xys * s).astype(np.float32)
42 | valid = (
43 | (maps[..., 0] >= 0) *
44 | (maps[..., 1] >= 0) *
45 | (maps[..., 0] < cam_intrinsic.width * s) *
46 | (maps[..., 1] < cam_intrinsic.height * s) * (invcolmapdepth > 0))
47 |
48 | if valid.sum() > 10 and (invcolmapdepth.max() - invcolmapdepth.min()) > 1e-3:
49 | maps = maps[valid, :]
50 | invcolmapdepth = invcolmapdepth[valid]
51 |
52 | invmonodepth = cv2.remap(invmonodepthmap, maps[..., 0], maps[..., 1], interpolation=cv2.INTER_LINEAR, borderMode=cv2.BORDER_REPLICATE)[..., 0]
53 |
54 | ## modify by italink
55 | segmentation_threshold = 0.5;
56 | invcolmapdepth = invcolmapdepth[invmonodepth > segmentation_threshold]
57 | invmonodepth = invmonodepth[invmonodepth > segmentation_threshold]
58 |
59 | ## Median / dev
60 | t_colmap = np.median(invcolmapdepth)
61 | s_colmap = np.mean(np.abs(invcolmapdepth - t_colmap))
62 |
63 | t_mono = np.median(invmonodepth)
64 | s_mono = np.mean(np.abs(invmonodepth - t_mono))
65 |
66 | scale = s_colmap / s_mono
67 | offset = t_colmap - t_mono * scale
68 | else:
69 | scale = 0
70 | offset = 0
71 | return {"image_name": image_meta.name[:-n_remove], "scale": scale, "offset": offset}
72 |
73 | if __name__ == '__main__':
74 | parser = argparse.ArgumentParser()
75 | parser.add_argument('--base_dir', default="../data/big_gaussians/standalone_chunks/campus")
76 | parser.add_argument('--depths_dir', default="../data/big_gaussians/standalone_chunks/campus/depths_any")
77 | parser.add_argument('--model_type', default="bin")
78 | args = parser.parse_args()
79 |
80 | cam_intrinsics, images_metas, points3d = read_model(os.path.join(args.base_dir, "sparse", "0"), ext=f".{args.model_type}")
81 |
82 | pts_indices = np.array([points3d[key].id for key in points3d])
83 | pts_xyzs = np.array([points3d[key].xyz for key in points3d])
84 | points3d_ordered = np.zeros([pts_indices.max()+1, 3])
85 | points3d_ordered[pts_indices] = pts_xyzs
86 |
87 | # depth_param_list = [get_scales(key, cam_intrinsics, images_metas, points3d_ordered, args) for key in images_metas]
88 | depth_param_list = Parallel(n_jobs=-1, backend="threading")(
89 | delayed(get_scales)(key, cam_intrinsics, images_metas, points3d_ordered, args) for key in images_metas
90 | )
91 |
92 | depth_params = {
93 | depth_param["image_name"]: {"scale": depth_param["scale"], "offset": depth_param["offset"]}
94 | for depth_param in depth_param_list if depth_param != None
95 | }
96 |
97 | with open(f"{args.base_dir}/sparse/0/depth_params.json", "w") as f:
98 | json.dump(depth_params, f, indent=2)
99 |
100 | print("make depth scale finished!")
101 |
--------------------------------------------------------------------------------
/Source/GaussianSplattingEditor/GaussianSplattingEditor.build.cs:
--------------------------------------------------------------------------------
1 | using UnrealBuildTool;
2 | using System.IO;
3 | public class GaussianSplattingEditor : ModuleRules
4 | {
5 | public GaussianSplattingEditor(ReadOnlyTargetRules Target) : base(Target)
6 | {
7 | PCHUsage = PCHUsageMode.UseExplicitOrSharedPCHs;
8 | AddEngineThirdPartyPrivateStaticDependencies(Target, "zlib");
9 |
10 | PublicDependencyModuleNames.AddRange(
11 | new string[]
12 | {
13 | "UnrealEd",
14 | "AssetTools",
15 | "Kismet",
16 | "Core",
17 | "RenderCore",
18 | "RHI",
19 | "AssetRegistry",
20 | "EditorFramework",
21 | "ImageCore",
22 | "Niagara",
23 | "GaussianSplattingRuntime",
24 | }
25 | );
26 |
27 | PrivateDependencyModuleNames.AddRange(
28 | new string[]
29 | {
30 | "CoreUObject",
31 | "Engine",
32 | "Slate",
33 | "SlateCore",
34 | "Niagara",
35 | "MeshDescription",
36 | "StaticMeshDescription",
37 | "PropertyEditor",
38 | "UnrealEd",
39 | "AssetRegistry",
40 | "EditorStyle",
41 | "InputCore",
42 | "ContentBrowser",
43 | "ContentBrowserData",
44 | "ToolMenus",
45 | "Projects",
46 | "NiagaraEditor",
47 | "LevelEditor",
48 | "Json",
49 | "AssetTools",
50 | "Landscape",
51 | "AdvancedPreviewScene",
52 | "AssetDefinition",
53 | "JsonUtilities",
54 | }
55 | );
56 | }
57 | }
--------------------------------------------------------------------------------
/Source/GaussianSplattingEditor/Private/GaussianSplattingEdMode.cpp:
--------------------------------------------------------------------------------
1 | #include "GaussianSplattingEdMode.h"
2 | #include "EditorModeManager.h"
3 | #include "ScopedTransaction.h"
4 | #include "Toolkits/ToolkitManager.h"
5 |
6 | const FEditorModeID FGaussianSplattingEdMode::EdID(TEXT("EM_GaussianSplatting"));
7 |
8 | void FGaussianSplattingEdMode::Enter()
9 | {
10 | FEdMode::Enter();
11 | if (!Toolkit.IsValid()){
12 | Toolkit = MakeShareable(new FGaussianSplattingEdModeToolkit);
13 | Toolkit->Init(Owner->GetToolkitHost());
14 | }
15 | }
16 |
17 | void FGaussianSplattingEdMode::Exit()
18 | {
19 | FToolkitManager::Get().CloseToolkit(Toolkit.ToSharedRef());
20 | Toolkit.Reset();
21 | FEdMode::Exit();
22 | }
23 |
24 | FGaussianSplattingEdModeToolkit::FGaussianSplattingEdModeToolkit()
25 | {
26 | GaussianSplattingEditor = SNew(SGaussianSplattingEdModePanel);
27 | }
28 |
29 | FGaussianSplattingEdModeToolkit::~FGaussianSplattingEdModeToolkit()
30 | {
31 | }
32 |
33 | FName FGaussianSplattingEdModeToolkit::GetToolkitFName() const
34 | {
35 | return FName("GaussianSplattingEdMode");
36 | }
37 |
38 | FText FGaussianSplattingEdModeToolkit::GetBaseToolkitName() const
39 | {
40 | return NSLOCTEXT("GaussianSplatting", "GaussianSplatting", "Gaussian Splatting");
41 | }
42 |
43 | class FEdMode* FGaussianSplattingEdModeToolkit::GetEditorMode() const
44 | {
45 | return GLevelEditorModeTools().GetActiveMode(FGaussianSplattingEdMode::EdID);
46 | }
47 |
48 | TSharedPtr FGaussianSplattingEdModeToolkit::GetInlineContent() const
49 | {
50 | return GaussianSplattingEditor;
51 | }
52 |
--------------------------------------------------------------------------------
/Source/GaussianSplattingEditor/Private/GaussianSplattingEdMode.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 | #include "EditorModes.h"
3 | #include "EdMode.h"
4 | #include "Editor.h"
5 | #include "EditorModeManager.h"
6 | #include "SGaussianSplattingEdModePanel.h"
7 |
8 | class FGaussianSplattingEdMode : public FEdMode
9 | {
10 | public:
11 | const static FEditorModeID EdID;
12 | void Enter() override;
13 | void Exit() override;
14 | };
15 |
16 | class FGaussianSplattingEdModeToolkit : public FModeToolkit
17 | {
18 | public:
19 | FGaussianSplattingEdModeToolkit();
20 | ~FGaussianSplattingEdModeToolkit();
21 | /** IToolkit interface */
22 | virtual FName GetToolkitFName() const override;
23 | virtual FText GetBaseToolkitName() const override;
24 | virtual class FEdMode* GetEditorMode() const override;
25 | virtual TSharedPtr GetInlineContent() const override;
26 | private:
27 | TSharedPtr GaussianSplattingEditor;
28 | TSharedPtr DetailsView;
29 | };
--------------------------------------------------------------------------------
/Source/GaussianSplattingEditor/Private/GaussianSplattingEditorModule.cpp:
--------------------------------------------------------------------------------
1 | #include "GaussianSplattingEditorModule.h"
2 | #include "EditorModeRegistry.h"
3 | #include "GaussianSplattingEdMode.h"
4 | #include "GaussianSplattingEditorStyle.h"
5 | #include "AssetToolsModule.h"
6 | #include "ContentBrowserMenuContexts.h"
7 | #include "GaussianSplattingPointCloud.h"
8 | #include "ContentBrowserModule.h"
9 | #include "Widgets/Notifications/SNotificationList.h"
10 | #include "Framework/Notifications/NotificationManager.h"
11 | #include "AssetRegistry/AssetRegistryModule.h"
12 | #include "Misc/Paths.h"
13 | #include "GaussianSplattingEditorLibrary.h"
14 | #include "IContentBrowserSingleton.h"
15 | #include "NiagaraEditorStyle.h"
16 |
17 |
18 | #define LOCTEXT_NAMESPACE "GaussianSplatting"
19 |
20 | void FGaussianSplattingEditorModule::StartupModule()
21 | {
22 | FEditorModeRegistry::Get().RegisterMode(
23 | FGaussianSplattingEdMode::EdID,
24 | LOCTEXT("GaussianSplatting", "Gaussian Splatting"),
25 | FSlateIcon(),
26 | true,
27 | 010200
28 | );
29 |
30 | FGaussianSplattingEditorStyle::Initialize();
31 | FGaussianSplattingEditorStyle::ReloadTextures();
32 | UToolMenus::RegisterStartupCallback(FSimpleMulticastDelegate::FDelegate::CreateRaw(this, &FGaussianSplattingEditorModule::RegisterMenus));
33 | }
34 |
35 | void FGaussianSplattingEditorModule::ShutdownModule()
36 | {
37 | UToolMenus::UnRegisterStartupCallback(this);
38 | UToolMenus::UnregisterOwner(this);
39 | FGaussianSplattingEditorStyle::Shutdown();
40 | FEditorModeRegistry::Get().UnregisterMode(FGaussianSplattingEdMode::EdID);
41 | }
42 |
43 | void FGaussianSplattingEditorModule::RegisterMenus()
44 | {
45 | FToolMenuOwnerScoped OwnerScoped(this);
46 | UToolMenu* Menu = UToolMenus::Get()->ExtendMenu("ContentBrowser.AssetContextMenu.GaussianSplattingPointCloud");
47 | FToolMenuSection& Section = Menu->FindOrAddSection("GetAssetActions");
48 | Section.AddDynamicEntry("GaussianSplattingEditor ", FNewToolMenuSectionDelegate::CreateLambda([this](FToolMenuSection& Section){
49 | if (UContentBrowserAssetContextMenuContext* Context = Section.FindContext()){
50 | if (Context->SelectedAssets.Num() == 1) {
51 | UGaussianSplattingPointCloud* PointCloud = Cast(Context->SelectedAssets[0].GetAsset());
52 | if (PointCloud == nullptr)
53 | return;
54 | Section.AddMenuEntry(
55 | "GS_CreateNiagara",
56 | LOCTEXT("GS_CreateNiagara", "Create Niagara System"),
57 | LOCTEXT("GS_CreateNiagaraTooltip", "Create Niagara System"),
58 | FSlateIcon(FNiagaraEditorStyle::Get().GetStyleSetName(), "ClassIcon.NiagaraActor"),
59 | FUIAction(
60 | FExecuteAction::CreateRaw(this, &FGaussianSplattingEditorModule::CreateNiagara, PointCloud)
61 | )
62 | );
63 |
64 | //Section.AddMenuEntry(
65 | // "GS_CreateStaticMesh",
66 | // LOCTEXT("GS_CreateStaticMesh", "Create Static Mesh"),
67 | // LOCTEXT("GS_CreateStaticMeshTooltip", "Create Static Mesh"),
68 | // FSlateIcon(FAppStyle::GetAppStyleSetName(), "ClassIcon.StaticMeshActor"),
69 | // FUIAction(
70 | // FExecuteAction::CreateRaw(this, &FGaussianSplattingEditorModule::CreateStaticMesh, PointCloud)
71 | // )
72 | //);
73 | }
74 | }
75 | }));
76 | }
77 |
78 | void FGaussianSplattingEditorModule::CreateStaticMesh(UGaussianSplattingPointCloud* PointCloud)
79 | {
80 | if (PointCloud == nullptr) {
81 | return;
82 | }
83 |
84 | FSaveAssetDialogConfig SaveAssetDialogConfig;
85 | SaveAssetDialogConfig.DefaultPath = FPackageName::GetLongPackagePath(PointCloud->GetOutermost()->GetName());
86 | SaveAssetDialogConfig.DefaultAssetName = FString::Printf(TEXT("SM_%s"), *PointCloud->GetName());
87 | SaveAssetDialogConfig.AssetClassNames.Add(UGaussianSplattingStep_GaussianSplatting::StaticClass()->GetClassPathName());
88 | SaveAssetDialogConfig.ExistingAssetPolicy = ESaveAssetDialogExistingAssetPolicy::AllowButWarn;
89 | SaveAssetDialogConfig.DialogTitleOverride = FText::FromString("Save As");
90 |
91 | const FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser");
92 | const FString SaveObjectPath = ContentBrowserModule.Get().CreateModalSaveAssetDialog(SaveAssetDialogConfig);
93 | if (SaveObjectPath.IsEmpty()){
94 | FNotificationInfo NotifyInfo(FText::FromString("Path is empty"));
95 | NotifyInfo.ExpireDuration = 5.0f;
96 | FSlateNotificationManager::Get().AddNotification(NotifyInfo);
97 | return;
98 | }
99 | const FString PackagePath = FPackageName::ObjectPathToPackageName(SaveObjectPath);
100 | const FString AssetName = FPaths::GetBaseFilename(PackagePath, true);
101 | if (!AssetName.IsEmpty()) {
102 | UPackage* NewPackage = CreatePackage(*PackagePath);
103 | UObject* NewAsset = UGaussianSplattingEditorLibrary::CreateStaticMeshFromPointCloud(PointCloud, NewPackage, *AssetName);
104 | NewAsset->SetFlags(RF_Public | RF_Standalone);
105 | FAssetRegistryModule::AssetCreated(NewAsset);
106 | FPackagePath NewPackagePath = FPackagePath::FromPackageNameChecked(NewPackage->GetName());
107 | FString PackageLocalPath = NewPackagePath.GetLocalFullPath();
108 | UPackage::SavePackage(NewPackage, NewAsset, RF_Public | RF_Standalone, *PackageLocalPath, GError, nullptr, false, true, SAVE_NoError);
109 | TArray ObjectsToSync;
110 | ObjectsToSync.Add(NewAsset);
111 | GEditor->SyncBrowserToObjects(ObjectsToSync);
112 | }
113 | }
114 |
115 | void FGaussianSplattingEditorModule::CreateNiagara(UGaussianSplattingPointCloud* PointCloud)
116 | {
117 | if (PointCloud == nullptr) {
118 | return;
119 | }
120 |
121 | FSaveAssetDialogConfig SaveAssetDialogConfig;
122 | SaveAssetDialogConfig.DefaultPath = FPackageName::GetLongPackagePath(PointCloud->GetOutermost()->GetName());;
123 | SaveAssetDialogConfig.DefaultAssetName = FString::Printf(TEXT("FX_%s"), *PointCloud->GetName());
124 | SaveAssetDialogConfig.AssetClassNames.Add(UGaussianSplattingStep_GaussianSplatting::StaticClass()->GetClassPathName());
125 | SaveAssetDialogConfig.ExistingAssetPolicy = ESaveAssetDialogExistingAssetPolicy::AllowButWarn;
126 | SaveAssetDialogConfig.DialogTitleOverride = FText::FromString("Save As");
127 |
128 | const FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser");
129 | const FString SaveObjectPath = ContentBrowserModule.Get().CreateModalSaveAssetDialog(SaveAssetDialogConfig);
130 | if (SaveObjectPath.IsEmpty()) {
131 | FNotificationInfo NotifyInfo(FText::FromString("Path is empty"));
132 | NotifyInfo.ExpireDuration = 5.0f;
133 | FSlateNotificationManager::Get().AddNotification(NotifyInfo);
134 | return;
135 | }
136 |
137 | const FString PackagePath = FPackageName::ObjectPathToPackageName(SaveObjectPath);
138 | const FString AssetName = FPaths::GetBaseFilename(PackagePath, true);
139 | if (!AssetName.IsEmpty()) {
140 | UPackage* NewPackage = CreatePackage(*PackagePath);
141 | UObject* NewAsset = UGaussianSplattingEditorLibrary::CreateNiagaraSystemFromPointCloud(PointCloud, NewPackage, *AssetName);
142 | NewAsset->SetFlags(RF_Public | RF_Standalone);
143 | FAssetRegistryModule::AssetCreated(NewAsset);
144 | FPackagePath NewPackagePath = FPackagePath::FromPackageNameChecked(NewPackage->GetName());
145 | FString PackageLocalPath = NewPackagePath.GetLocalFullPath();
146 | UPackage::SavePackage(NewPackage, NewAsset, RF_Public | RF_Standalone, *PackageLocalPath, GError, nullptr, false, true, SAVE_NoError);
147 | TArray ObjectsToSync;
148 | ObjectsToSync.Add(NewAsset);
149 | GEditor->SyncBrowserToObjects(ObjectsToSync);
150 | }
151 | }
152 |
153 | #undef LOCTEXT_NAMESPACE
154 |
155 | IMPLEMENT_MODULE(FGaussianSplattingEditorModule, GaussianSplattingEditor)
--------------------------------------------------------------------------------
/Source/GaussianSplattingEditor/Private/GaussianSplattingEditorSettings.cpp:
--------------------------------------------------------------------------------
1 | #include "GaussianSplattingEditorSettings.h"
2 | #include "Interfaces/IPluginManager.h"
3 |
4 | FString UGaussianSplattingEditorSettings::GetWorkHome() const
5 | {
6 | const FString PluginDir = IPluginManager::Get().FindPlugin(TEXT("GaussianSplattingForUnrealEngine"))->GetBaseDir();
7 | return FPaths::ConvertRelativePathToFull(PluginDir) / "Work";
8 | }
9 |
10 | FString UGaussianSplattingEditorSettings::GetWorkDir(FString WorkName) const
11 | {
12 | FString WorkHome = GetWorkHome();
13 | return WorkHome / WorkName;;
14 | }
15 |
16 | FString UGaussianSplattingEditorSettings::GetGaussianSplattingHelperPath() const
17 | {
18 | const FString PluginDir = IPluginManager::Get().FindPlugin(TEXT("GaussianSplattingForUnrealEngine"))->GetBaseDir();
19 | return PluginDir / "Scripts" / "gaussian_splatting_helper.py";
20 | }
21 |
22 | FString UGaussianSplattingEditorSettings::GetPythonExecutablePath() const
23 | {
24 | return PythonExecutablePath.FilePath;
25 | }
26 |
27 | FString UGaussianSplattingEditorSettings::GetColmapExecutablePath() const
28 | {
29 | return ColmapExecutablePath.FilePath;
30 | }
31 |
32 | FString UGaussianSplattingEditorSettings::GetGaussianSplattingRepoDir() const
33 | {
34 | return GaussianSplattingRepoDir.Path;
35 | }
36 |
37 | void UGaussianSplattingEditorSettings::PostInitProperties()
38 | {
39 | Super::PostInitProperties();
40 | PythonExecutablePath.FilePath = PythonExecutablePathConifg;
41 | ColmapExecutablePath.FilePath = ColmapExecutablePathConfig;
42 | GaussianSplattingRepoDir.Path = GaussianSplattingRepoDirConfig;
43 |
44 | if (PythonExecutablePath.FilePath.IsEmpty() || ColmapExecutablePath.FilePath.IsEmpty()) {
45 | FString SystemPath = FPlatformMisc::GetEnvironmentVariable(TEXT("Path"));
46 | TArray PathParts;
47 | SystemPath.ParseIntoArray(PathParts, TEXT(";"), true);
48 | for (const FString& PathPart : PathParts){
49 | FString PythonPath = FPaths::Combine(PathPart, TEXT("python.exe"));
50 | if (FPaths::FileExists(PythonPath)){
51 | PythonExecutablePath.FilePath = PythonPath.Replace(TEXT("\\"), TEXT("/"));
52 | break;
53 | }
54 | }
55 | for (const FString& PathPart : PathParts){
56 | FString ColmapPath = FPaths::Combine(PathPart, TEXT("colmap.exe"));
57 | if (FPaths::FileExists(ColmapPath)){
58 | ColmapExecutablePath.FilePath = ColmapPath.Replace(TEXT("\\"), TEXT("/"));
59 | break;
60 | }
61 | }
62 | }
63 | }
64 |
65 | void UGaussianSplattingEditorSettings::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent)
66 | {
67 | Super::PostEditChangeProperty(PropertyChangedEvent);
68 | const FName PropertyName = PropertyChangedEvent.GetMemberPropertyName();
69 |
70 | if (PropertyName == GET_MEMBER_NAME_CHECKED(UGaussianSplattingEditorSettings, PythonExecutablePath)
71 | || PropertyName == GET_MEMBER_NAME_CHECKED(UGaussianSplattingEditorSettings, ColmapExecutablePath)
72 | || PropertyName == GET_MEMBER_NAME_CHECKED(UGaussianSplattingEditorSettings, GaussianSplattingRepoDir)
73 | ) {
74 | Modify();
75 | PythonExecutablePathConifg = PythonExecutablePath.FilePath;
76 | ColmapExecutablePathConfig = ColmapExecutablePath.FilePath;
77 | GaussianSplattingRepoDirConfig = GaussianSplattingRepoDir.Path;
78 | TryUpdateDefaultConfigFile();
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/Source/GaussianSplattingEditor/Private/GaussianSplattingEditorSettings.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "GaussianSplattingEditorSettings.generated.h"
4 |
5 | UCLASS(EditInlineNew, CollapseCategories, config = GaussianSplattingEditor, defaultconfig)
6 | class UGaussianSplattingEditorSettings : public UObject {
7 | GENERATED_BODY()
8 | public:
9 | FString GetWorkHome() const;
10 |
11 | FString GetWorkDir(FString WorkName) const;
12 |
13 | FString GetGaussianSplattingHelperPath() const;
14 |
15 | FString GetPythonExecutablePath() const;
16 |
17 | FString GetColmapExecutablePath() const;
18 |
19 | FString GetGaussianSplattingRepoDir() const;
20 |
21 | void PostInitProperties() override;
22 |
23 | void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override;
24 | public:
25 | UPROPERTY(EditAnywhere, meta = (FilePathFilter = "exe", RelativeToGameDir), Category = "Gaussian Splatting")
26 | FFilePath PythonExecutablePath;
27 |
28 | UPROPERTY(EditAnywhere, meta = (FilePathFilter = "exe", RelativeToGameDir), Category = "Gaussian Splatting")
29 | FFilePath ColmapExecutablePath;
30 |
31 | UPROPERTY(EditAnywhere, meta = (RelativeToGameDir), Category = "Gaussian Splatting")
32 | FDirectoryPath GaussianSplattingRepoDir;
33 |
34 | UPROPERTY(Config)
35 | FString PythonExecutablePathConifg;
36 |
37 | UPROPERTY(Config)
38 | FString ColmapExecutablePathConfig;
39 |
40 | UPROPERTY(Config)
41 | FString GaussianSplattingRepoDirConfig;
42 | };
43 |
--------------------------------------------------------------------------------
/Source/GaussianSplattingEditor/Private/GaussianSplattingEditorStyle.cpp:
--------------------------------------------------------------------------------
1 | #include "GaussianSplattingEditorStyle.h"
2 | #include "Framework/Application/SlateApplication.h"
3 | #include "Styling/SlateStyleRegistry.h"
4 | #include "Slate/SlateGameResources.h"
5 | #include "Interfaces/IPluginManager.h"
6 | #include "Styling/SlateStyle.h"
7 | #include "Styling/SlateStyleMacros.h"
8 |
9 | #define RootToContentDir Style->RootToContentDir
10 | TSharedPtr FGaussianSplattingEditorStyle::StyleInstance = nullptr;
11 |
12 | void FGaussianSplattingEditorStyle::Initialize()
13 | {
14 | if (!StyleInstance.IsValid())
15 | {
16 | StyleInstance = Create();
17 |
18 | FSlateStyleRegistry::RegisterSlateStyle(*StyleInstance);
19 | }
20 | }
21 |
22 | void FGaussianSplattingEditorStyle::Shutdown()
23 | {
24 | FSlateStyleRegistry::UnRegisterSlateStyle(*StyleInstance);
25 |
26 | ensure(StyleInstance.IsUnique());
27 |
28 | StyleInstance.Reset();
29 | }
30 |
31 | FName FGaussianSplattingEditorStyle::GetStyleSetName()
32 | {
33 | static FName StyleSetName(TEXT("GaussianSplattingEditorStyle"));
34 |
35 | return StyleSetName;
36 | }
37 |
38 | TSharedRef FGaussianSplattingEditorStyle::Create()
39 | {
40 | const FVector2D IconSize(16, 16);
41 | TSharedRef Style = MakeShareable(new FSlateStyleSet("GaussianSplattingEditorStyle"));
42 | Style->SetContentRoot(IPluginManager::Get().FindPlugin("GaussianSplattingForUnrealEngine")->GetBaseDir() / TEXT("Resources"));
43 | Style->Set("GaussianSplattingEditor.Capture", new IMAGE_BRUSH_SVG(TEXT("Capture"), IconSize));
44 | Style->Set("GaussianSplattingEditor.SparseReconstruction", new IMAGE_BRUSH_SVG(TEXT("SparseReconstruction"), IconSize));
45 | Style->Set("GaussianSplattingEditor.GaussianSplatting", new IMAGE_BRUSH_SVG(TEXT("GaussianSplatting"), IconSize));
46 | return Style;
47 | }
48 |
49 | void FGaussianSplattingEditorStyle::ReloadTextures()
50 | {
51 | if (FSlateApplication::IsInitialized())
52 | {
53 | FSlateApplication::Get().GetRenderer()->ReloadTextureResources();
54 | }
55 | }
56 |
57 | const ISlateStyle& FGaussianSplattingEditorStyle::Get()
58 | {
59 | return *StyleInstance;
60 | }
61 |
--------------------------------------------------------------------------------
/Source/GaussianSplattingEditor/Private/GaussianSplattingEditorStyle.h:
--------------------------------------------------------------------------------
1 | // Copyright Epic Games, Inc. All Rights Reserved.
2 |
3 | #pragma once
4 |
5 | #include "CoreMinimal.h"
6 | #include "Styling/SlateStyle.h"
7 |
8 | class FGaussianSplattingEditorStyle
9 | {
10 | public:
11 |
12 | static void Initialize();
13 |
14 | static void Shutdown();
15 |
16 | /** reloads textures used by slate renderer */
17 | static void ReloadTextures();
18 |
19 | /** @return The Slate style set for the Shooter game */
20 | static const ISlateStyle& Get();
21 |
22 | static FName GetStyleSetName();
23 |
24 | private:
25 |
26 | static TSharedRef< class FSlateStyleSet > Create();
27 |
28 | private:
29 |
30 | static TSharedPtr< class FSlateStyleSet > StyleInstance;
31 | };
--------------------------------------------------------------------------------
/Source/GaussianSplattingEditor/Private/GaussianSplattingHLODBuilder.cpp:
--------------------------------------------------------------------------------
1 | #include "GaussianSplattingHLODBuilder.h"
2 | #include "Serialization/ArchiveCrc32.h"
3 | #include "Framework/Notifications/NotificationManager.h"
4 | #include "Widgets/Notifications/SNotificationList.h"
5 | #include "Kismet/GameplayStatics.h"
6 | #include "GaussianSplattingStep.h"
7 | #include "Interfaces/IPluginManager.h"
8 | #include "Components/SceneCaptureComponent2D.h"
9 | #include "GaussianSplattingEditorSettings.h"
10 | #include "NiagaraComponent.h"
11 | #include "AssetCompilingManager.h"
12 | #include "ShaderCompiler.h"
13 | #include "Dom/JsonObject.h"
14 | #include "Kismet/GameplayStatics.h"
15 | #include "Components/SkyLightComponent.h"
16 | #include "LandscapeComponent.h"
17 | #include "GaussianSplattingEditorLibrary.h"
18 | #include "JsonObjectConverter.h"
19 |
20 | UGaussianSplattingHLODBuilderSettings::UGaussianSplattingHLODBuilderSettings(const FObjectInitializer& ObjectInitializer)
21 | : Super(ObjectInitializer)
22 | {
23 | if (!HasAnyFlags(RF_ClassDefaultObject)) {
24 | CaptureSettings = CreateDefaultSubobject("Capture");
25 | SparseReconstructionSettings = CreateDefaultSubobject("SparseReconstruction");
26 | GaussianSplattingEditorSettings = CreateDefaultSubobject("GaussianSplatting");
27 | }
28 | }
29 |
30 | uint32 UGaussianSplattingHLODBuilderSettings::GetCRC() const
31 | {
32 | FArchiveCrc32 Ar;
33 | FString HLODBaseKey = "1EC5FBC75A71412EB296F1E7E8411257";
34 | Ar << HLODBaseKey;
35 | TArray Buffer;
36 | FMemoryWriter SettingsAr(Buffer, true);
37 | CaptureSettings->SerializeScriptProperties(SettingsAr);
38 | SparseReconstructionSettings->SerializeScriptProperties(SettingsAr);
39 | GaussianSplattingEditorSettings->SerializeScriptProperties(SettingsAr);
40 | Ar << Buffer;
41 | uint32 Hash = Ar.GetCrc();
42 | return Hash;
43 | }
44 |
45 | UGaussianSplattingHLODBuilder::UGaussianSplattingHLODBuilder(const FObjectInitializer& ObjectInitializer)
46 | : Super(ObjectInitializer)
47 | {
48 | }
49 |
50 | TSubclassOf UGaussianSplattingHLODBuilder::GetSettingsClass() const
51 | {
52 | return UGaussianSplattingHLODBuilderSettings::StaticClass();
53 | }
54 |
55 | TArray UGaussianSplattingHLODBuilder::Build(const FHLODBuildContext& InHLODBuildContext, const TArray& InSourceComponents) const
56 | {
57 | const FString PluginDir = IPluginManager::Get().FindPlugin(TEXT("GaussianSplattingForUnrealEngine"))->GetBaseDir();
58 | const UGaussianSplattingHLODBuilderSettings* Settings = Cast(HLODBuilderSettings);
59 |
60 | const int32 GarbageCollectionFrequency = 10;
61 | static int32 Counter = 0;
62 | InHLODBuildContext.World->FlushLevelStreaming(EFlushLevelStreamingType::Visibility);
63 |
64 | if (InHLODBuildContext.AssetsBaseName.Contains(TEXT("_DL"))) {
65 | return {};
66 | }
67 |
68 | if (FApp::CanEverRender()){
69 | FAssetCompilingManager::Get().FinishAllCompilation();
70 | FAssetCompilingManager::Get().ProcessAsyncTasks();
71 | UTexture::ForceUpdateTextureStreaming();
72 | IStreamingManager::Get().StreamAllResources();
73 | if (GShaderCompilingManager && GShaderCompilingManager->GetNumRemainingJobs() > 0){
74 | GShaderCompilingManager->FinishAllCompilation();
75 | }
76 | }
77 |
78 | if (!GarbageCollectionFrequency || Counter++ % GarbageCollectionFrequency == 0){
79 | UE_LOG(LogGaussianSplatting, Warning, TEXT("Pre CollectGarbage"));
80 | GEngine->ForceGarbageCollection();
81 | UE_LOG(LogGaussianSplatting, Warning, TEXT("Post CollectGarbage"));
82 | }
83 |
84 | static bool bNeedRecaptureSky = true;
85 | if (bNeedRecaptureSky) {
86 | ASkyLight* SkyLight = Cast(UGameplayStatics::GetActorOfClass(InHLODBuildContext.World, ASkyLight::StaticClass()));
87 | USkyLightComponent* SkyLightComp = SkyLight->GetLightComponent();
88 | SkyLightComp->MarkRenderStateDirty();
89 | SkyLightComp->RecaptureSky();
90 | bNeedRecaptureSky = false;
91 | }
92 |
93 | TObjectPtr CaptureStep = Settings->CaptureSettings;
94 | TObjectPtr SparseReconstructionStep = Settings->SparseReconstructionSettings;
95 | TObjectPtr GaussianSplattingStep = Settings->GaussianSplattingEditorSettings;
96 | CaptureStep->SetWorld(InHLODBuildContext.World);
97 | SparseReconstructionStep->SetWorld(InHLODBuildContext.World);
98 | GaussianSplattingStep->SetWorld(InHLODBuildContext.World);
99 |
100 | const FString WorkDir = GetDefault()->GetWorkDir(InHLODBuildContext.World->GetName() / InHLODBuildContext.AssetsBaseName);
101 | FString PlyPath = FString::Printf(TEXT("%s/output/point_cloud/iteration_%d/point_cloud.ply"), *WorkDir, GaussianSplattingStep->Iterations);
102 | bool bUseCache = FParse::Param(FCommandLine::Get(), TEXT("UseCache"));
103 |
104 | CaptureStep->Activate();
105 | CaptureStep->SetWorkDir(WorkDir);
106 | SparseReconstructionStep->SetWorkDir(WorkDir);
107 | GaussianSplattingStep->SetWorkDir(WorkDir);
108 |
109 | CaptureStep->SourceMode = EGaussianSplattingSourceMode::Select;
110 | CaptureStep->SetSelectionByComponents(InSourceComponents);
111 |
112 | if (!(FPaths::FileExists(PlyPath) && bUseCache)) {
113 | CaptureStep->Capture();
114 | SparseReconstructionStep->ReconstructionSparse(false);
115 | GaussianSplattingStep->Train(false);
116 | }
117 | else {
118 | UE_LOG(LogGaussianSplatting, Warning, TEXT("Use Cache Ply : %s"), *PlyPath);
119 | }
120 |
121 | TSet SourceAssets;
122 | for (auto SourceComponent : InSourceComponents) {
123 | if (UStaticMeshComponent* StaticMeshComp = Cast(SourceComponent)) {
124 | if (UStaticMesh* StaticMesh = StaticMeshComp->GetStaticMesh()) {
125 | SourceAssets.Add(StaticMesh->GetName());
126 | }
127 | }
128 | else if(ULandscapeComponent* LandscapeComp = Cast(SourceComponent)) {
129 | SourceAssets.Add(LandscapeComp->GetOwner()->GetActorLabel());
130 | }
131 | else {
132 | SourceAssets.Add(SourceComponent->GetName());
133 | }
134 | }
135 | FBoxSphereBounds Bounds = CaptureStep->CurrentBounds;
136 | FString InfoPath = FString::Printf(TEXT("%s/output/point_cloud/iteration_%d/point_cloud_meta.json"), *WorkDir, GaussianSplattingStep->Iterations);
137 | TSharedRef WriteJsonObject = MakeShared();
138 |
139 | FGaussianSplattingPointCloudMetaInfo MetaInfo;
140 | MetaInfo.Location = Bounds.Origin;
141 | MetaInfo.BoxExtent = Bounds.BoxExtent;
142 | MetaInfo.SourceAssets = SourceAssets;
143 | FJsonObjectConverter::UStructToJsonObject(FGaussianSplattingPointCloudMetaInfo::StaticStruct(), &MetaInfo, WriteJsonObject, 0, 0);
144 |
145 | FString JsonString;
146 | TSharedRef> JsonWriter = TJsonWriterFactory<>::Create(&JsonString);
147 | if (FJsonSerializer::Serialize(WriteJsonObject, JsonWriter) && JsonWriter->Close()) {
148 | FFileHelper::SaveStringToFile(JsonString, *InfoPath);
149 | }
150 | if (GaussianSplattingStep->bClippingByMask) {
151 | PlyPath = GaussianSplattingStep->Clip(PlyPath);
152 | }
153 | UGaussianSplattingPointCloud* PointCloud = UGaussianSplattingEditorLibrary::LoadSplatPly(PlyPath, InHLODBuildContext.AssetsOuter, *InHLODBuildContext.AssetsBaseName);
154 | if (PointCloud != nullptr) {
155 | UNiagaraSystem* NiagaraSystem = UGaussianSplattingEditorLibrary::CreateNiagaraSystemFromPointCloud(PointCloud, InHLODBuildContext.AssetsOuter, *(InHLODBuildContext.AssetsBaseName + "_Niagara"));
156 | if (NiagaraSystem) {
157 | NiagaraSystem->ClearFlags(RF_Public | RF_Standalone);
158 | UNiagaraComponent* NiagaraComponent = NewObject(InHLODBuildContext.AssetsOuter);
159 | NiagaraComponent->SetWorldLocation(CaptureStep->CurrentBounds.Origin);
160 | NiagaraComponent->SetAsset(NiagaraSystem);
161 | return { NiagaraComponent };
162 | }
163 | }
164 | return {};
165 | }
--------------------------------------------------------------------------------
/Source/GaussianSplattingEditor/Private/GaussianSplattingHLODBuilder.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "WorldPartition/HLOD/HLODBuilder.h"
4 | #include "GaussianSplattingStep.h"
5 | #include "GaussianSplattingHLODBuilder.generated.h"
6 |
7 | UCLASS()
8 | class UGaussianSplattingHLODBuilderSettings : public UHLODBuilderSettings
9 | {
10 | GENERATED_UCLASS_BODY()
11 |
12 | uint32 GetCRC() const override;
13 |
14 | public:
15 | UPROPERTY(VisibleAnywhere, Instanced, NoClear, meta = (EditInline), Category = "Gaussian Splatting")
16 | TObjectPtr CaptureSettings;
17 |
18 | UPROPERTY(VisibleAnywhere, Instanced, NoClear, meta = (EditInline), Category = "Gaussian Splatting")
19 | TObjectPtr SparseReconstructionSettings;
20 |
21 | UPROPERTY(VisibleAnywhere, Instanced, NoClear, meta = (EditInline), Category = "Gaussian Splatting")
22 | TObjectPtr GaussianSplattingEditorSettings;
23 | };
24 |
25 | UCLASS()
26 | class UGaussianSplattingHLODBuilder : public UHLODBuilder
27 | {
28 | GENERATED_UCLASS_BODY()
29 | public:
30 | virtual TSubclassOf GetSettingsClass() const override;
31 | virtual TArray Build(const FHLODBuildContext& InHLODBuildContext, const TArray& InSourceComponents) const override;
32 |
33 | };
34 |
--------------------------------------------------------------------------------
/Source/GaussianSplattingEditor/Private/GaussianSplattingPointCloudAssetFactory.cpp:
--------------------------------------------------------------------------------
1 | #include "GaussianSplattingPointCloudAssetFactory.h"
2 | #include "Misc/Paths.h"
3 | #include "NiagaraSystemFactoryNew.h"
4 | #include "GaussianSplattingEditorLibrary.h"
5 | #include "IContentBrowserSingleton.h"
6 | #include "ContentBrowserModule.h"
7 | #include "Widgets/Notifications/SNotificationList.h"
8 | #include "Framework/Notifications/NotificationManager.h"
9 | #include "AssetRegistry/AssetRegistryModule.h"
10 | #include "GaussianSplattingPointCloudEditor.h"
11 | #include "NiagaraActor.h"
12 | #include "NiagaraComponent.h"
13 | #include "NiagaraFunctionLibrary.h"
14 | #include "GaussianSplattingPointCloudActor.h"
15 |
16 | #define LOCTEXT_NAMESPACE "GaussianSplatting"
17 |
18 | UGaussianSplattingPointCloudAssetFactory::UGaussianSplattingPointCloudAssetFactory(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer)
19 | {
20 | Formats.Add(FString(TEXT("ply;PLY file")) + NSLOCTEXT("GaussianSplattingPointCloud", "PLY", ".ply File").ToString());
21 | SupportedClass = UGaussianSplattingPointCloud::StaticClass();
22 | bCreateNew = false;
23 | bEditorImport = true;
24 | bEditAfterNew = true;
25 | }
26 |
27 | UObject* UGaussianSplattingPointCloudAssetFactory::FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags,
28 | const FString& Filename, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled)
29 | {
30 | FString FileNamePart, FolderPart, ExtensionPart;
31 | FPaths::Split(Filename, FolderPart, FileNamePart, ExtensionPart);
32 | if (ExtensionPart == "ply"){
33 | GEditor->GetEditorSubsystem()->BroadcastAssetPreImport(this, InClass, InParent, *FileNamePart, TEXT("ply"));
34 | UGaussianSplattingPointCloud* PointCloud = UGaussianSplattingEditorLibrary::LoadSplatPly(Filename, InParent, *FileNamePart);
35 | PointCloud->SetFlags(RF_Public | RF_Standalone);
36 | PointCloud->MarkPackageDirty();
37 | GEditor->GetEditorSubsystem()->BroadcastAssetPostImport(this, nullptr);
38 | return PointCloud;
39 | }
40 | return nullptr;
41 | }
42 |
43 |
44 | FText UAssetDefinition_GaussianSplattingPointCloud::GetAssetDisplayName() const
45 | {
46 | return LOCTEXT("GaussianSplattingPointCloud", "GaussianSplattingPointCloud");
47 | }
48 |
49 | TSoftClassPtr UAssetDefinition_GaussianSplattingPointCloud::GetAssetClass() const
50 | {
51 | return UGaussianSplattingPointCloud::StaticClass();
52 | }
53 |
54 | FLinearColor UAssetDefinition_GaussianSplattingPointCloud::GetAssetColor() const
55 | {
56 | return FLinearColor(0.0f, 0.5f, 1.0f);
57 | }
58 |
59 | TConstArrayView UAssetDefinition_GaussianSplattingPointCloud::GetAssetCategories() const
60 | {
61 | static const auto Categories = { EAssetCategoryPaths::Misc };
62 | return Categories;
63 | }
64 |
65 | EAssetCommandResult UAssetDefinition_GaussianSplattingPointCloud::OpenAssets(const FAssetOpenArgs& OpenArgs) const
66 | {
67 | for (UGaussianSplattingPointCloud* PointCloud : OpenArgs.LoadObjects()){
68 | TSharedRef NewGaussianSplattingPointCloudEditor(new FGaussianSplattingPointCloudEditor());
69 | NewGaussianSplattingPointCloudEditor->InitEditor(OpenArgs.GetToolkitMode(), OpenArgs.ToolkitHost, PointCloud);
70 | }
71 | return EAssetCommandResult::Handled;
72 | }
73 |
74 | UActorFactory_GaussianSplattingPointCloud::UActorFactory_GaussianSplattingPointCloud(const FObjectInitializer& ObjectInitializer)
75 | : Super(ObjectInitializer)
76 | {
77 | DisplayName = LOCTEXT("GaussianSplattingPointCloudDisplayName", "Gaussian Splatting Point Cloud");
78 | NewActorClass = AGaussianSplattingPointCloudActor::StaticClass();
79 | bUseSurfaceOrientation = true;
80 | }
81 |
82 | bool UActorFactory_GaussianSplattingPointCloud::CanCreateActorFrom(const FAssetData& AssetData, FText& OutErrorMsg)
83 | {
84 | if (!AssetData.IsValid() || !AssetData.IsInstanceOf(UGaussianSplattingPointCloud::StaticClass())){
85 | OutErrorMsg = NSLOCTEXT("CanCreateActor", "NoGaussianSplattingPointCloud", "A valid point cloud must be specified.");
86 | return false;
87 | }
88 | return true;
89 | }
90 |
91 | void UActorFactory_GaussianSplattingPointCloud::PostSpawnActor(UObject* Asset, AActor* NewActor)
92 | {
93 | Super::PostSpawnActor(Asset, NewActor);
94 |
95 | UGaussianSplattingPointCloud* PointCloud = CastChecked(Asset);
96 |
97 | // Change properties
98 | AGaussianSplattingPointCloudActor* NiagaraActor = CastChecked(NewActor);
99 | UNiagaraComponent* NiagaraComponent = NiagaraActor->GetNiagaraComponent();
100 | check(NiagaraComponent);
101 |
102 | NiagaraComponent->UnregisterComponent();
103 | UGaussianSplattingEditorLibrary::SetupPointCloudToNiagaraComponent(PointCloud, NiagaraComponent);
104 | NiagaraComponent->RegisterComponent();
105 | }
106 |
107 | UObject* UActorFactory_GaussianSplattingPointCloud::GetAssetFromActorInstance(AActor* Instance)
108 | {
109 | check(Instance->IsA(NewActorClass));
110 | AGaussianSplattingPointCloudActor* NiagaraActor = CastChecked(Instance);
111 | UNiagaraComponent* NiagaraComponent = NiagaraActor->GetNiagaraComponent();
112 | check(NiagaraComponent);
113 | if (UNiagaraDataInterfaceGaussianSplattingPointCloud* ArrayDI = UNiagaraFunctionLibrary::GetDataInterface(NiagaraComponent, "PointCloud")) {
114 | return ArrayDI->GetPointCloud();
115 | }
116 | return nullptr;
117 | }
118 |
119 | FQuat UActorFactory_GaussianSplattingPointCloud::AlignObjectToSurfaceNormal(const FVector& InSurfaceNormal, const FQuat& ActorRotation) const
120 | {
121 | // Meshes align the Z (up) axis with the surface normal
122 | return FindActorAlignmentRotation(ActorRotation, FVector(0.f, 0.f, 1.f), InSurfaceNormal);
123 | }
124 |
125 | #undef LOCTEXT_NAMESPACE
--------------------------------------------------------------------------------
/Source/GaussianSplattingEditor/Private/GaussianSplattingPointCloudAssetFactory.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Factories/Factory.h"
4 | #include "AssetTypeActions_Base.h"
5 | #include "AssetDefinitionDefault.h"
6 | #include "ActorFactories/ActorFactory.h"
7 | #include "GaussianSplattingPointCloudAssetFactory.generated.h"
8 |
9 | UCLASS(hidecategories = Object)
10 | class UGaussianSplattingPointCloudAssetFactory: public UFactory{
11 | GENERATED_UCLASS_BODY()
12 | public:
13 | virtual UObject* FactoryCreateFile(UClass* InClass, UObject* InParent, FName InName, EObjectFlags Flags, const FString& Filename, const TCHAR* Parms, FFeedbackContext* Warn, bool& bOutOperationCanceled) override;
14 | };
15 |
16 | UCLASS()
17 | class UAssetDefinition_GaussianSplattingPointCloud: public UAssetDefinitionDefault
18 | {
19 | GENERATED_BODY()
20 | public:
21 | virtual FText GetAssetDisplayName() const override final;
22 | TSoftClassPtr GetAssetClass() const override final;
23 | virtual FLinearColor GetAssetColor() const override final;
24 | TConstArrayView GetAssetCategories() const override final;
25 | EAssetCommandResult OpenAssets(const FAssetOpenArgs& OpenArgs) const override final;
26 | };
27 |
28 | UCLASS(MinimalAPI)
29 | class UActorFactory_GaussianSplattingPointCloud : public UActorFactory
30 | {
31 | GENERATED_UCLASS_BODY()
32 |
33 | //~ Begin UActorFactory Interface
34 | virtual bool CanCreateActorFrom( const FAssetData& AssetData, FText& OutErrorMsg ) override;
35 | virtual void PostSpawnActor( UObject* Asset, AActor* NewActor) override;
36 | virtual UObject* GetAssetFromActorInstance(AActor* ActorInstance) override;
37 | virtual FQuat AlignObjectToSurfaceNormal(const FVector& InSurfaceNormal, const FQuat& ActorRotation) const override;
38 | //~ End UActorFactory Interface
39 | };
40 |
41 |
--------------------------------------------------------------------------------
/Source/GaussianSplattingEditor/Private/GaussianSplattingPointCloudEditor.cpp:
--------------------------------------------------------------------------------
1 | #include "GaussianSplattingPointCloudEditor.h"
2 | #include "AssetEditorModeManager.h"
3 | #include "SGaussianSplattingPointCloudEditorViewport.h"
4 | #include "GaussianSplattingEditorLibrary.h"
5 | #include "EditorViewportTabContent.h"
6 | #include "AdvancedPreviewSceneModule.h"
7 | #include "SGaussianSplattingPointCloudFeatureEditor.h"
8 | #include "NiagaraDataInterfaceArrayFunctionLibrary.h"
9 |
10 | #define LOCTEXT_NAMESPACE "GaussianSplatting"
11 |
12 | const FName GaussianSplattingPointCloudEditorAppIdentifier = FName(TEXT("GaussianSplattingPointCloudEditorApp"));
13 | const FName FGaussianSplattingPointCloudEditor::ViewportTabId(TEXT("GaussianSplattingPointCloudEditor_Viewport"));
14 | const FName FGaussianSplattingPointCloudEditor::PropertiesTabId(TEXT("GaussianSplattingPointCloudEditor_Properties"));
15 | const FName FGaussianSplattingPointCloudEditor::PreviewSceneSettingsTabId(TEXT("GaussianSplattingPointCloudEditor_PreviewScene"));
16 | const FName FGaussianSplattingPointCloudEditor::FeatureEditorTabId(TEXT("GaussianSplattingPointCloudEditor_Features"));
17 |
18 | FGaussianSplattingPointCloudEditor::~FGaussianSplattingPointCloudEditor()
19 | {
20 | GEditor->UnregisterForUndo(this);
21 | }
22 |
23 | UGaussianSplattingPointCloud* FGaussianSplattingPointCloudEditor::GetPointCloud()
24 | {
25 | return PointCloud;
26 | }
27 |
28 | void FGaussianSplattingPointCloudEditor::InitEditor(const EToolkitMode::Type Mode, const TSharedPtr& InitToolkitHost, UGaussianSplattingPointCloud* ObjectToEdit)
29 | {
30 | PointCloud = ObjectToEdit;
31 | PointCloud->SetFlags(RF_Transactional);
32 | const TSharedRef StandaloneDefaultLayout = FTabManager::NewLayout("Standalone_GaussianSplattingPointCloudEditor_Layout_v6")
33 | ->AddArea
34 | (
35 | FTabManager::NewPrimaryArea()->SetOrientation(Orient_Vertical)
36 | ->Split
37 | (
38 | FTabManager::NewSplitter()->SetOrientation(Orient_Horizontal)
39 | ->Split
40 | (
41 | FTabManager::NewSplitter()->SetOrientation(Orient_Vertical)
42 | ->SetSizeCoefficient(0.7f)
43 | ->Split
44 | (
45 | FTabManager::NewStack()
46 | ->AddTab(ViewportTabId, ETabState::OpenedTab)
47 | ->SetSizeCoefficient(0.8f)
48 | ->SetHideTabWell(true)
49 | )
50 | ->Split
51 | (
52 | FTabManager::NewStack()
53 | ->SetSizeCoefficient(0.2f)
54 | ->AddTab(FeatureEditorTabId, ETabState::OpenedTab)
55 | ->SetHideTabWell(true)
56 | )
57 | )
58 | ->Split
59 | (
60 | FTabManager::NewSplitter()->SetOrientation(Orient_Vertical)
61 | ->SetSizeCoefficient(0.25f)
62 | ->Split
63 | (
64 | FTabManager::NewStack()
65 | ->SetSizeCoefficient(0.7f)
66 | ->AddTab(PropertiesTabId, ETabState::OpenedTab)
67 | ->SetForegroundTab(PropertiesTabId)
68 | )
69 | )
70 | )
71 | );
72 |
73 | const bool bCreateDefaultStandaloneMenu = true;
74 | const bool bCreateDefaultToolbar = true;
75 | FAssetEditorToolkit::InitAssetEditor(Mode, InitToolkitHost, GaussianSplattingPointCloudEditorAppIdentifier, StandaloneDefaultLayout, bCreateDefaultToolbar, bCreateDefaultStandaloneMenu, ObjectToEdit);
76 |
77 | GEditor->RegisterForUndo(this);
78 | }
79 |
80 | void FGaussianSplattingPointCloudEditor::SelectPointsByIndex(TArray InIndices)
81 | {
82 | SelectedIndices = InIndices;
83 | TArray Selection;
84 | Selection.AddZeroed(PointCloud->GetPointCount());
85 | for (auto Index : InIndices) {
86 | Selection[Index] = true;
87 | }
88 | UNiagaraDataInterfaceArrayFunctionLibrary::SetNiagaraArrayBool(GetViewport()->GetPreviewComponent(), "Selection", Selection);
89 | }
90 |
91 | void FGaussianSplattingPointCloudEditor::RemovePointsByIndex(TArray InIndices)
92 | {
93 | if (!InIndices.IsEmpty()) {
94 | TArray Points = PointCloud->GetPoints();
95 | InIndices.Sort();
96 | for (int32 i = InIndices.Num() - 1; i >= 0; --i){
97 | int32 IndexToRemove = InIndices[i];
98 | if (IndexToRemove < Points.Num()){
99 | Points.RemoveAt(IndexToRemove);
100 | }
101 | }
102 |
103 | GEditor->BeginTransaction(LOCTEXT("RemovePoints", "Remove Points"));
104 | PointCloud->Modify();
105 | PointCloud->SetPoints(Points);
106 | GEditor->EndTransaction();
107 | }
108 | SelectPointsByIndex({});
109 | }
110 |
111 | void FGaussianSplattingPointCloudEditor::RemoveSelectedPoints()
112 | {
113 | RemovePointsByIndex(SelectedIndices);
114 | }
115 |
116 | void FGaussianSplattingPointCloudEditor::RegisterTabSpawners(const TSharedRef& InTabManager)
117 | {
118 | WorkspaceMenuCategory = InTabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_GaussianSplattingPointCloudEditor", "Gaussian Splatting Point Cloud Editor"));
119 | auto WorkspaceMenuCategoryRef = WorkspaceMenuCategory.ToSharedRef();
120 |
121 | FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
122 |
123 | InTabManager->RegisterTabSpawner(ViewportTabId, FOnSpawnTab::CreateSP(this, &FGaussianSplattingPointCloudEditor::SpawnTab_Viewport))
124 | .SetDisplayName(LOCTEXT("ViewportTab", "Viewport"))
125 | .SetGroup(WorkspaceMenuCategoryRef)
126 | .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Viewports"))
127 | .SetReadOnlyBehavior(ETabReadOnlyBehavior::Custom);
128 |
129 | InTabManager->RegisterTabSpawner(PropertiesTabId, FOnSpawnTab::CreateSP(this, &FGaussianSplattingPointCloudEditor::SpawnTab_Properties))
130 | .SetDisplayName(LOCTEXT("PropertiesTab", "Details"))
131 | .SetGroup(WorkspaceMenuCategoryRef)
132 | .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Details"))
133 | .SetReadOnlyBehavior(ETabReadOnlyBehavior::Custom);
134 |
135 | InTabManager->RegisterTabSpawner(PreviewSceneSettingsTabId, FOnSpawnTab::CreateSP(this, &FGaussianSplattingPointCloudEditor::SpawnTab_PreviewSceneSettings))
136 | .SetDisplayName(LOCTEXT("PreviewSceneTab", "Preview Scene Settings"))
137 | .SetGroup(WorkspaceMenuCategoryRef)
138 | .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Details"))
139 | .SetReadOnlyBehavior(ETabReadOnlyBehavior::Custom);
140 |
141 | InTabManager->RegisterTabSpawner(FeatureEditorTabId, FOnSpawnTab::CreateSP(this, &FGaussianSplattingPointCloudEditor::SpawnTab_FeatureEditor))
142 | .SetDisplayName(LOCTEXT("PropertiesTab", "Features"))
143 | .SetGroup(WorkspaceMenuCategoryRef)
144 | .SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Features"))
145 | .SetReadOnlyBehavior(ETabReadOnlyBehavior::Custom);
146 | }
147 |
148 | void FGaussianSplattingPointCloudEditor::UnregisterTabSpawners(const TSharedRef& InTabManager)
149 | {
150 | FAssetEditorToolkit::UnregisterTabSpawners(InTabManager);
151 |
152 | InTabManager->UnregisterTabSpawner(ViewportTabId);
153 | InTabManager->UnregisterTabSpawner(PropertiesTabId);
154 | }
155 |
156 | TSharedRef FGaussianSplattingPointCloudEditor::SpawnTab_Viewport(const FSpawnTabArgs& Args)
157 | {
158 | TSharedRef DockableTab = SNew(SDockTab);
159 |
160 | TWeakPtr WeakSharedThis(SharedThis(this));
161 | AssetEditorViewportFactoryFunction MakeViewportFunc = [WeakSharedThis](const FAssetEditorViewportConstructionArgs& InArgs)
162 | {
163 | return SNew(SGaussianSplattingPointCloudEditorViewport)
164 | .Editor(WeakSharedThis);
165 | };
166 |
167 | ViewportTabContent = MakeShareable(new FEditorViewportTabContent());
168 | ViewportTabContent->OnViewportTabContentLayoutChanged().AddRaw(this, &FGaussianSplattingPointCloudEditor::OnEditorLayoutChanged);
169 |
170 | const FString LayoutId = FString("GaussianSplattingPointCloudEditorViewport");
171 | ViewportTabContent->Initialize(MakeViewportFunc, DockableTab, LayoutId);
172 | return DockableTab;
173 | }
174 |
175 | TSharedRef FGaussianSplattingPointCloudEditor::SpawnTab_Properties(const FSpawnTabArgs& Args)
176 | {
177 | FPropertyEditorModule& EditModule = FModuleManager::Get().GetModuleChecked("PropertyEditor");
178 | FDetailsViewArgs DetailsViewArgs;
179 | //DetailsViewArgs.bAllowMultipleTopLevelObjects = true;
180 | DetailsViewArgs.bShowObjectLabel = false;
181 | DetailsViewArgs.bAllowSearch = true;
182 | DetailsViewArgs.bAllowFavoriteSystem = true;
183 | DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::ENameAreaSettings::HideNameArea;
184 | DetailsViewArgs.ViewIdentifier = FName("BlueprintDefaults");
185 | auto DetailsView = EditModule.CreateDetailView(DetailsViewArgs);
186 | DetailsView->SetObject(PointCloud);
187 | TWeakPtr WeakSharedThis(SharedThis(this));
188 | TSharedRef DockableTab = SNew(SDockTab)
189 | [
190 | DetailsView
191 | ];
192 | return DockableTab;
193 | }
194 |
195 | TSharedRef FGaussianSplattingPointCloudEditor::SpawnTab_FeatureEditor(const FSpawnTabArgs& Args)
196 | {
197 | TWeakPtr WeakSharedThis(SharedThis(this));
198 | TSharedRef DockableTab = SNew(SDockTab)
199 | [
200 | SAssignNew(FeatureEditor, SGaussianSplattingPointCloudFeatureEditor)
201 | .Editor(WeakSharedThis)
202 | ];
203 | return DockableTab;
204 | }
205 |
206 | TSharedRef FGaussianSplattingPointCloudEditor::SpawnTab_PreviewSceneSettings(const FSpawnTabArgs& Args)
207 | {
208 | check(Args.GetTabId() == PreviewSceneSettingsTabId);
209 | return SAssignNew(PreviewSceneDockTab, SDockTab)
210 | .Label(LOCTEXT("StaticMeshPreviewScene_TabTitle", "Preview Scene Settings"))
211 | [
212 | AdvancedPreviewSettingsWidget.IsValid() ? AdvancedPreviewSettingsWidget.ToSharedRef() : SNullWidget::NullWidget
213 | ];
214 | }
215 |
216 | void FGaussianSplattingPointCloudEditor::PostInitAssetEditor()
217 | {
218 |
219 | }
220 |
221 | void FGaussianSplattingPointCloudEditor::CreateEditorModeManager()
222 | {
223 | TSharedPtr NewManager = MakeShared();
224 | EditorModeManager = NewManager;
225 | }
226 |
227 | TSharedPtr FGaussianSplattingPointCloudEditor::GetViewport() const
228 | {
229 | if (ViewportTabContent.IsValid()){
230 | return StaticCastSharedPtr(ViewportTabContent->GetFirstViewport());
231 | }
232 | return TSharedPtr();
233 | }
234 |
235 | void FGaussianSplattingPointCloudEditor::ClearPropertyEditorSelection()
236 | {
237 | FeatureEditor->ClearSelection();
238 | }
239 |
240 | void FGaussianSplattingPointCloudEditor::OnEditorLayoutChanged()
241 | {
242 | TSharedPtr Viewport = GetViewport();
243 | FAdvancedPreviewSceneModule& AdvancedPreviewSceneModule = FModuleManager::LoadModuleChecked("AdvancedPreviewScene");
244 |
245 | AdvancedPreviewSettingsWidget = AdvancedPreviewSceneModule.CreateAdvancedPreviewSceneSettingsWidget(Viewport->GetPreviewScene(), nullptr, TArray(), TArray());
246 | if (PreviewSceneDockTab.IsValid()){
247 | PreviewSceneDockTab.Pin()->SetContent(AdvancedPreviewSettingsWidget.ToSharedRef());
248 | }
249 | UNiagaraSystem* DebugNiagraSystem = LoadObject(nullptr, TEXT("/GaussianSplattingForUnrealEngine/Niagara/NS_GaussianSplattingPointCloudDebug.NS_GaussianSplattingPointCloudDebug"));
250 | UGaussianSplattingEditorLibrary::SetupPointCloudToNiagaraComponent(PointCloud, Viewport->GetPreviewComponent(), DebugNiagraSystem);
251 | }
252 |
253 | void FGaussianSplattingPointCloudEditor::AddReferencedObjects(FReferenceCollector& Collector)
254 | {
255 | Collector.AddReferencedObject(PointCloud);
256 | }
257 |
258 | FString FGaussianSplattingPointCloudEditor::GetReferencerName() const
259 | {
260 | return TEXT("FGaussianSplattingPointCloudEditor");
261 | }
262 |
263 | void FGaussianSplattingPointCloudEditor::PostUndo(bool bSuccess)
264 | {
265 | if (PointCloud) {
266 | PointCloud->OnPointsChanged.Broadcast();
267 | }
268 | }
269 |
270 | void FGaussianSplattingPointCloudEditor::PostRedo(bool bSuccess)
271 | {
272 | if (PointCloud) {
273 | PointCloud->OnPointsChanged.Broadcast();
274 | }
275 | }
276 |
277 | FName FGaussianSplattingPointCloudEditor::GetToolkitFName() const
278 | {
279 | return FName("GaussianSplattingPointCloudEditor");
280 | }
281 |
282 | FText FGaussianSplattingPointCloudEditor::GetBaseToolkitName() const
283 | {
284 | return LOCTEXT("AppLabel", "Gaussian Splatting Point Cloud Editor");
285 | }
286 |
287 | FString FGaussianSplattingPointCloudEditor::GetWorldCentricTabPrefix() const
288 | {
289 | return LOCTEXT("WorldCentricTabPrefix", "GaussianSplattingPointCloud").ToString();
290 | }
291 |
292 | FLinearColor FGaussianSplattingPointCloudEditor::GetWorldCentricTabColorScale() const
293 | {
294 | return FLinearColor(0.3f, 0.2f, 0.5f, 0.5f);
295 | }
296 |
297 | FString FGaussianSplattingPointCloudEditor::GetDocumentationLink() const
298 | {
299 | return FString(TEXT("Engine/Content/Types/GaussianSplattingPointCloud/Editor"));
300 | }
301 |
302 | #undef LOCTEXT_NAMESPACE
--------------------------------------------------------------------------------
/Source/GaussianSplattingEditor/Private/GaussianSplattingPointCloudEditor.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "CoreMinimal.h"
4 | #include "Engine/EngineBaseTypes.h"
5 | #include "Toolkits/AssetEditorToolkit.h"
6 | #include "GaussianSplattingPointCloud.h"
7 |
8 | class FGaussianSplattingPointCloudEditor : public FAssetEditorToolkit, public FGCObject, public FEditorUndoClient
9 | {
10 | public:
11 | ~FGaussianSplattingPointCloudEditor();
12 | UGaussianSplattingPointCloud* GetPointCloud();
13 | void InitEditor(const EToolkitMode::Type Mode, const TSharedPtr& InitToolkitHost, UGaussianSplattingPointCloud* ObjectToEdit);
14 | void SelectPointsByIndex(TArray InIndices);
15 | void RemovePointsByIndex(TArray InIndices);
16 | void RemoveSelectedPoints();
17 | TSharedPtr GetViewport() const;
18 | void ClearPropertyEditorSelection();
19 | protected:
20 | virtual FName GetToolkitFName() const override;
21 | virtual FText GetBaseToolkitName() const override;
22 | virtual FString GetWorldCentricTabPrefix() const override;
23 | virtual FLinearColor GetWorldCentricTabColorScale() const override;
24 | virtual FString GetDocumentationLink() const override;
25 |
26 | virtual void PostInitAssetEditor() override;
27 | void CreateEditorModeManager() override;
28 | void OnEditorLayoutChanged();
29 |
30 | virtual void RegisterTabSpawners(const TSharedRef& TabManager) override;
31 | virtual void UnregisterTabSpawners(const TSharedRef& TabManager) override;
32 |
33 | TSharedRef SpawnTab_Viewport(const FSpawnTabArgs& Args);
34 | TSharedRef SpawnTab_Properties(const FSpawnTabArgs& Args);
35 | TSharedRef SpawnTab_FeatureEditor(const FSpawnTabArgs& Args);
36 | TSharedRef SpawnTab_PreviewSceneSettings(const FSpawnTabArgs& Args);
37 |
38 | virtual void AddReferencedObjects(FReferenceCollector& Collector) override;
39 | virtual FString GetReferencerName() const override;
40 |
41 | virtual void PostUndo(bool bSuccess) override;
42 | virtual void PostRedo(bool bSuccess) override;
43 | public:
44 | static const FName ViewportTabId;
45 | static const FName PropertiesTabId;
46 | static const FName PreviewSceneSettingsTabId;
47 | static const FName FeatureEditorTabId;
48 |
49 | TObjectPtr PointCloud;
50 | TSharedPtr ViewportTabContent;
51 | TSharedPtr AdvancedPreviewSettingsWidget;
52 | TWeakPtr PreviewSceneDockTab;
53 | TSharedPtr FeatureEditor;
54 | TArray SelectedIndices;
55 | };
56 |
57 |
58 |
--------------------------------------------------------------------------------
/Source/GaussianSplattingEditor/Private/GaussianSplattingStep.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Components/DirectionalLightComponent.h"
4 | #include "Engine/SceneCapture2D.h"
5 | #include "Engine/TextureRenderTarget2D.h"
6 | #include "HAL/RunnableThread.h"
7 | #include "Components/SceneCaptureComponent.h"
8 | #include "Engine/TriggerSphere.h"
9 | #include "NiagaraSystem.h"
10 | #include "Engine/SkyLight.h"
11 | #include "Engine/Scene.h"
12 | #include "GaussianSplattingPointCloud.h"
13 | #include "GaussianSplattingStep.generated.h"
14 |
15 | DECLARE_LOG_CATEGORY_EXTERN(LogGaussianSplatting, Log, All);
16 |
17 | DECLARE_DELEGATE_RetVal(bool, FOnRequestTaskStart)
18 |
19 | UENUM()
20 | enum class EGaussianSplattingSourceMode : uint8
21 | {
22 | Select,
23 | Locate,
24 | Custom,
25 | };
26 |
27 | UENUM()
28 | enum class EGaussianSplattingCameraMode : uint8
29 | {
30 | Hemisphere,
31 | Sphere,
32 | };
33 |
34 |
35 | UENUM()
36 | enum class EGaussianSplattingOutputType: uint8
37 | {
38 | Niagara,
39 | StaticMesh
40 | };
41 |
42 | UCLASS(EditInlineNew, CollapseCategories, config = GaussianSplattingEditor, defaultconfig, meta = (DisplayName = "Gaussian Splatting Editor"))
43 | class UGaussianSplattingStepBase: public UObject {
44 | GENERATED_BODY()
45 | public:
46 | virtual void Activate(){}
47 |
48 | virtual void Deactivate(){}
49 |
50 | void SetWorld(UWorld* InWorld) { World = InWorld; }
51 |
52 | virtual UWorld* GetWorld() const override { return World; }
53 |
54 | void SetWorkDir(FString InWorkDir){ WorkDir = InWorkDir; };
55 |
56 | void ExecuteCommand(FString ExecutePath, FString Command, bool bAsync = true, TFunction FinishedCallback = {});
57 |
58 | virtual void ReceiveMessage(const FString& Message);
59 |
60 | TObjectPtr World;
61 |
62 | FString WorkDir;
63 |
64 | TSharedPtr Worker;
65 |
66 | TSharedPtr WorkThread;
67 |
68 | float TaskProgressPercent = 0.0f;
69 | FText LastTaskStatusText = FText::FromString("");
70 | bool bRequestCancelTask = false;
71 |
72 | FOnRequestTaskStart OnRequestTaskStart;
73 | FSimpleMulticastDelegate OnTaskFinished;
74 | };
75 |
76 | UCLASS(EditInlineNew, CollapseCategories, config = GaussianSplattingEditor, defaultconfig, meta = (DisplayName = "Gaussian Splatting Editor"))
77 | class UGaussianSplattingStep_Capture: public UGaussianSplattingStepBase {
78 | GENERATED_BODY()
79 | public:
80 | void Activate() override;
81 |
82 | void Deactivate() override;
83 |
84 | UFUNCTION(CallInEditor, meta = (DisplayPriority = 1))
85 | void Capture();
86 |
87 | UFUNCTION(CallInEditor, meta = (DisplayPriority = 2))
88 | void PrevCamera();
89 |
90 | UFUNCTION(CallInEditor, meta = (DisplayPriority = 3))
91 | void NextCamera();
92 |
93 | void SetSelectionByComponents(const TArray& InSourceComponents);
94 |
95 | void OnActorSelectionChanged(const TArray& NewSelection, bool bForceRefresh);
96 |
97 | void OnComponentTransformChanged(USceneComponent* Component, ETeleportType TeleportType);
98 |
99 | void UpdateCameraMatrix();
100 |
101 | void SetCurrentCameraIndex(int InIndex);
102 |
103 | void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
104 | public:
105 | UPROPERTY(EditAnywhere, Config, Category = "Gaussian Splatting")
106 | EGaussianSplattingSourceMode SourceMode = EGaussianSplattingSourceMode::Select;
107 |
108 | UPROPERTY(EditAnywhere, Transient, meta = (EditCondition = "SourceMode == EGaussianSplattingSourceMode::Select", EditConditionHides), Category = "Gaussian Splatting")
109 | TArray> SelectionActors;
110 |
111 | UPROPERTY(EditAnywhere, Config, meta = (EditCondition = "SourceMode != EGaussianSplattingSourceMode::Custom", EditConditionHides), Category = "Gaussian Splatting")
112 | EGaussianSplattingCameraMode CameraMode = EGaussianSplattingCameraMode::Hemisphere;
113 |
114 | UPROPERTY(EditAnywhere, Config, meta = (EditCondition = "SourceMode != EGaussianSplattingSourceMode::Custom", EditConditionHides), Category = "Gaussian Splatting")
115 | int FrameXY = 10;
116 |
117 | UPROPERTY(EditAnywhere, Config, meta = (UIMin = 0.01, ClampMin = 0.01, UIMax = 2), meta = (EditCondition = "SourceMode != EGaussianSplattingSourceMode::Custom", EditConditionHides), Category = "Gaussian Splatting")
118 | float CaptureDistanceScale = 0.6f;
119 |
120 | UPROPERTY(VisibleAnywhere, Transient, meta = (EditCondition = "SourceMode == EGaussianSplattingSourceMode::Locate", EditConditionHides), Category = "Gaussian Splatting")
121 | TObjectPtr LocateActor;
122 |
123 | UPROPERTY(EditAnywhere, Transient, meta = (EditCondition = "SourceMode != EGaussianSplattingSourceMode::Select", EditConditionHides), Category = "Gaussian Splatting")
124 | TArray> HiddenActors;
125 |
126 | UPROPERTY(VisibleAnywhere, Transient, Category = "Gaussian Splatting")
127 | TObjectPtr SceneCapture;
128 |
129 | UPROPERTY(VisibleAnywhere, Transient, Category = "Gaussian Splatting")
130 | TObjectPtr RenderTarget;
131 |
132 | UPROPERTY(EditAnywhere, Config, Category = "Gaussian Splatting")
133 | int RenderTargetResolution = 1024;
134 |
135 | UPROPERTY(EditAnywhere, Config, Category = "Gaussian Splatting")
136 | bool bCaptureFinalColor = false;
137 |
138 | UPROPERTY(EditAnywhere, Config, Category = "Gaussian Splatting")
139 | bool bCaptureDepth = true;
140 |
141 | UPROPERTY(EditAnywhere, Config, Category = "Gaussian Splatting")
142 | TArray ShowFlagSettings;
143 |
144 | UPROPERTY(EditAnywhere, Config, Category = "Gaussian Splatting")
145 | struct FPostProcessSettings PostProcessSettings;
146 |
147 | UPROPERTY(EditAnywhere, Transient, meta = (EditCondition = "SourceMode == EGaussianSplattingSourceMode::Custom", EditConditionHides), Category = "Gaussian Splatting")
148 | TArray CameraActors;
149 |
150 | FBoxSphereBounds CurrentBounds;
151 |
152 | int CurrentCameraIndex = 0;
153 | };
154 |
155 | UCLASS(EditInlineNew, CollapseCategories, config = GaussianSplattingEditor, defaultconfig, meta = (DisplayName = "Gaussian Splatting Editor"))
156 | class UGaussianSplattingStep_SparseReconstruction: public UGaussianSplattingStepBase {
157 | GENERATED_BODY()
158 | public:
159 | void Activate() override;
160 |
161 | void Deactivate() override;
162 |
163 | UFUNCTION(CallInEditor, meta = (DisplayPriority = 1))
164 | void Reconstruction();
165 |
166 | UFUNCTION(CallInEditor, meta = (DisplayPriority = 2))
167 | void ColmapEdit();
168 |
169 | UFUNCTION(CallInEditor, meta = (DisplayPriority = 3))
170 | void ColmapView();
171 |
172 | void ReconstructionSparse(bool bAsync, TFunction FinishedCallback = {});
173 |
174 | void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
175 |
176 | void UpdateParams();
177 | public:
178 | UPROPERTY(VisibleAnywhere, Category = "Feature Extractor")
179 | FString FeatureExtractorParams;
180 |
181 | UPROPERTY(EditAnywhere, Config, Category = "Feature Extractor")
182 | int MaxNumFeaturtes = 8192;
183 |
184 | UPROPERTY(EditAnywhere, Config, Category = "Feature Extractor")
185 | FString FeatureExtractorParamsCustom;
186 |
187 | UPROPERTY(VisibleAnywhere, Category = "Exhaustive Matcher")
188 | FString ExhaustiveMatcherParams;
189 |
190 | UPROPERTY(EditAnywhere, Config, Category = "Exhaustive Matcher")
191 | FString ExhaustiveMatcherParamsCustom;
192 |
193 | UPROPERTY(VisibleAnywhere, Category = "Mapper")
194 | FString MapperParams;
195 |
196 | UPROPERTY(EditAnywhere, Config, Category = "Mapper")
197 | int AbsPoseMinNumInliers = 1;
198 |
199 | UPROPERTY(EditAnywhere, Config, Category = "Mapper")
200 | FString MapperParamsCustom;
201 |
202 | UPROPERTY(VisibleAnywhere, Category = "Model Aligner")
203 | FString ModelAlignerParams;
204 |
205 | UPROPERTY(EditAnywhere, Config, Category = "Model Aligner")
206 | FString ModelAlignerParamsCustom;
207 | };
208 |
209 | UCLASS(EditInlineNew, CollapseCategories, config = GaussianSplattingEditor, defaultconfig, meta = (DisplayName = "Gaussian Splatting Editor"))
210 | class UGaussianSplattingStep_GaussianSplatting : public UGaussianSplattingStepBase {
211 | GENERATED_BODY()
212 | public:
213 | void Activate() override;
214 |
215 | void Deactivate() override;
216 |
217 | UFUNCTION(CallInEditor, meta = (DisplayPriority = 1))
218 | void Train();
219 |
220 | void Train(bool bAsync, TFunction FinishedCallback = {});
221 |
222 | FString Clip(FString PlyPath);
223 |
224 | UFUNCTION(CallInEditor, meta = (DisplayPriority = 2))
225 | void Reload();
226 |
227 | UFUNCTION(CallInEditor, meta = (DisplayPriority = 3))
228 | void Export();
229 |
230 | UObject* LoadPly(UObject* Outer, FName AssetName);
231 |
232 | void PostEditChangeProperty(FPropertyChangedEvent& PropertyChangedEvent) override;
233 |
234 | void UpdateParams();
235 | public:
236 | UPROPERTY(VisibleAnywhere, Transient, Category = "Output")
237 | TObjectPtr Result;
238 |
239 | UPROPERTY()
240 | TObjectPtr LocalPackage;
241 |
242 | UPROPERTY(Config)
243 | FString LastSavePath;
244 |
245 | UPROPERTY(VisibleAnywhere, Category="Train")
246 | FString GaussianSplattingTrainParams;
247 |
248 | UPROPERTY(EditAnywhere, Config, Category = "Train", meta = (UIMin = 1, ClampMin = 1, UIMax = 8))
249 | int Resolution = 1;
250 |
251 | UPROPERTY(EditAnywhere, Config, Category = "Train", meta = (Tooltip = "Number of total iterations to train for."))
252 | int Iterations = 7000;
253 |
254 | UPROPERTY(EditAnywhere, Config, AdvancedDisplay, Category = "Train", meta = (Tooltip = "Spherical harmonics features learning rate."))
255 | float Feature_LR = 0.0025f;
256 |
257 | UPROPERTY(EditAnywhere, Config, AdvancedDisplay, Category = "Train", meta = (Tooltip = "Opacity learning rate."))
258 | float Opacity_LR = 0.05f;
259 |
260 | UPROPERTY(EditAnywhere, Config, AdvancedDisplay, Category = "Train", meta = (Tooltip = "Scaling learning rate."))
261 | float Scaling_LR = 0.005f;
262 |
263 | UPROPERTY(EditAnywhere, Config, AdvancedDisplay, Category = "Train", meta = (Tooltip = "Rotation learning rate."))
264 | float Rotation_LR = 0.001f;
265 |
266 | UPROPERTY(EditAnywhere, Config, AdvancedDisplay, Category = "Train", meta = (Tooltip = "Number of steps (from 0) where position learning rate goes from to."))
267 | int Position_LR_MaxSteps = 30000;
268 |
269 | UPROPERTY(EditAnywhere, Config, AdvancedDisplay, Category = "Train", meta = (Tooltip = "Initial 3D position learning rate."))
270 | float Position_LR_Init = 0.00016f;
271 |
272 | UPROPERTY(EditAnywhere, Config, AdvancedDisplay, Category = "Train", meta = (Tooltip = "Final 3D position learning rate."))
273 | float Position_LR_Final = 0.0000016f;
274 |
275 | UPROPERTY(EditAnywhere, Config, AdvancedDisplay, Category = "Train", meta = (Tooltip = "Position learning rate multiplier."))
276 | float Position_LR_DelayMult = 0.01f;
277 |
278 | UPROPERTY(EditAnywhere, Config, AdvancedDisplay, Category = "Train", meta = (Tooltip = "Iteration where densification starts."))
279 | int DensifyFromIter = 500;
280 |
281 | UPROPERTY(EditAnywhere, Config, AdvancedDisplay, Category = "Train", meta = (Tooltip = "Iteration where densification stops."))
282 | int DensifyUntilIter = 15000;
283 |
284 | UPROPERTY(EditAnywhere, Config, AdvancedDisplay, Category = "Train", meta = (Tooltip = "Limit that decides if points should be densified based on 2D position gradient."))
285 | float DensifyGradThreshold = 0.0002f;
286 |
287 | UPROPERTY(EditAnywhere, Config, AdvancedDisplay, Category = "Train", meta = (Tooltip = "How frequently to densify."))
288 | int DensificationInterval = 100;
289 |
290 | UPROPERTY(EditAnywhere, Config, AdvancedDisplay, Category = "Train", meta = (Tooltip = "How frequently to reset opacity."))
291 | int OpacityResetInterval = 3000;
292 |
293 | UPROPERTY(EditAnywhere, Config, AdvancedDisplay, Category = "Train", meta = (UIMin = 0, ClampMin = 0, UIMax = 1))
294 | float Depth_L1_WeightInit = 1.0f;
295 |
296 | UPROPERTY(EditAnywhere, Config, AdvancedDisplay, Category = "Train", meta = (UIMin = 0, ClampMin = 0, UIMax = 1))
297 | float Depth_L1_WeightFinal = 0.01f;
298 |
299 | UPROPERTY(EditAnywhere, Config, AdvancedDisplay, Category = "Train", meta = (UIMin = 0, ClampMin = 0, UIMax = 1, Tooltip = "Influence of SSIM on total loss from 0 to 1."))
300 | float LambdaDssim = 0.2f;
301 |
302 | UPROPERTY(EditAnywhere, Config, AdvancedDisplay, Category = "Train", meta = (UIMin = 0, ClampMin = 0, UIMax = 1, Tooltip = "Percentage of scene extent (0--1) a point must exceed to be forcibly densified"))
303 | float PercentDense = 0.01f;
304 |
305 | UPROPERTY(EditAnywhere, Config, Category = "Load")
306 | EGaussianSplattingCompressionMethod CompressionMethod = EGaussianSplattingCompressionMethod::Zlib;
307 |
308 | UPROPERTY(EditAnywhere, Config, Category = "Load")
309 | bool bClippingByMask = false;
310 |
311 | UPROPERTY(EditAnywhere, Config, meta = (EditCondition = "bClippingByMask", EditConditionHides, UIMin = 1, ClampMin = 0, UIMax = 20), Category = "Load")
312 | int MaskDilation = 5;
313 |
314 | UPROPERTY(EditAnywhere, Config, meta = (EditCondition = "bClippingByMask", EditConditionHides, UIMin = 1, ClampMin = 0.01, UIMax = 1), Category = "Load")
315 | float ClipThreshold = 0.8;
316 |
317 | UPROPERTY(EditAnywhere, Config, Category = "Load")
318 | float DistanceOfObservation = 0.0f;
319 |
320 | UPROPERTY(EditAnywhere, Config, Category = "Load")
321 | float MinScreenSizeOfObservation = 0.01f;
322 |
323 | FSimpleDelegate OnPlyLoadFinished;
324 | };
--------------------------------------------------------------------------------
/Source/GaussianSplattingEditor/Private/SGaussianSplattingEdModePanel.cpp:
--------------------------------------------------------------------------------
1 | #include "SGaussianSplattingEdModePanel.h"
2 | #include "Widgets/Layout/SWrapBox.h"
3 | #include "GaussianSplattingEditorStyle.h"
4 | #include "Kismet/GameplayStatics.h"
5 | #include "Components/SceneCaptureComponent2D.h"
6 | #include "Misc/Optional.h"
7 | #include "LevelEditor.h"
8 | #include "ThumbnailRendering/ThumbnailManager.h"
9 | #include "GaussianSplattingEditorSettings.h"
10 | #include "Widgets/Notifications/SProgressBar.h"
11 | #include "IContentBrowserSingleton.h"
12 | #include "ContentBrowserModule.h"
13 | #include "Widgets/Notifications/SNotificationList.h"
14 | #include "Framework/Notifications/NotificationManager.h"
15 | #include "AssetRegistry/AssetRegistryModule.h"
16 | #include "Widgets/Input/SSegmentedControl.h"
17 | #include "PropertyCustomizationHelpers.h"
18 |
19 | #define LOCTEXT_NAMESPACE "GaussianSplatting"
20 |
21 | SGaussianSplattingEdModePanel::~SGaussianSplattingEdModePanel()
22 | {
23 | if (StepCapture) {
24 | StepCapture->Deactivate();
25 | }
26 | if (StepSparseReconstruction) {
27 | StepSparseReconstruction->Deactivate();
28 | }
29 | if (StepGaussianSplatting) {
30 | StepGaussianSplatting->Deactivate();
31 | }
32 | }
33 |
34 | void SGaussianSplattingEdModePanel::Construct(const FArguments& InArgs)
35 | {
36 | FPropertyEditorModule& EditModule = FModuleManager::Get().GetModuleChecked("PropertyEditor");
37 | FDetailsViewArgs DetailsViewArgs;
38 | //DetailsViewArgs.bAllowMultipleTopLevelObjects = true;
39 | DetailsViewArgs.bShowObjectLabel = false;
40 | DetailsViewArgs.bAllowSearch = true;
41 | DetailsViewArgs.bAllowFavoriteSystem = true;
42 | DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::ENameAreaSettings::HideNameArea;
43 | DetailsViewArgs.ViewIdentifier = FName("BlueprintDefaults");
44 | DetailsView = EditModule.CreateDetailView(DetailsViewArgs);
45 | DetailsView->OnFinishedChangingProperties().AddSP(this, &SGaussianSplattingEdModePanel::OnTabPropertyChanged);
46 |
47 | UWorld* EditorWorld = GEditor->GetEditorWorldContext().World();
48 |
49 | bool bPackageDirty = EditorWorld->GetPackage()->IsDirty();
50 |
51 | UObject* Outer = EditorWorld != nullptr ? EditorWorld->GetPackage() : GetTransientPackage();
52 | WorkDir = GetDefault()->GetWorkDir("GaussianSplattingEditor");
53 |
54 | StepCapture = NewObject(Outer, NAME_None, RF_Transient);
55 | StepSparseReconstruction = NewObject(Outer, NAME_None, RF_Transient);
56 | StepGaussianSplatting = NewObject(Outer, NAME_None, RF_Transient);
57 |
58 | StepCapture->SetWorld(EditorWorld);
59 | StepCapture->SetWorkDir(WorkDir);
60 | StepCapture->LoadConfig();
61 | StepCapture->Activate();
62 |
63 | StepSparseReconstruction->SetWorld(EditorWorld);
64 | StepSparseReconstruction->SetWorkDir(WorkDir);
65 | StepSparseReconstruction->LoadConfig();
66 | StepSparseReconstruction->Activate();
67 |
68 | StepGaussianSplatting->SetWorld(EditorWorld);
69 | StepGaussianSplatting->SetWorkDir(WorkDir);
70 | StepGaussianSplatting->LoadConfig();
71 | StepGaussianSplatting->Activate();
72 |
73 | if (!bPackageDirty) {
74 | EditorWorld->GetPackage()->SetDirtyFlag(false);
75 | }
76 |
77 | StepCapture->OnRequestTaskStart.BindSP(this, &SGaussianSplattingEdModePanel::OnRequestTaskStart, (UGaussianSplattingStepBase*) StepCapture.Get());
78 | StepSparseReconstruction->OnRequestTaskStart.BindSP(this, &SGaussianSplattingEdModePanel::OnRequestTaskStart, (UGaussianSplattingStepBase*)StepSparseReconstruction.Get());
79 | StepGaussianSplatting->OnRequestTaskStart.BindSP(this, &SGaussianSplattingEdModePanel::OnRequestTaskStart, (UGaussianSplattingStepBase*)StepGaussianSplatting.Get());
80 |
81 | StepCapture->OnTaskFinished.AddSP(this, &SGaussianSplattingEdModePanel::OnTaskFinished, (UGaussianSplattingStepBase*)StepCapture.Get());
82 | StepSparseReconstruction->OnTaskFinished.AddSP(this, &SGaussianSplattingEdModePanel::OnTaskFinished, (UGaussianSplattingStepBase*)StepSparseReconstruction.Get());
83 | StepGaussianSplatting->OnTaskFinished.AddSP(this, &SGaussianSplattingEdModePanel::OnTaskFinished, (UGaussianSplattingStepBase*)StepGaussianSplatting.Get());
84 |
85 | ChildSlot
86 | [
87 | SNew(SVerticalBox)
88 | + SVerticalBox::Slot()
89 | .Padding(0)
90 | .AutoHeight()
91 | .MaxHeight(100)
92 | .VAlign(VAlign_Top)
93 | [
94 | SNew(SHorizontalBox)
95 | + SHorizontalBox::Slot()
96 | .FillWidth(100)
97 | [
98 | SNew(SSegmentedControl)
99 | .Value(0) // InitialValue
100 | .OnValueChanged(this, &SGaussianSplattingEdModePanel::OnTabChanged)
101 | + SSegmentedControl::Slot(0)
102 | .Icon(FGaussianSplattingEditorStyle::Get().GetBrush("GaussianSplattingEditor.Capture"))
103 | .Text(LOCTEXT("Capture", "Capture"))
104 |
105 | + SSegmentedControl::Slot(1)
106 | .Icon(FGaussianSplattingEditorStyle::Get().GetBrush("GaussianSplattingEditor.SparseReconstruction"))
107 | .Text(LOCTEXT("Sparse", "Sparse"))
108 |
109 | + SSegmentedControl::Slot(2)
110 | .Icon(FGaussianSplattingEditorStyle::Get().GetBrush("GaussianSplattingEditor.GaussianSplatting"))
111 | .Text(LOCTEXT("Gaussian", "Gaussian"))
112 |
113 | + SSegmentedControl::Slot(3)
114 | .Icon(FAppStyle::Get().GetBrush("Icons.Settings"))
115 | .Text(LOCTEXT("Settings", "Settings"))
116 | ]
117 | + SHorizontalBox::Slot()
118 | .AutoWidth()
119 | .Padding(5)
120 | [
121 | PropertyCustomizationHelpers::MakeBrowseButton(
122 | FSimpleDelegate::CreateSP(this, &SGaussianSplattingEdModePanel::OnClicked_Browse),
123 | FText()
124 | )
125 | ]
126 | ]
127 | + SVerticalBox::Slot()
128 | .AutoHeight()
129 | .HAlign(HAlign_Fill)
130 | .Padding(10, 5)
131 | [
132 | SNew(SHorizontalBox)
133 | .Visibility(this, &SGaussianSplattingEdModePanel::OnGetProgressBarVisibility)
134 | + SHorizontalBox::Slot()
135 | .FillWidth(100)
136 | [
137 | SNew(SBox)
138 | .HeightOverride(5)
139 | [
140 | SNew(SProgressBar)
141 | .Percent(this, &SGaussianSplattingEdModePanel::OnGetProgressPercent)
142 | .FillColorAndOpacity(FSlateColor(FLinearColor(0.0f, 1.0f, 1.0f)))
143 | ]
144 | ]
145 | + SHorizontalBox::Slot()
146 | .HAlign(HAlign_Center)
147 | .AutoWidth()
148 | .Padding(5, 0)
149 | [
150 | SNew(SButton)
151 | .ToolTipText(LOCTEXT("Cancel", "Cancel Current Task"))
152 | .OnClicked(this, &SGaussianSplattingEdModePanel::OnClicked_Cancel)
153 | .ContentPadding(0.0f)
154 | [
155 | SNew(SImage)
156 | .Image(FAppStyle::GetBrush("Symbols.X"))
157 | .DesiredSizeOverride(FVector2D(12, 12))
158 | .ColorAndOpacity(FLinearColor(1.0f, 1.0f, 1.0f, 1.0f))
159 | ]
160 | ]
161 | ]
162 | +SVerticalBox::Slot()
163 | .VAlign(VAlign_Fill)
164 | .FillHeight(100)
165 | [
166 | DetailsView.ToSharedRef()
167 | ]
168 | ];
169 |
170 | OnTabChanged(0);
171 | }
172 |
173 | void SGaussianSplattingEdModePanel::AddReferencedObjects(FReferenceCollector& Collector)
174 | {
175 | Collector.AddReferencedObject(StepCapture);
176 | Collector.AddReferencedObject(StepSparseReconstruction);
177 | Collector.AddReferencedObject(StepGaussianSplatting);
178 | }
179 |
180 | void SGaussianSplattingEdModePanel::OnTabChanged(int32 TabIndex)
181 | {
182 | UObject* ObjectToEdit = nullptr;
183 | if (TabIndex == 0) {
184 | ObjectToEdit = StepCapture;
185 | }
186 | else if (TabIndex == 1) {
187 | ObjectToEdit = StepSparseReconstruction;
188 | }
189 | else if (TabIndex == 2) {
190 | ObjectToEdit = StepGaussianSplatting;
191 | }
192 | else {
193 | ObjectToEdit = GetMutableDefault();
194 | }
195 | DetailsView->SetObject(ObjectToEdit);
196 | }
197 |
198 | void SGaussianSplattingEdModePanel::OnTabPropertyChanged(const FPropertyChangedEvent& ChangedEvent)
199 | {
200 | if (ChangedEvent.GetNumObjectsBeingEdited() == 1) {
201 | if (UObject* ObjectToEdit = const_cast(ChangedEvent.GetObjectBeingEdited(0))) {
202 | ObjectToEdit->TryUpdateDefaultConfigFile();
203 | }
204 | }
205 | }
206 |
207 | bool SGaussianSplattingEdModePanel::OnRequestTaskStart(UGaussianSplattingStepBase* Step)
208 | {
209 | if (CurrentTask != nullptr) {
210 | FNotificationInfo NotifyInfo(LOCTEXT("ExecutionFailed", "Execution failed\n there is currently a running task"));
211 | NotifyInfo.ExpireDuration = 5.0f;
212 | NotifyInfo.bUseSuccessFailIcons = true;
213 | TSharedPtr NotificationPtr = FSlateNotificationManager::Get().AddNotification(NotifyInfo);
214 | if (NotificationPtr){
215 | NotificationPtr->SetCompletionState(SNotificationItem::CS_Fail);
216 | }
217 | return false;
218 | }
219 | CurrentTask = Step;
220 | return true;
221 | }
222 |
223 | EVisibility SGaussianSplattingEdModePanel::OnGetProgressBarVisibility() const
224 | {
225 | return CurrentTask ? EVisibility::Visible : EVisibility::Hidden;
226 | }
227 |
228 | TOptional SGaussianSplattingEdModePanel::OnGetProgressPercent() const
229 | {
230 | return CurrentTask ? CurrentTask->TaskProgressPercent : 0;
231 | }
232 |
233 | void SGaussianSplattingEdModePanel::OnTaskFinished(UGaussianSplattingStepBase* Step)
234 | {
235 | CurrentTask = nullptr;
236 | }
237 |
238 | void SGaussianSplattingEdModePanel::OnClicked_Browse()
239 | {
240 | if (!IFileManager::Get().DirectoryExists(*WorkDir))
241 | return;
242 | FPlatformProcess::ExploreFolder(*WorkDir);
243 | }
244 |
245 | FReply SGaussianSplattingEdModePanel::OnClicked_Cancel()
246 | {
247 | if (CurrentTask) {
248 | CurrentTask->bRequestCancelTask = true;
249 | }
250 | return FReply::Handled();
251 | }
252 |
253 | //FReply SGaussianSplattingEdModePanel::OnClicked_Export()
254 | //{
255 | // UObject* Cache = AssetThumbnail->GetAsset();
256 | // if (!Cache) {
257 | // return FReply::Handled();
258 | // }
259 | // FSaveAssetDialogConfig SaveAssetDialogConfig;
260 | // SaveAssetDialogConfig.DefaultPath = LastSavePath;
261 | // SaveAssetDialogConfig.DefaultAssetName = "GaussianSplattingPoints";
262 | // SaveAssetDialogConfig.AssetClassNames.Add(UTexture2D::StaticClass()->GetClassPathName());
263 | // SaveAssetDialogConfig.ExistingAssetPolicy = ESaveAssetDialogExistingAssetPolicy::AllowButWarn;
264 | // SaveAssetDialogConfig.DialogTitleOverride = FText::FromString("Save As");
265 | //
266 | // const FContentBrowserModule& ContentBrowserModule = FModuleManager::LoadModuleChecked("ContentBrowser");
267 | // const FString SaveObjectPath = ContentBrowserModule.Get().CreateModalSaveAssetDialog(SaveAssetDialogConfig);
268 | // if (SaveObjectPath.IsEmpty()){
269 | // FNotificationInfo NotifyInfo(FText::FromString("Path is empty"));
270 | // NotifyInfo.ExpireDuration = 5.0f;
271 | // FSlateNotificationManager::Get().AddNotification(NotifyInfo);
272 | // return FReply::Handled();
273 | // }
274 | // const FString PackagePath = FPackageName::ObjectPathToPackageName(SaveObjectPath);
275 | // const FString AssetName = FPaths::GetBaseFilename(PackagePath, true);
276 | // LastSavePath = PackagePath;
277 | // if (!AssetName.IsEmpty()) {
278 | // UPackage* NewPackage = CreatePackage(*PackagePath);
279 | // UObject* NewAsset = DuplicateObject(Cache, NewPackage, *AssetName);
280 | // NewAsset->SetFlags(RF_Public | RF_Standalone);
281 | // FAssetRegistryModule::AssetCreated(NewAsset);
282 | // FPackagePath NewPackagePath = FPackagePath::FromPackageNameChecked(NewPackage->GetName());
283 | // FString PackageLocalPath = NewPackagePath.GetLocalFullPath();
284 | // UPackage::SavePackage(NewPackage, NewAsset, RF_Public | RF_Standalone, *PackageLocalPath, GError, nullptr, false, true, SAVE_NoError);
285 | // TArray ObjectsToSync;
286 | // ObjectsToSync.Add(NewAsset);
287 | // GEditor->SyncBrowserToObjects(ObjectsToSync);
288 | // AssetThumbnail->SetAsset(NewAsset);
289 | // AssetThumbnailBox->SetContent(
290 | // AssetThumbnail->MakeThumbnailWidget()
291 | // );
292 | // }
293 | // return FReply::Handled();
294 | //}
295 |
296 |
297 | FText SGaussianSplattingEdModePanel::OnGetStatusText() const
298 | {
299 | //if (CurrentSelectedStepObject)
300 | // return CurrentSelectedStepObject->LastStatusText;
301 | return FText();
302 | }
303 |
304 | #undef LOCTEXT_NAMESPACE
305 |
--------------------------------------------------------------------------------
/Source/GaussianSplattingEditor/Private/SGaussianSplattingEdModePanel.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Components/DirectionalLightComponent.h"
4 | #include "Engine/SceneCapture2D.h"
5 | #include "Engine/TextureRenderTarget2D.h"
6 | #include "HAL/RunnableThread.h"
7 | #include "Widgets/SCompoundWidget.h"
8 | #include "Widgets/Views/SListView.h"
9 | #include "Widgets/Input/STextComboBox.h"
10 | #include "Widgets/Layout/SWidgetSwitcher.h"
11 | #include "Widgets/Input/SSuggestionTextBox.h"
12 | #include "GaussianSplattingStep.h"
13 | #include "AssetThumbnail.h"
14 | #include "Misc/Optional.h"
15 |
16 | class SGaussianSplattingEdModePanel : public SCompoundWidget, public FGCObject
17 | {
18 | public:
19 | SLATE_BEGIN_ARGS(SGaussianSplattingEdModePanel) {}
20 | SLATE_END_ARGS()
21 | public:
22 | ~SGaussianSplattingEdModePanel();
23 | void Construct(const FArguments& InArgs);
24 | protected:
25 | virtual void AddReferencedObjects(FReferenceCollector& Collector) override;
26 |
27 | virtual FString GetReferencerName() const override { return TEXT("GaussianSplattingEditor"); }
28 |
29 | void OnTabChanged(int32 TabIndex);
30 |
31 | void OnTabPropertyChanged(const FPropertyChangedEvent& ChangedEvent);
32 |
33 | bool OnRequestTaskStart(UGaussianSplattingStepBase* Step);
34 |
35 | EVisibility OnGetProgressBarVisibility() const;
36 |
37 | TOptional OnGetProgressPercent() const;
38 |
39 | void OnTaskFinished(UGaussianSplattingStepBase* Step);
40 |
41 | void OnClicked_Browse();
42 |
43 | FReply OnClicked_Cancel();
44 |
45 | FText OnGetStatusText() const;
46 |
47 | private:
48 | FString WorkDir;
49 | TSharedPtr DetailsView;
50 | TObjectPtr StepCapture;
51 | TObjectPtr StepSparseReconstruction;
52 | TObjectPtr StepGaussianSplatting;
53 |
54 | TObjectPtr CurrentTask;
55 | TSharedPtr Worker;
56 | TSharedPtr WorkThread;
57 | FString LastSavePath;
58 | };
--------------------------------------------------------------------------------
/Source/GaussianSplattingEditor/Private/SGaussianSplattingPointCloudEditorViewport.cpp:
--------------------------------------------------------------------------------
1 | #include "SGaussianSplattingPointCloudEditorViewport.h"
2 | #include "Widgets/DeclarativeSyntaxSupport.h"
3 | #include "Editor/UnrealEdEngine.h"
4 | #include "UnrealEdGlobals.h"
5 | #include "EditorViewportCommands.h"
6 | #include "EditorViewportTabContent.h"
7 | #include "Framework/Application/SlateApplication.h"
8 | #include "Framework/MultiBox/MultiBoxBuilder.h"
9 | #include "AdvancedPreviewScene.h"
10 | #include "Particles/ParticlePerfStatsManager.h"
11 | #include "NiagaraSystemInstanceController.h"
12 | #include "SGaussianSplattingPointCloudEditorViewportClient.h"
13 |
14 | #define LOCTEXT_NAMESPACE "GaussianSplatting"
15 |
16 | void SGaussianSplattingPointCloudEditorViewport::Construct(const FArguments& InArgs)
17 | {
18 | Editor = InArgs._Editor;
19 |
20 | PreviewScene = MakeShareable(new FAdvancedPreviewScene(FPreviewScene::ConstructionValues()));
21 | PreviewScene->SetFloorVisibility(false);
22 | PreviewScene->SetEnvironmentVisibility(true);
23 |
24 | PreviewComponent = NewObject(GetTransientPackage(), NAME_None, RF_Transient);
25 | PreviewComponent->CastShadow = 0;
26 | PreviewComponent->bCastDynamicShadow = 0;
27 | PreviewComponent->SetAllowScalability(false);
28 | PreviewComponent->SetForceSolo(true);
29 | PreviewComponent->SetAgeUpdateMode(ENiagaraAgeUpdateMode::TickDeltaTime);
30 | PreviewComponent->SetCanRenderWhileSeeking(false);
31 | PreviewComponent->SetRelativeLocation(FVector::ZeroVector);
32 |
33 | PreviewScene->AddComponent(PreviewComponent, PreviewComponent->GetRelativeTransform());
34 |
35 | SEditorViewport::FArguments ViewportArgs;
36 | SEditorViewport::Construct(ViewportArgs);
37 | }
38 |
39 | TSharedRef SGaussianSplattingPointCloudEditorViewport::MakeEditorViewportClient()
40 | {
41 | TSharedPtr ViewportClient = MakeShareable(new FGaussianSplattingPointCloudEditorViewportClient(nullptr, PreviewScene.Get(), SharedThis(this)));
42 | ViewportClient->SetEditor(Editor);
43 | Client = ViewportClient;
44 | Client->SetViewportType(LVT_Perspective);
45 | Client->SetRealtime(true);
46 | Client->SetViewLocation(EditorViewportDefs::DefaultPerspectiveViewLocation);
47 | Client->SetViewRotation(EditorViewportDefs::DefaultPerspectiveViewRotation);
48 | Client->bSetListenerPosition = false;
49 | return Client.ToSharedRef();
50 | }
51 |
52 | #undef LOCTEXT_NAMESPACE
53 |
--------------------------------------------------------------------------------
/Source/GaussianSplattingEditor/Private/SGaussianSplattingPointCloudEditorViewport.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "CoreMinimal.h"
4 | #include "Widgets/SWidget.h"
5 | #include "SEditorViewport.h"
6 | #include "AssetEditorViewportLayout.h"
7 | #include "NiagaraSystem.h"
8 | #include "NiagaraComponent.h"
9 | #include "SAssetEditorViewport.h"
10 |
11 | class FEditorViewportClient;
12 | class FViewportTabContent;
13 | class FMenuBuilder;
14 | class FAdvancedPreviewScene;
15 | class FGaussianSplattingPointCloudEditor;
16 |
17 | class SGaussianSplattingPointCloudEditorViewport : public SAssetEditorViewport
18 | {
19 | SLATE_BEGIN_ARGS(SGaussianSplattingPointCloudEditorViewport) {}
20 | SLATE_ARGUMENT(TWeakPtr, Editor)
21 | SLATE_END_ARGS()
22 | public:
23 | void Construct(const FArguments& InArgs);
24 | TObjectPtr GetPreviewComponent() { return PreviewComponent; }
25 | TSharedRef GetPreviewScene() { return PreviewScene.ToSharedRef(); }
26 | protected:
27 | virtual void BindCommands() override {}
28 | virtual TSharedRef MakeEditorViewportClient() override;
29 | private:
30 | TWeakPtr Editor;
31 | TSharedPtr PreviewScene;
32 | TObjectPtr PreviewComponent = nullptr;
33 | };
34 |
--------------------------------------------------------------------------------
/Source/GaussianSplattingEditor/Private/SGaussianSplattingPointCloudEditorViewportClient.cpp:
--------------------------------------------------------------------------------
1 | #include "SGaussianSplattingPointCloudEditorViewportClient.h"
2 | #include "SGaussianSplattingPointCloudEditorViewport.h"
3 | #include "GaussianSplattingPointCloudEditor.h"
4 | #include "CanvasTypes.h"
5 | #include "CanvasItem.h"
6 | #include "EditorDragTools.h"
7 | #include "AdvancedPreviewScene.h"
8 |
9 | class FDragTool_PointsFrustumSelect : public FDragTool
10 | {
11 | public:
12 | explicit FDragTool_PointsFrustumSelect(FGaussianSplattingPointCloudEditorViewportClient* InViewportClient)
13 | : FDragTool(InViewportClient->GetModeTools())
14 | , ViewportClient(InViewportClient)
15 | {
16 |
17 | }
18 |
19 | virtual void AddDelta(const FVector& InDelta) override {
20 | FIntPoint MousePos;
21 | ViewportClient->Viewport->GetMousePos(MousePos);
22 |
23 | EndWk = FVector(MousePos);
24 | End = EndWk;
25 | }
26 |
27 | virtual void StartDrag(FEditorViewportClient* InViewportClient, const FVector& InStart, const FVector2D& InStartScreen) override {
28 | FDragTool::StartDrag(InViewportClient, InStart, InStartScreen);
29 |
30 | const bool bUseHoverFeedback = GEditor != NULL && GetDefault()->bEnableViewportHoverFeedback;
31 |
32 | FIntPoint MousePos;
33 | InViewportClient->Viewport->GetMousePos(MousePos);
34 |
35 | Start = FVector(InStartScreen.X, InStartScreen.Y, 0);
36 | End = EndWk = Start;
37 | }
38 |
39 | virtual void EndDrag() override {
40 | const int32 ViewportSizeX = ViewportClient->Viewport->GetSizeXY().X;
41 | const int32 ViewportSizeY = ViewportClient->Viewport->GetSizeXY().Y;
42 | if (Start.X > End.X){
43 | Swap(Start.X, End.X);
44 | }
45 | if (Start.Y > End.Y){
46 | Swap(Start.Y, End.Y);
47 | }
48 |
49 | TSharedRef PreviewScene = ViewportClient->GetEditor()->GetViewport()->GetPreviewScene();
50 | UNiagaraComponent* PreviewComponent = ViewportClient->GetEditor()->GetViewport()->GetPreviewComponent();
51 | UGaussianSplattingPointCloud* PointCloud = ViewportClient->GetEditor()->PointCloud;
52 | const TArray& Points = PointCloud->GetPoints();
53 |
54 | FSceneViewFamilyContext ViewFamily(FSceneViewFamily::ConstructionValues(ViewportClient->Viewport, PreviewScene->GetScene(), ViewportClient->EngineShowFlags));
55 | FSceneView* View = ViewportClient->CalcSceneView(&ViewFamily);
56 | const FMatrix& ViewMatrix = View->ViewMatrices.GetViewMatrix();
57 | const FMatrix& ProjectionMatrix = View->ViewMatrices.GetProjectionMatrix();
58 | const FMatrix ModelMatrix = PreviewComponent->GetComponentTransform().ToMatrixWithScale();
59 | FMatrix MVPMatrix = ModelMatrix * ViewMatrix * ProjectionMatrix;
60 |
61 | TArray SelectedIndices;
62 |
63 | for (int i = 0; i < Points.Num(); i++) {
64 | FVector4 Point(Points[i].Position);
65 | FVector4 TransformedPoint = MVPMatrix.TransformFVector4(Point);
66 | if (TransformedPoint.W != 0.0f) {
67 | TransformedPoint /= TransformedPoint.W;
68 | }
69 | FVector2D ViewportPoint;
70 | ViewportPoint.X = (TransformedPoint.X + 1.0f) * 0.5f * ViewportSizeX;
71 | ViewportPoint.Y = (1.0f - (TransformedPoint.Y + 1.0f) * 0.5f) * ViewportSizeY;
72 | bool bIsInside = (ViewportPoint.X >= Start.X) && (ViewportPoint.X <= End.X) &&
73 | (ViewportPoint.Y >= Start.Y) && (ViewportPoint.Y <= End.Y);
74 | if (bIsInside) {
75 | SelectedIndices.Add(i);
76 | }
77 | }
78 | ViewportClient->GetEditor()->ClearPropertyEditorSelection();
79 | ViewportClient->GetEditor()->SelectPointsByIndex(SelectedIndices);
80 | FDragTool::EndDrag();
81 | }
82 | virtual void Render(const FSceneView* View, FCanvas* Canvas) override {
83 | FCanvasBoxItem BoxItem(FVector2D(Start.X, Start.Y) / Canvas->GetDPIScale(), FVector2D(End.X - Start.X, End.Y - Start.Y) / Canvas->GetDPIScale());
84 | BoxItem.SetColor(FLinearColor::White);
85 | Canvas->DrawItem(BoxItem);
86 |
87 |
88 | }
89 | private:
90 | FGaussianSplattingPointCloudEditorViewportClient* ViewportClient;
91 | };
92 |
93 |
94 | void FGaussianSplattingPointCloudEditorViewportClient::SetEditor(TWeakPtr InEditor)
95 | {
96 | Editor = InEditor;
97 | }
98 |
99 | FGaussianSplattingPointCloudEditor* FGaussianSplattingPointCloudEditorViewportClient::GetEditor()
100 | {
101 | return Editor.Pin().Get();
102 | }
103 |
104 | void FGaussianSplattingPointCloudEditorViewportClient::Tick(float DeltaSeconds)
105 | {
106 | FEditorViewportClient::Tick(DeltaSeconds);
107 | PreviewScene->GetWorld()->Tick(LEVELTICK_All, DeltaSeconds);
108 | }
109 |
110 | void FGaussianSplattingPointCloudEditorViewportClient::DrawCanvas(FViewport& InViewport, FSceneView& View, FCanvas& Canvas)
111 | {
112 | FEditorViewportClient::DrawCanvas(InViewport, View, Canvas);
113 | }
114 |
115 | bool FGaussianSplattingPointCloudEditorViewportClient::InputKey(const FInputKeyEventArgs& EventArgs)
116 | {
117 | if (EventArgs.Key == EKeys::Delete && EventArgs.Event == IE_Pressed) {
118 | Editor.Pin()->RemoveSelectedPoints();
119 | }
120 | return FEditorViewportClient::InputKey(EventArgs);
121 | }
122 |
123 | TSharedPtr FGaussianSplattingPointCloudEditorViewportClient::MakeDragTool(EDragTool::Type DragToolType)
124 | {
125 | TSharedPtr DragTool;
126 | switch (DragToolType){
127 | case EDragTool::BoxSelect:
128 | DragTool = MakeShareable(new FDragTool_PointsFrustumSelect(this));
129 | break;
130 | case EDragTool::FrustumSelect:
131 | DragTool = MakeShareable(new FDragTool_PointsFrustumSelect(this));
132 | break;
133 | case EDragTool::Measure:
134 | break;
135 | case EDragTool::ViewportChange:
136 | break;
137 | };
138 | return DragTool;
139 | }
140 |
--------------------------------------------------------------------------------
/Source/GaussianSplattingEditor/Private/SGaussianSplattingPointCloudEditorViewportClient.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "CoreMinimal.h"
4 | #include "EditorViewportClient.h"
5 |
6 | class FGaussianSplattingPointCloudEditor;
7 |
8 | class FGaussianSplattingPointCloudEditorViewportClient : public FEditorViewportClient
9 | {
10 | public:
11 | using FEditorViewportClient::FEditorViewportClient;
12 | void SetEditor(TWeakPtr InEditor);
13 | FGaussianSplattingPointCloudEditor* GetEditor();
14 | protected:
15 | void Tick(float DeltaSeconds) override;
16 | void DrawCanvas(FViewport& InViewport, FSceneView& View, FCanvas& Canvas) override;
17 | bool InputKey(const FInputKeyEventArgs& EventArgs) override;
18 | TSharedPtr MakeDragTool(EDragTool::Type DragToolType) override;
19 |
20 | private:
21 | TWeakPtr Editor;
22 | };
23 |
--------------------------------------------------------------------------------
/Source/GaussianSplattingEditor/Private/SGaussianSplattingPointCloudFeatureEditor.cpp:
--------------------------------------------------------------------------------
1 | #include "SGaussianSplattingPointCloudFeatureEditor.h"
2 | #include "GaussianSplattingPointCloud.h"
3 | #include "GaussianSplattingPointCloudEditor.h"
4 |
5 | #define MAKE_PAINT_GEOMETRY_PT(Geometry, X, Y) Geometry.ToPaintGeometry(FSlateLayoutTransform(1.0f, FVector2D(X, Y)))
6 | #define MAKE_PAINT_GEOMETRY_RC(Geometry, X, Y, W, H) Geometry.ToPaintGeometry(FVector2D(W, H), FSlateLayoutTransform(1.0f, FVector2D(X, Y)))
7 |
8 | class SGaussianSplattingPointCloudHistogram : public SCompoundWidget
9 | {
10 | public:
11 | SLATE_BEGIN_ARGS(SGaussianSplattingPointCloudHistogram) {}
12 | SLATE_ARGUMENT(TObjectPtr, PointCloud)
13 | SLATE_END_ARGS()
14 | public:
15 | void Construct(const FArguments& InArgs) {
16 | PointCloud = InArgs._PointCloud;
17 | PointCloud->OnPointsChanged.AddSP(this, &SGaussianSplattingPointCloudHistogram::RefreshData);
18 | RefreshData();
19 | SetPixelSnapping(EWidgetPixelSnapping::Disabled);
20 | }
21 | int32 OnPaint(const FPaintArgs& Args, const FGeometry& AllottedGeometry, const FSlateRect& MyCullingRect, FSlateWindowElementList& OutDrawElements, int32 LayerId, const FWidgetStyle& InWidgetStyle, bool bParentEnabled) const override {
22 | static const FSlateBrush* WhiteBrush = FAppStyle::GetBrush("WhiteBrush");
23 | const bool bEnabled = ShouldBeEnabled(bParentEnabled);
24 | const ESlateDrawEffect DrawEffects = bEnabled ? ESlateDrawEffect::NoPixelSnapping : ESlateDrawEffect::DisabledEffect;
25 | int ViewWidth = AllottedGeometry.GetLocalSize().X;
26 | int ViewHeight = AllottedGeometry.GetLocalSize().Y;
27 | float BarWidth = ViewWidth / (float) HistogramData.Num();
28 | for (int i = 0; i < HistogramData.Num(); i++) {
29 | int Count = HistogramData[i];
30 | float Factor = FMath::Clamp(Count / (float) MaxBarCount, 0.0f, 1.0f);
31 | float BarHeight = ViewHeight * Factor;
32 | FLinearColor Color = SelectBar.Contains(i) ? FLinearColor(0.368f, 0.262f, 0.560f) : FLinearColor(0.1f, 0.5f, 0.9f);
33 | FSlateDrawElement::MakeBox(OutDrawElements, LayerId, MAKE_PAINT_GEOMETRY_RC(AllottedGeometry, i * BarWidth, ViewHeight - BarHeight, BarWidth - 1, BarHeight), WhiteBrush, DrawEffects, Color);
34 | }
35 | if (!MouseDownPosition.IsZero()) {
36 | float StartPosX = MouseDownPosition.X;
37 | float EndPosX = MouseMovePosition.X;
38 | if (StartPosX > EndPosX) {
39 | Swap(StartPosX, EndPosX);
40 | }
41 | FSlateDrawElement::MakeBox(OutDrawElements, LayerId, MAKE_PAINT_GEOMETRY_RC(AllottedGeometry, StartPosX, 0, EndPosX - StartPosX, ViewHeight), WhiteBrush, DrawEffects, FLinearColor(0.368f, 0.262f, 0.560f, 0.5f));
42 | }
43 | return LayerId;
44 | }
45 |
46 | FReply OnMouseButtonDown(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override {
47 | if (MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton)) {
48 | MouseDownPosition = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
49 | MouseMovePosition = MouseDownPosition;
50 | return FReply::Handled().CaptureMouse(SharedThis(this));
51 | }
52 | return FReply::Handled();
53 | }
54 | FReply OnMouseMove(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override {
55 | if (MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton)) {
56 | MouseMovePosition = MyGeometry.AbsoluteToLocal(MouseEvent.GetScreenSpacePosition());
57 | }
58 | return FReply::Handled();
59 | }
60 | FReply OnMouseButtonUp(const FGeometry& MyGeometry, const FPointerEvent& MouseEvent) override {
61 | if (!MouseEvent.IsAltDown()) {
62 | SelectBar.Reset();
63 | }
64 | int ViewWidth = MyGeometry.GetLocalSize().X;
65 | float StartPosX = MouseDownPosition.X;
66 | float EndPosX = MouseMovePosition.X;
67 | if (StartPosX > EndPosX) {
68 | Swap(StartPosX, EndPosX);
69 | }
70 | float BarWidth = ViewWidth / (float)HistogramData.Num();
71 | for (int i = 0; i < HistogramData.Num(); i++) {
72 | float BarLeft = i * BarWidth;
73 | float BarRight = BarLeft + BarWidth;
74 | if ((BarLeft >= StartPosX && BarLeft < EndPosX) ||(BarRight >= StartPosX && BarRight < EndPosX)) {
75 | SelectBar.Add(i);
76 | }
77 | }
78 | FString Str;
79 | for (auto Item : GetSelectIndices()) {
80 | Str += FString::Printf(TEXT(", %d"), (int)Item);
81 | }
82 | OnSelectChanged.ExecuteIfBound();
83 | MouseDownPosition = FVector2D::ZeroVector;
84 | MouseMovePosition = FVector2D::ZeroVector;
85 | return FReply::Handled().ReleaseMouseCapture();
86 | }
87 | void RefreshData() {
88 | auto Points = PointCloud->GetPoints();
89 | if (!Points.IsEmpty()) {
90 | float MaxSize = Points[0].Scale.Length();
91 | float MinSize = Points.Last().Scale.Length();
92 | HistogramData.Reset();
93 | HistogramData.AddZeroed(FMath::Min(NumOfBar, Points.Num()));
94 | MaxBarCount = 0;
95 | for (int i = 0; i < Points.Num(); i++) {
96 | float Size = Points[i].Scale.Length();
97 | int Index = ((Size - MinSize) / ( MaxSize - MinSize)) * (HistogramData.Num() - 1);
98 | Index = FMath::Clamp(Index, 0, HistogramData.Num() - 1);
99 | HistogramData[Index]++;
100 | MaxBarCount = FMath::Max(MaxBarCount, HistogramData[Index]);
101 | }
102 | }
103 | SelectBar.Reset();
104 | }
105 |
106 | TArray GetSelectIndices() {
107 | TArray Indices;
108 | if (SelectBar.IsEmpty())
109 | return Indices;
110 | int NumPoint = PointCloud->GetPoints().Num();
111 | int StartIndex = 0;
112 | for (int i = HistogramData.Num() - 1; i >= 0 ; i--) {
113 | int BarCount = HistogramData[i];
114 | if (SelectBar.Contains(i)) {
115 | for (int j = 0; j < BarCount; j++) {
116 | Indices.Add(StartIndex + j);
117 | }
118 | }
119 | StartIndex += BarCount;
120 | }
121 | Indices.Sort();
122 | return Indices;
123 | }
124 | public:
125 | TObjectPtr PointCloud;
126 | const int NumOfBar = 128;
127 | TArray HistogramData;
128 | TSet SelectBar;
129 | int MaxBarCount = 0;
130 | FVector2D MouseDownPosition = FVector2D::ZeroVector;
131 | FVector2D MouseMovePosition = FVector2D::ZeroVector;
132 | FSimpleDelegate OnSelectChanged;
133 | };
134 |
135 | void SGaussianSplattingPointCloudFeatureEditor::Construct(const FArguments& InArgs)
136 | {
137 | Editor = InArgs._Editor;
138 |
139 | ChildSlot
140 | [
141 | SNew(SBox)
142 | .HeightOverride(100)
143 | [
144 | SAssignNew(Histogram, SGaussianSplattingPointCloudHistogram)
145 | .PointCloud(Editor.Pin()->PointCloud)
146 | ]
147 | ];
148 |
149 | Histogram->OnSelectChanged.BindSPLambda(this, [this]() {
150 | Editor.Pin()->SelectPointsByIndex(Histogram->GetSelectIndices());
151 | });
152 | }
153 |
154 | void SGaussianSplattingPointCloudFeatureEditor::ClearSelection()
155 | {
156 | Histogram->SelectBar.Reset();
157 | }
158 |
159 | FReply SGaussianSplattingPointCloudFeatureEditor::OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent)
160 | {
161 | if (InKeyEvent.GetKey() == EKeys::Delete)
162 | {
163 | if (TSharedPtr EditorPtr = Editor.Pin())
164 | {
165 | EditorPtr->RemoveSelectedPoints();
166 | }
167 | return FReply::Handled();
168 | }
169 | return FReply::Unhandled();
170 | }
171 |
172 |
--------------------------------------------------------------------------------
/Source/GaussianSplattingEditor/Private/SGaussianSplattingPointCloudFeatureEditor.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Widgets/SCompoundWidget.h"
4 |
5 | class FGaussianSplattingPointCloudEditor;
6 |
7 | class SGaussianSplattingPointCloudFeatureEditor : public SCompoundWidget
8 | {
9 | SLATE_BEGIN_ARGS(SGaussianSplattingPointCloudFeatureEditor) {}
10 | SLATE_ARGUMENT(TWeakPtr, Editor)
11 | SLATE_END_ARGS()
12 | public:
13 | void Construct(const FArguments& InArgs);
14 | void ClearSelection();
15 | protected:
16 | virtual bool SupportsKeyboardFocus() const override { return true; }
17 | virtual FReply OnKeyDown(const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent) override;
18 | private:
19 | TWeakPtr Editor;
20 | TSharedPtr Histogram;
21 | };
22 |
--------------------------------------------------------------------------------
/Source/GaussianSplattingEditor/Public/GaussianSplattingEditorLibrary.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "CoreMinimal.h"
4 | #include "Engine/Texture2D.h"
5 | #include "NiagaraSystem.h"
6 | #include "GaussianSplattingPointCloudDataInterface.h"
7 | #include "GaussianSplattingEditorLibrary.generated.h"
8 |
9 | USTRUCT(BlueprintType)
10 | struct FGaussianSplattingPointCloudMetaInfo
11 | {
12 | GENERATED_BODY()
13 | public:
14 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Gaussian Splatting")
15 | FVector Location = FVector::ZeroVector;
16 |
17 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Gaussian Splatting")
18 | FVector BoxExtent = FVector::ZeroVector;
19 |
20 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Gaussian Splatting")
21 | TSet SourceAssets;
22 | };
23 |
24 | USTRUCT(BlueprintType)
25 | struct FGaussianSplattingPointCloudsImportSettings
26 | {
27 | GENERATED_BODY()
28 | public:
29 | UPROPERTY(BlueprintReadWrite, Category = "Gaussian Splatting")
30 | TObjectPtr World;
31 |
32 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Gaussian Splatting")
33 | FString SearchDir;
34 |
35 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Gaussian Splatting")
36 | FString SaveContentDir;
37 |
38 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Gaussian Splatting")
39 | TSoftObjectPtr TemplateSystem;
40 |
41 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Gaussian Splatting")
42 | bool bUseStandaloneNiagraSystem = false;
43 |
44 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Gaussian Splatting")
45 | bool bRepartition = false;
46 |
47 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (EditCondition = bRepartition), Category = "Gaussian Splatting")
48 | FString PartitionBaseName = "Cell";
49 |
50 | UPROPERTY(BlueprintReadWrite, EditAnywhere, meta = (EditCondition = bRepartition), Category = "Gaussian Splatting")
51 | int32 CellSize = 25600;
52 | };
53 |
54 | UCLASS()
55 | class GAUSSIANSPLATTINGEDITOR_API UGaussianSplattingEditorLibrary : public UBlueprintFunctionLibrary
56 | {
57 | GENERATED_BODY()
58 | public:
59 | UFUNCTION(BlueprintCallable, Category = "Gaussian Splatting")
60 | static UGaussianSplattingPointCloud* LoadSplatPly(FString FileName, UObject* Outer, FName AssetName = NAME_None);
61 |
62 | UFUNCTION(BlueprintCallable, Category = "Gaussian Splatting")
63 | static UNiagaraSystem* CreateNiagaraSystemFromPointCloud(UGaussianSplattingPointCloud* PointCloud, UObject* Outer, FName AssetName = NAME_None, UNiagaraSystem* Template = nullptr);
64 |
65 | UFUNCTION(BlueprintCallable, Category = "Gaussian Splatting")
66 | static void SetupPointCloudToNiagaraComponent(UGaussianSplattingPointCloud* PointCloud, UNiagaraComponent* NiagaraComponent, UNiagaraSystem* NiagaraSystem = nullptr);
67 |
68 | UFUNCTION(BlueprintCallable, Category = "Gaussian Splatting")
69 | static UStaticMesh* CreateStaticMeshFromPointCloud(UGaussianSplattingPointCloud* PointCloud, UObject* Outer, FName AssetName = NAME_None);
70 |
71 | static UTexture2D* CreateFloat16TextureFromData(UObject* Outer, FString Name, uint32 Width, uint32 Height, TArray Data);
72 |
73 | static void FakeEngineTick(UWorld* InWorld, float InDelta = 0.03f, int InCount = 1);
74 |
75 | static FLinearColor SRGBToLinear(const FLinearColor& Color);
76 |
77 | static FLinearColor LinearToSRGB(const FLinearColor& Color);
78 |
79 | static FVector2D GetTangledUV(int FrameXY, int Index);
80 |
81 | static FVector UVtoPyramid(FVector2D UV);
82 |
83 | static FVector UVtoOctahedron(FVector2D uv);
84 |
85 | UFUNCTION(BlueprintCallable, Category = "Gaussian Splatting")
86 | static void ImportPointClouds(
87 | UWorld* World,
88 | FString SearchDir,
89 | FString SaveContentDir,
90 | UNiagaraSystem* TemplateSystem = nullptr,
91 | bool bUseStandaloneNiagraSystem = false
92 | );
93 |
94 | UFUNCTION(BlueprintCallable, Category = "Gaussian Splatting")
95 | static void RepartitionPointClouds(
96 | UWorld* World,
97 | FString PartitionBaseName = "Cell",
98 | int32 CellSize = 51200,
99 | UNiagaraSystem* TemplateSystem = nullptr,
100 | bool bUseStandaloneNiagraSystem = false
101 | );
102 | };
103 |
--------------------------------------------------------------------------------
/Source/GaussianSplattingEditor/Public/GaussianSplattingEditorModule.h:
--------------------------------------------------------------------------------
1 | #pragma once
2 |
3 | #include "Modules/ModuleManager.h"
4 | #include "GaussianSplattingPointCloudAssetFactory.h"
5 |
6 | class UGaussianSplattingPointCloud;
7 |
8 | class FGaussianSplattingEditorModule : public IModuleInterface
9 | {
10 | public:
11 | virtual void StartupModule() override;
12 | virtual void ShutdownModule() override;
13 | private:
14 | void RegisterMenus();
15 | void CreateStaticMesh(UGaussianSplattingPointCloud* PointCloud);
16 | void CreateNiagara(UGaussianSplattingPointCloud* PointCloud);
17 | };
18 |
--------------------------------------------------------------------------------
/Source/GaussianSplattingRuntime/GaussianSplattingRuntime.build.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using UnrealBuildTool;
3 |
4 | public class GaussianSplattingRuntime: ModuleRules
5 | {
6 | public GaussianSplattingRuntime(ReadOnlyTargetRules Target) : base(Target)
7 | {
8 | PCHUsage = ModuleRules.PCHUsageMode.UseExplicitOrSharedPCHs;
9 |
10 |
11 |
12 | PublicIncludePaths.AddRange(
13 | new string[] {
14 | }
15 | );
16 |
17 | string EnginePath = Path.GetFullPath(Target.RelativeEnginePath);
18 | PrivateIncludePaths.AddRange(new string[] {
19 | Path.Combine(EnginePath, "Plugins/FX/Niagara/Source/Niagara/Private")
20 | });
21 |
22 |
23 | PublicDependencyModuleNames.AddRange(
24 | new string[]
25 | {
26 | "Core",
27 | "RenderCore",
28 | "Engine",
29 | "RHI",
30 | "InputCore",
31 | }
32 | );
33 |
34 |
35 | PrivateDependencyModuleNames.AddRange(
36 | new string[]
37 | {
38 | "CoreUObject",
39 | "Engine",
40 | "Projects",
41 | "Slate",
42 | "SlateCore",
43 | "Niagara",
44 | "NiagaraShader",
45 | "zlib"
46 | }
47 | );
48 | }
49 | }
50 |
--------------------------------------------------------------------------------
/Source/GaussianSplattingRuntime/Private/Compression/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2024 Niantic Labs
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
--------------------------------------------------------------------------------
/Source/GaussianSplattingRuntime/Private/Compression/Spz.cpp:
--------------------------------------------------------------------------------
1 | #include "Spz.h"
2 |
3 | #include
4 | #include
5 | #include
6 | #include
7 | #include
8 | #include
9 | #include
10 | #include
11 | #include
12 |
13 | #ifdef ANDROID
14 | #include
15 | #endif
16 |
17 | #include
18 | #include
19 | #include
20 | #include
21 | #include
22 | #include
23 | #include
24 | #include
25 |
26 | namespace Spz {
27 |
28 | class MemBuf : public std::streambuf {
29 | public:
30 | MemBuf(const uint8_t* begin, const uint8_t* end) {
31 | char* pBegin = const_cast(reinterpret_cast(begin));
32 | char* pEnd = const_cast(reinterpret_cast(end));
33 | this->setg(pBegin, pBegin, pEnd);
34 | }
35 | };
36 |
37 | namespace {
38 |
39 | #ifdef SPZ_ENABLE_LOGS
40 | #ifdef ANDROID
41 | static constexpr char LOG_TAG[] = "SPZ";
42 | template static void SpzLog(const char* fmt, Args &&...args) {
43 | __android_log_print(ANDROID_LOG_INFO, LOG_TAG, fmt,
44 | std::forward(args)...);
45 | }
46 | #else
47 | template static void SpzLog(const char* fmt, Args &&...args) {
48 | printf(fmt, std::forward(args)...);
49 | printf("\n");
50 | fflush(stdout);
51 | }
52 | #endif // ANDROID
53 |
54 | template static void SpzLog(const char* fmt) {
55 | SpzLog("%s", fmt);
56 | }
57 | #else
58 | // No-op versions when logging is disabled
59 | template static void SpzLog(const char* fmt, Args &&...args) {}
60 |
61 | template static void SpzLog(const char* fmt) {}
62 | #endif // SPZ_ENABLE_LOGS
63 |
64 | // Scale factor for DC color components. To convert to RGB, we should multiply
65 | // by 0.282, but it can be useful to represent base colors that are out of range
66 | // if the higher spherical harmonics bands bring them back into range so we
67 | // multiply by a smaller value.
68 | constexpr float colorScale = 0.15f;
69 |
70 | int degreeForDim(int dim) {
71 | if (dim < 3)
72 | return 0;
73 | if (dim < 8)
74 | return 1;
75 | if (dim < 15)
76 | return 2;
77 | return 3;
78 | }
79 |
80 | int dimForDegree(int degree) {
81 | switch (degree) {
82 | case 0:
83 | return 0;
84 | case 1:
85 | return 3;
86 | case 2:
87 | return 8;
88 | case 3:
89 | return 15;
90 | default:
91 | SpzLog("[SPZ: ERROR] Unsupported SH degree: %d\n", degree);
92 | return 0;
93 | }
94 | }
95 |
96 | uint8_t toUint8(float x) {
97 | return static_cast(std::clamp(std::round(x), 0.0f, 255.0f));
98 | }
99 |
100 | // Quantizes to 8 bits, the round to nearest bucket center. 0 always maps to a
101 | // bucket center.
102 | uint8_t quantizeSH(float x, int bucketSize) {
103 | int q = static_cast(std::round(x * 128.0f) + 128.0f);
104 | q = (q + bucketSize / 2) / bucketSize * bucketSize;
105 | return static_cast(std::clamp(q, 0, 255));
106 | }
107 |
108 | float unquantizeSH(uint8_t x) {
109 | return (static_cast(x) - 128.0f) / 128.0f;
110 | }
111 |
112 | float sigmoid(float x) { return 1 / (1 + std::exp(-x)); }
113 |
114 | float invSigmoid(float x) { return std::log(x / (1.0f - x)); }
115 |
116 | template size_t countBytes(const std::vector& vec) {
117 | return vec.size() * sizeof(vec[0]);
118 | }
119 |
120 | #define CHECK(x) \
121 | { \
122 | if (!(x)) { \
123 | SpzLog("[SPZ: ERROR] Check failed: %s:%d: %s", __FILE__, __LINE__, #x); \
124 | return false; \
125 | } \
126 | }
127 |
128 | #define CHECK_GE(x, y) CHECK((x) >= (y))
129 | #define CHECK_LE(x, y) CHECK((x) <= (y))
130 | #define CHECK_EQ(x, y) CHECK((x) == (y));
131 |
132 | bool checkSizes(const PackedGaussians& packed, int numPoints, bool usesFloat16) {
133 | CHECK_EQ(packed.positions.size(), numPoints * 3 * (usesFloat16 ? 2 : 3));
134 | CHECK_EQ(packed.scales.size(), numPoints * 3);
135 | CHECK_EQ(packed.rotations.size(), numPoints * 3);
136 | CHECK_EQ(packed.alphas.size(), numPoints);
137 | CHECK_EQ(packed.colors.size(), numPoints * 3);
138 | return true;
139 | }
140 |
141 | constexpr uint8_t FlagAntialiased = 0x1;
142 |
143 | struct PackedGaussiansHeader {
144 | uint32_t magic = 0x5053474e; // NGSP = Niantic gaussian splat
145 | uint32_t version = 2;
146 | uint32_t numPoints = 0;
147 | uint8_t shDegree = 0;
148 | uint8_t fractionalBits = 0;
149 | uint8_t flags = 0;
150 | uint8_t reserved = 0;
151 | };
152 |
153 | bool decompressGzippedImpl(
154 | const uint8_t* compressed, size_t size, int windowSize, std::vector* out) {
155 | std::vector buffer(8192);
156 | z_stream stream = {};
157 | stream.next_in = const_cast(compressed);
158 | stream.avail_in = size;
159 | if (inflateInit2(&stream, windowSize) != Z_OK) {
160 | return false;
161 | }
162 | out->clear();
163 | bool success = false;
164 | while (true) {
165 | stream.next_out = buffer.data();
166 | stream.avail_out = buffer.size();
167 | int res = inflate(&stream, Z_NO_FLUSH);
168 | if (res != Z_OK && res != Z_STREAM_END) {
169 | break;
170 | }
171 | out->insert(out->end(), buffer.data(), buffer.data() + buffer.size() - stream.avail_out);
172 | if (res == Z_STREAM_END) {
173 | success = true;
174 | break;
175 | }
176 | }
177 | inflateEnd(&stream);
178 | return success;
179 | }
180 |
181 | bool decompressGzipped(const uint8_t* compressed, size_t size, std::vector* out) {
182 | // Here 16 means enable automatic gzip header detection; consider switching this to 32 to enable
183 | // both automated gzip and zlib header detection.
184 | return decompressGzippedImpl(compressed, size, 16 | MAX_WBITS, out);
185 | }
186 |
187 | bool decompressGzipped(const uint8_t* compressed, size_t size, std::string* out) {
188 | std::vector buffer;
189 | if (!decompressGzipped(compressed, size, &buffer)) {
190 | return false;
191 | }
192 | out->assign(reinterpret_cast(buffer.data()), buffer.size());
193 | return true;
194 | }
195 |
196 | bool compressGzipped(const uint8_t* data, size_t size, std::vector* out) {
197 | std::vector buffer(8192);
198 | z_stream stream = {};
199 | if (
200 | deflateInit2(&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, 16 + MAX_WBITS, 9, Z_DEFAULT_STRATEGY)
201 | != Z_OK) {
202 | return false;
203 | }
204 | out->clear();
205 | out->reserve(size / 4);
206 | stream.next_in = const_cast(reinterpret_cast(data));
207 | stream.avail_in = size;
208 | bool success = false;
209 | while (true) {
210 | stream.next_out = buffer.data();
211 | stream.avail_out = buffer.size();
212 | int res = deflate(&stream, Z_FINISH);
213 | if (res != Z_OK && res != Z_STREAM_END) {
214 | break;
215 | }
216 | out->insert(out->end(), buffer.data(), buffer.data() + buffer.size() - stream.avail_out);
217 | if (res == Z_STREAM_END) {
218 | success = true;
219 | break;
220 | }
221 | }
222 | deflateEnd(&stream);
223 | return success;
224 | }
225 | } // namespace
226 |
227 | PackedGaussians packGaussians(const TArray& g) {
228 |
229 | const int numPoints = g.Num();
230 |
231 | // Use 12 bits for the fractional part of coordinates (~0.25 millimeter
232 | // resolution). In the future we can use different values on a per-splat basis
233 | // and still be compatible with the decoder.
234 | PackedGaussians packed = {
235 | .numPoints = numPoints,
236 | .fractionalBits = 12,
237 | };
238 | packed.positions.resize(numPoints * 3 * 3);
239 | packed.scales.resize(numPoints * 3);
240 | packed.rotations.resize(numPoints * 3);
241 | packed.alphas.resize(numPoints);
242 | packed.colors.resize(numPoints * 3);
243 |
244 | // Store coordinates as 24-bit fixed point values.
245 | const float scale = (1 << packed.fractionalBits);
246 | for (size_t i = 0; i < numPoints; i++) {
247 | const FVector3f& UEPosition = g[i].Position;
248 | for (size_t j = 0; j < 3; j++) {
249 | const int32_t fixed32 =
250 | static_cast(std::round(UEPosition[j] / 100.0f * scale));
251 | packed.positions[i * 9 + j * 3 + 0] = fixed32 & 0xff;
252 | packed.positions[i * 9 + j * 3 + 1] = (fixed32 >> 8) & 0xff;
253 | packed.positions[i * 9 + j * 3 + 2] = (fixed32 >> 16) & 0xff;
254 | }
255 | }
256 |
257 | for (size_t i = 0; i < numPoints ; i++) {
258 | const FVector3f& UEScale = g[i].Scale;
259 | for (size_t j = 0; j < 3; j++) {
260 | packed.scales[i * 3 + j] = toUint8((FMath::Loge(UEScale[j] / 100.0f) + 10.0f) * 16.0f);
261 | }
262 | }
263 |
264 | for (size_t i = 0; i < numPoints; i++) {
265 | // Normalize the quaternion, make w positive, then store xyz. w can be
266 | // derived from xyz. NOTE: These are already in xyzw order.
267 | FQuat4f Quat = g[i].Quat;
268 | Quat.Normalize();
269 | FVector4 Q(Quat.X, Quat.Y, Quat.Z, Quat.W);
270 | Q = Q * (Q[3] < 0 ? -127.5f : 127.5f);
271 | Q = Q + FVector4(127.5f, 127.5f, 127.5f, 127.5f);
272 |
273 | packed.rotations[i * 3 + 0] = toUint8(Q[0]);
274 | packed.rotations[i * 3 + 1] = toUint8(Q[1]);
275 | packed.rotations[i * 3 + 2] = toUint8(Q[2]);
276 | }
277 |
278 | for (size_t i = 0; i < numPoints; i++) {
279 | const FVector4f& Color = g[i].Color;
280 | packed.alphas[i] = toUint8(sigmoid(Color.W) * 255.0f);
281 | packed.colors[i * 3 + 0] = toUint8(Color[0] * (colorScale * 255.0f) + (0.5f * 255.0f));
282 | packed.colors[i * 3 + 1] = toUint8(Color[1] * (colorScale * 255.0f) + (0.5f * 255.0f));
283 | packed.colors[i * 3 + 2] = toUint8(Color[2] * (colorScale * 255.0f) + (0.5f * 255.0f));
284 | }
285 | return packed;
286 | }
287 |
288 | UnpackedGaussian PackedGaussian::unpack(bool usesFloat16,
289 | int fractionalBits) const {
290 | UnpackedGaussian result;
291 | if (usesFloat16) {
292 | // Decode legacy float16 format. We can remove this at some point as it was
293 | // never released.
294 | const auto* halfData = reinterpret_cast(position.data());
295 | for (size_t i = 0; i < 3; i++) {
296 | result.position[i] = halfToFloat(halfData[i]);
297 | }
298 | }
299 | else {
300 | // Decode 24-bit fixed point coordinates
301 | float fixedScale = 1.0 / (1 << fractionalBits);
302 | for (size_t i = 0; i < 3; i++) {
303 | int32_t fixed32 = position[i * 3 + 0];
304 | fixed32 |= position[i * 3 + 1] << 8;
305 | fixed32 |= position[i * 3 + 2] << 16;
306 | fixed32 |= (fixed32 & 0x800000) ? 0xff000000 : 0; // sign extension
307 | result.position[i] = static_cast(fixed32) * fixedScale;
308 | }
309 | }
310 |
311 | for (size_t i = 0; i < 3; i++) {
312 | result.scale[i] = (scale[i] / 16.0f - 10.0f);
313 | }
314 |
315 | const uint8_t* r = &rotation[0];
316 | FVector3f xyz = {
317 | static_cast(r[0]),
318 | static_cast(r[1]),
319 | static_cast(r[2])
320 | };
321 | xyz = xyz / 127.5f + FVector3f(-1, -1, -1);
322 |
323 | result.rotation[0] = xyz.X;
324 | result.rotation[1] = xyz.Y;
325 | result.rotation[2] = xyz.Z;
326 |
327 | // Compute the real component - we know the quaternion is normalized and w is
328 | // non-negative
329 | result.rotation[3] = std::sqrt(std::max(0.0f, 1.0f - xyz.SquaredLength()));
330 |
331 | result.alpha = invSigmoid(alpha / 255.0f);
332 |
333 | for (size_t i = 0; i < 3; i++) {
334 | result.color[i] = ((color[i] / 255.0f) - 0.5f) / colorScale;
335 | }
336 |
337 | return result;
338 | }
339 |
340 | PackedGaussian PackedGaussians::at(int i) const {
341 | PackedGaussian result;
342 | int positionBits = usesFloat16() ? 6 : 9;
343 | int start3 = i * 3;
344 | const auto* p = &positions[i * positionBits];
345 | std::copy(p, p + positionBits, result.position.data());
346 | std::copy(&scales[start3], &scales[start3 + 3], result.scale.data());
347 | std::copy(&rotations[start3], &rotations[start3 + 3], result.rotation.data());
348 | std::copy(&colors[start3], &colors[start3 + 3], result.color.data());
349 | result.alpha = alphas[i];
350 |
351 | return result;
352 | }
353 |
354 | UnpackedGaussian PackedGaussians::unpack(int i) const {
355 | return at(i).unpack(usesFloat16(), fractionalBits);
356 | }
357 |
358 | bool PackedGaussians::usesFloat16() const {
359 | return positions.size() == numPoints * 3 * 2;
360 | }
361 |
362 | TArray unpackGaussians(const PackedGaussians& packed) {
363 | const int numPoints = packed.numPoints;
364 |
365 | const bool usesFloat16 = packed.usesFloat16();
366 | if (!checkSizes(packed, numPoints, usesFloat16)) {
367 | return {};
368 | }
369 |
370 | TArray result;
371 | result.SetNum(numPoints);
372 |
373 | if (usesFloat16) {
374 | // Decode legacy float16 format. We can remove this at some point as it was
375 | // never released.
376 | const auto* halfData =
377 | reinterpret_cast(packed.positions.data());
378 | for (size_t i = 0; i < numPoints; i++) {
379 | for (size_t j = 0; j < 3; j++) {
380 | result[i].Position[j] = halfToFloat(halfData[i * 3 + j]);
381 | }
382 | }
383 | }
384 | else {
385 | // Decode 24-bit fixed point coordinates
386 | float scale = 1.0 / (1 << packed.fractionalBits);
387 | for (size_t i = 0; i < numPoints; i++) {
388 | for (size_t j = 0; j < 3; j++) {
389 | int32_t fixed32 = packed.positions[i * 9 + j * 3 + 0];
390 | fixed32 |= packed.positions[i * 9 + j * 3 + 1] << 8;
391 | fixed32 |= packed.positions[i * 9 + j * 3 + 2] << 16;
392 | fixed32 |= (fixed32 & 0x800000) ? 0xff000000 : 0; // sign extension
393 | result[i].Position[j] = static_cast(fixed32) * scale * 100.0f;
394 | }
395 | }
396 | }
397 |
398 | for (size_t i = 0; i < numPoints; i++) {
399 | for (size_t j = 0; j < 3; j++) {
400 | result[i].Scale[j] = 100.0f * FMath::Exp(packed.scales[i * 3 + j] / 16.0f - 10.0f);
401 | }
402 | }
403 |
404 | for (size_t i = 0; i < numPoints; i++) {
405 | const uint8_t* r = &packed.rotations[i * 3];
406 |
407 | FVector3f xyz = {
408 | static_cast(r[0]),
409 | static_cast(r[1]),
410 | static_cast(r[2])
411 | };
412 | xyz = xyz / 127.5f + FVector3f(-1, -1, -1);
413 |
414 | result[i].Quat.X = xyz.X;
415 | result[i].Quat.Y = xyz.Y;
416 | result[i].Quat.Z = xyz.Z;
417 | result[i].Quat.W = std::sqrt(std::max(0.0f, 1.0f - xyz.SquaredLength()));
418 | }
419 |
420 | for (size_t i = 0; i < numPoints; i++) {
421 | result[i].Color.A = invSigmoid(packed.alphas[i] / 255.0f);
422 | result[i].Color.R = ((packed.colors[i * 3 + 0] / 255.0f) - 0.5f) / colorScale;
423 | result[i].Color.G = ((packed.colors[i * 3 + 1] / 255.0f) - 0.5f) / colorScale;
424 | result[i].Color.B = ((packed.colors[i * 3 + 2] / 255.0f) - 0.5f) / colorScale;
425 | }
426 |
427 | return result;
428 | }
429 |
430 | void serializePackedGaussians(const PackedGaussians& packed,
431 | std::ostream& out) {
432 | PackedGaussiansHeader header = {
433 | .numPoints = static_cast(packed.numPoints),
434 | .fractionalBits = static_cast(packed.fractionalBits),
435 | .flags = static_cast(0),
436 | };
437 | out.write(reinterpret_cast(&header), sizeof(header));
438 | out.write(reinterpret_cast(packed.positions.data()),
439 | countBytes(packed.positions));
440 | out.write(reinterpret_cast(packed.alphas.data()),
441 | countBytes(packed.alphas));
442 | out.write(reinterpret_cast(packed.colors.data()),
443 | countBytes(packed.colors));
444 | out.write(reinterpret_cast(packed.scales.data()),
445 | countBytes(packed.scales));
446 | out.write(reinterpret_cast(packed.rotations.data()),
447 | countBytes(packed.rotations));
448 | }
449 |
450 | PackedGaussians deserializePackedGaussians(std::istream& in) {
451 | constexpr int maxPointsToRead = 10000000;
452 |
453 | PackedGaussiansHeader header;
454 | in.read(reinterpret_cast(&header), sizeof(header));
455 | if (!in || header.magic != PackedGaussiansHeader().magic) {
456 | SpzLog("[SPZ ERROR] deserializePackedGaussians: header not found");
457 | return {};
458 | }
459 | if (header.version < 1 || header.version > 2) {
460 | SpzLog("[SPZ ERROR] deserializePackedGaussians: version not supported: %d",
461 | header.version);
462 | return {};
463 | }
464 | if (header.numPoints > maxPointsToRead) {
465 | SpzLog("[SPZ ERROR] deserializePackedGaussians: Too many points: %d",
466 | header.numPoints);
467 | return {};
468 | }
469 | if (header.shDegree > 3) {
470 | SpzLog("[SPZ ERROR] deserializePackedGaussians: Unsupported SH degree: %d",
471 | header.shDegree);
472 | return {};
473 | }
474 | const int numPoints = header.numPoints;
475 | const int shDim = dimForDegree(header.shDegree);
476 | const bool usesFloat16 = header.version == 1;
477 | PackedGaussians result = { .numPoints = numPoints,
478 | .fractionalBits = header.fractionalBits};
479 | result.positions.resize(numPoints * 3 * (usesFloat16 ? 2 : 3));
480 | result.scales.resize(numPoints * 3);
481 | result.rotations.resize(numPoints * 3);
482 | result.alphas.resize(numPoints);
483 | result.colors.resize(numPoints * 3);
484 | in.read(reinterpret_cast(result.positions.data()),
485 | countBytes(result.positions));
486 | in.read(reinterpret_cast(result.alphas.data()),
487 | countBytes(result.alphas));
488 | in.read(reinterpret_cast(result.colors.data()),
489 | countBytes(result.colors));
490 | in.read(reinterpret_cast(result.scales.data()),
491 | countBytes(result.scales));
492 | in.read(reinterpret_cast(result.rotations.data()),
493 | countBytes(result.rotations));
494 | if (!in) {
495 | SpzLog("[SPZ ERROR] deserializePackedGaussians: read error");
496 | return {};
497 | }
498 | return result;
499 | }
500 |
501 | float halfToFloat(Half h) {
502 | auto sgn = ((h >> 15) & 0x1);
503 | auto exponent = ((h >> 10) & 0x1f);
504 | auto mantissa = h & 0x3ff;
505 |
506 | float signMul = sgn == 1 ? -1.0 : 1.0;
507 | if (exponent == 0) {
508 | // Subnormal numbers (no exponent, 0 in the mantissa decimal).
509 | return signMul * std::pow(2.0f, -14.0f) * static_cast(mantissa) / 1024.0f;
510 | }
511 |
512 | if (exponent == 31) {
513 | // Infinity or NaN
514 | if (mantissa != 0) {
515 | return std::numeric_limits::quiet_NaN();
516 | }
517 | else {
518 | return signMul * std::numeric_limits::infinity();
519 | }
520 | }
521 |
522 | // non-zero exponent implies 1 in the mantissa decimal.
523 | return signMul * std::pow(2.0f, static_cast(exponent) - 15.0f)
524 | * (1.0f + static_cast(mantissa) / 1024.0f);
525 | }
526 |
527 | Half floatToHalf(float f) {
528 | auto f32 = BitCast(f);
529 | int sign = (f32 >> 31) & 0x01; // 1 bit -> 1 bit
530 | int exponent = ((f32 >> 23) & 0xff); // 8 bits -> 5 bits
531 | int mantissa = f32 & 0x7fffff; // 23 bits -> 10 bits
532 |
533 | // Handle inf and nan from float.
534 | if (exponent == 0xFF) {
535 | if (mantissa == 0) {
536 | return (sign << 15) | 0x7C00; // Inf
537 | }
538 |
539 | return (sign << 15) | 0x7C01; // Nan
540 | }
541 |
542 | // If the exponent is greater than the range of half, return +/- Inf.
543 | int centeredExp = exponent - 127;
544 | if (centeredExp > 15) {
545 | return (sign << 15) | 0x7C00;
546 | }
547 |
548 | // Normal numbers. centeredExp = [-15, 15]
549 | if (centeredExp > -15) {
550 | return (sign << 15) | ((centeredExp + 15) << 10) | (mantissa >> 13);
551 | }
552 |
553 | // Subnormal numbers.
554 | int fullMantissa = 0x800000 | mantissa;
555 | int shift = -(centeredExp + 14); // Shift is in [-1 to -113]
556 | int newMantissa = fullMantissa >> shift;
557 | return (sign << 15) | (newMantissa >> 13);
558 | }
559 |
560 |
561 | bool compress(const TArray