- Home /
how to make a planet
how to edit a spherical mesh already created in Autodesk Maya and edit its surface procedural to create a realistic planet it's possible?
example . . . . . . . sorry for my bad english
Answer by aldonaletto · Jan 01, 2018 at 02:42 PM
You could use Mathf.PerlinNoise(x,y) in order to generate "random" heights at each vertex. This function returns randomized and smoothed values between 0 and 1 according to the x,y coordinates passed to it, much like it was reading the values from a texture. We can get each vertex, combine somehow x,y with z in order to get a 2D pair and pass it to PerlinNoise to get the vertex height modifier. Since this value is between 0 and 1, we can also assign it as (height, 0) to the vertex uv coordinate, what allows us to have different colors according to the height via albedo texture.
There's a problem, however: it's necessary to recalculate normals after changing the vertices, but the Unity's builtin function Mesh.RecalculateNormals doesn't work fine in this case (it can't recognize neighbor vertices unless they're shared by neighbor triangles). The quick solution is to adopt Chris Marango's RecalculateNormals interesting function: it finds all neighbor vertices without relying on they being shared or not. It's a good idea to visit his blog, but as a convenience I've posted the script here too - just save it as a new C# script in your project.
The script that adds Perlin bumps to a mesh follows below, and must be attached to any mesh you want to modify. It includes a simple rotation function in Update in order to better demonstrate its results - just remove the rotation code if you don't need it.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class PerlinHills : MonoBehaviour {
public float height=0.2f; // max height (model space)
public float mult=200f; // perlin granularity
public float vel=60f; // planet rotation speed degrees/second
Vector3 offset;
void Start () {
// select a random offset
offset = mult * Random.insideUnitSphere;
CreateHills ();
}
float PerlinHeight(Vector3 v){
Vector3 pos = v * mult + offset; // apply offset and mult
pos.y += pos.z; pos.x -= pos.z; // combine x,y with z
return Mathf.PerlinNoise (pos.x, pos.y); // return perlin noise
}
void CreateHills(){
Mesh mesh = GetComponent<MeshFilter> ().mesh;
Vector3[] verts = mesh.vertices;
Vector2[] uvs = mesh.uv;
for (int i = 0; i < verts.Length; i++) {
Vector3 v = verts [i];
float h = PerlinHeight (v); // get perlin value (0..1)
verts [i] = v * (1 + height * h); // move vertex away from center by h*height
uvs [i] = new Vector2 (h, 0); // set uv to allow different colors according to height
}
mesh.vertices = verts; // set the modified vertices...
mesh.uv = uvs; // and uvs
// update normals without Unity limitations
NormalSolver.RecalculateNormals (mesh, 60f);
}
void Update () {
// demo: just rotate planet
transform.Rotate (Vector3.up, Time.deltaTime * vel);
}
}
This is Chris Marango's NormalSolver class - just save it in your project as a new C# script (don't assign it to any object):
/*
* The following code was taken from: http://schemingdeveloper.com
*
* Visit our game studio website: http://stopthegnomes.com
*
* License: You may use this code however you see fit, as long as you include this notice
* without any modifications.
*
* You may not publish a paid asset on Unity store if its main function is based on
* the following code, but you may publish a paid asset that uses this code.
*
* If you intend to use this in a Unity store asset or a commercial project, it would
* be appreciated, but not required, if you let me know with a link to the asset. If I
* don't get back to you just go ahead and use it anyway!
*/
using System;
using System.Collections.Generic;
using UnityEngine;
public static class NormalSolver
{
/// <summary>
/// Recalculate the normals of a mesh based on an angle threshold. This takes
/// into account distinct vertices that have the same position.
/// </summary>
/// <param name="mesh"></param>
/// <param name="angle">
/// The smoothing angle. Note that triangles that already share
/// the same vertex will be smooth regardless of the angle!
/// </param>
public static void RecalculateNormals(this Mesh mesh, float angle) {
var cosineThreshold = Mathf.Cos(angle * Mathf.Deg2Rad);
var vertices = mesh.vertices;
var normals = new Vector3[vertices.Length];
// Holds the normal of each triangle in each sub mesh.
var triNormals = new Vector3[mesh.subMeshCount][];
var dictionary = new Dictionary<VertexKey, List<VertexEntry>>(vertices.Length);
for (var subMeshIndex = 0; subMeshIndex < mesh.subMeshCount; ++subMeshIndex) {
var triangles = mesh.GetTriangles(subMeshIndex);
triNormals[subMeshIndex] = new Vector3[triangles.Length / 3];
for (var i = 0; i < triangles.Length; i += 3) {
int i1 = triangles[i];
int i2 = triangles[i + 1];
int i3 = triangles[i + 2];
// Calculate the normal of the triangle
Vector3 p1 = vertices[i2] - vertices[i1];
Vector3 p2 = vertices[i3] - vertices[i1];
Vector3 normal = Vector3.Cross(p1, p2).normalized;
int triIndex = i / 3;
triNormals[subMeshIndex][triIndex] = normal;
List<VertexEntry> entry;
VertexKey key;
if (!dictionary.TryGetValue(key = new VertexKey(vertices[i1]), out entry)) {
entry = new List<VertexEntry>(4);
dictionary.Add(key, entry);
}
entry.Add(new VertexEntry(subMeshIndex, triIndex, i1));
if (!dictionary.TryGetValue(key = new VertexKey(vertices[i2]), out entry)) {
entry = new List<VertexEntry>();
dictionary.Add(key, entry);
}
entry.Add(new VertexEntry(subMeshIndex, triIndex, i2));
if (!dictionary.TryGetValue(key = new VertexKey(vertices[i3]), out entry)) {
entry = new List<VertexEntry>();
dictionary.Add(key, entry);
}
entry.Add(new VertexEntry(subMeshIndex, triIndex, i3));
}
}
// Each entry in the dictionary represents a unique vertex position.
foreach (var vertList in dictionary.Values) {
for (var i = 0; i < vertList.Count; ++i) {
var sum = new Vector3();
var lhsEntry = vertList[i];
for (var j = 0; j < vertList.Count; ++j) {
var rhsEntry = vertList[j];
if (lhsEntry.VertexIndex == rhsEntry.VertexIndex) {
sum += triNormals[rhsEntry.MeshIndex][rhsEntry.TriangleIndex];
} else {
// The dot product is the cosine of the angle between the two triangles.
// A larger cosine means a smaller angle.
var dot = Vector3.Dot(
triNormals[lhsEntry.MeshIndex][lhsEntry.TriangleIndex],
triNormals[rhsEntry.MeshIndex][rhsEntry.TriangleIndex]);
if (dot >= cosineThreshold) {
sum += triNormals[rhsEntry.MeshIndex][rhsEntry.TriangleIndex];
}
}
}
normals[lhsEntry.VertexIndex] = sum.normalized;
}
}
mesh.normals = normals;
}
private struct VertexKey
{
private readonly long _x;
private readonly long _y;
private readonly long _z;
// Change this if you require a different precision.
private const int Tolerance = 100000;
// Magic FNV values. Do not change these.
private const long FNV32Init = 0x811c9dc5;
private const long FNV32Prime = 0x01000193;
public VertexKey(Vector3 position) {
_x = (long)(Mathf.Round(position.x * Tolerance));
_y = (long)(Mathf.Round(position.y * Tolerance));
_z = (long)(Mathf.Round(position.z * Tolerance));
}
public override bool Equals(object obj) {
var key = (VertexKey)obj;
return _x == key._x && _y == key._y && _z == key._z;
}
public override int GetHashCode() {
long rv = FNV32Init;
rv ^= _x;
rv *= FNV32Prime;
rv ^= _y;
rv *= FNV32Prime;
rv ^= _z;
rv *= FNV32Prime;
return rv.GetHashCode();
}
}
private struct VertexEntry {
public int MeshIndex;
public int TriangleIndex;
public int VertexIndex;
public VertexEntry(int meshIndex, int triIndex, int vertIndex) {
MeshIndex = meshIndex;
TriangleIndex = triIndex;
VertexIndex = vertIndex;
}
}
}
I'm also including a simple (and ugly) height-to-color texture that serves as an example on how to paint your planet according to the height (import and assign it to the Albedo texture in a new material, then assign the material to your planet):
OBS: The whole thing is intended as a hint on how to generate planets procedurally. You can replace the simple Perlin noise function by a 2D monochromatic texture where each point is a terrain height, for instance, in order to obtain more realistic elevations (the ones created by Unity's Perlin function let a lot to desire...)
Your answer
Follow this Question
Related Questions
Best way to optimize mesh updating through code? 1 Answer
Setting mesh.vertices to a 3-dimensional array 1 Answer
Mesh adaption 1 Answer
How to edit a mesh from a script 1 Answer