Unity 平滑法线小工具V0.1

学习目标:

Unity 平滑法线小工具V1

步骤:

  1. 先创建一个Editor的文件夹
    文件夹放哪都行,文件名字必须为Editor

  2. 创建一个C#文件
    在这里插入图片描述

  3. 继承EditorWindow类
    在这里插入图片描述

  4. OnGUI方法

private void OnGUI()
    {
        // Get Object
        Transform selectedObject = Selection.activeGameObject.transform;
        
        // Check is null
        if (selectedObject == null)
        {
            EditorGUILayout.LabelField("Please Select a gameObject that you want to smooth normal!");    
            return;
        }
        
        // Get Mesh
        var meshFilter          = selectedObject.GetComponent<MeshFilter>();
        var skinnedMeshRenderer = selectedObject.GetComponent<SkinnedMeshRenderer>();
        Mesh mesh = null;
        if (meshFilter != null)
        {
            mesh = meshFilter.sharedMesh;
        }
        else if(skinnedMeshRenderer != null)
        {
            mesh = skinnedMeshRenderer.sharedMesh;
        }
        else
        {
            EditorGUILayout.LabelField("Selected object without mesh");
            return;
        }

        // Draw GUI
        EditorGUILayout.BeginVertical();
        EditorGUILayout.LabelField("You are selecting: " + selectedObject.name);
        
        EditorGUILayout.BeginHorizontal();
        EditorGUILayout.LabelField("Is normal data is in TangentSpace?");
        isTangentSpace = EditorGUILayout.Toggle(isTangentSpace);
        EditorGUILayout.EndHorizontal();
        
        // Set data to channel
        if (GUILayout.Button("Generate smooth normals into color  channel"))
        {
            SetObject2Channel(ref mesh, Channel.color);
        }
        if (GUILayout.Button("Generate smooth normals into normal  channel"))
        {
            SetObject2Channel(ref mesh, Channel.normal);
        }
        if (GUILayout.Button("Generate smooth normals into tangent  channel"))
        {
            SetObject2Channel(ref mesh, Channel.tangent);
        }
        
        EditorGUILayout.EndVertical();
    }
  1. 将功能显示在菜单栏上
[MenuItem("CustomTools/Smooth Normal")]
private static void OpenWindows()
{
    GetWindow<SmoothNormal>(false, "smooth normal", true).Show();
}
  1. 平滑算法(平滑法线在网上有很多算法,这里简单写一个)
private static Vector3[] GenerateSmoothNormals(Mesh srcMesh)
    {
        Vector3[] verticies = srcMesh.vertices;
        Vector3[] normals = srcMesh.normals;
        Vector3[] smoothNormals = normals;

        var averageNormalsDict = new Dictionary<Vector3, Vector3>();
        for (int i = 0; i < verticies.Length; i++)
        {
            if (!averageNormalsDict.ContainsKey(verticies[i]))
            {
                averageNormalsDict.Add(verticies[i], normals[i]);
            }
            else
            {
                averageNormalsDict[verticies[i]] = (averageNormalsDict[verticies[i]] + normals[i]).normalized;
            }
        }

        for (int i = 0; i < smoothNormals.Length; i++)
        {
            smoothNormals[i] = averageNormalsDict[verticies[i]];
        }

        if (isTangentSpace)
        {
            return GetTangentSpaceNormal(smoothNormals, srcMesh);    
        }
        return smoothNormals;
    }

7.切线空间下的法线数据
(如果模型是有骨骼的就用切线空间的法线,然后在shader里用TBN矩阵转回来,不绑骨骼可用可不用)

private static Vector3[] GetTangentSpaceNormal(Vector3[] smoothedNormals, Mesh srcMesh)
{
    Vector3[] normals = srcMesh.normals;
    Vector4[] tangents = srcMesh.tangents;

    Vector3[] smoothedNormals_TS = new Vector3[smoothedNormals.Length];

    for (int i = 0; i < smoothedNormals_TS.Length; i++)
    {
        Vector3 normal  = normals[i];
        Vector4 tangent = tangents[i];

        Vector3 tangentV3 = new Vector3(tangent.x, tangent.y, tangent.z);

        var bitangent = Vector3.Cross(normal, tangentV3) * tangent.w;
        bitangent        = bitangent.normalized;

        var TBN = new Matrix4x4(tangentV3, bitangent, normal, Vector4.zero);
        TBN = TBN.transpose;

        var smoothedNormal_TS = TBN.MultiplyVector(smoothedNormals[i]).normalized;
            
        smoothedNormals_TS[i] = smoothedNormal_TS;
    }

    return smoothedNormals_TS;
}
  1. Vector3转Vector4和Color的方法(Color可以存负数,也可以映射到0~1,shader里再转回来)
