Unity Tutorials Beta

Learn how to create games in Unity, a powerful and popular game engine.

Runtime Mesh Manipulation With Unity

One of the benefits of using Unity as your game development platform is its powerful 3D engine. In this tutorial, you’ll get an introduction to the world of 3D objects and mesh manipulation.

Version

  • C# 7.3, Unity 2019.1, Unity
Update note: Sean Duffy updated this tutorial for Unity 2019.1.2. Arai Lanju wrote the original.

Welcome to the world of 3D objects and mesh manipulation! One of the benefits of using Unity as your game development platform is its powerful 3D engine. That 3D engine, along with Unity’s ability to use custom editors, makes the development of 3D games and apps so much easier.

With the growth of virtual reality and augmented reality (VR/AR) technology, most developers will inadvertently find themselves wrestling with the gritty bits of 3D concepts. So let this tutorial be your starting point. Don’t worry, there will be no complicated 3D math here — just lots of hearts, drawings, arrows and loads of fun!

Note: This tutorial is intended for users who are familiar with the Unity editor and have some experience with C# programming. If you need to brush up on these topics, check out Introduction to Unity UI and Introduction to Unity Scripting first.

You need to have at least Unity 2019.1.3 installed. You can download the latest version of Unity here.

This tutorial uses a custom editor. You can learn more about custom editors at Extending the Unity Editor.

Getting Started

Understanding Meshes

Time to start with the basic vocabulary of 3D rendering. The shape of a 3D object is defined by its mesh. A mesh is like a net of points, or vertices. The invisible lines that connect these vertices form triangles, which define the basic shape of the object.

Example of a square's mesh.

But in addition to the shape, the engine needs to know how to draw the surface of the object. So a mesh’s data also includes its normals, which are vectors that determine which way a particular triangle is facing, and thus how light bounces off of it. Finally, a UV Map maps a material to an object, specifying how textures wrap around the shape.

In Unity, there are two primary rendering components: The Mesh Filter, which stores the mesh data of a model, and the Mesh Renderer, which combines the mesh data with materials to render the object in the scene.

Got all that? Here’s a cheat sheet for easy reference:

  • Vertices: A vertex is a point in 3D space. Often abbreviated to “vert”.
  • Lines/Edges: The invisible lines that connect vertices to one another.
  • Triangles: Formed when edges connect three vertices.
  • UV Map: Maps a material to an object, specifying how textures wrap around the object’s shape.
  • Normals: The directional vector of a vertex or a surface. This characteristically points outward, perpendicular to the mesh surface, and helps determine how light bounces off of the object.
  • Mesh: Holds all the vertices, edges, triangles, normals and UV data of a model.

Here are the basic steps (in pseudocode) to create a 3D mesh:

  • Create a new mesh named “myMesh”.
  • Add data to myMesh’s vertices and triangle properties.
  • Create a new mesh filter named “myMeshFilter”.
  • Assign myMesh to myMeshFilter’s mesh property.

Setting Up the Project

Now that you have the basics covered, download the project using the Download Materials button at the top or bottom of this page, then unpack the files and open the starter project in Unity. Check out the folder structure in the Project view:

Screenshot of the starter project's folder structure.

  • Prefabs: This contains a CustomHeart prefab, which you’ll use to save your 3D mesh at runtime.
  • Scenes: This contains the three scenes that you will use for the different parts of this tutorial.
  • Editor: The scripts inside this folder give you special powers in the editor during development.
  • Scripts: This contains runtime scripts, or components. When you attach these components to a GameObject, they’ll execute when you click Play.
  • Materials: This folder contains the material for the mesh you’ll be working with.

In the next section, you will create a custom editor to visualize the parts of a 3D mesh.

Poking and Prodding Meshes With a Custom Editor

Open 01 Mesh Study Demo inside RW/Scenes. In the Scene view, you will see a humble cube:

Starting out with a 3D cube.

You’re going to build a custom editor to tear this poor cube apart! (And then you’ll learn how to keep it in one piece.)

