1
\$\begingroup\$

I'm currently working on a procedurally generated sphere but I can't get rid of those seams between the meshes. My first step is to generate a cube and normalize all vertices. I've already search the internet for solution but I never achieved a full removal only just made less visible the seams.

Correct me if I'm wrong but the root cause is this: Unity uses per vertex normals for creating smooth shading instead of flat shading and at the mesh edge the normals are not correctly calculated by the built-in RecalculateNormals method.

So the solution what I found: during vertex generation of a mesh also create border vertices and Unity will calculate correct normals and after that I remove border vertices and triangles from the mesh.

Here you can see seams without any fix: enter image description here

I tried to generate border for every side of the cube, call RecalculateNormals on the Mesh object and reassign vertices, indices and normals without the border. The result is better but not perfect (Solution A): enter image description here

I was thinking about this border. If I generate one strip border and I normalize also the border vertices then these border triangles won't be there where adjacent mesh's edge triangles are located:

enter image description here

So I tried to move the border vertices to overlap the adjacent mesh's most outer triangles (most outer strip): enter image description here

And the result is almost perfect but the borders are visible (Solution B): enter image description here

Is it possible to remove seams fully? I want to use that solution where I just create border on the plane of the current mesh and not move to adjacent mesh. It's easier to create meshes with one unit wider.

EDIT1

This code is used for generation. I collect mesh/border vertices and indices in separate lists in the MeshData object.

private MeshData CalculateMeshData()
{
    MeshData meshData = new MeshData();
    meshData.ChunkSize = CHUNK_SIZE;
    meshData.BorderSize = BORDER_SIZE;

    int borderedResolution = meshData.BorderedResolution;
    int vertexIndex = -1;
    float faceWidth = _radius * 2 / CHUNK_SIZE;
    float currentYPosition = -_radius - faceWidth * BORDER_SIZE;

    for (int y = 0; y < borderedResolution; y++)
    {
        float currentXPosition = -_radius - faceWidth * BORDER_SIZE;
        for (int x = 0; x < borderedResolution; x++)
        {
            bool isMeshVertex = !IsBorderVertex(x, y, borderedResolution);
            Vector3 axisAOffset = _axisA * currentXPosition;
            Vector3 axisbOffset = _axisB * currentYPosition;
            Vector3 pointOnUnitCube = _center + axisAOffset + axisbOffset;
            Vector3 vertex = pointOnUnitCube.normalized;

            vertexIndex++;
            meshData.AddVertex(vertex, isMeshVertex);

            // Prevents to create more two triangles next to the edge of the mesh.
            if (x != borderedResolution - 1 && y != borderedResolution - 1)
            {
                bool isMeshTriangle = IsMeshTriangle(x, y, borderedResolution);
                meshData.AddTriangle(vertexIndex,
                                         vertexIndex + borderedResolution + 1,
                                         vertexIndex + borderedResolution,
                                         isMeshTriangle);

                meshData.AddTriangle(vertexIndex,
                                     vertexIndex + 1,
                                     vertexIndex + borderedResolution + 1,
                                     isMeshTriangle);
            }

            currentXPosition += faceWidth;
        }

        currentYPosition += faceWidth;
    }

    return meshData;
}

And here is the mesh creation:

private void GenerateMesh()
{
    // Creates object and its components.
    _meshObject = new GameObject("mesh");
    _meshFilter = _meshObject.AddComponent<MeshFilter>();
    _meshRenderer = _meshObject.AddComponent<MeshRenderer>();
    _meshRenderer.material = Shape.Material;
    _mesh = new Mesh();
    _meshFilter.sharedMesh = _mesh;

    // Creates the mesh and calculates normals with the border faces.
    MeshData meshData = CalculateMeshData();
    _mesh.vertices = meshData.GetVertices(true);
    _mesh.triangles = meshData.GetIndices(true);
    _mesh.RecalculateNormals();

    // Recreates mesh without the border.
    Vector3[] normals = _mesh.normals;
    _mesh.Clear();
    _mesh.vertices = meshData.GetVertices(false);
    _mesh.triangles = meshData.GetIndices(false);
    _mesh.normals = meshData.GetMeshNormals(normals);
}

The GetVertices and GetIndices method has a switch which controls whether mesh data returned including border or not.

The GetMeshNormals method returns normals for the mesh without the border.

\$\endgroup\$
2
\$\begingroup\$

The problem is that RecalculateNormals doesn't know about the adjacent meshes.

It works similar to the code I show in this answer: for each face in your mesh, it calculates a face normal. Then for each vertex, it averages-together the normals from each face it touches, proportionate to the area of those faces.

Let's take a vertex in the middle of your mesh. It's surrounded by faces...

  • to its right, with a normal leaning slightly to the right (+x)
  • to its left, with a normal leaning slightly to the left (-x)
  • in front of it, with a normal leaning slightly forward (+z)
  • behind it, with a normal leaning slightly backward (-z)

All these slight leans cancel out when we average the normals, and you're left with a normal that points straight out along the line joining the vertex to the sphere's center.

Now let's take a vertex on the edge of your mesh. It's missing faces on one side to counter-balance the leaning of the normals from faces on the other side. So it gets a normal appropriate to those faces that are present in the mesh, without considering the absent faces beyond the edge. The result is a slight bias inward toward the center of your mesh.

The simplest solution here is to just calculate your own normals. If your mesh is a sphere, you can take the vector from the sphere's center to your vertex and normalize that. Now it's exactly the vertex normal you want, no matter whether it's on the edge of your mesh or somewhere in the middle. And the calculation is consistent, so the next mesh chunk you generate will exactly agree with this normal at the place where they meet. This may even be more efficient than RecalculateNormals since you're saving it the work of computing and summing the face normals.

If you're displacing your sphere (eg. with noise functions), then this gets a bit more complicated, but computing exact normals analytically is often possible, and where it's not, we can find ways to compute normals in a consistent way using eg. partial differences. But we'll need a new question with details of your deformation/displacement method to be able to advise on the specifics here.

\$\endgroup\$
10
  • \$\begingroup\$ I forgot to mention that some noise (Perlin noise) will be added in the next step so setting normals to the direction between vertex and center of the sphere is not applicable. \$\endgroup\$ Dec 20 '20 at 7:02
  • \$\begingroup\$ Is it enough to use only one unit wide border in the same plane? I started to use your code to calculate normals for each faces but should I skip the border indices in the loop where average normal is calculated? \$\endgroup\$ Dec 20 '20 at 9:55
  • \$\begingroup\$ Re: noise, see my last paragraph. We can still calculate correct normals in that case without relying on averaging face normals and needing extra polygon strips. We'll just need to see the specifics of your perturbation code to show you how to do it correctly for the method you're using. Do not use my normal generation code from.the linked answer — as I said above, it does effectively the same thing as RecalculateNormals, so it will not fix your problem. \$\endgroup\$
    – DMGregory
    Dec 20 '20 at 12:29
  • \$\begingroup\$ I added my code in the description. \$\endgroup\$ Dec 20 '20 at 16:18
  • \$\begingroup\$ I don't see the code that perturbs the sphere using Perlin noise. Am I missing something? Maybe you'd like to ask a second question about that aspect, in a new post? \$\endgroup\$
    – DMGregory
    Dec 20 '20 at 16:27

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.