# Smooth Voxel terrain. How is it done?

Hello Unity Community!

I am a young student developer and started off with Unity a few months ago. I understand most of the basics and I started following these tutorials for Voxel terrain engines:

I’m planning on making a smooth, destructible Voxel terrain, like in these games:

Yogventures

Castle Story

Windbourne

TUG

I’ve read a few Threads: (After playing Minecraft… etc). I read that one solution would be making a basic Voxel terrain and changing the mesh’s vertices depending on its neighbours. I tried this on a 2D terrain. Here’s the code:

``````using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class SmoothPolygonGeneratorTest : MonoBehaviour {

public List<Vector3> newVertices = new List<Vector3>();
public List<int> newTriangles = new List<int>();
public List<Vector2> newUV = new List<Vector2>();

public List<Vector3> colVertices = new List<Vector3>();
public List<int> colTriangles = new List<int>();
private int colCount;

private Mesh mesh;
private MeshCollider col;

private float tUnit = 0.25f;
private Vector2 tStone = new Vector2 (1, 0);
private Vector2 tGrass = new Vector2 (0, 1);
private Vector2 tRandom = new Vector2 (0, 2);

public byte[,] blocks;
private int squareCount;
public bool update=false;

// Use this for initialization
void Start () {
mesh = GetComponent<MeshFilter> ().mesh;
col = GetComponent<MeshCollider> ();

GenTerrain();
BuildMesh();
UpdateMesh();
}

void Update(){
if(update){
BuildMesh();
UpdateMesh();
update=false;
}
}

int NoiseInt (int x, int y, float scale, float mag, float exp){

return (int) (Mathf.Pow ((Mathf.PerlinNoise(x/scale,y/scale)*mag),(exp) ));

}

void GenTerrain(){
blocks=new byte[96,128];

for(int px=0;px<blocks.GetLength(0);px++){
int stone= NoiseInt(px,0, 80,15,1);
stone+= NoiseInt(px,0, 50,30,1);
stone+= NoiseInt(px,0, 10,10,1);
stone+=75;

int dirt = NoiseInt(px,0, 100f,35,1);
dirt+= NoiseInt(px,100, 50,30,1);
dirt+=75;

for(int py=0;py<blocks.GetLength(1);py++){
if(py<stone){
blocks[px, py]=1;

if(NoiseInt(px,py,12,16,1)>10){  //dirt spots
blocks[px,py]=2;

}

if(NoiseInt(px,py*2,16,14,1)>10){ //Caves
blocks[px,py]=0;

}

} else if(py<dirt) {
blocks[px,py]=2;
}

}
}
}

void BuildMesh(){
for(int px=1;px<blocks.GetLength(0);px++){
for(int py=1;py<blocks.GetLength(1);py++){

if(blocks[px,py]!=0){

if(blocks[px,py]==1){
if(Block(px, py + 1)==0 && Block(px, py-1) == 0) {
if(Block (px + 1, py) != 0 && Block (px - 1, py)== 0) {
SmoothTerrain(px,py,7,tStone,0f);
}
else if (Block (px + 1, py) == 0 && Block (px -1, py) != 0) {
SmoothTerrain(px,py,8,tStone,0f);
}
else if(Block(px - 1, py)==0 || Block(px + 1, py)==0) {
SmoothSquare(px,py,tStone,0f,0f);
}
else if (Block(px - 1, py)!=0 || Block(px + 1, py)!=0) {
SmoothSquare(px,py,tStone,0f,0f);
}
}
else if(Block(px, py + 1 )==0 && Block(px,py -1)!=0) {
if(Block(px + 1, py)!=0 && Block(px- 1, py)==0) {
if (CheckBlock(px + 2,py) != 0 && CheckBlock(px + 1, py +1) == 0){
SmoothTerrain(px,py,1,tStone,0.4f);
}
else {
SmoothTerrain(px,py,1,tStone,0f);
}
}
else if(Block(px - 1, py)!=0 && Block(px + 1, py)==0){
if (CheckBlock(px - 2,py) != 0 && CheckBlock(px - 1, py +1) == 0){
SmoothTerrain(px,py,2,tStone,0.4f);
}
else {
SmoothTerrain(px,py,2,tStone,0f);
}
}
else if(Block(px - 1, py)==0 || Block(px + 1, py)==0) {
SmoothTerrain(px,py,3,tStone, 0f);
}
else if (Block(px - 1, py)!=0 || Block(px + 1, py)!=0) {
if(CheckBlock(px-2,py) ==0 && CheckBlock(px-1, py +1)==0) {
SmoothSquare(px,py,tStone,0.4f,0f);
}
else if(CheckBlock(px+2,py) ==0 && CheckBlock(px+1, py +1)==0) {
SmoothSquare(px,py,tStone,0f,0.4f);
}
else {
SmoothSquare(px,py,tStone,0f,0f);
}
}
}
else if(Block(px, py -1)==0 && Block (px, py +1)!=0) {
if(Block(px + 1, py)!=0 && Block(px- 1, py)==0) {
SmoothTerrain(px,py,4,tStone, 0f);
}
else if(Block(px - 1, py)!=0 && Block(px + 1, py)==0){
SmoothTerrain(px,py,5,tStone, 0f);
}
else if(Block(px - 1, py)==0 || Block(px + 1, py)==0) {
SmoothTerrain(px,py,6,tStone, 0f);
}
else if (Block(px - 1, py)!=0 || Block(px + 1, py)!=0) {
SmoothSquare(px,py,tStone,0f,0f);
}

}
else if(Block (px, py -1) != 0 && Block (px, py + 1) != 0) {
if(Block (px + 1, py) == 0 && Block (px -1, py) == 0){
SmoothSquare(px,py,tStone,0f,0f);
}
else if (Block (px + 1, py) != 0 && Block (px - 1, py) == 0) {
SmoothSquare(px,py,tStone,0f,0f);
}
else if (Block (px + 1, py) == 0 && Block (px - 1, py) != 0) {
SmoothSquare(px,py,tStone,0f,0f);
}
else if(Block (px + 1, py)!= 0 && Block (px- 1, py)!= 0){
GenSquare(px,py,tStone);
}
}

} else if(blocks[px,py]==2){
if(Block(px, py + 1)==0 && Block(px, py-1) == 0) {
if(Block (px + 1, py) != 0 && Block (px - 1, py)== 0) {
SmoothTerrain(px,py,7,tGrass,0f);
}
else if (Block (px + 1, py) == 0 && Block (px -1, py) != 0) {
SmoothTerrain(px,py,8,tGrass,0f);
}
else if(Block(px - 1, py)==0 || Block(px + 1, py)==0) {
SmoothSquare(px,py,tGrass,0f,0f);
}
else if (Block(px - 1, py)!=0 || Block(px + 1, py)!=0) {
SmoothSquare(px,py,tGrass,0f,0f);
}
}
else if(Block(px, py + 1 )==0 && Block(px,py -1)!=0) {
if(Block(px + 1, py)!=0 && Block(px- 1, py)==0) {
if (CheckBlock(px + 2,py) != 0 && CheckBlock(px + 1, py +1) == 0){
SmoothTerrain(px,py,1,tGrass,0.4f);
}
else {
SmoothTerrain(px,py,1,tGrass,0f);
}
}
else if(Block(px - 1, py)!=0 && Block(px + 1, py)==0){
if (CheckBlock(px - 2,py) != 0 && CheckBlock(px - 1, py +1) == 0){
SmoothTerrain(px,py,2,tGrass,0.4f);
}
else {
SmoothTerrain(px,py,2,tGrass,0f);
}
}
else if(Block(px - 1, py)==0 || Block(px + 1, py)==0) {
SmoothTerrain(px,py,3,tGrass, 0f);
}
else if (Block(px - 1, py)!=0 || Block(px + 1, py)!=0) {
if(CheckBlock(px-2,py) ==0 && CheckBlock(px-1, py +1)==0) {
SmoothSquare(px,py,tGrass,0.4f,0f);
}
else if(CheckBlock(px+2,py) ==0 && CheckBlock(px+1, py +1)==0) {
SmoothSquare(px,py,tGrass,0f,0.4f);
}
else {
SmoothSquare(px,py,tGrass,0f,0f);
}
}
}
else if(Block(px, py -1)==0 && Block (px, py +1)!=0) {
if(Block(px + 1, py)!=0 && Block(px- 1, py)==0) {
SmoothTerrain(px,py,4,tGrass, 0f);
}
else if(Block(px - 1, py)!=0 && Block(px + 1, py)==0){
SmoothTerrain(px,py,5,tGrass, 0f);
}
else if(Block(px - 1, py)==0 || Block(px + 1, py)==0) {
SmoothTerrain(px,py,6,tGrass, 0f);
}
else if (Block(px - 1, py)!=0 || Block(px + 1, py)!=0) {
SmoothSquare(px,py,tGrass,0f,0f);
}

}
else if(Block (px, py -1) != 0 && Block (px, py + 1) != 0) {
if(Block (px + 1, py) == 0 && Block (px -1, py) == 0){
SmoothSquare(px,py,tGrass,0f,0f);
}
else if (Block (px + 1, py) != 0 && Block (px - 1, py) == 0) {
SmoothSquare(px,py,tGrass,0f,0f);
}
else if (Block (px + 1, py) == 0 && Block (px - 1, py) != 0) {
SmoothSquare(px,py,tGrass,0f,0f);
}
else if(Block (px + 1, py)!= 0 && Block (px- 1, py)!= 0){
GenSquare(px,py,tGrass);
}
}
}
}
}
}
}

byte Block (int x, int y){

if(x==-1 || x==blocks.GetLength(0) || y==-1 || y==blocks.GetLength(1)){
return (byte)1;
}

return blocks[x,y];
}

byte CheckBlock (int x, int y) {
if( x == -1 || x == -2 || x ==blocks.GetLength(0) + 1 || x ==blocks.GetLength(0) || y == -1 || y == -2  || y == blocks.GetLength(1) + 1 || y == blocks.GetLength(1) ) {
return (byte) 1;
}
return blocks[x,y];
}

void GenSquare(int x, int y, Vector2 texture){

newVertices.Add( new Vector3 (x  ,  y  , 0 ));
newVertices.Add( new Vector3 (x + 1 ,  y  , 0 ));
newVertices.Add( new Vector3 (x + 1 ,  y-1 , 0 ));
newVertices.Add( new Vector3 (x  ,  y-1 , 0 ));

newUV.Add(new Vector2 (tUnit * texture.x, tUnit * texture.y + tUnit));
newUV.Add(new Vector2 (tUnit * texture.x + tUnit, tUnit * texture.y + tUnit));
newUV.Add(new Vector2 (tUnit * texture.x + tUnit, tUnit * texture.y));
newUV.Add(new Vector2 (tUnit * texture.x, tUnit * texture.y));

squareCount++;

}

void UpdateMesh () {
mesh.Clear ();
mesh.vertices = newVertices.ToArray();
mesh.triangles = newTriangles.ToArray();
mesh.uv = newUV.ToArray();
mesh.Optimize ();
mesh.RecalculateNormals ();

newVertices.Clear();
newTriangles.Clear();
newUV.Clear();
squareCount=0;

Mesh newMesh = new Mesh();
newMesh.vertices = colVertices.ToArray();
newMesh.triangles = colTriangles.ToArray();
col.sharedMesh= newMesh;

colVertices.Clear();
colTriangles.Clear();
colCount=0;
}
void SmoothTerrain(int x, int y, int count, Vector2 texture, float rate) {
if(count == 1) {
newVertices.Add( new Vector3 (x  ,  y  , 0 ));
newVertices.Add( new Vector3 (x + 1 ,  y- rate  , 0 ));
newVertices.Add( new Vector3 (x + 1 ,  y-1 , 0 ));
newVertices.Add( new Vector3 (x  ,  y-1 , 0 ));

newUV.Add(new Vector2 (tUnit * texture.x, tUnit * texture.y + tUnit));
newUV.Add(new Vector2 (tUnit * texture.x + tUnit, tUnit * texture.y + tUnit));
newUV.Add(new Vector2 (tUnit * texture.x + tUnit, tUnit * texture.y));
newUV.Add(new Vector2 (tUnit * texture.x, tUnit * texture.y));

colVertices.Add( new Vector3 (x  ,  y-1  , 1));
colVertices.Add( new Vector3 (x + 1 ,  y- rate  , 1));
colVertices.Add( new Vector3 (x + 1 ,  y- rate , 0 ));
colVertices.Add( new Vector3 (x  ,  y-1  , 0 ));

ColliderTriangles();

colCount++;

squareCount++;
}
else if(count == 2) {
newVertices.Add( new Vector3 (x  ,  y  , 0 ));
newVertices.Add( new Vector3 (x  ,  y- rate   , 0 ));
newVertices.Add( new Vector3 (x + 1 ,  y-1 , 0 ));
newVertices.Add( new Vector3 (x  ,  y-1 , 0 ));

newUV.Add(new Vector2 (tUnit * texture.x, tUnit * texture.y + tUnit));
newUV.Add(new Vector2 (tUnit * texture.x + tUnit, tUnit * texture.y + tUnit));
newUV.Add(new Vector2 (tUnit * texture.x + tUnit, tUnit * texture.y));
newUV.Add(new Vector2 (tUnit * texture.x, tUnit * texture.y));

colVertices.Add( new Vector3 (x  ,  y- rate  , 0));
colVertices.Add( new Vector3 (x  ,  y- rate  , 1));
colVertices.Add( new Vector3 (x + 1 ,  y-1 , 1 ));
colVertices.Add( new Vector3 (x +1 ,  y-1  , 0 ));

ColliderTriangles();

colCount++;

squareCount++;
}

else if (count == 3) {
newVertices.Add( new Vector3 (x  ,  y  , 0 ));
newVertices.Add( new Vector3 (x+1  ,  y-1   , 0 ));
newVertices.Add( new Vector3 (x + 0.5f ,  y-0.5f , 0 ));
newVertices.Add( new Vector3 (x  ,  y-1 , 0 ));

newUV.Add(new Vector2 (tUnit * texture.x, tUnit * texture.y + tUnit));
newUV.Add(new Vector2 (tUnit * texture.x + tUnit, tUnit * texture.y + tUnit));
newUV.Add(new Vector2 (tUnit * texture.x + tUnit, tUnit * texture.y));
newUV.Add(new Vector2 (tUnit * texture.x, tUnit * texture.y));

colVertices.Add( new Vector3 (x  ,  y-1  , 0));
colVertices.Add( new Vector3 (x  ,  y-1  , 1));
colVertices.Add( new Vector3 (x + 0.5f ,  y-0.5f , 1 ));
colVertices.Add( new Vector3 (x+0.5f  ,  y-0.5f  , 0 ));

ColliderTriangles();
colCount++;
colVertices.Add( new Vector3 (x + 0.5f ,  y-0.5f , 0 ));
colVertices.Add( new Vector3 (x+0.5f  ,  y-0.5f  , 1 ));
colVertices.Add( new Vector3 (x+1  ,  y-1  , 1 ));
colVertices.Add( new Vector3 (x+1 ,  y-1  , 0 ));

ColliderTriangles();
colCount++;
squareCount++;

}
else if(count == 4) {
newVertices.Add( new Vector3 (x  ,  y  , 0 ));
newVertices.Add( new Vector3 (x + 1 ,  y  , 0 ));
newVertices.Add( new Vector3 (x + 1 ,  y-(1 - rate) , 0 ));
newVertices.Add( new Vector3 (x  ,  y-1 , 0 ));

newUV.Add(new Vector2 (tUnit * texture.x, tUnit * texture.y + tUnit));
newUV.Add(new Vector2 (tUnit * texture.x + tUnit, tUnit * texture.y + tUnit));
newUV.Add(new Vector2 (tUnit * texture.x + tUnit, tUnit * texture.y));
newUV.Add(new Vector2 (tUnit * texture.x, tUnit * texture.y));

colVertices.Add( new Vector3 (x  ,  y  , 0));
colVertices.Add( new Vector3 (x  ,  y  , 1));
colVertices.Add( new Vector3 (x + 1 ,  y-(1 - rate) , 1 ));
colVertices.Add( new Vector3 (x +1 ,  y-(1-rate)  , 0 ));

ColliderTriangles();

colCount++;

squareCount++;
}
else if(count == 5) {
newVertices.Add( new Vector3 (x  ,  y  , 0 ));
newVertices.Add( new Vector3 (x + 1 ,  y   , 0 ));
newVertices.Add( new Vector3 (x + 1 ,  y-1 , 0 ));
newVertices.Add( new Vector3 (x  ,  y-(1-rate) , 0 ));

newUV.Add(new Vector2 (tUnit * texture.x, tUnit * texture.y + tUnit));
newUV.Add(new Vector2 (tUnit * texture.x + tUnit, tUnit * texture.y + tUnit));
newUV.Add(new Vector2 (tUnit * texture.x + tUnit, tUnit * texture.y));
newUV.Add(new Vector2 (tUnit * texture.x, tUnit * texture.y));

colVertices.Add( new Vector3 (x +1 ,  y  , 0));
colVertices.Add( new Vector3 (x +1  ,  y  , 1));
colVertices.Add( new Vector3 (x  ,  y-(1 - rate) , 1 ));
colVertices.Add( new Vector3 (x ,  y-(1-rate)  , 0 ));

ColliderTriangles();

colCount++;

squareCount++;
}
else if (count == 6) {
newVertices.Add( new Vector3 (x  ,  y  , 0 ));
newVertices.Add( new Vector3 (x+1  , y    , 0 ));
newVertices.Add( new Vector3 (x + 0.5f ,  y-0.5f , 0 ));
newVertices.Add( new Vector3 (x  ,  y-1 , 0 ));

newUV.Add(new Vector2 (tUnit * texture.x, tUnit * texture.y + tUnit));
newUV.Add(new Vector2 (tUnit * texture.x + tUnit, tUnit * texture.y + tUnit));
newUV.Add(new Vector2 (tUnit * texture.x + tUnit, tUnit * texture.y));
newUV.Add(new Vector2 (tUnit * texture.x, tUnit * texture.y));

colVertices.Add( new Vector3 (x  ,  y  , 0));
colVertices.Add( new Vector3 (x  ,  y  , 1));
colVertices.Add( new Vector3 (x + 0.5f ,  y-0.5f , 1 ));
colVertices.Add( new Vector3 (x+0.5f  ,  y-0.5f  , 0 ));

ColliderTriangles();
colCount++;

colVertices.Add( new Vector3 (x + 0.5f ,  y-0.5f , 0 ));
colVertices.Add( new Vector3 (x+0.5f  ,  y-0.5f  , 1 ));
colVertices.Add( new Vector3 (x+1  ,  y  , 1 ));
colVertices.Add( new Vector3 (x+1 ,  y  , 0 ));

ColliderTriangles();

colCount++;

squareCount++;
}
else if (count == 7) {
newVertices.Add( new Vector3 (x  ,  y  , 0 ));
newVertices.Add( new Vector3 (x+1  , y    , 0 ));
newVertices.Add( new Vector3 (x + 0.5f ,  y-0.5f , 0 ));
newVertices.Add( new Vector3 (x+1  ,  y-1 , 0 ));

newUV.Add(new Vector2 (tUnit * texture.x, tUnit * texture.y + tUnit));
newUV.Add(new Vector2 (tUnit * texture.x + tUnit, tUnit * texture.y + tUnit));
newUV.Add(new Vector2 (tUnit * texture.x + tUnit, tUnit * texture.y));
newUV.Add(new Vector2 (tUnit * texture.x, tUnit * texture.y));

colVertices.Add( new Vector3 (x + 1  ,  y-1  , 0));
colVertices.Add( new Vector3 (x + 1 ,  y-1  , 1));
colVertices.Add( new Vector3 (x + 0.5f ,  y-0.5f , 1 ));
colVertices.Add( new Vector3 (x+0.5f  ,  y-0.5f  , 0 ));

ColliderTriangles();
colCount++;

colVertices.Add( new Vector3 (x + 0.5f ,  y-0.5f , 0 ));
colVertices.Add( new Vector3 (x+0.5f  ,  y-0.5f  , 1 ));
colVertices.Add( new Vector3 (x+1  ,  y  , 1 ));
colVertices.Add( new Vector3 (x+1 ,  y  , 0 ));

ColliderTriangles();

colCount++;

squareCount++;
}
else if (count == 8) {
newVertices.Add( new Vector3 (x  ,  y  , 0 ));
newVertices.Add( new Vector3 (x  , y    , 0 ));
newVertices.Add( new Vector3 (x + 0.5f ,  y-0.5f , 0 ));
newVertices.Add( new Vector3 (x  ,  y-1 , 0 ));

newUV.Add(new Vector2 (tUnit * texture.x, tUnit * texture.y + tUnit));
newUV.Add(new Vector2 (tUnit * texture.x + tUnit, tUnit * texture.y + tUnit));
newUV.Add(new Vector2 (tUnit * texture.x + tUnit, tUnit * texture.y));
newUV.Add(new Vector2 (tUnit * texture.x, tUnit * texture.y));

colVertices.Add( new Vector3 (x  ,  y-1  , 0));
colVertices.Add( new Vector3 (x  ,  y-1  , 1));
colVertices.Add( new Vector3 (x + 0.5f ,  y-0.5f , 1 ));
colVertices.Add( new Vector3 (x+0.5f  ,  y-0.5f  , 0 ));

ColliderTriangles();
colCount++;

colVertices.Add( new Vector3 (x + 0.5f ,  y-0.5f , 0 ));
colVertices.Add( new Vector3 (x+0.5f  ,  y-0.5f  , 1 ));
colVertices.Add( new Vector3 (x  ,  y  , 1 ));
colVertices.Add( new Vector3 (x ,  y  , 0 ));

ColliderTriangles();

colCount++;

squareCount++;
}
}

void SmoothSquare(int x, int y, Vector2 texture, float rate, float brate) {
newVertices.Add( new Vector3 (x  ,  y-rate  , 0 ));
newVertices.Add( new Vector3 (x + 1 ,  y -brate, 0 ));
newVertices.Add( new Vector3 (x + 1 ,  y-1 , 0 ));
newVertices.Add( new Vector3 (x  ,  y-1 , 0 ));

newUV.Add(new Vector2 (tUnit * texture.x, tUnit * texture.y + tUnit));
newUV.Add(new Vector2 (tUnit * texture.x + tUnit, tUnit * texture.y + tUnit));
newUV.Add(new Vector2 (tUnit * texture.x + tUnit, tUnit * texture.y));
newUV.Add(new Vector2 (tUnit * texture.x, tUnit * texture.y));

if(Block(x,y+1)==0){
colVertices.Add( new Vector3 (x  ,  y - rate  , 1));
colVertices.Add( new Vector3 (x + 1 ,  y - brate  , 1));
colVertices.Add( new Vector3 (x + 1 ,  y - brate, 0 ));
colVertices.Add( new Vector3 (x  ,  y - rate  , 0 ));

ColliderTriangles();

colCount++;
}

//bot
if(Block(x,y-1)==0){
colVertices.Add( new Vector3 (x  ,  y -1 , 0));
colVertices.Add( new Vector3 (x + 1 ,  y -1 , 0));
colVertices.Add( new Vector3 (x + 1 ,  y -1 , 1 ));
colVertices.Add( new Vector3 (x  ,  y -1 , 1 ));

ColliderTriangles();
colCount++;
}

//left
if(Block(x-1,y)==0){
colVertices.Add( new Vector3 (x  ,  y -1 , 1));
colVertices.Add( new Vector3 (x  ,  y  , 1));
colVertices.Add( new Vector3 (x  ,  y  , 0 ));
colVertices.Add( new Vector3 (x  ,  y -1 , 0 ));

ColliderTriangles();

colCount++;
}

//right
if(Block(x+1,y)==0){
colVertices.Add( new Vector3 (x +1 ,  y   , 1));
colVertices.Add( new Vector3 (x +1 ,  y  , 0));
colVertices.Add( new Vector3 (x +1 ,  y -1 , 0 ));
colVertices.Add( new Vector3 (x +1 ,  y-1  , 1 ));

ColliderTriangles();

colCount++;
}

squareCount++;
}

void ColliderTriangles(){
}
}
``````

[21337-unity+before.png|21337]

before changes on script

[21339-unity+smoothing.png|21339]

after

Is this the right way or would the performance be too slow? Is an obvious better way? (By maybe setting meshes vertices by Perlin Noise, if it´s even possible). Because I have otherwise no idea, what to do. I would really appreciate your support!

Your code base will expand a lot. I would strongly suggest reading a book like “Clean Code: A Handbook of Agile Software Craftsmanship”. Other vise you will end up with problems increase exponentially with your code base.