Author Topic: UNITY - Use your Tileset texture as a repeatable texture  (Read 341 times)

Setsuki

  • Newbie
  • *
  • Posts: 5
    • View Profile
UNITY - Use your Tileset texture as a repeatable texture
« on: April 05, 2017, 12:08:20 PM »
I am currently using Crocotile to do models for my Unity project, and while it is near perfect for texture atlasing, there are performance gains to be had when you simply want to repeat one tile on a plane ; Using crocotile, this forces you to have a higher ploycount than a simple quad.

I have made a shader that repeats a tile over a basic Unity quad, in order to benefit from both texture atlasing and texture repeating, with a low polycount.

You can see the result in the linked file

Here is what you need :
RepeatTileTool.cs (anywhere in your project)
Code: [Select]
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
#endif

[ExecuteInEditMode]
public class RepeatTileTool : MonoBehaviour
{
    public Rect position;
    public Vector2 v2UnscaledRepeat = Vector2.one;
    public Vector2 v2PaddingInPixels = Vector2.one;
    // Use this for initialization
    void Awake () {
        UpdateMesh();
}

// Update is called once per frame
void Update () {
#if UNITY_EDITOR
        if(!Application.isPlaying && Selection.activeGameObject == gameObject)
        {
            UpdateMesh();
        }
#endif
    }
    /// <summary>
    /// we "hide" the informations about the tiling in the mesh
    /// </summary>
    void UpdateMesh()
    {
        Mesh mesh = GetComponent<MeshFilter>().sharedMesh;
        Mesh newMesh = new Mesh();
        newMesh.vertices = mesh.vertices;
        newMesh.uv = mesh.uv;
        newMesh.triangles= mesh.triangles;
        newMesh.normals= mesh.normals;
        newMesh.tangents = mesh.tangents;
        Texture tex = GetComponent<MeshRenderer>().sharedMaterial.mainTexture;
        Vector2 v2Divided = new Vector2(position.width / tex.width , position.height / tex.height);

        //get the UVs corresponding to those
        float fUVxMin = v2Divided.x * position.x;
        float fUVxMax = (v2Divided.x * position.x) + v2Divided.x;
        float fUVyMax = 1- (v2Divided.y * position.y);
        float fUVyMin = 1-((v2Divided.y * position.y) + v2Divided.y);


        //padding
        fUVxMin += v2PaddingInPixels.x /tex.width;
        fUVxMax -= v2PaddingInPixels.x / tex.width;
        fUVyMin += v2PaddingInPixels.y / tex.height;
        fUVyMax -= v2PaddingInPixels.y / tex.height;
       

        Vector2 [] v2RepeatToInput = new Vector2[mesh.vertexCount];
        Color [] colorsToInput = new Color[mesh.vertexCount];
        for (int i = 0; i < mesh.vertexCount; ++i)
        {
            v2RepeatToInput[i] = new Vector2(v2UnscaledRepeat.x * transform.lossyScale.x, v2UnscaledRepeat.y * transform.lossyScale.y);
            colorsToInput[i] = new Color(fUVxMin, fUVxMax, fUVyMin, fUVyMax);
        }
        newMesh.colors = colorsToInput;
        newMesh.uv2 = v2RepeatToInput;
        GetComponent<MeshFilter>().sharedMesh = newMesh;
    }
}

RepeatTileInspector.cs (NEEDS to be in an "Editor" folder)
Code: [Select]
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEditor;

