📜 ⬆️ ⬇️

Unity3d We play with the mesh. Part 2 - Deformation of the mesh with a height map

In the first part, we looked at how to generate a mesh using a height map. Enough time has passed, so I think it's time to consider ways to deform the mesh using the same height map. I ask all interested under cat.



Content


  1. Unity3d We play with the mesh. Part 1 - Generating a mesh using an elevation map
  2. Unity3d We play with the mesh. Part 2 - Deformation of the mesh with a height map
  3. Unity3d We play with the mesh. Part 3 - Collision Based Mesh Warp

Here we will need a deeper understanding of the mesh, so ...


Consider the mesh in more detail.


From the previous article, we learned what attributes the mesh owns and what they are for, but superficially. Now let's clarify in more detail what they are for and what they are responsible for.


In order not to run back and forth, here’s a list of what the mesh owns:



Tangents are not a necessary part, so we will not touch them. For the deformation on the height map, we will need UV coordinates and normals. Well, to deal with all of this, let's try to understand the simplest figure.



What do you think, what is this figure? Plane? This is not true. Plane has many more triangles, vertices, uv coordinates, etc. To verify this, you can create a Plane and turn on a display of the Wireframe type. So what is it? You can say it's a testing ground.


He has:



We will translate into Unity the amount of this total



And now we will try to create it in Unity by means of a code. Create 4 spheres and place them in the parent with the name "vertices". Let's write the TestPoly script, which we will hang on the "vertices" object or wherever.


TestPoly.cs
 using UnityEngine; public class TestPoly : MonoBehaviour { //   public GameObject v0; public GameObject v1; public GameObject v2; public GameObject v3; public Material mat; // ,      GameObject poly; Mesh mesh; void Start() { poly = new GameObject("poly"); mesh = new Mesh(); Vector3[] vertices = new Vector3[] { //    v0.transform.position, v1.transform.position, v2.transform.position, v3.transform.position, }; Vector3[] normals = new Vector3[] { //  ,       ? new Vector3(0, 1, 0), new Vector3(0, 1, 0), new Vector3(0, 1, 0), new Vector3(0, 1, 0), }; Vector2[] UVs = new Vector2[] { // UV new Vector2(0, 0), new Vector2(1, 0), new Vector2(0, 1), new Vector2(1, 1), //  X  Y,   U  V,    }; int[] triangles = new int[] { 0, 2, 3, //   3, 1, 0, //   }; mesh.vertices = vertices; mesh.normals = normals; mesh.uv = UVs; mesh.triangles = triangles; poly.AddComponent<MeshRenderer>().material = mat; //    ;    poly.AddComponent<MeshFilter>().sharedMesh = mesh; //    ;    } } 

Great, we see our mesh.



Let's add to Update ability to change it by vertices to make it a little more interesting.


Update to TestPoly.cs
 void Update() { //     Raycast' //      Vector3[] vertices = new Vector3[] { //      v0.transform.position, v1.transform.position, v2.transform.position, v3.transform.position, }; mesh.vertices = vertices; //      } 

The mesh is deformed as planned. And the display remains unchanged.



If you decide to rotate the mesh and notice a lag, then you are doing it wrong. To avoid a break in a coup, you need to rotate the vertices. That is the object "verticies". After all, the script sets vertices on these points.


Any mesh consists roughly of such polygons. From personal experience, I know that the best understanding comes from personal interaction, so try changing the array of UVs, triangles and normals and watch the result. If you are ready, let's move on.


Small interactive


Surely you are already tired, so why not digress? How about a little game? :)


How many do you think vertices have a cube?


Just think before you open the spoilers :)


eight

Almost true! You have selected the number of edge vertices. 4 below and 4 above.



sixteen

No, unfortunately, it is not. Try creating a cube in Unity and look at it from all sides.


24

Yes that's right. The cube has 24 vertices.
In total, the cube has 8 edge vertices and each of the vertices has 3 normals, each of which is perpendicular to the surface (surface) and vertex (top), respectively.
8 * 3 = 24



Yellow displays normal vectors for one of the vertices.


32

No, you made a mistake in the calculations. The logic is correct, but think again. Creating a cube in Unity can help you.


48

No, this is clearly a bust ...


Theory of Deformation with a Height Map


In theory, I would like to deform the mesh by traversing each pixel of the height map, taking a grayscale and changing the height of the vertex of the mesh relative to the resulting grayscale value multiplied by some constant.


But in practice ... As we can remember, the height map contains a maximum of 65025 pixels, which we can convert to vertices, the minimum is 1. What if the deformable mesh contains not the same number of vertices? This is where the UV coordinates will help us.


We will need to go through all the vertices. And move each along its normal to the grayscale value obtained from the height map, which we will obtain by texture coordinates and multiply by a constant.


If nothing is clear, reread it thoughtfully. If again, nothing is clear, let's write the code. Logic sometimes helps.


We write expansion


By tradition, create a class that inherits from ParentEditor . It can be found in the previous article . Fill the class with variables and make the base OnGUI .


