Unity Editor 画地面贴图工具

这次我想实现一下一个画地面贴图的工具:
首先想到的是EditorWindow,于是我先写一个EditorWindowTexturePaintWindow,如下:

using UnityEditor;
using UnityEngine;

public class TexturePaintWindow : EditorWindow
{
    static TexturePaintWindow window;

    [MenuItem("Tools/Terrain Paint")]
    static void AddWindow()
    {
        Rect wr = new Rect(0, 0, 400, 600);
        window = (TexturePaintWindow)EditorWindow.GetWindowWithRect(typeof(TexturePaintWindow), wr, true, "Texture Calculate");
        window.Show();
    }

    Transform terrain;

    private void Update()
    {
        terrain = Selection.activeTransform;
        if (terrain == null) return;
        Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
        RaycastHit raycastHit;
        if(Input.GetMouseButton(0))
        {
            if (Physics.Raycast(ray, out raycastHit))
            {
                Debug.DrawLine(ray.origin, raycastHit.point, Color.red);
                Debug.Log(raycastHit.normal);
            }
        }
    }
}

但我发现我什么输出都没有得到,一番调试后我发现是因为Input类在EditorWindow里并不起作用,于是我又找到了Event,于是代码变成了这样,依旧是鼠标右键按下获取当前点击物体的法线:

private void Update()
{
    terrain = Selection.activeTransform;
    if (terrain == null) return;
    Ray ray = Camera.main.ScreenPointToRay(Input.mousePosition);
    Event _event = Event.current;
    RaycastHit raycastHit;
    if(_event.button == 0 && _event.rawType == EventType.MouseDown)
    {
        if (Physics.Raycast(ray, out raycastHit))
        {
            Debug.DrawLine(ray.origin, raycastHit.point, Color.red);
            Debug.Log(raycastHit.normal);
        }
    }
}

但我发现EventUpdate 里也不起作用,于是我把代码移到 OnGUI里,此时Event起作用了,但是在窗口失焦的情况下Event依旧不起作用,可是我又要点击窗口外的模型进行绘制,于是这个办法走向了死路。
在看了喵喵Myaunity在模型上绘画之后,我又被迫加入了一个Editor类,但是Editor又必须寄托于一个MonoBehaviour类才可以运行,于是最后我添加了两个类,TexturePaintTexturePaintObj,这下就可以把EditorWindow里面的代码移过来了:

using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(TexturePaintObj))]
public class TexturePaint : Editor
{
    GameObject gameObject;

    private void OnSceneGUI()
    {
        gameObject = Selection.activeGameObject;
        Event _event = Event.current;
        //解决拖拽问题
        HandleUtility.AddDefaultControl(0);
        if (_event.button == 0 && _event.rawType == EventType.MouseDown)
        {
        	//Camera.main.ScreenPointToRay依旧不对,于是换HandleUtility.GUIPointToWorldRay
            Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
            RaycastHit raycastHit;
            if (Physics.Raycast(ray, out raycastHit,Mathf.Infinity))
            {
                Debug.DrawLine(ray.origin, raycastHit.point, Color.red);
                Debug.Log(raycastHit.normal);
            }
        }
    }
}

这里还解决了两个小问题,一是鼠标左键按下时会变更成多选框,导致选中的物体失去选中,这里可以用 HandleUtility.AddDefaultControl(为默认控件添加 ID。如果没有其他控件可选,则选择此控件。) 解决,二是原来的Camera.main.ScreenPointToRay依旧不对,于是换成了HandleUtility.GUIPointToWorldRay(将 2D GUI 位置转换为世界空间射线。)
但是问题依旧很多接下来需要慢慢解决。

实现一个填色的功能

上面的脚本已经可以让我们在鼠标左键按下时获得命中的坐标点信息,接下来我先从简单的做起——先做一个根据笔刷大小去填色的简单功能。
首先给TexturePaintWindow类添加一个笔刷大小和强度(以后会用到)的滑条:

在这里插入图片描述

using UnityEditor;
using UnityEngine;

public class TexturePaintWindow : EditorWindow
{
    static TexturePaintWindow window;
    public static float BrushSize = 1.0f;
    public static float BrushStrength = 50f;