Customizing the Editor Script

Select the Editor folder in the Project view. Scripts in this special folder modify how the Unity editor works; they do not become part of the built game.

Screenshot of the Editor folder and its contents.

Open MeshInspector.cs and view the source code. Note that this class inherits from Unity’s base Editor class — that’s what makes Unity understand this is a custom editor rather than a game script.

The first step is to tell Unity what kind of objects that this special editor should draw. Add this attribute on the line above the MeshInspector class declaration:

[CustomEditor(typeof(MeshStudy))]

Now, when any GameObject with the Mesh Study component attached to it is visible in the Scene view, this class will handle drawing it. But right now, you won’t know if that’s happening.

OnSceneGUI is an event method that Unity calls every time it renders the Scene view in the editor. It’s your chance to modify how Unity draws your object in the scene. Add the following at the beginning of OnSceneGUI:

mesh = target as MeshStudy;
Debug.Log("Custom editor is running");

The base Editor class provides a reference to the object you’re customizing in the target variable, which has type Object. You can’t do much that’s useful with a plain vanilla Object, so this code casts target as the type MeshStudy. Logging a message allows you to see in the console that the custom editor is indeed running.

Save the file and return to Unity. Go to the RW/Scripts folder and drag MeshStudy.cs onto the Cube GameObject in the Hierarchy to attach the component to it.

Screenshot showing where to drag MeshStudy.cs.

Look in the console and make sure your code is running. Then go ahead and remove the Debug.Log line so it doesn’t flood your console.

Cloning a Mesh

When you’re working with a 3D mesh with a custom editor in Edit mode, it is easy to accidentally overwrite Unity’s default mesh — that is, the built-in Sphere, Cube, Cylinder and so on. If that happens, you’ll need to restart Unity.

Image of a frustrated face.

To avoid this, you’ll clone the mesh before making any changes to it in Edit mode.

Open MeshStudy.cs. This script inherits from MonoBehaviour, so its Start will not run in Edit mode. Luckily, that’s easy to fix!

In MeshStudy, above the class declaration, add the following:

[ExecuteInEditMode]

When a class has this attribute, its Start will fire in both Play mode and Edit mode. Now that you’ve added it, you can instantiate and clone your mesh object before changing anything.

Add the following code to InitMesh:

meshFilter = GetComponent<MeshFilter>();
originalMesh = meshFilter.sharedMesh; //1
clonedMesh = new Mesh(); //2

clonedMesh.name = "clone";
clonedMesh.vertices = originalMesh.vertices;
clonedMesh.triangles = originalMesh.triangles;
clonedMesh.normals = originalMesh.normals;
clonedMesh.uv = originalMesh.uv;
meshFilter.mesh = clonedMesh;  //3

vertices = clonedMesh.vertices; //4
triangles = clonedMesh.triangles;
isCloned = true; //5
Debug.Log("Init & Cloned");

Here’s what’s happening:

  1. Grabs whatever mesh you’ve originally assigned in MeshFilter.
  2. Creates a new mesh instance called clonedMesh and sets its properties by copying the first mesh.
  3. Assigns the copied mesh back to the mesh filter.
  4. Updates local variables, which you’ll need later.
  5. Sets isCloned to true; you’ll reference this later.

Save the file and return to Unity. The console should show the message “Init & Cloned”.

Select Cube in the Hierarchy and look at the Inspector. The Mesh Filter will show a mesh named clone. Great! This means you have cloned the mesh successfully.

Screenshot showing the cloned mesh filter in the Inspector.

But notice there’s no new Mesh asset in your project — the cloned mesh only lives in Unity’s memory right now, and it will disappear if you close the scene. You’ll learn how to save a Mesh later.

Resetting a Mesh

For now, you want to give yourself an easy way to reset your mesh so you can play around without fear. Go back to MeshInspector.cs.

