ID:1614040
 
So, I am currently working on creating a Voxel Terrain engine, and as you can see it is in Unity3D. The reason why I want to create a Voxel Terrain engine is because Unity's terrain system only really has 1 axis, the y axis. There are no X or Z axis (left/right and depth). While this isn't all that bad, it still limits how far development in my projects can go.

Things currently wrong with the Unity Terrain
1. It has some performance issues and the textures can get ugly in terms of blending. You'll see a grass area blending over a tiled area and just think to yourself, "Eww, that's nasty." One way around this is to make a gameObject with the texture of the tile applied to it so it doesn't blend onto it. It would be nice if you could "paint" a no blend section on the terrain though.

2. Refferring back to the limited terrain axises, Unity Terrain can only be raised and lowered. Because there is no axis for depth or an axis for moving side-to-side you are unable to create things like caves and tunnels without importing an object first.

3. Editing the Unity Terrain in realtime can be a real pain in the.. yeah. It gets laggy and inefficient real quick. The terrain ends up creating an insane load of unneccsary triangles when raising or lowering the terrain in real time. Yeah, I could smooth it out to lower the tris count, but, it's not worth it if it is not voxel!

Lastly, creating extremely large terrains (I'm talking vast as f*) things can get hairy quick with the Unity terrain.

With this script I created, with the mental aid from Ss4toby to prevent me from going crazy trying to understand how the darn thing works, I can create a 100000x100000 map in an instant. Now that's vast. But, it's all grass and flat. It only needs 2 tris (6 verts).

Editing in may seem like a pain, but it is quite simple. See image below script on how to edit it. The only issue is.. how will we perform that image's theory? Any ideas?

using UnityEngine;
using System.Collections;

public class VoxelTerrain : MonoBehaviour {

Mesh mesh;

//Shapes
Vector3[] cubeShape = new Vector3[] {
//front - z (looking at it from the side, this is the coord
//that lets you see how thin the triangle is. Remember this for
//UV mapping. It makes the logic easier.)
new Vector3(0, 0, 0), new Vector3(0, 1, 0), new Vector3(1, 1, 0),
new Vector3(1, 1, 0), new Vector3(1, 0, 0), new Vector3(0, 0, 0),

//back - z
new Vector3(0, 0, 1), new Vector3(1, 1, 1), new Vector3(0, 1, 1),
new Vector3(1, 1, 1), new Vector3(0, 0, 1), new Vector3(1, 0, 1),

//right - x
new Vector3(1, 0, 0), new Vector3(1, 1, 0), new Vector3(1, 0, 1),
new Vector3(1, 1, 1), new Vector3(1, 0, 1), new Vector3(1, 1, 0),

//left - x
new Vector3(0, 0, 0), new Vector3(0, 0, 1), new Vector3(0, 1, 0),
new Vector3(0, 1, 1), new Vector3(0, 1, 0), new Vector3(0, 0, 1),

//top - y
new Vector3(0, 1, 0), new Vector3(0, 1, 1), new Vector3(1, 1, 0),
new Vector3(1, 1, 0), new Vector3(0, 1, 1), new Vector3(1, 1, 1),


//bottom - y
new Vector3(0, 0, 0), new Vector3(1, 0, 0), new Vector3(0, 0, 1),
new Vector3(1, 0, 0), new Vector3(1, 0, 1), new Vector3(0, 0, 1)
};

Vector3[] pyramidShape = new Vector3[] {
};

Vector3[] sphereShape = new Vector3[] {
};

Vector3[] blockShape;
Vector3[] terrainShape;

Vector3[] planeShape = new Vector3[] {
new Vector3 (0, 0, 0), new Vector3 (0, 0, 1), new Vector3 (1, 0, 0),
new Vector3 (1, 0, 0), new Vector3 (0, 0, 1), new Vector3 (1, 0, 1)
};




public Material grassTexture;

void Start() {
//Make sure the requirements are set. Otherwise, don't run
if(!grassTexture) {
Debug.Log("Failed to start. Update the textures.");
return;
}

//Check and add the required components
if(!GetComponent<MeshFilter>())
gameObject.AddComponent("MeshFilter");

if(!GetComponent<MeshRenderer>())
gameObject.AddComponent("MeshRenderer");


//Set up the GameObject so it has the required mesh settings
mesh = GetComponent<MeshFilter>().mesh;
MeshSetup();


//Create the vertices for the cube.
mesh.vertices = cubeShape;


//Create the uv for the cube
UVCreator();


//Add the triangles to render the cube
TriangleSetup();


//Set the normals automatically. It's needed for lighting
mesh.RecalculateNormals();


//Set the cube's texture and add a collider
renderer.material = grassTexture;
if(!GetComponent<MeshCollider>())
gameObject.AddComponent("MeshCollider");

//Set up shape variables
BlockCreator();
}

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

int xSize = 0;
int ySize = 0;
int zSize = 0;
int areaSize = 0;
void OnGUI () {
GUILayout.BeginArea(new Rect(10, 10, 250, 250));
GUILayout.Label("Modes");
if(GUILayout.Button("Cube")){
CreateMesh(cubeShape);
}

GUILayout.Button("Pyramid");

GUILayout.Button("Sphere");

if(GUILayout.Button("3x3x3 Block")) {
CreateMesh(blockShape);
}

if(GUILayout.Button("Editing Terrain")) {
if(!(xSize != null && xSize > 0 && xSize <= 25)) {
Debug.Log("X Repeats must have a valid entry that is not less than 0, is an integer, and is less than or equal to 25.");
}
else {
if(!(zSize != null && zSize > 0 && zSize <= 25))
Debug.Log("Z Repeats must have a valid entry that is not less than 0, is an integer, and is less than or equal to 25.");
else {
if(!(areaSize != null && areaSize > 0 && areaSize <= 10000))
Debug.Log("Size of Repeats must have a valid entry that is not less than 0, is an integer, and is less than or equal to 100000.");
else {
PlaneCreator(xSize, zSize, areaSize);
transform.position = new Vector3 (-((xSize * areaSize))/2, transform.position.y, -(zSize * areaSize)/2);
CreateMesh(terrainShape);
}
}
}
}
GUILayout.BeginHorizontal();
GUILayout.Label("X Repeats:");
string tempxSize = GUILayout.TextField(xSize.ToString());
if(int.TryParse(tempxSize, out xSize))
xSize = int.Parse(tempxSize);
if(xSize < 1)
xSize = 1;
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.Label("Y Repeats(Not for Terrain):");
string tempySize = GUILayout.TextField(ySize.ToString());
if(int.TryParse(tempySize, out ySize))
ySize = int.Parse(tempySize);
if(ySize < 1)
ySize = 1;
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.Label("Z Repeats:");
string tempzSize = GUILayout.TextField(zSize.ToString());
if(int.TryParse(tempzSize, out zSize))
zSize = int.Parse(tempzSize);
if(zSize < 1)
zSize = 1;
GUILayout.EndHorizontal();
GUILayout.BeginHorizontal();
GUILayout.Label("Size of Repeats:");
string tempareaSize = GUILayout.TextField(areaSize.ToString());
if(int.TryParse(tempareaSize, out areaSize))
areaSize = int.Parse(tempareaSize);
if(areaSize < 1)
areaSize = 1;
GUILayout.EndHorizontal();
GUILayout.EndArea();
}

void MeshSetup () {
mesh.Clear();
}

void UVCreator () {
//Create the uv for the cube
//How this works:
//If a triangle has the same x coords for all 3 coords then it is not visible "always"
// on the x coords. There will come a time where it appears to be a thin line. As such,
// let the UV focus on the other 2 axises.

//The same thing applies for y and z coordonates.

//However, if there are no 3 coords for a triangle that match, the it is visible on both
//The x and y axis at all times so long as the depth(z) doesn't cause it to be hidden(i.e.
//an object in front of it would be the depth). Because of this, the UV should focus on
//x and y at all times.

//Moral of the story? Set the damn UV to the coords where the triangle will be visible mostly, if
//not at all times.
Vector2[] uvs = new Vector2[mesh.vertices.Length];
for(int i = 0; i < uvs.Length;) {
if(mesh.vertices[i].x == mesh.vertices[i+1].x && mesh.vertices[i].x == mesh.vertices[i+2].x) {
uvs[i] = new Vector2(mesh.vertices[i].y, mesh.vertices[i].z);
uvs[i+1] = new Vector2(mesh.vertices[i+1].y, mesh.vertices[i+1].z);
uvs[i+2] = new Vector2(mesh.vertices[i+2].y, mesh.vertices[i+2].z);
}
else
if(mesh.vertices[i].y == mesh.vertices[i+1].y && mesh.vertices[i].y == mesh.vertices[i+2].y) {
uvs[i] = new Vector2(mesh.vertices[i].x, mesh.vertices[i].z);
uvs[i+1] = new Vector2(mesh.vertices[i+1].x, mesh.vertices[i+1].z);
uvs[i+2] = new Vector2(mesh.vertices[i+2].x, mesh.vertices[i+2].z);
}
else
if(mesh.vertices[i].z == mesh.vertices[i+1].z && mesh.vertices[i].z == mesh.vertices[i+2].z) {
uvs[i] = new Vector2(mesh.vertices[i].x, mesh.vertices[i].y);
uvs[i+1] = new Vector2(mesh.vertices[i+1].x, mesh.vertices[i+1].y);
uvs[i+2] = new Vector2(mesh.vertices[i+2].x, mesh.vertices[i+2].y);
}
else {
uvs[i] = new Vector2(mesh.vertices[i].x, mesh.vertices[i].y);
uvs[i+1] = new Vector2(mesh.vertices[i+1].x, mesh.vertices[i+1].y);
uvs[i+2] = new Vector2(mesh.vertices[i+2].x, mesh.vertices[i+2].y);
}

i += 3;
}
mesh.uv = uvs;
}

void TriangleSetup () {
//Add the triangles to render the cube
int[] triangleNumbers = new int[mesh.vertices.Length];
Vector2[] uvs = new Vector2[mesh.vertices.Length];
for(int i = 0; i < mesh.vertices.Length;) {
triangleNumbers[i] = i;
triangleNumbers[i+1] = i+1;
triangleNumbers[i+2] = i+2;

if(mesh.vertices[i].x == mesh.vertices[i+1].x && mesh.vertices[i].x == mesh.vertices[i+2].x) {
uvs[i] = new Vector2(mesh.vertices[i].y, mesh.vertices[i].z);
uvs[i+1] = new Vector2(mesh.vertices[i+1].y, mesh.vertices[i+1].z);
uvs[i+2] = new Vector2(mesh.vertices[i+2].y, mesh.vertices[i+2].z);
}
else
if(mesh.vertices[i].y == mesh.vertices[i+1].y && mesh.vertices[i].y == mesh.vertices[i+2].y) {
uvs[i] = new Vector2(mesh.vertices[i].x, mesh.vertices[i].z);
uvs[i+1] = new Vector2(mesh.vertices[i+1].x, mesh.vertices[i+1].z);
uvs[i+2] = new Vector2(mesh.vertices[i+2].x, mesh.vertices[i+2].z);
}
else
if(mesh.vertices[i].z == mesh.vertices[i+1].z && mesh.vertices[i].z == mesh.vertices[i+2].z) {
uvs[i] = new Vector2(mesh.vertices[i].x, mesh.vertices[i].y);
uvs[i+1] = new Vector2(mesh.vertices[i+1].x, mesh.vertices[i+1].y);
uvs[i+2] = new Vector2(mesh.vertices[i+2].x, mesh.vertices[i+2].y);
}
else {
uvs[i] = new Vector2(mesh.vertices[i].x, mesh.vertices[i].y);
uvs[i+1] = new Vector2(mesh.vertices[i+1].x, mesh.vertices[i+1].y);
uvs[i+2] = new Vector2(mesh.vertices[i+2].x, mesh.vertices[i+2].y);
}

i += 3;
}
mesh.uv = uvs;
mesh.triangles = triangleNumbers;
}

void CreateMesh(Vector3[] meshVertices) {
MeshSetup();
mesh.vertices = meshVertices;
//UVCreator();
TriangleSetup();
mesh.RecalculateNormals();
GetComponent<MeshCollider>().sharedMesh = null;
GetComponent<MeshCollider>().sharedMesh = mesh;
}

void BlockCreator() {
blockShape = new Vector3[cubeShape.Length * 27];
int lastIndex = 0;

int xBlocks = 3;
int yBlocks = 3;
int zBlocks = 3;
/*
for(int x = 0; x < xBlocks; x++) {
for(int y = 0; y < yBlocks; y++) {
for(int z = 0; z < zBlocks; z++) {
for(int i = 0; i < cubeShape.Length; i++)
blockShape[(i + lastIndex)] = new Vector3(cubeShape[i].x + x, cubeShape[i].y + y, cubeShape[i].z + z);

lastIndex += cubeShape.Length;
}
}
}
*/

for(int i = 0; i < cubeShape.Length; i++) {
blockShape[i] = new Vector3(cubeShape[i].x, cubeShape[i].y, cubeShape[i].z);
}


}

void PlaneCreator(int passedX, int passedZ, int passedSize) {
//Keeps track where the for loop left off
int lastIndex = 0;

//Set how squares should be created on the x and z coords and the size of each triangle
int xBlocks = passedX;
int zBlocks = passedZ;
int size = passedSize;

//Set the "BASE" dimensions of the plane so that it's as big as the passedSize
Vector3[] tempPlaneShape = new Vector3[] {
new Vector3 (0, 0, 0), new Vector3 (0, 0, 1 * size), new Vector3 (1 * size, 0, 0),
new Vector3 (1 * size, 0, 0), new Vector3 (0, 0, 1 * size), new Vector3 (1 * size, 0, 1 * size)
};

terrainShape = new Vector3[tempPlaneShape.Length * xBlocks * zBlocks];

for(int x = 0; x < xBlocks * size; x+=size) {
for(int z = 0; z < zBlocks * size; z+=size) {
for(int i = 0; i < tempPlaneShape.Length; i++) {
terrainShape[(i + lastIndex)] = new Vector3(tempPlaneShape[i].x + x, tempPlaneShape[i].y, tempPlaneShape[i].z + z);
}

lastIndex += tempPlaneShape.Length;
}
}
}
}



Starting up the project. It first creates a cube with a grass texture.


Next, I run a little test to make sure the Editing Terrain button works. It creates a 1x1 square plane with 2 tris and 6 verts.


As you can see, creating a 10x10 plane now, with an increased size of 10 (meaning each triangle is 10 units in length. What units, I have no clue. But, 10 units.), the size of the plan increases a lot. Versus that tiny 1x1 plane you saw there.


Leaving the dimensions at 10x10 and increasing the size to 10000, you can see that the map is now 10x10 and 10000 in size. Totaling a 100,000x100,000 map. The quality is lowered a little bit in the texture. But, that's simply because it extends out of the range of the Unity Editor a little.


As you can see, it also works with just 2 tris. You get the same result with fewer tris.


Here, I am displaying a 10,000,000x10,000,000 plane with 2 tris. While the generation is instant, the quality diminishes greatly because, once again, it's out of range. If you look on the left, which is the scene view, you can see that the white grid lines are what represents the "limits" I speak of. It seems that rendering things bigger than that grid just gets a bit weird. While there's no RAM or CPU consumption, there's some minor errors.


Ignore the warnings in the console. Those are there because I commented things out in order to move forward with this project.

What then hell am I trying to achieve exactly?

Well, instead of an up and down terrain.. I'm looking to obtain my own voxel terrain. Have a look at some already made terrains, that of which either requires money to purchase or they're just not available for commercial projects:

Cubiquity (Open-source and non-commercial): https://www.youtube.com/watch?v=NLQozqoPMho (Planet Voxels), https://www.youtube.com/watch?v=auKomEGDNwA (Sculpting in Voxels)

TerraVol v2 (Commercial but Costs 85$): http://terravol.com/demo_v2/ (Nice demo with digging, filling, and change of textures. Even creates physics enabled balls and more).
Does that explain what it is?

Honestly, if you do have Unity, check out Cubiquity. It works in real-time as well. If you don't, try the demo for TerraVol. It's actually pretty damned good. Everything in there looks utterly beautiful and the frame rate doesn't seem to drop that bad. You still have to install the Unity WebPlayer. It's not big really.
Xirre, are you sure BYOND is made to handle this? No offensive but BYOND doesn't do 3D well.
In response to Zecronious
Zecronious wrote:
Xirre, are you sure BYOND is made to handle this? No offensive but BYOND doesn't do 3D well.

This is for Unity I believe o.o
Oh haha, it's because he posted it using dm tags. Looked like DM code to me.

My bad.
Haha, that's awful. Being dead serious and people think it's trolling.
Cloud Magic wrote:
Zecronious wrote:
Xirre, are you sure BYOND is made to handle this? No offensive but BYOND doesn't do 3D well.

I seriously thought this was trolling and a poke at how Tom turned down F_A's request for 3d

Lol. No. I'm done with trying to get BYOND 3D to come in to existence. 3 strikes and I'm out. So, I'm working in Unity3D now. I got a Networking system that can support thousands of users easily by saving bandwidth. :) Now, I want voxel terrains to work because I'm looking to allow real-time terrain changes. Such as digging and piling up dirt.

Back on topic, does anyone have any suggestions on how to go about this?