    [MenuItem("Tools/Terrain Paint")]
    static void AddWindow()
    {
        Rect wr = new Rect(0, 0, 400, 600);
        window = (TexturePaintWindow)EditorWindow.GetWindowWithRect(typeof(TexturePaintWindow), wr, true, "Terrain Paint");
        window.Show();
    }

    Transform terrain;
    GUIStyle gUIStyle = new GUIStyle();

    private void Update()
    {
        
    }

    private void OnGUI()
    {
        gUIStyle.fontSize = 16;
        gUIStyle.normal.textColor = new Color(1, 1, 1);

        GUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Brush Size:",gUIStyle);
            BrushSize=EditorGUILayout.Slider(BrushSize, 0.01f, 32f);
        GUILayout.EndHorizontal();
        GUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Brush Strength:", gUIStyle);
            BrushStrength = EditorGUILayout.Slider(BrushStrength, 0.0f, 100f);
        GUILayout.EndHorizontal();
    }
}

我感觉我可能描述不清楚思路,因为我自己就在这里面绕了好久,才理清楚,于是我画了一张图:
在这里插入图片描述
首先需要明确的一点是因为是在Mask上作画,所以一切坐标的运算都以像素做单位(数组),并且这里主要关注Height=Width的情况。下面我简单描述下主要步骤:

  1. 计算出笔刷大小占地形大小的比例,进而计算出笔刷在Mask里所占的像素点数量。
  2. 计算出鼠标击中的像素点,并通过第一步结果计算出X、YHeight、Width
  3. 通过第二步所得对MaskTex的像素进行替换。

代码如下:

using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(TexturePaintObj))]
public class TexturePaint : Editor
{
    GameObject gameObject;
    Texture2D MaskTex;

    private void Awake()
    {
        MaskTex = new Texture2D(512, 512);
    }

    private void OnSceneGUI()
    {
        gameObject = Selection.activeGameObject;
		if (gameObject == null) return;
        //获取世界坐标系下的Terrain大小
        Vector3 terrainSize=gameObject.GetComponent<Renderer>().bounds.size;
        Material material = gameObject.GetComponent<MeshRenderer>().sharedMaterial;

        Event _event = Event.current;
        HandleUtility.AddDefaultControl(0);

        Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
        RaycastHit raycastHit;
		
		//1.计算出笔刷大小占地形大小的比例,进而计算出笔刷在Mask里所占的像素点数量。
		//(brushSize /terrainSize.x) * MaskTex.width 这样看好理解些 
        float brushSize = TexturePaintWindow.BrushSize;
        int brushSizeInPourcent=(int)Mathf.Round(brushSize * MaskTex.width/terrainSize.x );
       
        if (Physics.Raycast(ray, out raycastHit,Mathf.Infinity))
        {
            Handles.color = new Color(1, 0, 0);
           
            Handles.DrawWireDisc(raycastHit.point, raycastHit.normal, brushSize*0.5f);
            Handles.DrawLine(raycastHit.point, raycastHit.point + raycastHit.normal.normalized * 0.5f);
            
            //修复旋转视角也会进入循环的Bug
            if (_event.button == 0 && (_event.rawType == EventType.MouseDown || _event.rawType == EventType.MouseDrag)&&!_event.alt)
            {
                
                //2.计算出鼠标击中的像素点
                Vector2 pixelUV = raycastHit.textureCoord;
                int PuX = Mathf.FloorToInt(pixelUV.x * MaskTex.width);
                int PuY = Mathf.FloorToInt(pixelUV.y * MaskTex.height);
                //2.计算X,Y
                int x = Mathf.Clamp(PuX - brushSizeInPourcent / 2, 0, MaskTex.width - 1);
                int y = Mathf.Clamp(PuY - brushSizeInPourcent / 2, 0, MaskTex.height - 1);
                //2.计算Width,Height(因为是正方形,其实可以省略一个变量)
                int width = Mathf.Clamp((PuX + brushSizeInPourcent / 2), 0, MaskTex.width) - x;
                int height = Mathf.Clamp((PuY + brushSizeInPourcent / 2), 0, MaskTex.height) - y;
				
				//随便给个随机颜色去替换(笔刷)
                Color[] randomColor = new Color[width * height];
                for (int i = 0; i < width; i++) 
                {
                    for (int j = 0; j < height; j++)
                    {
                        randomColor[j * width + i] = Random.ColorHSV();
                    }
                }
				
				//3.替换颜色
                MaskTex.SetPixels(x, y, width, height,randomColor);
                MaskTex.Apply();
                material.SetTexture("_MainTex", MaskTex);
            }
        }
    }
}

