{"id":397,"date":"2021-06-05T18:21:10","date_gmt":"2021-06-05T18:21:10","guid":{"rendered":"https:\/\/bronsonzgeb.com\/?p=397"},"modified":"2021-06-05T18:21:11","modified_gmt":"2021-06-05T18:21:11","slug":"gpu-mesh-voxelizer-part-3-render-tons-of-voxels-with-drawmeshinstancedindirect","status":"publish","type":"post","link":"https:\/\/bronsonzgeb.com\/index.php\/2021\/06\/05\/gpu-mesh-voxelizer-part-3-render-tons-of-voxels-with-drawmeshinstancedindirect\/","title":{"rendered":"GPU Mesh Voxelizer Part 3: Render tons of voxels with DrawMeshInstancedIndirect"},"content":{"rendered":"\n<p>In this article, we render all the voxels from our voxelization process. If you haven&#8217;t read the previous parts, you can find part 1&nbsp;<a rel=\"noreferrer noopener\" target=\"_blank\" href=\"https:\/\/bronsonzgeb.com\/index.php\/2021\/05\/22\/gpu-mesh-voxelizer-part-1\/\">here<\/a>&nbsp;and part 2&nbsp;<a rel=\"noreferrer noopener\" target=\"_blank\" href=\"https:\/\/bronsonzgeb.com\/index.php\/2021\/05\/29\/gpu-mesh-voxelizer-part-2\/\">here<\/a>. We&#8217;ll generate a mesh and draw it via the `DrawMeshInstancedIndirect` API. Doing so requires us to write a custom shader as well. Let&#8217;s get started.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Drawing tons of meshes with DrawMeshInstancedIndirect<\/h2>\n\n\n\n<p>Our goal is to draw millions of voxels. I chose to approach this using the <code>DrawMeshInstancedIndirect<\/code> API, which draws a copy of a single mesh in a zillion different places. It works by uploading a single copy of a mesh and an array of positions to the GPU. Then in the shader, we grab the relevant position and use it to place and draw an instance of the mesh. Here&#8217;s what the drawing code looks like.<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>if (_drawBlocks)\n{\n    _blocksArgsBuffer.SetData(new&#91;] {_voxelMesh.triangles.Length, _gridPointCount, 0, 0, 0});\n    _blocksMaterial.SetBuffer(\"_Positions\", _voxelPointsBuffer);\n    Graphics.DrawMeshInstancedIndirect(_voxelMesh, 0, _blocksMaterial, _meshCollider.bounds, _blocksArgsBuffer);\n}<\/code><\/pre>\n\n\n\n<p>The `DrawMeshInstancedIndirect` API takes five arguments here:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>The mesh<\/li><li>The submesh index (we won&#8217;t have submeshes, so 0 for us)<\/li><li>A material<\/li><li>The bounds<\/li><li>An args buffer<\/li><\/ol>\n\n\n\n<p>Let&#8217;s tackle them in order, starting with the voxel mesh.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Generating a Voxel Mesh<\/h2>\n\n\n\n<p>The plan is to create a small cube mesh that&#8217;s the size of one voxel, then draw as many instances of that mesh as we need. Alternatively, we could use a constant size cube and scale it, but I figure that creating a new mesh one time (or every time we change the voxel size) is cheaper than scaling every voxel every time we render. Realistically, the difference is probably negligible, but this is the choice I made.<\/p>\n\n\n\n<p>So, first thing&#8217;s first, we need to create the vertices. As you may know, a cube has eight unique vertices. However, if we want our cubes to have a nice flat look, we need to duplicate several vertices. Why is that? Because each vertex holds a single normal, but each vertex shares three different faces, each with its own normal. And so, if we reuse the same vertex across multiple faces, the normal will get averaged across the three faces. The result is that the lighting will be smooth across the sharp corners of the cube. To illustrate, here&#8217;s what that looks like in Blender.<\/p>\n\n\n\n<figure class=\"wp-block-gallery columns-2 is-cropped wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex\"><ul class=\"blocks-gallery-grid\"><li class=\"blocks-gallery-item\"><figure><img loading=\"lazy\" decoding=\"async\" width=\"467\" height=\"408\" src=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/06\/2021-06-02-14_57_18-Window-1.png\" alt=\"\" data-id=\"405\" data-link=\"https:\/\/bronsonzgeb.com\/?attachment_id=405\" class=\"wp-image-405\" srcset=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/06\/2021-06-02-14_57_18-Window-1.png 467w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/06\/2021-06-02-14_57_18-Window-1-300x262.png 300w\" sizes=\"auto, (max-width: 467px) 100vw, 467px\" \/><figcaption class=\"blocks-gallery-item__caption\">Smoothed Normals<\/figcaption><\/figure><\/li><li class=\"blocks-gallery-item\"><figure><img loading=\"lazy\" decoding=\"async\" width=\"467\" height=\"408\" src=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/06\/2021-06-02-14_56_51-Window-1.png\" alt=\"\" data-id=\"406\" data-full-url=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/06\/2021-06-02-14_56_51-Window-1.png\" data-link=\"https:\/\/bronsonzgeb.com\/?attachment_id=406\" class=\"wp-image-406\" srcset=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/06\/2021-06-02-14_56_51-Window-1.png 467w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/06\/2021-06-02-14_56_51-Window-1-300x262.png 300w\" sizes=\"auto, (max-width: 467px) 100vw, 467px\" \/><figcaption class=\"blocks-gallery-item__caption\">Sharp Normals<\/figcaption><\/figure><\/li><\/ul><\/figure>\n\n\n\n<p>So what does this mean? It means we need four vertices per cube face, even if many of those vertices share a position. Let&#8217;s write a new function to generate our voxel mesh called <code>GenerateVoxelMesh<\/code>. Here&#8217;s the skeleton of the method.<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>Mesh GenerateVoxelMesh(float size)\n{\n\tvar mesh = new Mesh();\n\t\/\/ TODO:  \n\t\/\/\t1. Generate the vertices\n\t\/\/\t2. Generate the triangle indices\n\t\/\/\t3. Assign them and recalculate Bounds and Normals\n\treturn mesh;\n}<\/code><\/pre>\n\n\n\n<p>Now let&#8217;s generate those vertices.<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>\/\/ Generate the vertices\nVector3&#91;] vertices =\n{\n    \/\/Front\n    new Vector3(0, 0, 0),       \/\/ Front Bottom Left    0\n    new Vector3(size, 0, 0),    \/\/ Front Bottom Right   1\n    new Vector3(size, size, 0), \/\/ Front Top Right      2\n    new Vector3(0, size, 0),    \/\/ Front Top Left       3\n\n    \/\/Top\n    new Vector3(size, size, 0),     \/\/ Front Top Right  4\n    new Vector3(0, size, 0),        \/\/ Front Top Left   5\n    new Vector3(0, size, size),     \/\/ Back Top Left    6\n    new Vector3(size, size, size),  \/\/ Back Top Right   7\n\n    \/\/Right\n    new Vector3(size, 0, 0),        \/\/ Front Bottom Right  8\n    new Vector3(size, size, 0),     \/\/ Front Top Right     9\n    new Vector3(size, size, size),  \/\/ Back Top Right      10\n    new Vector3(size, 0, size),     \/\/ Back Bottom Right   11\n\n    \/\/Left\n    new Vector3(0, 0, 0),       \/\/ Front Bottom Left    12\n    new Vector3(0, size, 0),    \/\/ Front Top Left       13\n    new Vector3(0, size, size), \/\/ Back Top Left        14\n    new Vector3(0, 0, size),    \/\/ Back Bottom Left     15\n\n    \/\/Back\n    new Vector3(0, size, size),     \/\/ Back Top Left     16\n    new Vector3(size, size, size),  \/\/ Back Top Right    17\n    new Vector3(size, 0, size),     \/\/ Back Bottom Right 18\n    new Vector3(0, 0, size),        \/\/ Back Bottom Left  19\n\n    \/\/Bottom\n    new Vector3(0, 0, 0),       \/\/ Front Bottom Left   20\n    new Vector3(size, 0, 0),    \/\/ Front Bottom Right  21\n    new Vector3(size, 0, size), \/\/ Back Bottom Right   22\n    new Vector3(0, 0, size)     \/\/ Back Bottom Left    23\n};<\/code><\/pre>\n\n\n\n<p>Each group of four vertices is a single face of the cube, and the origin of the cube is at the front-bottom-left vertex. It&#8217;s worth mentioning again that the width and height of a face is the size of one voxel so that we don&#8217;t have to perform any scaling.<\/p>\n\n\n\n<p>The next thing we need to do is define the triangles. The triangles array defines the indices of the vertices to use in clockwise order. The renderer will grab three indices from the triangles array, then use those indices to obtain three vertices from the vertex array, then draw that triangle. Here&#8217;s the triangles array definition.<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>\/\/Generate the triangle indices\nint&#91;] triangles =\n{\n    \/\/Front\n    0, 2, 1,\n    0, 3, 2,\n\n    \/\/ Top\n    4, 5, 6,\n    4, 6, 7,\n\n    \/\/ Right\n    8, 9, 10,\n    8, 10, 11,\n\n    \/\/ Left\n    12, 15, 14,\n    12, 14, 13,\n\n    \/\/ Back\n    17, 16, 19,\n    17, 19, 18,\n\n    \/\/ Bottom\n    20, 22, 23,\n    20, 21, 22\n};<\/code><\/pre>\n\n\n\n<p>Every three indices define a single triangle, so every six defines a face. <\/p>\n\n\n\n<p>Finally, we set our vertices and triangles and recalculate the bounds and face normals.<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>\/\/Assign them and recalculate Bounds and Normals\nmesh.SetVertices(vertices);\nmesh.SetTriangles(triangles, 0);\nmesh.RecalculateBounds();\nmesh.RecalculateNormals();<\/code><\/pre>\n\n\n\n<p>That concludes the <code>GenerateVoxelMesh<\/code> function. Next, we have to create the material. It would be nice to use a standard material, but we need to write a shader to work with our custom voxel points data.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Surface Shader with Custom Instancing Support<\/h2>\n\n\n\n<p>Let&#8217;s use a surface shader to help reduce the boilerplate code. That way, I can focus on explaining the essential parts. Here&#8217;s a basic surface shader for a starting point.<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>Shader \"Custom\/Basic\" {\n    Properties {\n        _Color (\"Color\", Color) = (1,1,1,1)\n    }\n    SubShader {\n        Tags { \"RenderType\"=\"Opaque\" }\n        LOD 200\n        \n        CGPROGRAM\n        #pragma surface surf Standard fullforwardshadows addshadow\n\n        \/\/ Use shader model 4.5 target to get compute shader support\n        #pragma target 4.5\n        \n        struct Input { fixed4 color : COLOR; };\n\n        fixed4 _Color;\n\n        void surf (Input IN, inout SurfaceOutputStandard o) {\n            o.Albedo = _Color.rgb;\n        }\n        ENDCG\n    }\n    FallBack Off\n}<\/code><\/pre>\n\n\n\n<p>Let&#8217;s start by adding our buffer of positions. Like in our Compute Shader, we&#8217;ll add a <code>StructuredBuffer&lt;float4&gt;<\/code> to hold the position of each voxel.<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>#ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED\nStructuredBuffer&lt;float4&gt; _Positions;\n#endif<\/code><\/pre>\n\n\n\n<p>We wrap it in a <code>UNITY_PROCEDURAL_INSTANCING_ENABLED<\/code> block, so it&#8217;s only available if we&#8217;re using GPU Instancing. Realistically, this shader won&#8217;t work at all if we aren&#8217;t, but we have to do it regardless. The next step is to use this buffer. If you recall, the vertex positions are relative to the bottom-front-left point of their respective voxel. So, we&#8217;ll create a translation matrix that moves each vertex from this voxel local-space to world-space. Let&#8217;s write a vertex function to position the vertices.<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>void vert(inout appdata_full v, out Input data)\n{\n    UNITY_INITIALIZE_OUTPUT(Input, data);\n\n    #ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED\n    v.vertex = mul(_Matrix, v.vertex);\n    #endif\n}<\/code><\/pre>\n\n\n\n<p>You&#8217;ll notice we wrapped the multiplication in another <code>UNITY_PROCEDURAL_INSTANCING_ENABLED<\/code> block. That&#8217;s because if we don&#8217;t have GPU instancing, we don&#8217;t have our positions buffer, so we won&#8217;t be able to position our vertices. Also, you may wonder where <code>_Matrix<\/code> came from. When we use <code>DrawMeshInstancedIndirect<\/code> we can specify a setup function to initialize per-instance data. This is where we generate <code>_Matrix<\/code>, which is the translation matrix mentioned above. Here&#8217;s what that looks like.<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>void setup()\n{\n    #ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED\n    float4 position = _Positions&#91;unity_InstanceID];\n    \n    _Matrix = float4x4(\n        1, 0, 0, position.x,\n        0, 1, 0, position.y,\n        0, 0, 1, position.z,\n        0, 0, 0, 1\n    );\n    #endif\n}<\/code><\/pre>\n\n\n\n<p>In this function, we first get the voxel position from our <code>_Positions<\/code> array. By the way, <code>unity_InstanceID<\/code> is automatically created by the renderer; it represents the current instance index. Then we use the position to generate a standard translation matrix. If you don&#8217;t know, a translation matrix is a matrix that will translate a vertex when the two are multiplied together. For this to all work, we still have to specify our vertex function and setup functions. We do this by modifying our #pragma statements.<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>#pragma surface surf Standard <strong>vertex:vert<\/strong> fullforwardshadows addshadow\n<strong>#pragma instancing_options procedural:setup<\/strong><\/code><\/pre>\n\n\n\n<p>Add <code>vertex:vert<\/code> to the existing surface pragma to specify that we&#8217;re using a custom vertex function called <code>vert<\/code>. Then add <code>#pragma instancing_options procedural:setup<\/code> to designate that we have a per-instance function called <code>setup<\/code>. Let&#8217;s also declare <code>_Matrix<\/code> in our instance variables.<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>#ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED\nStructuredBuffer&lt;float4&gt; _Positions;\n<strong>float4x4 _Matrix;<\/strong>\n#endif<\/code><\/pre>\n\n\n\n<p>At this point, if you drew the voxels, you&#8217;d end up with a big solid block like this:<\/p>\n\n\n\n<div class=\"wp-block-image\"><figure class=\"aligncenter size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"515\" height=\"452\" src=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/06\/2021-06-04-14_34_58-Clipboard.png\" alt=\"\" class=\"wp-image-408\" srcset=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/06\/2021-06-04-14_34_58-Clipboard.png 515w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/06\/2021-06-04-14_34_58-Clipboard-300x263.png 300w\" sizes=\"auto, (max-width: 515px) 100vw, 515px\" \/><\/figure><\/div>\n\n\n\n<p>That&#8217;s because our <code>_Positions<\/code> array holds the position of every single voxel, whether they&#8217;re solid or not. If you recall, we marked our solid voxels by setting their <code>w<\/code> component to 1. So let&#8217;s clip all the other voxels.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Clipping the empty voxels<\/h2>\n\n\n\n<p>To clip empty voxels we can use the <code>clip()<\/code> function inside our <code>surf<\/code> function. This function takes a float argument, and if it&#8217;s less than 0, it&#8217;ll discard the fragment. Let&#8217;s set up a <code>_Clip<\/code> variable and initialize it in our <code>setup()<\/code> function.<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>\/\/ ...\nhalf _Clip;\nvoid setup()\n{\n\t#ifdef UNITY_PROCEDURAL_INSTANCING_ENABLED\n\tfloat4 position = _Positions&#91;unity_InstanceID];\n\n\t_Matrix = float4x4(\n    \t    1, 0, 0, position.x,\n\t    0, 1, 0, position.y,\n\t    0, 0, 1, position.z,\n\t    0, 0, 0, 1\n\t);\n\t<strong>_Clip = -1.0 + position.w;<\/strong>\n\t#endif\n}\n\/\/ ...<\/code><\/pre>\n\n\n\n<p>Then we just call <code>clip()<\/code> in the <code>surf<\/code> function.<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>void surf(Input IN, inout SurfaceOutputStandard o)\n{\n    <strong>clip(_Clip);<\/strong>\n    o.Albedo = _Color.rgb;\n}<\/code><\/pre>\n\n\n\n<p>As a result, we can render a proper voxelized version of our mesh.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"633\" src=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/06\/2021-06-02-14_40_08-Clipboard-1024x633.png\" alt=\"\" class=\"wp-image-409\" srcset=\"https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/06\/2021-06-02-14_40_08-Clipboard-1024x633.png 1024w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/06\/2021-06-02-14_40_08-Clipboard-300x185.png 300w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/06\/2021-06-02-14_40_08-Clipboard-768x475.png 768w, https:\/\/bronsonzgeb.com\/wp-content\/uploads\/2021\/06\/2021-06-02-14_40_08-Clipboard.png 1142w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Wrapping Up<\/h2>\n\n\n\n<p>Let&#8217;s revisit the first block of code.<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>if (_drawBlocks)\n{\n    _blocksArgsBuffer.SetData(new&#91;] {_voxelMesh.triangles.Length, _gridPointCount, 0, 0, 0});\n    _blocksMaterial.SetBuffer(\"_Positions\", _voxelPointsBuffer);\n    Graphics.DrawMeshInstancedIndirect(_voxelMesh, 0, _blocksMaterial, _meshCollider.bounds, <strong>_blocksArgsBuffer<\/strong>);\n}<\/code><\/pre>\n\n\n\n<p>The final aspect to consider is the args buffer. In case you forgot, the args buffer is a buffer that holds five arguments:<\/p>\n\n\n\n<ol class=\"wp-block-list\"><li>the vertices count (per instance)<\/li><li>the instance count<\/li><li>the starting index of the vertices<\/li><li>the starting index of the indices<\/li><li>some reserved value that&#8217;s always 0<\/li><\/ol>\n\n\n\n<p>So in our case, we feed it the number of vertices in our mesh, the number of voxels we have, and the rest of the arguments are 0. By the way, I haven&#8217;t mentioned it yet, but this block of code goes inside the update loop. Here&#8217;s my entire <code>Update<\/code> method.<\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>void Update()\n{\n    VoxelizeMeshWithGPU();\n\n    if (_drawPointGrid)\n    {\n        _gridPointMaterial.SetMatrix(LocalToWorldMatrix, transform.localToWorldMatrix);\n        _gridPointMaterial.SetVector(BoundsMin, new Vector4(_boundsMin.x, _boundsMin.y, _boundsMin.z, 0.0f));\n        _gridPointMaterial.SetBuffer(VoxelGridPoints, _voxelPointsBuffer);\n        _pointsArgsBuffer.SetData(new&#91;] {1, _gridPointCount, 0, 0, 0});\n        Graphics.DrawProceduralIndirect(_gridPointMaterial, _meshCollider.bounds, MeshTopology.Points,\n            _pointsArgsBuffer);\n    }\n\n    if (_drawBlocks)\n    {\n        _blocksArgsBuffer.SetData(new&#91;] {_voxelMesh.triangles.Length, _gridPointCount, 0, 0, 0});\n        _blocksMaterial.SetBuffer(\"_Positions\", _voxelPointsBuffer);\n        Graphics.DrawMeshInstancedIndirect(_voxelMesh, 0, _blocksMaterial, _meshCollider.bounds, _blocksArgsBuffer);\n    }\n}<\/code><\/pre>\n\n\n\n<p>And the <code>GenerateVoxelMesh<\/code> function is used inside the <code>VoxelizeMeshWithGPU<\/code> method whenever the voxel size changes. <\/p>\n\n\n\n<pre class=\"wp-block-code has-small-font-size\"><code>void VoxelizeMeshWithGPU()\n{\n    Profiler.BeginSample(\"Voxelize Mesh (GPU)\");\n\n    Bounds bounds = _meshCollider.bounds;\n    _boundsMin = transform.InverseTransformPoint(bounds.min);\n\n    Vector3 voxelCount = bounds.extents \/ _halfSize;\n    int xGridSize = Mathf.CeilToInt(voxelCount.x);\n    int yGridSize = Mathf.CeilToInt(voxelCount.y);\n    int zGridSize = Mathf.CeilToInt(voxelCount.z);\n\n    bool resizeVoxelPointsBuffer = false;\n    if (_gridPoints == null || _gridPoints.Length != xGridSize * yGridSize * zGridSize || _voxelPointsBuffer == null)\n    {\n        _gridPoints = new Vector4&#91;xGridSize * yGridSize * zGridSize];\n        resizeVoxelPointsBuffer = true;\n    }\n\n    if (resizeVoxelPointsBuffer || _voxelPointsBuffer == null || !_voxelPointsBuffer.IsValid())\n    {\n        _voxelPointsBuffer?.Dispose();\n        _voxelPointsBuffer = new ComputeBuffer(xGridSize * yGridSize * zGridSize, 4 * sizeof(float));\n    }\n\n\n    if (resizeVoxelPointsBuffer)\n    {\n        _voxelPointsBuffer.SetData(_gridPoints);\n\n        <strong>_voxelMesh = GenerateVoxelMesh(_halfSize * 2.0f);<\/strong>\n    }\n\n    if (_meshVerticesBuffer == null || !_meshVerticesBuffer.IsValid())\n    {\n        _meshVerticesBuffer?.Dispose();\n\n        var sharedMesh = _meshFilter.sharedMesh;\n        _meshVerticesBuffer = new ComputeBuffer(sharedMesh.vertexCount, 3 * sizeof(float));\n        _meshVerticesBuffer.SetData(sharedMesh.vertices);\n    }\n\n    if (_meshTrianglesBuffer == null || !_meshTrianglesBuffer.IsValid())\n    {\n        _meshTrianglesBuffer?.Dispose();\n\n        var sharedMesh = _meshFilter.sharedMesh;\n            _meshTrianglesBuffer = new ComputeBuffer(sharedMesh.triangles.Length, sizeof(int));\n        _meshTrianglesBuffer.SetData(sharedMesh.triangles);\n    }\n\n    var voxelizeKernel = _voxelizeComputeShader.FindKernel(\"VoxelizeMesh\");\n    _voxelizeComputeShader.SetInt(\"_GridWidth\", xGridSize);\n    _voxelizeComputeShader.SetInt(\"_GridHeight\", yGridSize);\n    _voxelizeComputeShader.SetInt(\"_GridDepth\", zGridSize);\n\n    _voxelizeComputeShader.SetFloat(\"_CellHalfSize\", _halfSize);\n\n    _voxelizeComputeShader.SetBuffer(voxelizeKernel, VoxelGridPoints, _voxelPointsBuffer);\n    _voxelizeComputeShader.SetBuffer(voxelizeKernel, \"_MeshVertices\", _meshVerticesBuffer);\n    _voxelizeComputeShader.SetBuffer(voxelizeKernel, \"_MeshTriangleIndices\", _meshTrianglesBuffer);\n    _voxelizeComputeShader.SetInt(\"_TriangleCount\", _meshFilter.sharedMesh.triangles.Length);\n\n    _voxelizeComputeShader.SetVector(BoundsMin, _boundsMin);\n\n    _voxelizeComputeShader.GetKernelThreadGroupSizes(voxelizeKernel, out uint xGroupSize, out uint yGroupSize, out uint zGroupSize);\n\n    _voxelizeComputeShader.Dispatch(voxelizeKernel,\n        Mathf.CeilToInt(xGridSize \/ (float) xGroupSize),\n        Mathf.CeilToInt(yGridSize \/ (float) yGroupSize),\n        Mathf.CeilToInt(zGridSize \/ (float) zGroupSize));\n    _gridPointCount = _voxelPointsBuffer.count;\n    _voxelPointsBuffer.GetData(_gridPoints);\n\n    Profiler.EndSample();\n}<\/code><\/pre>\n\n\n\n<p>Despite posting the code here, I recommend reading it in the Github project linked at the end.<\/p>\n\n\n\n<p>That wraps up this post on rendering our voxels. At this point, you may notice that the inside of the mesh is empty. That&#8217;s because our voxelizer works by checking triangle\/voxel intersection, and there are no triangles inside the mesh. We&#8217;ll tackle that in a future post.<\/p>\n\n\n\n<p><strong>Explore the complete project&nbsp;<\/strong><a rel=\"noreferrer noopener\" target=\"_blank\" href=\"https:\/\/github.com\/bzgeb\/UnityGPUMeshVoxelizerPart3\"><strong>here<\/strong><\/a><strong>&nbsp;on GitHub. If you like my work,&nbsp;<\/strong><a rel=\"noreferrer noopener\" target=\"_blank\" href=\"https:\/\/bronsonzgeb.com\/index.php\/join-my-mailing-list\/\"><strong>join my mailing list<\/strong><\/a><strong>&nbsp;to be notified when the next part is released.<\/strong><\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this article, we render all the voxels from our voxelization process. If you haven&#8217;t read the previous parts, you can find part 1&nbsp;here&nbsp;and part 2&nbsp;here. We&#8217;ll generate a mesh and draw it via the `DrawMeshInstancedIndirect` API. Doing so requires us to write a custom shader as well. Let&#8217;s get started. Drawing tons of meshes [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":409,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[25,17,1],"tags":[26,8,5,34],"class_list":["post-397","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-compute-shaders","category-graphics","category-unity-programming","tag-compute-shaders","tag-game-development","tag-unity","tag-voxelization"],"_links":{"self":[{"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/posts\/397","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/comments?post=397"}],"version-history":[{"count":7,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/posts\/397\/revisions"}],"predecessor-version":[{"id":410,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/posts\/397\/revisions\/410"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/media\/409"}],"wp:attachment":[{"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/media?parent=397"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/categories?post=397"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bronsonzgeb.com\/index.php\/wp-json\/wp\/v2\/tags?post=397"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}