Search Unity

I need help optimizing a procedurally generated chunk

Discussion in 'Scripting' started by Missingno525, Aug 12, 2017.

  1. Missingno525

    Missingno525

    Joined:
    Aug 20, 2014
    Posts:
    17
    I am working on a survival game where the world is made out of cubes (much like Minecraft and many others), but I am having trouble with generating the chunks in a timely manner. Currently, generating a chunk takes at most 800ms on my computer.

    This is what the profiler says:
    Profiler.PNG

    Also, here is the code being used to generate cubes:

    MeshData.cs
    Code (CSharp):
    1. public MeshData () {
    2.  
    3.         vertices = new List<Vector3> (65000);
    4.         uvs = new List<Vector2> (65000);
    5.         triangles = new List<int> (65000);
    6.     }
    7.  
    8.     public Mesh GetMesh (Block [,,] blocks, int offsetX, int offsetY) {
    9.  
    10.         for (int i = 0; i < blocks.GetLength (0); i++) {
    11.             for (int j = 0; j < blocks.GetLength (1); j++) {
    12.                 for (int k = 0; k < blocks.GetLength (2); k++) {
    13.  
    14.                     CreateVoxel (blocks [i, j, k]);
    15.                 }
    16.             }
    17.         }
    18.  
    19.         Mesh mesh = new Mesh ();
    20.  
    21.         mesh.SetVertices (vertices);
    22.         mesh.SetUVs (0, uvs);
    23.         mesh.SetTriangles (triangles, 0);
    24.  
    25.         mesh.RecalculateNormals ();
    26.         mesh.RecalculateTangents ();
    27.         mesh.RecalculateBounds ();
    28.  
    29.         return mesh;
    30.     }
    31.  
    32.  
    33. void CreateVoxel (Block data) {
    34.  
    35.         if (data.shape == Block.Shape.Cube)
    36.             CreateCube (data);
    37.     }
    38.  
    39.  
    40.     void CreateCube (Block data) {
    41.  
    42.         int numVertices = vertices.Count;
    43.  
    44.         if (data.CanSeeFace  (Vector3.forward)) {
    45.  
    46.             CreateSquare (data);
    47.             ApplyRotation (vertices.Count - 4, data.position, new Vector3 (0f, 0f, 0f));
    48.         }
    49.  
    50.         if (data.CanSeeFace  (Vector3.right)) {
    51.  
    52.             CreateSquare (data);
    53.             ApplyRotation (vertices.Count - 4, data.position, new Vector3 (0f, 90f, 0f));
    54.         }
    55.  
    56.         if (data.CanSeeFace  (Vector3.back)) {
    57.  
    58.             CreateSquare (data);
    59.             ApplyRotation (vertices.Count - 4, data.position, new Vector3 (0f, 180f, 0f));
    60.         }
    61.  
    62.         if (data.CanSeeFace  (Vector3.left)) {
    63.  
    64.             CreateSquare (data);
    65.             ApplyRotation (vertices.Count - 4, data.position, new Vector3 (0f, 270f, 0f));
    66.         }
    67.  
    68.         if (data.CanSeeFace  (Vector3.up)) {
    69.  
    70.             CreateSquare (data);
    71.             ApplyRotation (vertices.Count - 4, data.position, new Vector3 (270f, 0f, 0f));
    72.         }
    73.  
    74.         if (data.CanSeeFace  (Vector3.down)) {
    75.  
    76.             CreateSquare (data);
    77.             ApplyRotation (vertices.Count - 4, data.position, new Vector3 (90f, 0f, 0f));
    78.         }
    79.  
    80.         ApplyRotation (numVertices, data.position, data.quaternion);
    81.     }
    82.  
    83. void CreateSquare (Block data, bool slant = false) {
    84.  
    85.             vertices.Add (new Vector3 (data.position.x - 0.5f, data.position.y - 0.5f, data.position.z + 0.5f));
    86.             vertices.Add (new Vector3 (data.position.x + 0.5f, data.position.y - 0.5f, data.position.z + 0.5f));
    87.             vertices.Add (new Vector3 (data.position.x - 0.5f, data.position.y + 0.5f, data.position.z + 0.5f));
    88.             vertices.Add (new Vector3 (data.position.x + 0.5f, data.position.y + 0.5f, data.position.z + 0.5f));
    89.  
    90.         triangles.Add (vertices.Count - 4);
    91.         triangles.Add (vertices.Count - 3);
    92.         triangles.Add (vertices.Count - 1);
    93.  
    94.         triangles.Add (vertices.Count - 4);
    95.         triangles.Add (vertices.Count - 1);
    96.         triangles.Add (vertices.Count - 2);
    97.     }
    98.  
    99. void ApplyRotation (int numVertices, Vector3 center, Quaternion rotation) {
    100.  
    101.         if (numVertices == vertices.Count)
    102.             return;
    103.  
    104.         for (int i = numVertices; i < vertices.Count; i++) {
    105.  
    106.             vertices [i] = rotation * (vertices [i] - center) + center;
    107.             //normals [i] = q * (normals [i] - center) + center;
    108.         }
    109.     }
    110.  
    111.     void ApplyRotation (int numVertices, Vector3 center, Vector3 rotation) {
    112.  
    113.         if (numVertices == vertices.Count)
    114.             return;
    115.  
    116.         Quaternion q = Quaternion.Euler (rotation);
    117.  
    118.         for (int i = numVertices; i < vertices.Count; i++) {
    119.  
    120.             vertices [i] = q * (vertices [i] - center) + center;
    121.             //normals [i] = q * (normals [i] - center) + center;
    122.         }
    123.     }

    Is there anything I can do to improve the chunk generation? I think Minecraft creates new chunks at runtime (because there are way too many chunks in the world to do it all when you start the game), so I know what I am trying to do is possible. Also, if I do generate them in a loading screen before you start the game, the loading time will vary with the size of the world. If it's 62x62 chunks (making the world about 1000x1000 blocks in size) it would take an absurd amount of time to load.

    Also, in just in case it's important, here is the code for the blocks and the chunk.

    Block.cs
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Block {
    6.  
    7.     public Vector3 position;
    8.     public Vector3 rotation;
    9.  
    10.     public Quaternion quaternion;
    11.  
    12.     public enum Face { Unknown, Square, Triangle, StairSide, Slant, StairFront };
    13.     public enum Shape { Cube, Slant, Stair, Half };
    14.     public enum Material { Air, Filled };
    15.  
    16.     public Shape shape;
    17.     public Material material;
    18.  
    19.     public Block (Vector3 position, Vector3 rotation, Shape shape, Material material) {
    20.  
    21.         this.position = position;
    22.         this.rotation = rotation;
    23.         this.shape = shape;
    24.         this.material = material;
    25.  
    26.         quaternion = Quaternion.Euler (rotation);
    27.     }
    28.  
    29.     public static Block Air = new Block (Vector3.zero, Vector3.zero, Block.Shape.Cube, Block.Material.Air);
    30.  
    31.     public Face GetFace (Vector3 direction) {
    32.  
    33.         if (shape == Shape.Cube)
    34.             return Face.Square;
    35.  
    36.         if (shape == Shape.Slant) {
    37.  
    38.             if (direction == quaternion * Vector3.back || direction == quaternion * Vector3.down)
    39.                 return Face.Square;
    40.  
    41.             if (direction == quaternion * Vector3.left || direction == quaternion * Vector3.right)
    42.                 return Face.Triangle;
    43.  
    44.             return Face.Slant;
    45.         }
    46.  
    47.         if (shape == Shape.Stair) {
    48.  
    49.             if (direction == quaternion * Vector3.back || direction == quaternion * Vector3.down)
    50.                 return Face.Square;
    51.  
    52.             if (direction == quaternion * Vector3.left || direction == quaternion * Vector3.right)
    53.                 return Face.StairSide;
    54.  
    55.             return Face.StairFront;
    56.         }
    57.  
    58.         return Face.Unknown;
    59.     }
    60.  
    61.     public bool CanSeeFace (Vector3 direction) {
    62.  
    63.         //Rotate the vector to the direction
    64.         direction = quaternion * direction;
    65.  
    66.         //Are we next to a block we can see through anyway?
    67.         Block neighbor = Chunk.instance.GetBlockAt (position.x + direction.x, position.y + direction.y, position.z + direction.z);
    68.  
    69.         if (neighbor == null || neighbor.material == Material.Air)
    70.             return true;
    71.  
    72.         //No?! Well, fudge no we have to do something more complicated.
    73.         Face myFace = GetFace (direction);
    74.         Face neighborFace = neighbor.GetFace ((direction * -1));
    75.  
    76.  
    77.         if (neighborFace == Face.Square) {
    78.  
    79.             if (myFace == Face.Square || myFace == Face.Triangle || myFace == Face.StairSide)
    80.                 return false;
    81.         }
    82.  
    83.         if (neighborFace == Face.StairSide && myFace == Face.Triangle && rotation == neighbor.rotation)
    84.             return false;
    85.  
    86.         if (neighborFace == Face.StairSide && myFace == Face.StairSide && rotation == neighbor.rotation)
    87.             return false;
    88.  
    89.         if (neighborFace == Face.Triangle && myFace == Face.Triangle && rotation == neighbor.rotation)
    90.             return false;
    91.  
    92.         return true;
    93.     }
    94. }
    95.  
    Chunk.cs
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Chunk : MonoBehaviour {
    6.  
    7.     public static Chunk instance;
    8.  
    9.     public static int size = 16;
    10.     public static int height = 16;
    11.  
    12.     Block [,,] blocks;
    13.     Block [] blocks1D;
    14.  
    15.  
    16.     void Awake () {
    17.  
    18.         instance = this;
    19.     }
    20.  
    21.     // Use this for initialization
    22.     void Start () {
    23.  
    24.         UnityEngine.Profiling.Profiler.BeginSample ("Initializing Voxels");
    25.  
    26.         blocks = new Block[size, height, size];
    27.  
    28.         for (int i = 0; i < size; i++) {
    29.             for (int j = 0; j < height; j++) {
    30.                 for (int k = 0; k < size; k++) {
    31.  
    32.                     blocks [i, j, k] = new Block (new Vector3 (i, j, k), Vector3.zero, Block.Shape.Cube, Block.Material.Filled);
    33.                 }
    34.             }
    35.         }
    36.         UnityEngine.Profiling.Profiler.EndSample ();
    37.  
    38.         UnityEngine.Profiling.Profiler.BeginSample ("Constructing Mesh");
    39.  
    40.         MeshData mesh = new MeshData ();
    41.         mesh.GetMesh (blocks, 0, 0);
    42.  
    43.         UnityEngine.Profiling.Profiler.EndSample ();
    44.  
    45.     }
    46.  
    47.     public Block GetBlockAt (float X, float Y, float Z) {
    48.  
    49.         int x, y, z;
    50.  
    51.         x = Mathf.FloorToInt (X + 0.5f);
    52.         y = Mathf.FloorToInt (Y + 0.5f);
    53.         z = Mathf.FloorToInt (Z + 0.5f);
    54.  
    55.         if (IsPointWithinBounds (x, y, z))
    56.             return blocks [x, y, z];
    57.  
    58.         return null;
    59.     }
    60.  
    61.     bool IsPointWithinBounds (float x, float y, float z) {
    62.  
    63.         return (x >= 0 && x < size) && (y >= 0 && y < height) && (z >= 0 && z < size);
    64.     }
    65. }
    66.  
    I appreciate any help.
     
  2. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Plus a million points for including a profiler shot. Can you fold out the line for MeshData.CreateVoxel? That's where most of your time is taken up (760ms).
     
  3. Missingno525

    Missingno525

    Joined:
    Aug 20, 2014
    Posts:
    17
    Here it is.
    MeshData Create Voxel.PNG
     
  4. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    So the first thing that jumps out at me is the sheer number of operations you are doing. For example you are calling get face for every single face, regardless of if its visible or not. You are calling GetBlockAt more times then you have blocks. And so on.

    There are several basic ways to approach this optimization
    • Make your algorithm more efficient.
    • Microoptimise the stuff that remains. Especially the ones you call frequently.
    • Paralise (or use coroutines) whatever is left to give the appearance of better performance.
    Specifics I would do
    • Give the blocks a transparent/opaque property. Parse your entire chuck to determine which blocks have transparent neighbors. Complete your algorithm only on blocks that have at least one transparent neighbor.
    • Roll your own IntVector3. You are spending a massive amount of time on vector, float and conversion operations.
    Try these out, and once you are done take another profiler snapshot, and post the updated code.
     
  5. Missingno525

    Missingno525

    Joined:
    Aug 20, 2014
    Posts:
    17
    I have done a few micro-optimizations along with what you suggested.

    I am no longer multiplying a quaternion by a vector to rotate a face towards a direction. Instead, there is an enum that represents the direction of the face and the position of the vertices are calculated by hand. I removed the face checking system because it is something I need to re-think.

    Also, the Block class no longer uses Vector3 for position and rotation. I am now using my own Vector3Int to represent position and a Vector2Short to represent rotation. The Vector2Short is being used because blocks can only rotate around two axis (one to flip it upside down and the Y-axis can be rotated in 90 degree increments).

    The chunk class is still the same.

    The changes I made seem a little better. The CreateMesh function takes only ~710ms instead of 785ms. Also, CreateCube takes less than 700ms. What I don't understand is why does CreateSquareFace taking almost 500ms?

    I think the next thing to work on is optimizing the CreateSquareFace function and finding a better solution for getting the visible blocks.
    Profiler After Some Optimizations.PNG

    Here is the new code:
    Code (CSharp):
    1. //Chunk.cs
    2.  
    3. using System.Collections;
    4. using System.Collections.Generic;
    5. using UnityEngine;
    6.  
    7. public class Chunk : MonoBehaviour {
    8.  
    9.     public static Chunk instance;
    10.  
    11.     public static int size = 16;
    12.     public static int height = 16;
    13.  
    14.     MeshData data;
    15.     Vector3Int position;
    16.  
    17.     Block [,,] blocks;
    18.  
    19.     // Use this for initialization
    20.     void Awake () {
    21.  
    22.         data = new MeshData ();
    23.  
    24.         position = new Vector3Int (0, 0, 0);
    25.         instance = this;
    26.     }
    27.  
    28.     void Start () {
    29.  
    30.         ConstructBlockArray ();
    31.  
    32.     }
    33.  
    34.     void ConstructBlockArray () {
    35.  
    36.         blocks = new Block[ size, height, size];
    37.  
    38.         for (int i = 0; i < size; i++) {
    39.             for (int j = 0; j < height; j++) {
    40.                 for (int k = 0; k < size; k++) {
    41.  
    42.                     blocks [i, j, k] = new Block (new Vector3Int (i, j, k), new Vector2Short (0, 0), Block.Shape.Cube, Block.Type.Filled);
    43.                 }
    44.             }
    45.         }
    46.  
    47.         GetComponent<MeshFilter> ().mesh = data.CreateMesh (blocks, position);
    48.     }
    49. }
    50.  
    51. //Block.cs
    52.  
    53. using System.Collections;
    54. using System.Collections.Generic;
    55. using UnityEngine;
    56.  
    57. public class Block {
    58.  
    59.     public Vector3Int position;
    60.     public Vector2Short rotation;
    61.  
    62.     public enum Type { Air, Filled };
    63.     public enum Shape { Cube };
    64.  
    65.     public Type type;
    66.     public Shape shape;
    67.  
    68.     public Block (Vector3Int position, Vector2Short rotation, Shape shape, Type type) {
    69.  
    70.         this.position = position;
    71.         this.rotation = rotation;
    72.         this.shape = shape;
    73.         this.type = type;
    74.  
    75.     }
    76. }
    77.  
    78. //MeshData.cs
    79. using System.Collections;
    80. using System.Collections.Generic;
    81. using UnityEngine;
    82.  
    83. public class MeshData {
    84.  
    85.     List<Vector3> vertices;
    86.     List<Vector2> uvs;
    87.     List<int> triangles;
    88.  
    89.     public MeshData () {
    90.  
    91.         vertices = new List<Vector3> (65000);
    92.         uvs = new List<Vector2> (65000);
    93.         triangles = new List<int> (65000);
    94.     }
    95.  
    96.     public Mesh CreateMesh (Block [,,] blocks, Vector3Int offset) {
    97.  
    98.         int sizeX = blocks.GetLength (0);
    99.         int sizeY = blocks.GetLength (1);
    100.         int sizeZ = blocks.GetLength (2);
    101.  
    102.         for (int i = 0; i < sizeX; i++) {
    103.             for (int j = 0; j < sizeY; j++) {
    104.                 for (int k = 0; k < sizeZ; k++) {
    105.  
    106.                     CreateVoxel (blocks [i, j, k]);
    107.                 }
    108.             }
    109.         }
    110.  
    111.  
    112.         Mesh mesh = new Mesh ();
    113.  
    114.         //NOTE: Until there is a smarter method of hiding faces, this will return an empty mesh
    115.         return mesh;
    116.  
    117.         mesh.SetVertices (vertices);
    118.         mesh.SetUVs (0, uvs);
    119.         mesh.SetTriangles (triangles, 0);
    120.  
    121.         mesh.RecalculateNormals ();
    122.         mesh.RecalculateTangents ();
    123.         mesh.RecalculateBounds ();
    124.  
    125.         return mesh;
    126.  
    127.     }
    128.  
    129.     void CreateVoxel (Block data) {
    130.  
    131.         if (data.shape == Block.Shape.Cube) {
    132.  
    133.             CreateCube (data);
    134.         }
    135.     }
    136.  
    137.     void CreateCube (Block data) {
    138.  
    139.         CreateSquareFace (data.position.x, data.position.y, data.position.z, Direction.Forward);
    140.         CreateSquareFace (data.position.x, data.position.y, data.position.z, Direction.Right);
    141.         CreateSquareFace (data.position.x, data.position.y, data.position.z, Direction.Back);
    142.         CreateSquareFace (data.position.x, data.position.y, data.position.z, Direction.Left);
    143.  
    144.         CreateSquareFace (data.position.x, data.position.y, data.position.z, Direction.Up);
    145.         CreateSquareFace (data.position.x, data.position.y, data.position.z, Direction.Down);
    146.  
    147.     }
    148.  
    149.     void CreateSquareFace (int x, int y, int z, Direction direction) {
    150.  
    151.         if (direction == Direction.Forward) {
    152.  
    153.             vertices.Add (new Vector3 (x - 0.5f, y - 0.5f, z + 0.5f));
    154.             vertices.Add (new Vector3 (x + 0.5f, y - 0.5f, z + 0.5f));
    155.             vertices.Add (new Vector3 (x - 0.5f, y + 0.5f, z + 0.5f));
    156.             vertices.Add (new Vector3 (x + 0.5f, y + 0.5f, z + 0.5f));
    157.  
    158.             triangles.Add (vertices.Count - 4);
    159.             triangles.Add (vertices.Count - 3);
    160.             triangles.Add (vertices.Count - 1);
    161.  
    162.             triangles.Add (vertices.Count - 4);
    163.             triangles.Add (vertices.Count - 1);
    164.             triangles.Add (vertices.Count - 2);
    165.         }
    166.  
    167.         if (direction == Direction.Right) {
    168.  
    169.             vertices.Add (new Vector3 (x + 0.5f, y - 0.5f, z + 0.5f));
    170.             vertices.Add (new Vector3 (x + 0.5f, y - 0.5f, z - 0.5f));
    171.             vertices.Add (new Vector3 (x + 0.5f, y + 0.5f, z + 0.5f));
    172.             vertices.Add (new Vector3 (x + 0.5f, y + 0.5f, z - 0.5f));
    173.  
    174.             triangles.Add (vertices.Count - 4);
    175.             triangles.Add (vertices.Count - 3);
    176.             triangles.Add (vertices.Count - 1);
    177.  
    178.             triangles.Add (vertices.Count - 4);
    179.             triangles.Add (vertices.Count - 1);
    180.             triangles.Add (vertices.Count - 2);
    181.         }
    182.  
    183.         if (direction == Direction.Back) {
    184.  
    185.             vertices.Add (new Vector3 (x - 0.5f, y - 0.5f, z - 0.5f));
    186.             vertices.Add (new Vector3 (x + 0.5f, y - 0.5f, z - 0.5f));
    187.             vertices.Add (new Vector3 (x - 0.5f, y + 0.5f, z - 0.5f));
    188.             vertices.Add (new Vector3 (x + 0.5f, y + 0.5f, z - 0.5f));
    189.  
    190.             triangles.Add (vertices.Count - 1);
    191.             triangles.Add (vertices.Count - 3);
    192.             triangles.Add (vertices.Count - 4);
    193.  
    194.             triangles.Add (vertices.Count - 2);
    195.             triangles.Add (vertices.Count - 1);
    196.             triangles.Add (vertices.Count - 4);
    197.  
    198.         }
    199.  
    200.         if (direction == Direction.Left) {
    201.  
    202.             vertices.Add (new Vector3 (x - 0.5f, y - 0.5f, z + 0.5f));
    203.             vertices.Add (new Vector3 (x - 0.5f, y - 0.5f, z - 0.5f));
    204.             vertices.Add (new Vector3 (x - 0.5f, y + 0.5f, z + 0.5f));
    205.             vertices.Add (new Vector3 (x - 0.5f, y + 0.5f, z - 0.5f));
    206.  
    207.             triangles.Add (vertices.Count - 1);
    208.             triangles.Add (vertices.Count - 3);
    209.             triangles.Add (vertices.Count - 4);
    210.  
    211.             triangles.Add (vertices.Count - 2);
    212.             triangles.Add (vertices.Count - 1);
    213.             triangles.Add (vertices.Count - 4);
    214.         }
    215.  
    216.         if (direction == Direction.Up) {
    217.  
    218.             vertices.Add (new Vector3 (x - 0.5f, y + 0.5f, z + 0.5f));
    219.             vertices.Add (new Vector3 (x - 0.5f, y + 0.5f, z - 0.5f));
    220.             vertices.Add (new Vector3 (x + 0.5f, y + 0.5f, z + 0.5f));
    221.             vertices.Add (new Vector3 (x + 0.5f, y + 0.5f, z - 0.5f));
    222.  
    223.             triangles.Add (vertices.Count - 1);
    224.             triangles.Add (vertices.Count - 3);
    225.             triangles.Add (vertices.Count - 4);
    226.  
    227.             triangles.Add (vertices.Count - 2);
    228.             triangles.Add (vertices.Count - 1);
    229.             triangles.Add (vertices.Count - 4);
    230.         }
    231.  
    232.         if (direction == Direction.Down) {
    233.  
    234.             vertices.Add (new Vector3 (x - 0.5f, y - 0.5f, z + 0.5f));
    235.             vertices.Add (new Vector3 (x - 0.5f, y - 0.5f, z - 0.5f));
    236.             vertices.Add (new Vector3 (x + 0.5f, y - 0.5f, z + 0.5f));
    237.             vertices.Add (new Vector3 (x + 0.5f, y - 0.5f, z - 0.5f));
    238.  
    239.             triangles.Add (vertices.Count - 4);
    240.             triangles.Add (vertices.Count - 3);
    241.             triangles.Add (vertices.Count - 1);
    242.  
    243.             triangles.Add (vertices.Count - 4);
    244.             triangles.Add (vertices.Count - 1);
    245.             triangles.Add (vertices.Count - 2);
    246.         }
    247.     }
    248. }
    249.  
    250. //Custom Structs
    251.  
    252. public struct Vector2Int {
    253.  
    254.     public int x;
    255.     public int y;
    256.  
    257.     public Vector2Int (int x, int y) {
    258.  
    259.         this.x = x;
    260.         this.y = y;
    261.     }
    262. }
    263.  
    264. public struct Vector2Short {
    265.  
    266.     public short x;
    267.     public short y;
    268.  
    269.     public Vector2Short (short x, short y) {
    270.  
    271.         this.x = x;
    272.         this.y = y;
    273.     }
    274. }
    275.  
    276. public struct Vector3Int {
    277.  
    278.     public int x;
    279.     public int y;
    280.     public int z;
    281.  
    282.     public Vector3Int (int x, int y, int z) {
    283.  
    284.         this.x = x;
    285.         this.y = y;
    286.         this.z = z;
    287.     }
    288. }
    289.  
    290.  
    291. //Custom enums
    292. public enum Direction { Forward, Back, Left, Right, Up, Down };
    293.  
     
  6. Missingno525

    Missingno525

    Joined:
    Aug 20, 2014
    Posts:
    17
    I had a hunch that I should try replacing the lists with fixed arrays, and to my surprise, it actually made it even better. Now, CreateSquareFace takes less than 150ms. Also, since my tests had the array much larger than the vertex limit (due to there currently being no system that hides invisible faces), I assume this is going to be much faster as time goes on. The only thing that worries me with this method is now chunks will always have the maximum number of vertices possible unless I copy the data to an array that is the perfect fit...

    P.S. I am beginning to think that StudentGameDev wasn't a good tutorial on how to make a voxel terrain...
     
    Kiwasi likes this.
  7. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    You are still calling this for every single face in the system. There really is no need to do this. Only call CreateSquareFace on blocks that are not empty and have at least one transparent neighbor.

    That alone will probably do more to optimize this algorithm then anything else you can try.
     
  8. Missingno525

    Missingno525

    Joined:
    Aug 20, 2014
    Posts:
    17
    I finally got it working much better. Instead of calling the GetBlockAt function every time I need to know if a face is visible, I made it so, while initializing the voxel array, I find the visible faces. The visible faces are stored in a byte where each bit represents if a face can be seen or not. For example, 0 0 1 1 1 1 0 0 means that a voxel is visible on all the sides, but not top or bottom. Now, CanSeeFace just compares the bit that represents the face instead of a convoluted method.

    Profiler After Some More Optimizations.PNG

    There are still a few things I can do. The mesh data only needs to render visible faces, so I can copy the visible blocks to a new array, so the CreateMesh function no longer iterates through all the voxels. But now that it runs at 50~200ms (depending on if the chunk is completely solid, Swiss cheese, or mostly air). In fact, these measurements were taken using the profiler in Deep Profile mode, which takes a lot of resources, so the actual performance is even better.

    Here is the code in case anyone wants to see exactly what I did:

    Block.cs

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Block {
    6.  
    7.     public Vector3Int position;
    8.     public Vector2Short rotation;
    9.  
    10.     public enum Type { Air, Filled };
    11.     public enum Shape { Cube };
    12.  
    13.     public Type type;
    14.     public Shape shape;
    15.  
    16.     public bool visible {
    17.  
    18.         get {
    19.  
    20.             if (_edgeOfChunk)
    21.                 return true;
    22.  
    23.             if (visibleSides != 0)
    24.                 return true;
    25.  
    26.  
    27.             return false;
    28.  
    29.         }
    30.     }
    31.  
    32.     bool _edgeOfChunk;
    33.  
    34.     //<summary>
    35.     //Visible sides is a binary value that represents what can be seen
    36.     //Binary: x x N E S W T B
    37.     //So...   x x 0 0 0 0 1 1 means it can be seen from top and bottom
    38.     //</summary>
    39.     byte visibleSides;
    40.  
    41.     public Block (Vector3Int position, Vector2Short rotation, Shape shape, Type type, bool edge) {
    42.  
    43.         this.position = position;
    44.         this.rotation = rotation;
    45.         this.shape = shape;
    46.         this.type = type;
    47.  
    48.         _edgeOfChunk = edge;
    49.     }
    50.  
    51.     public void SetVisible (Direction direction) {
    52.  
    53.  
    54.         if (direction == Direction.Forward) {
    55.  
    56.             visibleSides |= 32;
    57.         }
    58.  
    59.         if (direction == Direction.Right) {
    60.  
    61.             visibleSides |= 16;
    62.         }
    63.  
    64.         if (direction == Direction.Back) {
    65.  
    66.             visibleSides |= 8;
    67.         }
    68.  
    69.         if (direction == Direction.Left) {
    70.  
    71.             visibleSides |= 4;
    72.         }
    73.  
    74.         if (direction == Direction.Up) {
    75.  
    76.             visibleSides |= 2;
    77.         }
    78.  
    79.         if (direction == Direction.Down) {
    80.  
    81.             visibleSides |= 1;
    82.         }
    83.     }
    84.  
    85.     public bool CanSeeFace (Direction direction) {
    86.  
    87.         if (direction == Direction.Forward && (visibleSides & 32) == 32) {
    88.  
    89.             return true;
    90.         }
    91.         if (direction == Direction.Right && (visibleSides & 16) == 16) {
    92.  
    93.             return true;
    94.         }
    95.         if (direction == Direction.Back && (visibleSides & 8) == 8) {
    96.  
    97.             return true;
    98.         }
    99.         if (direction == Direction.Left && (visibleSides & 4) == 4) {
    100.  
    101.             return true;
    102.         }
    103.         if (direction == Direction.Up && (visibleSides & 2) == 2) {
    104.  
    105.             return true;
    106.         }
    107.         if (direction == Direction.Down && (visibleSides & 1) == 1) {
    108.  
    109.             return true;
    110.         }
    111.  
    112.  
    113.         return false;
    114.     }
    115.  
    116.    
    117. }
    118.  
    Vector3Int.cs

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public struct Vector3Int {
    6.  
    7.     public int x;
    8.     public int y;
    9.     public int z;
    10.  
    11.     public Vector3Int (int x, int y, int z) {
    12.  
    13.         this.x = x;
    14.         this.y = y;
    15.         this.z = z;
    16.     }
    17.  
    18.     public bool IsEqual (Vector3Int rhs) {
    19.  
    20.         return rhs.x == x && rhs.y == y && rhs.z == z;
    21.     }
    22.  
    23.     public override string ToString ()
    24.     {
    25.         return "( " + x + ", " + y + ", " + z + ")";
    26.     }
    27. }
    28.  
    Chunk.cs
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Chunk : MonoBehaviour {
    6.  
    7.     public static int size = 16;
    8.     public static int height = 16;
    9.  
    10.     public MeshFilter filter;
    11.  
    12.     MeshData data;
    13.     Vector3Int position;
    14.  
    15.     Block [] blocks;
    16.  
    17.     // Use this for initialization
    18.     void Awake () {
    19.  
    20.         data = new MeshData ();
    21.  
    22.     }
    23.  
    24.     public void CreateChunk (Vector3Int position) {
    25.  
    26.         this.position = position;
    27.  
    28.         blocks = new Block[ size * height * size];
    29.  
    30.         InitializeVoxelArray ();
    31.         ConstructMesh ();
    32.     }
    33.  
    34.     void InitializeVoxelArray () {
    35.  
    36.         int[] airBlockIndices = new int[size * size * height];
    37.         int numAirBlocks = 0;
    38.  
    39.         for (int i = 0; i < size; i++) {
    40.             for (int j = 0; j < height; j++) {
    41.                 for (int k = 0; k < size; k++) {
    42.  
    43.                     int index = i * size * height + j * height + k;
    44.  
    45.                     bool onEdge = false;
    46.  
    47.                     if (i == 0 || j == 0 || k == 0 || i == size - 1 || j == height - 1 || k == size - 1) {
    48.  
    49.                         onEdge = true;
    50.                     }
    51.  
    52.                     blocks [index] = new Block (
    53.                         new Vector3Int (i + position.x, j + position.y, k + position.z),
    54.                         new Vector2Short (0, 0),
    55.                         Block.Shape.Cube, Block.Type.Filled,
    56.                         onEdge
    57.                     );
    58.  
    59.                     if (blocks [index].type == Block.Type.Air) {
    60.  
    61.                         airBlockIndices [numAirBlocks] = index;
    62.                         numAirBlocks++;
    63.                     }
    64.  
    65.                     if (onEdge) {
    66.  
    67.                         if (i == 0) {
    68.  
    69.                             blocks [index].SetVisible (Direction.Left);
    70.                         }
    71.  
    72.                         if (i == height - 1) {
    73.  
    74.                             blocks [index].SetVisible (Direction.Right);
    75.                         }
    76.  
    77.                         if (j == 0) {
    78.  
    79.                             blocks [index].SetVisible (Direction.Down);
    80.                         }
    81.  
    82.                         if (j == height - 1) {
    83.  
    84.                             blocks [index].SetVisible (Direction.Up);
    85.                         }
    86.  
    87.                         if (k == 0) {
    88.  
    89.                             blocks [index].SetVisible (Direction.Back);
    90.                         }
    91.  
    92.                         if (k == height - 1) {
    93.  
    94.                             blocks [index].SetVisible (Direction.Forward);
    95.                         }
    96.                     }
    97.                 }
    98.             }
    99.         }
    100.  
    101.         for (int i = 0; i < numAirBlocks; i++) {
    102.            
    103.             SetVisibilityAroundAir (airBlockIndices [i]);
    104.         }
    105.     }
    106.  
    107.     void SetVisibilityAroundAir (int index) {
    108.  
    109.         int max = size * size * height;
    110.  
    111.         if (index + 1 < max) {
    112.  
    113.             blocks [index + 1].SetVisible (Direction.Back);
    114.         }
    115.  
    116.         if (index - 1 >= 0) {
    117.  
    118.             blocks [index - 1].SetVisible (Direction.Forward);
    119.         }
    120.  
    121.         if (index + size < max) {
    122.  
    123.             blocks [index + size].SetVisible (Direction.Down);
    124.         }
    125.  
    126.         if (index - size >= 0) {
    127.  
    128.             blocks [index - size].SetVisible (Direction.Up);
    129.         }
    130.  
    131.         if (index + size * height < max) {
    132.  
    133.             blocks [index + size * height].SetVisible (Direction.Left);
    134.         }
    135.  
    136.         if (index - size * height >= 0) {
    137.  
    138.             blocks [index - size * height].SetVisible (Direction.Right);
    139.         }
    140.     }
    141.  
    142.     void ConstructMesh () {
    143.  
    144.         filter.mesh = data.CreateMesh (blocks);
    145.  
    146.     }
    147.  
    148.     public Block GetBlockAt (int x, int y, int z) {
    149.  
    150.         x -= position.x;
    151.         y -= position.y;
    152.         z -= position.z;
    153.  
    154.         if (IsPointWithinBounds (x, y, z))
    155.             return blocks [x * size * height + y * height + z];
    156.  
    157.         return World.instance.GetBlockAt (x, y, z);
    158.     }
    159.  
    160.     public Block[] GetNeighbors (Block block) {
    161.  
    162.         Block[] neighbors = new Block [6];
    163.  
    164.         neighbors [0] = GetBlockAt (block.position.x, block.position.y, block.position.z + 1);
    165.         neighbors [1] = GetBlockAt (block.position.x, block.position.y, block.position.z - 1);
    166.         neighbors [2] = GetBlockAt (block.position.x + 1, block.position.y, block.position.z);
    167.         neighbors [3] = GetBlockAt (block.position.x - 1, block.position.y, block.position.z);
    168.  
    169.         neighbors [4] = GetBlockAt (block.position.x, block.position.y + 1, block.position.z);
    170.         neighbors [5] = GetBlockAt (block.position.x, block.position.y - 1, block.position.z);
    171.  
    172.         return neighbors;
    173.     }
    174.  
    175.     public bool IsPointWithinBounds (int x, int y, int z) {
    176.  
    177.         //Is it not out of bounds?
    178.         return !(x >= size || x < 0 || y >= height || y < 0 || z >= size || z < 0);
    179.     }
    180. }
    181.  
    MeshData.cs

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class MeshData {
    6.  
    7.     Vector3[] vertices;
    8.     Vector3[] normals;
    9.  
    10.     Vector2[] uvs;
    11.     int[] triangles;
    12.  
    13.     int vertexCount = 0;
    14.     int triangleCount = 0;
    15.     int normalCount = 0;
    16.  
    17.     public MeshData () {
    18.  
    19.         vertices = new Vector3[65535];
    20.         normals = new Vector3[65535];
    21.  
    22.         uvs = new Vector2[65535];
    23.         triangles = new int[65535];
    24.  
    25.         vertexCount = 0;
    26.         triangleCount = 0;
    27.         normalCount = 0;
    28.     }
    29.  
    30.     public Mesh CreateMesh (Block [] blocks) {
    31.  
    32.         int size = blocks.Length;
    33.  
    34.         for (int i = 0; i < size; i++) {
    35.  
    36.             if (blocks [i].type == Block.Type.Air || blocks [i].visible == false)
    37.                 continue;
    38.  
    39.             CreateVoxel (blocks [i]);
    40.         }
    41.  
    42.  
    43.         Mesh mesh = new Mesh ();
    44.  
    45.         mesh.vertices = vertices;
    46.         mesh.normals = normals;
    47.  
    48.         mesh.uv = uvs;
    49.         mesh.triangles = triangles;
    50.  
    51.         mesh.RecalculateBounds ();
    52.  
    53.         return mesh;
    54.  
    55.     }
    56.  
    57.     void CreateVoxel (Block data) {
    58.  
    59.         if (data.shape == Block.Shape.Cube) {
    60.  
    61.             CreateCube (data);
    62.         }
    63.     }
    64.  
    65.     void CreateCube (Block data) {
    66.  
    67.         if (data.CanSeeFace (Direction.Forward))
    68.             CreateSquareFace (data.position.x, data.position.y, data.position.z, Direction.Forward);
    69.        
    70.         if (data.CanSeeFace (Direction.Right))
    71.             CreateSquareFace (data.position.x, data.position.y, data.position.z, Direction.Right);
    72.        
    73.         if (data.CanSeeFace (Direction.Back))
    74.             CreateSquareFace (data.position.x, data.position.y, data.position.z, Direction.Back);
    75.        
    76.         if (data.CanSeeFace (Direction.Left))
    77.             CreateSquareFace (data.position.x, data.position.y, data.position.z, Direction.Left);
    78.  
    79.         if (data.CanSeeFace (Direction.Up))
    80.             CreateSquareFace (data.position.x, data.position.y, data.position.z, Direction.Up);
    81.        
    82.         if (data.CanSeeFace (Direction.Down))
    83.             CreateSquareFace (data.position.x, data.position.y, data.position.z, Direction.Down);
    84.  
    85.     }
    86.  
    87.     void CreateSquareFace (int x, int y, int z, Direction direction) {
    88.  
    89.         if (direction == Direction.Forward) {
    90.  
    91.             vertices [vertexCount] = new Vector3 (x - 0.5f, y - 0.5f, z + 0.5f);
    92.             vertices [vertexCount + 1] = new Vector3 (x + 0.5f, y - 0.5f, z + 0.5f);
    93.             vertices [vertexCount + 2] = new Vector3 (x - 0.5f, y + 0.5f, z + 0.5f);
    94.             vertices [vertexCount + 3] = new Vector3 (x + 0.5f, y + 0.5f, z + 0.5f);
    95.  
    96.             vertexCount += 4;
    97.  
    98.             normals [normalCount] = Vector3.forward;
    99.             normals [normalCount + 1] = Vector3.forward;
    100.             normals [normalCount + 2] = Vector3.forward;
    101.             normals [normalCount + 3] = Vector3.forward;
    102.  
    103.             normalCount += 4;
    104.  
    105.             triangles [triangleCount] = vertexCount - 4;
    106.             triangles [triangleCount + 1] = vertexCount - 3;
    107.             triangles [triangleCount + 2] = vertexCount - 1;
    108.  
    109.             triangles [triangleCount + 3] = vertexCount - 4;
    110.             triangles [triangleCount + 4] = vertexCount - 1;
    111.             triangles [triangleCount + 5] = vertexCount - 2;
    112.  
    113.             triangleCount += 6;
    114.         }
    115.  
    116.         if (direction == Direction.Back) {
    117.  
    118.             vertices [vertexCount] = new Vector3 (x - 0.5f, y - 0.5f, z - 0.5f);
    119.             vertices [vertexCount + 1] = new Vector3 (x + 0.5f, y - 0.5f, z - 0.5f);
    120.             vertices [vertexCount + 2] = new Vector3 (x - 0.5f, y + 0.5f, z - 0.5f);
    121.             vertices [vertexCount + 3] = new Vector3 (x + 0.5f, y + 0.5f, z - 0.5f);
    122.  
    123.             vertexCount += 4;
    124.  
    125.             normals [normalCount] = Vector3.back;
    126.             normals [normalCount + 1] = Vector3.back;
    127.             normals [normalCount + 2] = Vector3.back;
    128.             normals [normalCount + 3] = Vector3.back;
    129.  
    130.             normalCount += 4;
    131.  
    132.             triangles [triangleCount] = vertexCount - 1;
    133.             triangles [triangleCount + 1] = vertexCount - 3;
    134.             triangles [triangleCount + 2] = vertexCount - 4;
    135.  
    136.             triangles [triangleCount + 3] = vertexCount - 2;
    137.             triangles [triangleCount + 4] = vertexCount - 1;
    138.             triangles [triangleCount + 5] = vertexCount - 4;
    139.  
    140.             triangleCount += 6;
    141.         }
    142.  
    143.  
    144.         if (direction == Direction.Right) {
    145.  
    146.             vertices [vertexCount] = new Vector3 (x + 0.5f, y - 0.5f, z + 0.5f);
    147.             vertices [vertexCount + 1] = new Vector3 (x + 0.5f, y - 0.5f, z - 0.5f);
    148.             vertices [vertexCount + 2] = new Vector3 (x + 0.5f, y + 0.5f, z + 0.5f);
    149.             vertices [vertexCount + 3] = new Vector3 (x + 0.5f, y + 0.5f, z - 0.5f);
    150.  
    151.             vertexCount += 4;
    152.  
    153.             normals [normalCount] = Vector3.right;
    154.             normals [normalCount + 1] = Vector3.right;
    155.             normals [normalCount + 2] = Vector3.right;
    156.             normals [normalCount + 3] = Vector3.right;
    157.  
    158.             normalCount += 4;
    159.  
    160.             triangles [triangleCount] = vertexCount - 4;
    161.             triangles [triangleCount + 1] = vertexCount - 3;
    162.             triangles [triangleCount + 2] = vertexCount - 1;
    163.  
    164.             triangles [triangleCount + 3] = vertexCount - 4;
    165.             triangles [triangleCount + 4] = vertexCount - 1;
    166.             triangles [triangleCount + 5] = vertexCount - 2;
    167.  
    168.             triangleCount += 6;
    169.         }
    170.  
    171.         if (direction == Direction.Left) {
    172.  
    173.             vertices [vertexCount] = new Vector3 (x - 0.5f, y - 0.5f, z + 0.5f);
    174.             vertices [vertexCount + 1] = new Vector3 (x - 0.5f, y - 0.5f, z - 0.5f);
    175.             vertices [vertexCount + 2] = new Vector3 (x - 0.5f, y + 0.5f, z + 0.5f);
    176.             vertices [vertexCount + 3] = new Vector3 (x - 0.5f, y + 0.5f, z - 0.5f);
    177.  
    178.             vertexCount += 4;
    179.  
    180.             normals [normalCount] = Vector3.left;
    181.             normals [normalCount + 1] = Vector3.left;
    182.             normals [normalCount + 2] = Vector3.left;
    183.             normals [normalCount + 3] = Vector3.left;
    184.  
    185.             normalCount += 4;
    186.  
    187.             triangles [triangleCount] = vertexCount - 1;
    188.             triangles [triangleCount + 1] = vertexCount - 3;
    189.             triangles [triangleCount + 2] = vertexCount - 4;
    190.  
    191.             triangles [triangleCount + 3] = vertexCount - 2;
    192.             triangles [triangleCount + 4] = vertexCount - 1;
    193.             triangles [triangleCount + 5] = vertexCount - 4;
    194.  
    195.             triangleCount += 6;
    196.         }
    197.  
    198.         if (direction == Direction.Up) {
    199.  
    200.             vertices [vertexCount] = new Vector3 (x - 0.5f, y + 0.5f, z + 0.5f);
    201.             vertices [vertexCount + 1] = new Vector3 (x - 0.5f, y + 0.5f, z - 0.5f);
    202.             vertices [vertexCount + 2] = new Vector3 (x + 0.5f, y + 0.5f, z + 0.5f);
    203.             vertices [vertexCount + 3] = new Vector3 (x + 0.5f, y + 0.5f, z - 0.5f);
    204.  
    205.             vertexCount += 4;
    206.  
    207.             normals [normalCount] = Vector3.up;
    208.             normals [normalCount + 1] = Vector3.up;
    209.             normals [normalCount + 2] = Vector3.up;
    210.             normals [normalCount + 3] = Vector3.up;
    211.  
    212.             normalCount += 4;
    213.  
    214.             triangles [triangleCount] = vertexCount - 1;
    215.             triangles [triangleCount + 1] = vertexCount - 3;
    216.             triangles [triangleCount + 2] = vertexCount - 4;
    217.  
    218.             triangles [triangleCount + 3] = vertexCount - 2;
    219.             triangles [triangleCount + 4] = vertexCount - 1;
    220.             triangles [triangleCount + 5] = vertexCount - 4;
    221.  
    222.             triangleCount += 6;
    223.         }
    224.  
    225.         if (direction == Direction.Down) {
    226.  
    227.             vertices [vertexCount] = new Vector3 (x - 0.5f, y - 0.5f, z + 0.5f);
    228.             vertices [vertexCount + 1] = new Vector3 (x - 0.5f, y - 0.5f, z - 0.5f);
    229.             vertices [vertexCount + 2] = new Vector3 (x + 0.5f, y - 0.5f, z + 0.5f);
    230.             vertices [vertexCount + 3] = new Vector3 (x + 0.5f, y - 0.5f, z - 0.5f);
    231.  
    232.             vertexCount += 4;
    233.  
    234.             normals [normalCount] = Vector3.down;
    235.             normals [normalCount + 1] = Vector3.down;
    236.             normals [normalCount + 2] = Vector3.down;
    237.             normals [normalCount + 3] = Vector3.down;
    238.  
    239.             normalCount += 4;
    240.  
    241.             triangles [triangleCount] = vertexCount - 4;
    242.             triangles [triangleCount + 1] = vertexCount - 3;
    243.             triangles [triangleCount + 2] = vertexCount - 1;
    244.  
    245.             triangles [triangleCount + 3] = vertexCount - 4;
    246.             triangles [triangleCount + 4] = vertexCount - 1;
    247.             triangles [triangleCount + 5] = vertexCount - 2;
    248.  
    249.             triangleCount += 6;
    250.         }
    251.  
    252.  
    253.     }
    254.  
    255.  
    256. }
    257.  
     
    Kiwasi likes this.
  9. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    0. The Block type has to be a value-type: an enum or a struct. If it's a struct it shouldn't contain anything other that the block's type. At least not its position and rotation, because it's a waste of memory -> reduces data locality -> increases the number of CPU cache misses -> slows down the calculations.

    Code (CSharp):
    1. public enum Block : byte { Air = 0, Stone, Sand, Soil }
    2.  
    3. public static class BlockExtensions
    4. {
    5.     public static bool IsVisible(this Block block)
    6.     {
    7.         return block != Block.Air;
    8.     }
    9. }
    This should drastically speed up the memory access.

    1. Once you have created a chunk, remember if it has any visible faces and remember the range of Y-positions where the visible faces are, so that the next time you do the same job, you could skip the blocks that don't produce visible faces.
     
    Kiwasi and Missingno525 like this.
  10. Missingno525

    Missingno525

    Joined:
    Aug 20, 2014
    Posts:
    17
    Where would it be appropriate to store the rotation, shape, and position data? I could have that data stored in arrays in the Chunk class, but I don't think it would be helpful to have to call the a chunk every time I need that information.

    Edit: I tested your suggestion and I put an array of bytes in the Chunk class to represent visible faces. The mesh generation algorithm takes only 6~20ms to create a single 16x16x16 chunk, which is crazy fast compared to what it was a few days ago.

    I am using the position of the block in the array as its position. The visible faces is stored in an a second array. For now, I will keep rotation data and shape data in separate arrays stored in the chunk class unless someone can point me towards a better solution.
     
    Last edited: Aug 14, 2017
  11. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    1. Rotation.
    Can't say anything about the rotation, I don't know what it means and how it's used, if used.

    2. Shape.
    First,
    Code (CSharp):
    1. public enum Type { Air, Filled };
    By default the underlying type for enums is Int32 that consumes 4 bytes, 3 of which are not used at all. Use Byte for the underlying type:
    Code (CSharp):
    1. public enum Type : byte { Air, Filled };
    Second, I doubt you need the whole byte to store just the block type. You can use individual bits for storing different data.

    3. Position.
    You already know the position:
    Code (CSharp):
    1. public Mesh CreateMesh(Block[] blocks)
    2. {
    3.    for (int z = 0; z < CHUNK_WIDTH; z++)
    4.     {
    5.         for (int y = 0; y < CHUNK_HEIGHT; y++)
    6.         {
    7.            for (int x = 0; x < CHUNK_WIDTH; x++)
    8.             {
    9.                 var index = z * CHUNK_WIDTH * CHUNK_HEIGHT + y * CHUNK_HEIGHT + x;
    10.                 var block = blocks[index];
    11.  
    12.                 if (block.type == Block.Type.Air || block.visible == false)
    13.                 {
    14.                     continue;
    15.                 }
    16.  
    17.                 var blockPosition = CHUNK_POSITION + new Vector3Int(x, y, z);
    18.                 CreateVoxel(block, blockPosition);
    19.             }
    20.         }
    21.     }
    22.  
    23.     ...
    24. }
    25.  
     
    Last edited: Aug 15, 2017
    Missingno525 and Kiwasi like this.
  12. Missingno525

    Missingno525

    Joined:
    Aug 20, 2014
    Posts:
    17
    I am using rotation to rotate blocks that can face different directions. For example, stairs and slopes may be placed upside down and they can have the incline be in different directions.

    I think I know what you are saying about the shape. I can just have something like Wood, Wood Stairs, and other shapes you may make a wooden block into.

    Once I get off work I will try it and post my results.
     
  13. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    If its mine craft style rotations you could probably represent it in a few bits.

    If my math is right there are 24 unique rotations. Which means you could represent it in 5 bits. So a single byte with some space left over.
     
  14. Missingno525

    Missingno525

    Joined:
    Aug 20, 2014
    Posts:
    17
    I have implemented your suggestions, alexzzzz. However, instead of an enum being used for the block, I gave the chunk an array of ushorts and there is a static class that interprets the bits in the ushort to get and set rotation, shape, and type data. Also, I decided not to use a struct because I would have to keep dereferencing to get the data.

    Probably when I am done, I will do a YouTube tutorial on procedurally creating a mesh because the tutorials I looked at really pointed me in the wrong direction. Anyway, thanks for the help!

    By type I mean Air, Stone, Dirt, etc.
     
  15. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    Structs are value-types like ushorts and enums. There are no extra references.
     
    Kiwasi likes this.
  16. Missingno525

    Missingno525

    Joined:
    Aug 20, 2014
    Posts:
    17
    Here is the code and a profiler snapshot. I have moved the ushort into a struct named Block. Using Time.realtimeSinceStartup, I can gauge the actual performance. If it's an empty chunk, it will only take 2~3ms. If none were created, it skips everything and gives the mesh filter an empty mesh. If half the blocks and air and half the blocks are solid, it looks like it would take about 10~30ms for creation. Meanwhile, a completely solid chunk takes between 4~20ms.
    Profiler after Block class is now struct.PNG

    World.cs
    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;

    public class World : MonoBehaviour {

    public GameObject chunk;
    public static int worldHeight = 1;

    // Use this for initialization
    void Start () {

    for (int i = 0; i < worldHeight; i++) {

    GameObject clone = Instantiate (chunk, this.transform);
    Chunk chunkScript = clone.GetComponent<Chunk> ();
    chunkScript.BuildChunk (new Vector3Int (0, i * Chunk.size, 0));
    }
    }

    // Update is called once per frame
    void Update () {

    }
    }


    Chunk.cs
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Chunk : MonoBehaviour {
    6.  
    7.     public static Chunk instance;
    8.  
    9.     public static int size = 15;
    10.     public static int height = 15;
    11.  
    12.     public MeshFilter filter;
    13.     public Vector3Int position;
    14.  
    15.     Block [] blocks;
    16.     byte [] faces;
    17.  
    18.     // Use this for initialization
    19.     void Awake () {
    20.  
    21.         instance = this;
    22.     }
    23.  
    24.     public void BuildChunk (Vector3Int position) {
    25.  
    26.         this.position = position;
    27.  
    28.         InitializeBlockData ();
    29.         CreateChunkMesh ();
    30.     }
    31.  
    32.     void InitializeBlockData () {
    33.  
    34.         int numAirBlocks = 0;
    35.         int[] airBlockIndices = new int[size * height * size];
    36.  
    37.         blocks = new Block[size * height * size];
    38.         faces = new byte[size * height * size];
    39.  
    40.         int index = 0;
    41.  
    42.         for (int x = 0; x < size; x++) {
    43.             for (int y = 0; y < height; y++) {
    44.                 for (int z = 0; z < size; z++) {
    45.  
    46.                     if (Random.Range (0, 2) == 0) {
    47.                         blocks [index] = blocks [index].Create (BlockType.Air);
    48.  
    49.                         airBlockIndices [numAirBlocks] = index;
    50.                         numAirBlocks++;
    51.                     }
    52.                     else
    53.                         blocks[index] = blocks [index].Create (BlockType.Filled);
    54.  
    55.  
    56.  
    57.                     if (x == 0)
    58.                         faces[index] |= (byte)Direction.Left;
    59.                    
    60.  
    61.                     if (x == size - 1)
    62.                         faces[index] |= (byte)Direction.Right;
    63.                    
    64.  
    65.                     if (y == 0)
    66.                         faces[index] |= (byte)Direction.Down;
    67.                    
    68.  
    69.                     if (y == height - 1)
    70.                         faces[index] |= (byte)Direction.Up;
    71.                    
    72.  
    73.                     if (z == 0)
    74.                         faces[index] |= (byte)Direction.Back;
    75.                    
    76.  
    77.                     if (z == size - 1)
    78.                         faces[index] |= (byte)Direction.Forward;
    79.                    
    80.  
    81.                     index++;
    82.                 }
    83.             }
    84.         }
    85.  
    86.         index = 0;
    87.  
    88.         int max = size * height * size;
    89.  
    90.         for (int i = 0; i < numAirBlocks; i++) {
    91.  
    92.             index = airBlockIndices [i];
    93.  
    94.             if (index - 1 >= 0) {
    95.                 faces [index - 1] |= (byte)Direction.Forward;
    96.             }
    97.             if (index + 1 < max) {
    98.                 faces [index + 1] |= (byte)Direction.Back;
    99.             }
    100.  
    101.             if (index - size >= 0) {
    102.                 faces [index - size] |= (byte)Direction.Up;
    103.             }
    104.             if (index + size < max) {
    105.                 faces [index + size] |= (byte)Direction.Down;
    106.             }
    107.  
    108.             if (index - size * height >= 0) {
    109.                 faces [index - size * height] |= (byte)Direction.Right;
    110.             }
    111.             if (index + size * height < max) {
    112.                 faces [index + size * height] |= (byte)Direction.Left;
    113.             }
    114.  
    115.             index++;
    116.         }
    117.     }
    118.  
    119.     void CreateChunkMesh () {
    120.  
    121.         MeshData data = new MeshData ();
    122.         filter.mesh = data.CreateMesh (blocks, faces, position);
    123.     }
    124. }
    125.  
    MeshData.cs
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class MeshData {
    6.  
    7.     Vector3[] vertices;
    8.     Vector3[] normals;
    9.  
    10.     Vector2[] uvs;
    11.     int[] triangles;
    12.  
    13.     int vertexCount = 0;
    14.     int triangleCount = 0;
    15.     int normalCount = 0;
    16.  
    17.  
    18.     public MeshData () {
    19.  
    20.         vertices = new Vector3[65535];
    21.         normals = new Vector3[65535];
    22.  
    23.         uvs = new Vector2[65535];
    24.         triangles = new int[65535];
    25.  
    26.         vertexCount = 0;
    27.         triangleCount = 0;
    28.         normalCount = 0;
    29.  
    30.     }
    31.  
    32.     public Mesh CreateMesh (Block [] data, byte[] faces, Vector3Int offset) {
    33.  
    34.         int index = 0;
    35.  
    36.         for (int x = 0; x < Chunk.size; x++) {
    37.             for (int y = 0; y < Chunk.height; y++) {
    38.                 for (int z = 0; z < Chunk.size; z++) {
    39.  
    40.                     if (data [index].IsVisible () == false) {
    41.                         index++;
    42.                         continue;
    43.                     }
    44.  
    45.                     Vector3Int position = new Vector3Int (offset.x + x, offset.y + y, offset.z + z);
    46.                     CreateVoxel (ref data [index], ref faces [index], position);
    47.  
    48.                     index++;
    49.                 }
    50.             }
    51.         }
    52.  
    53.         Mesh mesh = new Mesh ();
    54.  
    55.         Vector3[] fittedVertices = new Vector3[vertexCount];
    56.         Vector3[] fittedNormals = new Vector3[vertexCount];
    57.         Vector2[] fittedUVs = new Vector2[vertexCount];
    58.  
    59.         for (int i = 0; i < vertexCount; i++) {
    60.  
    61.             fittedVertices [i] = vertices [i];
    62.             fittedNormals [i] = normals [i];
    63.         }
    64.  
    65.         int[] fittedTris = new int [triangleCount];
    66.  
    67.         for (int i = 0; i < triangleCount; i++) {
    68.  
    69.             fittedTris [i] = triangles [i];
    70.         }
    71.  
    72.         mesh.vertices = fittedVertices;
    73.         mesh.normals = fittedNormals;
    74.  
    75.         mesh.uv = fittedUVs;
    76.         mesh.triangles = fittedTris;
    77.  
    78.         mesh.RecalculateBounds ();
    79.  
    80.         return mesh;
    81.  
    82.     }
    83.  
    84.     void CreateVoxel (ref Block data, ref byte face, Vector3Int position) {
    85.  
    86.         CreateCube (ref data, ref face, position);
    87.     }
    88.  
    89.     void CreateCube (ref Block data, ref byte face, Vector3Int position) {
    90.  
    91.         if ((face & (byte)Direction.Forward) != 0) {
    92.  
    93.             CreateSquareFace (position.x, position.y, position.z, Direction.Forward);
    94.         }
    95.         if ((face & (byte)Direction.Right) != 0) {
    96.  
    97.             CreateSquareFace (position.x, position.y, position.z, Direction.Right);
    98.         }
    99.         if ((face & (byte)Direction.Back) != 0) {
    100.  
    101.             CreateSquareFace (position.x, position.y, position.z, Direction.Back);
    102.         }
    103.         if ((face & (byte)Direction.Left) != 0) {
    104.  
    105.             CreateSquareFace (position.x, position.y, position.z, Direction.Left);
    106.         }
    107.  
    108.         if ((face & (byte)Direction.Up) != 0) {
    109.  
    110.             CreateSquareFace (position.x, position.y, position.z, Direction.Up);
    111.         }
    112.         if ((face & (byte)Direction.Down) != 0) {
    113.  
    114.             CreateSquareFace (position.x, position.y, position.z, Direction.Down);
    115.         }
    116.  
    117.     }
    118.  
    119.     void CreateSquareFace (int x, int y, int z, Direction direction) {
    120.  
    121.         if (direction == Direction.Forward) {
    122.  
    123.             vertices [vertexCount] = new Vector3 (x - 0.5f, y - 0.5f, z + 0.5f);
    124.             vertices [vertexCount + 1] = new Vector3 (x + 0.5f, y - 0.5f, z + 0.5f);
    125.             vertices [vertexCount + 2] = new Vector3 (x - 0.5f, y + 0.5f, z + 0.5f);
    126.             vertices [vertexCount + 3] = new Vector3 (x + 0.5f, y + 0.5f, z + 0.5f);
    127.  
    128.             vertexCount += 4;
    129.  
    130.             normals [normalCount] = Vector3.forward;
    131.             normals [normalCount + 1] = Vector3.forward;
    132.             normals [normalCount + 2] = Vector3.forward;
    133.             normals [normalCount + 3] = Vector3.forward;
    134.  
    135.             normalCount += 4;
    136.  
    137.             triangles [triangleCount] = vertexCount - 4;
    138.             triangles [triangleCount + 1] = vertexCount - 3;
    139.             triangles [triangleCount + 2] = vertexCount - 1;
    140.  
    141.             triangles [triangleCount + 3] = vertexCount - 4;
    142.             triangles [triangleCount + 4] = vertexCount - 1;
    143.             triangles [triangleCount + 5] = vertexCount - 2;
    144.  
    145.             triangleCount += 6;
    146.         }
    147.  
    148.         if (direction == Direction.Back) {
    149.  
    150.             vertices [vertexCount] = new Vector3 (x - 0.5f, y - 0.5f, z - 0.5f);
    151.             vertices [vertexCount + 1] = new Vector3 (x + 0.5f, y - 0.5f, z - 0.5f);
    152.             vertices [vertexCount + 2] = new Vector3 (x - 0.5f, y + 0.5f, z - 0.5f);
    153.             vertices [vertexCount + 3] = new Vector3 (x + 0.5f, y + 0.5f, z - 0.5f);
    154.  
    155.             vertexCount += 4;
    156.  
    157.             normals [normalCount] = Vector3.back;
    158.             normals [normalCount + 1] = Vector3.back;
    159.             normals [normalCount + 2] = Vector3.back;
    160.             normals [normalCount + 3] = Vector3.back;
    161.  
    162.             normalCount += 4;
    163.  
    164.             triangles [triangleCount] = vertexCount - 1;
    165.             triangles [triangleCount + 1] = vertexCount - 3;
    166.             triangles [triangleCount + 2] = vertexCount - 4;
    167.  
    168.             triangles [triangleCount + 3] = vertexCount - 2;
    169.             triangles [triangleCount + 4] = vertexCount - 1;
    170.             triangles [triangleCount + 5] = vertexCount - 4;
    171.  
    172.             triangleCount += 6;
    173.         }
    174.  
    175.  
    176.         if (direction == Direction.Right) {
    177.  
    178.             vertices [vertexCount] = new Vector3 (x + 0.5f, y - 0.5f, z + 0.5f);
    179.             vertices [vertexCount + 1] = new Vector3 (x + 0.5f, y - 0.5f, z - 0.5f);
    180.             vertices [vertexCount + 2] = new Vector3 (x + 0.5f, y + 0.5f, z + 0.5f);
    181.             vertices [vertexCount + 3] = new Vector3 (x + 0.5f, y + 0.5f, z - 0.5f);
    182.  
    183.             vertexCount += 4;
    184.  
    185.             normals [normalCount] = Vector3.right;
    186.             normals [normalCount + 1] = Vector3.right;
    187.             normals [normalCount + 2] = Vector3.right;
    188.             normals [normalCount + 3] = Vector3.right;
    189.  
    190.             normalCount += 4;
    191.  
    192.             triangles [triangleCount] = vertexCount - 4;
    193.             triangles [triangleCount + 1] = vertexCount - 3;
    194.             triangles [triangleCount + 2] = vertexCount - 1;
    195.  
    196.             triangles [triangleCount + 3] = vertexCount - 4;
    197.             triangles [triangleCount + 4] = vertexCount - 1;
    198.             triangles [triangleCount + 5] = vertexCount - 2;
    199.  
    200.             triangleCount += 6;
    201.         }
    202.  
    203.         if (direction == Direction.Left) {
    204.  
    205.             vertices [vertexCount] = new Vector3 (x - 0.5f, y - 0.5f, z + 0.5f);
    206.             vertices [vertexCount + 1] = new Vector3 (x - 0.5f, y - 0.5f, z - 0.5f);
    207.             vertices [vertexCount + 2] = new Vector3 (x - 0.5f, y + 0.5f, z + 0.5f);
    208.             vertices [vertexCount + 3] = new Vector3 (x - 0.5f, y + 0.5f, z - 0.5f);
    209.  
    210.             vertexCount += 4;
    211.  
    212.             normals [normalCount] = Vector3.left;
    213.             normals [normalCount + 1] = Vector3.left;
    214.             normals [normalCount + 2] = Vector3.left;
    215.             normals [normalCount + 3] = Vector3.left;
    216.  
    217.             normalCount += 4;
    218.  
    219.             triangles [triangleCount] = vertexCount - 1;
    220.             triangles [triangleCount + 1] = vertexCount - 3;
    221.             triangles [triangleCount + 2] = vertexCount - 4;
    222.  
    223.             triangles [triangleCount + 3] = vertexCount - 2;
    224.             triangles [triangleCount + 4] = vertexCount - 1;
    225.             triangles [triangleCount + 5] = vertexCount - 4;
    226.  
    227.             triangleCount += 6;
    228.         }
    229.  
    230.         if (direction == Direction.Up) {
    231.  
    232.             vertices [vertexCount] = new Vector3 (x - 0.5f, y + 0.5f, z + 0.5f);
    233.             vertices [vertexCount + 1] = new Vector3 (x - 0.5f, y + 0.5f, z - 0.5f);
    234.             vertices [vertexCount + 2] = new Vector3 (x + 0.5f, y + 0.5f, z + 0.5f);
    235.             vertices [vertexCount + 3] = new Vector3 (x + 0.5f, y + 0.5f, z - 0.5f);
    236.  
    237.             vertexCount += 4;
    238.  
    239.             normals [normalCount] = Vector3.up;
    240.             normals [normalCount + 1] = Vector3.up;
    241.             normals [normalCount + 2] = Vector3.up;
    242.             normals [normalCount + 3] = Vector3.up;
    243.  
    244.             normalCount += 4;
    245.  
    246.             triangles [triangleCount] = vertexCount - 1;
    247.             triangles [triangleCount + 1] = vertexCount - 3;
    248.             triangles [triangleCount + 2] = vertexCount - 4;
    249.  
    250.             triangles [triangleCount + 3] = vertexCount - 2;
    251.             triangles [triangleCount + 4] = vertexCount - 1;
    252.             triangles [triangleCount + 5] = vertexCount - 4;
    253.  
    254.             triangleCount += 6;
    255.         }
    256.  
    257.         if (direction == Direction.Down) {
    258.  
    259.             vertices [vertexCount] = new Vector3 (x - 0.5f, y - 0.5f, z + 0.5f);
    260.             vertices [vertexCount + 1] = new Vector3 (x - 0.5f, y - 0.5f, z - 0.5f);
    261.             vertices [vertexCount + 2] = new Vector3 (x + 0.5f, y - 0.5f, z + 0.5f);
    262.             vertices [vertexCount + 3] = new Vector3 (x + 0.5f, y - 0.5f, z - 0.5f);
    263.  
    264.             vertexCount += 4;
    265.  
    266.             normals [normalCount] = Vector3.down;
    267.             normals [normalCount + 1] = Vector3.down;
    268.             normals [normalCount + 2] = Vector3.down;
    269.             normals [normalCount + 3] = Vector3.down;
    270.  
    271.             normalCount += 4;
    272.  
    273.             triangles [triangleCount] = vertexCount - 4;
    274.             triangles [triangleCount + 1] = vertexCount - 3;
    275.             triangles [triangleCount + 2] = vertexCount - 1;
    276.  
    277.             triangles [triangleCount + 3] = vertexCount - 4;
    278.             triangles [triangleCount + 4] = vertexCount - 1;
    279.             triangles [triangleCount + 5] = vertexCount - 2;
    280.  
    281.             triangleCount += 6;
    282.         }
    283.     }
    284. }
    285.  
    Block.cs
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public enum Shape : byte { Cube = 0 };
    6. public enum BlockType : byte { Air = 0, Filled };
    7.  
    8. public struct Block {
    9.  
    10.     public ushort value;
    11. }
    12.  
    13. public static class BlockExtensions {
    14.  
    15.     public static Block Create (this Block block, BlockType type) {
    16.  
    17.         block.value &= 0; //Clear out the data
    18.         block.value |= (ushort)type;
    19.  
    20.         return block;
    21.     }
    22.  
    23.     public static bool IsVisible (this Block block) {
    24.  
    25.         BlockType type = block.GetBlockType ();
    26.  
    27.         return type != BlockType.Air;
    28.     }
    29.  
    30.     public static BlockType GetBlockType (this Block block) {
    31.  
    32.         return (BlockType)((ushort)block.value);
    33.     }
    34. }
    Direction and Vector3Int
    Code (CSharp):
    1. public enum Direction : byte {
    2.     Forward = 1,
    3.     Back = 2,
    4.     Right = 4,
    5.     Left = 8,
    6.     Up = 16,
    7.     Down = 32
    8. };
    Code (CSharp):
    1. public struct Vector3Int {
    2.  
    3.     public int x;
    4.     public int y;
    5.     public int z;
    6.  
    7.     public Vector3Int (int x = 0, int y = 0, int z = 0) {
    8.  
    9.         this.x = x;
    10.         this.y = y;
    11.         this.z = z;
    12.     }
    13. }
    14.  
     
  17. alexzzzz

    alexzzzz

    Joined:
    Nov 20, 2010
    Posts:
    1,447
    Microoptimizations that can shave off a couple of milliseconds:

    1. Try to cache Vector3.left, Vector3.right and others in static fields and use the cached values in calculations:
    Code (CSharp):
    1. private static readonly Vector3 left = Vector3.left;
    2. private static readonly Vector3 right = Vector3.right;
    3. ...
    2. Try to use a switch-statement or a chain of if-else-if-else... instead of independent if-statements:
    Code (CSharp):
    1. void CreateSquareFace(int x, int y, int z, Direction direction)
    2. {
    3.     if (direction == Direction.Forward)
    4.     {
    5.         // ...
    6.     }
    7.     else if (direction == Direction.Back)
    8.     {
    9.         // ...
    10.     }
    11.     else if (direction == Direction.Right)
    12.     {
    13.         // ...
    14.     }
    15.     else if (direction == Direction.Left)
    16.     {
    17.         // ...
    18.     }
    19.     else if (direction == Direction.Up)
    20.     {
    21.         // ...
    22.     }
    23.     else if (direction == Direction.Down)
    24.     {
    25.         // ...
    26.     }
    27. }

    PS

    BTW, have you seen this mega-thread: After playing minecraft... ?
     
    Last edited: Aug 16, 2017
  18. Missingno525

    Missingno525

    Joined:
    Aug 20, 2014
    Posts:
    17
    Yes, I have seen that thread. It has given me some more ways for optimization, and it has given me ways I can implement things that I want to have down the line (such as volumetric water).
     
  19. Missingno525

    Missingno525

    Joined:
    Aug 20, 2014
    Posts:
    17
    I just thought of another optimization. Right now, to find the visible faces, I iterate through the chunk and skip any transparent blocks.

    However, if I create a left face, then I shouldn't check the block to the left until I generate a face to the right. This can be done with a simple bool that checks if a face was generated to the left or right. I am not sure how to handle this for the other directions though. Does anyone have ideas? Otherwise I will just sketch it out to better visualize what I am doing.