private static Vector4[] ConvertV3ToV4Array(Vector3[] v_Array)
{
    Vector4[] v_Array_AfterConvert = new Vector4[v_Array.Length];

    for (int i = 0; i < v_Array_AfterConvert.Length; i++)
    {
        Vector4 v4 = new Vector4();
        Vector3 v3 = v_Array[i];
        v4.x = v3.x;
        v4.y = v3.y;
        v4.z = v3.z;
        v4.w = 1.0f;

        v_Array_AfterConvert[i] = v4;
    }

    return v_Array_AfterConvert;
}
    
private static Color[] ConvertV3ToColor(Vector3[] v_Array)
{
    Color[] v_Array_AfterConvert = new Color[v_Array.Length];

    for (int i = 0; i < v_Array_AfterConvert.Length; i++)
    {
        Color v4 = new Color();
        Vector3 v3 = v_Array[i];
        v4.r = v3.x;
        v4.g = v3.y;
        v4.b = v3.z;
        v4.a = 1.0f;

        v_Array_AfterConvert[i] = v4;
    }

    return v_Array_AfterConvert;
}
  1. 写入通道
private static bool SetObject2Channel(ref Mesh selectedMesh, Channel channel)
{
    switch (channel)
    {
        case Channel.color:
            selectedMesh.colors   = ConvertV3ToColor(GenerateSmoothNormals(selectedMesh));
            break;
        case Channel.normal:
            selectedMesh.normals  = GenerateSmoothNormals(selectedMesh);
            break;
        case Channel.tangent:
            selectedMesh.tangents = ConvertV3ToV4Array(GenerateSmoothNormals(selectedMesh));
            break;
        
        default:
            return false;
    }

    return true;
}
  1. 完成
    在这里插入图片描述

  2. 完整代码

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

public class SmoothNormal : EditorWindow
{
    // Statue Vals
    private static bool isTangentSpace = true;
    
    private void OnGUI()
    {
        // Get Object
        Transform selectedObject = Selection.activeGameObject.transform;
        
        // Check is null
        if (selectedObject == null)
        {
            EditorGUILayout.LabelField("Please Select a gameObject that you want to smooth normal!");    
            return;
        }
        
        // Get Mesh
        var meshFilter          = selectedObject.GetComponent<MeshFilter>();
        var skinnedMeshRenderer = selectedObject.GetComponent<SkinnedMeshRenderer>();
        Mesh mesh = null;
        if (meshFilter != null)
        {
            mesh = meshFilter.sharedMesh;
        }
        else if(skinnedMeshRenderer != null)
        {
            mesh = skinnedMeshRenderer.sharedMesh;
        }
        else
        {
            EditorGUILayout.LabelField("Selected object without mesh");
            return;
        }

        // Draw GUI
        EditorGUILayout.BeginVertical();
        EditorGUILayout.LabelField("You are selecting: " + selectedObject.name);
        
        EditorGUILayout.BeginHorizontal();
        EditorGUILayout.LabelField("Is normal data is in TangentSpace?");
        isTangentSpace = EditorGUILayout.Toggle(isTangentSpace);
        
        EditorGUILayout.EndHorizontal();
        
        // Set data to channel
        if (GUILayout.Button("Generate smooth normals into color  channel"))
        {
            SetObject2Channel(ref mesh, Channel.color);
        }
        if (GUILayout.Button("Generate smooth normals into normal  channel"))
        {
            SetObject2Channel(ref mesh, Channel.normal);
        }
        if (GUILayout.Button("Generate smooth normals into tangent  channel"))
        {
            SetObject2Channel(ref mesh, Channel.tangent);
        }
        
        EditorGUILayout.EndVertical();
    }

