2
\$\begingroup\$

I am trying to show both sides of a mesh. I understand this question has been asked a lot, but all of the answers I've found seem like they're written for Unity 5 and I can't find a "Cull" setting on the shader.

Beyond that, I'm actually okay with extending the mesh to render both sides. In what I've read online, this may actually be the "correct" way to do it in that this should get all the lighting, shadows, etc. to render correctly.

My issue is that, when I make a mesh face two different directions, it's like the materials collide and I get a black appearance instead of the material I'm expecting:

Creating both faces

The way I would expect

The bottom is what I expect to see, but the top is what I'm getting when I render both faces.

To recreate, create an empty game object. Add a MeshRenderer and a MeshFilter, then add the TestMesh script:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public class TestMesh : MonoBehaviour
{
    public float width = 1f;
    public float length = 1f;
    public bool showPlane = true;

    private void OnValidate()
    {
        if(showPlane)
        {
            MeshRenderer rendObj = gameObject.GetComponent<MeshRenderer>();
            MeshFilter meshObj = gameObject.GetComponent<MeshFilter>();
            if((rendObj==null)||(meshObj==null))
            {
                Debug.LogErrorFormat("Error - TestMesh: MeshRenderers and MeshFilters need to be added to {0}", gameObject.ToString());
                return;
            }
            Mesh testMesh = meshObj.sharedMesh;
            testMesh.Clear();
            CreateMesh(testMesh);
        }
    }

    private void CreateMesh(Mesh mesh)
    {
        float x = 0.5f * width;
        float y = 0f;
        float z = 0.5f * length;

        Vector3 origin = Vector3.zero;

        // Make the corners of the plane
        Vector3 corner1 = new Vector3( x, y,  z);
        Vector3 corner2 = new Vector3( x, y, -z);
        Vector3 corner3 = new Vector3(-x, y, -z);
        Vector3 corner4 = new Vector3(-x, y,  z);

        // Rotate them to whatever the orientation of the gameObject is
        corner1 = gameObject.transform.rotation * corner1;
        corner2 = gameObject.transform.rotation * corner2;
        corner3 = gameObject.transform.rotation * corner3;
        corner4 = gameObject.transform.rotation * corner4;

        // Make them into a list for easy iteration
        List<Vector3> corners = new List<Vector3>(new Vector3[] { corner1, corner2, corner3, corner4, corner1}); // Add corner1 again at the end to close the loop

        // Initialize the vertices, uvs, and triangles lists
        List<Vector3> vertices = new List<Vector3>();
        List<Vector2> uvs = new List<Vector2>();
        List<int> triangles = new List<int>();

        // vertices[0] is the origin of the plane, and the plane is comprised of four triangles. 
        vertices.Add(origin);
        uvs.Add(Vector3.zero);

        // Add the first corner to the vertices list, so now vertices[1] = corner1
        vertices.Add(corners[0]);
        uvs.Add(new Vector2(corners[0].x, corners[0].z));

        // Loop through the remainder of the corners
        for (int i=1; i<corners.Count; i++)
        {
            vertices.Add(corners[i]);
            uvs.Add(new Vector2(corners[i].x, corners[i].z));

            triangles.Add(0); // Each triangle starts at the origin and links two corners
            triangles.Add(i); // What you're doing here is linking a vertex of a triangle to an entry in the triangles list
            triangles.Add(i + 1); // Adding 0 means you want the first vertex to be vertices[0] - the origin. Adding [i] means 
                                  // you want cornerI, because remember that vertices[0] is origin, so vertices[1] is corner1, 
                                  // vertices[2] is corner2, etc.


            triangles.Add(0);       //     
            triangles.Add(i + 1);   //   <------- HERE IS THE TROUBLE
            triangles.Add(i);       //
                                    // I would have thought I can add the vertices in reverse order to flip the auto-calculated face normal, but
                                    // what I'm getting is a black material where it looks like maybe I'm seeing a doubling of the material even though
                                    // I should only see one face. 

        }

        // Convert the lists to arrays and send them to the mesh. 
        mesh.vertices = vertices.ToArray();
        mesh.uv = uvs.ToArray();
        mesh.triangles = triangles.ToArray();
        mesh.RecalculateNormals();
    }
}

You can use any material if you want, but here's the .mat file for the laser material I've got (it's a generic material that's slightly transparent and red):

