- Home /
QuadPlane to Sphere Terrain Generator Misaligned
Some time ago, I began looking into terrain generation alternatives. I wanted a game world in which upwards of thousands of players could play on one planet (for now, we'll ignore the hardware requirements, as there's numerous solutions for). My research showed that the normal Unity terrain generators wouldn't suffice. They have very limited functionality when it comes to edges. You could teleport the player, provide RollingGrid tiling methods, etc. None of these felt appropriate. Then I came across some weblinks defining a method called ClipMapping (alternatively, MipMapping), which was a GPU rendering of terrain based on a height map and clipped for various definition, mixed with some procedurally generated algorithms to produce more detailed terrain. This was impressive because it used a shader to focus all of the rendering on the GPU side.
That aside, I've recently come upon some documentation that depicts methods to procedurally generate through C# (CPU based) that helps me step into the process with a little more ease. The process very generically described building a plane mesh based on Perlin noise, and further along, Gaussian. By using various algorithms, you can generate hills, mountains, valleys, and just about anything else you could want, that would generate in as much detail as you wanted. Because this process was SO general, I had to research quite a few sites before I got a working model.
Now, to solve the previous edge-travel scenarios, I found some formulas and algorithms that allow you to take a plane mesh (or in our case, a generated terrain mesh) and warp it via a normalized radial expansion, and create 6 of these to generate a sphere.
[tldr;] This is where things get tricky. I have my planet, and the 6 sides, but they aren't lining up very smoothly. Definitely not close enough to write a sewing algorithm to fit them together without some rough stretching. I need some help looking through the code for errors, and solutions to said errors to help generate a quad-plane sphere, with the future ability to subdivide, each producing better detail (the local variable for detail is Samples).
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
[ExecuteInEditMode()]
[RequireComponent(typeof(MeshFilter))]
[RequireComponent(typeof(MeshRenderer))]
public class LiveMeshGen : MonoBehaviour {
public Texture2D heightmap = null;
public float Width {
get { return transform.localScale.x; }
}
[Range(0,254)]
public int Samples = 100;
public float DistMod = 9.9f;
public float Radius = 100f;
public bool NeedsUpdate = false;
public bool Generate = false;
public bool Spherify = false;
public Material mat;
private MeshRenderer mr;
private MeshFilter mf;
private MeshCollider mc;
void Start () {
}
void Update() {
if (mf == null)
mf = GetComponent<MeshFilter> ();
if (mr == null)
mr = GetComponent<MeshRenderer> ();
if (NeedsUpdate) {
if (Generate) {
heightmap = new Texture2D (Samples, Samples);
}
GenerateMesh();
mat.mainTexture = heightmap;
mr.material = mat;
NeedsUpdate = false;
}
}
void GenerateMesh ()
{
//float start_time = Time.time;
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch ();
sw.Start ();
float perSample = (float) 1 / (Samples-1);
Vector3[] verts = new Vector3[Samples*Samples];
List<int> tris = new List<int>();
//int[] tris = new int[verts.Length * 3];
Vector2[] uvs = new Vector2[Samples*Samples];
for (int i = 0, p = 0; i < Samples; i++) {
for (int j = 0; j < Samples; j++) {
// Current vertex
var center = new Vector3(i * perSample, 0, j * perSample);
// Height of this vertex (from heightmap)
float h = GetHeight(center.x, center.z);
center.y = h+(DistMod);
// offset to center
center = center-new Vector3(1f/2,0,1f/2);
verts[p] = center; // translate position
// UV coords in [0,1] space
uvs[p++] = new Vector2(i/(Samples - 1f),
j/(Samples - 1f));
// not right, or bottom border
if (j+1 < Samples && i+1 < Samples) {
tris.Add((j)*Samples+(i)); // triangulate this vertex
tris.Add((j)*Samples+(i+1)); // triangulate right vertex
tris.Add((j+1)*Samples+(i)); // triangulate bottom vertex
}
// not left, or bottom border
if (j+1 < Samples && i-1 >= 0) {
tris.Add((j)*Samples+(i)); // triangulate this vertex
tris.Add((j+1)*Samples+(i)); // triangulate bottom vertex
tris.Add((j+1)*Samples+(i-1)); // triangulate bottom-left vertex
}
}
}
// Generate the mesh object.
Mesh ret = new Mesh();
if (Spherify) {
for (int i = 0; i < verts.Length; i++)
verts[i] = verts[i].normalized * Radius;
}
ret.vertices = verts;
ret.triangles = tris.ToArray();
ret.uv = uvs;//.ToArray();
// Assign the mesh object and update it.
ret.RecalculateBounds();
ret.RecalculateNormals();
mf.mesh = ret;
sw.Stop ();
Debug.Log("ProceduralTerrain was generated in " + sw.ElapsedMilliseconds + "ms.");
}
public float GetHeight(float x, float z) {
return 0; // flat plane for debugging
//if (Generate)
//return Mathf.PerlinNoise (x, z);
//return heightmap.GetPixel (Mathf.RoundToInt((float)x/Width*heightmap.width), Mathf.RoundToInt((float)z/Width*heightmap.height)).r/256f;
}
}
Recap: In order to get the planes close to the proper spacing, I had to add a custom variable "DistMod" that is multiplied on the vertex height which changes it from a very malformed beanie cap, to a quad-sphere shape. None of the research pages I searched through showed much of any of this, so I'm sure theres something I'm missing. Also, as I wanted to make quick tweaks without compiling over and over, I ran this script in execution mode. I created a boolean NeedsUpdate to force a re-generation only when I wanted it. I'm sure there's other ways, but it worked.
[**EDIT**]: Redacted some work to work on the plane itself to ensure it was centered properly, and scaled properly. With redundant code removed, and everything working properly, I need to know the height-offset required to generate 1/6th of a sphere when the normalized vertex is multiplied by the radius.
Answer by Komak57 · Jan 01, 2015 at 04:53 PM
Answer: The initial offset was generated because the terrain had not been centered properly (every vertex must be offset). The next issue was the scaling. EDGE was a variable for Samples per Unity, but in order for Unity's localScale to matter, you have to adjust everything based on 1 unity width. Finally, to offset for 1/6th of a sphere, you need to push every vertex vertically (meaning height) by half of your unscaled height (0.5f).
Notes: A mesh can only be 4-254 vertecies squared because there is a limit of 65,000 vertecies in a mesh. I'll be writing a script to generate the sphere as one, and generate all the meshes so that I can subdivide when necessary.