How to approach voxel graphics using the unity engine - unity3d

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class VoxelRenderer : MonoBehaviour
{
public Mesh mesh;
public Material material;
[SerializeField]
public int size = 10;
[SerializeField]
public int offset = -10;
// Start is called before the first frame update
void Start()
{
}
void Update()
{
int limit = size;
for (int x = 0; x < limit; x++)
{
for (int y = 0; y < limit; y++)
{
for (int z = 0; z < limit; z++)
{
Graphics.DrawMesh(mesh, new Vector3(x*2 +offset, y*2 + offset, z * 2 + offset), Quaternion.identity, material, 0);
}
}
}
}
}
Now this renders fine for size 10~ but if i go for 100+ (which is obviously low for which is required for a planet) i get massive fps drops presumably because of 10000's of batches per frame. Am i going the wrong way here? Or is it an occlussion issue or something similar i am running into here?

Draw calls can really hurt your performance. You can use batching and GPU instancing, but I think the best solution is using CombineMeshes. By combining 1000s of meshes into 1, you can get that many fewer draw calls.
Unity has a limit of slightly less than 2^16=65536 vertices per mesh. This prevents you from having a giant voxel as only one mesh. I suggest that you combine groups of a few thousand meshes ("chunks") and when you need to update the voxel data, only redo the mesh groups whose data changed. This not only gets around the vertex limit, but also saves performance by recombining only the meshes that need it.
In a minecraft-like game that I worked on, I made a script that generated cube blocks without drawing any hidden faces (e.g. two blocks touching each other wouldn't generate faces between them), but this will only work if your meshes are cubes and the code is messy.
I think the best solution would be to make two MonoBehaviours.
A Chunk class could handle may voxels, but not the whole planet, and the VoxelRenderer can handle spawning as many chunks are needed to make the whole planet. Make a prefab with a MeshRenderer, this Chunk component, and the right material assigned.
Please note that this is just a quick skeleton of what you'd need.
Chunk:
public class Chunk : MonoBehaviour
{
public Mesh mesh;
// Start is called before the first frame update
void UpdateChunk()
{
CombineInstance[] ci = new CombingInstance[CHUNK_SIZE*CHUNK_SIZE*CHUNK_SIZE];
for(int x = 0; x < VoxelRenderer.CHUNK_SIZE;x++){
//add 2 more for loops for y and z
ci[x*CHUNK_SIZE*CHUNK_SIZE + y*CHUNK_SIZE + z].Transform =...
}
GetComponent<MeshFilter>().mesh = new Mesh();
GetComponent<MeshFilter>().mesh.CombineMeshes(ci);
}
VoxelRenderer:
public class VoxelRenderer : MonoBehaviour
{
public const int CHUNK_SIZE = 8;
public GameObject chunkPrefab;
[SerializeField]
public int size = 10;
[SerializeField]
public int offset = -10;
private Chunk[,,] chunks;
// Start is called before the first frame update
void Start()
{
int limit = size;
int limitDividedByChunkSize = Mathf.RountToInt(limit/(float)CHUNK_SIZE);
chunks = new Chunk[limitDividedByChunkSize,limitDividedByChunkSize,limitDividedByChunkSize];
for (int x = 0; x < limit; x+=CHUNK_SIZE)
{
for (int y = 0; y < limit; y+=CHUNK_SIZE)
{
for (int z = 0; z < limit; z+=CHUNK_SIZE)
{
GameObject g = Instantiate(chunkPrefab, new Vector3(x*2*CHUNK_SIZE +offset, y*2*CHUNK_SIZE + offset, z * 2*CHUNK_SIZE + offset));
Chunks[x,y,z] = g.GetComponent<Chunk>();
}
}
}
}
//this updates all voxel chunks, for minor changes to the voxel data use chunks[x,y,z].UpdateChunk();
void UpdateAllVoxelChunks()
{
for (int x = 0; x < chunks.Length; x++)
{
for (int y = 0; y < chunks[x].Length; y++)
{
for (int z = 0; z < chunks[y].Length; z++)
{
Chunks[x,y,z].UpdateChunk();
}
}
}
}
}

Related

Problem in Unity: ArgumentOutOfRangeException: Index was out of range

I always have the following problem when I execute the code in Unity:
ArgumentOutOfRangeException: Index was out of range. Must be non-negative and less than the size of the collection.
Parameter name: index
The property Grid I set directly in the Unity editor with a value of 10 for x and y.
public class World : MonoBehaviour
{
public Room[,] Dungeon { get; set; }
public Vector2 Grid;
private void Awake()
{
Dungeon = new Room[(int)Grid.x, (int)Grid.y];
StartCoroutine(GenerateFloor());
}
public IEnumerator GenerateFloor()
{
for (int x = 0; x < Grid.x; x++)
{
for (int y = 0; y < Grid.y; y++)
{
Dungeon[x, y] = new Room
{
RoomIndex = new Vector2(x, y)
};
}
}
yield return new WaitForSeconds(3);
Vector2 exitLocation = new Vector2((int)Random.Range(0, Grid.x), (int)Random.Range(0, Grid.y));
Dungeon[(int)exitLocation.x, (int)exitLocation.y].Exit = true;
Dungeon[(int)exitLocation.x, (int)exitLocation.y].Empty = false;
Debug.Log("Exit is at: " + exitLocation); }}
I hope you can help me in this case
It's pretty hard to see an error like this, without debugging the code. But in General,try to break public Vector2 Grid; 2 int variables and to use them,so do not need to be explicitly cast to an int, and your code will become significantly readable if you want x==y then we can make the test in Awake and fail to accidentally ask the wrong value. And Vector2 exitLocation can be replaced with Vector2Int or 2 variables.

MeshRenderer has wrong bounds when rotated

When I try to get the bounds of my models (created in Blender) and show them in Inspector:
As you can see the bounds are correct when the objects are not rotated. But when they are (left-most object) bounds start getting totally wrong.
Here is a script that shows / gets the bounds:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GetBounds : MonoBehaviour
{
public MeshRenderer mesh_renderer = null;
public bool show_bounds = false;
private void OnDrawGizmos()
{
if (!show_bounds) return;
Gizmos.DrawWireCube(mesh_renderer.bounds.center, mesh_renderer.bounds.size);
Gizmos.DrawWireSphere(mesh_renderer.bounds.center, 0.3f);
}
}
How can I fix this?
In this thread I have come across this image which explains it pretty much
Unity dos not recalculate the Mesh.bounds all the time except when you add a mesh for the first time or "manually" invoke Mesh.RecalculateBounds.
It then uses this local space Mesh.bounds in order to calculate the translated, scaled and rotated Renderer.bounds in global space based on the Mesh.bounds. This way it always has to iterate a fixed amount of 8 vertices of the bounding box.
There was also a solution provided if you want to get the exact bounds calculated directly from the vertices. I adopted and cleaned it up a bit
public class GetBounds : MonoBehaviour
{
public MeshRenderer mesh_renderer;
public bool show_bounds;
public MeshFilter meshFilter;
public Mesh mesh;
private void OnDrawGizmos()
{
if (!mesh_renderer) return;
if (!show_bounds) return;
if (!meshFilter) meshFilter = mesh_renderer.GetComponent<MeshFilter>();
if (!meshFilter) return;
if (!mesh) mesh = meshFilter.mesh;
if (!mesh) return;
var vertices = mesh.vertices;
if (vertices.Length <= 0) return;
// TransformPoint converts the local mesh vertice dependent on the transform
// position, scale and orientation into a global position
var min = transform.TransformPoint(vertices[0]);
var max = min;
// Iterate through all vertices
// except first one
for (var i = 1; i < vertices.Length; i++)
{
var V = transform.TransformPoint(vertices[i]);
// Go through X,Y and Z of the Vector3
for (var n = 0; n < 3; n++)
{
max = Vector3.Max(V, max);
min = Vector3.Min(V, min);
}
}
var bounds = new Bounds();
bounds.SetMinMax(min, max);
// ust to compare it to the original bounds
Gizmos.DrawWireCube(mesh_renderer.bounds.center, mesh_renderer.bounds.size);
Gizmos.DrawWireSphere(mesh_renderer.bounds.center, 0.3f);
Gizmos.color = Color.green;
Gizmos.DrawWireCube(bounds.center, bounds.size);
Gizmos.DrawWireSphere(bounds.center, 0.3f);
}
}
Result:
In WHITE: The MeshRenderer.bounds
In GREEN: The "correct" calculated vertex bounds

How do we update the position of an object consistetly in update() method?

We're using Verlet method to move a Tukey. But it only moves it once and not continuously. The codes for updating the position of the Turkey is in Update() method so it should rune every frame. But It only runed once.
Futhermore, we put three times the codes for updating the position of the Turkey Line rendered object in the update method and it seems that the new position of the Turkey only move as if we moved it once.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class GenerateTurkeys : MonoBehaviour
{
public LineRenderer lineRenderer;
// Start is called before the first frame update
//int numberOfTurkeys;
static int NUM_PARTICLES = 26;
float fTimeStep;
Vector3[] m_position = new Vector3[NUM_PARTICLES];
Vector3[] m_acceleration = new Vector3[NUM_PARTICLES];
Vector3[] m_oldPosition = new Vector3[NUM_PARTICLES];
Vector3[] m_newPosition = new Vector3[NUM_PARTICLES];
void Start()
{
lineRenderer = gameObject.GetComponent<LineRenderer>();
lineRenderer.GetPositions(m_position);
for(int i = 0; i < m_acceleration.Length; i++)
{
m_acceleration[i] = new Vector3(0.0f, -9.8f, 0.0f);
}
fTimeStep = 5.5f;
}
// Verlet integration step void ParticleSystem::
void Verlet()
{
var random_direction = Random.Range(-1, 1);
for (int i = 0; i < NUM_PARTICLES; i++)
{
m_newPosition[i] = 2 * m_position[i] - m_oldPosition[i] + m_acceleration[i] * fTimeStep * fTimeStep;
m_oldPosition[i] = m_position[i];
}
}
// Update is called once per frame
void FixedUpdate()
{
Verlet();
lineRenderer.SetPositions(m_newPosition);
}
}
First of all, FixedUpdate is used by the physics engine and updates differently from the normal Update method. Unless what you want to do has to be synced with the physics engine then you should use Update.
Secondly, your m_position vector is never updated, you call lineRenderer.getPositions only in the Start method. Because of this, your m_oldPositions will always be the same and the position won't change. To correct this your Verlet method should also update the m_position vector after the new position has been computed.
Something like this:
void Verlet()
{
var random_direction = Random.Range(-1, 1);
for (int i = 0; i < NUM_PARTICLES; i++)
{
m_newPosition[i] = 2 * m_position[i] - m_oldPosition[i] + m_acceleration[i] * fTimeStep * fTimeStep;
m_oldPosition[i] = m_position[i];
m_position[i] = m_newPosition[i];
}
}

HoloLens Draw a graph

What is the best way to draw a graph for the HoloLens in unity?
I am new to this platform and have no idea which packages will work and which dont, the graph gets data dynamically.
EDIT: I have tried LineRenderer but it seems very limited in version 5.4 of Unity
A possible Solution for drawing a 3D-Graph is using a particle system:
Simple Example for a Component Script for a particle system:
public class Graph: MonoBehaviour {
//Particle-Resolution of the Graph
[Range(10, 100)]
public int resolution = 10;
private int currentResolution;
private ParticleSystem.Particle[] points;
void Start()
{
currentResolution = resolution;
points = new ParticleSystem.Particle[resolution];
float increment = 1f / (resolution - 1);
for (int i = 0; i < resolution; i++)
{
float x = i * increment;
points[i].position = new Vector3(x, 0f, 0f);
points[i].startColor = new Color(0f, 0f, 0f);
points[i].startSize = 0.1f;
}
}
void Update()
{
if ((currentResolution != resolution) || (points == null))
{
CreatePoints();
}
FunctionDelegate f = functionDelegates[(int)function];
for (int i = 0; i < points.Length; i++)
{
Vector3 p = points[i].position;
p.y = Sine(p.x);
points[i].position = p;
Color c = points[i].GetCurrentColor(GetComponent<ParticleSystem>());
c.g = p.y;
c.r = 1f - p.y;
points[i].startColor = c;
}
GetComponent<ParticleSystem>().SetParticles(points, points.Length);
}
private static float Sine(float x)
{
return 0.5f + 0.5f * Mathf.Sin(2 * Mathf.PI * x + Time.timeSinceLevelLoad);
}
}
A good tutorial for drawing 2D/3D graphs (including this example) with a particle system from CatLikeCoding (Jasper Flick). Refer to: http://catlikecoding.com/unity/tutorials/graphs/. It's a bit outdated and you must use startSize/startColor instead the depreceated color/size-Properties in this case.
But i'have testet it with the hololens allready and it worked fine. Some experiments with the HoloToolkit shaders for a better performance are necessary if you have a big amount of particles :-)
If you have further questions: Just ask me.

Unity Roguelike Project: Argument Out of Range Exception

I'm getting an Argument Out of Range Exception for this script I'm following in a Unity tutorial.
Here's the exception:
ArgumentOutOfRangeException: Argument is out of range.
Parameter name: index
System.Collections.Generic.List`1[UnityEngine.Vector3].get_Item (Int32 index) (at /Users/builduser/buildslave/mono-runtime-and-classlibs/build/mcs/class/corlib/System.Collections.Generic/Dictionary.cs:225)
BoardManager.RandomPosition () (at Assets/Scripts/BoardManager.cs:75)
And here's the actual script for the project:
using UnityEngine;
using System; //Allows serializable attribute, which modifies how variables appear
using System.Collections.Generic; //Generic allows use of lists
using Random = UnityEngine.Random; //Used because Random class is in both System and Unity engine namespaces
public class BoardManager : MonoBehaviour
{
[Serializable]
public class Count
{
public int maximum;
public int minimum;
public Count (int min, int max) //Assignment constructor for Count to set values of min, max when we declare a new Count
{
minimum = min;
maximum = max;
}
}
public int columns = 8; //8x8 gameboard
public int rows = 8;
public Count wallCount = new Count(5, 9); //Specifies random range for walls to spawn in each level
public Count foodCount = new Count(1, 5); //Random range for food spawns
public GameObject exit; //Variable that holds exit prefab
public GameObject[] floorTiles; //Game Object Arrays hold our different prefabs to choose between
public GameObject[] wallTiles;
public GameObject[] foodTiles;
public GameObject[] enemyTiles;
public GameObject[] outerWallTiles;
private Transform boardHolder; //Manages Hierarchy
private List<Vector3> gridPositions = new List<Vector3>(); //Declares a private list of Vector3s
//Tracks all possible positions on gameboard, and keeps track of whether object has been spawned in that position or not
void InitialiseList()
{
gridPositions.Clear();
for (int x = 1; x < columns - 1; x++) //Nested FOR loops to fill list with each of the positions on our gameboard as a Vector3
{
for (int y = 1; y < rows - 1; y++)
{
gridPositions.Add(new Vector3(x, y, 0f)); //Adds x and y positions to the list
}
}
}
void BoardSetup() //Sets up outer wall and floor of gameboard
{
boardHolder = new GameObject("Board").transform;
for (int x = -1; x < columns + 1; x++)
{
for (int y = -1; y < rows + 1; y++)
{
GameObject toInstantiate = floorTiles[Random.Range(0, floorTiles.Length)]; //Defines a new GameObject
if (x == -1 || x == columns || y == -1 || y == rows)
{
toInstantiate = outerWallTiles[Random.Range(0, outerWallTiles.Length)];
}
GameObject instance = Instantiate(toInstantiate, new Vector3(x, y, 0f), Quaternion.identity) as GameObject;
//Instantiates a new GameObject. Rotation is always the same. Used as GameObject.
instance.transform.SetParent(boardHolder);
}
}
}
Vector3 RandomPosition()
{
try
{
int randomIndex = Random.Range(0, gridPositions.Count);
Vector3 randomPosition = gridPositions[randomIndex]; //THROWING AN EXCEPTION
gridPositions.RemoveAt(randomIndex); //Prevents duplicate position spawns
return randomPosition;
}
catch (Exception ex1)
{
Debug.Log(ex1);
throw;
}
}
void LayoutObjectAtRandom(GameObject[] tileArray, int minimum, int maximum)
{
int objectCount = Random.Range(minimum, maximum + 1);
for (int i = 0; i < objectCount; objectCount++)
{
Vector3 randomPosition = RandomPosition();
GameObject tileChoice = tileArray[Random.Range(0, tileArray.Length)];
Instantiate(tileChoice, randomPosition, Quaternion.identity);
}
}
public void SetupScene(int level) //Called by the GameManager
{
BoardSetup();
InitialiseList();
LayoutObjectAtRandom(wallTiles, wallCount.minimum, wallCount.maximum);
LayoutObjectAtRandom(foodTiles, foodCount.minimum, foodCount.maximum);
int enemyCount = (int)Mathf.Log(level, 2f); //Scales enemy count based on level
LayoutObjectAtRandom(enemyTiles, enemyCount, enemyCount);
Instantiate(exit, new Vector3(columns - 1, rows - 1, 0f), Quaternion.identity);
}
}
I'm assuming that the RandomPositions() function is throwing the exception, though I am unable to find a problem with the code. Does anyone see the problem?