%YAML 1.1
%TAG !u! tag:unity3d.com,2011:
--- !u!21 &2100000
Material:
  serializedVersion: 6
  m_ObjectHideFlags: 0
  m_CorrespondingSourceObject: {fileID: 0}
  m_PrefabInstance: {fileID: 0}
  m_PrefabAsset: {fileID: 0}
  m_Name: LaserPlane
  m_Shader: {fileID: 46, guid: 0000000000000000f000000000000000, type: 0}
  m_ShaderKeywords: _GLOSSYREFLECTIONS_OFF _SPECULARHIGHLIGHTS_OFF
  m_LightmapFlags: 4
  m_EnableInstancingVariants: 0
  m_DoubleSidedGI: 1
  m_CustomRenderQueue: -1
  stringTagMap: {}
  disabledShaderPasses: []
  m_SavedProperties:
    serializedVersion: 3
    m_TexEnvs:
    - _BumpMap:
        m_Texture: {fileID: 0}
        m_Scale: {x: 1, y: 1}
        m_Offset: {x: 0, y: 0}
    - _DetailAlbedoMap:
        m_Texture: {fileID: 0}
        m_Scale: {x: 1, y: 1}
        m_Offset: {x: 0, y: 0}
    - _DetailMask:
        m_Texture: {fileID: 0}
        m_Scale: {x: 1, y: 1}
        m_Offset: {x: 0, y: 0}
    - _DetailNormalMap:
        m_Texture: {fileID: 0}
        m_Scale: {x: 1, y: 1}
        m_Offset: {x: 0, y: 0}
    - _EmissionMap:
        m_Texture: {fileID: 0}
        m_Scale: {x: 1, y: 1}
        m_Offset: {x: 0, y: 0}
    - _MainTex:
        m_Texture: {fileID: 0}
        m_Scale: {x: 1, y: 1}
        m_Offset: {x: 0, y: 0}
    - _MetallicGlossMap:
        m_Texture: {fileID: 0}
        m_Scale: {x: 1, y: 1}
        m_Offset: {x: 0, y: 0}
    - _OcclusionMap:
        m_Texture: {fileID: 0}
        m_Scale: {x: 1, y: 1}
        m_Offset: {x: 0, y: 0}
    - _ParallaxMap:
        m_Texture: {fileID: 0}
        m_Scale: {x: 1, y: 1}
        m_Offset: {x: 0, y: 0}
    m_Floats:
    - _BumpScale: 1
    - _Cutoff: 0.5
    - _DetailNormalMapScale: 1
    - _DstBlend: 0
    - _GlossMapScale: 1
    - _Glossiness: 0
    - _GlossyReflections: 0
    - _Metallic: 0
    - _Mode: 0
    - _OcclusionStrength: 1
    - _Parallax: 0.02
    - _SmoothnessTextureChannel: 0
    - _SpecularHighlights: 0
    - _SrcBlend: 1
    - _UVSec: 0
    - _ZWrite: 1
    m_Colors:
    - _Color: {r: 1, g: 0.027406208, b: 0, a: 0.58431375}
    - _EmissionColor: {r: 257.00394, g: 0, b: 0, a: 1}

All I'm doing is flipping the order of the triangles, so I do

triangles.Add(0);
triangles.Add(1);
triangles.Add(2);

to make the forward-facing triangle and then do:

triangles.Add(0);
triangles.Add(2);
triangles.Add(1);

to make the backwards-facing triangle.

Again, I would expect each to get culled appropriately such that all I see is the correctly rendered material, but that's not happening. I've tried all kinds of settings in the material, but I can't find a cull setting (not that I want to go that route anyways) and I can't find anything that gets the material to render correctly.

\$\endgroup\$
  • \$\begingroup\$ Are you sure your normals are pointing outward? Try changing the order of your vertices just in case. \$\endgroup\$ – Dan Jun 6 '19 at 22:30
  • \$\begingroup\$ @Dan - changing the vertex order is how I'm currently flipping the normal. It's in the for loop in the CreateMesh method. It results in this weird overlap (first image in the post) that ruins the material appearance. The material I'm using has transparency, so it looks kind of like a shadow, but non-transparent materials show up solid black. If I comment out either triangle triplet the other renders as expected, I just can't seem to do both and I don't know if it's a bug or if I'm doing something wrong. \$\endgroup\$ – Chuck Jun 6 '19 at 23:37
1
\$\begingroup\$

RecalculateNormals (and normals in general) work on vertices. That is, each vertex has a single normal value. The problem you are seeing is due to the fact that each vertex is shared between two triangles that are facing opposite each other. The normal is just not going to work.

The solution is to duplicate your vertices, one set for the front face, and another for the back face.

\$\endgroup\$
  • \$\begingroup\$ I thought I had given that a try already but I'll give it another shot tomorrow. \$\endgroup\$ – Chuck Jun 7 '19 at 0:45
  • \$\begingroup\$ If this doesn’t work as is, you may need to calculate the normals yourself manually, in case RecalculateNormals is confused by the mesh layout. For a simple plane this should be trivial. \$\endgroup\$ – Ed Marty Jun 7 '19 at 0:48
  • \$\begingroup\$ I don't understand how to calculate normals for a vertex. I understand calculating the normal for a triangle, but how does a point have a normal? Right now I've got the vertices shared between faces. It doesn't matter for small meshes, but it makes quite a difference as the meshes get larger. Do I calculate the normal for the face and assign that as the normal for each vertex? This would imply that I can't share vertices unless the faces are coplanar. \$\endgroup\$ – Chuck Jun 7 '19 at 11:55
  • 1
    \$\begingroup\$ Normals are defined per vertex and interpolated across the connected faces in the same way UVs (and any other properties like vertex color) are. Generally, for a vertex that is in the middle of a surface that should appear smooth, the normal should be the average of all the connected faces’ normals. If you want to have a hard edge, you need to double up the vertices and assigned different normals to each one. If you want more details, you could post another question. \$\endgroup\$ – Ed Marty Jun 7 '19 at 13:00
  • \$\begingroup\$ The solution was, in fact, to duplicate the vertices. I come from a Matlab background, where I am used to using the patch command with the 'Faces' and 'Vertices' option. If I want an additional face, I just add a row to 'Faces' and it's done - no need to bother with normals. In Unity, I can still share vertices on the mesh for a particular facing (forwards or backwards), but I can't share vertices between the forward-facing and backward-facing mesh. Thanks for the guidance! \$\endgroup\$ – Chuck Jun 7 '19 at 18:13

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.