OnInspectorGUI lets you customize the Inspector for your object with extra GUI elements and logic. In OnInspectorGUI, find the comment //draw reset button and replace it with the following:

if (GUILayout.Button("Reset")) //1
{
    mesh.Reset(); //2
}
  1. This code draws a Reset button in the Inspector. The draw function returns true when it’s pressed.
  2. When pressed, the button calls Reset in MeshStudy.cs.

Save the file and return to MeshStudy.cs. Add the following to Reset:

if (clonedMesh != null && originalMesh != null) //1
{
    clonedMesh.vertices = originalMesh.vertices; //2
    clonedMesh.triangles = originalMesh.triangles;
    clonedMesh.normals = originalMesh.normals;
    clonedMesh.uv = originalMesh.uv;
    meshFilter.mesh = clonedMesh; //3

    vertices = clonedMesh.vertices; //4
    triangles = clonedMesh.triangles;
}

Here’s what this code does step-by-step:

  1. Checks that both the original mesh and the clone mesh exist, in case the object’s mesh filter doesn’t have any data in it.
  2. Resets all the properties of clonedMesh to those of the original mesh.
  3. Assigns clonedMesh back to the Mesh Filter component.
  4. Updates local variables.

Save the file and return to Unity.

In the Inspector, click on the Test Edit button to mess with the cube’s mesh, then press the Reset button to restore it.

Gif showing changes to the cube's mesh.

Understanding Vertices and Triangles With Unity

As you saw earlier, a mesh consists of vertices connected by edges to form triangles. Triangles define the basic shape of the object.

Note: Unity’s Mesh class keeps track of vertices and triangles with two arrays:
  • It stores vertices as an array of Vector3.
  • It stores triangles as an array of integers. Each integer is the index of one of the vertices in the verts array, and each group of three consecutive integers represents one triangle.

    For example, the group triangles[0], triangles[1], triangles[2] represents one triangle, the group triangles[3], triangles[4], triangles[5] represents the next triangle and so on.

So, in a simple Quad mesh that consists of four vertices and two triangles, the Quad’s mesh data would be:

Gif demonstrating the mesh data of a simple Quad mesh.

Visualizing Vertices

It will be easier to see how this works if you can draw and move around the vertices on a mesh with handles. Handles are tools for working with objects in the Scene view, like the draggable sphere for the Rotate tool. Now, you’re going to write your own handle!

In MeshInspector.cs, look for EditMesh and add the following:

handleTransform = mesh.transform; //1
handleRotation = Tools.pivotRotation == PivotRotation.Local ? 
    handleTransform.rotation : Quaternion.identity; //2
for (int i = 0; i < mesh.vertices.Length; i++) //3
{
    ShowPoint(i);
}
  1. Gets the mesh's transform, which you'll need to know where to draw the vertices in world space.
  2. Gets the current pivot Rotation mode, to draw the handle the same way as everything else in the scene.
  3. Loops through the mesh's vertices and draws dots with ShowPoint.

In ShowPoint, replace the //draw dot comment with:

Vector3 point = handleTransform.TransformPoint(mesh.vertices[index]); //1
Handles.color = Color.blue;
point = Handles.FreeMoveHandle(point, handleRotation, mesh.handleSize,
    Vector3.zero, Handles.DotHandleCap); //2
  1. This line converts the vertex's local position into world space.
  2. Draws the dot using the Handles utility class.

Handles.FreeMoveHandle makes an unconstrained movement handle, which you will use in the next section to enable dragging the points around.

Save the file and return to Unity.

Check the Cube's MeshInspector and make sure that you've checked Move Vertex Point.

You should now see the vertices of the mesh marked with blue dots on screen. Try attaching the script to other 3D objects and see the results for yourself! :]

Screenshot showing how the Inspector view should look.

Moving a Single Vertex

You'll start with the most basic kind of mesh manipulation: Moving a single vertex.

Open MeshInspector.cs. Inside ShowPoint, replace the //drag comment with the following:

