I’m woking on UI shaders and noticed that the uvs I get from a simple image as input differ from the ones from a 9-sliced image. This totally makes sense, but I wasn’t expecting it. I’m wondering if there is a way to still get the simple uv data in the shader.
Comparison:
Nope, I didn’t need it in the end. But from working quite a bit on ui shaders,I recommend checking out MeshModifiers: https://docs.unity3d.com/ScriptReference/UI.BaseMeshEffect.html
With those you should be easily able to do what you want to do (e.g. put the original uv information in the vertex color).
Unity’s 9 slice UI elements work by actually slicing the mesh into 9 quads, so it’s a mesh with 16 vertices, not 4, hence why this isn’t solvable by applying a shader to a sprite already set to use the built in 9-slice support. It’ll also break on any atlased sprite as their UVs are not necessarily in a 0.0 to 1.0 range, so that 0.5 test won’t work there either.
Yeah, as I played around with it more I discovered it’s a truly flimsy solution. :-/
Seems like there should be an easier way to do this…
I guess one way would be to calculate bottom-left, top-right vertex in CS, then set those values on the material and use them to infer the UV in the vertex shader.
As already pointed out, ui elements have a very nice base class with which you can do all kind of magic without the need of setting material properties
Here’s one that works for me
Just make sure in the shader you are looking at vertex color channel Red instead of U coordinates.
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.Pool;
using UnityEngine.UI;
public class ImageSliceWithUV : BaseMeshEffect
{
protected ImageSliceWithUV()
{}
public override void ModifyMesh(VertexHelper vh)
{
List<UIVertex> verts = new List<UIVertex>();
vh.GetUIVertexStream(verts);
var orderedVerts = verts.OrderBy(v => v.position.x).ToList();
var minX = orderedVerts.First().position.x;
var maxX = orderedVerts.Last().position.x;
for (int i = 0; i < verts.Count; i++)
{
UIVertex vertex = verts[i];
Vector3 position = vertex.position;
vertex.color.r = (byte)(Mathf.InverseLerp(minX, maxX, position.x) * 255);
verts[i] = vertex;
}
vh.Clear();
vh.AddUIVertexTriangleStream(verts);
}
}
Thanks a bunch man! I was struggling for 30 minutes before I started searching online only to come to the conclusion that it’s not possible solely relying on a 2nd uv within a custom shader file.
If anyone else is wondering, this fixes/modifies both axis instead of only x.
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
using UnityEngine.UI;
public class ImageSliceWithUV : BaseMeshEffect
{
protected ImageSliceWithUV()
{ }
public override void ModifyMesh(VertexHelper vh)
{
List<UIVertex> verts = new List<UIVertex>();
vh.GetUIVertexStream(verts);
var orderedVerts = verts.OrderBy(v => v.position.x).ToList();
var orderedVerts2 = verts.OrderBy(v => v.position.y).ToList();
var minX = orderedVerts.First().position.x;
var maxX = orderedVerts.Last().position.x;
var minY = orderedVerts.First().position.y;
var maxY = orderedVerts.Last().position.y;
for (int i = 0; i < verts.Count; i++)
{
UIVertex vertex = verts[i];
Vector3 position = vertex.position;
vertex.color.r = (byte)(Mathf.InverseLerp(minX, maxX, position.x) * 255);
vertex.color.g = (byte)(Mathf.InverseLerp(minY, maxY, position.y) * 255);
verts[i] = vertex;
}
vh.Clear();
vh.AddUIVertexTriangleStream(verts);
}
}
The base class it inherits has an [ExecuteAlways] tag so you’ll immediately be able to see it working.
I suggest everyone saving this code-snippet as it’ll probably come in handy if you’d like to spice-up your UI with e.i. scrolling backdrops, patterns and textures alongside the sliced UI borders/elements.