Having trouble with black borders when using a texture atlas

I think I must be doing something wrong. I’m trying to use a texture atlas, but when I set it up, I always get these black borders:

17016-atlasborders.png

The red and green lines are in my grey texure, but the rest of the lines shouldn’t be there. I made up a quick test with only 2 textures [17017-textures.zip|17017] and the following script. Can anyone tell me what I’m doing wrong?

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class AtlasTest : MonoBehaviour {
	
	public Texture2D dirtTexture;
	public Texture2D stoneTexture;
	
	void Start () {
		Texture2D[] atlasTextures = new Texture2D[2];
		atlasTextures[0] = dirtTexture;
		atlasTextures[1] = stoneTexture;
		
		Texture2D terrainAtlas = new Texture2D(4096, 4096);
		Rect[] terrainUVs = terrainAtlas.PackTextures(atlasTextures, 2, 4096, true);
		
		Rect dirtUVs = terrainUVs[0];
		Rect stoneUVs = terrainUVs[1];
		
		List<Vector3> vertices = new List<Vector3>();
		List<int> triangles = new List<int>();
		List<Vector2> uv = new List<Vector2>();
		
		int i = 0;
		int width = 20;
		int depth = 20;
		for (int x=0; x<width; ++x) {
			for (int z=0; z<depth; ++z) {
				Vector3 vertice = new Vector3((float)x, 0.0f, (float)z);
				vertices.Add(vertice);
				
				Rect uvsToUse;
				if (x <= 10) {
					uvsToUse = dirtUVs;
				} else {
					uvsToUse = stoneUVs;
				}
				
				if (x % 2 == 0) {
					if (z % 2 == 0) {
						uv.Add(new Vector2(uvsToUse.xMin, uvsToUse.yMin));
					} else {
						uv.Add(new Vector2(uvsToUse.xMin, uvsToUse.yMax));
					}
				} else {
					if (z % 2 == 0) {
						uv.Add(new Vector2(uvsToUse.xMax, uvsToUse.yMin));
					} else {
						uv.Add(new Vector2(uvsToUse.xMax, uvsToUse.yMax));
					}
				}
				
				if (z > 0 && x > 0) {
					triangles.Add(i);
					triangles.Add(i - depth - 1);
					triangles.Add(i - depth);
					
					triangles.Add(i);
					triangles.Add(i - 1);
					triangles.Add(i - depth - 1);
				}
				
				i++;
			}
		}
		
		Mesh mesh = new Mesh();
		mesh.vertices = vertices.ToArray();
		mesh.triangles = triangles.ToArray();
		mesh.uv = uv.ToArray();
		mesh.RecalculateNormals();
		
		gameObject.GetComponent<MeshFilter>().mesh = mesh;
		gameObject.GetComponent<MeshRenderer>().material.mainTexture = terrainAtlas;
	}
}

Ok, I’ve more or less figured out how to solve the problem. I’m not completely satisfied with it, but I guess this is the way to go. I’m open to alternatives if anyone can think of any.

My solution involves assuming a certain amount of each texture is padding. What I’m not happy about is the amount of inner texture padding that’s needed. In my tests it seems like I need to discard about 20% of each texture if it’ll be viewed at steep angles (e.g. a ground texture). Here’s the code to fix it:

/* in my case the texture size is 1024x1024, so 200 is roughly 20% padding.
 * such a high number is needed because my textures need to be viewed from steep
 * angles, and the borders show up at steep angles with any lesser value :-(
 */
int textureInnerPaddingInPixels = 200;

float uvPaddingX = (float)textureInnerPaddingInPixels / terrainAtlas.width;
float uvPaddingY = (float)textureInnerPaddingInPixels / terrainAtlas.height;

int total = terrainUVs.Length;
for (int index=0; index<total; ++index) {
	Rect uvRect = terrainUVs[index];
	uvRect.xMin += uvPaddingX * 2.0f;
	uvRect.yMin += uvPaddingY * 2.0f;
	uvRect.width -= uvPaddingX;
	uvRect.height -= uvPaddingY;
	terrainUVs[index] = uvRect;
}