You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Unity自定义Mesh着色显示异常问题求助

Unity Mesh顶点色光照异常问题修复

背景说明

基于顶点(Vertex)和三角索引(Triangle Indices)数据创建Mesh,在createPolyMesh函数中生成Polygon对象,存储带颜色的顶点数组与三角索引数组(本次所有顶点使用同一颜色),后续添加了法线(Normals)和UV计算(无需纹理,仅需基于顶点色实现正确光照)。

核心函数代码

private void createPolyMesh()
{
    bool multiplePolygons = false;
    
    for(int h = 0; h < protoData.Polygons.Count; h++) 
    {
        PolygonData.Data.Types.Polygon polygon = protoData.Polygons[h];

        Vector3[] polyVertices = new Vector3[polygon.Vertex.Count];
        int[] triangles = new int[(polygon.TriangleIndices.Count * 3)];
        Color[] colors = new Color[polygon.Vertex.Count];
        Vector2[] uvs = new Vector2[polygon.Vertex.Count];
        Vector3[] normals = new Vector3[polygon.Vertex.Count];

        GameObject polyObject = new GameObject("Polygon " + h);
        Mesh polyMesh = new Mesh();
        polyObject.AddComponent<MeshFilter>().mesh = polyMesh;
        polyObject.AddComponent<MeshRenderer>();
        
        for(int i = 0; i < polygon.Vertex.Count; i++) {
            polyVertices[i] = new Vector3(polygon.Vertex[i].Point.X, polygon.Vertex[i].Point.Y, polygon.Vertex[i].Point.Z);
            uvs[i] = new Vector2(polyVertices[i][0], polyVertices[i][2]);
            colors[i] = new Color(polygon.Vertex[i].Color.R, polygon.Vertex[i].Color.G, polygon.Vertex[i].Color.B, polygon.Vertex[i].Color.A);
        }

        for(int j = 0; j < polygon.TriangleIndices.Count; j++) {
            int k = (j + (j * 2));
            triangles[k] = (int)polygon.TriangleIndices[j].A;
            triangles[k + 1] = (int)polygon.TriangleIndices[j].B;
            triangles[k + 2] = (int)polygon.TriangleIndices[j].C;
        }

        for(int l = 0; l < polygon.Vertex.Count - 2; l++) {
            int m = (l + (l * 2));
            Vector3 p0 = polyVertices[l];
            Vector3 p1 = polyVertices[l+1];
            Vector3 p2 = polyVertices[l+2];

            normals[l] = (Vector3.Cross(p1-p0, p2-p0)).normalized;

        }

        polyMesh.vertices = polyVertices;
        polyMesh.uv = uvs;
        polyMesh.triangles = triangles;
        polyMesh.colors = colors;
        polyMesh.normals = normals;
        polyObject.GetComponent<MeshRenderer>().material = new Material(Shader.Find("Custom/VertexColor"));

        TopologySetter topologySetter = new TopologySetter();
        topologySetter.addPolyMesh(polyObject.GetComponent<MeshFilter>().mesh);

        if(protoData.Polygons.Count > 1 && h == 0) 
        {
            GameObject model = new GameObject("Model");
            multiplePolygons = true;
        } 
        
        if(multiplePolygons) 
        {
            polyObject.transform.SetParent(GameObject.Find("Model").transform);
        }
    }
}

原Shader代码

Shader "Custom/NewSurfaceShader"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        sampler2D _MainTex;

        struct Input
        {
            float2 uv_MainTex;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;

        // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
        // #pragma instancing_options assumeuniformscaling
        UNITY_INSTANCING_BUFFER_START(Props)
            // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, IN.uv_MainTex) * _Color;
            o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

问题现象

使用上述Shader后,3D球体Mesh着色效果与预期差异极大,显示异常,初步排查为法线未正确覆盖整个Mesh导致。


修复方案

1. 修复法线计算逻辑

原代码仅计算了前polygon.Vertex.Count - 2个顶点的法线,剩余顶点法线为默认零向量,直接导致光照异常。有两种修复方式:

方式一:使用Unity内置方法自动计算(推荐)

移除手动计算法线的循环,改用Mesh.RecalculateNormals(),Unity会自动基于三角面计算并平均顶点法线:

// 替换原法线计算代码,在设置完vertices、triangles后调用
polyMesh.vertices = polyVertices;
polyMesh.uv = uvs;
polyMesh.triangles = triangles;
polyMesh.colors = colors;
// 自动计算法线
polyMesh.RecalculateNormals();

方式二:手动正确计算顶点法线

遍历每个三角面计算面法线,将面法线累加到对应顶点的法线数组,最后归一化:

// 初始化法线数组为零向量
for(int l = 0; l < normals.Length; l++) {
    normals[l] = Vector3.zero;
}

// 遍历每个三角面,计算面法线并分配给顶点
for(int j = 0; j < polygon.TriangleIndices.Count; j++) {
    int idxA = (int)polygon.TriangleIndices[j].A;
    int idxB = (int)polygon.TriangleIndices[j].B;
    int idxC = (int)polygon.TriangleIndices[j].C;

    Vector3 p0 = polyVertices[idxA];
    Vector3 p1 = polyVertices[idxB];
    Vector3 p2 = polyVertices[idxC];

    Vector3 faceNormal = Vector3.Cross(p1 - p0, p2 - p0).normalized;

    // 将面法线加到对应顶点的法线中
    normals[idxA] += faceNormal;
    normals[idxB] += faceNormal;
    normals[idxC] += faceNormal;
}

// 归一化每个顶点的法线
for(int l = 0; l < normals.Length; l++) {
    normals[l] = normals[l].normalized;
}

polyMesh.normals = normals;

2. 修改Shader以支持顶点色

原Shader未使用Mesh的顶点色数据,而是依赖纹理和颜色叠加,需修改为直接采样顶点色:

Shader "Custom/VertexColorStandard"
{
    Properties
    {
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        #pragma surface surf Standard fullforwardshadows vertex:vert

        #pragma target 3.0

        struct Input
        {
            float4 vertexColor : COLOR; // 接收顶点色
        };

        half _Glossiness;
        half _Metallic;

        // 顶点着色器,传递顶点色到片段着色器
        void vert (inout appdata_full v, out Input o)
        {
            UNITY_INITIALIZE_OUTPUT(Input, o);
            o.vertexColor = v.color;
        }

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            // 使用顶点色作为Albedo
            o.Albedo = IN.vertexColor.rgb;
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = IN.vertexColor.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

3. 更新代码中的Shader引用

将原代码中指定Shader的部分改为新的顶点色Shader:

polyObject.GetComponent<MeshRenderer>().material = new Material(Shader.Find("Custom/VertexColorStandard"));

内容的提问来源于stack exchange,提问作者Fl0ris

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.04 20:10:24