if (GUI.changed) //3
{
    mesh.DoAction(index, handleTransform.InverseTransformPoint(point)); //4
}
  1. GUI.changed monitors any changes made to the dots, which works nicely with Handles.FreeMoveHandle to detect a dragging action.
  2. On dragging a vertex, call mesh.DoAction with the index of the vertex and the vertex's position as parameters. This line also converts the vertex's position back to local space with InverseTransformPoint.

Save MeshInspector.cs and go to MeshStudy.cs. Add the following in DoAction:

PullOneVertex(index, localPos);

Then add the following to PullOneVertex:

vertices[index] = newPos; //1
clonedMesh.vertices = vertices; //2
clonedMesh.RecalculateNormals(); //3
  1. Updates the target vertex's position.
  2. Assigns the updated vertices array back to the cloned mesh.
  3. Tells Unity to re-draw the mesh to reflect the change.

Save the script and return to Unity. Try dragging one of the dots on the cube.

SScreenshot showing how the cube looks when you've dragged the dots.

It seems like some of the vertices share the same position, so when you pull only one, the other vertices stay behind and your mesh breaks. You'll learn how to fix this problem shortly. :]

Looking at the Vertices Array

Visually, a cube mesh consists of eight vertices, six sides and 12 triangles. Time to see if Unity agrees.

Visual depiction of a cube mesh.

Go to MeshStudy.cs, and before Start, look for a variable named vertices. You'll see that it has the [HideInInspector] attribute.

Temporarily comment out that attribute for a quick peek into the array:

//[HideInInspector]
public Vector3[] vertices;

Note: More complicated 3D meshes can have thousands of vertices. Unity will freeze up if it tries to show all those values in the Inspector, so in general, you'll hide the array with [HideInInspector]. You're just peeking!

Save the file, return to Unity and look at your cube. You can now see the vertices property on Mesh Study. Click on the arrow icon beside it to show the array of Vector3 elements.

Screenshot of the vertices property on Mesh Study.

You can see that the array size is 24, which means that there are definitely vertices sharing the same position! Take a moment and think about why there might be multiple vertices in the same place.

[spoiler title = "Why 24 Vertices?"]
The simplest answer is:
A cube has six sides and each side has four vertices that form a plane. 6 × 4 = 24 vertices.

There are other ways to think about this, if this is hard to grasp. But for now, just know that some meshes will have vertices that share the same position.


[/spoiler]

Since you're done peeking into the verts array, go ahead and uncomment [HideInInspector].

Finding All Similar Vertices

You can see that manipulating a mesh is going to take more than just moving single vertices — you have to move all the vertices for a particular point in space in order to keep the mesh together. So you're now ready to unbreak your heart, er, mesh.

In MeshStudy.cs, replace all the code inside DoAction with:

PullSimilarVertices(index, localPos);

Go to PullSimilarVertices and add the following:

Vector3 targetVertexPos = vertices[index]; //1
List<int> relatedVertices = FindRelatedVertices(targetVertexPos, false); //2
foreach (int i in relatedVertices) //3
{
    vertices[i] = newPos;
}
clonedMesh.vertices = vertices; //4
clonedMesh.RecalculateNormals();
  1. Gets the target vertex position from the vertices array.
  2. Finds all the vertices that share the same position as the target vertex and puts their indices into a list.
  3. Loops through that list and updates the position of all related vertices.
  4. Assigns the updated vertices back to clonedMesh.vertices, then redraws the mesh.

Save the file and return to Unity. Click and drag any of the vertices; the mesh should now retain its form without breaking.

Gif of a Cube mesh that retains its form without breaking.

Save the scene. You've taken the first step towards being a mesh magician!

Manipulating Meshes

Editing meshes in Unity is fun, but what if you could add some "squish" to your game by deforming meshes at runtime? Next, you'll try that out in its most basic form — pushing and pulling some predefined vertices.

No math...

Collecting the Selected Indices