在这里插入图片描述

实现主要功能

这里要实现的东西还是挺多的,慢慢来,首先是读取笔刷和材质贴图贴图:
在这里插入图片描述
这里需要先写一个Shader给地形Plane,这里随便写了一下:

Shader "TerrianPaint/TP 4 Texure"
{
    Properties
    {
        _Tpaint1 ("Texture 1", 2D) = "white" {}
        _Tpaint2 ("Texture 2", 2D) = "white" {}
        _Tpaint3 ("Texture 3", 2D) = "white" {}
        _Tpaint4 ("Texture 4", 2D) = "white" {}
        _Tmask1 ("Paint Mask", 2D) = "black" {}
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 100

        Pass
        {
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag
            
            #include "UnityCG.cginc"
            
            struct appdata
            {
                float4 vertex : POSITION;
                float2 uv : TEXCOORD0;
            };
            
            struct v2f
            {
                float4 uv : TEXCOORD0;
                float4 uv2 : TEXCOORD1;
                float2 maskUV:TEXCOORD2;
                float4 vertex : SV_POSITION;
            };
            
            sampler2D _Tpaint1,_Tpaint2,_Tpaint3,_Tpaint4;
            float4 _Tpaint1_ST,_Tpaint2_ST,_Tpaint3_ST,_Tpaint4_ST;
            sampler2D _Tmask1;
            
            v2f vert(appdata v)
            {
                v2f o;
                o.vertex = UnityObjectToClipPos(v.vertex);
                o.uv.xy = TRANSFORM_TEX(v.uv, _Tpaint1);
                o.uv.zw = TRANSFORM_TEX(v.uv, _Tpaint2);
                o.uv2.xy = TRANSFORM_TEX(v.uv, _Tpaint3);
                o.uv2.zw = TRANSFORM_TEX(v.uv, _Tpaint4);

                o.maskUV=v.uv;
                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target
            {
                fixed3 col1 = tex2D(_Tpaint1, i.uv.xy).rgb;
                fixed3 col2 = tex2D(_Tpaint2, i.uv.zw).rgb;
                fixed3 col3 = tex2D(_Tpaint3, i.uv2.xy).rgb;
                fixed3 col4 = tex2D(_Tpaint4, i.uv2.zw).rgb;

                fixed4 mask=tex2D(_Tmask1,i.maskUV);
                fixed3 final=col1*mask.r+col2*mask.g+col3*mask.b+col4*mask.a;
                return fixed4(final,1);
            }
            ENDCG
        }
    }
}

获取材质面板的贴图:

void GetPaintTexs(GameObject select)
{
    Material material = select.GetComponent<MeshRenderer>().sharedMaterial;
    if (material == null) return;
    paintTexs = new Texture[4];
    for (int i=0;i<paintTexs.Length;i++)
    {
        string texName = "_Tpaint" + (i + 1);
        paintTexs[i] = AssetPreview.GetAssetPreview(material.GetTexture(texName)) as Texture;
    }
}

获取笔刷贴图,这里直接用了T4M的图,如果用别的图一定记得把Read/Write Enable打开:

private void Awake()
{
    if (Directory.Exists(brushPath))
    {
        DirectoryInfo directoryInfo = new DirectoryInfo(brushPath);
        FileInfo[] every = directoryInfo.GetFiles("*.png", SearchOption.AllDirectories);
        brushTexs = new Texture[every.Length];
        for (int i = 0; i < every.Length; i++) 
        {
            string path = every[i].ToString().Replace(@"\", "/");
            path=path.Replace(Application.dataPath, "Assets");
            brushTexs[i]=(AssetDatabase.LoadAssetAtPath<Texture>(path));
        }
    } 
}

最后用GUILayout.SelectionGrid做列表,再根据选中的贴图选择要画的通道,最后TexturePaintWindow的代码就变成了这样:

using System.Collections;
using System.IO;
using UnityEditor;
using UnityEngine;

public class TexturePaintWindow : EditorWindow
{
    static TexturePaintWindow window;
    public static float BrushSize = 1.0f;
    public static float BrushStrength = 50f;
    public static Texture2D BrushTex;
    public static Color TargetColor = new Color(1, 0, 0, 0);

    int brushIndex = 0;
    int paintIndex = 0;
    Texture[] brushTexs;
    Texture[] paintTexs;

    string brushPath = "Assets/TerrainPaint/Editor/Brushes";

    [MenuItem("Tools/Terrain Paint")]
    static void AddWindow()
    {
        Rect wr = new Rect(0, 0, 400, 600);
        window = (TexturePaintWindow)EditorWindow.GetWindowWithRect(typeof(TexturePaintWindow), wr, true, "Terrain Paint");
        window.Show();
    }

    Transform terrain;
    GUIStyle gUIStyle = new GUIStyle();

    private void Awake()
    {
        if (Directory.Exists(brushPath))
        {
            DirectoryInfo directoryInfo = new DirectoryInfo(brushPath);
            FileInfo[] every = directoryInfo.GetFiles("*.png", SearchOption.AllDirectories);
            brushTexs = new Texture[every.Length];
            for (int i = 0; i < every.Length; i++) 
            {
                string path = every[i].ToString().Replace(@"\", "/");
                path=path.Replace(Application.dataPath, "Assets");
                brushTexs[i]=(AssetDatabase.LoadAssetAtPath<Texture>(path));
            }
        } 
    }

    void GetPaintTexs(GameObject select)
    {
        Material material = select.GetComponent<MeshRenderer>().sharedMaterial;
        if (material == null) return;
        paintTexs = new Texture[4];
        for (int i=0;i<paintTexs.Length;i++)
        {
            string texName = "_Tpaint" + (i + 1);
            paintTexs[i] = AssetPreview.GetAssetPreview(material.GetTexture(texName)) as Texture;
        }
    }

    void SetTargetColor(int num)
    {
        switch(num)
        {
            case 0:
                TargetColor = new Color(1, 0, 0, 0);
                break;
            case 1:
                TargetColor = new Color(0, 1, 0, 0);
                break;
            case 2:
                TargetColor = new Color(0, 0, 1, 0);
                break;
            case 3:
                TargetColor = new Color(0, 0, 0, 1);
                break;
        }    
    }
    private void OnGUI()
    {
        GameObject select = Selection.activeGameObject;
        if (select == null || select.GetComponent<TexturePaintObj>() == null) return;
        GetPaintTexs(select);

        if (paintTexs.Length>0)
        {
            paintIndex= GUILayout.SelectionGrid(paintIndex, paintTexs, 4, "gridlist", GUILayout.Width(390), GUILayout.Height(60));
            SetTargetColor(paintIndex);
        }

        GUILayout.BeginHorizontal();
        if (brushTexs.Length>0)
        {
            brushIndex = GUILayout.SelectionGrid(brushIndex, brushTexs, 8, "gridlist", GUILayout.Width(390), GUILayout.Height(100));
            BrushTex = brushTexs[brushIndex] as Texture2D;
        }

        gUIStyle.fontSize = 16;
        gUIStyle.normal.textColor = new Color(1, 1, 1);

        GUILayout.EndHorizontal();
        GUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Brush Size:",gUIStyle);
            BrushSize=EditorGUILayout.Slider(BrushSize, 0.01f, 32f);
        GUILayout.EndHorizontal();
        GUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Brush Strength:", gUIStyle);
            BrushStrength = EditorGUILayout.Slider(BrushStrength, 0.0f, 100f);
        GUILayout.EndHorizontal();
    }
}

TexturePaint只需简单修改一下就好:

using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(TexturePaintObj))]
public class TexturePaint : Editor
{
    GameObject gameObject;
    Texture2D MaskTex;

    private void Awake()
    {
    	//让MaskTex初始是(1,0,0,0)
        int width = 512;
        int height = 512;
        MaskTex = new Texture2D(width, height);
        Color[] colors = new Color[width * height];
        for(int i=0;i<width*height;i++)
        {
            colors[i] = new Color(1, 0, 0, 0);
        }
        MaskTex.SetPixels(0, 0, width, height, colors);
    }

    private void OnSceneGUI()
    {
        gameObject = Selection.activeGameObject;
        if (gameObject == null) return;

        Vector3 terrainSize=gameObject.GetComponent<Renderer>().bounds.size;
        Material material = gameObject.GetComponent<MeshRenderer>().sharedMaterial;
        if (material == null) return;
   
        Event _event = Event.current;
        HandleUtility.AddDefaultControl(0);

        Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
        RaycastHit raycastHit;
		
		//获取笔刷贴图
        Texture2D brushTex = TexturePaintWindow.BrushTex;
        if (brushTex == null) return;
        float brushSize = TexturePaintWindow.BrushSize;

        int brushSizeInPourcent=(int)Mathf.Round(brushSize * MaskTex.width/terrainSize.x );
        if (Physics.Raycast(ray, out raycastHit,Mathf.Infinity))
        {
            Handles.color = new Color(1, 0, 0);
            Handles.DrawWireDisc(raycastHit.point, raycastHit.normal, brushSize*0.5f);
            Handles.DrawLine(raycastHit.point, raycastHit.point + raycastHit.normal.normalized * 0.5f);
            if (_event.button == 0 && (_event.rawType == EventType.MouseDown || _event.rawType == EventType.MouseDrag)&&!_event.alt)
            {
                
                Vector2 pixelUV = raycastHit.textureCoord;
                int PuX = Mathf.FloorToInt(pixelUV.x * MaskTex.width);
                int PuY = Mathf.FloorToInt(pixelUV.y * MaskTex.height);
                int x = Mathf.Clamp(PuX - brushSizeInPourcent / 2, 0, MaskTex.width - 1);
                int y = Mathf.Clamp(PuY - brushSizeInPourcent / 2, 0, MaskTex.height - 1);
                int width = Mathf.Clamp((PuX + brushSizeInPourcent / 2), 0, MaskTex.width) - x;
                int height = Mathf.Clamp((PuY + brushSizeInPourcent / 2), 0, MaskTex.height) - y;

                Color[] targetColor = MaskTex.GetPixels(x, y, width, height);
                for (int i = 0; i < width; i++) 
                {
                    for (int j = 0; j < height; j++)
                    {
                        float brushX = i * 1.0f / (width - 1);
                        float brushY = j * 1.0f / (height - 1);
                        float alpha = brushTex.GetPixelBilinear(brushX, brushY).a * TexturePaintWindow.BrushStrength / 100.0f;
                        int index = j * width + i;
                        targetColor[index] = Color.Lerp(targetColor[index],TexturePaintWindow.TargetColor,alpha);
                    }
                }
                
			    MaskTex.SetPixels(x, y, width, height,targetColor);
                MaskTex.Apply();
                material.SetTexture("_Tmask1", MaskTex);
            }
        }
    }
}

这里使用了GetPixelBilinear,参数的是(0,1)之间的UV值,再根据笔刷贴图的Alpha以及BrushStrength对选择的通道进行混合就可以了,原理还是很简单的,下面是效果展示:

在模型上作画

在这里插入图片描述

我这样写还有个Bug就是,边缘无法填色,笔刷越大,没法填的地方越大,因为把X,Y限制了区域,而笔刷的获取却没进行处理:
在这里插入图片描述
我想了很久发现还是unity在模型上绘画的方法好,顺便加上撤销。

				float[] brushAlpha = new float[brushSizeInPourcent * brushSizeInPourcent];
                for (int i = 0; i <brushSizeInPourcent; i++)
                {
                    for (int j = 0; j < brushSizeInPourcent; j++)
                    {
                        int index = j * brushSizeInPourcent + i;
                        float brushX = i * 1.0f / brushSizeInPourcent;
                        float brushY = j * 1.0f / brushSizeInPourcent;
                        brushAlpha[index]= brushTex.GetPixelBilinear(brushX, brushY).a * TexturePaintWindow.BrushStrength / 100.0f;
                    }
                }

                Color[] targetColor = MaskTex.GetPixels(x, y, width, height);
                for (int i = 0; i < width; i++) 
                {
                    for (int j = 0; j < height; j++)
                    {
                        int brushX = Mathf.Clamp((x + i) - ucX, 0, brushSizeInPourcent);
                        int brushY = Mathf.Clamp((y + j) - ucY, 0, brushSizeInPourcent);
                        int index = j * width + i;
                        float alpha = brushAlpha[brushY * brushSizeInPourcent + brushX];
                        targetColor[index] = Color.Lerp(targetColor[index],TexturePaintWindow.TargetColor,alpha);
                    }
                }
                Undo.RegisterCompleteObjectUndo(MaskTex, "meshPaint");

将MaskTex存储到本地

要将MaskTex保存到本地其实也不难,难的找到是保存的时机和频率,这边把TexturePaint代码改了一下,添加了保存的功能:

using System.IO;
using UnityEditor;
using UnityEngine;

[CustomEditor(typeof(TexturePaintObj))]
public class TexturePaint : Editor
{
    GameObject gameObject;
    Texture2D MaskTex;
    bool isDraw = false;

    private void OnSceneGUI()
    {
        gameObject = Selection.activeGameObject;
        if (gameObject == null) return;

        Vector3 terrainSize=gameObject.GetComponent<Renderer>().bounds.size;
        Material material = gameObject.GetComponent<MeshRenderer>().sharedMaterial;
        if (material == null) return;
   
        Event _event = Event.current;
        HandleUtility.AddDefaultControl(0);

        Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
        RaycastHit raycastHit;

        Texture2D brushTex = TexturePaintWindow.BrushTex;
        if (brushTex == null) return;
        float brushSize = TexturePaintWindow.BrushSize;
         
        if (TexturePaintWindow.MaskTex == null) return;
        MaskTex = TexturePaintWindow.MaskTex;
        int brushSizeInPourcent=(int)Mathf.Round(brushSize * MaskTex.width/terrainSize.x );
        if (Physics.Raycast(ray, out raycastHit,Mathf.Infinity))
        {
            Handles.color = new Color(1, 0, 0);
            Handles.DrawWireDisc(raycastHit.point, raycastHit.normal, brushSize*0.5f);
            Handles.DrawLine(raycastHit.point, raycastHit.point + raycastHit.normal.normalized * 0.5f);
            if (_event.button == 0 && (_event.rawType == EventType.MouseDown || _event.rawType == EventType.MouseDrag)&&!_event.alt)
            {
   
                Vector2 pixelUV = raycastHit.textureCoord;
                int PuX = Mathf.FloorToInt(pixelUV.x * MaskTex.width);
                int PuY = Mathf.FloorToInt(pixelUV.y * MaskTex.height);
                int ucX = PuX - brushSizeInPourcent / 2;
                int ucY = PuY - brushSizeInPourcent / 2;
                //Debug.Log(ucX+","+ucY);
                int x = Mathf.Clamp(ucX, 0, MaskTex.width - 1);
                int y = Mathf.Clamp(ucY, 0, MaskTex.height - 1);
                int width = Mathf.Clamp((PuX + brushSizeInPourcent / 2), 0, MaskTex.width) - x;
                int height = Mathf.Clamp((PuY + brushSizeInPourcent / 2), 0, MaskTex.height) - y;

                float[] brushAlpha = new float[brushSizeInPourcent * brushSizeInPourcent];
                for (int i = 0; i <brushSizeInPourcent; i++)
                {
                    for (int j = 0; j < brushSizeInPourcent; j++)
                    {
                        int index = j * brushSizeInPourcent + i;
                        float brushX = i * 1.0f / brushSizeInPourcent;
                        float brushY = j * 1.0f / brushSizeInPourcent;
                        brushAlpha[index]= brushTex.GetPixelBilinear(brushX, brushY).a * TexturePaintWindow.BrushStrength / 100.0f;
                    }
                }

                Color[] targetColor = MaskTex.GetPixels(x, y, width, height);
                for (int i = 0; i < width; i++) 
                {
                    for (int j = 0; j < height; j++)
                    {
                        int brushX = Mathf.Clamp((x + i) - ucX, 0, brushSizeInPourcent);
                        int brushY = Mathf.Clamp((y + j) - ucY, 0, brushSizeInPourcent);
                        int index = j * width + i;
                        float alpha = brushAlpha[brushY * brushSizeInPourcent + brushX];
                        targetColor[index] = Color.Lerp(targetColor[index],TexturePaintWindow.TargetColor,alpha);
                    }
                }
                Undo.RegisterCompleteObjectUndo(MaskTex, "meshPaint");

                MaskTex.SetPixels(x, y, width, height,targetColor);
                MaskTex.Apply();
                isDraw = true;
            }
            else if (isDraw&&_event.rawType ==EventType.MouseUp&&_event.alt==false&&_event.button==0)
            {
                isDraw = false;
                TexturePaintWindow.SaveMaskTex(gameObject);
            }
        }
    }
}

TexturePaintWindow添加了三个函数GetMaskTex: 获取MaskTexCreateMaskTex: 创建MaskTexSaveMaskTex: 保存MaskTex

using System.IO;
using UnityEditor;
using UnityEngine;

public class TexturePaintWindow : EditorWindow
{
    static TexturePaintWindow window;
    public static float BrushSize = 1.0f;
    public static float BrushStrength = 50f;
    public static Texture2D BrushTex;
    public static Texture2D MaskTex;
    public static Color TargetColor = new Color(1, 0, 0, 0);
    static Material material;

    int brushIndex = 0;
    int paintIndex = 0;
    Texture[] brushTexs;
    Texture[] paintTexs;

    string brushPath = "Assets/TerrainPaint/Editor/Brushes";
    static string MaskTexPath = "Assets/Textures/TerrainMask/";

    [MenuItem("Tools/Terrain Paint")]
    static void AddWindow()
    {
        Rect wr = new Rect(0, 0, 400, 600);
        window = (TexturePaintWindow)EditorWindow.GetWindowWithRect(typeof(TexturePaintWindow), wr, true, "Terrain Paint");
        window.Show();
    }

    Transform terrain;
    GUIStyle gUIStyle = new GUIStyle();

    private void Awake()
    {
        if (Directory.Exists(brushPath))
        {
            DirectoryInfo directoryInfo = new DirectoryInfo(brushPath);
            FileInfo[] every = directoryInfo.GetFiles("*.png", SearchOption.AllDirectories);
            brushTexs = new Texture[every.Length];
            for (int i = 0; i < every.Length; i++) 
            {
                string path = every[i].ToString().Replace(@"\", "/");
                path=path.Replace(Application.dataPath, "Assets");
                brushTexs[i]=(AssetDatabase.LoadAssetAtPath<Texture>(path));
            }
        } 
    }

    void GetPaintTexs(GameObject select)
    {
        material = select.GetComponent<MeshRenderer>().sharedMaterial;
        if (material == null) return;
        paintTexs = new Texture[4];
        for (int i=0;i<paintTexs.Length;i++)
        {
            string texName = "_Tpaint" + (i + 1);
            paintTexs[i] = AssetPreview.GetAssetPreview(material.GetTexture(texName)) as Texture;
        }
    }

    void GetMaskTex()
    {
        Texture2D mask = material.GetTexture("_Tmask1") as Texture2D;
        MaskTex = mask;
        if(MaskTex==null)
        {
            CreateMaskTex(material);
        }
    }

    void CreateMaskTex(Material material)
    {
        int width = 512;
        int height = 512;
        MaskTex = new Texture2D(width, height);
        Color[] colors = new Color[width * height];
        for (int i = 0; i < width * height; i++)
        {
            colors[i] = new Color(1, 0, 0, 0);
        }
        MaskTex.SetPixels(0, 0, width, height, colors);
        MaskTex.Apply();
        //material.SetTexture("_Tmask1", MaskTex);
    }

    public static void SaveMaskTex(GameObject gameObject)
    {
        string path = MaskTexPath + gameObject.name + ".png";
        byte[] bytes = MaskTex.EncodeToPNG();
        File.WriteAllBytes(path, bytes);

        AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
        TextureImporter textureImporter = AssetImporter.GetAtPath(path) as TextureImporter;
        textureImporter.isReadable = true;
        textureImporter.textureCompression = TextureImporterCompression.Uncompressed;
        AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceUpdate);
		
		//这里是为了确保下次打开MaskTex不丢失
        Texture2D texture=AssetDatabase.LoadAssetAtPath<Texture2D>(path);
        material.SetTexture("_Tmask1", texture);
    }
    void SetTargetColor(int num)
    {
        switch(num)
        {
            case 0:
                TargetColor = new Color(1, 0, 0, 0);
                break;
            case 1:
                TargetColor = new Color(0, 1, 0, 0);
                break;
            case 2:
                TargetColor = new Color(0, 0, 1, 0);
                break;
            case 3:
                TargetColor = new Color(0, 0, 0, 1);
                break;
        }    
    }
    private void OnGUI()
    {
        GameObject select = Selection.activeGameObject;
        if (select == null || select.GetComponent<TexturePaintObj>() == null) return;
        GetPaintTexs(select);
        GetMaskTex();

        if (paintTexs.Length>0)
        {
            paintIndex= GUILayout.SelectionGrid(paintIndex, paintTexs, 4, "gridlist", GUILayout.Width(390), GUILayout.Height(60));
            SetTargetColor(paintIndex);
        }

        GUILayout.BeginHorizontal();
        if (brushTexs.Length>0)
        {
            brushIndex = GUILayout.SelectionGrid(brushIndex, brushTexs, 8, "gridlist", GUILayout.Width(390), GUILayout.Height(100));
            BrushTex = brushTexs[brushIndex] as Texture2D;
        }

        gUIStyle.fontSize = 16;
        gUIStyle.normal.textColor = new Color(1, 1, 1);

        GUILayout.EndHorizontal();
        GUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Brush Size:",gUIStyle);
            BrushSize=EditorGUILayout.Slider(BrushSize, 0.01f, 32f);
        GUILayout.EndHorizontal();
        GUILayout.BeginHorizontal();
            EditorGUILayout.LabelField("Brush Strength:", gUIStyle);
            BrushStrength = EditorGUILayout.Slider(BrushStrength, 0.0f, 100f);
        GUILayout.EndHorizontal();
    }
}

感觉写了好久,差不多两周,感觉最近比以前要忙那么一点。而且好多东西都自己硬想,实在想不通才看别人的写法,所以更慢了。2020就要结束了呀,希望自己2021再接再厉。
在这里插入图片描述

  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
### 回答1: Unity Mesh EditorUnity网格编辑器)是Unity引擎中的一个功能,它允许开发者对游戏中的网格进行编辑和修改。 Unity中的网格是由顶点和三角形组成的几何体。通过Mesh Editor,开发者可以直接编辑这些顶点和三角形,从而实现对游戏中的物体形状和外观的调整和个性化。 使用Unity Mesh Editor,开发者可以进行多种编辑操作。首先,开发者可以通过添加、移动、旋转和缩放顶点来自定义网格的形状。这样,开发者可以根据需求创建独特的物体形态,例如角度、弯曲、扭曲等变换。其次,开发者可以编辑网格的三角形,包括合并、拆分和重新连接三角形,从而改变物体的多边形结构。这样,开发者可以修改物体的外观,使其更加复杂或简化。此外,开发者还可以通过在网格的表面上创建凸起或凹陷的形状,来实现更多样化的外观效果。最后,开发者还可以通过使用Unity提供的纹理编辑工具,为网格添加贴图材质,从而使物体的外观更加逼真。 在开发过程中,Unity Mesh Editor是一个非常有用的工具。它提供了直观易用的界面,使开发者能够快速编辑和调整网格。通过对物体形状和外观进行调整,开发者可以创造独特的游戏体验,增加游戏的吸引力和可玩性。 总之,Unity Mesh EditorUnity引擎中的一个重要功能,它允许开发者对游戏中的网格进行编辑和修改,以实现个性化的物体形状和外观。 ### 回答2: Unity的网格编辑器是一种功能强大的工具,用于创建、修改和处理三维网格模型。它允许开发者在游戏开发过程中对网格进行精细的控制和调整。 Unity的网格编辑器功能丰富,包括对顶点、面和边等网格元素的修改。开发者可以通过添加、删除、移动和调整顶点来改变网格的形状。他们还可以对网格进行切割、旋转和缩放等操作,以实现更复杂的设计和效果。 网格编辑器还提供了各种选项和工具,以帮助开发者在编辑过程中获得准确的控制和预览效果。例如,通过调整顶点的权重和颜色,开发者可以在网格上创建平滑的过渡和纹理效果。此外,还可以将不同的材质应用于网格的不同区域,以实现更细致的外观。 网格编辑器还支持多种导入和导出格式,以使开发者能够使用其他建模软件创建的网格模型进行进一步编辑。这样,开发者可以自由地选择最适合他们需求的工具和流程来处理和修改网格。 总之,Unity的网格编辑器是一个功能完善的工具,为开发者提供了丰富的选项和功能来创建、修改和处理三维网格模型。它使开发者能够更好地控制和调整网格的形状、纹理和外观,从而实现更精细和逼真的游戏效果。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值