Unity自定义台体网格顶部/底部法线异常问题求助
Unity台体网格法线异常问题解决
问题描述
我们在Unity中通过代码生成树干模型,使用以下代码创建台体网格,能生成基础3D形状,但顶部和底部的法线显示异常,导致光照效果失常——法线向上时过亮,向下时过暗。我们是按照教程编写的顶面与底面三角面逻辑,需要排查异常原因并解决。项目要求必须在Unity内实现,不能使用Blender等外部建模软件。
原始代码
using System.Collections; using System.Collections.Generic; using UnityEngine; [ExecuteInEditMode] public class Frustum : MonoBehaviour { /*====== PUBLIC ======*/ public float height = 1; public float top_radius = 0.9F; public float base_radius = 1F; public int circle_subdivisions = 30; /*====== PRIVATE ======*/ private Mesh mesh; private Vector3[] vao; private int[] ibo; // Start is called before the first frame update void Start() { mesh = new Mesh(); mesh.name = "Cone"; this.GetComponent<MeshFilter>().mesh = mesh; draw(); } // OnValidate is called at each public input change private void OnValidate() { draw(); } private void draw() { ComputeFrustumVao(); ComputeFrustumIbo(); mesh.Clear(); mesh.vertices = vao; mesh.triangles = ibo; mesh.RecalculateBounds(); mesh.RecalculateTangents(); mesh.RecalculateNormals(); // mesh.Optimize(); } private void ComputeFrustumVao() { // COMPUTE BASE VERTICES List<Vector3> vertices = ComputeCircleVertices(transform.position, base_radius, circle_subdivisions); // ADD TOP VERTICES Vector3 top_origin = transform.position + new Vector3(0, height, 0); vertices.AddRange(ComputeCircleVertices(top_origin, top_radius, circle_subdivisions)); vao = vertices.ToArray(); } private List<Vector3> ComputeCircleVertices(Vector3 origin, float radius, int nb_subdivisions) { List<Vector3> vao = new List<Vector3>(); float step_angle = (2*Mathf.PI)/nb_subdivisions; for(int i = 0; i < nb_subdivisions; ++i){ float current_angle = step_angle * i; Vector3 translate_vector = new Vector3( Mathf.Cos(current_angle) * radius, // x coordinate 0, // y coordinate Mathf.Sin(current_angle) * radius // z coordinate ); Vector3 new_vertex = origin + translate_vector; vao.Add(new_vertex); } return vao; } private void ComputeFrustumIbo() { List<int> indices = new List<int>(); int nb_vertices = vao.Length; int nb_vertices_per_circle = nb_vertices/2; int nb_triangles = nb_vertices_per_circle - 2; // DRAW BASE for(int i = 0; i < nb_triangles; ++i){ indices.Add(0); indices.Add(i + 1); indices.Add(i + 2); } // DRAW TOP for(int i = nb_vertices_per_circle; i < nb_vertices_per_circle + nb_triangles; ++i){ indices.Add(nb_vertices_per_circle); indices.Add(i + 2); indices.Add(i + 1); } // DRAW SIDES for(int i = 0; i < nb_vertices_per_circle; ++i){ int next_index = (i + 1) % nb_vertices_per_circle; indices.Add(i); // BOTTOM LEFT indices.Add( nb_vertices_per_circle + next_index); // TOP RIGHT indices.Add(next_index); // BOTTOM RIGHT indices.Add(i); // BOTTOM LEFT indices.Add(i + nb_vertices_per_circle); // TOP LEFT indices.Add(nb_vertices_per_circle + next_index); // TOP RIGHT } ibo = indices.ToArray(); } void OnDrawGizmosSelected() //Draw the markers, if the object is selected { Gizmos.color = Color.red; for (int i=0; i< mesh.vertices.Length; i++){ Gizmos.color= new Color(((float)i)/mesh.vertices.Length,0, 0); Vector3 worldPos = transform.TransformPoint(mesh.vertices[i]); Gizmos.DrawCube(worldPos, Vector3.one * 0.1f); } } }
异常原因
问题核心在于顶面和底面三角面的顶点缠绕顺序(Winding Order)错误:
- Unity的
RecalculateNormals()方法会根据三角面顶点的顺时针/逆时针顺序计算法线方向:顶点按逆时针顺序排列时,法线指向模型外部;顺时针则指向内部。 - 原始代码中,底面三角面是顺时针顺序,导致法线向下(指向模型内部);顶面的索引循环逻辑错误,导致法线方向也不符合预期,最终引发光照异常。
解决方案
方案1:修正三角面顶点顺序(推荐)
修改ComputeFrustumIbo()方法中的底面和顶面索引生成逻辑:
private void ComputeFrustumIbo() { List<int> indices = new List<int>(); int nb_vertices = vao.Length; int nb_vertices_per_circle = nb_vertices/2; int nb_triangles = nb_vertices_per_circle - 2; // 修正:底面三角面反转顶点顺序,让法线向上(模型外部) for(int i = 0; i < nb_triangles; ++i){ indices.Add(0); indices.Add(i + 2); indices.Add(i + 1); } // 修正:顶面三角面用正确的索引逻辑,保持逆时针顺序,法线向上 for(int i = 0; i < nb_triangles; ++i){ int topCenter = nb_vertices_per_circle; indices.Add(topCenter); indices.Add(topCenter + i + 1); indices.Add(topCenter + i + 2); } // 侧面索引逻辑保持不变 for(int i = 0; i < nb_vertices_per_circle; ++i){ int next_index = (i + 1) % nb_vertices_per_circle; indices.Add(i); // BOTTOM LEFT indices.Add( nb_vertices_per_circle + next_index); // TOP RIGHT indices.Add(next_index); // BOTTOM RIGHT indices.Add(i); // BOTTOM LEFT indices.Add(i + nb_vertices_per_circle); // TOP LEFT indices.Add(nb_vertices_per_circle + next_index); // TOP RIGHT } ibo = indices.ToArray(); }
修改后,RecalculateNormals()会自动计算出正确的外部法线,光照效果恢复正常。
方案2:手动指定法线(彻底控制)
如果自动计算仍有问题,可以跳过RecalculateNormals(),手动生成法线数组:
修改draw()方法:
private void draw() { ComputeFrustumVao(); ComputeFrustumIbo(); mesh.Clear(); mesh.vertices = vao; mesh.triangles = ibo; mesh.RecalculateBounds(); mesh.RecalculateTangents(); // 手动生成法线 Vector3[] normals = new Vector3[vao.Length]; int half = vao.Length / 2; // 底面法线全部向上 for(int i = 0; i < half; i++){ normals[i] = Vector3.up; } // 顶面法线全部向上 for(int i = half; i < vao.Length; i++){ normals[i] = Vector3.up; } // 侧面法线计算:垂直于侧边和底边的方向 for(int i = 0; i < half; i++){ int next = (i + 1) % half; Vector3 bottomEdge = vao[next] - vao[i]; Vector3 sideEdge = vao[i + half] - vao[i]; normals[i] = Vector3.Cross(bottomEdge, sideEdge).normalized; normals[i + half] = normals[i]; } mesh.normals = normals; // 注释掉自动计算法线的代码 // mesh.RecalculateNormals(); }
这种方式可以完全控制每个顶点的法线方向,避免自动计算的不确定性。
内容的提问来源于stack exchange,提问作者Jordan Roberty
相关产品推荐
相关产品推荐