You'll start by making a custom editor that lets you select the vertices to move around in real time. Open up the 02 Create Heart Mesh scene inside RW/Scenes. You'll see a red sphere in the Scene view.

Screenshot of the red sphere that you'll transform into a heart.

Select the Sphere in the Hierarchy and look at the Heart Mesh component. This is the script that will store the verts you select.

But right now, there aren't any verts shown in the scene. So next, you're going to fix that!

Open RW/Editor/HeartMeshInspector.cs. In ShowHandle, inside the if statement, add the following code:

Handles.color = Color.blue;
if (Handles.Button(point, handleRotation, mesh.pickSize, mesh.pickSize, 
    Handles.DotHandleCap)) //1
{
    mesh.selectedIndices.Add(index); //2
}
  1. This makes Unity draw the vertices of the mesh as buttons, so you can click on them.
  2. When you click the button, it adds the selected index to the mesh.selectedIndices list.

Add the following code at the end of OnInspectorGUI, after the existing if statement:

if (GUILayout.Button("Clear Selected Vertices"))
{
    mesh.ClearAllData();
}

This adds a custom Reset button in the Inspector. Next, you'll write the code to clear out your selection.

Save the file and open RW/Scripts/HeartMesh.cs. In ClearAllData, add the following:

selectedIndices = new List<int>();
targetIndex = 0;
targetVertex = Vector3.zero;

This clears the values in the selectedIndices list and sets targetIndex to zero. It also resets the targetVertex position.

Save the file and return to Unity. Select the Sphere and look at its HeartMesh component. Make sure you've checked Is Edit Mode so you can see the mesh's vertices in the Scene view. Then click on the arrow icon next to Selected Indices to show the array.

Click on a few blue dots and watch new entries appear in Selected Indices. Try out your Clear Selected Vertices button to make sure it clears all values correctly.

Gif showing how Selected Indices works.

Note: You have the option to show/hide the transform handle with Show Transform Handle in the custom Inspector, since it can get in the way of selecting verts. Just remember not to panic when you find the Transform handle missing from your other scenes! Be sure to switch it back on before you exit.

Deforming the Sphere Into a Heart Shape

Updating mesh vertices in real time requires three steps:

  1. Copy the current mesh vertices (before animation) to modifiedVertices.
  2. Do calculations and update values on modifiedVertices.
  3. Copy modifiedVertices to the current mesh on every step change and get Unity to redraw the mesh.

Go to HeartMesh.cs and add the following variables before Start:

public float radiusOfEffect = 0.3f; //1 
public float pullValue = 0.3f; //2
public float duration = 1.2f; //3
int currentIndex = 0; //4
bool isAnimate = false; 
float startTime = 0f;
float runTime = 0f; 

Moving a vertex should have some influence on the vertices around it to maintain a smooth shape. These variables control that effect.

  1. Radius of area affected by the targeted vertex.
  2. The strength of the pull.
  3. How long the animation will run.
  4. Current index of the selectedIndices list.

In Init, before the if statement, add:

currentIndex = 0;

This sets currentIndex — the first index of the selectedIndices list — to 0 at the beginning of the game.

Still in Init, before the closing braces of the else statement, add:

StartDisplacement();

StartDisplacement is what actually moves the vertices. It only runs when isEditMode is false.

Right now, this method does nothing, so add the following to StartDisplacement:

targetVertex = originalVertices[selectedIndices[currentIndex]]; //1
startTime = Time.time; //2
isAnimate = true;
  1. Singles out the targetVertex from the originalVertices array to start the animation. Remember, each array item is a List of integer values.
  2. Sets the start time to the current time and changes isAnimate to true.

After StartDisplacement, create a new method called FixedUpdate with the following code:

