栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 游戏开发 > 其他

Unity 平滑法线小工具V0.1

其他 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

Unity 平滑法线小工具V0.1

学习目标: Unity 平滑法线小工具V1
步骤:
  1. 先创建一个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();
        var skinnedMeshRenderer = selectedObject.GetComponent();
        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(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();
        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();
        var skinnedMeshRenderer = selectedObject.GetComponent();
        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(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();
        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把法线重新算回来,这里其实有坑,自己还没完全搞懂。
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/901645.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号