i'm a newbie and i am following a tutorial on procedural landmass generation. However, my plane does not look right. It has a lot of seams/cracks. Is there someone who can point me in the right direction?
Below is my MeshGenerator scripts:
public static class MeshGenerator
{
public static MeshData GenerateTerrainMesh(float[,] heightMap, float heightMultiplier, AnimationCurve heightCurve)
{
int width = heightMap.GetLength(0);
int height = heightMap.GetLength(1);
float topLeftX = (width - 1) / -2f;
float topLeftZ = (height - 1) / 2f;
MeshData meshData = new MeshData(width, height);
int vertexIndex = 0;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
meshData.vertices[vertexIndex] = new Vector3(topLeftX + x, heightCurve.Evaluate(heightMap[x,y]) * heightMultiplier, topLeftZ - y);
meshData.uvs[vertexIndex] = new Vector2(x / (float)width, y / (float)height);
if (x < width - 1 && y < height - 1)
{
meshData.AddTriangle(vertexIndex, vertexIndex + width + 1, vertexIndex + width);
meshData.AddTriangle(vertexIndex, + width + 1, vertexIndex + 1);
}
vertexIndex++;
}
}
return meshData;
}
}
public class MeshData
{
public Vector3[] vertices;
public int[] triangles;
public Vector2[] uvs;
int triangleIndex;
public MeshData(int meshWidth, int meshHeight)
{
vertices = new Vector3[meshWidth * meshHeight];
uvs = new Vector2[meshWidth * meshHeight];
triangles = new int[(meshWidth-1) * (meshHeight-1)*6];
}
public void AddTriangle(int a, int b, int c)
{
triangles[triangleIndex] = a;
triangles[triangleIndex+1] = b;
triangles[triangleIndex+2] = c;
triangleIndex += 3;
}
public Mesh CreateMesh()
{
Mesh mesh = new Mesh();
mesh.vertices = vertices;
mesh.triangles = triangles;
mesh.uv = uvs;
mesh.RecalculateNormals();
return mesh;
}
}
You triangle indices are wrong, this is rather obvious since you have a bunch of triangle-shaped holes. Notably
meshData.AddTriangle(vertexIndex, + width + 1, vertexIndex + 1);
the second vertex of the second triangle is a constant value, and that is most likely incorrect
You should not need to keep a running total of vertexIndex, you should be perfectly able to compute the triangle indices from the grid indices:
var v1 = y * (width+1) + x; // You should have one more column of vertices than you have grid cells
var v2 = v2 + 1; // the vertex one column to the right
var v3 = v1 + width+1; // the vertex one row down
var v4 = v3 + 1;
meshData.AddTriangle(v1, v2, v4);
meshData.AddTriangle(v1, v4, v3);
You may need to invert the vertex order to ensure the normals are oriented correctly.
Related
I am relatively new to Terrain Generation in Unity, and am currently stuck in one place. I have followed Brackey's tutorial on terrain generation, and in that tutorial, he uses something like this:
float y = Mathf.PerlinNoise(x, z) * 2f;
To manipulate the height of the terrain. I also followed Sebastian Lague's tutorial on this. This is where I am stuck.
I want to use Sebastian Lague's Noise.cs file that he created (can be found on his GitHub) to manipulate the terrain height.
The reason is because this noise generator, rather than Mathf.PerlinNoise(), gives you a much better control over the texture it outputs. The problem is, Noise.cs will return a 2D float array, while Mathf.PerlinNoise() returns a 1D float value. Is there a way for Noise.cs to return a float value, just like Mathf's function?
Noise.cs:
using UnityEngine;
using System.Collections;
public static class Noise {
public static float[,] GenerateNoiseMap(int mapWidth, int mapHeight, int seed, float scale, int octaves, float persistance, float lacunarity, Vector2 offset) {
float[,] noiseMap = new float[mapWidth,mapHeight];
System.Random prng = new System.Random (seed);
Vector2[] octaveOffsets = new Vector2[octaves];
for (int i = 0; i < octaves; i++) {
float offsetX = prng.Next (-100000, 100000) + offset.x;
float offsetY = prng.Next (-100000, 100000) + offset.y;
octaveOffsets [i] = new Vector2 (offsetX, offsetY);
}
if (scale <= 0) {
scale = 0.0001f;
}
float maxNoiseHeight = float.MinValue;
float minNoiseHeight = float.MaxValue;
float halfWidth = mapWidth / 2f;
float halfHeight = mapHeight / 2f;
for (int y = 0; y < mapHeight; y++) {
for (int x = 0; x < mapWidth; x++) {
float amplitude = 1;
float frequency = 1;
float noiseHeight = 0;
for (int i = 0; i < octaves; i++) {
float sampleX = (x-halfWidth) / scale * frequency + octaveOffsets[i].x;
float sampleY = (y-halfHeight) / scale * frequency + octaveOffsets[i].y;
float perlinValue = Mathf.PerlinNoise (sampleX, sampleY) * 2 - 1;
noiseHeight += perlinValue * amplitude;
amplitude *= persistance;
frequency *= lacunarity;
}
if (noiseHeight > maxNoiseHeight) {
maxNoiseHeight = noiseHeight;
} else if (noiseHeight < minNoiseHeight) {
minNoiseHeight = noiseHeight;
}
noiseMap [x, y] = noiseHeight;
}
}
for (int y = 0; y < mapHeight; y++) {
for (int x = 0; x < mapWidth; x++) {
noiseMap [x, y] = Mathf.InverseLerp (minNoiseHeight, maxNoiseHeight, noiseMap [x, y]);
}
}
return noiseMap;
}
}
MeshGenerator.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent(typeof(MeshFilter))]
public class MeshGenerator : MonoBehaviour
{
Mesh mesh;
Vector3[] vertices;
int[] triangles;
public int xSize = 20;
public int zSize = 20;
int mapWidth;
int mapHeight;
int seed;
float scale;
int octaves;
float persistance;
float lacunarity;
Vector2 offset;
// Start is called before the first frame update
void Start()
{
// Initialize everything
mesh = new Mesh();
GetComponent<MeshFilter>().mesh = mesh;
CreateShape();
UpdateMesh();
}
void CreateShape()
{
// Creating the grid of vertices
vertices = new Vector3[(xSize + 1) * (zSize + 1)];
// Setting vertex positions
for (int i = 0, z = 0; z <= zSize; z++)
{
for (int x = 0; x <= xSize; x++)
{
//float y = Mathf.PerlinNoise(x * .3f, z * .3f) * 2f;
float y = Noise.GenerateNoiseMap(mapWidth, mapHeight, seed, scale, octaves, persistance, lacunarity, offset);
vertices[i] = new Vector3(x, y, z);
i++;
}
}
triangles = new int[xSize * zSize * 6];
int vert = 0;
int tris = 0;
for (int z = 0; z < zSize; z++)
{
for (int x = 0; x < xSize; x++)
{
triangles[tris + 0] = vert + 0;
triangles[tris + 1] = vert + xSize + 1;
triangles[tris + 2] = vert + 1;
triangles[tris + 3] = vert + 1;
triangles[tris + 4] = vert + xSize + 1;
triangles[tris + 5] = vert + xSize + 2;
vert++;
tris += 6;
}
vert++;
}
}
void UpdateMesh()
{
// Clear mesh data, reset with above vars and recalculate normals
mesh.Clear();
mesh.vertices = vertices;
mesh.triangles = triangles;
mesh.RecalculateNormals();
}
private void OnDrawGizmos()
{
if (vertices == null) return;
Gizmos.color = Color.red;
// Draw Vertex Gizmos
for (int i = 0; i < vertices.Length; i++)
{
Gizmos.DrawSphere(vertices[i], .1f);
}
}
}
I've figured it out, I would've had to put vertices[i] = new Vector3(x, y[x, z], z); Now I've already tried this, but I suppose visual studio bugged and did not save it properly. Anyways, yes, I just needed to use x/y to pick out my float.
I use the following code to generate a Cube as a single mesh. My purpose is to generate a sphere from it by normalizing as I have shown in the commented line (I just have to do that to all those statements in the following lines). The problem here is that the mesh changes from a cube to a flat plane as I keep increasing the resolution (parameter given as public int resolution).
(This code was inspired by this video https://youtu.be/QN39W020LqU . But I am using the technique in my own way as given by the following code, so that I can generate a single mesh instead of a combination of 6 meshes, this is required for my work)
[code=CSharp]
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class Sc_Planet : MonoBehaviour
{
[Range(2, 512)]
public int resolution = 2;
[Range(2, 256)]
public int radius = 10;
MeshFilter meshFilter;
void OnValidate()
{
Initialize();
}
void Initialize()
{
if (meshFilter == null)
{
GameObject meshObj = new GameObject("mesh_Planet");
meshObj.transform.parent = transform;
meshObj.AddComponent<MeshRenderer>().sharedMaterial = new Material(Shader.Find("Standard"));
meshFilter = meshObj.AddComponent<MeshFilter>();
meshFilter.sharedMesh = new Mesh();
}
int xmax = resolution + 1;
int ymax = resolution + 1;
float dx = 1.0f / resolution;
float dy = 1.0f / resolution;
Vector3[] vertsTop = new Vector3[xmax * ymax];
Vector3[] vertsRight = new Vector3[xmax * ymax];
Vector3[] vertsFront = new Vector3[xmax * ymax];
Vector3[] vertsBottom = new Vector3[xmax * ymax];
Vector3[] vertsLeft = new Vector3[xmax * ymax];
Vector3[] vertsBack = new Vector3[xmax * ymax];
for (int y = 0; y < ymax; y++)
{
for (int x = 0; x < xmax; x++)
{
float px = dx * x - 0.5f;
float py = dy * y - 0.5f;
int t = x + y * xmax;
//vertsTop[t] = new Vector3(py, 0.5f, px).normalized * radius;
vertsTop[t] = new Vector3(py, 0.5f, px);
vertsRight[t] = new Vector3(px, py, 0.5f);
vertsFront[t] = new Vector3(0.5f, px, py);
vertsBottom[t] = new Vector3(px, -0.5f, py);
vertsLeft[t] = new Vector3(py, px, -0.5f);
vertsBack[t] = new Vector3(-0.5f, py, px);
}
}
List<int> trianglesList = new List<int>();
for (int y = 0; y < ymax - 1; ++y)
{
for (int x = 0; x < xmax; ++x)
{
if (x % xmax != xmax - 1)
{
int f = x + y * xmax;
trianglesList.Add(f);
trianglesList.Add(f + 1);
trianglesList.Add(f + 1 + xmax);
trianglesList.Add(f);
trianglesList.Add(f + 1 + xmax);
trianglesList.Add(f + xmax);
}
}
}
List<Vector3> verts = new List<Vector3>();
Dictionary<Vector3, int> vdict = new Dictionary<Vector3, int>();
List<int> triangles = new List<int>();
int nextIndex = 0;
void addFace(Vector3 [] in_verts, List<int> in_triangles)
{
for(int i = 0; i < in_verts.Length; ++i)
{
if (!vdict.ContainsKey(in_verts[i]))
{
vdict.Add(in_verts[i], nextIndex);
verts.Add(in_verts[i]);
++nextIndex;
}
}
for(int i = 0; i < in_triangles.Count; ++i)
{
triangles.Add(vdict[in_verts[in_triangles[i]]]);
}
}
addFace(vertsTop, trianglesList);
addFace(vertsRight, trianglesList);
addFace(vertsFront, trianglesList);
addFace(vertsBottom, trianglesList);
addFace(vertsLeft, trianglesList);
addFace(vertsBack, trianglesList);
var mesh = meshFilter.sharedMesh;
mesh.Clear();
mesh.vertices = verts.ToArray();
mesh.triangles = triangles.ToArray();
mesh.RecalculateNormals();
}
}
[/code]
This code works in Blender (I used python to script it on Blender and it works very well for any resolution).
The only problem is that when I use this in Unity, the meshes become weird as I have shown in the images I have attached below.
At Resolution = 96 :
At Resolution = 122 :
At Resolution = 182 :
At Resolution = 344:
Why is this happening?
How should I correct it?
(I have also posted this in unity forums: Why cube mesh becomes a plane when in high resolution?)
Ok I found the answer. This is exceeding the limit of vertices on unity api for 16-bit based meshes. I had to change it to a 32-bit indexed mesh to correct it.
Details are in this docuemntaiton page : https://docs.unity3d.com/ScriptReference/Rendering.IndexFormat.html?_ga=2.9556401.501737799.1635227368-67181881.1629608252
I just had to add the code :
mesh.indexFormat = UnityEngine.Rendering.IndexFormat.UInt32;
That was it.
I am building a program where I can convert black&white images to a terrain.
I have a working code, but because Unity has a limit on the number of vertices a mesh can have The code doesn't work for big images.
So, I tried to split the image to smaller images and create mesh for each part. it works, but I get weird spacing between my meshes and I am not sure why.
Code:
public static class MeshGenerator
{
private static Transform CreateGameObjectWithMesh(Mesh m)
{
var go = new GameObject();
go.AddComponent<MeshFilter>().mesh = m;
go.AddComponent<MeshRenderer>();
m.RecalculateNormals();
return go.transform;
}
public static void Generate(float[,] heightsData)
{
int width = heightsData.GetLength(0);
int height = heightsData.GetLength(1);
float topLeftX = (width - 1) / -2f;
float topLeftZ = (height - 1) / 2f;
// Just for testing, I am working with 256X256 texture and split it to 4 parts. (would be modified when working)
for (int i = 0; i < 4; i++)
{
var startX = (i % 2) * 128;
var startY = i < 2 ? 0 : 128;
StartMeshCreationThread(mesh =>
{
var t = CreateGameObjectWithMesh(mesh);
t.position = new Vector3(topLeftX + startX + 128f / 2f, 0, topLeftZ - startY - 128f / 2f);
}, startX, startY, 128, 128, heightsData,
new Vector2(topLeftX, topLeftZ), (x, y) => new Vector2(x / (float) width, y / (float) height));
}
}
// Would create a new thread in the future.
private static void StartMeshCreationThread(Action<Mesh> onFinishedCallback, int startX, int startY, int width,
int height, float[,] heightsData, Vector2 orgTopLeft, Func<int, int, Vector2> getUVPosition)
{
var meshData = new MeshData (width, height);
float topLeftX = (width - 1) / -2f;
float topLeftZ = (height - 1) / 2f;
int vertexIndex = 0;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
var vertexPosition = new Vector3(topLeftX + x, heightsData[x + startX, y + startY] * 10f, topLeftZ - y);
meshData.AddVertexData(vertexIndex, vertexPosition, getUVPosition(x + startX, y + startY));
if (x < width - 1 && y < height - 1)
{
meshData.AddTriangle (vertexIndex, vertexIndex + width + 1, vertexIndex + width);
meshData.AddTriangle (vertexIndex + width + 1, vertexIndex, vertexIndex + 1);
}
vertexIndex++;
}
}
onFinishedCallback(meshData.CreateMesh());
}
private class MeshData
{
private readonly Vector3[] vertices;
private readonly Vector2[] uvs;
private readonly int[] triangles;
private int triangleIndex;
public MeshData(int width, int height)
{
vertices = new Vector3[width * height];
uvs = new Vector2[width * height];
triangles = new int[(width - 1) * (height - 1) * 6];
triangleIndex = 0;
}
public void AddVertexData(int index, Vector3 vertexPosition, Vector2 uvPosition)
{
vertices[index] = vertexPosition;
uvs[index] = uvPosition;
}
public void AddTriangle(int v1, int v2, int v3)
{
triangles[triangleIndex + 0] = v1;
triangles[triangleIndex + 1] = v2;
triangles[triangleIndex + 2] = v3;
triangleIndex += 3;
}
public Mesh CreateMesh()
{
var mesh = new Mesh {vertices = vertices, triangles = triangles, uv = uvs};
return mesh;
}
}
}
Result (for 256X256) texture:
(main source)
Project: intelligence scissors.
The first part of the project is to load an image (in the form of RGBPixel 2d array) then to construct a graph of weights to use it later to determine the shortest path between 2 points on the image (the 2 points will be determined by an anchor and a free point..In short i will have the source and the destination points).
I have a function that open the image and return a RGBPixel 2d array already made.Now image is loaded i want to construct the graph to do the i should use a function called CalculatePixelEnergies here is the code
public static Vector2D CalculatePixelEnergies(int x, int y, RGBPixel[,] ImageMatrix)
{
if (ImageMatrix == null) throw new Exception("image is not set!");
Vector2D gradient = CalculateGradientAtPixel(x, y, ImageMatrix);
double gradientMagnitude = Math.Sqrt(gradient.X * gradient.X + gradient.Y * gradient.Y);
double edgeAngle = Math.Atan2(gradient.Y, gradient.X);
double rotatedEdgeAngle = edgeAngle + Math.PI / 2.0;
Vector2D energy = new Vector2D();
energy.X = Math.Abs(gradientMagnitude * Math.Cos(rotatedEdgeAngle));
energy.Y = Math.Abs(gradientMagnitude * Math.Sin(rotatedEdgeAngle));
return energy;
}
This function use CalculateGradientAtPixel, Here is the code in case you want it.
private static Vector2D CalculateGradientAtPixel(int x, int y, RGBPixel[,] ImageMatrix)
{
Vector2D gradient = new Vector2D();
RGBPixel mainPixel = ImageMatrix[y, x];
double pixelGrayVal = 0.21 * mainPixel.red + 0.72 * mainPixel.green + 0.07 * mainPixel.blue;
if (y == GetHeight(ImageMatrix) - 1)
{
//boundary pixel.
for (int i = 0; i < 3; i++)
{
gradient.Y = 0;
}
}
else
{
RGBPixel downPixel = ImageMatrix[y + 1, x];
double downPixelGrayVal = 0.21 * downPixel.red + 0.72 * downPixel.green + 0.07 * downPixel.blue;
gradient.Y = pixelGrayVal - downPixelGrayVal;
}
if (x == GetWidth(ImageMatrix) - 1)
{
//boundary pixel.
gradient.X = 0;
}
else
{
RGBPixel rightPixel = ImageMatrix[y, x + 1];
double rightPixelGrayVal = 0.21 * rightPixel.red + 0.72 * rightPixel.green + 0.07 * rightPixel.blue;
gradient.X = pixelGrayVal - rightPixelGrayVal;
}
return gradient;
}
In my code of graph construction i decided to make a 2d double array to hold the weights, here what i do but it seems to be a wrong construction
public static double [,] calculateWeights(RGBPixel[,] ImageMatrix)
{
double[,] weights = new double[1000, 1000];
int height = ImageOperations.GetHeight(ImageMatrix);
int width = ImageOperations.GetWidth(ImageMatrix);
for (int y = 0; y < height - 1; y++)
{
for (int x = 0; x < width - 1; x++)
{
Vector2D e;
e = ImageOperations.CalculatePixelEnergies(x, y, ImageMatrix);
weights[y + 1, x] = 1 / e.X;
weights[y, x + 1] = 1 / e.Y;
}
}
return weights;
}
an example for an image
an other example for an image
I'm following this tutorial set: https://www.youtube.com/watch?v=owBt9SNKXCI&index=6&list=PLbghT7MmckI4qGA0Wm_TZS8LVrqS47I9R to dynamically build a tile map layout. It works to a point, but it generates a very strange layout with 128 x 128 sized tiles.
Clearly that strange partitioning shouldn't be happening, but I cannot seem to track down what's going on to cause it. Here is my version of the code, which is mostly identical to quill18creates's version sans a few small differences:
using UnityEngine;
using System.Collections;
[ExecuteInEditMode]
public class TileMap : MonoBehaviour {
public int size_x = 100;
public int size_z = 50;
public float tileSize = 1.0f;
public Texture2D terrainTiles;
int tileResolution = 128;
// Use this for initialization
void Start () {
BuildMesh();
}
Color[][] ChopUpTiles() {
int numTilesPerRow = terrainTiles.width / tileResolution;
int numRows = terrainTiles.height / tileResolution;
Color[][] tiles = new Color[numTilesPerRow*numRows][];
for(int y=0; y < numRows; y++) {
for(int x=0; x < numTilesPerRow; x++) {
tiles[y * numTilesPerRow + x] = terrainTiles.GetPixels( x*tileResolution , y*tileResolution, tileResolution, tileResolution );
}
}
return tiles;
}
void BuildTexture() {
//DTileMap map = new DTileMap(size_x, size_z);
int texWidth = size_x * tileResolution;
int texHeight = size_z * tileResolution;
Texture2D texture = new Texture2D(texWidth, texHeight);
Color[][] tiles = ChopUpTiles();
for(int y=0; y < size_z; y++) {
for(int x=0; x < size_x; x++) {
Color[] p = tiles[Mathf.RoundToInt(Random.Range(0, 5))];
texture.SetPixels(x * tileResolution, y * tileResolution, tileResolution, tileResolution, p);
}
}
//texture.filterMode = FilterMode.Bilinear;
texture.wrapMode = TextureWrapMode.Clamp;
texture.Apply();
MeshRenderer mesh_renderer = GetComponent<MeshRenderer>();
mesh_renderer.sharedMaterials[0].mainTexture = texture;
}
public void BuildMesh() {
int numTiles = size_x * size_z;
int numTris = numTiles * 2;
int vsize_x = size_x + 1;
int vsize_z = size_z + 1;
int numVerts = vsize_x * vsize_z;
// Generate the mesh data
Vector3[] vertices = new Vector3[ numVerts ];
Vector3[] normals = new Vector3[numVerts];
Vector2[] uv = new Vector2[numVerts];
int[] triangles = new int[ numTris * 3 ];
int x, z;
for(z=0; z < vsize_z; z++) {
for(x=0; x < vsize_x; x++) {
vertices[ z * vsize_x + x ] = new Vector3( x*tileSize, 0, -z*tileSize );
normals[ z * vsize_x + x ] = Vector3.up;
uv[ (z * vsize_x) + x ] = new Vector2( (float)x / size_x, (float)z / size_z );
}
}
Debug.Log ("Done Verts!");
for(z=0; z < size_z; z++) {
for(x=0; x < size_x; x++) {
int squareIndex = z * size_x + x;
int triOffset = squareIndex * 6;
triangles[triOffset + 0] = z * vsize_x + x + 0;
triangles[triOffset + 2] = z * vsize_x + x + vsize_x + 0;
triangles[triOffset + 1] = z * vsize_x + x + vsize_x + 1;
triangles[triOffset + 3] = z * vsize_x + x + 0;
triangles[triOffset + 5] = z * vsize_x + x + vsize_x + 1;
triangles[triOffset + 4] = z * vsize_x + x + 1;
}
}
// Create a new Mesh and populate with the data
Mesh mesh = new Mesh();
mesh.vertices = vertices;
mesh.triangles = triangles;
mesh.normals = normals;
mesh.uv = uv;
// Assign our mesh to our filter/renderer/collider
MeshFilter mesh_filter = GetComponent<MeshFilter>();
MeshCollider mesh_collider = GetComponent<MeshCollider>();
mesh_filter.mesh = mesh;
mesh_collider.sharedMesh = mesh;
BuildTexture();
}
}
I don't get exactly what part is wrong in the image but I think it is that the same tiles are lumping together.
I tried your code and it works well for me. But I guess the following part could be causing the lumping together problem for you:
Color[] p = tiles[Mathf.RoundToInt(Random.Range(0, 5))];
Instead you should do:
Color[] p = tiles[Random.Range(0, 5)];
Because, the other way, Random is generating float numbers and maybe they are near the each other that rounding them to the integer gives same tile. Give it a try.
Also just reminding, make sure the width and the height of your texture is divisible by 128.
Well, it was definitely a size issue, but there was also an issue with positioning. Tiles have to start at the bottom-left corner for the coordinate system to find them.