Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

[Solved] Procedural Mesh: Wrong normal maps on some of the faces.

Discussion in 'General Graphics' started by paternostrox, Dec 7, 2019.

  1. paternostrox

    paternostrox

    Joined:
    Oct 31, 2015
    Posts:
    42
    I'm building procedural meshes and trying to get normal maps to work on them. When building a cube out of quads, normal maps work (kind of) as intended on 4 of the faces, and 2 of them (on opposite sides) are weird.

    Image of a default cube working with normal maps as intended (left), and my procedural cube with weird faces (right): https://imgur.com/a/4AwP9uN

    This is a test script for generating the cube, so anyone can test it. Just get this on a gameobject, assign a material with the standard shader and the normal map attached to this post.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class BuildCube : MonoBehaviour {
    6.  
    7.     public Material cubeMaterial;
    8.  
    9.     enum Cubeside {BOTTOM, TOP, LEFT, RIGHT, FRONT, BACK};
    10.  
    11.     void CreateQuad(Cubeside side)
    12.     {
    13.         Mesh mesh = new Mesh();
    14.         mesh.name = "ScriptedMesh" + side.ToString();
    15.  
    16.         Vector3[] vertices = new Vector3[4];
    17.         Vector3[] normals = new Vector3[4];
    18.         Vector2[] uvs = new Vector2[4];
    19.         int[] triangles = new int[6];
    20.  
    21.         //all possible UVs
    22.         Vector2 uv00 = new Vector2( 0f, 0f );
    23.         Vector2 uv10 = new Vector2( 1f, 0f );
    24.         Vector2 uv01 = new Vector2( 0f, 1f );
    25.         Vector2 uv11 = new Vector2( 1f, 1f );
    26.  
    27.         //all possible vertices
    28.         Vector3 p0 = new Vector3( -0.5f,  -0.5f,  0.5f );
    29.         Vector3 p1 = new Vector3(  0.5f,  -0.5f,  0.5f );
    30.         Vector3 p2 = new Vector3(  0.5f,  -0.5f, -0.5f );
    31.         Vector3 p3 = new Vector3( -0.5f,  -0.5f, -0.5f );    
    32.         Vector3 p4 = new Vector3( -0.5f,   0.5f,  0.5f );
    33.         Vector3 p5 = new Vector3(  0.5f,   0.5f,  0.5f );
    34.         Vector3 p6 = new Vector3(  0.5f,   0.5f, -0.5f );
    35.         Vector3 p7 = new Vector3( -0.5f,   0.5f, -0.5f );
    36.  
    37.         switch(side)
    38.         {
    39.             case Cubeside.BOTTOM:
    40.                 vertices = new Vector3[] {p0, p1, p2, p3};
    41.                 normals = new Vector3[] {Vector3.down, Vector3.down,
    42.                                             Vector3.down, Vector3.down};
    43.                 uvs = new Vector2[] {uv11, uv01, uv00, uv10};
    44.                 triangles = new int[] { 3, 1, 0, 3, 2, 1};
    45.             break;
    46.             case Cubeside.TOP:
    47.                 vertices = new Vector3[] {p7, p6, p5, p4};
    48.                 normals = new Vector3[] {Vector3.up, Vector3.up,
    49.                                             Vector3.up, Vector3.up};
    50.                 uvs = new Vector2[] {uv11, uv01, uv00, uv10};
    51.                 triangles = new int[] {3, 1, 0, 3, 2, 1};
    52.             break;
    53.             case Cubeside.LEFT:
    54.                 vertices = new Vector3[] {p7, p4, p0, p3};
    55.                 normals = new Vector3[] {Vector3.left, Vector3.left,
    56.                                             Vector3.left, Vector3.left};
    57.                 uvs = new Vector2[] {uv11, uv01, uv00, uv10};
    58.                 triangles = new int[] {3, 1, 0, 3, 2, 1};
    59.             break;
    60.             case Cubeside.RIGHT:
    61.                 vertices = new Vector3[] {p5, p6, p2, p1};
    62.                 normals = new Vector3[] {Vector3.right, Vector3.right,
    63.                                             Vector3.right, Vector3.right};
    64.                 uvs = new Vector2[] {uv11, uv01, uv00, uv10};
    65.                 triangles = new int[] {3, 1, 0, 3, 2, 1};
    66.             break;
    67.             case Cubeside.FRONT:
    68.                 vertices = new Vector3[] {p4, p5, p1, p0};
    69.                 normals = new Vector3[] {Vector3.forward, Vector3.forward,
    70.                                             Vector3.forward, Vector3.forward};
    71.                 uvs = new Vector2[] {uv11, uv01, uv00, uv10};
    72.                 triangles = new int[] {3, 1, 0, 3, 2, 1};
    73.             break;
    74.             case Cubeside.BACK:
    75.                 vertices = new Vector3[] {p6, p7, p3, p2};
    76.                 normals = new Vector3[] {Vector3.back, Vector3.back,
    77.                                             Vector3.back, Vector3.back};
    78.                 uvs = new Vector2[] {uv11, uv01, uv00, uv10};
    79.                 triangles = new int[] {3, 1, 0, 3, 2, 1};
    80.             break;
    81.         }
    82.  
    83.         mesh.vertices = vertices;
    84.         mesh.normals = normals;
    85.         mesh.uv = uvs;
    86.         mesh.triangles = triangles;
    87.    
    88.         mesh.RecalculateBounds();
    89.    
    90.         GameObject quad = new GameObject("Quad");
    91.         quad.transform.parent = this.gameObject.transform;
    92.          MeshFilter meshFilter = (MeshFilter) quad.AddComponent(typeof(MeshFilter));
    93.         meshFilter.mesh = mesh;
    94.         MeshRenderer renderer = quad.AddComponent(typeof(MeshRenderer)) as MeshRenderer;
    95.         renderer.material = cubeMaterial;
    96.     }
    97.  
    98.     void CombineQuads()
    99.     {
    100.    
    101.         //1. Combine all children meshes
    102.         MeshFilter[] meshFilters = GetComponentsInChildren<MeshFilter>();
    103.         CombineInstance[] combine = new CombineInstance[meshFilters.Length];
    104.         int i = 0;
    105.         while (i < meshFilters.Length) {
    106.             combine[i].mesh = meshFilters[i].sharedMesh;
    107.             combine[i].transform = meshFilters[i].transform.localToWorldMatrix;
    108.             i++;
    109.         }
    110.  
    111.         //2. Create a new mesh on the parent object
    112.         MeshFilter mf = (MeshFilter) this.gameObject.AddComponent(typeof(MeshFilter));
    113.         mf.mesh = new Mesh();
    114.  
    115.         //3. Add combined meshes on children as the parent's mesh
    116.         mf.mesh.CombineMeshes(combine);
    117.  
    118.         //4. Create a renderer for the parent
    119.         MeshRenderer renderer = this.gameObject.AddComponent(typeof(MeshRenderer)) as MeshRenderer;
    120.         renderer.material = cubeMaterial;
    121.  
    122.         //5. Delete all uncombined children
    123.         foreach (Transform quad in this.transform) {
    124.              Destroy(quad.gameObject);
    125.          }
    126.  
    127.     }
    128.  
    129.     void CreateCube()
    130.     {
    131.         CreateQuad(Cubeside.FRONT);
    132.         CreateQuad(Cubeside.BACK);
    133.         CreateQuad(Cubeside.TOP);
    134.         CreateQuad(Cubeside.BOTTOM);
    135.         CreateQuad(Cubeside.LEFT);
    136.         CreateQuad(Cubeside.RIGHT);
    137.         CombineQuads();
    138.     }
    139.  
    140.     void Start () {
    141.         CreateCube();
    142.     }
    143. }
    144.  
    I also found this thread:
    https://forum.unity.com/threads/nor...correctly-on-left-right-faces-of-mesh.505352/

    But couldn't really understand what the problem was in this case since the author never replied back and my mesh won't look right even though my UV Set is UV0 on the material I described. I tried to read @bgolus article linked there on triplanar mapping, but couldn't really understand either because I know too little about the subject I guess.

    How can I get normal maps to work as intended on procedural meshes?
     

    Attached Files:

    Last edited: Dec 7, 2019
    jakken109 likes this.
  2. paternostrox

    paternostrox

    Joined:
    Oct 31, 2015
    Posts:
    42
    Oh ok, so very simple solution. I didn't knew I had to recalculate the tangents. Happily, the mesh class have a method for recalculating the tangets. So if someone sees this, just remember to RecalculateTangents after everything. Only then normal maps will look fine on your own little meshy.
     
  3. jakken109

    jakken109

    Joined:
    Dec 2, 2018
    Posts:
    1
    This is such a simple solution, and it's even documented under the RecalculateTangents function that its needed to make normal maps work with generated meshes. WHY IS THIS INFORMATION SO DIFFICULT TO FIND?! Necroing this thread so hopefully it will be easier to find for the next person.