MeshDeformator
 using UnityEngine; using UnityEditor; public class MeshDeformator : ParentEditor { public GameObject sourceGameObject; public string mname = "Enter name: "; public Texture2D heightMap; public float height = 35f; //   [MenuItem("Tools/Mesh Deformator")] static void Init() { MeshDeformator md = (MeshDeformator)GetWindow(typeof(MeshDeformator)); md.Show(); } void OnGUI() { sourceGameObject = (GameObject)EditorGUILayout.ObjectField("Game Object to deform", sourceGameObject, typeof(GameObject), true); mname = EditorGUILayout.TextField("Mesh name", mname); heightMap = (Texture2D)EditorGUILayout.ObjectField("Height Map", heightMap, typeof(Texture2D), false); height = EditorGUILayout.Slider("Height scale", height, -100, 100); } } 

Add the DeformMesh method to the class DeformMesh


DeformMesh
  public void DeformMesh() { Mesh temp_mesh = new Mesh(); //      ParentEditor temp_mesh = CopyMesh(sourceGameObject.GetComponent<MeshFilter>().sharedMesh); GameObject temp_go = new GameObject(sourceGameObject.name + ".clone"); temp_go.transform.position = sourceGameObject.transform.position; //      UV  Vector3[] vertices = temp_mesh.vertices; Vector2[] UVs = temp_mesh.uv; for (int i = 0; i < vertices.Length; i++) { //       float pixelHeight = heightMap.GetPixel((int)(UVs[i].x * (heightMap.width - 1) + 0.5), (int)(UVs[i].y * (heightMap.height - 1) + 0.5)).grayscale; //      vertices[i] += (temp_mesh.normals[i] * pixelHeight) * height; } temp_mesh.vertices = vertices; //   temp_mesh.RecalculateNormals(); //   temp_mesh.RecalculateBounds(); //  bounds temp_go.AddComponent<MeshFilter>().sharedMesh = temp_mesh; temp_go.AddComponent<MeshRenderer>().material = sourceGameObject.GetComponent<MeshRenderer>().sharedMaterial; sourceGameObject = temp_go; } 

You could see that we used two methods: RecalculateNormals() and RecalculateBounds() . We have already used the first one, but RecalculateBounds() is not yet available. What does he do? Roughly speaking, it does recalculation of mesh.bounds (mesh boundaries). That is, it updates the data about our frame.


Let's combine everything into one script and add the ability to save our mesh and the resulting object to the prefab.


MeshDeformator.cs
 using UnityEngine; using UnityEditor; public class MeshDeformator : ParentEditor { public GameObject sourceGameObject; public string mname = "Enter mesh name: "; public Texture2D heightMap; public float height = 35f; [MenuItem("Tools/Mesh Deformator")] static void Init() { MeshDeformator md = (MeshDeformator)GetWindow(typeof(MeshDeformator)); md.Show(); } void OnGUI() { sourceGameObject = (GameObject)EditorGUILayout.ObjectField("Game Object to deform", sourceGameObject, typeof(GameObject), true); mname = EditorGUILayout.TextField("Mesh name", mname); heightMap = (Texture2D)EditorGUILayout.ObjectField("Height Map", heightMap, typeof(Texture2D), false); height = EditorGUILayout.Slider("Height scale", height, -100, 100); if (GUILayout.Button("Deform Mesh", GUILayout.Height(20))) { DeformMesh(); } if (GUILayout.Button("Save", GUILayout.Height(20))) { CreatePaths(); //  ParentEditor Mesh updated_mesh = sourceGameObject.GetComponent<MeshFilter>().sharedMesh; AssetDatabase.CreateAsset(updated_mesh, "Assets/MeshTools/Meshes/Updated/" + mname + ".asset"); PrefabUtility.CreatePrefab("Assets/MeshTools/Prefabs/Updated/" + mname + ".prefab", sourceGameObject); //      Assets/MeshTools/Updated/Meshes/mname // Assets/MeshTools/Updated/Prefabs/mname  } } public void DeformMesh() { Mesh temp_mesh = new Mesh(); temp_mesh = CopyMesh(sourceGameObject.GetComponent<MeshFilter>().sharedMesh); GameObject temp_go = new GameObject(mname + ".clone"); temp_go.transform.position = sourceGameObject.transform.position; Vector3[] vertices = temp_mesh.vertices; Vector2[] UVs = temp_mesh.uv; for (int i = 0; i < vertices.Length; i++) { float pixelHeight = heightMap.GetPixel((int)(UVs[i].x * (heightMap.width - 1) + 0.5), (int)(UVs[i].y * (heightMap.height - 1) + 0.5)).grayscale; vertices[i] += (temp_mesh.normals[i] * pixelHeight) * height; } temp_mesh.vertices = vertices; temp_mesh.RecalculateNormals(); temp_mesh.RecalculateBounds(); temp_go.AddComponent<MeshFilter>().sharedMesh = temp_mesh; temp_go.AddComponent<MeshRenderer>().material = sourceGameObject.GetComponent<MeshRenderer>().sharedMaterial; sourceGameObject = temp_go; } } 

We are testing


I took a high-poly sphere, the number of vertices: 1324. And I deformed it with different height scale factors.


In the image below you can see:




What's next?


What awaits us with you further in the course of the articles "Unity3D. We dabble with the mesh."?



Attention, a mistake


After reading the article, I found an error related to the object positioning and vector algebra. This does not affect the main focus of the article, but nevertheless ...


If within a week someone guesses about the error, the article will be updated and the first one who guessed it will be mentioned in it. If someone corrects the error within a week, the article will be updated to mention this user already.


Error found


The article is corrected and the plus to karma and reputation for finding and correcting an error is received by the user MrShoor . My congratulations, and most importantly respect for efficiency!


What I would like to add


What I would like to add, so that there is still some omission regarding the normals. I mis-configured the normals for my polygon. I indicated Vector3.up , and it would be correct if my polygon lay (looked at the sky). But my polygon stands and looks into the camera, and therefore the normal (0, 0, -1), that is, directed in the opposite direction from the camera or Vector3.back .


For ease of understanding, the image below shows the correct normal displayed in green, the incorrect one in red, and the tangents to the normal in blue.



')

Source: https://habr.com/ru/post/328564/


All Articles