/ Assets / scripts / SilhouetteEdgeCalculator.cs
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  }