- Home /
Subdivision and normalization of procedural mesh vertices destroying UV.
I have a class that I have created that generates a cube, subdivides that cube and then normalizes those points to create a cube mapped sphere. Everything works as expected except that the UVs of the mesh are unusable and shaders cannot be applies effectively. I was wondering if there was a simple fix that could be in either the subdivide action script that I did not create, or my sphere generation script.
EDIT: I have uploaded the project here. The scene that would be easiest to see my problem is BetterPlanetwithRadiusDependentNoiseValues and you can apply the test shaders included to see the problem.
Subdivide:
// C#
using UnityEngine;
using System.Collections;
using System.Collections.Generic;
public class MeshHelper
{
static List<Vector3> vertices;
static List<Vector3> normals;
// [... all other vertex data arrays you need]
static List<int> indices;
static Dictionary<uint,int> newVectices;
static int GetNewVertex(int i1, int i2)
{
// We have to test both directions since the edge
// could be reversed in another triangle
uint t1 = ((uint)i1 << 16) | (uint)i2;
uint t2 = ((uint)i2 << 16) | (uint)i1;
if (newVectices.ContainsKey(t2))
return newVectices[t2];
if (newVectices.ContainsKey(t1))
return newVectices[t1];
// generate vertex:
int newIndex = vertices.Count;
newVectices.Add(t1,newIndex);
// calculate new vertex
vertices.Add((vertices[i1] + vertices[i2]) * 0.5f);
normals.Add((normals[i1] + normals[i2]).normalized);
// [... all other vertex data arrays]
return newIndex;
}
public static void Subdivide(Mesh mesh)
{
newVectices = new Dictionary<uint,int>();
vertices = new List<Vector3>(mesh.vertices);
normals = new List<Vector3>(mesh.normals);
// [... all other vertex data arrays]
indices = new List<int>();
int[] triangles = mesh.triangles;
for (int i = 0; i < triangles.Length; i += 3)
{
int i1 = triangles[i + 0];
int i2 = triangles[i + 1];
int i3 = triangles[i + 2];
int a = GetNewVertex(i1, i2);
int b = GetNewVertex(i2, i3);
int c = GetNewVertex(i3, i1);
indices.Add(i1); indices.Add(a); indices.Add(c);
indices.Add(i2); indices.Add(b); indices.Add(a);
indices.Add(i3); indices.Add(c); indices.Add(b);
indices.Add(a ); indices.Add(b); indices.Add(c); // center triangle
}
mesh.vertices = vertices.ToArray();
mesh.normals = normals.ToArray();
// [... all other vertex data arrays]
mesh.triangles = indices.ToArray();
// since this is a static function and it uses static variables
// we should erase the arrays to free them:
newVectices = null;
vertices = null;
normals = null;
// [... all other vertex data arrays]
indices = null;
}
}
and here is my script that generates a sphere.
using System.Collections.Generic;
using LibNoise.Unity;
using LibNoise.Unity.Generator;
using LibNoise.Unity.Operator;
[RequireComponent (typeof (MeshFilter))]
[RequireComponent (typeof (MeshRenderer))]
public class RandPlanet : MonoBehaviour {
public int passes = 2;
public float radius = 10f;
private float divizor;
int sec = System.DateTime.Now.Second;
int day = System.DateTime.Now.Day;
int millisec = System.DateTime.Now.Millisecond;
int PsuedoRand;
private double frequency;
public int NoOceanChance = 90;
private int octaves;
private RidgedMultifractal noise;
private int zscalar = 1;
private bool hasOcean;
void Start () {
MeshFilter meshFilter = gameObject.GetComponent<MeshFilter>();
Mesh mesh = new Mesh ();
meshFilter.mesh = mesh;
int BetterRand = (int)Random.Range (1, 2487);
int OceanRand = (int)Random.Range (0, 100);
divizor = (50/radius);
frequency = (1.25/radius);
octaves = (int)(radius/10);
if (OceanRand > NoOceanChance)
{
hasOcean = true;
}
if (OceanRand < NoOceanChance)
{
hasOcean = false;
}
mesh.vertices = new Vector3[]{
new Vector3(-0.5f,-0.5f,-0.5f),
new Vector3(0.5f,-0.5f,-0.5f),
new Vector3(0.5f,0.5f,-0.5f),
new Vector3(-0.5f,0.5f,-0.5f),
new Vector3(0.5f,-0.5f,-0.5f),
new Vector3(0.5f,-0.5f,0.5f),
new Vector3(0.5f,0.5f,0.5f),
new Vector3(0.5f,0.5f,-0.5f),
new Vector3(0.5f,-0.5f,0.5f),
new Vector3(-0.5f,-0.5f,0.5f),
new Vector3(-0.5f,0.5f,0.5f),
new Vector3(0.5f,0.5f,0.5f),
new Vector3(-0.5f,-0.5f,0.5f),
new Vector3(-0.5f,-0.5f,-0.5f),
new Vector3(-0.5f,0.5f,-0.5f),
new Vector3(-0.5f,0.5f,0.5f),
new Vector3(-0.5f,0.5f,-0.5f),
new Vector3(0.5f,0.5f,-0.5f),
new Vector3(0.5f,0.5f,0.5f),
new Vector3(-0.5f,0.5f,0.5f),
new Vector3(-0.5f,-0.5f,-0.5f),
new Vector3(-0.5f,-0.5f,0.5f),
new Vector3(0.5f,-0.5f,0.5f),
new Vector3(0.5f,-0.5f,-0.5f),
};
int faces = 6;
List<int> triangles = new List<int>();
List<Vector2> uvs = new List<Vector2>();
for (int i = 0; i < faces; i++) {
int triangleOffset = i*4;
triangles.Add(0+triangleOffset);
triangles.Add(2+triangleOffset);
triangles.Add(1+triangleOffset);
triangles.Add(0+triangleOffset);
triangles.Add(3+triangleOffset);
triangles.Add(2+triangleOffset);
uvs.Add(new Vector2(0,0));
uvs.Add(new Vector2(1,0));
uvs.Add(new Vector2(1,1));
uvs.Add(new Vector2(0,1));
}
mesh.triangles = triangles.ToArray();
mesh.uv = uvs.ToArray();
mesh.RecalculateNormals();
mesh.RecalculateBounds ();
mesh.Optimize();
noise = new RidgedMultifractal();
mesh = GetComponent<MeshFilter>().mesh;
PsuedoRand = (sec * day * millisec);
noise.Seed = PsuedoRand * BetterRand;
noise.Frequency = frequency;
noise.OctaveCount = octaves;
noise.Quality = QualityMode.High;
Noise2D Noise2DTex = new Noise2D(512, 512, noise);
Texture2D NoiseTex = Noise2DTex.GetTexture(LibNoise.Unity.Gradient.Terrain);
for(int i = 0; i < passes; i++)
{
MeshHelper.Subdivide(mesh);
}
Vector3[] vertices = mesh.vertices;
Vector3[] verticesN = mesh.vertices;
Vector3[] normals = mesh.normals;
for (int i = 0; i < vertices.Length; i++)
{
Vector3 terrmod = vertices[i].normalized * radius;
verticesN[i] = vertices[i].normalized * (radius + ((float)noise.GetValue(terrmod.x, terrmod.y, terrmod.z*zscalar)/divizor));
}
if (hasOcean == true)
{
GameObject oceanObject = new GameObject();
oceanObject.AddComponent("MeshFilter");
oceanObject.AddComponent("MeshRenderer");
MeshFilter oceanFilter = oceanObject.GetComponent<MeshFilter>();
Mesh oceanmesh = new Mesh ();
oceanFilter.mesh = oceanmesh;
oceanmesh.vertices = new Vector3[]{
new Vector3(-0.5f,-0.5f,-0.5f),
new Vector3(0.5f,-0.5f,-0.5f),
new Vector3(0.5f,0.5f,-0.5f),
new Vector3(-0.5f,0.5f,-0.5f),
new Vector3(0.5f,-0.5f,-0.5f),
new Vector3(0.5f,-0.5f,0.5f),
new Vector3(0.5f,0.5f,0.5f),
new Vector3(0.5f,0.5f,-0.5f),
new Vector3(0.5f,-0.5f,0.5f),
new Vector3(-0.5f,-0.5f,0.5f),
new Vector3(-0.5f,0.5f,0.5f),
new Vector3(0.5f,0.5f,0.5f),
new Vector3(-0.5f,-0.5f,0.5f),
new Vector3(-0.5f,-0.5f,-0.5f),
new Vector3(-0.5f,0.5f,-0.5f),
new Vector3(-0.5f,0.5f,0.5f),
new Vector3(-0.5f,0.5f,-0.5f),
new Vector3(0.5f,0.5f,-0.5f),
new Vector3(0.5f,0.5f,0.5f),
new Vector3(-0.5f,0.5f,0.5f),
new Vector3(-0.5f,-0.5f,-0.5f),
new Vector3(-0.5f,-0.5f,0.5f),
new Vector3(0.5f,-0.5f,0.5f),
new Vector3(0.5f,-0.5f,-0.5f),
};
int Ofaces = 6;
List<int> Otriangles = new List<int>();
List<Vector2> Ouvs = new List<Vector2>();
for (int i = 0; i < Ofaces; i++) {
int OtriangleOffset = i*4;
Otriangles.Add(0+OtriangleOffset);
Otriangles.Add(2+OtriangleOffset);
Otriangles.Add(1+OtriangleOffset);
Otriangles.Add(0+OtriangleOffset);
Otriangles.Add(3+OtriangleOffset);
Otriangles.Add(2+OtriangleOffset);
Ouvs.Add(new Vector2(0,0));
Ouvs.Add(new Vector2(1,0));
Ouvs.Add(new Vector2(1,1));
Ouvs.Add(new Vector2(0,1));
}
oceanmesh.triangles = triangles.ToArray();
oceanmesh.uv = uvs.ToArray();
oceanObject.renderer.material = new Material(Shader.Find("FX/Water (simple)"));
oceanmesh.RecalculateNormals();
oceanmesh.RecalculateBounds ();
oceanmesh.Optimize();
for(int i = 0; i < passes; i++)
{
MeshHelper.Subdivide(oceanmesh);
}
Vector3[] Overtices = oceanmesh.vertices;
Vector3[] OverticesN = oceanmesh.vertices;
Vector3[] Onormals = oceanmesh.normals;
for (int i = 0; i < Overtices.Length; i++)
{
OverticesN[i] = Overtices[i].normalized * (radius);
}
oceanmesh.vertices = OverticesN;
oceanmesh.RecalculateNormals();
oceanmesh.Optimize();
}
renderer.material.mainTexture = NoiseTex;
mesh.vertices = verticesN;
mesh.RecalculateNormals();
mesh.Optimize();
}
}
So the "all the other vertex arrays" stuff, does that include UVs? Because you need to subdivide those too....
I realize that now, but I have no clue how to go about subdividing the UVs. Would the act of normalizing the cube to create a sphere also have an effect on the UVs or would it still preserve the cube map?
I would have thought you'd apply the same process as you do to the vertices
Answer by Aras · May 15, 2013 at 04:54 AM
All the mesh data is "per vertex". If you produce more vertices, then you should also produce more UVs (subdivided in some way, e.g. linearly interpolated between the original ones).