    [MenuItem("CustomTools/Smooth Normal")]
    private static void OpenWindows()
    {
        GetWindow<SmoothNormal>(false, "smooth normal", true).Show();
    }

    private enum Channel
    {
        color,
        normal,
        tangent
    }
    
    private static Vector3[] GenerateSmoothNormals(Mesh srcMesh)
    {
        Vector3[] verticies = srcMesh.vertices;
        Vector3[] normals = srcMesh.normals;
        Vector3[] smoothNormals = normals;

        var averageNormalsDict = new Dictionary<Vector3, Vector3>();
        for (int i = 0; i < verticies.Length; i++)
        {
            if (!averageNormalsDict.ContainsKey(verticies[i]))
            {
                averageNormalsDict.Add(verticies[i], normals[i]);
            }
            else
            {
                averageNormalsDict[verticies[i]] = (averageNormalsDict[verticies[i]] + normals[i]).normalized;
            }
        }

        for (int i = 0; i < smoothNormals.Length; i++)
        {
            smoothNormals[i] = averageNormalsDict[verticies[i]];
        }

        if (isTangentSpace)
        {
            return GetTangentSpaceNormal(smoothNormals, srcMesh);    
        }
        return smoothNormals;
    }
    
    private static Vector3[] GetTangentSpaceNormal(Vector3[] smoothedNormals, Mesh srcMesh)
    {
        Vector3[] normals = srcMesh.normals;
        Vector4[] tangents = srcMesh.tangents;

        Vector3[] smoothedNormals_TS = new Vector3[smoothedNormals.Length];

        for (int i = 0; i < smoothedNormals_TS.Length; i++)
        {
            Vector3 normal  = normals[i];
            Vector4 tangent = tangents[i];

            Vector3 tangentV3 = new Vector3(tangent.x, tangent.y, tangent.z);

            var bitangent = Vector3.Cross(normal, tangentV3) * tangent.w;
            bitangent        = bitangent.normalized;

            var TBN = new Matrix4x4(tangentV3, bitangent, normal, Vector4.zero);
            TBN = TBN.transpose;

            var smoothedNormal_TS = TBN.MultiplyVector(smoothedNormals[i]).normalized;
            
            smoothedNormals_TS[i] = smoothedNormal_TS;
        }

        return smoothedNormals_TS;
    }
    
    private static Vector4[] ConvertV3ToV4Array(Vector3[] v_Array)
    {
        Vector4[] v_Array_AfterConvert = new Vector4[v_Array.Length];

        for (int i = 0; i < v_Array_AfterConvert.Length; i++)
        {
            Vector4 v4 = new Vector4();
            Vector3 v3 = v_Array[i];
            v4.x = v3.x;
            v4.y = v3.y;
            v4.z = v3.z;
            v4.w = 1.0f;

            v_Array_AfterConvert[i] = v4;
        }

        return v_Array_AfterConvert;
    }
    
    private static Color[] ConvertV3ToColor(Vector3[] v_Array)
    {
        Color[] v_Array_AfterConvert = new Color[v_Array.Length];

        for (int i = 0; i < v_Array_AfterConvert.Length; i++)
        {
            Color v4 = new Color();
            Vector3 v3 = v_Array[i];
            v4.r = v3.x;
            v4.g = v3.y;
            v4.b = v3.z;
            v4.a = 1.0f;

            v_Array_AfterConvert[i] = v4;
        }

        return v_Array_AfterConvert;
    }
    
    private static bool SetObject2Channel(ref Mesh selectedMesh, Channel channel)
    {
        switch (channel)
        {
            case Channel.color:
                selectedMesh.colors   = ConvertV3ToColor(GenerateSmoothNormals(selectedMesh));
                break;
            case Channel.normal:
                selectedMesh.normals  = GenerateSmoothNormals(selectedMesh);
                break;
            case Channel.tangent:
                selectedMesh.tangents = ConvertV3ToV4Array(GenerateSmoothNormals(selectedMesh));
                break;
            
            default:
                return false;
        }

        return true;
    }
}

  1. 小结:
    (1)Color可以存负数
    (2)skinmeshrender会改变切线数据,shader里使用TBN把法线重新算回来,这里其实有坑,自己还没完全搞懂。
  • 1
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值