├── 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 | ![3dgs](Resources/3dgs.gif) 8 | 9 | ![image-20250425144116238](Resources/image-20250425144116238.png) 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 | ![image-20250125114152866](Resources/image-20250125114152866.png) 56 | 57 | - 首次执行需要在编辑器面板的【Settings】页面指定相关的目录: 58 | 59 | ![image-20250125114302564](Resources/image-20250125114302564.png) 60 | 61 | ### 执行相机捕获 62 | 63 | - 该步骤的目标在于构建一组相机阵列,生成稀疏重建和高斯训练所需的数据集。 64 | 65 | ![image-20250125124847911](Resources/image-20250125124847911.png) 66 | 67 | #### 构建相机阵列 68 | 69 | 构建相机阵列的方式有三种: 70 | 71 | - **Select** :通过选中物体来确定需要捕获的场景图形 72 | - **Locate** :通过一个 `LocateActor` 来锚定捕获的中心点,再通过设置 `HiddenActors` 来排除不需要捕获的场景图形 73 | - **Custom** :以自定义的方式添加相机位,再通过设置 `HiddenActors` 来排除不需要捕获的场景图形 74 | 75 | **Select** 模式适用于单个或少量图形的捕获,我们可以在场景中按住`Ctrl`键来多选图形,也可以在场景大纲中进行选择,相机阵列将会以选中图形的包围盒来自动更新相机位置: 76 | 77 | ![image-20250125125848081](Resources/image-20250125125848081.png) 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 | ![image-20250125131850213](Resources/image-20250125131850213.png) 98 | 99 | - `Hidden Actors`:需要排除的图形 100 | 101 | **Custom** 模式用来自定义相机机位,切换到 **Custom** 模式,编辑器面板中显示 `Camera Actors` 选项,点击 【+】新增时会在当前编辑器视口位置添加新的机位: 102 | 103 | ![image-20250125132939563](Resources/image-20250125132939563.png) 104 | 105 | 在构建好相机阵列和设置好捕获配置后,点击【Capture】按钮,将在插件的工作目录下生成数据集,点击右侧的【Browse】 图标,可以浏览捕获结果: 106 | 107 | - `depths`:深度图 108 | - `images`:原始图像 109 | - `masks`:遮罩 110 | - `cameras.txt`:相机的位置信息 111 | 112 | ![image-20250125123858563](Resources/image-20250125123858563.png) 113 | 114 | 确保捕获的原始图像没有太大问题,就可以进行下一个步骤。 115 | 116 | ![image-20250125143907659](Resources/image-20250125143907659.png) 117 | 118 | ### 执行稀疏重建 119 | 120 | - 该步骤本质上是在调用 [colmap](https://github.com/colmap/colmap) 的命令行程序来完成稀疏点云的重建。 121 | 122 | ![image-20250125133400148](Resources/image-20250125133400148.png) 123 | 124 | 通常情况下,只需要点击 【Reconstruction】来执行稀疏重建,控制台可以观察到执行的日志,在结束后点击 【Colmap View】 可以查看重建的结果是否存在明显异常,如果不存在明显异常就可以进行下一步骤。 125 | 126 | ![image-20250125141645234](Resources/image-20250125141645234.png) 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 | ![image-20250125141810400](Resources/image-20250125141810400.png) 139 | 140 | ### 执行高斯溅射 141 | 142 | - 该步骤本质上是在调用 [gaussian-splatting](https://github.com/graphdeco-inria/gaussian-splatting) 提供的算法来完成3D高斯的训练。 143 | 144 | ![image-20250125134459423](Resources/image-20250125134459423.png) 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 | ![image-20250125143147405](Resources/image-20250125143147405.png) 162 | 163 | 导致后直接点击资产可以打开一个简易的高斯点云编辑器: 164 | 165 | ![image-20250425144806596](Resources/image-20250425144806596.png) 166 | 167 | 下侧的柱状图表示的高斯点的尺寸分布,目前支持的操作有: 168 | 169 | - 鼠标在柱状区域拖拽可以进行选择高斯点 170 | - 键盘按住 `Ctrl` + `Alt` 可以在视口中拖拽鼠标框选高斯点 171 | - 按下`Delete` 可以删除当前选中的高斯点 172 | - `Ctrl + Z` / `Ctrl + Y` 为撤销重做 173 | 174 | 高斯点云资产拖拽到场景会生成一个NiagaraActor,也可以导出独立的 Niagara System: 175 | 176 | ![image-20250425145537057](Resources/image-20250425145537057.png) 177 | 178 | ## LOD 179 | 180 | 粒子形式的高斯点云可以自动剔除掉小于某个屏幕尺寸的高斯点,以此来减少系统的调度和内存的开销: 181 | 182 | ![123Tes1tz1](Resources/123Tes1tz1.gif) 183 | 184 | 可以在粒子系统的参数面板来修改 `MinFeatureScreenSize` 来修改单个粒子系统剔除的阈值 185 | 186 | ![image-20250125144933867](Resources/image-20250125144933867.png) 187 | 188 | 也可以使用控制台参数来进行全局的调整: 189 | 190 | - `r.GaussianSplatting.ScreenSizeBias`:屏幕尺寸计算时的偏移,默认为 `0` 191 | - `r.GaussianSplatting.ScreenSizeScale`:屏幕尺寸计算时的缩放系数,默认为 `1` 192 | 193 | ## 自定义高斯算法 194 | 195 | 虚幻引擎提供了非常便利的编辑器,借助此插件可以快速的生成一些合成数据集,如果有算法定制化或测试的需求,在插件目录的 `/WorkHome/Scripts/.`下找到该插件所使用的 `python` 脚本: 196 | 197 | ![image-20250125142437998](Resources/image-20250125142437998.png) 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 g, int compressionLevel, 562 | int workers, std::vector& output) { 563 | if (g.Num() == 0) { 564 | SpzLog("[SPZ: ERROR] Parsed TArray is empty."); 565 | return false; 566 | } 567 | 568 | PackedGaussians packed = packGaussians(g); 569 | 570 | std::stringstream ss; 571 | serializePackedGaussians(packed, ss); 572 | const std::string uncompressed = ss.str(); 573 | 574 | if (!compressGzipped(reinterpret_cast(uncompressed.data()), uncompressed.size(), &output)) { 575 | SpzLog("[SPZ: ERROR] Zstd compression failed."); 576 | return false; 577 | } 578 | 579 | return true; 580 | } 581 | 582 | bool decompress(const std::span input, TArray& output) { 583 | std::vector decompressed; 584 | if (!decompressGzipped(input.data(), input.size(), &decompressed) || 585 | decompressed.empty()) { 586 | return false; 587 | } 588 | 589 | MemBuf memBuffer(decompressed.data(), 590 | decompressed.data() + decompressed.size()); 591 | std::istream stream(&memBuffer); 592 | PackedGaussians packed = deserializePackedGaussians(stream); 593 | 594 | if (packed.numPoints == 0 && packed.positions.empty()) { 595 | return false; 596 | } 597 | 598 | output = unpackGaussians(packed); 599 | 600 | return true; 601 | } 602 | } // namespace Spz 603 | -------------------------------------------------------------------------------- /Source/GaussianSplattingRuntime/Private/Compression/Spz.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include "GaussianSplattingPointCloud.h" 8 | 9 | // https://github.com/nianticlabs/spz 10 | namespace Spz { 11 | 12 | // Represents a single inflated gaussian. Each gaussian has 236 bytes. Although the data is easier 13 | // to intepret in this format, it is not more precise than the packed format, since it was inflated. 14 | struct UnpackedGaussian { 15 | std::array position; // x, y, z 16 | std::array rotation; // x, y, z, w 17 | std::array scale; // std::log(scale) 18 | std::array color; // rgb sh0 encoding 19 | float alpha; // inverse logistic 20 | }; 21 | 22 | // Represents a single low precision gaussian. Each gaussian has exactly 64 bytes, even if it does 23 | // not have full spherical harmonics. 24 | struct PackedGaussian { 25 | std::array position{}; 26 | std::array rotation{}; 27 | std::array scale{}; 28 | std::array color{}; 29 | uint8_t alpha = 0; 30 | UnpackedGaussian unpack(bool usesFloat16, int fractionalBits) const; 31 | }; 32 | 33 | // Represents a full splat with lower precision. Each splat has at most 64 bytes, although splats 34 | // with fewer spherical harmonics degrees will have less. The data is stored non-interleaved. 35 | struct PackedGaussians { 36 | int numPoints = 0; // Total number of points (gaussians) 37 | int fractionalBits = 0; // Number of bits used for fractional part of fixed-point coords 38 | 39 | std::vector positions; 40 | std::vector scales; 41 | std::vector rotations; 42 | std::vector alphas; 43 | std::vector colors; 44 | 45 | bool usesFloat16() const; 46 | PackedGaussian at(int i) const; 47 | UnpackedGaussian unpack(int i) const; 48 | }; 49 | 50 | using Half = uint16_t; 51 | 52 | // Half-precision helpers. 53 | float halfToFloat(Half h); 54 | Half floatToHalf(float f); 55 | 56 | GAUSSIANSPLATTINGRUNTIME_API bool compress( 57 | const TArray g, 58 | int compressionLevel, 59 | int workers, 60 | std::vector& output); 61 | 62 | GAUSSIANSPLATTINGRUNTIME_API bool decompress( 63 | const std::span input, 64 | TArray& output); 65 | 66 | } // namespace Spz 67 | 68 | -------------------------------------------------------------------------------- /Source/GaussianSplattingRuntime/Private/GaussianSplattingPointCloud.cpp: -------------------------------------------------------------------------------- 1 | #include "GaussianSplattingPointCloud.h" 2 | #include "Compression/Spz.h" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | const float SH_0 = 0.28209479177387814f; 14 | 15 | FGaussianSplattingPoint::FGaussianSplattingPoint(FVector3f InPos /*= FVector3f::ZeroVector*/, FQuat4f InQuat /*= FQuat4f::Identity*/, FVector3f InScale /*= {1,1,1}*/, FLinearColor InColor /*= FLinearColor::Black*/) : Position(InPos) 16 | , Quat(InQuat) 17 | , Scale(InScale) 18 | , Color(InColor) 19 | { 20 | 21 | } 22 | 23 | bool FGaussianSplattingPoint::operator<(const FGaussianSplattingPoint& Other) const 24 | { 25 | return Position.X < Other.Position.X; 26 | } 27 | 28 | bool FGaussianSplattingPoint::operator!=(const FGaussianSplattingPoint& Other) const 29 | { 30 | return !(*this == Other); 31 | } 32 | 33 | bool FGaussianSplattingPoint::operator==(const FGaussianSplattingPoint& Other) const 34 | { 35 | return Position == Other.Position && Quat == Other.Quat && Scale == Other.Scale && Color == Other.Color; 36 | } 37 | 38 | UGaussianSplattingPointCloud::UGaussianSplattingPointCloud(FObjectInitializer const& ObjectInitializer) 39 | : Super(ObjectInitializer) 40 | { 41 | } 42 | 43 | FRichCurve UGaussianSplattingPointCloud::CalcFeatureCurve() 44 | { 45 | FRichCurve Curve; 46 | int Step = FMath::Max(Points.Num() / FeatureLevel, 1u); 47 | for (int i = 0; i < Points.Num(); i += Step) { 48 | auto KeyHandle = Curve.AddKey(4 * Points[i].Scale.Length(), i); 49 | Curve.SetKeyInterpMode(KeyHandle, ERichCurveInterpMode::RCIM_Constant); 50 | } 51 | return Curve; 52 | } 53 | 54 | FBox UGaussianSplattingPointCloud::CalcBounds() 55 | { 56 | FBox Bounds; 57 | Bounds.Min = FVector(FLT_MAX); 58 | Bounds.Max = FVector(FLT_MIN); 59 | 60 | for (const auto& Point : Points) { 61 | Bounds.Max = FVector::Max(Bounds.Max, FVector(Point.Position)); 62 | Bounds.Min = FVector::Min(Bounds.Min, FVector(Point.Position)); 63 | } 64 | return Bounds.ExpandBy(FVector(Points[0].Scale.Length())); 65 | } 66 | 67 | void UGaussianSplattingPointCloud::SetPoints(const TArray& InPoints, bool bReorder /*= true*/) 68 | { 69 | Points = InPoints; 70 | if (bReorder) { 71 | Algo::Sort(Points, [](const FGaussianSplattingPoint& ItemA, const FGaussianSplattingPoint& ItemB) { 72 | return ItemA.Scale.Length() > ItemB.Scale.Length(); 73 | }); 74 | } 75 | OnPointsChanged.Broadcast(); 76 | } 77 | 78 | const TArray& UGaussianSplattingPointCloud::GetPoints() const 79 | { 80 | return Points; 81 | } 82 | 83 | int32 UGaussianSplattingPointCloud::GetPointCount() const 84 | { 85 | return Points.Num(); 86 | } 87 | 88 | FLinearColor SRGBToLinear(const FLinearColor& Color) 89 | { 90 | auto SRGBToLinearFloat = [](const float Color) -> float 91 | { 92 | return (Color <= 0.04045f) ? Color / 12.92f : FMath::Pow((Color + 0.055f) / 1.055f, 2.4f); 93 | }; 94 | 95 | return FLinearColor( 96 | SRGBToLinearFloat(Color.R) 97 | , SRGBToLinearFloat(Color.G) 98 | , SRGBToLinearFloat(Color.B) 99 | , Color.A 100 | ); 101 | } 102 | 103 | TArray ParseSplatFromStream(std::istream& in) 104 | { 105 | TArray Result; 106 | if (!in.good()) { 107 | UE_LOG(LogTemp, Warning, TEXT("Unable to read from input stream.")); 108 | return Result; 109 | } 110 | 111 | std::string line; 112 | std::getline(in, line); 113 | if (line != "ply") { 114 | UE_LOG(LogTemp, Warning, TEXT("Input data is not a .ply file.")); 115 | return Result; 116 | } 117 | 118 | std::getline(in, line); 119 | if (line != "format binary_little_endian 1.0") { 120 | UE_LOG(LogTemp, Warning, TEXT("Unsupported .ply format.")); 121 | return Result; 122 | } 123 | 124 | std::getline(in, line); 125 | if (line.find("element vertex ") != 0) { 126 | UE_LOG(LogTemp, Warning, TEXT("Missing vertex count.")); 127 | return Result; 128 | } 129 | 130 | int numPoints = std::stoi(line.substr(std::strlen("element vertex "))); 131 | 132 | if (numPoints <= 0 || numPoints > 10 * 1024 * 1024) { 133 | UE_LOG(LogTemp, Warning, TEXT("Invalid vertex count: %d"), numPoints); 134 | return Result; 135 | } 136 | 137 | UE_LOG(LogTemp, Log, TEXT("Loading %d points"), numPoints); 138 | std::unordered_map fields; // name -> index 139 | for (int i = 0;; i++) { 140 | if (!std::getline(in, line)) { 141 | UE_LOG(LogTemp, Warning, TEXT("Unexpected end of header.")); 142 | return Result; 143 | } 144 | 145 | if (line == "end_header") 146 | break; 147 | 148 | if (line.find("property float ") != 0) { 149 | UE_LOG(LogTemp, Warning, TEXT("Unsupported property data type")); 150 | return Result; 151 | } 152 | std::string name = line.substr(std::strlen("property float ")); 153 | fields[name] = i; 154 | } 155 | 156 | // Returns the index for a given field name, ensuring the name exists. 157 | const auto index = [&fields](const std::string& name) { 158 | const auto& itr = fields.find(name); 159 | if (itr == fields.end()) { 160 | UE_LOG(LogTemp, Warning, TEXT("Missing field")); 161 | return -1; 162 | } 163 | return itr->second; 164 | }; 165 | 166 | const std::vector positionIdx = { index("x"), index("y"), index("z") }; 167 | const std::vector scaleIdx = { index("scale_0"), index("scale_1"), 168 | index("scale_2") }; 169 | const std::vector rotIdx = { index("rot_1"), index("rot_2"), 170 | index("rot_3"), index("rot_0") }; 171 | const std::vector alphaIdx = { index("opacity") }; 172 | const std::vector colorIdx = { index("f_dc_0"), index("f_dc_1"), 173 | index("f_dc_2") }; 174 | 175 | // Check that only valid indices were returned. 176 | auto checkIndices = [&](const std::vector& idxVec) -> bool { 177 | for (auto idx : idxVec) { 178 | if (idx < 0) { 179 | return false; 180 | } 181 | } 182 | return true; 183 | }; 184 | 185 | if (!checkIndices(positionIdx) || !checkIndices(scaleIdx) || 186 | !checkIndices(rotIdx) || !checkIndices(alphaIdx) || 187 | !checkIndices(colorIdx)) { 188 | return Result; 189 | } 190 | 191 | // Spherical harmonics are optional and variable in size (depending on degree) 192 | std::vector shIdx; 193 | for (int i = 0; i < 45; i++) { 194 | const auto& itr = fields.find("f_rest_" + std::to_string(i)); 195 | if (itr == fields.end()) 196 | break; 197 | shIdx.push_back(itr->second); 198 | } 199 | const int shDim = static_cast(shIdx.size() / 3); 200 | 201 | // If spherical harmonics fields are present, ensure they are complete 202 | if (shIdx.size() % 3 != 0) { 203 | UE_LOG(LogTemp, Warning, TEXT("Incomplete spherical harmonics fields.")); 204 | return Result; 205 | } 206 | 207 | std::vector values; 208 | values.resize(numPoints * fields.size()); 209 | 210 | in.read(reinterpret_cast(values.data()), 211 | values.size() * sizeof(float)); 212 | if (!in.good()) { 213 | UE_LOG(LogTemp, Warning, TEXT("Unable to load data from input stream.")); 214 | return Result; 215 | } 216 | 217 | Result.SetNum(numPoints); 218 | 219 | for (size_t i = 0; i < static_cast(numPoints); i++) { 220 | size_t vertexOffset = i * fields.size(); 221 | FGaussianSplattingPoint& Point = Result[i]; 222 | 223 | // Position 224 | FVector3f Position = FVector3f( 225 | values[vertexOffset + positionIdx[0]], 226 | values[vertexOffset + positionIdx[1]], 227 | values[vertexOffset + positionIdx[2]] 228 | ); 229 | Point.Position = 100 * FVector3f(Position.X, -Position.Z, -Position.Y); 230 | 231 | // Scale 232 | FVector3f Scale = FVector3f( 233 | FMath::Exp(values[vertexOffset + scaleIdx[0]]), 234 | FMath::Exp(values[vertexOffset + scaleIdx[1]]), 235 | FMath::Exp(values[vertexOffset + scaleIdx[2]]) 236 | ); 237 | Point.Scale = 100 * FVector3f(Scale.X, Scale.Z, Scale.Y); 238 | 239 | // Rotation 240 | FQuat4f Quat = FQuat4f( 241 | values[vertexOffset + rotIdx[0]], 242 | values[vertexOffset + rotIdx[1]], 243 | values[vertexOffset + rotIdx[2]], 244 | values[vertexOffset + rotIdx[3]] 245 | ); 246 | Quat.Normalize(); 247 | 248 | Point.Quat = FQuat4f(Quat.X, -Quat.Z, -Quat.Y, Quat.W); 249 | 250 | // Color 251 | FLinearColor Color = FLinearColor( 252 | SH_0 * values[vertexOffset + colorIdx[0]] + 0.5f, 253 | SH_0 * values[vertexOffset + colorIdx[1]] + 0.5f, 254 | SH_0 * values[vertexOffset + colorIdx[2]] + 0.5f, 255 | 1.0f / (1.0f + FMath::Exp(-values[vertexOffset + alphaIdx[0]])) 256 | ); 257 | Point.Color = SRGBToLinear(Color); 258 | } 259 | return Result; 260 | } 261 | 262 | void UGaussianSplattingPointCloud::LoadFromFile(FString InFilePath) 263 | { 264 | Points = LoadPointsFromFile(InFilePath); 265 | } 266 | 267 | TArray UGaussianSplattingPointCloud::LoadPointsFromFile(FString InFilePath) 268 | { 269 | std::ifstream IStream(TCHAR_TO_UTF8(*InFilePath), std::ios::binary); 270 | if (!IStream.is_open()) { 271 | UE_LOG(LogTemp, Warning, TEXT("Unable to open: %s"), *InFilePath); 272 | return {}; 273 | } 274 | return ParseSplatFromStream(IStream); 275 | } 276 | 277 | void UGaussianSplattingPointCloud::Serialize(FArchive& Ar) 278 | { 279 | Super::Serialize(Ar); 280 | if (GetCompressionMethod() == EGaussianSplattingCompressionMethod::None) { 281 | Ar << Points; 282 | } 283 | else if(GetCompressionMethod() == EGaussianSplattingCompressionMethod::Zlib){ 284 | if (Ar.IsLoading()) { 285 | std::vector CompressedData; 286 | int CompressedDataSize = 0; 287 | Ar << CompressedDataSize; 288 | CompressedData.resize(CompressedDataSize); 289 | Ar.Serialize(CompressedData.data(), CompressedData.size() * sizeof(uint8_t)); 290 | Spz::decompress(CompressedData, Points); 291 | } 292 | else if (Ar.IsSaving()) { 293 | std::vector CompressedData; 294 | Spz::compress(Points, 3, 1, CompressedData); 295 | int CompressedDataSize = CompressedData.size(); 296 | Ar << CompressedDataSize; 297 | Ar.Serialize(CompressedData.data(), CompressedData.size() * sizeof(uint8_t)); 298 | } 299 | } 300 | } 301 | -------------------------------------------------------------------------------- /Source/GaussianSplattingRuntime/Private/GaussianSplattingPointCloudActor.cpp: -------------------------------------------------------------------------------- 1 | #include "GaussianSplattingPointCloudActor.h" 2 | #include "NiagaraFunctionLibrary.h" 3 | #include "GaussianSplattingPointCloudDataInterface.h" 4 | 5 | UGaussianSplattingPointCloud* AGaussianSplattingPointCloudActor::GetPointCloud() const 6 | { 7 | UNiagaraComponent* NiagaraComp = GetNiagaraComponent(); 8 | check(NiagaraComp); 9 | if (UNiagaraDataInterfaceGaussianSplattingPointCloud* DI = UNiagaraFunctionLibrary::GetDataInterface(NiagaraComp, "PointCloud")) { 10 | return DI->GetPointCloud(); 11 | } 12 | return nullptr; 13 | } 14 | -------------------------------------------------------------------------------- /Source/GaussianSplattingRuntime/Private/GaussianSplattingPointCloudDataInterface.cpp: -------------------------------------------------------------------------------- 1 | #include "GaussianSplattingPointCloudDataInterface.h" 2 | #include "NiagaraCompileHashVisitor.h" 3 | #include "NiagaraShaderParametersBuilder.h" 4 | #include "NiagaraSystemInstance.h" 5 | #include "NiagaraRenderer.h" 6 | 7 | #define LOCTEXT_NAMESPACE "GaussianSplatting" 8 | 9 | static TAutoConsoleVariable CVarGaussianSplattingScreenSizeBias( 10 | TEXT("r.GaussianSplatting.ScreenSizeBias"), 11 | 0.0f, 12 | TEXT(""), 13 | ECVF_RenderThreadSafe | ECVF_Scalability); 14 | 15 | static TAutoConsoleVariable CVarGaussianSplattingMaxFeatureSize( 16 | TEXT("r.GaussianSplatting.MaxFeatureSize"), 17 | 0.0f, 18 | TEXT(""), 19 | ECVF_RenderThreadSafe | ECVF_Scalability); 20 | 21 | static TAutoConsoleVariable CVarGaussianSplattingScreenSizeScale( 22 | TEXT("r.GaussianSplatting.ScreenSizeScale"), 23 | 1.0f, 24 | TEXT(""), 25 | ECVF_RenderThreadSafe | ECVF_Scalability); 26 | 27 | FNiagaraDataInterfaceProxyGaussianSplattingPointCloud::FNiagaraDataInterfaceProxyGaussianSplattingPointCloud(class UNiagaraDataInterfaceGaussianSplattingPointCloud* InOwner) 28 | : Owner(InOwner) 29 | { 30 | 31 | } 32 | 33 | FNiagaraDataInterfaceProxyGaussianSplattingPointCloud::~FNiagaraDataInterfaceProxyGaussianSplattingPointCloud() 34 | { 35 | 36 | } 37 | 38 | void FNiagaraDataInterfaceProxyGaussianSplattingPointCloud::MakeBufferDirty() 39 | { 40 | bDirty = true; 41 | } 42 | 43 | void FNiagaraDataInterfaceProxyGaussianSplattingPointCloud::TryUpdateBuffer() 44 | { 45 | if (PointCloud != Owner->PointCloud) { 46 | PointCloud = Owner->PointCloud; 47 | if (PointCloud) { 48 | PointCloud->OnPointsChanged.AddLambda([this]() { 49 | bDirty = true; 50 | }); 51 | } 52 | bDirty = true; 53 | } 54 | if (bDirty) { 55 | PostDataToGPU(); 56 | bDirty = false; 57 | } 58 | } 59 | 60 | void FNiagaraDataInterfaceProxyGaussianSplattingPointCloud::PostDataToGPU() 61 | { 62 | if (Owner == nullptr || Owner->PointCloud == nullptr) { 63 | return; 64 | } 65 | const TArray& Points = Owner->PointCloud->GetPoints(); 66 | TArray PointData; 67 | PointData.SetNum(Points.Num() * 4); 68 | for (int i = 0; i < Points.Num(); i++) { 69 | const FGaussianSplattingPoint& Point = Points[i]; 70 | PointData[i * 4] = Point.Position; 71 | PointData[i * 4 + 1] = FVector4f(Point.Quat.X, Point.Quat.Y, Point.Quat.Z, Point.Quat.W); 72 | PointData[i * 4 + 2] = Point.Scale; 73 | PointData[i * 4 + 3] = Point.Color; 74 | } 75 | 76 | ENQUEUE_RENDER_COMMAND(FUpdateSpectrumBuffer)( 77 | [this, PointData](FRHICommandListImmediate& RHICmdList) 78 | { 79 | const int32 NumBytesInBuffer = sizeof(FVector4f) * PointData.Num(); 80 | 81 | if (NumBytesInBuffer != GaussianPointDataBuffer.NumBytes){ 82 | if (GaussianPointDataBuffer.NumBytes > 0) 83 | GaussianPointDataBuffer.Release(); 84 | if (NumBytesInBuffer > 0) 85 | GaussianPointDataBuffer.Initialize(RHICmdList, TEXT("FNiagaraDataInterfaceProxySpectrum_PositionBuffer"), sizeof(FVector4f), PointData.Num(), EPixelFormat::PF_A32B32G32R32F, BUF_Static); 86 | } 87 | 88 | if (GaussianPointDataBuffer.NumBytes > 0){ 89 | float* BufferData = static_cast(RHICmdList.LockBuffer(GaussianPointDataBuffer.Buffer, 0, NumBytesInBuffer, EResourceLockMode::RLM_WriteOnly)); 90 | FScopeLock ScopeLock(&BufferLock); 91 | FPlatformMemory::Memcpy(BufferData, PointData.GetData(), NumBytesInBuffer); 92 | RHICmdList.UnlockBuffer(GaussianPointDataBuffer.Buffer); 93 | } 94 | }); 95 | } 96 | 97 | UNiagaraDataInterfaceGaussianSplattingPointCloud::UNiagaraDataInterfaceGaussianSplattingPointCloud(FObjectInitializer const& ObjectInitializer) 98 | : Super(ObjectInitializer) 99 | 100 | { 101 | if (!HasAnyFlags(RF_ClassDefaultObject)){ 102 | Proxy = MakeUnique(this); 103 | } 104 | } 105 | 106 | #if WITH_EDITORONLY_DATA 107 | 108 | #if UE_VERSION_NEWER_THAN(5, 4, 0) 109 | void UNiagaraDataInterfaceGaussianSplattingPointCloud::GetFunctionsInternal(TArray& OutFunctions) const 110 | { 111 | Super::GetFunctionsInternal(OutFunctions); 112 | 113 | #else 114 | void UNiagaraDataInterfaceGaussianSplattingPointCloud::GetFunctions(TArray& OutFunctions) 115 | { 116 | Super::GetFunctions(OutFunctions); 117 | #endif 118 | { 119 | FNiagaraFunctionSignature GetPointDataSignature; 120 | GetPointDataSignature.Name = GetPointDataFunctionName; 121 | GetPointDataSignature.Inputs.Add(FNiagaraVariable(GetClass(), TEXT("GaussianSplattingPointCloud"))); 122 | GetPointDataSignature.Inputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("Index"))); 123 | GetPointDataSignature.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Position"))); 124 | GetPointDataSignature.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec4Def(), TEXT("Quat"))); 125 | GetPointDataSignature.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetVec3Def(), TEXT("Scale"))); 126 | GetPointDataSignature.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetColorDef(), TEXT("Color"))); 127 | GetPointDataSignature.bMemberFunction = true; 128 | GetPointDataSignature.bRequiresContext = false; 129 | OutFunctions.Add(GetPointDataSignature); 130 | } 131 | { 132 | FNiagaraFunctionSignature GetPointCountSignature; 133 | GetPointCountSignature.Name = GetPointCountFunctionName; 134 | GetPointCountSignature.Inputs.Add(FNiagaraVariable(GetClass(), TEXT("GaussianSplattingPointCloud"))); 135 | GetPointCountSignature.Outputs.Add(FNiagaraVariable(FNiagaraTypeDefinition::GetIntDef(), TEXT("PointCount"))); 136 | GetPointCountSignature.bMemberFunction = true; 137 | GetPointCountSignature.bRequiresContext = false; 138 | OutFunctions.Add(GetPointCountSignature); 139 | } 140 | } 141 | 142 | #endif 143 | 144 | DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceGaussianSplattingPointCloud, GetPointCount); 145 | DEFINE_NDI_DIRECT_FUNC_BINDER(UNiagaraDataInterfaceGaussianSplattingPointCloud, GetPointData); 146 | 147 | void UNiagaraDataInterfaceGaussianSplattingPointCloud::SetPointCloud(UGaussianSplattingPointCloud* InPointCloud) 148 | { 149 | PointCloud = InPointCloud; 150 | if (auto DIProxy = GetProxyAs()) { 151 | DIProxy->MakeBufferDirty(); 152 | } 153 | } 154 | 155 | UGaussianSplattingPointCloud* UNiagaraDataInterfaceGaussianSplattingPointCloud::GetPointCloud() const 156 | { 157 | return PointCloud; 158 | } 159 | 160 | void UNiagaraDataInterfaceGaussianSplattingPointCloud::GetPointCount(FVectorVMExternalFunctionContext& Context) 161 | { 162 | VectorVM::FExternalFuncRegisterHandler OutPointCount(Context); 163 | 164 | for (int32 InstanceIdx = 0; InstanceIdx < Context.GetNumInstances(); ++InstanceIdx) 165 | { 166 | *OutPointCount.GetDestAndAdvance() = PointCloud->GetPoints().Num(); 167 | } 168 | } 169 | 170 | void UNiagaraDataInterfaceGaussianSplattingPointCloud::GetPointData(FVectorVMExternalFunctionContext& Context) 171 | { 172 | VectorVM::FExternalFuncInputHandler InIndex(Context); 173 | VectorVM::FExternalFuncRegisterHandler PosX(Context); 174 | VectorVM::FExternalFuncRegisterHandler PosY(Context); 175 | VectorVM::FExternalFuncRegisterHandler PosZ(Context); 176 | 177 | VectorVM::FExternalFuncRegisterHandler QuatX(Context); 178 | VectorVM::FExternalFuncRegisterHandler QuatY(Context); 179 | VectorVM::FExternalFuncRegisterHandler QuatZ(Context); 180 | VectorVM::FExternalFuncRegisterHandler QuatW(Context); 181 | 182 | VectorVM::FExternalFuncRegisterHandler ScaleX(Context); 183 | VectorVM::FExternalFuncRegisterHandler ScaleY(Context); 184 | VectorVM::FExternalFuncRegisterHandler ScaleZ(Context); 185 | 186 | VectorVM::FExternalFuncRegisterHandler ColorR(Context); 187 | VectorVM::FExternalFuncRegisterHandler ColorG(Context); 188 | VectorVM::FExternalFuncRegisterHandler ColorB(Context); 189 | VectorVM::FExternalFuncRegisterHandler ColorA(Context); 190 | 191 | auto Points = PointCloud->GetPoints(); 192 | for (int32 InstanceIdx = 0; InstanceIdx < Context.GetNumInstances(); ++InstanceIdx){ 193 | int32 Index = InIndex.Get(); 194 | FGaussianSplattingPoint& Point = Points[Index]; 195 | *PosX.GetDest() = Point.Position.X; 196 | *PosX.GetDest() = Point.Position.X; 197 | *PosX.GetDest() = Point.Position.X; 198 | 199 | *QuatX.GetDest() = Point.Quat.X; 200 | *QuatY.GetDest() = Point.Quat.Y; 201 | *QuatZ.GetDest() = Point.Quat.Z; 202 | *QuatZ.GetDest() = Point.Quat.Z; 203 | 204 | *ScaleX.GetDest() = Point.Scale.X; 205 | *ScaleY.GetDest() = Point.Scale.Y; 206 | *ScaleZ.GetDest() = Point.Scale.Z; 207 | 208 | *ColorR.GetDest() = Point.Color.R; 209 | *ColorG.GetDest() = Point.Color.G; 210 | *ColorB.GetDest() = Point.Color.B; 211 | *ColorA.GetDest() = Point.Color.A; 212 | 213 | InIndex.Advance(); 214 | 215 | PosX.Advance(); 216 | PosY.Advance(); 217 | PosZ.Advance(); 218 | 219 | QuatX.Advance(); 220 | QuatY.Advance(); 221 | QuatZ.Advance(); 222 | QuatZ.Advance(); 223 | 224 | ScaleX.Advance(); 225 | ScaleY.Advance(); 226 | ScaleZ.Advance(); 227 | 228 | ColorR.Advance(); 229 | ColorG.Advance(); 230 | ColorB.Advance(); 231 | ColorA.Advance(); 232 | } 233 | } 234 | 235 | void UNiagaraDataInterfaceGaussianSplattingPointCloud::GetVMExternalFunction(const FVMExternalFunctionBindingInfo& BindingInfo, void* InstanceData, FVMExternalFunction& OutFunc) 236 | { 237 | if (BindingInfo.Name == GetPointDataFunctionName){ 238 | NDI_FUNC_BINDER(UNiagaraDataInterfaceGaussianSplattingPointCloud, GetPointData)::Bind(this, OutFunc); 239 | } 240 | else if (BindingInfo.Name == GetPointCountFunctionName){ 241 | NDI_FUNC_BINDER(UNiagaraDataInterfaceGaussianSplattingPointCloud, GetPointCount)::Bind(this, OutFunc); 242 | } 243 | else{ 244 | ensureMsgf(false, TEXT("Error! Function defined for this class but not bound.")); 245 | } 246 | } 247 | 248 | #if WITH_EDITORONLY_DATA 249 | bool UNiagaraDataInterfaceGaussianSplattingPointCloud::AppendCompileHash(FNiagaraCompileHashVisitor* InVisitor) const 250 | { 251 | bool bSuccess = Super::AppendCompileHash(InVisitor); 252 | bSuccess &= InVisitor->UpdateShaderParameters(); 253 | return bSuccess; 254 | } 255 | 256 | bool UNiagaraDataInterfaceGaussianSplattingPointCloud::GetFunctionHLSL(const FNiagaraDataInterfaceGPUParamInfo& ParamInfo, const FNiagaraDataInterfaceGeneratedFunction& FunctionInfo, int FunctionInstanceIndex, FString& OutHLSL) 257 | { 258 | bool ParentRet = Super::GetFunctionHLSL(ParamInfo, FunctionInfo, FunctionInstanceIndex, OutHLSL); 259 | if (ParentRet) 260 | { 261 | return true; 262 | } 263 | else if (FunctionInfo.DefinitionName == GetPointDataFunctionName) 264 | { 265 | static const TCHAR* FormatBounds = TEXT(R"( 266 | void {FunctionName}(int In_PointIndex, out float3 Out_Position, out float4 Out_Quat, out float3 Out_Scale, out float4 Out_Color) 267 | { 268 | int PointIndex = In_PointIndex < {PointCount} ? In_PointIndex : {PointCount} - 1; 269 | Out_Position = {PointDataBuffer}.Load(PointIndex * 4 ).xyz; 270 | Out_Quat = {PointDataBuffer}.Load(PointIndex * 4 + 1); 271 | Out_Scale = {PointDataBuffer}.Load(PointIndex * 4 + 2).xyz; 272 | Out_Color = {PointDataBuffer}.Load(PointIndex * 4 + 3); 273 | } 274 | )"); 275 | 276 | TMap ArgsBounds = { 277 | {TEXT("FunctionName"), FStringFormatArg(FunctionInfo.InstanceName)}, 278 | {TEXT("PointCount"), FStringFormatArg(ParamInfo.DataInterfaceHLSLSymbol + PointCountName)}, 279 | {TEXT("PointDataBuffer"), FStringFormatArg(ParamInfo.DataInterfaceHLSLSymbol + PointDataBufferName)}, 280 | }; 281 | OutHLSL += FString::Format(FormatBounds, ArgsBounds); 282 | return true; 283 | } 284 | else if (FunctionInfo.DefinitionName == GetPointCountFunctionName) 285 | { 286 | static const TCHAR* FormatBounds = TEXT(R"( 287 | void {FunctionName}(out int Out_Val) 288 | { 289 | Out_Val = {PointCount}; 290 | } 291 | )"); 292 | TMap ArgsBounds = { 293 | {TEXT("FunctionName"), FStringFormatArg(FunctionInfo.InstanceName)}, 294 | {TEXT("PointCount"), FStringFormatArg(ParamInfo.DataInterfaceHLSLSymbol + PointCountName)}, 295 | }; 296 | OutHLSL += FString::Format(FormatBounds, ArgsBounds); 297 | return true; 298 | } 299 | else 300 | { 301 | return false; 302 | } 303 | } 304 | 305 | void UNiagaraDataInterfaceGaussianSplattingPointCloud::GetParameterDefinitionHLSL(const FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) 306 | { 307 | Super::GetParameterDefinitionHLSL(ParamInfo, OutHLSL); 308 | 309 | static const TCHAR* FormatDeclarations = TEXT(R"( 310 | int {PointCountName}; 311 | Buffer {PointDataBufferName}; 312 | )"); 313 | 314 | TMap ArgsDeclarations = { 315 | {TEXT("PointCountName"), FStringFormatArg(ParamInfo.DataInterfaceHLSLSymbol + PointCountName)}, 316 | {TEXT("PointDataBufferName"), FStringFormatArg(ParamInfo.DataInterfaceHLSLSymbol + PointDataBufferName)}, 317 | }; 318 | OutHLSL += FString::Format(FormatDeclarations, ArgsDeclarations); 319 | } 320 | #endif 321 | 322 | void UNiagaraDataInterfaceGaussianSplattingPointCloud::BuildShaderParameters(FNiagaraShaderParametersBuilder& ShaderParametersBuilder) const 323 | { 324 | ShaderParametersBuilder.AddNestedStruct(); 325 | } 326 | 327 | void UNiagaraDataInterfaceGaussianSplattingPointCloud::SetShaderParameters(const FNiagaraDataInterfaceSetShaderParametersContext& Context) const 328 | { 329 | FNiagaraDataInterfaceProxyGaussianSplattingPointCloud& DIProxy = Context.GetProxy(); 330 | DIProxy.TryUpdateBuffer(); 331 | UNiagaraDataInterfaceGaussianSplattingPointCloud* Current = DIProxy.Owner; 332 | FShaderParameters* ShaderParameters = Context.GetParameterNestedStruct(); 333 | ShaderParameters->PointCount = Current->PointCloud ? Current->PointCloud->GetPoints().Num() : 0; 334 | ShaderParameters->PointDataBuffer = FNiagaraRenderer::GetSrvOrDefaultFloat4(DIProxy.GaussianPointDataBuffer.SRV); 335 | } 336 | 337 | bool UNiagaraDataInterfaceGaussianSplattingPointCloud::Equals(const UNiagaraDataInterface* Other) const 338 | { 339 | bool bIsEqual = Super::Equals(Other); 340 | const UNiagaraDataInterfaceGaussianSplattingPointCloud* OtherPointCloud = CastChecked(Other); 341 | return OtherPointCloud->PointCloud == PointCloud; 342 | } 343 | 344 | #if WITH_EDITOR 345 | void UNiagaraDataInterfaceGaussianSplattingPointCloud::PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) 346 | { 347 | Super::PostEditChangeProperty(PropertyChangedEvent); 348 | static const FName PointCloudFName = GET_MEMBER_NAME_CHECKED(UNiagaraDataInterfaceGaussianSplattingPointCloud, PointCloud); 349 | 350 | if (!HasAnyFlags(RF_ClassDefaultObject)){ 351 | if (PropertyChangedEvent.GetMemberPropertyName() == PointCloudFName) { 352 | GetProxyAs()->MakeBufferDirty(); 353 | } 354 | } 355 | } 356 | #endif //WITH_EDITOR 357 | 358 | void UNiagaraDataInterfaceGaussianSplattingPointCloud::PostInitProperties() 359 | { 360 | Super::PostInitProperties(); 361 | 362 | if (HasAnyFlags(RF_ClassDefaultObject)){ 363 | ENiagaraTypeRegistryFlags Flags = ENiagaraTypeRegistryFlags::AllowAnyVariable | ENiagaraTypeRegistryFlags::AllowParameter; 364 | FNiagaraTypeRegistry::Register(FNiagaraTypeDefinition(GetClass()), Flags); 365 | } 366 | else { 367 | GetProxyAs()->MakeBufferDirty(); 368 | } 369 | } 370 | 371 | void UNiagaraDataInterfaceGaussianSplattingPointCloud::PostLoad() 372 | { 373 | Super::PostLoad(); 374 | } 375 | 376 | bool UNiagaraDataInterfaceGaussianSplattingPointCloud::CopyToInternal(UNiagaraDataInterface* Destination) const 377 | { 378 | if (!Super::CopyToInternal(Destination)){ 379 | return false; 380 | } 381 | 382 | UNiagaraDataInterfaceGaussianSplattingPointCloud* CastedDestination = Cast(Destination); 383 | if (CastedDestination){ 384 | CastedDestination->PointCloud = PointCloud; 385 | } 386 | return true; 387 | } 388 | 389 | 390 | // Global VM function names, also used by the shaders code generation methods. 391 | const FName UNiagaraDataInterfaceGaussianSplattingPointCloud::GetPointDataFunctionName("GetPointData"); 392 | const FName UNiagaraDataInterfaceGaussianSplattingPointCloud::GetPointCountFunctionName("GetPointCount"); 393 | 394 | // Global variable prefixes, used in HLSL parameter declarations. 395 | const FString UNiagaraDataInterfaceGaussianSplattingPointCloud::PointCountName(TEXT("_PointCount")); 396 | const FString UNiagaraDataInterfaceGaussianSplattingPointCloud::PointDataBufferName(TEXT("_PointDataBuffer")); 397 | 398 | #undef LOCTEXT_NAMESPACE 399 | -------------------------------------------------------------------------------- /Source/GaussianSplattingRuntime/Private/GaussianSplattingRuntimeModule.cpp: -------------------------------------------------------------------------------- 1 | #include "GaussianSplattingRuntimeModule.h" 2 | #include "Interfaces/IPluginManager.h" 3 | 4 | #define LOCTEXT_NAMESPACE "GaussianSplatting" 5 | 6 | void FGaussianSplattingRuntimeModule::StartupModule() 7 | { 8 | } 9 | 10 | void FGaussianSplattingRuntimeModule::ShutdownModule() 11 | { 12 | } 13 | 14 | #undef LOCTEXT_NAMESPACE 15 | 16 | IMPLEMENT_MODULE(FGaussianSplattingRuntimeModule, GaussianSplattingRuntime) -------------------------------------------------------------------------------- /Source/GaussianSplattingRuntime/Public/GaussianSplattingPointCloud.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "UObject/NoExportTypes.h" 4 | #include "NiagaraDataInterfaceCurve.h" 5 | #include "GaussianSplattingPointCloud.generated.h" 6 | 7 | 8 | UENUM() 9 | enum class EGaussianSplattingCompressionMethod : uint8 10 | { 11 | None, 12 | Zlib, 13 | }; 14 | 15 | USTRUCT(BlueprintType, meta = (DisplayName = "Gaussian Splatting Point")) 16 | struct GAUSSIANSPLATTINGRUNTIME_API FGaussianSplattingPoint 17 | { 18 | GENERATED_BODY() 19 | public: 20 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Gaussian Splatting") 21 | FVector3f Position; 22 | 23 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Gaussian Splatting") 24 | FQuat4f Quat; 25 | 26 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Gaussian Splatting") 27 | FVector3f Scale; 28 | 29 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Gaussian Splatting") 30 | FLinearColor Color; 31 | 32 | FGaussianSplattingPoint(FVector3f InPos = FVector3f::ZeroVector, FQuat4f InQuat = FQuat4f::Identity, FVector3f InScale = {1,1,1}, FLinearColor InColor = FLinearColor::Black); 33 | 34 | bool operator==(const FGaussianSplattingPoint& Other)const; 35 | bool operator!=(const FGaussianSplattingPoint& Other)const; 36 | bool operator<(const FGaussianSplattingPoint& Other)const; 37 | 38 | friend FORCEINLINE uint32 GetTypeHash(const FGaussianSplattingPoint& ID) 39 | { 40 | return HashCombine(HashCombine(HashCombine(GetTypeHash(ID.Position), GetTypeHash(ID.Quat)), GetTypeHash(ID.Scale)), GetTypeHash(ID.Color)); 41 | } 42 | friend FORCEINLINE FArchive& operator<<(FArchive& Ar, FGaussianSplattingPoint& Point) 43 | { 44 | Ar << Point.Position; 45 | Ar << Point.Quat; 46 | Ar << Point.Scale; 47 | Ar << Point.Color; 48 | return Ar; 49 | } 50 | }; 51 | 52 | UCLASS(Blueprintable, BlueprintType, EditInlineNew, CollapseCategories) 53 | class GAUSSIANSPLATTINGRUNTIME_API UGaussianSplattingPointCloud : public UObject { 54 | GENERATED_UCLASS_BODY() 55 | public: 56 | FSimpleMulticastDelegate OnPointsChanged; 57 | 58 | FRichCurve CalcFeatureCurve(); 59 | 60 | FBox CalcBounds(); 61 | 62 | void SetPoints(const TArray& InPoints, bool bReorder = true); 63 | 64 | const TArray& GetPoints() const; 65 | 66 | int32 GetPointCount() const; 67 | 68 | void LoadFromFile(FString InFilePath); 69 | 70 | static TArray LoadPointsFromFile(FString InFilePath); 71 | 72 | EGaussianSplattingCompressionMethod GetCompressionMethod() const { return CompressionMethod; } 73 | 74 | void SetCompressionMethod(EGaussianSplattingCompressionMethod val) { CompressionMethod = val; } 75 | 76 | private: 77 | void Serialize(FArchive& Ar) override; 78 | 79 | private: 80 | UPROPERTY(EditAnywhere, Category = "Gaussian Splatting") 81 | EGaussianSplattingCompressionMethod CompressionMethod = EGaussianSplattingCompressionMethod::Zlib; 82 | 83 | UPROPERTY(Transient) 84 | TArray Points; 85 | 86 | UPROPERTY() 87 | uint32 FeatureLevel = 64; 88 | }; 89 | -------------------------------------------------------------------------------- /Source/GaussianSplattingRuntime/Public/GaussianSplattingPointCloudActor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "NiagaraActor.h" 4 | #include "GaussianSplattingPointCloud.h" 5 | #include "GaussianSplattingPointCloudActor.generated.h" 6 | 7 | 8 | UCLASS(CollapseCategories) 9 | class GAUSSIANSPLATTINGRUNTIME_API AGaussianSplattingPointCloudActor : public ANiagaraActor { 10 | GENERATED_BODY() 11 | public: 12 | UGaussianSplattingPointCloud* GetPointCloud() const; 13 | }; 14 | 15 | -------------------------------------------------------------------------------- /Source/GaussianSplattingRuntime/Public/GaussianSplattingPointCloudDataInterface.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "NiagaraDataInterfaceArray.h" 4 | #include "Misc/EngineVersionComparison.h" 5 | #include "GaussianSplattingPointCloud.h" 6 | #include "GaussianSplattingPointCloudDataInterface.generated.h" 7 | 8 | struct FNiagaraDataInterfaceProxyGaussianSplattingPointCloud : public FNiagaraDataInterfaceProxy 9 | { 10 | FNiagaraDataInterfaceProxyGaussianSplattingPointCloud(class UNiagaraDataInterfaceGaussianSplattingPointCloud* InOwner); 11 | 12 | virtual ~FNiagaraDataInterfaceProxyGaussianSplattingPointCloud(); 13 | 14 | void MakeBufferDirty(); 15 | void TryUpdateBuffer(); 16 | void PostDataToGPU(); 17 | 18 | virtual int32 PerInstanceDataPassedToRenderThreadSize() const override{ return 0; } 19 | 20 | TObjectPtr Owner = nullptr; 21 | TObjectPtr PointCloud; 22 | bool bDirty = false; 23 | FReadBuffer GaussianPointDataBuffer; 24 | FCriticalSection BufferLock; 25 | }; 26 | 27 | UCLASS(EditInlineNew, Category = "Array", meta = (DisplayName = "Gaussian Splatting Point Cloud", Experimental), Blueprintable, BlueprintType) 28 | class GAUSSIANSPLATTINGRUNTIME_API UNiagaraDataInterfaceGaussianSplattingPointCloud: public UNiagaraDataInterface 29 | { 30 | GENERATED_UCLASS_BODY() 31 | 32 | BEGIN_SHADER_PARAMETER_STRUCT(FShaderParameters, ) 33 | SHADER_PARAMETER(int, PointCount) 34 | SHADER_PARAMETER_SRV(Buffer, PointDataBuffer) 35 | END_SHADER_PARAMETER_STRUCT() 36 | public: 37 | void SetPointCloud(UGaussianSplattingPointCloud* InPointCloud); 38 | UGaussianSplattingPointCloud* GetPointCloud() const; 39 | void GetPointCount(FVectorVMExternalFunctionContext& Context); 40 | void GetPointData(FVectorVMExternalFunctionContext& Context); 41 | 42 | protected: 43 | friend struct FNiagaraDataInterfaceProxyGaussianSplattingPointCloud; 44 | 45 | static const FName GetPointDataFunctionName; 46 | static const FName GetPointCountFunctionName; 47 | 48 | static const FString PointCountName; 49 | static const FString PointDataBufferName; 50 | 51 | UPROPERTY(BlueprintReadWrite, EditAnywhere, Category = "Gaussian Splatting") 52 | TObjectPtr PointCloud; 53 | 54 | virtual void GetVMExternalFunction(const FVMExternalFunctionBindingInfo& BindingInfo, void* InstanceData, FVMExternalFunction& OutFunc) override; 55 | 56 | virtual bool CanExecuteOnTarget(ENiagaraSimTarget Target) const override{ return true; } 57 | 58 | #if WITH_EDITORONLY_DATA 59 | virtual bool AppendCompileHash(FNiagaraCompileHashVisitor* InVisitor) const override; 60 | virtual bool GetFunctionHLSL(const FNiagaraDataInterfaceGPUParamInfo& ParamInfo, const FNiagaraDataInterfaceGeneratedFunction& FunctionInfo, int FunctionInstanceIndex, FString& OutHLSL) override; 61 | virtual void GetParameterDefinitionHLSL(const FNiagaraDataInterfaceGPUParamInfo& ParamInfo, FString& OutHLSL) override; 62 | #endif 63 | virtual void BuildShaderParameters(FNiagaraShaderParametersBuilder& ShaderParametersBuilder) const override; 64 | virtual void SetShaderParameters(const FNiagaraDataInterfaceSetShaderParametersContext& Context) const override; 65 | 66 | virtual bool Equals(const UNiagaraDataInterface* Other) const override; 67 | 68 | #if WITH_EDITOR 69 | virtual void PostEditChangeProperty(struct FPropertyChangedEvent& PropertyChangedEvent) override; 70 | #endif // WITH_EDITOR 71 | 72 | virtual void PostInitProperties() override; 73 | virtual void PostLoad() override; 74 | 75 | protected: 76 | #if WITH_EDITORONLY_DATA 77 | 78 | #if UE_VERSION_NEWER_THAN(5, 4, 0) 79 | virtual void GetFunctionsInternal(TArray& OutFunctions) const override; 80 | #else 81 | void GetFunctions(TArray& OutFunctions) override; 82 | #endif 83 | 84 | #endif 85 | virtual bool CopyToInternal(UNiagaraDataInterface* Destination) const override; 86 | }; 87 | -------------------------------------------------------------------------------- /Source/GaussianSplattingRuntime/Public/GaussianSplattingRuntimeModule.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "CoreMinimal.h" 4 | #include "Modules/ModuleManager.h" 5 | 6 | class FGaussianSplattingRuntimeModule : public IModuleInterface 7 | { 8 | public: 9 | /** IModuleInterface implementation */ 10 | virtual void StartupModule() override; 11 | virtual void ShutdownModule() override; 12 | }; 13 | --------------------------------------------------------------------------------