Guest User

GrassComputeScript.cs

a guest
Jul 26th, 2021
4,861
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 14.31 KB | None | 0 0
  1. // @Minionsart version
  2. // credits  to  forkercat https://gist.github.com/junhaowww/fb6c030c17fe1e109a34f1c92571943f
  3. // and  NedMakesGames https://gist.github.com/NedMakesGames/3e67fabe49e2e3363a657ef8a6a09838
  4. // for the base setup for compute shaders
  5. using System;
  6. using UnityEngine;
  7. using UnityEngine.Rendering;
  8. using UnityEditor;
  9.  
  10. [ExecuteInEditMode]
  11. public class GrassComputeScript : MonoBehaviour
  12. {
  13.     [Header("Components")]
  14.     [SerializeField] private GrassPainter grassPainter = default;
  15.     [SerializeField] private Mesh sourceMesh = default;
  16.     [SerializeField] private Material material = default;
  17.     [SerializeField] private ComputeShader computeShader = default;
  18.  
  19.     // Blade
  20.     [Header("Blade")]
  21.     public float grassHeight = 1;
  22.     public float grassWidth = 0.06f;
  23.     public float grassRandomHeight = 0.25f;
  24.     [Range(0, 1)] public float bladeRadius = 0.6f;
  25.     [Range(0, 1)] public float bladeForwardAmount = 0.38f;
  26.     [Range(1, 5)] public float bladeCurveAmount = 2;
  27.  
  28.     [SerializeField]
  29.     ShaderInteractor interactor;
  30.  
  31.     // Wind
  32.     [Header("Wind")]
  33.     public float windSpeed = 10;
  34.     public float windStrength = 0.05f;
  35.     // Interactor
  36.     [Header("Interactor")]
  37.     public float affectRadius = 0.3f;
  38.     public float affectStrength = 5;
  39.     // LOD
  40.     [Header("LOD")]
  41.     public float minFadeDistance = 40;
  42.     public float maxFadeDistance = 60;
  43.     // Material
  44.     [Header("Material")]
  45.     public Color topTint = new Color(1, 1, 1);
  46.     public Color bottomTint = new Color(0, 0, 1);
  47.     public float ambientStrength = 0.1f;
  48.     // Other
  49.     [Header("Other")]
  50.     public UnityEngine.Rendering.ShadowCastingMode castShadow;
  51.  
  52.     private Camera m_MainCamera;
  53.  
  54.     private readonly int m_AllowedBladesPerVertex = 6;
  55.     private readonly int m_AllowedSegmentsPerBlade = 7;
  56.  
  57.     // The structure to send to the compute shader
  58.     // This layout kind assures that the data is laid out sequentially
  59.     [System.Runtime.InteropServices.StructLayout(System.Runtime.InteropServices.LayoutKind
  60.         .Sequential)]
  61.     private struct SourceVertex
  62.     {
  63.         public Vector3 position;
  64.         public Vector3 normal;
  65.         public Vector2 uv;
  66.         public Vector3 color;
  67.     }
  68.  
  69.     // A state variable to help keep track of whether compute buffers have been set up
  70.     private bool m_Initialized;
  71.     // A compute buffer to hold vertex data of the source mesh
  72.     private ComputeBuffer m_SourceVertBuffer;
  73.     // A compute buffer to hold vertex data of the generated mesh
  74.     private ComputeBuffer m_DrawBuffer;
  75.     // A compute buffer to hold indirect draw arguments
  76.     private ComputeBuffer m_ArgsBuffer;
  77.     // Instantiate the shaders so data belong to their unique compute buffers
  78.     private ComputeShader m_InstantiatedComputeShader;
  79.     private Material m_InstantiatedMaterial;
  80.     // The id of the kernel in the grass compute shader
  81.     private int m_IdGrassKernel;
  82.     // The x dispatch size for the grass compute shader
  83.     private int m_DispatchSize;
  84.     // The local bounds of the generated mesh
  85.     private Bounds m_LocalBounds;
  86.  
  87.     private Camera sceneCam;
  88.  
  89.     // The size of one entry in the various compute buffers
  90.     private const int SOURCE_VERT_STRIDE = sizeof(float) * (3 + 3 + 2 + 3);
  91.     private const int DRAW_STRIDE = sizeof(float) * (3 + (3 + 2 + 3) * 3);
  92.     private const int INDIRECT_ARGS_STRIDE = sizeof(int) * 4;
  93.  
  94.     // The data to reset the args buffer with every frame
  95.     // 0: vertex count per draw instance. We will only use one instance
  96.     // 1: instance count. One
  97.     // 2: start vertex location if using a Graphics Buffer
  98.     // 3: and start instance location if using a Graphics Buffer
  99.     private int[] argsBufferReset = new int[] { 0, 1, 0, 0 };
  100.  
  101. #if UNITY_EDITOR
  102.     SceneView view;
  103.  
  104.  
  105.     // get the scene camera in case of no maincam
  106.     void OnFocus()
  107.     {
  108.         // Remove delegate listener if it has previously
  109.         // been assigned.
  110.         SceneView.duringSceneGui -= this.OnScene;
  111.         // Add (or re-add) the delegate.
  112.         SceneView.duringSceneGui += this.OnScene;
  113.     }
  114.  
  115.     void OnDestroy()
  116.     {
  117.         // When the window is destroyed, remove the delegate
  118.         // so that it will no longer do any drawing.
  119.         SceneView.duringSceneGui -= this.OnScene;
  120.     }
  121.  
  122.     void OnScene(SceneView scene)
  123.     {
  124.         view = scene;
  125.  
  126.     }
  127.  
  128. #endif
  129.     private void OnValidate()
  130.     {
  131.         // Set up components
  132.         m_MainCamera = Camera.main;
  133.         grassPainter = GetComponent<GrassPainter>();
  134.         sourceMesh = GetComponent<MeshFilter>().sharedMesh;  // generated by GeometryGrassPainter
  135.     }
  136.  
  137.     private void OnEnable()
  138.     {
  139.         // If initialized, call on disable to clean things up
  140.         if (m_Initialized)
  141.         {
  142.             OnDisable();
  143.         }
  144. #if UNITY_EDITOR
  145.         SceneView.duringSceneGui += this.OnScene;
  146. #endif
  147.         m_MainCamera = Camera.main;
  148.  
  149.         // Setup compute shader and material manually
  150.  
  151.         // Don't do anything if resources are not found,
  152.         // or no vertex is put on the mesh.
  153.         if (grassPainter == null || sourceMesh == null || computeShader == null || material == null)
  154.         {
  155.             return;
  156.         }
  157.         sourceMesh = GetComponent<MeshFilter>().sharedMesh;
  158.  
  159.         if (sourceMesh.vertexCount == 0)
  160.         {
  161.             return;
  162.         }
  163.  
  164.         m_Initialized = true;
  165.  
  166.         // Instantiate the shaders so they can point to their own buffers
  167.         m_InstantiatedComputeShader = Instantiate(computeShader);
  168.         m_InstantiatedMaterial = Instantiate(material);
  169.  
  170.         // Grab data from the source mesh
  171.         Vector3[] positions = sourceMesh.vertices;
  172.         Vector3[] normals = sourceMesh.normals;
  173.         Vector2[] uvs = sourceMesh.uv;
  174.         Color[] colors = sourceMesh.colors;
  175.  
  176.         // Create the data to upload to the source vert buffer
  177.         SourceVertex[] vertices = new SourceVertex[positions.Length];
  178.         for (int i = 0; i < vertices.Length; i++)
  179.         {
  180.             Color color = colors[i];
  181.             vertices[i] = new SourceVertex()
  182.             {
  183.                 position = positions[i],
  184.                 normal = normals[i],
  185.                 uv = uvs[i],
  186.                 color = new Vector3(color.r, color.g, color.b) // Color --> Vector3
  187.             };
  188.         }
  189.  
  190.         int numSourceVertices = vertices.Length;
  191.  
  192.         // Each segment has two points
  193.         int maxBladesPerVertex = Mathf.Max(1, m_AllowedBladesPerVertex);
  194.         int maxSegmentsPerBlade = Mathf.Max(1, m_AllowedSegmentsPerBlade);
  195.         int maxBladeTriangles = maxBladesPerVertex * ((maxSegmentsPerBlade - 1) * 2 + 1);
  196.  
  197.         // Create compute buffers
  198.         // The stride is the size, in bytes, each object in the buffer takes up
  199.         m_SourceVertBuffer = new ComputeBuffer(vertices.Length, SOURCE_VERT_STRIDE,
  200.             ComputeBufferType.Structured, ComputeBufferMode.Immutable);
  201.         m_SourceVertBuffer.SetData(vertices);
  202.  
  203.         m_DrawBuffer = new ComputeBuffer(numSourceVertices * maxBladeTriangles, DRAW_STRIDE,
  204.             ComputeBufferType.Append);
  205.         m_DrawBuffer.SetCounterValue(0);
  206.  
  207.         m_ArgsBuffer =
  208.             new ComputeBuffer(1, INDIRECT_ARGS_STRIDE, ComputeBufferType.IndirectArguments);
  209.  
  210.         // Cache the kernel IDs we will be dispatching
  211.         m_IdGrassKernel = m_InstantiatedComputeShader.FindKernel("Main");
  212.  
  213.         // Set buffer data
  214.         m_InstantiatedComputeShader.SetBuffer(m_IdGrassKernel, "_SourceVertices",
  215.             m_SourceVertBuffer);
  216.         m_InstantiatedComputeShader.SetBuffer(m_IdGrassKernel, "_DrawTriangles", m_DrawBuffer);
  217.         m_InstantiatedComputeShader.SetBuffer(m_IdGrassKernel, "_IndirectArgsBuffer",
  218.             m_ArgsBuffer);
  219.         // Set vertex data
  220.         m_InstantiatedComputeShader.SetInt("_NumSourceVertices", numSourceVertices);
  221.         m_InstantiatedComputeShader.SetInt("_MaxBladesPerVertex", maxBladesPerVertex);
  222.         m_InstantiatedComputeShader.SetInt("_MaxSegmentsPerBlade", maxSegmentsPerBlade);
  223.  
  224.         m_InstantiatedMaterial.SetBuffer("_DrawTriangles", m_DrawBuffer);
  225.  
  226.         m_InstantiatedMaterial.SetColor("_TopTint", topTint);
  227.         m_InstantiatedMaterial.SetColor("_BottomTint", bottomTint);
  228.         m_InstantiatedMaterial.SetFloat("_AmbientStrength", ambientStrength);
  229.  
  230.  
  231.         // Calculate the number of threads to use. Get the thread size from the kernel
  232.         // Then, divide the number of triangles by that size
  233.         m_InstantiatedComputeShader.GetKernelThreadGroupSizes(m_IdGrassKernel,
  234.             out uint threadGroupSize, out _, out _);
  235.         m_DispatchSize = Mathf.CeilToInt((float)numSourceVertices / threadGroupSize);
  236.  
  237.         // Get the bounds of the source mesh and then expand by the maximum blade width and height
  238.         m_LocalBounds = sourceMesh.bounds;
  239.         m_LocalBounds.Expand(Mathf.Max(grassHeight + grassRandomHeight, grassWidth));
  240.  
  241.         SetGrassDataBase();
  242.     }
  243.  
  244.     private void OnDisable()
  245.     {
  246.         // Dispose of buffers and copied shaders here
  247.         if (m_Initialized)
  248.         {
  249.             // If the application is not in play mode, we have to call DestroyImmediate
  250.             if (Application.isPlaying)
  251.             {
  252.                 Destroy(m_InstantiatedComputeShader);
  253.                 Destroy(m_InstantiatedMaterial);
  254.             }
  255.             else
  256.             {
  257.                 DestroyImmediate(m_InstantiatedComputeShader);
  258.                 DestroyImmediate(m_InstantiatedMaterial);
  259.             }
  260.  
  261.             // Release each buffer
  262.             m_SourceVertBuffer?.Release();
  263.             m_DrawBuffer?.Release();
  264.             m_ArgsBuffer?.Release();
  265.         }
  266.  
  267.         m_Initialized = false;
  268.     }
  269.  
  270.     // LateUpdate is called after all Update calls
  271.     private void LateUpdate()
  272.     {
  273.         // If in edit mode, we need to update the shaders each Update to make sure settings changes are applied
  274.         // Don't worry, in edit mode, Update isn't called each frame
  275.         if (Application.isPlaying == false)
  276.         {
  277.             OnDisable();
  278.             OnEnable();
  279.         }
  280.  
  281.         // If not initialized, do nothing (creating zero-length buffer will crash)
  282.         if (!m_Initialized)
  283.         {
  284.             // Initialization is not done, please check if there are null components
  285.             // or just because there is not vertex being painted.
  286.             return;
  287.         }
  288.  
  289.         // Clear the draw and indirect args buffers of last frame's data
  290.         m_DrawBuffer.SetCounterValue(0);
  291.         m_ArgsBuffer.SetData(argsBufferReset);
  292.  
  293.         // Transform the bounds to world space
  294.         Bounds bounds = TransformBounds(m_LocalBounds);
  295.  
  296.         // Update the shader with frame specific data
  297.         SetGrassDataUpdate();
  298.  
  299.         // Dispatch the grass shader. It will run on the GPU
  300.         m_InstantiatedComputeShader.Dispatch(m_IdGrassKernel, m_DispatchSize, 1, 1);
  301.  
  302.         // DrawProceduralIndirect queues a draw call up for our generated mesh
  303.         Graphics.DrawProceduralIndirect(m_InstantiatedMaterial, bounds, MeshTopology.Triangles,
  304.             m_ArgsBuffer, 0, null, null, castShadow, true, gameObject.layer);
  305.     }
  306.  
  307.     private void SetGrassDataBase()
  308.     {
  309.         // Send things to compute shader that dont need to be set every frame
  310.         m_InstantiatedComputeShader.SetMatrix("_LocalToWorld", transform.localToWorldMatrix);
  311.         m_InstantiatedComputeShader.SetFloat("_Time", Time.time);
  312.  
  313.         m_InstantiatedComputeShader.SetFloat("_GrassHeight", grassHeight);
  314.         m_InstantiatedComputeShader.SetFloat("_GrassWidth", grassWidth);
  315.         m_InstantiatedComputeShader.SetFloat("_GrassRandomHeight", grassRandomHeight);
  316.  
  317.         m_InstantiatedComputeShader.SetFloat("_WindSpeed", windSpeed);
  318.         m_InstantiatedComputeShader.SetFloat("_WindStrength", windStrength);
  319.  
  320.         m_InstantiatedComputeShader.SetFloat("_InteractorRadius", affectRadius);
  321.         m_InstantiatedComputeShader.SetFloat("_InteractorStrength", affectStrength);
  322.  
  323.         m_InstantiatedComputeShader.SetFloat("_BladeRadius", bladeRadius);
  324.         m_InstantiatedComputeShader.SetFloat("_BladeForward", bladeForwardAmount);
  325.         m_InstantiatedComputeShader.SetFloat("_BladeCurve", Mathf.Max(0, bladeCurveAmount));
  326.  
  327.         m_InstantiatedComputeShader.SetFloat("_MinFadeDist", minFadeDistance);
  328.         m_InstantiatedComputeShader.SetFloat("_MaxFadeDist", maxFadeDistance);
  329.  
  330.     }
  331.  
  332.     private void SetGrassDataUpdate()
  333.     {
  334.         // Compute Shader
  335.         //  m_InstantiatedComputeShader.SetMatrix("_LocalToWorld", transform.localToWorldMatrix);
  336.         m_InstantiatedComputeShader.SetFloat("_Time", Time.time);
  337.         if (interactor != null)
  338.         {
  339.             m_InstantiatedComputeShader.SetVector("_PositionMoving", interactor.transform.position);
  340.         }
  341.         else
  342.         {
  343.             m_InstantiatedComputeShader.SetVector("_PositionMoving", Vector3.zero);
  344.         }
  345.  
  346.         if (m_MainCamera != null)
  347.         {
  348.             m_InstantiatedComputeShader.SetVector("_CameraPositionWS", m_MainCamera.transform.position);
  349.  
  350.         }
  351. #if UNITY_EDITOR
  352.         // if we dont have a main camera (it gets added during gameplay), use the scene camera
  353.         else if (view != null)
  354.         {
  355.             m_InstantiatedComputeShader.SetVector("_CameraPositionWS", view.camera.transform.position);
  356.         }
  357. #endif
  358.  
  359.     }
  360.  
  361.  
  362.     // This applies the game object's transform to the local bounds
  363.     // Code by benblo from https://answers.unity.com/questions/361275/cant-convert-bounds-from-world-coordinates-to-loca.html
  364.     private Bounds TransformBounds(Bounds boundsOS)
  365.     {
  366.         var center = transform.TransformPoint(boundsOS.center);
  367.  
  368.         // transform the local extents' axes
  369.         var extents = boundsOS.extents;
  370.         var axisX = transform.TransformVector(extents.x, 0, 0);
  371.         var axisY = transform.TransformVector(0, extents.y, 0);
  372.         var axisZ = transform.TransformVector(0, 0, extents.z);
  373.  
  374.         // sum their absolute value to get the world extents
  375.         extents.x = Mathf.Abs(axisX.x) + Mathf.Abs(axisY.x) + Mathf.Abs(axisZ.x);
  376.         extents.y = Mathf.Abs(axisX.y) + Mathf.Abs(axisY.y) + Mathf.Abs(axisZ.y);
  377.         extents.z = Mathf.Abs(axisX.z) + Mathf.Abs(axisY.z) + Mathf.Abs(axisZ.z);
  378.  
  379.         return new Bounds { center = center, extents = extents };
  380.     }
  381. }
Advertisement
Add Comment
Please, Sign In to add comment