Interesting cel-shaded like effect

This commit is contained in:
2026-03-18 16:49:41 +08:00
parent 77040b7d56
commit 8ca58a1f13
3 changed files with 144 additions and 59 deletions
+1 -1
View File
@@ -157,7 +157,7 @@ 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.data[0].m_Value propertyPath: m_PropertySheet.m_Float.m_Array.Array.data[0].m_Value
value: 64 value: 32
objectReference: {fileID: 0} 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
+139 -54
View File
@@ -22,6 +22,8 @@ using UnityEngine.Playables;
using Unity.Android.Gradle.Manifest; using Unity.Android.Gradle.Manifest;
using UnityEngine.Timeline; using UnityEngine.Timeline;
using Unity.Jobs; using Unity.Jobs;
using System.Xml.Schema;
using Unity.Android.Gradle;
namespace SplatVfx.Editor namespace SplatVfx.Editor
{ {
@@ -110,9 +112,24 @@ namespace SplatVfx.Editor
{ {
public uint NumElements; public uint NumElements;
public uint BinaryDataStartOffset; public uint BinaryDataStartOffset;
public int[] ElementByteOffsets; public NativeArray<int> ElementByteOffsets;
public TypeEnum[] ElementTypes; public NativeArray<int> ElementTypes;
public int BytesPerElement; 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) private static (string headerString, int endHeaderPosition) GetHeader(string filePath)
@@ -133,20 +150,19 @@ namespace SplatVfx.Editor
} }
// Old logic that works because AppendLine adds another \n // Old logic that works because AppendLine adds another \n
// TODO: Fix this logic. Start with using Append instead of AppendLine // TODO: Fix this logic
int endHeaderPosition = headerBuilder.Length - headerBuilder.ToString().Split('\n').Length + 1; int endHeaderPosition = headerBuilder.Length - headerBuilder.ToString().Split('\n').Length + 1;
Debug.Log($"Total filesize: {fileStream.Length}");
Debug.Log($"Byte stream start position: {endHeaderPosition}");
return (headerBuilder.ToString(), endHeaderPosition); return (headerBuilder.ToString(), endHeaderPosition);
} }
private static HeaderData ParseMetadata(string headerString) private static HeaderData ParseMetadata(string headerString)
{ {
HeaderData result = new(); HeaderData result = new(59);
string line; string line;
result.ElementByteOffsets = new int[59];
result.ElementTypes = new TypeEnum[59];
using var reader = new StringReader(headerString); using var reader = new StringReader(headerString);
while ((line = reader.ReadLine()) != null) while ((line = reader.ReadLine()) != null)
@@ -170,13 +186,14 @@ namespace SplatVfx.Editor
{ {
int ix = (int)dataName; int ix = (int)dataName;
result.ElementByteOffsets[ix] = result.BytesPerElement; result.ElementByteOffsets[ix] = result.BytesPerElement;
result.ElementTypes[ix] = dataType; result.ElementTypes[ix] = (int)dataType;
} }
result.BytesPerElement += TypeToByteLengths[dataType]; result.BytesPerElement += TypeToByteLengths[dataType];
break; break;
} }
} }
Debug.Log($"Bytes per Element: {result.BytesPerElement}");
return result; return result;
} }
@@ -184,21 +201,21 @@ namespace SplatVfx.Editor
[BurstCompile] [BurstCompile]
private struct SplatDataNative private struct SplatDataNative
{ {
NativeArray<float3> position; public NativeArray<float3> position;
NativeArray<float3> color; public NativeArray<float3> color;
NativeArray<float> sh; public NativeArray<float> sh;
NativeArray<float> opacity; public NativeArray<float> opacity;
NativeArray<float3> scale; public NativeArray<float3> scale;
NativeArray<float4> rotation; public NativeArray<float4> rotation;
public SplatDataNative(int count) public SplatDataNative(int count)
{ {
position = new NativeArray<float3>(count, Allocator.Temp); position = new NativeArray<float3>(count, Allocator.Persistent);
color = new NativeArray<float3>(count, Allocator.Temp); color = new NativeArray<float3>(count, Allocator.Persistent);
sh = new NativeArray<float>(count, Allocator.Temp); sh = new NativeArray<float>(count * 45, Allocator.Persistent);
opacity = new NativeArray<float>(count, Allocator.Temp); opacity = new NativeArray<float>(count, Allocator.Persistent);
scale = new NativeArray<float3>(count, Allocator.Temp); scale = new NativeArray<float3>(count, Allocator.Persistent);
rotation = new NativeArray<float4>(count, Allocator.Temp); rotation = new NativeArray<float4>(count, Allocator.Persistent);
} }
public void Dispose() public void Dispose()
@@ -216,67 +233,135 @@ namespace SplatVfx.Editor
{ {
var splatData = ScriptableObject.CreateInstance<SplatData>(); var splatData = ScriptableObject.CreateInstance<SplatData>();
int elementsPerChunk = MAX_CHUNK_SIZE / headerData.BytesPerElement; 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];
int elementsPerChunk = Mathf.Min(
MAX_CHUNK_SIZE / headerData.BytesPerElement,
(int)headerData.NumElements
);
int chunkSize = elementsPerChunk * headerData.BytesPerElement; int chunkSize = elementsPerChunk * headerData.BytesPerElement;
Debug.Log($"Elements per chunk: {elementsPerChunk}");
Debug.Log($"Chunk size: {chunkSize}");
using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read); using var fileStream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
using var reader = new BinaryReader(fileStream);
fileStream.Seek(endHeaderPosition, SeekOrigin.Begin); fileStream.Seek(endHeaderPosition, SeekOrigin.Begin);
var dataList = new List<SplatDataNative>(); int totalBytes = (int)fileStream.Length - endHeaderPosition;
var jobHandles = new List<JobHandle>(); Debug.Log($"Total bytes: {totalBytes}");
for (int pos = endHeaderPosition; pos < fileStream.Length; pos += chunkSize) 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)
{ {
int currChunkSize = (int)(pos + chunkSize < fileStream.Length ? chunkSize : fileStream.Length - pos); numElemsRead = numBytesRead / headerData.BytesPerElement;
byte[] chunkBytes = reader.ReadBytes(currChunkSize); var splatElementJob = new SplatElementJob
int numElemInChunk = currChunkSize / headerData.BytesPerElement;
var splatDataNative = new SplatDataNative(numElemInChunk);
dataList.Add(splatDataNative);
var splatChunkJob = new SplatChunkJob
{ {
chunkBytes = chunkBytes, chunkBytes = chunkBuffer,
splatDataNative = splatDataNative, splatDataNative = splatDataNative,
headerData = headerData, headerData = headerData,
numElem = numElemInChunk totalElemsRead = totalElemsRead,
numElemsRead = numElemsRead
}; };
var handle = splatChunkJob.Schedule(); splatElementJob.Schedule().Complete();
jobHandles.Add(handle);
for (int i = 0; i < numElemsRead; i++)
{
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]);
}
// NativeArray<Vector3>.Copy(splatDataNative.position.Reinterpret<Vector3>(), 0, splatData.PositionArray, totalElemsRead, numElemsRead);
totalElemsRead += numElemsRead;
Debug.Log($"Current Elements Read: {totalElemsRead}");
} }
// Copy data here Debug.Log($"First position Vector3: {splatData.PositionArray[0]}");
headerData.Dispose();
splatDataNative.Dispose();
chunkBuffer.Dispose();
splatData.ReleaseGpuResources(); splatData.ReleaseGpuResources();
return splatData; return splatData;
} }
[BurstCompile] [BurstCompile]
private struct SplatChunkJob : IJob private struct SplatElementJob : IJob
{ {
public byte[] chunkBytes; public NativeArray<byte> chunkBytes;
public SplatDataNative splatDataNative; public SplatDataNative splatDataNative;
public HeaderData headerData; [ReadOnly] public HeaderData headerData;
public int numElem; [ReadOnly] public int totalElemsRead;
[ReadOnly] public int numElemsRead;
public void Execute() public void Execute()
{ {
throw new NotImplementedException(); for (int i = 0; i < numElemsRead; i++)
} {
} int elemIx = i;
int start = elemIx * headerData.BytesPerElement;
[BurstCompile] splatDataNative.position[elemIx] = new()
private struct SplatElementJob : IJobParallelFor {
{ x = chunkBytes.ReinterpretLoad<float>(start + headerData.ElementByteOffsets[(int)DataEnum.x]),
public byte[] chunkBytes; y = chunkBytes.ReinterpretLoad<float>(start + headerData.ElementByteOffsets[(int)DataEnum.y]),
public SplatDataNative splatDataNative; z = chunkBytes.ReinterpretLoad<float>(start + headerData.ElementByteOffsets[(int)DataEnum.z])
public HeaderData headerData; };
public void Execute(int ix) float3 color = new()
{ {
throw new NotImplementedException(); 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 (int j = 0; j < 45; j++)
{
splatDataNative.sh[elemIx + j] = chunkBytes[start + headerData.ElementByteOffsets[(int)DataEnum.f_rest_0 + j]];
}
float opacity = chunkBytes[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])
};
}
} }
} }
+3 -3
View File
@@ -209,7 +209,7 @@ MonoBehaviour:
x: -692 x: -692
y: -492 y: -492
width: 1846 width: 1846
height: 2501 height: 2502
--- !u!114 &114350483966674976 --- !u!114 &114350483966674976
MonoBehaviour: MonoBehaviour:
m_ObjectHideFlags: 1 m_ObjectHideFlags: 1
@@ -13741,7 +13741,7 @@ MonoBehaviour:
m_Parent: {fileID: 114350483966674976} m_Parent: {fileID: 114350483966674976}
m_Children: m_Children:
- {fileID: 8926484042661619332} - {fileID: 8926484042661619332}
m_UIPosition: {x: 705, y: 700} m_UIPosition: {x: 705, y: 741}
m_UICollapsed: 0 m_UICollapsed: 0
m_UISuperCollapsed: 0 m_UISuperCollapsed: 0
m_InputSlots: [] m_InputSlots: []
@@ -16773,7 +16773,7 @@ MonoBehaviour:
m_Type: m_Type:
m_SerializableType: System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral, m_SerializableType: System.Boolean, mscorlib, Version=4.0.0.0, Culture=neutral,
PublicKeyToken=b77a5c561934e089 PublicKeyToken=b77a5c561934e089
m_SerializableObject: True m_SerializableObject: False
m_Space: -1 m_Space: -1
m_Property: m_Property:
name: _vfx_enabled name: _vfx_enabled