Compare commits

...

9 Commits

Author SHA1 Message Date
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
17 changed files with 2205 additions and 1192 deletions
+12 -4
View File
@@ -12,8 +12,8 @@ MonoBehaviour:
m_Script: {fileID: 11500000, guid: bf2edee5c58d82540a51f03df9d42094, type: 3}
m_Name: Mobile_RPAsset
m_EditorClassIdentifier:
k_AssetVersion: 12
k_AssetPreviousVersion: 12
k_AssetVersion: 13
k_AssetPreviousVersion: 13
m_RendererType: 1
m_RendererData: {fileID: 0}
m_RendererDataList:
@@ -23,7 +23,7 @@ MonoBehaviour:
m_RequireOpaqueTexture: 0
m_OpaqueDownsampling: 0
m_SupportsTerrainHoles: 1
m_SupportsHDR: 1
m_SupportsHDR: 0
m_HDRColorBufferPrecision: 0
m_MSAA: 1
m_RenderScale: 0.8
@@ -53,6 +53,7 @@ MonoBehaviour:
m_AdditionalLightsShadowResolutionTierHigh: 1024
m_ReflectionProbeBlending: 1
m_ReflectionProbeBoxProjection: 1
m_ReflectionProbeAtlas: 1
m_ShadowDistance: 50
m_ShadowCascadeCount: 1
m_Cascade2Split: 0.25
@@ -78,11 +79,11 @@ MonoBehaviour:
m_UseAdaptivePerformance: 1
m_ColorGradingMode: 0
m_ColorGradingLutSize: 32
m_AllowPostProcessAlphaOutput: 0
m_UseFastSRGBLinearConversion: 1
m_SupportDataDrivenLensFlare: 1
m_SupportScreenSpaceLensFlare: 1
m_GPUResidentDrawerMode: 0
m_UseLegacyLightmaps: 0
m_SmallMeshScreenPercentage: 0
m_GPUResidentDrawerEnableOcclusionCullingInCameras: 0
m_ShadowType: 1
@@ -109,6 +110,7 @@ MonoBehaviour:
m_PrefilterDebugKeywords: 1
m_PrefilterWriteRenderingLayers: 1
m_PrefilterHDROutput: 1
m_PrefilterAlphaOutput: 0
m_PrefilterSSAODepthNormals: 1
m_PrefilterSSAOSourceDepthLow: 1
m_PrefilterSSAOSourceDepthMedium: 0
@@ -126,8 +128,14 @@ MonoBehaviour:
m_PrefilterSoftShadowsQualityHigh: 1
m_PrefilterSoftShadows: 0
m_PrefilterScreenCoord: 1
m_PrefilterScreenSpaceIrradiance: 0
m_PrefilterNativeRenderPass: 1
m_PrefilterUseLegacyLightmaps: 0
m_PrefilterBicubicLightmapSampling: 0
m_PrefilterReflectionProbeRotation: 0
m_PrefilterReflectionProbeBlending: 0
m_PrefilterReflectionProbeBoxProjection: 0
m_PrefilterReflectionProbeAtlas: 0
m_ShaderVariantLogLevel: 0
m_ShadowCascades: 0
m_Textures:
+3
View File
@@ -34,6 +34,9 @@ MonoBehaviour:
highQualityFiltering:
m_OverrideState: 1
m_Value: 1
filter:
m_OverrideState: 0
m_Value: 0
downscale:
m_OverrideState: 0
m_Value: 0
+74 -2
View File
@@ -133,6 +133,10 @@ PrefabInstance:
objectReference: {fileID: 0}
- target: {fileID: -973084862543737302, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3}
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
objectReference: {fileID: 0}
- target: {fileID: -973084862543737302, guid: 25c54d8c074f08447ab1c822a6a052fa, type: 3}
@@ -147,6 +151,10 @@ PrefabInstance:
propertyPath: m_PropertySheet.m_Float.m_Array.Array.data[0].m_Name
value: ScaleMultiplier
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}
propertyPath: m_PropertySheet.m_Uint.m_Array.Array.data[0].m_Value
value: 200000
@@ -159,6 +167,14 @@ PrefabInstance:
propertyPath: m_PropertySheet.m_Float.m_Array.Array.data[0].m_Value
value: 64
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}
propertyPath: m_PropertySheet.m_Uint.m_Array.Array.data[0].m_Overridden
value: 1
@@ -171,6 +187,62 @@ PrefabInstance:
propertyPath: m_PropertySheet.m_Float.m_Array.Array.data[0].m_Overridden
value: 1
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}
propertyPath: m_Enabled
value: 1
@@ -318,8 +390,8 @@ Transform:
m_PrefabAsset: {fileID: 0}
m_GameObject: {fileID: 1826889287}
serializedVersion: 2
m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
m_LocalPosition: {x: 0, y: 0, z: -5}
m_LocalRotation: {x: -0.048649408, y: -0.18707958, z: 0.009037506, w: -0.9810978}
m_LocalPosition: {x: -0.4305725, y: 0.22459221, z: -1.4613647}
m_LocalScale: {x: 1, y: 1, z: 1}
m_ConstrainProportionsScale: 0
m_Children: []
+235 -212
View File
@@ -3,34 +3,15 @@ using Unity.Mathematics;
using UnityEngine;
using UnityEngine.VFX;
using UnityEngine.VFX.Utility;
using UnityEditor;
using UnityEditor.AssetImporters;
using UnityEditor.Experimental;
using System;
using System.IO;
using System.Runtime.InteropServices;
using System.Text;
using UnityEditor.Rendering.LookDev;
using System.Linq;
using System.Collections.Generic;
using Unity.VectorGraphics;
using Unity.VisualScripting;
using System.Collections;
using Unity.Collections;
using UnityEditor.Experimental.GraphView;
/*******************************************************************************
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
*******************************************************************************/
using Unity.Jobs;
namespace SplatVfx.Editor
{
@@ -39,16 +20,14 @@ namespace SplatVfx.Editor
public sealed class SplatImporter : ScriptedImporter
{
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)
{
var ply = ParsePLYFile(context.assetPath);
var header_info = ParseHeader(ply.header);
var (headerString, endHeaderPosition) = GetHeader(context.assetPath);
var headerData = ParseMetadata(headerString);
var data = ImportAsSplatData(header_info, ply.binaryData);
var data = ImportData(headerData, context.assetPath, endHeaderPosition);
data.name = Path.GetFileNameWithoutExtension(context.assetPath);
context.AddObjectToAsset("data", data);
@@ -57,14 +36,7 @@ namespace SplatVfx.Editor
context.SetMainObject(prefab);
}
#endregion
#region Prefab construction
const string DefaultVfxPath = "Assets/vfx-graph-gs/SplatVFX.vfx";
[BurstCompile]
GameObject CreatePrefab(SplatData data)
private GameObject CreatePrefab(SplatData data)
{
var go = new GameObject();
var vfx = go.AddComponent<VisualEffect>();
@@ -78,12 +50,70 @@ namespace SplatVfx.Editor
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]
(string header, byte[] binaryData) ParsePLYFile(string filePath)
private enum DataEnum
{
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 reader = new StreamReader(fileStream);
@@ -100,229 +130,222 @@ namespace SplatVfx.Editor
}
}
long endHeaderPosition = headerBuilder.Length - headerBuilder.ToString().Split('\n').Length + 1;
fileStream.Seek(endHeaderPosition, SeekOrigin.Begin);
// Old logic that works because AppendLine adds another \n
// 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);
var binaryData = binaryReader.ReadBytes((int)(fileStream.Length - endHeaderPosition));
return (headerBuilder.ToString(), binaryData);
return (headerBuilder.ToString(), endHeaderPosition);
}
[BurstCompile]
(string format, int num_element, List<(string, string)> properties)
ParseHeader(string header)
private static HeaderData ParseMetadata(string headerString)
{
using var reader = new StringReader(header);
HeaderData result = new(59);
string line;
string format = "";
int num_element = 0;
var properties = new List<(string, string)>();
using var reader = new StringReader(headerString);
while ((line = reader.ReadLine()) != null)
{
if (line.StartsWith("end_header"))
{
if (line.Trim() == "end_header")
break;
}
var strings = line.Split(" ");
switch (strings.FirstOrDefault())
{
case "format":
format = strings[1];
break;
case "element":
num_element = int.Parse(strings[2]);
result.NumElements = uint.Parse(strings[2]);
break;
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;
}
}
Debug.Log($"Bytes per Element: {result.BytesPerElement}");
return (format, num_element, properties);
return result;
}
[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 stream = new MemoryStream(binaryData);
var reader = new BinaryReader(stream);
splatData.PositionArray = new Vector3[headerData.NumElements];
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];
splatData.ScaleArray = new Vector3[header_info.num_element];
splatData.RotationArray = new Quaternion[header_info.num_element];
splatData.ColorArray = new Color[header_info.num_element];
splatData.OpacityArray = new float[header_info.num_element];
splatData.SHArray = new float[header_info.num_element * 45];
int elementsPerChunk = Mathf.Min(
MAX_CHUNK_SIZE / headerData.BytesPerElement,
(int)headerData.NumElements
);
int chunkSize = elementsPerChunk * headerData.BytesPerElement;
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[i].x = val;
break;
case "y":
splatData.PositionArray[i].y = val;
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"))
splatData.PositionArray[totalElemsRead + i] = splatDataNative.position[i];
splatData.ColorArray[totalElemsRead + i].r = splatDataNative.color[i].x;
splatData.ColorArray[totalElemsRead + i].g = splatDataNative.color[i].y;
splatData.ColorArray[totalElemsRead + i].b = splatDataNative.color[i].z;
splatData.OpacityArray[totalElemsRead + i] = splatDataNative.opacity[i];
splatData.ScaleArray[totalElemsRead + i] = splatDataNative.scale[i];
splatData.RotationArray[totalElemsRead + i] = new quaternion(splatDataNative.rotation[i]);
for (int j = 0; j < 45; j++)
{
switch (property.name[5])
{
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;
splatData.SHArray[(totalElemsRead + i) * 45 + j] = splatDataNative.sh[i * 45 + j];
}
}
totalElemsRead += numElemsRead;
// Debug.Log($"Current Elements Read: {totalElemsRead}");
}
// Debug.Log($"First position Vector3: {splatData.PositionArray[0]}");
headerData.Dispose();
splatDataNative.Dispose();
chunkBuffer.Dispose();
splatData.ReleaseGpuResources();
return splatData;
}
// struct ReadData
// {
// public float px, py, pz;
// public float sx, sy, sz;
// public byte r, g, b, a;
// public byte rw, rx, ry, rz;
// }
[BurstCompile]
private struct SplatElementJob : IJob
{
public NativeArray<byte> chunkBytes;
public SplatDataNative splatDataNative;
[ReadOnly] public HeaderData headerData;
[ReadOnly] public int totalElemsRead;
[ReadOnly] public int numElemsRead;
// (Vector3[] position, Vector3[] axis, Color[] color)
// LoadDataArrays(string path)
// {
// var bytes = (Span<byte>)File.ReadAllBytes(path);
// var count = bytes.Length / 32;
public void Execute()
{
for (int i = 0; i < numElemsRead; i++)
{
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];
// var axis = new Vector3[count * 3];
// var color = new Color[count];
float3 color = new()
{
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++)
// ParseReadData(source[i],
// out position[i],
// 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
for (int j = 0; j < 45; j++)
{
splatDataNative.sh[elemIx * 45 + j] = chunkBytes.ReinterpretLoad<float>(start + headerData.ElementByteOffsets[(int)DataEnum.f_rest_0 + j]);
}
} // 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,15 +1,18 @@
#define sh(i) (float3)SHBuffer.Load3(Index * 45 + (i - 1) * 15)
const float SH_C0 = 0.28209479177387814f;
const float SH_C1 = 0.4886025119029199f;
const float SH_C2[] = {
/// 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_C1 = 0.4886025119029199f;
const float SH_C2[] = {
1.0925484305920792f,
-1.0925484305920792f,
0.31539156525252005f,
-1.0925484305920792f,
0.5462742152960396f
};
const float SH_C3[] = {
};
const float SH_C3[] = {
-0.5900435899266435f,
2.890611442640554f,
-0.4570457994644658f,
@@ -17,41 +20,54 @@ const float SH_C3[] = {
-0.4570457994644658f,
1.445305721320277f,
-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 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)
{
float x = dir.x;
float y = dir.y;
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)
{
float xx = x * x, yy = y * y, zz = z * z;
float xy = x * y, yz = y * z, xz = x * z;
result = result +
SH_C2[0] * xy * sh(4) +
SH_C2[1] * yz * sh(5) +
SH_C2[2] * (2.0f * zz - xx - yy) * sh(6) +
SH_C2[3] * xz * sh(7) +
SH_C2[4] * (xx - yy) * sh(8);
SH_C2[0] * xy * sh[4] +
SH_C2[1] * yz * sh[5] +
SH_C2[2] * (2.0f * zz - xx - yy) * sh[6] +
SH_C2[3] * xz * sh[7] +
SH_C2[4] * (xx - yy) * sh[8];
if (Degrees > 2)
{
result = result +
SH_C3[0] * y * (3.0f * xx - yy) * sh(9) +
SH_C3[1] * xy * z * sh(10) +
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[4] * x * (4.0f * zz - xx - yy) * sh(13) +
SH_C3[5] * z * (xx - yy) * sh(14) +
SH_C3[6] * x * (xx - 3.0f * yy) * sh(15);
SH_C3[0] * y * (3.0f * xx - yy) * sh[9] +
SH_C3[1] * xy * z * sh[10] +
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[4] * x * (4.0f * zz - xx - yy) * sh[13] +
SH_C3[5] * z * (xx - yy) * sh[14] +
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
// "Differentiable Point-Based Radiance Fields for
// Efficient View Synthesis" by Zhang et al. (2022)
@@ -1,8 +1,10 @@
/// Hidden
float CrossProduct(float2 A, float2 B)
{
return A.x * B.y - A.y * B.x;
}
/// Hidden
float SweepArea(float2 A, float2 B)
{
return abs(CrossProduct(A, B));
+1 -1
View File
@@ -1,5 +1,6 @@
{
"dependencies": {
"com.unity.cinemachine": "2.10.7",
"com.unity.ide.visualstudio": "2.0.27",
"com.unity.inputsystem": "1.18.0",
"com.unity.render-pipelines.universal": "17.3.0",
@@ -9,7 +10,6 @@
"com.unity.visualeffectgraph": "17.3.0",
"com.unity.visualscripting": "1.9.10",
"com.unity.modules.accessibility": "1.0.0",
"com.unity.modules.adaptiveperformance": "1.0.0",
"com.unity.modules.ai": "1.0.0",
"com.unity.modules.androidjni": "1.0.0",
"com.unity.modules.animation": "1.0.0",
+9 -8
View File
@@ -10,6 +10,15 @@
},
"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": {
"version": "2.6.2",
"depth": 2,
@@ -175,14 +184,6 @@
"source": "builtin",
"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": {
"version": "1.0.0",
"depth": 0,
+1 -1
View File
@@ -61,7 +61,7 @@ GraphicsSettings:
UnityEngine.Rendering.Universal.UniversalRenderPipeline: {fileID: 11400000, guid: 18dc0cd2c080841dea60987a38ce93fa, type: 2}
m_ShaderBuildSettings:
keywordDeclarationOverrides: []
m_LightsUseLinearIntensity: 1
m_LightsUseLinearIntensity: 0
m_LightsUseColorTemperature: 1
m_LogWhenShaderIsCompiled: 0
m_LightProbeOutsideHullStrategy: 0
+13 -16
View File
@@ -12,11 +12,13 @@ MonoBehaviour:
m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0}
m_Name:
m_EditorClassIdentifier:
m_EnablePreviewPackages: 0
m_EnablePackageDependencies: 0
m_EnablePreReleasePackages: 0
m_AdvancedSettingsExpanded: 1
m_ScopedRegistriesSettingsExpanded: 1
oneTimeWarningShown: 0
m_SeeAllPackageVersions: 0
m_DismissPreviewPackagesInUse: 0
oneTimeWarningShown: 1
oneTimePackageErrorsPopUpShown: 0
m_Registries:
- m_Id: main
m_Name:
@@ -24,20 +26,15 @@ MonoBehaviour:
m_Scopes: []
m_IsDefault: 1
m_Capabilities: 7
m_ConfigSource: 0
m_Compliance:
m_Status: 0
m_Violations: []
m_UserSelectedRegistryName:
m_UserAddingNewScopedRegistry: 0
m_RegistryInfoDraft:
m_ErrorMessage:
m_Original:
m_Id:
m_Name:
m_Url:
m_Scopes: []
m_IsDefault: 0
m_Capabilities: 0
m_Modified: 0
m_Name:
m_Url:
m_Scopes:
-
m_SelectedScopeIndex: 0
m_ErrorMessage:
m_UserModificationsInstanceId: -900
m_OriginalInstanceId: -902
m_LoadAssets: 0
+9 -3
View File
@@ -47,7 +47,7 @@ PlayerSettings:
defaultScreenWidthWeb: 960
defaultScreenHeightWeb: 600
m_StereoRenderingPath: 0
m_ActiveColorSpace: 1
m_ActiveColorSpace: 0
unsupportedMSAAFallback: 0
m_SpriteBatchMaxVertexCount: 65535
m_SpriteBatchVertexThreshold: 300
@@ -535,6 +535,9 @@ PlayerSettings:
- m_BuildTarget: AndroidPlayer
m_APIs: 150000000b000000
m_Automatic: 0
- m_BuildTarget: WebGLSupport
m_APIs: 1c0000000b000000
m_Automatic: 0
m_BuildTargetVRSettings: []
m_DefaultShaderChunkSizeInMB: 16
m_DefaultShaderChunkCount: 0
@@ -560,6 +563,9 @@ PlayerSettings:
- serializedVersion: 3
m_BuildTarget: Android
m_Formats: 03000000
- serializedVersion: 3
m_BuildTarget: WebGL
m_Formats: 05000000
playModeTestRunnerEnabled: 0
runPlayModeTestAsEditModeTest: 0
actionOnDotNetUnhandledException: 1
@@ -808,7 +814,7 @@ PlayerSettings:
webGLTemplate: APPLICATION:Default
webGLAnalyzeBuildSize: 0
webGLUseEmbeddedResources: 0
webGLCompressionFormat: 0
webGLCompressionFormat: 1
webGLWasmArithmeticExceptions: 0
webGLLinkerTarget: 1
webGLThreadsSupport: 0
@@ -819,7 +825,7 @@ PlayerSettings:
webGLMemoryLinearGrowthStep: 16
webGLMemoryGeometricGrowthStep: 0.2
webGLMemoryGeometricGrowthCap: 96
webGLPowerPreference: 2
webGLPowerPreference: 1
webGLWebAssemblyTable: 0
webGLWebAssemblyBigInt: 0
webGLCloseOnQuit: 0
+8 -7
View File
@@ -4,9 +4,9 @@
QualitySettings:
m_ObjectHideFlags: 0
serializedVersion: 5
m_CurrentQuality: 1
m_CurrentQuality: 0
m_QualitySettings:
- serializedVersion: 4
- serializedVersion: 5
name: Mobile
pixelLightCount: 2
shadows: 2
@@ -34,6 +34,7 @@ QualitySettings:
adaptiveVsyncExtraA: 0
adaptiveVsyncExtraB: 0
lodBias: 1
meshLodThreshold: 1
maximumLODLevel: 0
enableLODCrossFade: 1
streamingMipmapsActive: 0
@@ -47,8 +48,7 @@ QualitySettings:
asyncUploadBufferSize: 16
asyncUploadPersistentBuffer: 1
resolutionScalingFixedDPIFactor: 1
customRenderPipeline: {fileID: 11400000, guid: 5e6cbd92db86f4b18aec3ed561671858,
type: 2}
customRenderPipeline: {fileID: 11400000, guid: 5e6cbd92db86f4b18aec3ed561671858, type: 2}
terrainQualityOverrides: 0
terrainPixelError: 1
terrainDetailDensityScale: 1
@@ -60,7 +60,7 @@ QualitySettings:
terrainMaxTrees: 50
excludedTargetPlatforms:
- Standalone
- serializedVersion: 4
- serializedVersion: 5
name: PC
pixelLightCount: 2
shadows: 2
@@ -88,6 +88,7 @@ QualitySettings:
adaptiveVsyncExtraA: 0
adaptiveVsyncExtraB: 0
lodBias: 2
meshLodThreshold: 1
maximumLODLevel: 0
enableLODCrossFade: 1
streamingMipmapsActive: 0
@@ -101,8 +102,7 @@ QualitySettings:
asyncUploadBufferSize: 16
asyncUploadPersistentBuffer: 1
resolutionScalingFixedDPIFactor: 1
customRenderPipeline: {fileID: 11400000, guid: 4b83569d67af61e458304325a23e5dfd,
type: 2}
customRenderPipeline: {fileID: 11400000, guid: 4b83569d67af61e458304325a23e5dfd, type: 2}
terrainQualityOverrides: 0
terrainPixelError: 1
terrainDetailDensityScale: 1
@@ -122,6 +122,7 @@ QualitySettings:
GameCoreXboxOne: 1
Lumin: 0
Nintendo Switch: 1
Nintendo Switch 2: 1
PS4: 1
PS5: 1
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
VFXManager:
m_ObjectHideFlags: 0
m_IndirectShader: {fileID: 0}
m_CopyBufferShader: {fileID: 0}
m_PrefixSumShader: {fileID: 0}
m_SortShader: {fileID: 0}
m_StripUpdateShader: {fileID: 0}
m_EmptyShader: {fileID: 0}
m_IndirectShader: {fileID: 7200000, guid: 84a17cfa13e40ae4082ef42714f0a81c, type: 3}
m_CopyBufferShader: {fileID: 7200000, guid: 23c51f21a3503f6428b527b01f8a2f4e, type: 3}
m_PrefixSumShader: {fileID: 7200000, guid: 783e0fdc1a277f54bb851fd2ceab1e7f, type: 3}
m_SortShader: {fileID: 7200000, guid: ea257ca3cfb12a642a5025e612af6b2a, type: 3}
m_StripUpdateShader: {fileID: 7200000, guid: 8fa6c4009fe2a4d4486c62736fc30ad8, type: 3}
m_EmptyShader: {fileID: 4800000, guid: 33a2079f6a2db4c4eb2e44b33f4ddf6b, type: 3}
m_RenderPipeSettingsPath:
m_FixedTimeStep: 0.016666668
m_MaxDeltaTime: 0.05
@@ -16,5 +16,5 @@ VFXManager:
m_MaxCapacity: 100000000
m_CompiledVersion: 7
m_RuntimeVersion: 39
m_RuntimeResources: {fileID: 0}
m_RuntimeResources: {fileID: 11400000, guid: bc10b42afe3813544bffd38ae2cd893d, type: 2}
m_BatchEmptyLifetime: 300
+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