0
\$\begingroup\$

I want create texture paint tool based not on vertex paint, but on splatmap UV. I already write simple shader:

o.Albedo = lerp(tex2D (_MainTex, IN.uv_MainTex), tex2D (_SecondTex, IN.uv_MainTex), tex2D (_MaskTex, IN.uv2_MaskTex).a).rgb;

All is simple: there is a background texture _MainTex for example for ground and "overlaying" texture _SecondTex for example for road. Both are tiled trough first UV-map uv_MainTex. Where second texture should appeared is determined by _MaskTex opacity, and this texture is not tiled but fit the surface of the mesh via uv2_MaskTex.

Sure, I can draw on mask texture in external image editor, save it every time, switch back to Unity and it will be updated, but it would be more fast and accurate to draw directly on mesh.

How do I see solution

  1. Add a script to the object I need. Script should have a mouse click listener.
  2. In listener I need somehow get click coordinates and convert them to second UV-map space coordinates.
  3. Then, no, actually, somewhere before script should create a copy of mask texture, store it exactly in operative memory (not as asset in file system), and assign as parameter to the object material. And then, on step 3 draw on that image copy by given coordinates from step 2.
  4. Changes should be displayed on mesh surface instantly.
  5. After finishing work with tool, script should rewrite mask texture from assets with mask texture from operative memory, and reassigned it.

I have small experience on programming Unity editor, so, any help will be appreciated.


Update

Currently I am facing problem with getting click coordinates in edit mode.

I created empty script TexturePaint and attached it to the object I want to paint. I also created TexturePaintEditor script that just placed in assets folder and derrived from Editor instead of MonoBehaviour.

The code:

using UnityEngine;
using UnityEditor;
using System.Collections;

[CustomEditor(typeof(TexturePaint))]
public class TexturePaintEditor : Editor {

private bool fl = false;

    void OnSceneGUI()
    {
        Event e = Event.current;
        
        int controlID = GUIUtility.GetControlID(FocusType.Passive);
    
        switch (Event.current.GetTypeForControl(controlID))
        {
            case EventType.MouseDown:
                Debug.Log("Mouse Down!");
                Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
                RaycastHit hit;
        
                    if ( Physics.Raycast( ray, out hit, Mathf.Infinity))
                        {
                            Vector3 myPos = hit.point;
                            Debug.Log(myPos);
                            PlaceObject(myPos);
                        }

                Event.current.Use();
                break;
            case EventType.MouseUp:
                Event.current.Use();
                break;
        case EventType.KeyDown:
                break;
        }
    }

    void PlaceObject(Vector3 myPos)
    {
    }

}

"Mouse down!" appears always when I click, but coordinates messages not: when I click some solid regular objects appears in log, but when click another, the some regular objects - not.

Update2 Seems raycast hits only objects with collider

Update3 No way... I did it! For those who perhaps following this post, wait a while, I will write a complete answer.

\$\endgroup\$
0

1 Answer 1

2
\$\begingroup\$
  1. In Unity create script TexturePaint.cs with default generated code. You don't need to change it.
  2. Create TexturePaintEditor.cs script (do not assigned to anything, just create) and paste code below

TexturePaintEditor.cs

using UnityEngine;
using UnityEditor;
using System.Collections;

[CustomEditor(typeof(TexturePaint))]
public class TexturePaintEditor : Editor {

private bool fl = false;
private bool fe = false;

private Texture2D tt;

    void OnSceneGUI()
    {
        Event e = Event.current;
        
        int controlID = GUIUtility.GetControlID(FocusType.Passive);
        switch (Event.current.GetTypeForControl(controlID))
        {
                case EventType.MouseMove:
                    if(fl || fe)
                    {
                    Ray ray = HandleUtility.GUIPointToWorldRay(Event.current.mousePosition);
                    RaycastHit hit;
        
                    if ( Physics.Raycast( ray, out hit, Mathf.Infinity))
                        {
                            Renderer rend = hit.transform.GetComponent<Renderer>();
                            MeshCollider meshCollider = hit.collider as MeshCollider;

                            if (rend == null || rend.sharedMaterial == null || rend.sharedMaterial.mainTexture == null || meshCollider == null)
                                return;

                            Texture2D tex = rend.material.GetTexture("_M2") as Texture2D;
                            Vector2 pixelUV = hit.textureCoord2;
                            pixelUV.x *= tex.width;
                            pixelUV.y *= tex.height;

                            if(fl)
                                tex.SetPixel((int)pixelUV.x, (int)pixelUV.y, Color.black); // draw
                            else if(fe)
                                tex.SetPixel((int)pixelUV.x, (int)pixelUV.y, Color.clear); // erase

                            tex.Apply();
                            tt = tex;
                        }
                    }
                break;
        case EventType.KeyDown:
        if(e.character == 'u') // switch to draw
        {
        fe = false;
        fl = !fl;
        Debug.Log(fl);
        if(!fl)
            SaveTexture(tt);
        }
        else if(e.character == 'y') // switch to erase
        {
        fl = false;
        fe = !fe;
        Debug.Log(fe);
        if(!fe)
            SaveTexture(tt);
        }
                break;
        }
    }

    protected void SaveTexture(Texture2D texture)
    {
     var path = AssetDatabase.GetAssetPath(texture);
     System.IO.File.WriteAllBytes(path, texture.EncodeToPNG());
     Debug.Log(path);
    }
}
  1. For the object with prepared uv's in external software, like Blender, add as component script TexturePaint.cs.
  2. Create new material and new shader for that material with the next code:

Terrain.shader

Shader "Custom/Terrain"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _M1 ("Albedo (RGB)", 2D) = "white" {}
        _M2 ("mask", 2D) = "white" {}
        _m1sc ("ms", Range(0,1)) = 0.5 // for scaling second texture
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        sampler2D _MainTex;
        sampler2D _M1;
        sampler2D _M2;

        struct Input
        {
            float2 uv_MainTex : TEXCOORD0;
            float2 uv2_M2;
        };

        half _Glossiness;
        half _Metallic;
        half _m1sc;
        fixed4 _Color;

        UNITY_INSTANCING_BUFFER_START(Props)
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            fixed4 c = lerp(tex2D (_MainTex, IN.uv_MainTex), tex2D (_M1, IN.uv_MainTex * _m1sc), tex2D (_M2, IN.uv2_M2).a);
            o.Albedo = c.rgb;
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}
  1. In material set textures you need. First is background, second is next layer, third used as mask.
  2. Go to third texture properties, Advanced - check Read/Write.
  3. Now select object, press u on keyboard and it will automatically draw second texture where the cursor is. To disable this mode press u again. y for erase, same logic.

enter image description here

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .