Unity描边断线解决记录

参考了这篇 https://blog.csdn.net/oldside1/article/details/106456379
导出模型的时候最好勾上导出切线空间和顶点色
这篇有优化方案 https://schemingdeveloper.com/2014/10/17/better-method-recalculate-normals-unity/
https://answers.unity.com/questions/1435670/can-a-vertex-point-vector-have-more-than-one-norma.html

Github链接
在这里插入图片描述

左边没处理,右边处理过

原理

记得补上,先去碎觉😄
其实就是构造TBN矩阵

这里记录了一些tangent.w的用途
https://blog.csdn.net/ronintao/article/details/52136673

实现

编辑器脚本

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

public class VextexColorTool : Editor
{
    public static string NewMeshPath = "Assets/Models/VectexCube.asset";

    [MenuItem("Tools/SmoothNormalToColor")]
    static void SmoothNormalToColor()
    {

        var trans = Selection.activeTransform;
        //获取Mesh
        Mesh mesh = new Mesh();
        if (trans.GetComponent<SkinnedMeshRenderer>())
        {
            mesh = trans.GetComponent<SkinnedMeshRenderer>().sharedMesh;
        }
        if (trans.GetComponent<MeshFilter>())
        {
            mesh = trans.GetComponent<MeshFilter>().sharedMesh;
        }
        Debug.Log(mesh.name);

        //声明一个Vector3数组,长度与mesh.normals一样,用于存放
        //与mesh.vertices中顶点一一对应的光滑处理后的法线值
        Vector3[] smoothedNormals = new Vector3[mesh.normals.Length];

        //开始一个循环,循环的次数 = mesh.normals.Length = mesh.vertices.Length = meshNormals.Length
        for (int i = 0; i < smoothedNormals.Length; i++)
        {
            //定义一个零值法线
            Vector3 smoothedNormal = new Vector3(0, 0, 0);
            //遍历mesh.vertices数组,如果遍历到的值与当前序号顶点值相同,则将其对应的法线与Normal相加
            for (int j = 0; j < smoothedNormals.Length; j++)
            {
                if (mesh.vertices[j] == mesh.vertices[i])
                {
                    smoothedNormal += mesh.normals[j];
                }
            }
            //归一化Normal并将meshNormals数列对应位置赋值为Normal,到此序号为i的顶点的对应法线光滑处理完成
            //此时求得的法线为模型空间下的法线
            smoothedNormal.Normalize();
            smoothedNormals[i] = smoothedNormal;
        }

        //构建模型空间→切线空间的转换矩阵
        ArrayList OtoTMatrixs = new ArrayList();
        for (int i = 0; i < mesh.normals.Length; i++)
        {
            Vector3[] OtoTMatrix = new Vector3[3];
            OtoTMatrix[0] = new Vector3(mesh.tangents[i].x, mesh.tangents[i].y, mesh.tangents[i].z);
            OtoTMatrix[1] = Vector3.Cross(mesh.normals[i], OtoTMatrix[0]) * mesh.tangents[i].w;
            OtoTMatrix[2] = mesh.normals[i];
            OtoTMatrixs.Add(OtoTMatrix);
        }

        //将meshNormals数组中的法线值一一与矩阵相乘,求得切线空间下的法线值
        for (int i = 0; i < smoothedNormals.Length; i++)
        {
            Vector3 tNormal;
            tNormal = Vector3.zero;
            tNormal.x = Vector3.Dot(((Vector3[])OtoTMatrixs[i])[0], smoothedNormals[i]);
            tNormal.y = Vector3.Dot(((Vector3[])OtoTMatrixs[i])[1], smoothedNormals[i]);
            tNormal.z = Vector3.Dot(((Vector3[])OtoTMatrixs[i])[2], smoothedNormals[i]);
            smoothedNormals[i] = tNormal;
        }

        //新建一个颜色数组把光滑处理后的法线值存入其中
        Color[] meshColors = new Color[mesh.colors.Length];
        for (int i = 0; i < meshColors.Length; i++)
        {
            meshColors[i].r = smoothedNormals[i].x * 0.5f + 0.5f;
            meshColors[i].g = smoothedNormals[i].y * 0.5f + 0.5f;
            meshColors[i].b = smoothedNormals[i].z * 0.5f + 0.5f;
            meshColors[i].a = mesh.colors[i].a;
        }

        //新建一个mesh,将之前mesh的所有信息copy过去
        // 如果模型还有blendshape 可以改成 Instansiate法
        Mesh newMesh = new Mesh();
        newMesh.vertices = mesh.vertices;
        newMesh.triangles = mesh.triangles;
        newMesh.normals = mesh.normals;
        newMesh.tangents = mesh.tangents;
        newMesh.uv = mesh.uv;
        newMesh.uv2 = mesh.uv2;
        newMesh.uv3 = mesh.uv3;
        newMesh.uv4 = mesh.uv4;
        newMesh.uv5 = mesh.uv5;
        newMesh.uv6 = mesh.uv6;
        newMesh.uv7 = mesh.uv7;
        newMesh.uv8 = mesh.uv8;
        //将新模型的颜色赋值为计算好的颜色
        newMesh.colors = meshColors;
        //newMesh.colors32 = new Color32(meshColors);
        newMesh.bounds = mesh.bounds;
        newMesh.indexFormat = mesh.indexFormat;
        newMesh.bindposes = mesh.bindposes;
        newMesh.boneWeights = mesh.boneWeights;
        //将新mesh保存为.asset文件,路径可以是"Assets/Character/Shader/VertexColorTest/TestMesh2.asset"                          
        AssetDatabase.CreateAsset(newMesh, NewMeshPath);
        AssetDatabase.SaveAssets();
        Debug.Log("Done");
    }
}



