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
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值