Compare commits

...

10 Commits

Author SHA1 Message Date
rsformoso add37a9288 Add readme and license 2026-03-19 11:57:12 +08:00
rsformoso 3c9e88531d Update documentation and TODO list 2026-03-19 11:20:43 +08:00
rsformoso 5c5d55955e Disable HDR. Fix yellow tint on camera 2026-03-19 10:56:13 +08:00
rsformoso 4d03d277a9 Add Cinemachine package 2026-03-18 22:56:39 +08:00
rsformoso 95d7b81cde Implement spherical harmonics 2026-03-18 22:33:10 +08:00
rsformoso b9a9220156 Misc configs 2026-03-18 20:18:48 +08:00
rsformoso aa1a6584f6 wip fix sh 2026-03-18 20:15:37 +08:00
rsformoso 6d4166ed60 Implement Burst compiled importer 2026-03-18 17:05:13 +08:00
rsformoso 8ca58a1f13 Interesting cel-shaded like effect 2026-03-18 16:49:41 +08:00
rsformoso 77040b7d56 tmp 2026-03-17 17:41:49 +08:00
19 changed files with 2272 additions and 1192 deletions
+12 -4
View File
@@ -12,8 +12,8 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: bf2edee5c58d82540a51f03df9d42094, type: 3} m_Script: {fileID: 11500000, guid: bf2edee5c58d82540a51f03df9d42094, type: 3}
m_Name: Mobile_RPAsset m_Name: Mobile_RPAsset
m_EditorClassIdentifier: m_EditorClassIdentifier:
k_AssetVersion: 12 k_AssetVersion: 13
k_AssetPreviousVersion: 12 k_AssetPreviousVersion: 13
m_RendererType: 1 m_RendererType: 1
m_RendererData: {fileID: 0} m_RendererData: {fileID: 0}
m_RendererDataList: m_RendererDataList:
@@ -23,7 +23,7 @@ MonoBehaviour:
m_RequireOpaqueTexture: 0 m_RequireOpaqueTexture: 0
m_OpaqueDownsampling: 0 m_OpaqueDownsampling: 0
m_SupportsTerrainHoles: 1 m_SupportsTerrainHoles: 1
m_SupportsHDR: 1 m_SupportsHDR: 0
m_HDRColorBufferPrecision: 0 m_HDRColorBufferPrecision: 0
m_MSAA: 1 m_MSAA: 1
m_RenderScale: 0.8 m_RenderScale: 0.8
@@ -53,6 +53,7 @@ MonoBehaviour:
m_AdditionalLightsShadowResolutionTierHigh: 1024 m_AdditionalLightsShadowResolutionTierHigh: 1024
m_ReflectionProbeBlending: 1 m_ReflectionProbeBlending: 1
m_ReflectionProbeBoxProjection: 1 m_ReflectionProbeBoxProjection: 1
m_ReflectionProbeAtlas: 1
m_ShadowDistance: 50 m_ShadowDistance: 50
m_ShadowCascadeCount: 1 m_ShadowCascadeCount: 1
m_Cascade2Split: 0.25 m_Cascade2Split: 0.25
@@ -78,11 +79,11 @@ MonoBehaviour:
m_UseAdaptivePerformance: 1 m_UseAdaptivePerformance: 1
m_ColorGradingMode: 0 m_ColorGradingMode: 0
m_ColorGradingLutSize: 32 m_ColorGradingLutSize: 32
m_AllowPostProcessAlphaOutput: 0
m_UseFastSRGBLinearConversion: 1 m_UseFastSRGBLinearConversion: 1
m_SupportDataDrivenLensFlare: 1 m_SupportDataDrivenLensFlare: 1
m_SupportScreenSpaceLensFlare: 1 m_SupportScreenSpaceLensFlare: 1
m_GPUResidentDrawerMode: 0 m_GPUResidentDrawerMode: 0
m_UseLegacyLightmaps: 0
m_SmallMeshScreenPercentage: 0 m_SmallMeshScreenPercentage: 0
m_GPUResidentDrawerEnableOcclusionCullingInCameras: 0 m_GPUResidentDrawerEnableOcclusionCullingInCameras: 0
m_ShadowType: 1 m_ShadowType: 1
@@ -109,6 +110,7 @@ MonoBehaviour:
m_PrefilterDebugKeywords: 1 m_PrefilterDebugKeywords: 1
m_PrefilterWriteRenderingLayers: 1 m_PrefilterWriteRenderingLayers: 1
m_PrefilterHDROutput: 1 m_PrefilterHDROutput: 1
m_PrefilterAlphaOutput: 0
m_PrefilterSSAODepthNormals: 1 m_PrefilterSSAODepthNormals: 1
m_PrefilterSSAOSourceDepthLow: 1 m_PrefilterSSAOSourceDepthLow: 1
m_PrefilterSSAOSourceDepthMedium: 0 m_PrefilterSSAOSourceDepthMedium: 0
@@ -126,8 +128,14 @@ MonoBehaviour:
m_PrefilterSoftShadowsQualityHigh: 1 m_PrefilterSoftShadowsQualityHigh: 1
m_PrefilterSoftShadows: 0 m_PrefilterSoftShadows: 0
m_PrefilterScreenCoord: 1 m_PrefilterScreenCoord: 1
m_PrefilterScreenSpaceIrradiance: 0
m_PrefilterNativeRenderPass: 1 m_PrefilterNativeRenderPass: 1
m_PrefilterUseLegacyLightmaps: 0 m_PrefilterUseLegacyLightmaps: 0
m_PrefilterBicubicLightmapSampling: 0
m_PrefilterReflectionProbeRotation: 0
m_PrefilterReflectionProbeBlending: 0
m_PrefilterReflectionProbeBoxProjection: 0
m_PrefilterReflectionProbeAtlas: 0
m_ShaderVariantLogLevel: 0 m_ShaderVariantLogLevel: 0
m_ShadowCascades: 0 m_ShadowCascades: 0
m_Textures: m_Textures:
+3
View File
@@ -34,6 +34,9 @@ MonoBehaviour:
highQualityFiltering: highQualityFiltering:
m_OverrideState: 1 m_OverrideState: 1
m_Value: 1 m_Value: 1
filter:
m_OverrideState: 0
m_Value: 0
downscale: downscale:
m_OverrideState: 0 m_OverrideState: 0
m_Value: 0 m_Value: 0
+74 -2
View File
@@ -133,6 +133,10 @@ PrefabInstance:
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: -973084862543737302, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3} - target: {fileID: -973084862543737302, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3}
propertyPath: m_PropertySheet.m_Float.m_Array.Array.size propertyPath: m_PropertySheet.m_Float.m_Array.Array.size
value: 2
objectReference: {fileID: 0}
- target: {fileID: -973084862543737302, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3}
propertyPath: m_PropertySheet.m_Gradient.m_Array.Array.size
value: 1 value: 1
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: -973084862543737302, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3} - target: {fileID: -973084862543737302, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3}
@@ -147,6 +151,10 @@ PrefabInstance:
propertyPath: m_PropertySheet.m_Float.m_Array.Array.data[0].m_Name propertyPath: m_PropertySheet.m_Float.m_Array.Array.data[0].m_Name
value: ScaleMultiplier value: ScaleMultiplier
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: -973084862543737302, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3}
propertyPath: m_PropertySheet.m_Float.m_Array.Array.data[1].m_Name
value: OpacityMultiplier
objectReference: {fileID: 0}
- target: {fileID: -973084862543737302, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3} - target: {fileID: -973084862543737302, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3}
propertyPath: m_PropertySheet.m_Uint.m_Array.Array.data[0].m_Value propertyPath: m_PropertySheet.m_Uint.m_Array.Array.data[0].m_Value
value: 200000 value: 200000
@@ -159,6 +167,14 @@ PrefabInstance:
propertyPath: m_PropertySheet.m_Float.m_Array.Array.data[0].m_Value propertyPath: m_PropertySheet.m_Float.m_Array.Array.data[0].m_Value
value: 64 value: 64
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: -973084862543737302, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3}
propertyPath: m_PropertySheet.m_Float.m_Array.Array.data[1].m_Value
value: 1
objectReference: {fileID: 0}
- target: {fileID: -973084862543737302, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3}
propertyPath: m_PropertySheet.m_Gradient.m_Array.Array.data[0].m_Name
value: GradientScale
objectReference: {fileID: 0}
- target: {fileID: -973084862543737302, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3} - target: {fileID: -973084862543737302, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3}
propertyPath: m_PropertySheet.m_Uint.m_Array.Array.data[0].m_Overridden propertyPath: m_PropertySheet.m_Uint.m_Array.Array.data[0].m_Overridden
value: 1 value: 1
@@ -171,6 +187,62 @@ PrefabInstance:
propertyPath: m_PropertySheet.m_Float.m_Array.Array.data[0].m_Overridden propertyPath: m_PropertySheet.m_Float.m_Array.Array.data[0].m_Overridden
value: 1 value: 1
objectReference: {fileID: 0} objectReference: {fileID: 0}
- target: {fileID: -973084862543737302, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3}
propertyPath: m_PropertySheet.m_Float.m_Array.Array.data[1].m_Overridden
value: 1
objectReference: {fileID: 0}
- target: {fileID: -973084862543737302, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3}
propertyPath: m_PropertySheet.m_Gradient.m_Array.Array.data[0].m_Overridden
value: 1
objectReference: {fileID: 0}
- target: {fileID: -973084862543737302, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3}
propertyPath: m_PropertySheet.m_Gradient.m_Array.Array.data[0].m_Value.atime0
value: 0
objectReference: {fileID: 0}
- target: {fileID: -973084862543737302, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3}
propertyPath: m_PropertySheet.m_Gradient.m_Array.Array.data[0].m_Value.atime1
value: 65535
objectReference: {fileID: 0}
- target: {fileID: -973084862543737302, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3}
propertyPath: m_PropertySheet.m_Gradient.m_Array.Array.data[0].m_Value.ctime0
value: 0
objectReference: {fileID: 0}
- target: {fileID: -973084862543737302, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3}
propertyPath: m_PropertySheet.m_Gradient.m_Array.Array.data[0].m_Value.ctime1
value: 65535
objectReference: {fileID: 0}
- target: {fileID: -973084862543737302, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3}
propertyPath: m_PropertySheet.m_Gradient.m_Array.Array.data[0].m_Value.key1.a
value: 1
objectReference: {fileID: 0}
- target: {fileID: -973084862543737302, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3}
propertyPath: m_PropertySheet.m_Gradient.m_Array.Array.data[0].m_Value.key1.b
value: 1
objectReference: {fileID: 0}
- target: {fileID: -973084862543737302, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3}
propertyPath: m_PropertySheet.m_Gradient.m_Array.Array.data[0].m_Value.key1.g
value: 1
objectReference: {fileID: 0}
- target: {fileID: -973084862543737302, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3}
propertyPath: m_PropertySheet.m_Gradient.m_Array.Array.data[0].m_Value.key1.r
value: 1
objectReference: {fileID: 0}
- target: {fileID: -973084862543737302, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3}
propertyPath: m_PropertySheet.m_Gradient.m_Array.Array.data[0].m_Value.m_Mode
value: 0
objectReference: {fileID: 0}
- target: {fileID: -973084862543737302, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3}
propertyPath: m_PropertySheet.m_Gradient.m_Array.Array.data[0].m_Value.m_ColorSpace
value: 1
objectReference: {fileID: 0}
- target: {fileID: -973084862543737302, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3}
propertyPath: m_PropertySheet.m_Gradient.m_Array.Array.data[0].m_Value.m_NumAlphaKeys
value: 2
objectReference: {fileID: 0}
- target: {fileID: -973084862543737302, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3}
propertyPath: m_PropertySheet.m_Gradient.m_Array.Array.data[0].m_Value.m_NumColorKeys
value: 2
objectReference: {fileID: 0}
- target: {fileID: -774367247743995920, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3} - target: {fileID: -774367247743995920, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3}
propertyPath: m_Enabled propertyPath: m_Enabled
value: 1 value: 1
@@ -318,8 +390,8 @@ Transform:
m_PrefabAsset: {fileID: 0} m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1826889287} m_GameObject: {fileID: 1826889287}
serializedVersion: 2 serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} m_LocalRotation: {x: -0.048649408, y: -0.18707958, z: 0.009037506, w: -0.9810978}
m_LocalPosition: {x: 0, y: 0, z: -5} m_LocalPosition: {x: -0.4305725, y: 0.22459221, z: -1.4613647}
m_LocalScale: {x: 1, y: 1, z: 1} m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0 m_ConstrainProportionsScale: 0
m_Children: [] m_Children: []
+235 -212
View File
@@ -3,34 +3,15 @@ using Unity.Mathematics;
using UnityEngine; using UnityEngine;
using UnityEngine.VFX; using UnityEngine.VFX;
using UnityEngine.VFX.Utility; using UnityEngine.VFX.Utility;
using UnityEditor;
using UnityEditor.AssetImporters; using UnityEditor.AssetImporters;
using UnityEditor.Experimental; using UnityEditor.Experimental;
using System;
using System.IO; using System.IO;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Text; using System.Text;
using UnityEditor.Rendering.LookDev;
using System.Linq; using System.Linq;
using System.Collections.Generic; using System.Collections.Generic;
using Unity.VectorGraphics;
using Unity.VisualScripting;
using System.Collections;
using Unity.Collections; using Unity.Collections;
using UnityEditor.Experimental.GraphView; using Unity.Jobs;
/*******************************************************************************
TODO:
1. Move header parsing to ParsePLYFile
2. Improve exception reporting and handling
3. Use Burst compiler for parsing binary data
4. If we can reasonably assume that items are contigious (e.g. position)
use MemoryMarshal
5. Use a struct for header_info
6. Write handler for data other than 32 bit float
7. Pass around Span<byte> instead of byte[] for memory safety
8. Implement opacity clamping in the VFX Graph
*******************************************************************************/
namespace SplatVfx.Editor namespace SplatVfx.Editor
{ {
@@ -39,16 +20,14 @@ namespace SplatVfx.Editor
public sealed class SplatImporter : ScriptedImporter public sealed class SplatImporter : ScriptedImporter
{ {
const float SH_C0 = 0.28209479177387814f; const float SH_C0 = 0.28209479177387814f;
const string DefaultVfxPath = "Assets/vfx-graph-gs/SplatVFX.vfx";
#region ScriptedImporter implementation
[BurstCompile]
public override void OnImportAsset(AssetImportContext context) public override void OnImportAsset(AssetImportContext context)
{ {
var ply = ParsePLYFile(context.assetPath); var (headerString, endHeaderPosition) = GetHeader(context.assetPath);
var header_info = ParseHeader(ply.header); var headerData = ParseMetadata(headerString);
var data = ImportAsSplatData(header_info, ply.binaryData); var data = ImportData(headerData, context.assetPath, endHeaderPosition);
data.name = Path.GetFileNameWithoutExtension(context.assetPath); data.name = Path.GetFileNameWithoutExtension(context.assetPath);
context.AddObjectToAsset("data", data); context.AddObjectToAsset("data", data);
@@ -57,14 +36,7 @@ namespace SplatVfx.Editor
context.SetMainObject(prefab); context.SetMainObject(prefab);
} }
#endregion private GameObject CreatePrefab(SplatData data)
#region Prefab construction
const string DefaultVfxPath = "Assets/vfx-graph-gs/SplatVFX.vfx";
[BurstCompile]
GameObject CreatePrefab(SplatData data)
{ {
var go = new GameObject(); var go = new GameObject();
var vfx = go.AddComponent<VisualEffect>(); var vfx = go.AddComponent<VisualEffect>();
@@ -78,12 +50,70 @@ namespace SplatVfx.Editor
return go; return go;
} }
#endregion const int MAX_CHUNK_SIZE = 1048576 * 256; // 256MiB
#region Reader implementation private enum TypeEnum
{
None, Float32, Float64, Float16, Int8, UInt8, Char, Short, UInt16
}
[BurstCompile] private enum DataEnum
(string header, byte[] binaryData) ParsePLYFile(string filePath) {
x, y, z, f_dc_0, f_dc_1, f_dc_2, f_rest_0, f_rest_1, f_rest_2,
f_rest_3, f_rest_4, f_rest_5, f_rest_6, f_rest_7, f_rest_8,
f_rest_9, f_rest_10, f_rest_11, f_rest_12, f_rest_13, f_rest_14,
f_rest_15, f_rest_16, f_rest_17, f_rest_18, f_rest_19, f_rest_20,
f_rest_21, f_rest_22, f_rest_23, f_rest_24, f_rest_25, f_rest_26,
f_rest_27, f_rest_28, f_rest_29, f_rest_30, f_rest_31, f_rest_32,
f_rest_33, f_rest_34, f_rest_35, f_rest_36, f_rest_37, f_rest_38,
f_rest_39, f_rest_40, f_rest_41, f_rest_42, f_rest_43, f_rest_44,
opacity, scale_0, scale_1, scale_2, rot_0, rot_1, rot_2, rot_3
}
private static readonly Dictionary<TypeEnum, int> TypeToByteLengths =
new()
{
{ TypeEnum.Float32, 4 },
{ TypeEnum.Float64, 8 },
{ TypeEnum.Float16, 2 }, // keeping this here to support half precision floats in the future
{ TypeEnum.UInt8, 1 },
{ TypeEnum.Int8, 1 }
};
private static readonly Dictionary<string, TypeEnum> TypeMappings =
new()
{
{ "float", TypeEnum.Float32 },
{ "double", TypeEnum.Float64 },
{ "uint8", TypeEnum.UInt8 },
{ "int8", TypeEnum.Int8 }
};
private struct HeaderData
{
public uint NumElements;
public uint BinaryDataStartOffset;
public NativeArray<int> ElementByteOffsets;
public NativeArray<int> ElementTypes;
public int BytesPerElement;
public HeaderData(int count)
{
NumElements = 0;
BinaryDataStartOffset = 0;
ElementByteOffsets = new NativeArray<int>(count, Allocator.Persistent);
ElementTypes = new NativeArray<int>(count, Allocator.Persistent);
BytesPerElement = 0;
}
public void Dispose()
{
ElementByteOffsets.Dispose();
ElementTypes.Dispose();
}
}
private static (string headerString, int endHeaderPosition) GetHeader(string filePath)
{ {
using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read); using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
using var reader = new StreamReader(fileStream); using var reader = new StreamReader(fileStream);
@@ -100,229 +130,222 @@ namespace SplatVfx.Editor
} }
} }
long endHeaderPosition = headerBuilder.Length - headerBuilder.ToString().Split('\n').Length + 1; // Old logic that works because AppendLine adds another \n
fileStream.Seek(endHeaderPosition, SeekOrigin.Begin); // TODO: Fix this logic
int endHeaderPosition = headerBuilder.Length - headerBuilder.ToString().Split('\n').Length + 1;
Debug.Log($"Total filesize: {fileStream.Length}");
Debug.Log($"Byte stream start position: {endHeaderPosition}");
var binaryReader = new BinaryReader(fileStream); return (headerBuilder.ToString(), endHeaderPosition);
var binaryData = binaryReader.ReadBytes((int)(fileStream.Length - endHeaderPosition));
return (headerBuilder.ToString(), binaryData);
} }
[BurstCompile] private static HeaderData ParseMetadata(string headerString)
(string format, int num_element, List<(string, string)> properties)
ParseHeader(string header)
{ {
using var reader = new StringReader(header); HeaderData result = new(59);
string line; string line;
string format = ""; using var reader = new StringReader(headerString);
int num_element = 0;
var properties = new List<(string, string)>();
while ((line = reader.ReadLine()) != null) while ((line = reader.ReadLine()) != null)
{ {
if (line.StartsWith("end_header")) if (line.Trim() == "end_header")
{
break; break;
}
var strings = line.Split(" "); var strings = line.Split(" ");
switch (strings.FirstOrDefault()) switch (strings.FirstOrDefault())
{ {
case "format":
format = strings[1];
break;
case "element": case "element":
num_element = int.Parse(strings[2]); result.NumElements = uint.Parse(strings[2]);
break; break;
case "property": case "property":
properties.Add((strings[2].Trim(), strings[1].Trim())); TypeEnum dataType = TypeMappings[strings[1]];
// If in DataEnum, store byte offset and type
if (DataEnum.TryParse(strings[2], out DataEnum dataName))
{
int ix = (int)dataName;
result.ElementByteOffsets[ix] = result.BytesPerElement;
result.ElementTypes[ix] = (int)dataType;
}
result.BytesPerElement += TypeToByteLengths[dataType];
break; break;
} }
} }
Debug.Log($"Bytes per Element: {result.BytesPerElement}");
return (format, num_element, properties); return result;
} }
[BurstCompile] [BurstCompile]
SplatData ImportAsSplatData((string format, int num_element, List<(string, string)> properties) header_info, byte[] binaryData) private struct SplatDataNative
{
public NativeArray<float3> position;
public NativeArray<float3> color;
public NativeArray<float> sh;
public NativeArray<float> opacity;
public NativeArray<float3> scale;
public NativeArray<float4> rotation;
public SplatDataNative(int count)
{
position = new NativeArray<float3>(count, Allocator.Persistent);
color = new NativeArray<float3>(count, Allocator.Persistent);
sh = new NativeArray<float>(count * 45, Allocator.Persistent);
opacity = new NativeArray<float>(count, Allocator.Persistent);
scale = new NativeArray<float3>(count, Allocator.Persistent);
rotation = new NativeArray<float4>(count, Allocator.Persistent);
}
public void Dispose()
{
position.Dispose();
color.Dispose();
sh.Dispose();
opacity.Dispose();
scale.Dispose();
rotation.Dispose();
}
}
private static SplatData ImportData(HeaderData headerData, string filePath, int endHeaderPosition)
{ {
var splatData = ScriptableObject.CreateInstance<SplatData>(); var splatData = ScriptableObject.CreateInstance<SplatData>();
var stream = new MemoryStream(binaryData); splatData.PositionArray = new Vector3[headerData.NumElements];
var reader = new BinaryReader(stream); splatData.ScaleArray = new Vector3[headerData.NumElements];
splatData.RotationArray = new Quaternion[headerData.NumElements];
splatData.ColorArray = new Color[headerData.NumElements];
splatData.OpacityArray = new float[headerData.NumElements];
splatData.SHArray = new float[headerData.NumElements * 45];
splatData.PositionArray = new Vector3[header_info.num_element]; int elementsPerChunk = Mathf.Min(
splatData.ScaleArray = new Vector3[header_info.num_element]; MAX_CHUNK_SIZE / headerData.BytesPerElement,
splatData.RotationArray = new Quaternion[header_info.num_element]; (int)headerData.NumElements
splatData.ColorArray = new Color[header_info.num_element]; );
splatData.OpacityArray = new float[header_info.num_element]; int chunkSize = elementsPerChunk * headerData.BytesPerElement;
splatData.SHArray = new float[header_info.num_element * 45];
for (int i = 0; i < header_info.num_element; i++) // Debug.Log($"Elements per chunk: {elementsPerChunk}");
// Debug.Log($"Chunk size: {chunkSize}");
using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
fileStream.Seek(endHeaderPosition, SeekOrigin.Begin);
int totalBytes = (int)fileStream.Length - endHeaderPosition;
// Debug.Log($"Total bytes: {totalBytes}");
var splatDataNative = new SplatDataNative(elementsPerChunk);
var chunkBuffer = new NativeArray<byte>(chunkSize, Allocator.Persistent);
var chunkBufferSpan = MemoryMarshal.AsBytes<byte>(chunkBuffer);
int numBytesRead;
int numElemsRead;
int totalElemsRead = 0;
while ((numBytesRead = fileStream.Read(chunkBufferSpan)) > 0)
{ {
foreach ((string name, string dtype) property in header_info.properties) numElemsRead = numBytesRead / headerData.BytesPerElement;
var splatElementJob = new SplatElementJob
{ {
float val = reader.ReadSingle(); chunkBytes = chunkBuffer,
splatDataNative = splatDataNative,
headerData = headerData,
totalElemsRead = totalElemsRead,
numElemsRead = numElemsRead
};
switch (property.name) splatElementJob.Schedule().Complete();
for (int i = 0; i < numElemsRead; i++)
{ {
case "x": splatData.PositionArray[totalElemsRead + i] = splatDataNative.position[i];
splatData.PositionArray[i].x = val; splatData.ColorArray[totalElemsRead + i].r = splatDataNative.color[i].x;
splatData.ColorArray[totalElemsRead + i].g = splatDataNative.color[i].y;
break; splatData.ColorArray[totalElemsRead + i].b = splatDataNative.color[i].z;
splatData.OpacityArray[totalElemsRead + i] = splatDataNative.opacity[i];
case "y": splatData.ScaleArray[totalElemsRead + i] = splatDataNative.scale[i];
splatData.PositionArray[i].y = val; splatData.RotationArray[totalElemsRead + i] = new quaternion(splatDataNative.rotation[i]);
for (int j = 0; j < 45; j++)
break;
case "z":
splatData.PositionArray[i].z = val;
break;
case "opacity":
splatData.OpacityArray[i] = 1 / (1 + math.exp(-val));
break;
default:
if (property.name.StartsWith("f_dc"))
{ {
switch (property.name[5]) splatData.SHArray[(totalElemsRead + i) * 45 + j] = splatDataNative.sh[i * 45 + j];
{
case '0':
splatData.ColorArray[i].r = 0.5f + val * SH_C0;
break;
case '1':
splatData.ColorArray[i].g = 0.5f + val * SH_C0;
break;
case '2':
splatData.ColorArray[i].b = 0.5f + val * SH_C0;
break;
}
}
else if (property.name.StartsWith("scale"))
{
switch (property.name[6])
{
case '0':
splatData.ScaleArray[i].x = math.exp(val);
break;
case '1':
splatData.ScaleArray[i].y = math.exp(val);
break;
case '2':
splatData.ScaleArray[i].z = math.exp(val);
break;
}
}
else if (property.name.StartsWith("rot"))
{
switch (property.name[4])
{
case '0':
splatData.RotationArray[i].w = val;
break;
case '1':
splatData.RotationArray[i].x = val;
break;
case '2':
splatData.RotationArray[i].y = val;
break;
case '3':
splatData.RotationArray[i].z = val;
break;
}
}
break;
} }
} }
totalElemsRead += numElemsRead;
// Debug.Log($"Current Elements Read: {totalElemsRead}");
} }
// Debug.Log($"First position Vector3: {splatData.PositionArray[0]}");
headerData.Dispose();
splatDataNative.Dispose();
chunkBuffer.Dispose();
splatData.ReleaseGpuResources(); splatData.ReleaseGpuResources();
return splatData; return splatData;
} }
// struct ReadData [BurstCompile]
// { private struct SplatElementJob : IJob
// public float px, py, pz; {
// public float sx, sy, sz; public NativeArray<byte> chunkBytes;
// public byte r, g, b, a; public SplatDataNative splatDataNative;
// public byte rw, rx, ry, rz; [ReadOnly] public HeaderData headerData;
// } [ReadOnly] public int totalElemsRead;
[ReadOnly] public int numElemsRead;
// (Vector3[] position, Vector3[] axis, Color[] color) public void Execute()
// LoadDataArrays(string path) {
// { for (int i = 0; i < numElemsRead; i++)
// var bytes = (Span<byte>)File.ReadAllBytes(path); {
// var count = bytes.Length / 32; int elemIx = i;
int start = elemIx * headerData.BytesPerElement;
// var source = MemoryMarshal.Cast<byte, ReadData>(bytes); splatDataNative.position[elemIx] = new()
{
x = chunkBytes.ReinterpretLoad<float>(start + headerData.ElementByteOffsets[(int)DataEnum.x]),
y = chunkBytes.ReinterpretLoad<float>(start + headerData.ElementByteOffsets[(int)DataEnum.y]),
z = chunkBytes.ReinterpretLoad<float>(start + headerData.ElementByteOffsets[(int)DataEnum.z])
};
// var position = new Vector3[count]; float3 color = new()
// var axis = new Vector3[count * 3]; {
// var color = new Color[count]; x = chunkBytes.ReinterpretLoad<float>(start + headerData.ElementByteOffsets[(int)DataEnum.f_dc_0]),
y = chunkBytes.ReinterpretLoad<float>(start + headerData.ElementByteOffsets[(int)DataEnum.f_dc_1]),
z = chunkBytes.ReinterpretLoad<float>(start + headerData.ElementByteOffsets[(int)DataEnum.f_dc_2])
};
color = 0.5f + color * SH_C0;
splatDataNative.color[elemIx] = color;
// for (var i = 0; i < count; i++) for (int j = 0; j < 45; j++)
// ParseReadData(source[i], {
// out position[i], splatDataNative.sh[elemIx * 45 + j] = chunkBytes.ReinterpretLoad<float>(start + headerData.ElementByteOffsets[(int)DataEnum.f_rest_0 + j]);
// out axis[i * 3],
// out axis[i * 3 + 1],
// out axis[i * 3 + 2],
// out color[i]);
// return (position, axis, color);
// }
// [BurstCompile]
// void ParseReadData(in ReadData src,
// out Vector3 position,
// out Vector3 axis1,
// out Vector3 axis2,
// out Vector3 axis3,
// out Color color)
// {
// var rv = (math.float4(src.rx, src.ry, src.rz, src.rw) - 128) / 128;
// var q = math.quaternion(-rv.x, -rv.y, rv.z, rv.w);
// position = math.float3(src.px, src.py, -src.pz);
// axis1 = math.mul(q, math.float3(src.sx, 0, 0));
// axis2 = math.mul(q, math.float3(0, src.sy, 0));
// axis3 = math.mul(q, math.float3(0, 0, src.sz));
// color = (Vector4)math.float4(src.r, src.g, src.b, src.a) / 255;
// }
#endregion
} }
} // namespace SplatVfx.Editor float opacity = chunkBytes.ReinterpretLoad<float>(start + headerData.ElementByteOffsets[(int)DataEnum.opacity]);
opacity = 1 / (1 + math.exp(-opacity));
splatDataNative.opacity[elemIx] = opacity;
float3 scale = new()
{
x = chunkBytes.ReinterpretLoad<float>(start + headerData.ElementByteOffsets[(int)DataEnum.scale_0]),
y = chunkBytes.ReinterpretLoad<float>(start + headerData.ElementByteOffsets[(int)DataEnum.scale_1]),
z = chunkBytes.ReinterpretLoad<float>(start + headerData.ElementByteOffsets[(int)DataEnum.scale_2])
};
scale = math.exp(scale);
splatDataNative.scale[elemIx] = scale;
splatDataNative.rotation[elemIx] = new()
{
w = chunkBytes.ReinterpretLoad<float>(start + headerData.ElementByteOffsets[(int)DataEnum.rot_0]),
x = chunkBytes.ReinterpretLoad<float>(start + headerData.ElementByteOffsets[(int)DataEnum.rot_1]),
y = chunkBytes.ReinterpretLoad<float>(start + headerData.ElementByteOffsets[(int)DataEnum.rot_2]),
z = chunkBytes.ReinterpretLoad<float>(start + headerData.ElementByteOffsets[(int)DataEnum.rot_3])
};
}
}
}
}
}
File diff suppressed because it is too large Load Diff
@@ -1,5 +1,8 @@
#define sh(i) (float3)SHBuffer.Load3(Index * 45 + (i - 1) * 15) /// Index: The splat's index
/// Degrees: The degrees of spherical harmonics to calculate
/// DirClr: A 4x4 matrix with the first row containing a vector from the splat position to the camera, and the second row containing its base color
/// SHBuffer: The spherical harmonics buffer from a SplatData object
float3 CalculateSHColor(uint Index, uint Degrees, float4x4 DirClr, StructuredBuffer<float> SHBuffer) {
const float SH_C0 = 0.28209479177387814f; const float SH_C0 = 0.28209479177387814f;
const float SH_C1 = 0.4886025119029199f; const float SH_C1 = 0.4886025119029199f;
const float SH_C2[] = { const float SH_C2[] = {
@@ -19,39 +22,52 @@ const float SH_C3[] = {
-0.5900435899266435f -0.5900435899266435f
}; };
// float3 CalculateSHColor(uint Index, uint Degrees, float3 Direction, StructuredBuffer<float3> SHBuffer) {
float3 CalculateSHColor(uint Index, uint Degrees, float4x4 DirClr, ByteAddressBuffer SHBuffer) {
float3 dir = DirClr[0].xyz; float3 dir = DirClr[0].xyz;
float3 result = DirClr[1].xyz; float3 result = DirClr[1].xyz;
float3 sh[16];
// for (int i = 1; i < 16; i++)
// {
// sh[i].x = SHBuffer[Index * 45 + (i - 1) * 3];
// sh[i].y = SHBuffer[Index * 45 + (i - 1) * 3 + 1];
// sh[i].z = SHBuffer[Index * 45 + (i - 1) * 3 + 2];
// }
for (int i = 1; i < 16; i++)
{
sh[i].x = SHBuffer[Index * 45 + (i - 1)];
sh[i].y = SHBuffer[Index * 45 + 15 + (i - 1)];
sh[i].z = SHBuffer[Index * 45 + 30 + (i - 1)];
}
if (Degrees > 0) if (Degrees > 0)
{ {
float x = dir.x; float x = dir.x;
float y = dir.y; float y = dir.y;
float z = dir.z; float z = dir.z;
result = result - SH_C1 * y * sh(1) + SH_C1 * z * sh(2) - SH_C1 * x * sh(3); result = result - SH_C1 * y * sh[1] + SH_C1 * z * sh[2] - SH_C1 * x * sh[3];
if (Degrees > 1) if (Degrees > 1)
{ {
float xx = x * x, yy = y * y, zz = z * z; float xx = x * x, yy = y * y, zz = z * z;
float xy = x * y, yz = y * z, xz = x * z; float xy = x * y, yz = y * z, xz = x * z;
result = result + result = result +
SH_C2[0] * xy * sh(4) + SH_C2[0] * xy * sh[4] +
SH_C2[1] * yz * sh(5) + SH_C2[1] * yz * sh[5] +
SH_C2[2] * (2.0f * zz - xx - yy) * sh(6) + SH_C2[2] * (2.0f * zz - xx - yy) * sh[6] +
SH_C2[3] * xz * sh(7) + SH_C2[3] * xz * sh[7] +
SH_C2[4] * (xx - yy) * sh(8); SH_C2[4] * (xx - yy) * sh[8];
if (Degrees > 2) if (Degrees > 2)
{ {
result = result + result = result +
SH_C3[0] * y * (3.0f * xx - yy) * sh(9) + SH_C3[0] * y * (3.0f * xx - yy) * sh[9] +
SH_C3[1] * xy * z * sh(10) + SH_C3[1] * xy * z * sh[10] +
SH_C3[2] * y * (4.0f * zz - xx - yy) * sh(11) + SH_C3[2] * y * (4.0f * zz - xx - yy) * sh[11] +
SH_C3[3] * z * (2.0f * zz - 3.0f * xx - 3.0f * yy) * sh(12) + SH_C3[3] * z * (2.0f * zz - 3.0f * xx - 3.0f * yy) * sh[12] +
SH_C3[4] * x * (4.0f * zz - xx - yy) * sh(13) + SH_C3[4] * x * (4.0f * zz - xx - yy) * sh[13] +
SH_C3[5] * z * (xx - yy) * sh(14) + SH_C3[5] * z * (xx - yy) * sh[14] +
SH_C3[6] * x * (xx - 3.0f * yy) * sh(15); SH_C3[6] * x * (xx - 3.0f * yy) * sh[15];
} }
} }
} }
@@ -60,6 +76,8 @@ float3 CalculateSHColor(uint Index, uint Degrees, float4x4 DirClr, ByteAddressBu
} }
/* /*
> Reference taken from the original Gaussian splatting paper code
// The implementation is loosely based on code for // The implementation is loosely based on code for
// "Differentiable Point-Based Radiance Fields for // "Differentiable Point-Based Radiance Fields for
// Efficient View Synthesis" by Zhang et al. (2022) // Efficient View Synthesis" by Zhang et al. (2022)
@@ -1,8 +1,10 @@
/// Hidden
float CrossProduct(float2 A, float2 B) float CrossProduct(float2 A, float2 B)
{ {
return A.x * B.y - A.y * B.x; return A.x * B.y - A.y * B.x;
} }
/// Hidden
float SweepArea(float2 A, float2 B) float SweepArea(float2 A, float2 B)
{ {
return abs(CrossProduct(A, B)); return abs(CrossProduct(A, B));
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2026 Rafael Formoso
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+1 -1
View File
@@ -1,5 +1,6 @@
{ {
"dependencies": { "dependencies": {
"com.unity.cinemachine": "2.10.7",
"com.unity.ide.visualstudio": "2.0.27", "com.unity.ide.visualstudio": "2.0.27",
"com.unity.inputsystem": "1.18.0", "com.unity.inputsystem": "1.18.0",
"com.unity.render-pipelines.universal": "17.3.0", "com.unity.render-pipelines.universal": "17.3.0",
@@ -9,7 +10,6 @@
"com.unity.visualeffectgraph": "17.3.0", "com.unity.visualeffectgraph": "17.3.0",
"com.unity.visualscripting": "1.9.10", "com.unity.visualscripting": "1.9.10",
"com.unity.modules.accessibility": "1.0.0", "com.unity.modules.accessibility": "1.0.0",
"com.unity.modules.adaptiveperformance": "1.0.0",
"com.unity.modules.ai": "1.0.0", "com.unity.modules.ai": "1.0.0",
"com.unity.modules.androidjni": "1.0.0", "com.unity.modules.androidjni": "1.0.0",
"com.unity.modules.animation": "1.0.0", "com.unity.modules.animation": "1.0.0",
+9 -8
View File
@@ -10,6 +10,15 @@
}, },
"url": "https://packages.unity.com" "url": "https://packages.unity.com"
}, },
"com.unity.cinemachine": {
"version": "2.10.7",
"depth": 0,
"source": "registry",
"dependencies": {
"com.unity.test-framework": "1.1.31"
},
"url": "https://packages.unity.com"
},
"com.unity.collections": { "com.unity.collections": {
"version": "2.6.2", "version": "2.6.2",
"depth": 2, "depth": 2,
@@ -175,14 +184,6 @@
"source": "builtin", "source": "builtin",
"dependencies": {} "dependencies": {}
}, },
"com.unity.modules.adaptiveperformance": {
"version": "1.0.0",
"depth": 0,
"source": "builtin",
"dependencies": {
"com.unity.modules.subsystems": "1.0.0"
}
},
"com.unity.modules.ai": { "com.unity.modules.ai": {
"version": "1.0.0", "version": "1.0.0",
"depth": 0, "depth": 0,
+1 -1
View File
@@ -61,7 +61,7 @@ GraphicsSettings:
UnityEngine.Rendering.Universal.UniversalRenderPipeline: {fileID: 11400000, guid: 18dc0cd2c080841dea60987a38ce93fa, type: 2} UnityEngine.Rendering.Universal.UniversalRenderPipeline: {fileID: 11400000, guid: 18dc0cd2c080841dea60987a38ce93fa, type: 2}
m_ShaderBuildSettings: m_ShaderBuildSettings:
keywordDeclarationOverrides: [] keywordDeclarationOverrides: []
m_LightsUseLinearIntensity: 1 m_LightsUseLinearIntensity: 0
m_LightsUseColorTemperature: 1 m_LightsUseColorTemperature: 1
m_LogWhenShaderIsCompiled: 0 m_LogWhenShaderIsCompiled: 0
m_LightProbeOutsideHullStrategy: 0 m_LightProbeOutsideHullStrategy: 0
+13 -16
View File
@@ -12,11 +12,13 @@ MonoBehaviour:
m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0} m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0}
m_Name: m_Name:
m_EditorClassIdentifier: m_EditorClassIdentifier:
m_EnablePreviewPackages: 0 m_EnablePreReleasePackages: 0
m_EnablePackageDependencies: 0
m_AdvancedSettingsExpanded: 1 m_AdvancedSettingsExpanded: 1
m_ScopedRegistriesSettingsExpanded: 1 m_ScopedRegistriesSettingsExpanded: 1
oneTimeWarningShown: 0 m_SeeAllPackageVersions: 0
m_DismissPreviewPackagesInUse: 0
oneTimeWarningShown: 1
oneTimePackageErrorsPopUpShown: 0
m_Registries: m_Registries:
- m_Id: main - m_Id: main
m_Name: m_Name:
@@ -24,20 +26,15 @@ MonoBehaviour:
m_Scopes: [] m_Scopes: []
m_IsDefault: 1 m_IsDefault: 1
m_Capabilities: 7 m_Capabilities: 7
m_ConfigSource: 0
m_Compliance:
m_Status: 0
m_Violations: []
m_UserSelectedRegistryName: m_UserSelectedRegistryName:
m_UserAddingNewScopedRegistry: 0 m_UserAddingNewScopedRegistry: 0
m_RegistryInfoDraft: m_RegistryInfoDraft:
m_ErrorMessage:
m_Original:
m_Id:
m_Name:
m_Url:
m_Scopes: []
m_IsDefault: 0
m_Capabilities: 0
m_Modified: 0 m_Modified: 0
m_Name: m_ErrorMessage:
m_Url: m_UserModificationsInstanceId: -900
m_Scopes: m_OriginalInstanceId: -902
- m_LoadAssets: 0
m_SelectedScopeIndex: 0
+9 -3
View File
@@ -47,7 +47,7 @@ PlayerSettings:
defaultScreenWidthWeb: 960 defaultScreenWidthWeb: 960
defaultScreenHeightWeb: 600 defaultScreenHeightWeb: 600
m_StereoRenderingPath: 0 m_StereoRenderingPath: 0
m_ActiveColorSpace: 1 m_ActiveColorSpace: 0
unsupportedMSAAFallback: 0 unsupportedMSAAFallback: 0
m_SpriteBatchMaxVertexCount: 65535 m_SpriteBatchMaxVertexCount: 65535
m_SpriteBatchVertexThreshold: 300 m_SpriteBatchVertexThreshold: 300
@@ -535,6 +535,9 @@ PlayerSettings:
- m_BuildTarget: AndroidPlayer - m_BuildTarget: AndroidPlayer
m_APIs: 150000000b000000 m_APIs: 150000000b000000
m_Automatic: 0 m_Automatic: 0
- m_BuildTarget: WebGLSupport
m_APIs: 1c0000000b000000
m_Automatic: 0
m_BuildTargetVRSettings: [] m_BuildTargetVRSettings: []
m_DefaultShaderChunkSizeInMB: 16 m_DefaultShaderChunkSizeInMB: 16
m_DefaultShaderChunkCount: 0 m_DefaultShaderChunkCount: 0
@@ -560,6 +563,9 @@ PlayerSettings:
- serializedVersion: 3 - serializedVersion: 3
m_BuildTarget: Android m_BuildTarget: Android
m_Formats: 03000000 m_Formats: 03000000
- serializedVersion: 3
m_BuildTarget: WebGL
m_Formats: 05000000
playModeTestRunnerEnabled: 0 playModeTestRunnerEnabled: 0
runPlayModeTestAsEditModeTest: 0 runPlayModeTestAsEditModeTest: 0
actionOnDotNetUnhandledException: 1 actionOnDotNetUnhandledException: 1
@@ -808,7 +814,7 @@ PlayerSettings:
webGLTemplate: APPLICATION:Default webGLTemplate: APPLICATION:Default
webGLAnalyzeBuildSize: 0 webGLAnalyzeBuildSize: 0
webGLUseEmbeddedResources: 0 webGLUseEmbeddedResources: 0
webGLCompressionFormat: 0 webGLCompressionFormat: 1
webGLWasmArithmeticExceptions: 0 webGLWasmArithmeticExceptions: 0
webGLLinkerTarget: 1 webGLLinkerTarget: 1
webGLThreadsSupport: 0 webGLThreadsSupport: 0
@@ -819,7 +825,7 @@ PlayerSettings:
webGLMemoryLinearGrowthStep: 16 webGLMemoryLinearGrowthStep: 16
webGLMemoryGeometricGrowthStep: 0.2 webGLMemoryGeometricGrowthStep: 0.2
webGLMemoryGeometricGrowthCap: 96 webGLMemoryGeometricGrowthCap: 96
webGLPowerPreference: 2 webGLPowerPreference: 1
webGLWebAssemblyTable: 0 webGLWebAssemblyTable: 0
webGLWebAssemblyBigInt: 0 webGLWebAssemblyBigInt: 0
webGLCloseOnQuit: 0 webGLCloseOnQuit: 0
+8 -7
View File
@@ -4,9 +4,9 @@
QualitySettings: QualitySettings:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
serializedVersion: 5 serializedVersion: 5
m_CurrentQuality: 1 m_CurrentQuality: 0
m_QualitySettings: m_QualitySettings:
- serializedVersion: 4 - serializedVersion: 5
name: Mobile name: Mobile
pixelLightCount: 2 pixelLightCount: 2
shadows: 2 shadows: 2
@@ -34,6 +34,7 @@ QualitySettings:
adaptiveVsyncExtraA: 0 adaptiveVsyncExtraA: 0
adaptiveVsyncExtraB: 0 adaptiveVsyncExtraB: 0
lodBias: 1 lodBias: 1
meshLodThreshold: 1
maximumLODLevel: 0 maximumLODLevel: 0
enableLODCrossFade: 1 enableLODCrossFade: 1
streamingMipmapsActive: 0 streamingMipmapsActive: 0
@@ -47,8 +48,7 @@ QualitySettings:
asyncUploadBufferSize: 16 asyncUploadBufferSize: 16
asyncUploadPersistentBuffer: 1 asyncUploadPersistentBuffer: 1
resolutionScalingFixedDPIFactor: 1 resolutionScalingFixedDPIFactor: 1
customRenderPipeline: {fileID: 11400000, guid: 5e6cbd92db86f4b18aec3ed561671858, customRenderPipeline: {fileID: 11400000, guid: 5e6cbd92db86f4b18aec3ed561671858, type: 2}
type: 2}
terrainQualityOverrides: 0 terrainQualityOverrides: 0
terrainPixelError: 1 terrainPixelError: 1
terrainDetailDensityScale: 1 terrainDetailDensityScale: 1
@@ -60,7 +60,7 @@ QualitySettings:
terrainMaxTrees: 50 terrainMaxTrees: 50
excludedTargetPlatforms: excludedTargetPlatforms:
- Standalone - Standalone
- serializedVersion: 4 - serializedVersion: 5
name: PC name: PC
pixelLightCount: 2 pixelLightCount: 2
shadows: 2 shadows: 2
@@ -88,6 +88,7 @@ QualitySettings:
adaptiveVsyncExtraA: 0 adaptiveVsyncExtraA: 0
adaptiveVsyncExtraB: 0 adaptiveVsyncExtraB: 0
lodBias: 2 lodBias: 2
meshLodThreshold: 1
maximumLODLevel: 0 maximumLODLevel: 0
enableLODCrossFade: 1 enableLODCrossFade: 1
streamingMipmapsActive: 0 streamingMipmapsActive: 0
@@ -101,8 +102,7 @@ QualitySettings:
asyncUploadBufferSize: 16 asyncUploadBufferSize: 16
asyncUploadPersistentBuffer: 1 asyncUploadPersistentBuffer: 1
resolutionScalingFixedDPIFactor: 1 resolutionScalingFixedDPIFactor: 1
customRenderPipeline: {fileID: 11400000, guid: 4b83569d67af61e458304325a23e5dfd, customRenderPipeline: {fileID: 11400000, guid: 4b83569d67af61e458304325a23e5dfd, type: 2}
type: 2}
terrainQualityOverrides: 0 terrainQualityOverrides: 0
terrainPixelError: 1 terrainPixelError: 1
terrainDetailDensityScale: 1 terrainDetailDensityScale: 1
@@ -122,6 +122,7 @@ QualitySettings:
GameCoreXboxOne: 1 GameCoreXboxOne: 1
Lumin: 0 Lumin: 0
Nintendo Switch: 1 Nintendo Switch: 1
Nintendo Switch 2: 1
PS4: 1 PS4: 1
PS5: 1 PS5: 1
Server: 0 Server: 0
+121
View File
@@ -0,0 +1,121 @@
{
"templatePinStates": [],
"dependencyTypeInfos": [
{
"userAdded": false,
"type": "UnityEngine.AnimationClip",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEditor.Animations.AnimatorController",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEngine.AnimatorOverrideController",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEditor.Audio.AudioMixerController",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEngine.ComputeShader",
"defaultInstantiationMode": 1
},
{
"userAdded": false,
"type": "UnityEngine.Cubemap",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEngine.GameObject",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEditor.LightingDataAsset",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEngine.LightingSettings",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEngine.Material",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEditor.MonoScript",
"defaultInstantiationMode": 1
},
{
"userAdded": false,
"type": "UnityEngine.PhysicsMaterial",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEngine.PhysicsMaterial2D",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEngine.Rendering.PostProcessing.PostProcessProfile",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEngine.Rendering.PostProcessing.PostProcessResources",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEngine.Rendering.VolumeProfile",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEditor.SceneAsset",
"defaultInstantiationMode": 1
},
{
"userAdded": false,
"type": "UnityEngine.Shader",
"defaultInstantiationMode": 1
},
{
"userAdded": false,
"type": "UnityEngine.ShaderVariantCollection",
"defaultInstantiationMode": 1
},
{
"userAdded": false,
"type": "UnityEngine.Texture",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEngine.Texture2D",
"defaultInstantiationMode": 0
},
{
"userAdded": false,
"type": "UnityEngine.Timeline.TimelineAsset",
"defaultInstantiationMode": 0
}
],
"defaultDependencyTypeInfo": {
"userAdded": false,
"type": "<default_scene_template_dependencies>",
"defaultInstantiationMode": 1
},
"newSceneOverride": 0
}
+16
View File
@@ -0,0 +1,16 @@
%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!114 &1
MonoBehaviour:
m_ObjectHideFlags: 53
m_CorrespondingSourceObject: {fileID: 0}
m_PrefabInstance: {fileID: 0}
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 0}
m_Enabled: 1
m_EditorHideFlags: 0
m_Script: {fileID: 11500000, guid: a287be6c49135cd4f9b2b8666c39d999, type: 3}
m_Name:
m_EditorClassIdentifier: Unity.Timeline.Editor::TimelineProjectSettings
assetDefaultFramerate: 60
m_DefaultFrameRate: 60
+7 -7
View File
@@ -3,12 +3,12 @@
--- !u!937362698 &1 --- !u!937362698 &1
VFXManager: VFXManager:
m_ObjectHideFlags: 0 m_ObjectHideFlags: 0
m_IndirectShader: {fileID: 0} m_IndirectShader: {fileID: 7200000, guid: 84a17cfa13e40ae4082ef42714f0a81c, type: 3}
m_CopyBufferShader: {fileID: 0} m_CopyBufferShader: {fileID: 7200000, guid: 23c51f21a3503f6428b527b01f8a2f4e, type: 3}
m_PrefixSumShader: {fileID: 0} m_PrefixSumShader: {fileID: 7200000, guid: 783e0fdc1a277f54bb851fd2ceab1e7f, type: 3}
m_SortShader: {fileID: 0} m_SortShader: {fileID: 7200000, guid: ea257ca3cfb12a642a5025e612af6b2a, type: 3}
m_StripUpdateShader: {fileID: 0} m_StripUpdateShader: {fileID: 7200000, guid: 8fa6c4009fe2a4d4486c62736fc30ad8, type: 3}
m_EmptyShader: {fileID: 0} m_EmptyShader: {fileID: 4800000, guid: 33a2079f6a2db4c4eb2e44b33f4ddf6b, type: 3}
m_RenderPipeSettingsPath: m_RenderPipeSettingsPath:
m_FixedTimeStep: 0.016666668 m_FixedTimeStep: 0.016666668
m_MaxDeltaTime: 0.05 m_MaxDeltaTime: 0.05
@@ -16,5 +16,5 @@ VFXManager:
m_MaxCapacity: 100000000 m_MaxCapacity: 100000000
m_CompiledVersion: 7 m_CompiledVersion: 7
m_RuntimeVersion: 39 m_RuntimeVersion: 39
m_RuntimeResources: {fileID: 0} m_RuntimeResources: {fileID: 11400000, guid: bc10b42afe3813544bffd38ae2cd893d, type: 2}
m_BatchEmptyLifetime: 300 m_BatchEmptyLifetime: 300
+46
View File
@@ -0,0 +1,46 @@
# Unity VFX Graph Gaussian Splatting Renderer
A Unity implementation of Gaussian splatting using VFX Graph for real-time rendering. This project uses Unity's VFX Graph system as an alternative to the EWA algorithm to provide more flexibility in accessing the splat's data structures.
## Installation
Import this project into Unity or copy the `Assets/vfx-graph-gs/` folder to your project
## Usage
### Importing PLY Files
1. Place your PLY file containing Gaussian splat data in your project
2. The custom `SplatImporter` will automatically process the file
3. A VFX prefab will be generated that you can drag on to a scene
## File Structure
```
Assets/vfx-graph-gs/
├── Scripts/ # Core C# implementation
│ ├── SplatData.cs # Data container and GPU buffer management
│ ├── SplatDataBinder.cs # VFX Graph integration
│ ├── SplatDataInspector.cs # Custom inspector for SplatData
│ └── SplatImporter.cs # PLY file importer with burst compilation
├── VFXGraphHLSL/ # GPU shader functions
│ ├── CalculateAxes.hlsl # Quaternion to matrix conversion
│ ├── CalculateSH.hlsl # Spherical harmonics calculations
│ └── GetMajorAxes.hlsl # Projection axis selection
├── SplatVFX.vfx # Main VFX Graph asset
└── [PLY files] # Gaussian splat data files
```
## License
This project is licensed under the MIT License. See the LICENSE file for details.
## References
- [Original Gaussian Splatting Code](https://github.com/graphdeco-inria/gaussian-splatting)
- [SplatVFX](https://github.com/keijiro/SplatVFX)
> ## Additional Notes
> - The importer processes files in 256MB chunks to handle large files
> - Presently, the splat file *must* have all splat data present, including spherical harmonics. It currently only supports `float` type variables and binary-encoded files.
+12
View File
@@ -0,0 +1,12 @@
# TODO
- [ ] Rewrite SH Array to use interleaved RGB rather than contiguous data per
channel
- [ ] Switch to using ByteAddressBuffers instead of loops in CalculateSH.hlsl
- [ ] Optimize CalculateAxes.hlsl
- [ ] Allow splat data buffers to be accessible by other VFX Graphs
- [ ] Add exception reporting and handling for incomplete splat data
- [ ] Add support for other PLY data types and endianness
- [ ] Add support for ASCII PLY data
- [ ] Fix the header parsing logic in SplatImporter.cs
- [ ] Add more thorough checks in SplatDataBinder.cs