SilhouetteEdgeCalculator.cs
1 using System; 2 using System.Collections.Generic; 3 using UnityEngine; 4 using System.Runtime.InteropServices; 5 using DefaultNamespace; 6 using UnityEditor; 7 8 public class SilhouetteEdgeCalculator : MonoBehaviour, IGreasePencilEdgeCalculator 9 { 10 11 // Constants 12 private const int ADJ_NONE = -1; 13 private const int INVALID = -2; 14 private static readonly int WorldSpaceCameraPos = Shader.PropertyToID("_WorldSpaceCameraPos"); 15 private static readonly int ObjectToWorldIt = Shader.PropertyToID("_ObjectToWorldIT"); 16 private static readonly int ObjectToWorld = Shader.PropertyToID("_ObjectToWorld"); 17 private static readonly int NextPointerSrc = Shader.PropertyToID("_nextPointerSrc"); 18 private static readonly int NextPointerDst = Shader.PropertyToID("_nextPointerDst"); 19 private static readonly int RadiusMultiplier = Shader.PropertyToID("_radiusMultiplier"); 20 21 // Public assets / parameters 22 private ComputeShader _silhouetteEdgeFinder; 23 private ComputeShader _edgesToStrokes; 24 private ComputeShader _strokesToGreasePencilStrokes; 25 26 private Mesh _sourceMesh; 27 28 private int FaceCount => _sourceMesh.triangles.Length / 3; 29 30 public Camera viewCamera; 31 public float radiusMultiplier = 1.0f; 32 33 34 // Compute buffers 35 private ComputeBuffer _verticesBuffer; 36 private ComputeBuffer _indicesBuffer; 37 private ComputeBuffer _adjIndicesBuffer; 38 private ComputeBuffer _strokesBuffer; 39 private ComputeBuffer _nextPointerSrcBuffer; 40 private ComputeBuffer _nextPointerDstBuffer; 41 // Two 1-element buffers to be used as UAV atomic counters by the compute shader 42 private ComputeBuffer _numStrokesCounterBuffer; 43 private ComputeBuffer _numStrokePointsCounterBuffer; 44 45 // Graphics buffers for GreasePencil output 46 private GraphicsBuffer _denseStrokesBuffer; 47 private GraphicsBuffer _colorBuffer; 48 49 // Kernel indices for shaders 50 private int _findSilhouetteEdge_Kernel; 51 52 private int _initialize_Kernel; 53 private int _findStrokeTail_Kernel; 54 private int _resetNext_Kernel; 55 private int _initDistances_Kernel; 56 private int _listRank_Kernel; 57 58 private int _setStrokeLengthAtTail_Kernel; 59 private int _calcStrokeOffsets_Kernel; 60 private int _invalidateEntries_Kernel; 61 private int _sorter_Kernel; 62 63 64 private const uint NUM_POINTER_JUMP_ITERATIONS = 8; 65 66 private void Awake() 67 { 68 _silhouetteEdgeFinder = Instantiate(Resources.Load<ComputeShader>("Lineart/ComputeShaders/SilhouetteEdge")); 69 _edgesToStrokes = Instantiate(Resources.Load<ComputeShader>("Lineart/ComputeShaders/EdgesToStrokes")); 70 _strokesToGreasePencilStrokes = Instantiate(Resources.Load<ComputeShader>("Lineart/ComputeShaders/StrokesToGreasePencil")); 71 72 _sourceMesh = GetComponent<MeshFilter>()?.sharedMesh; 73 74 ResolveViewCamera(); 75 } 76 77 void Start() 78 { 79 if (!SystemInfo.supportsComputeShaders) 80 { 81 Debug.LogError("Compute shaders are not supported on this platform."); 82 return; 83 } 84 85 if (_sourceMesh == null) 86 { 87 return; 88 } 89 90 InitializeKernels(); 91 InitializeBuffers(); 92 BindBuffersToShaders(); 93 } 94 95 void OnDestroy() 96 { 97 _verticesBuffer?.Release(); 98 _indicesBuffer?.Release(); 99 _adjIndicesBuffer?.Release(); 100 _strokesBuffer?.Release(); 101 _nextPointerSrcBuffer?.Release(); 102 _nextPointerDstBuffer?.Release(); 103 _numStrokesCounterBuffer?.Release(); 104 _numStrokePointsCounterBuffer?.Release(); 105 _denseStrokesBuffer?.Release(); 106 _colorBuffer?.Release(); 107 108 if (_silhouetteEdgeFinder != null) Destroy(_silhouetteEdgeFinder); 109 if (_edgesToStrokes != null) Destroy(_edgesToStrokes); 110 if (_strokesToGreasePencilStrokes != null) Destroy(_strokesToGreasePencilStrokes); 111 } 112 113 void InitializeKernels() 114 { 115 _findSilhouetteEdge_Kernel = _silhouetteEdgeFinder.FindKernel("FindSilhouetteEdge"); 116 117 _initialize_Kernel = _edgesToStrokes.FindKernel("Initialize"); 118 _findStrokeTail_Kernel = _edgesToStrokes.FindKernel("FindStrokeTail"); 119 _resetNext_Kernel = _edgesToStrokes.FindKernel("ResetNextPointer"); 120 _initDistances_Kernel = _edgesToStrokes.FindKernel("InitializeRanksAndDistances"); 121 _listRank_Kernel = _edgesToStrokes.FindKernel("CalculateRanksAndDistances"); 122 123 _setStrokeLengthAtTail_Kernel = _strokesToGreasePencilStrokes.FindKernel("SetStrokeLengthAtTail"); 124 _calcStrokeOffsets_Kernel = _strokesToGreasePencilStrokes.FindKernel("CalculateArrayOffsets"); 125 _invalidateEntries_Kernel = _strokesToGreasePencilStrokes.FindKernel("InvalidateEntries"); 126 _sorter_Kernel = _strokesToGreasePencilStrokes.FindKernel("MoveToDenseArray"); 127 } 128 129 void InitializeBuffers() 130 { 131 CreateBuffersForSilhouetteEdgeFinder(); 132 CreateBuffersForEdgesToStrokes(); 133 CreateBuffersForGreasePencilStrokes(); 134 } 135 136 void CreateBuffersForSilhouetteEdgeFinder() 137 { 138 Vector3[] positions = _sourceMesh.vertices; 139 Vector3[] normals = _sourceMesh.normals; 140 SilhouetteSourceVertex[] vertexDataArray = new SilhouetteSourceVertex[positions.Length]; 141 for (int i = 0; i < positions.Length; i++) 142 { 143 vertexDataArray[i] = new SilhouetteSourceVertex { position = positions[i], normal = normals[i] }; 144 } 145 146 _verticesBuffer = new ComputeBuffer(vertexDataArray.Length, Marshal.SizeOf(typeof(SilhouetteSourceVertex))); 147 _verticesBuffer.SetData(vertexDataArray); 148 149 int[] indices = _sourceMesh.triangles; 150 _indicesBuffer = new ComputeBuffer(indices.Length, sizeof(int)); 151 _indicesBuffer.SetData(indices); 152 153 int[] adjData = CalculateAdjacency(indices); 154 _adjIndicesBuffer = new ComputeBuffer(adjData.Length, sizeof(uint)); 155 _adjIndicesBuffer.SetData(adjData); 156 157 _strokesBuffer = new ComputeBuffer(FaceCount, SilhouetteStrokeEdge.SizeOf); 158 } 159 160 void CreateBuffersForEdgesToStrokes() 161 { 162 _nextPointerSrcBuffer = new ComputeBuffer(FaceCount, sizeof(int)); 163 _nextPointerDstBuffer = new ComputeBuffer(FaceCount, sizeof(int)); 164 } 165 166 void CreateBuffersForGreasePencilStrokes() 167 { 168 //TODO: find a tighter limit for these buffer sizes 169 //output buffers 170 _denseStrokesBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 2 * FaceCount, GreasePencilRenderer.GreasePencilStrokeVert.SizeOf); 171 _colorBuffer = new GraphicsBuffer(GraphicsBuffer.Target.Structured, 2 * FaceCount, GreasePencilRenderer.GreasePencilColorVert.SizeOf); 172 173 //offset calculation buffers 174 _numStrokesCounterBuffer = new ComputeBuffer(1, sizeof(uint)); 175 _numStrokePointsCounterBuffer = new ComputeBuffer(1, sizeof(uint)); 176 } 177 178 void BindBuffersToShaders() 179 { 180 _silhouetteEdgeFinder.SetInt("_NumFaces", FaceCount); 181 _silhouetteEdgeFinder.SetBuffer(_findSilhouetteEdge_Kernel, "_Vertices", _verticesBuffer); 182 _silhouetteEdgeFinder.SetBuffer(_findSilhouetteEdge_Kernel, "_Indices", _indicesBuffer); 183 _silhouetteEdgeFinder.SetBuffer(_findSilhouetteEdge_Kernel, "_AdjIndices", _adjIndicesBuffer); 184 _silhouetteEdgeFinder.SetBuffer(_findSilhouetteEdge_Kernel, "_outStrokes", _strokesBuffer); 185 186 _edgesToStrokes.SetInt("_NumFaces", FaceCount); 187 _edgesToStrokes.SetBuffer(_initialize_Kernel, "_strokes", _strokesBuffer); 188 _edgesToStrokes.SetBuffer(_findStrokeTail_Kernel, "_strokes", _strokesBuffer); 189 _edgesToStrokes.SetBuffer(_listRank_Kernel, "_strokes", _strokesBuffer); 190 _edgesToStrokes.SetBuffer(_resetNext_Kernel, "_strokes", _strokesBuffer); 191 _edgesToStrokes.SetBuffer(_initDistances_Kernel, "_strokes", _strokesBuffer); 192 193 _strokesToGreasePencilStrokes.SetInt("_NumFaces", FaceCount); 194 195 _strokesToGreasePencilStrokes.SetBuffer(_setStrokeLengthAtTail_Kernel, "_strokes", _strokesBuffer); 196 197 _strokesToGreasePencilStrokes.SetBuffer(_calcStrokeOffsets_Kernel, "_strokes", _strokesBuffer); 198 _strokesToGreasePencilStrokes.SetBuffer(_calcStrokeOffsets_Kernel, "numStrokesCounter", _numStrokesCounterBuffer); 199 _strokesToGreasePencilStrokes.SetBuffer(_calcStrokeOffsets_Kernel, "numStrokePointsCounter", _numStrokePointsCounterBuffer); 200 201 _strokesToGreasePencilStrokes.SetBuffer(_invalidateEntries_Kernel, "_denseArray", _denseStrokesBuffer); 202 203 _strokesToGreasePencilStrokes.SetBuffer(_sorter_Kernel, "_strokes", _strokesBuffer); 204 _strokesToGreasePencilStrokes.SetBuffer(_sorter_Kernel, "_denseArray", _denseStrokesBuffer); 205 _strokesToGreasePencilStrokes.SetBuffer(_sorter_Kernel, "_colorArray", _colorBuffer); 206 } 207 208 private void BindNextPointers(int kernel) 209 { 210 _edgesToStrokes.SetBuffer(kernel, NextPointerSrc, _nextPointerSrcBuffer); 211 _edgesToStrokes.SetBuffer(kernel, NextPointerDst, _nextPointerDstBuffer); 212 } 213 214 private void SwapNextPointers() 215 { 216 (_nextPointerSrcBuffer, _nextPointerDstBuffer) = (_nextPointerDstBuffer, _nextPointerSrcBuffer); 217 } 218 219 public void CalculateEdges() 220 { 221 if (_sourceMesh == null || viewCamera == null) return; 222 223 _silhouetteEdgeFinder.SetVector(WorldSpaceCameraPos, viewCamera.transform.position); 224 225 Matrix4x4 objectToWorld = transform.localToWorldMatrix; 226 _silhouetteEdgeFinder.SetMatrix(ObjectToWorld, objectToWorld); 227 _silhouetteEdgeFinder.SetMatrix(ObjectToWorldIt, objectToWorld.inverse.transpose); 228 229 int threadGroups = Mathf.CeilToInt(FaceCount / 64.0f); 230 if (threadGroups > 0) 231 _silhouetteEdgeFinder.Dispatch(_findSilhouetteEdge_Kernel, threadGroups, 1, 1); 232 233 RunEdgesToStrokePasses(threadGroups); 234 235 RunStrokesToGreasePencilPass(threadGroups); 236 } 237 238 239 240 private void ResolveViewCamera() 241 { 242 #if UNITY_EDITOR 243 if (viewCamera == null) 244 { 245 SceneView sceneView = SceneView.lastActiveSceneView; 246 viewCamera = sceneView != null ? sceneView.camera : Camera.main; 247 } 248 #else 249 if (viewCamera == null) viewCamera = Camera.main; 250 #endif 251 } 252 253 public int GetMaximumBufferLength() 254 { 255 return FaceCount; 256 } 257 public GraphicsBuffer GetStrokeBuffer() 258 { 259 return _denseStrokesBuffer; 260 } 261 public GraphicsBuffer GetColorBuffer() 262 { 263 return _colorBuffer; 264 } 265 266 void RunEdgesToStrokePasses(int threadGroups) 267 { 268 BindNextPointers(_initialize_Kernel); 269 _edgesToStrokes.Dispatch(_initialize_Kernel, threadGroups, 1, 1); 270 SwapNextPointers(); 271 272 for (int i = 0; i < NUM_POINTER_JUMP_ITERATIONS; ++i) 273 { 274 BindNextPointers(_findStrokeTail_Kernel); 275 _edgesToStrokes.Dispatch(_findStrokeTail_Kernel, threadGroups, 1, 1); 276 SwapNextPointers(); 277 } 278 279 //TODO: merge next two kernels 280 BindNextPointers(_resetNext_Kernel); 281 _edgesToStrokes.Dispatch(_resetNext_Kernel, threadGroups, 1, 1); 282 SwapNextPointers(); 283 284 _edgesToStrokes.Dispatch(_initDistances_Kernel, threadGroups, 1, 1); 285 286 for (int i = 0; i < NUM_POINTER_JUMP_ITERATIONS; ++i) 287 { 288 BindNextPointers(_listRank_Kernel); 289 _edgesToStrokes.Dispatch(_listRank_Kernel, threadGroups, 1, 1); 290 SwapNextPointers(); 291 } 292 } 293 294 private void DebugStrokes() 295 { 296 var strokes = new SilhouetteStrokeEdge[FaceCount]; 297 _strokesBuffer.GetData(strokes); 298 299 int printCount = 0; 300 for (int j = 0; j < strokes.Length && printCount < 10; j++) 301 { 302 if (strokes[j].adj != INVALID && strokes[j].adj != ADJ_NONE) 303 { 304 Debug.Log($"Stroke[{j}] pos={strokes[j].pos} adj={strokes[j].adj} minPoint={strokes[j].minPoint} rank={strokes[j].rank} dist={strokes[j].distFromTail:F4}"); 305 printCount++; 306 } 307 } 308 } 309 310 private void RunStrokesToGreasePencilPass(int threadGroups) 311 { 312 _numStrokesCounterBuffer.SetData(new[] { 0u }); 313 _numStrokePointsCounterBuffer.SetData(new[] { 0u }); 314 315 _strokesToGreasePencilStrokes.Dispatch(_setStrokeLengthAtTail_Kernel, threadGroups, 1, 1); 316 _strokesToGreasePencilStrokes.Dispatch(_calcStrokeOffsets_Kernel, threadGroups, 1, 1); 317 _strokesToGreasePencilStrokes.Dispatch(_invalidateEntries_Kernel, threadGroups, 1, 1); 318 319 _strokesToGreasePencilStrokes.SetFloat(RadiusMultiplier, radiusMultiplier); 320 _strokesToGreasePencilStrokes.Dispatch(_sorter_Kernel, threadGroups, 1, 1); 321 } 322 323 private void DebugGp() 324 { 325 var gpStrokes = new GreasePencilRenderer.GreasePencilStrokeVert[2 * FaceCount]; 326 _denseStrokesBuffer.GetData(gpStrokes); 327 328 for (int j = 0; j < gpStrokes.Length; j++) 329 { 330 Debug.Log($"GP Stroke[{j}] pos={gpStrokes[j].pos} mat={gpStrokes[j].mat} strokePointIdx={gpStrokes[j].point_id}"); 331 } 332 } 333 334 private void DebugDrawSilhouetteEdges() 335 { 336 var debugStrokes = new SilhouetteStrokeEdge[FaceCount]; 337 _strokesBuffer.GetData(debugStrokes); 338 339 for (int i = 0; i < debugStrokes.Length; i++) 340 { 341 var adj1 = debugStrokes[i].adj; 342 343 var pos1 = debugStrokes[i].pos; 344 if (adj1 != INVALID && adj1 >= 0 && adj1 < debugStrokes.Length) 345 { 346 var pos2 = debugStrokes[adj1].pos; 347 Debug.DrawLine(pos1, pos2, Color.red); 348 } 349 } 350 } 351 352 private static int[] CalculateAdjacency(int[] triangles) 353 { 354 if (triangles == null) throw new ArgumentNullException(nameof(triangles)); 355 if (triangles.Length % 3 != 0) throw new ArgumentException("Triangle array length must be a multiple of 3.", nameof(triangles)); 356 357 int faceCount = triangles.Length / 3; 358 int[] adj = new int[triangles.Length]; 359 360 for (int i = 0; i < adj.Length; i++) adj[i] = INVALID; 361 362 var edgeToFaces = new Dictionary<long, List<int>>(triangles.Length); 363 364 for (int f = 0; f < faceCount; f++) 365 { 366 int baseIdx = f * 3; 367 int v0 = triangles[baseIdx + 0]; 368 int v1 = triangles[baseIdx + 1]; 369 int v2 = triangles[baseIdx + 2]; 370 371 long[] keys = new long[3]; 372 keys[0] = ((long)Math.Min(v0, v1) << 32) | (uint)Math.Max(v0, v1); 373 keys[1] = ((long)Math.Min(v1, v2) << 32) | (uint)Math.Max(v1, v2); 374 keys[2] = ((long)Math.Min(v2, v0) << 32) | (uint)Math.Max(v2, v0); 375 376 for (int e = 0; e < 3; e++) 377 { 378 if (!edgeToFaces.TryGetValue(keys[e], out var list)) 379 { 380 list = new List<int>(2); 381 edgeToFaces[keys[e]] = list; 382 } 383 list.Add(f); 384 } 385 } 386 387 for (int f = 0; f < faceCount; f++) 388 { 389 int baseIdx = f * 3; 390 int v0 = triangles[baseIdx + 0]; 391 int v1 = triangles[baseIdx + 1]; 392 int v2 = triangles[baseIdx + 2]; 393 394 long[] keys = new long[3]; 395 keys[0] = ((long)Math.Min(v0, v1) << 32) | (uint)Math.Max(v0, v1); 396 keys[1] = ((long)Math.Min(v1, v2) << 32) | (uint)Math.Max(v1, v2); 397 keys[2] = ((long)Math.Min(v2, v0) << 32) | (uint)Math.Max(v2, v0); 398 399 for (int e = 0; e < 3; e++) 400 { 401 var faces = edgeToFaces[keys[e]]; 402 int neighbor = INVALID; 403 404 foreach (var other in faces) 405 { 406 if (other != f) 407 { 408 neighbor = other; 409 break; 410 } 411 } 412 adj[baseIdx + e] = neighbor; 413 } 414 } 415 416 return adj; 417 } 418 }