[CustomEditor( typeof( RepeatTileTool ) )]
public class RepeatTileInspector : Editor
{
private MeshRenderer m_renderer;
Texture2D m_editorTexture;
RepeatTileTool me;

public RepeatTileInspector()
{
Undo.undoRedoPerformed += RecalculateTexture;
}

public override void OnInspectorGUI()
{
me = ( RepeatTileTool )target;
if( m_renderer == null )
{
m_renderer = me.GetComponent<MeshRenderer>();
}

//if any changes is made, we remember it, as we'll need to redraw the texture
bool bChanged = false;
EditorGUI.BeginChangeCheck();

Undo.RecordObject( me, me.name );
me.position = EditorGUILayout.RectField( "Position and size", me.position );

me.v2PaddingInPixels = EditorGUILayout.Vector2Field( "Padding (In pixels)", me.v2PaddingInPixels );

me.v2UnscaledRepeat = EditorGUILayout.Vector2Field( "Texture repeat per meter", me.v2UnscaledRepeat );

bChanged = EditorGUI.EndChangeCheck();

//draw the editor texture and add a square around the selected tile
if( m_editorTexture == null || bChanged )
{
RecalculateTexture();
}
//show the texture

int nPadding = 35;
int nWidth = Mathf.Min( Screen.width - nPadding, m_editorTexture.width - nPadding );
float fRatio = 1;
if( nWidth != m_editorTexture.width - nPadding )
{
//we're screen bound
fRatio = ( float )( Screen.width - nPadding ) / ( float )( m_editorTexture.width - nPadding );
}

Rect r = GUILayoutUtility.GetRect( nWidth, m_editorTexture.height * fRatio );
GUI.DrawTexture( r, ( m_editorTexture ), ScaleMode.ScaleToFit );

}

private void RecalculateTexture()
{
m_editorTexture = new Texture2D( m_renderer.sharedMaterial.mainTexture.width, m_renderer.sharedMaterial.mainTexture.height );
m_editorTexture.SetPixels( ( ( Texture2D )m_renderer.sharedMaterial.mainTexture ).GetPixels() );

Rect realPosition = new Rect( me.position.x * me.position.width, me.position.y * me.position.height, me.position.width, me.position.height );

for( int x = ( int )realPosition.xMin ; x < ( int )realPosition.xMax ; ++x )
{

m_editorTexture.SetPixel( x, m_editorTexture.height - ( int )realPosition.yMin, Color.red );
m_editorTexture.SetPixel( x, m_editorTexture.height - ( int )realPosition.yMax, Color.red );
}


for( int y = m_editorTexture.height - ( ( int )realPosition.yMax - 1 ) ; y < m_editorTexture.height - ( ( int )realPosition.yMin + 1 ) ; ++y )
{
m_editorTexture.SetPixel( ( int )realPosition.xMin, y, Color.red );
m_editorTexture.SetPixel( ( int )realPosition.xMax, y, Color.red );

}

realPosition.x += me.v2PaddingInPixels.x;
realPosition.width -= 2 * me.v2PaddingInPixels.x;
realPosition.y += me.v2PaddingInPixels.y;
realPosition.height -= 2 * me.v2PaddingInPixels.y;

for( int x = ( int )realPosition.xMin ; x < ( int )realPosition.xMax ; ++x )
{

m_editorTexture.SetPixel( x, m_editorTexture.height - ( int )realPosition.yMin, Color.green );
m_editorTexture.SetPixel( x, m_editorTexture.height - ( int )realPosition.yMax, Color.green );
}


for( int y = m_editorTexture.height - ( ( int )realPosition.yMax - 1 ) ; y < m_editorTexture.height - ( ( int )realPosition.yMin + 1 ) ; ++y )
{
m_editorTexture.SetPixel( ( int )realPosition.xMin, y, Color.green );
m_editorTexture.SetPixel( ( int )realPosition.xMax, y, Color.green );

}

m_editorTexture.Apply();
}

}

RepeatTileShader.shader, anywhere in the project
Code: [Select]
Shader "Custom/Repeat Tile Shader" {
Properties {
_MainTex("Albedo (RGB)", 2D) = "white" {}
_FakeTex("Seriously?", 2D) = "white" {}
}
SubShader {
Tags { "RenderType"="Opaque" }
LOD 200

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

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

sampler2D _MainTex;

struct Input {
float2 uv_MainTex;
float2 uv2_FakeTex;
float4 color : COLOR;
};
void surf (Input IN, inout SurfaceOutput o) {
//IN.uv2_FakeTex contains the repetition values
//IN.color contains the UVs of the tile we want to repeat

//uv are currently between 0 and 1. We make them between 0 and Repeat
IN.uv_MainTex.x *= IN.uv2_FakeTex.x;
//then, we remove the overflow to get it back between 0 and 1
IN.uv_MainTex.x -= floor(IN.uv_MainTex.x);

//We do the same with y
IN.uv_MainTex.y *= IN.uv2_FakeTex.y;
IN.uv_MainTex.y -= floor(IN.uv_MainTex.y);

IN.uv_MainTex.x = lerp(IN.color.r, IN.color.g, IN.uv_MainTex.x);
IN.uv_MainTex.y = lerp(IN.color.b, IN.color.a, IN.uv_MainTex.y);

fixed4 c = tex2Dlod (_MainTex, half4(IN.uv_MainTex.x,IN.uv_MainTex.y,0,0));

o.Albedo = c.rgb;
o.Alpha = c.a;
}
ENDCG
}
FallBack "Diffuse"
}

All you need to do now is :
Create a quad (GameObject/3D Object/Quad)
Apply a material using the Custom/Repeat Tile Shader shader, and using your tileset as texture
Add the component "RepeatTileTool" to the quad.
You should now see in your inspector your texture, and a simple editor to set the position, width and height of the rect you want to see on your quad. You can also set a pixel padding, and finally, a scaling tool.

Note : Due to visual issues, the shader doesn't use mipmaps. You can have mipmaps enabled, but the shader will ignore them.