protected void FixedUpdate() //1
{
    if (!isAnimate) //2
    {
        return;
    }

    runTime = Time.time - startTime; //3

    if (runTime < duration)  //4
    {
        Vector3 targetVertexPos = 
            meshFilter.transform.InverseTransformPoint(targetVertex);
        DisplaceVertices(targetVertexPos, pullValue, radiusOfEffect);
    }
    else //5
    {
        currentIndex++;
        if (currentIndex < selectedIndices.Count) //6
        {
            StartDisplacement();
        }
        else //7
        {
            originalMesh = GetComponent<MeshFilter>().mesh;
            isAnimate = false;
            isMeshReady = true;
        }
    }
}

Here's what the code is doing:

  1. The FixedUpdate method runs on a fixed interval, meaning that it's frame rate independent. Read more about it here.
  2. If isAnimate is false, it won't do anything.
  3. Keeps track of how long the animation has been running.
  4. If the animation hasn't been running too long, it continues the animation by getting the world space coordinates of targetVertex and calling DisplaceVertices.
  5. Otherwise, time's up! Adds one to currentIndex to start processing the animation for the next selected vertex.
  6. Checks if all the selected vertices have been processed. If not, calls StartDisplacement with the latest vertex.
  7. Otherwise, you've reached the end of the list of selected vertices. This line makes a copy of the current mesh and sets isAnimate to false to stop the animation.

Making the Vertices Move Smoothly

In DisplaceVertices, add the following:

Vector3 currentVertexPos = Vector3.zero;
float sqrRadius = radius * radius; //1

for (int i = 0; i < modifiedVertices.Length; i++) //2
{
    currentVertexPos = modifiedVertices[i];
    float sqrMagnitude = (currentVertexPos - targetVertexPos).sqrMagnitude; //3
    if (sqrMagnitude > sqrRadius)
    {
        continue; //4
    }
    float distance = Mathf.Sqrt(sqrMagnitude); //5
    float falloff = GaussFalloff(distance, radius);
    Vector3 translate = (currentVertexPos * force) * falloff; //6
    translate.z = 0f;
    Quaternion rotation = Quaternion.Euler(translate);
    Matrix4x4 m = Matrix4x4.TRS(translate, rotation, Vector3.one);
    modifiedVertices[i] = m.MultiplyPoint3x4(currentVertexPos);
}
originalMesh.vertices = modifiedVertices; //7
originalMesh.RecalculateNormals();

This code loops over every vertex in the mesh and displaces those that are close to the ones you selected in the editor. It does some math tricks to create a smooth, organic effect, like pushing your thumb into clay. You'll learn more about this later on.

Here's a closer look at what this code does:

  1. Gets the square of the radius.
  2. Loops through every vertex in the mesh.
  3. Finds the distance between the current vertex and the target vertex and squares it.
  4. If this vertex is outside the area of effect, exits the loop early and continues to the next vertex.
  5. Otherwise, calculates the falloff value based on the distance. Gaussian functions create a smooth bell curve.
  6. Calculates how far to move based on distance, then sets the rotation (direction of displacement) based on the result. This makes the vertex move "outward", that is, directly away from the targetVertex, making it seem to puff out from the center.
  7. On exiting the loop, stores the updated modifiedVertices in the original mesh and has Unity recalculate the normals.

Save your file and return to Unity. Select the Sphere, go to the HeartMesh component, and try adding some vertices into your Selected Indices property. Turn off Is Edit mode and press Play to preview your work.

Effects of adding vertices to your Heart Mesh component.

Play around with the Radius Of Effect, Pull Value and Duration settings to see different results. When you are ready, update the settings per the screenshot below.

Indices to set to make your sphere turn into a heart.

Press Play. Did your sphere balloon into a heart shape?

Gif showing the sphere turning into a heart.

Congratulations! In the next section, you will learn how to save the mesh for further use.

Saving Your Mesh in Real Time

Right now, your heart comes and goes whenever you push the Play button. If you want a love that lasts, you need a way to write the mesh to a file.

A simple way is to set up a placeholder prefab that has a 3D object as its child, then replace its mesh asset with your heart (...er, your Heart mesh) via a script.

In the Project view, find Prefabs/CustomHeart. Double-click the prefab to open it in Prefab Editing mode.