shader

 v2f vert(a2v v)
{
    v2f o;

    // 从顶点颜色中读取法线信息,并将其值范围从0~1还原为-1~1
    float3 vertNormal = v.vertexColor.rgb * 2 - 1;
    // 使用法线与切线叉乘计算副切线用于构建切线→模型空间转换矩阵
    float3 binormal = cross(v.normal, v.tangent) * v.tangent.w;
    // 构建切线 -> 模型空间转换矩阵
    float3x3 TtoO = float3x3(v.tangent.xyz,
                             binormal.xyz,
                             v.normal.xyz);
    TtoO = transpose(TtoO);
    // 将法线转换到模型空间下
    vertNormal = mul(TtoO, vertNormal);
    // 模型坐标 + 法线 * 自定义粗细值 * 顶点颜色A通道 = 轮廓线模型					
    o.vertex = UnityObjectToClipPos(v.vertex + vertNormal *_OutlineWidth);

    return o;
}

优化

上一个计算法线的算法事件复杂度是 O ( n 2 ) O(n^2) O(n2)
下面我们来优化一下,其实就是空间换时间嘛😀

// 空间换时间 (存下每个法线的相同的顶点索引)  其实就是真正的共享顶点 立方体的话因该是24 / 3=8个共享顶点。
// 这里用到了hash表 (可以去领扣刷刷hash表相关的题,游戏还是会用到的)
Dictionary<Vector3, List<int>> vertexDic = new Dictionary<Vector3, List<int>>();
for (int i = 0; i < mesh.vertices.Length; i++)
{
    if (!vertexDic.ContainsKey(mesh.vertices[i]))
    {
        List<int> vertexIndexs = new List<int>();
        vertexIndexs.Add(i);
        vertexDic.Add(mesh.vertices[i], vertexIndexs);
    }
    else
    {
        vertexDic[mesh.vertices[i]].Add(i);
    }
}
// 平均化每个顶点
foreach (var item in vertexDic)
{
    Vector3 smoothedNormal = new Vector3(0, 0, 0);
    foreach (var index in item.Value)
    {
        smoothedNormal += mesh.normals[index];
    }
    // 记得归一化
    smoothedNormal.Normalize();
    foreach (var index in item.Value)
    {
        smoothedNormals[index] = smoothedNormal;
    }
}

这样事件复杂度就从O(n^2) 降到了 O(n) 降的还是蛮高的

如果加入blendshape文件过大的问题

可以package manage搜fbx导出工具 导出二进制FBX,这样就小了

平滑法线

public static List<Vector3> SmoothNormals(Mesh mesh)
{
    // Group vertices by location
    var groups = mesh.vertices
        .Select((vertex, index) => new KeyValuePair<Vector3, int>(vertex, index))
        .GroupBy(pair => pair.Key);

    // Copy normals to a new list
    var smoothNormals = new List<Vector3>(mesh.normals);

    // Average normals for grouped vertices
    foreach (var group in groups)
    {
        // Skip single vertices
        // 不注掉的话 角色平滑后的法线再平滑会出问题 神奇的问题
        // 有大佬知道为啥请告诉我??? 感激不尽 mail: wssgg13@qq.com
        // if (group.Count() == 1) {
        //     continue;
        // }

        // Calculate the average normal
        var smoothNormal = Vector3.zero;

        foreach (var pair in group)
        {
            smoothNormal += mesh.normals[pair.Value];
        }

        smoothNormal.Normalize();

        // Assign smooth normal to each vertex
        foreach (var pair in group)
        {
            var normals = mesh.normals;
            var tangents = mesh.tangents;

            var binormal = (Vector3.Cross(normals[pair.Value], tangents[pair.Value]) * tangents[pair.Value].w)
                .normalized;

            var tbn = new Matrix4x4(
                tangents[pair.Value],
                binormal,
                normals[pair.Value],
                Vector4.zero);
            tbn = tbn.transpose;

            smoothNormals[pair.Value] = tbn.MultiplyVector(smoothNormal).normalized;
        }
    }

    return smoothNormals;
}
  • 2
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值