- Home /
Procedural Uniform UVs On a Plane
I have a script that procedurally generates a plane (rectangular) via width and height input. The plane is composed of a lattice across a grid of 1.0f spacing. For example, a plane of 4x5 has 20 quads, or 40 triangles. I understand how UVs work but I'm having some trouble 'building' the UV Vector2's procedurally. Basically, I want to have a UV mapping: 0,0 / 1,0 / 0,1 / 1,1 for each quad on the mesh. This is for an experiment in dynamic UV mapping so simply setting a UV for the whole plane and tiling the texture does not apply here. Many thanks in advance.
Here's a superb asset, free, that does this ...
http://wiki.unity3d.com/index.php?title=CreatePlane
I love that script - it's priceless. The example code may help.
the specific answer to your questions is just set the UV like that.
Regarding your specific question:
Here's my classic answer on the topic :)
http://answers.unity3d.com/questions/293607/tiling-uv-mapping.html
note the hand drawn diagrams. If you are gonna weave mesh
YOU $$anonymous$$UST DRAW DIAGRA$$anonymous$$S ON PAPER !!!!
I hope it helps. Be sure to up-thumb my other famous answer ok :)
@Fattie Thank you for the link to that CreatePlane script! I modified it to support two sided planes (with identically mapped UV's on both sides) which can be useful for collision detection or rendering very thin flat objects.
I can't update the community Wiki (yet) but perhaps you can on my behalf? Code is in my answer below as it's too big for a comment.
Right, it's a great script. I don't bother with wikis but I'm certain someone will take care of that. Cheers !!
They gave me access so it's updated now, for all to benefit ;)
Answer by Huacanacha · May 22, 2014 at 09:54 PM
Here's a modified version of the CreatePlane script (linked by Fattie in a comment above) that supports two-sided planes/quads. It has correctly reversed UV mapping and minimal vertices (4 certs, 4 tris for a two sided quad). Very useful for collision detection and thin transparent objects with transparency (or not).
Original source (now updated to support two-sided planes): http://wiki.unity3d.com/index.php?title=CreatePlane
Usage: add script to an Editor folder in your project, then select Game Object -> Create Other -> Custom Plane...
using UnityEngine;
using UnityEditor;
using System.Collections;
public class CreatePlane : ScriptableWizard
{
public enum Orientation
{
Horizontal,
Vertical
}
public enum AnchorPoint
{
TopLeft,
TopHalf,
TopRight,
RightHalf,
BottomRight,
BottomHalf,
BottomLeft,
LeftHalf,
Center
}
public int widthSegments = 1;
public int lengthSegments = 1;
public float width = 1.0f;
public float length = 1.0f;
public Orientation orientation = Orientation.Horizontal;
public AnchorPoint anchor = AnchorPoint.Center;
public bool addCollider = false;
public bool createAtOrigin = true;
public bool twoSided = false;
public string optionalName;
static Camera cam;
static Camera lastUsedCam;
[MenuItem("GameObject/Create Other/Custom Plane...")]
static void CreateWizard()
{
cam = Camera.current;
// Hack because camera.current doesn't return editor camera if scene view doesn't have focus
if (!cam)
cam = lastUsedCam;
else
lastUsedCam = cam;
ScriptableWizard.DisplayWizard("Create Plane",typeof(CreatePlane));
}
void OnWizardUpdate()
{
widthSegments = Mathf.Clamp(widthSegments, 1, 254);
lengthSegments = Mathf.Clamp(lengthSegments, 1, 254);
}
void OnWizardCreate()
{
GameObject plane = new GameObject();
if (!string.IsNullOrEmpty(optionalName))
plane.name = optionalName;
else
plane.name = "Plane";
if (!createAtOrigin && cam)
plane.transform.position = cam.transform.position + cam.transform.forward*5.0f;
else
plane.transform.position = Vector3.zero;
Vector2 anchorOffset;
string anchorId;
switch (anchor)
{
case AnchorPoint.TopLeft:
anchorOffset = new Vector2(-width/2.0f,length/2.0f);
anchorId = "TL";
break;
case AnchorPoint.TopHalf:
anchorOffset = new Vector2(0.0f,length/2.0f);
anchorId = "TH";
break;
case AnchorPoint.TopRight:
anchorOffset = new Vector2(width/2.0f,length/2.0f);
anchorId = "TR";
break;
case AnchorPoint.RightHalf:
anchorOffset = new Vector2(width/2.0f,0.0f);
anchorId = "RH";
break;
case AnchorPoint.BottomRight:
anchorOffset = new Vector2(width/2.0f,-length/2.0f);
anchorId = "BR";
break;
case AnchorPoint.BottomHalf:
anchorOffset = new Vector2(0.0f,-length/2.0f);
anchorId = "BH";
break;
case AnchorPoint.BottomLeft:
anchorOffset = new Vector2(-width/2.0f,-length/2.0f);
anchorId = "BL";
break;
case AnchorPoint.LeftHalf:
anchorOffset = new Vector2(-width/2.0f,0.0f);
anchorId = "LH";
break;
case AnchorPoint.Center:
default:
anchorOffset = Vector2.zero;
anchorId = "C";
break;
}
MeshFilter meshFilter = (MeshFilter)plane.AddComponent(typeof(MeshFilter));
plane.AddComponent(typeof(MeshRenderer));
string planeAssetName = plane.name + widthSegments + "x" + lengthSegments + "W" + width + "L" + length + (orientation == Orientation.Horizontal? "H" : "V") + anchorId + ".asset";
Mesh m = (Mesh)AssetDatabase.LoadAssetAtPath("Assets/Editor/" + planeAssetName,typeof(Mesh));
if (m == null)
{
m = new Mesh();
m.name = plane.name;
int hCount2 = widthSegments+1;
int vCount2 = lengthSegments+1;
int numTriangleVertices = widthSegments * lengthSegments * 6;
if (twoSided) {
numTriangleVertices *= 2;
}
int numVertices = hCount2 * vCount2;
Vector3[] vertices = new Vector3[numVertices];
Vector2[] uvs = new Vector2[numVertices];
int[] triangleVertices = new int[numTriangleVertices];
int index = 0;
float uvFactorX = 1.0f/widthSegments;
float uvFactorY = 1.0f/lengthSegments;
float scaleX = width/widthSegments;
float scaleY = length/lengthSegments;
for (float y = 0.0f; y < vCount2; y++)
{
for (float x = 0.0f; x < hCount2; x++)
{
if (orientation == Orientation.Horizontal)
{
vertices[index] = new Vector3(x*scaleX - width/2f - anchorOffset.x, 0.0f, y*scaleY - length/2f - anchorOffset.y);
}
else
{
vertices[index] = new Vector3(x*scaleX - width/2f - anchorOffset.x, y*scaleY - length/2f - anchorOffset.y, 0.0f);
}
uvs[index++] = new Vector2(x*uvFactorX, y*uvFactorY);
}
}
index = 0;
for (int y = 0; y < lengthSegments; y++)
{
for (int x = 0; x < widthSegments; x++)
{
triangleVertices[index] = (y * hCount2) + x;
triangleVertices[index+1] = ((y+1) * hCount2) + x;
triangleVertices[index+2] = (y * hCount2) + x + 1;
triangleVertices[index+3] = ((y+1) * hCount2) + x;
triangleVertices[index+4] = ((y+1) * hCount2) + x + 1;
triangleVertices[index+5] = (y * hCount2) + x + 1;
index += 6;
}
if (twoSided) {
// Same tri vertices, different order so normals are reversed
for (int x = 0; x < widthSegments; x++)
{
triangleVertices[index] = (y * hCount2) + x;
triangleVertices[index+1] = (y * hCount2) + x + 1;
triangleVertices[index+2] = ((y+1) * hCount2) + x;
triangleVertices[index+3] = ((y+1) * hCount2) + x;
triangleVertices[index+4] = (y * hCount2) + x + 1;
triangleVertices[index+5] = ((y+1) * hCount2) + x + 1;
index += 6;
}
}
}
m.vertices = vertices;
m.uv = uvs;
m.triangles = triangleVertices;
m.RecalculateNormals();
AssetDatabase.CreateAsset(m, "Assets/Editor/" + planeAssetName);
AssetDatabase.SaveAssets();
}
meshFilter.sharedMesh = m;
m.RecalculateBounds();
if (addCollider)
plane.AddComponent(typeof(BoxCollider));
Selection.activeObject = plane;
}
}
Answer by Owen-Reynolds · Apr 10, 2013 at 05:14 PM
You didn't say what part you were having touble with, but...
"for each QUAD on the mesh": the trick is the verts have UVs -- faces and quads don't, they guess them from surrounding verts. Two side-by-side quads normally share the middle two verts, so the bottom middle vert can't have (1,0) for the left quad and also (0,0) for the right quad.
One solution is not to share verts. This is what a modelling program does whenever you make a seam (invisibly splits those verts.)
The other is to use the graphics-card's built-in love of UV-tiling. Set x's in the "two adjacent quads" example as 0,1,2. The two quads still share that x=1 middle vert. But quad #2 shows a full tile, from 1 to 2. This is what setting tiling secretly does.
But, if you wanted each quad to show 3/4ths of the image, it can't be done w/o splitting verts (well, unless you want x=0, 0.75, 0, 0.75 for alternating reverse directions.)
i guess it's all totally flat, so the normals don't matter (I suppose - they are all just "up")
with vert sharing, I'd suggest he simply not share any verts at all. theres never any need to. performance/size is irrelevant and it's always easier to make mesh when you simply don't share verts ! the two arrays just line up 1:1, super-simple.
also you can just let the uv value increase to 20 or whatever, it will do it, I suppose .. NP
Your answer
Follow this Question
Related Questions
Assigning UV Map to model at runtime 0 Answers
using script to fill the uv array 0 Answers
Split a mesh while using same UVs and texture position. 0 Answers
Distortion of Texture from some Angles 0 Answers
A way to "fold" a texture on a mesh? 1 Answer