Click on the Arrow icon to expand its contents in the Hierarchy and select Child. This is where you'll store your generated mesh.

Saving your mesh.

Exit prefab editing mode and open HeartMeshInspector.cs. At the end of OnInspectorGUI, before the closing braces, add the following:

if (!mesh.isEditMode && mesh.isMeshReady)
{
    string path = "Assets/RW/Prefabs/CustomHeart.prefab"; //1

    if (GUILayout.Button("Save Mesh"))
    {
        mesh.isMeshReady = false;
        Object prefabToInstantiate = 
            AssetDatabase.LoadAssetAtPath(path, typeof(GameObject)); //2
        Object referencePrefab =
            AssetDatabase.LoadAssetAtPath (path, typeof(GameObject));
        GameObject gameObj =
            (GameObject)PrefabUtility.InstantiatePrefab(prefabToInstantiate);
        Mesh prefabMesh = (Mesh)AssetDatabase.LoadAssetAtPath(path,
            typeof(Mesh)); //3
        if (!prefabMesh)
        {
            prefabMesh = new Mesh();
            AssetDatabase.AddObjectToAsset(prefabMesh, path);
        }
        else
        {
            prefabMesh.Clear();
        }
        prefabMesh = mesh.SaveMesh(prefabMesh); //4

        gameObj.GetComponentInChildren<MeshFilter>().mesh = prefabMesh; //5
        PrefabUtility.SaveAsPrefabAsset(gameObj, path); //6
        Object.DestroyImmediate(gameObj); //7
    }
}

Here's what the code does:

  1. Stores the CustomHeart prefab object asset path, which you need to be able to write to the file.
  2. Creates two objects from the CustomHeart prefab, one as a GameObject and the other as a reference.
  3. Creates an instance of the mesh asset prefabMesh from CustomHeart. If it finds the asset, it clears its data; otherwise, it creates a new empty mesh.
  4. Updates prefabMesh with new mesh data and associates it with the CustomHeart asset.
  5. Updates the GameObject's mesh asset with prefabMesh.
  6. Creates a Prefab Asset at the given path from the given gameObj, including any children in the scene. This replaces whatever was in the CustomHeart prefab.
  7. Destroys gameObj immediately.

Save your file and go to HeartMesh.cs. Replace the body of SaveMeshwith the following:

meshToSave.name = "HeartMesh";
meshToSave.vertices = originalMesh.vertices;
meshToSave.triangles = originalMesh.triangles;
meshToSave.normals = originalMesh.normals;
return meshToSave;

This will return a mesh asset based on the heart-shaped mesh.

Save the file and return to Unity. Press Play. When the animation ends, a Save Mesh button will appear in the Inspector. Click on the button to save your new mesh, then stop the player.

Find Prefabs/CustomHeart again in the Project view and open it in Prefab Editing mode. You will see a brand spanking new heart-shaped mesh has been saved in your prefab!

Your new heart mesh.

Good Job!

Putting It All Together

In the previous section, you learned how to modify a mesh by selecting individual vertices. While this is cool, you can do more interesting things if you know how to select vertices procedurally.

In the previous scene, DisplaceVertices uses a Gaussian falloff formula to determine how much to "pull" each vertex within the radius of the effect. But there are other mathematical functions you can use to calculate the 'fall off' point; that is, where the pull strength starts decaying. Each function produces a different shape:

Three shapes that different falloff formulas make.

Like cupcake toppings...

In this section, you will learn how to manipulate vertices using a calculated curve.

Based on the principle that velocity equals distance divided by time (v=(d/t)), you can determine the vector's position by referencing its distance divided by its time factor.

Determining the vector's position mathematically.

Using the Curve Method

Save your current scene, then open 03 Customize Heart Mesh from the Scenes folder.

Find the instance of your CustomHeart prefab in the hierarchy and click on the arrow icon next to it to expand its content. Select the Child object.

View its properties in the Inspector. You'll see Mesh Filter with the Heart Mesh asset. Attach Custom Heart to Child. The asset should now change from HeartMesh to clone.

Attaching a Custom Heart to Child.

Open CustomHeart.cs and add the following right above Start:

public enum CurveType
{
    Curve1, Curve2
}

public CurveType curveType;
Curve curve;

This creates a public enum named CurveType and makes it available in the Inspector.

Go to CurveType1 and add the following:

Vector3[] curvepoints = new Vector3[3]; //1
curvepoints[0] = new Vector3(0, 1, 0);
curvepoints[1] = new Vector3(0.5f, 0.5f, 0);
curvepoints[2] = new Vector3(1, 0, 0);
curve = new Curve(curvepoints[0], curvepoints[1], curvepoints[2], false); //2

What's going on?

  1. The basic curve consists of three points. This code sets and plots the points for the first curve.
  2. Generates the first curve with Curve and assigns its values to curve. You can set the last parameter to true to draw the curve as a preview.

Now go to CurveType2 and add the following:

Vector3[] curvepoints = new Vector3[3]; //1
curvepoints[0] = new Vector3(0, 0, 0);
curvepoints[1] = new Vector3(0.5f, 1, 0);
curvepoints[2] = new Vector3(1, 0, 0);
curve = new Curve(curvepoints[0], curvepoints[1], curvepoints[2], false); //2

This works much like CurveType1.

  1. Sets and plots the points for the second curve.
  2. Generates the second curve with the Curve method and assigns its values to curve.

In StartDisplacement, before the closing braces, add the following:

if (curveType == CurveType.Curve1)
{
    CurveType1();
}
else if (curveType == CurveType.Curve2)
{
    CurveType2();
}

This will generate different curves depending on what you select as the Curve Type in the Custom Heart component.

In DisplaceVertices, inside the for loop, before the closing braces, add the following:

float increment = curve.GetPoint(distance).y * force; //1
Vector3 translate = (vert * increment) * Time.deltaTime; //2
Quaternion rotation = Quaternion.Euler(translate); 
Matrix4x4 m = Matrix4x4.TRS(translate, rotation, Vector3.one);
modifiedVertices[i] = m.MultiplyPoint3x4(modifiedVertices[i]);

This might look familiar — it's much like the code you added to HeartMesh.

  1. Gets the curve's position at the given distance and multiplies its y value by force to get increment.
  2. Creates a new Vector3 called translate to store the new position for the current vertex and applies its Transform accordingly.

Save the file and return to Unity. Check out the properties in Custom Heart on the Child GameObject.

In the Edit Type drop-down menu, you can now select Add Indices or Remove Indices to update your list of vertices. Select None to exit Edit mode, then click Play see the results. Experiment with different settings and vertex selections.

Effects of different settings and vertex selections.

To see an example of the different curve types, enter these values:

Effects of different curve types.

Set Curve Type to Curve1, check that Edit Type is set to None and press Play.

How different curve types affect your heart mesh.

You should see how the mesh fans out into a pattern. Move the model around to its side-view so you can see the shape this curve produces. Exit Play and try it again with Curve 2 to compare the results of both curve types:

Effect of applying the first curve type.

Effect of applying the second curve type.

That's it! You can click Clear Selected Vertices to reset the Selected Indices and experiment with your own patterns. Don't forget that there are several factors that will affect the final shape of the mesh:

  • The size of the radius.
  • The spread of vertices within the area.
  • The pattern position of the selected vertices.
  • The method that you choose for displacement.

Where to Go From Here?

You can get all of the files for the final project by using the Download Materials button at the top and bottom of this tutorial.

Don't stop here! Try out more advanced techniques with Procedural Maze Generation.

I hope you have enjoyed this tutorial and found the information useful. Special credit to Jasper Flick from Catlike Coding for his great tutorials, which helped me put together the demos for this project.

Modified heart mesh.

Feel free to join the discussion forum below for any questions or comments!

Add a rating for this content

Contributors

Comments