This is a bit complicated, but it boils down to be quite a simple problem, I hope. So here is how it goes: I am using Unity to generate a map gameobject during runtime from a bsp file which has a whole bunch of vertices, faces, uvs, texture references, and so on. The meshes created come out exactly as they should be, and all the textures come out fine. There is one problem though, there are so many meshes created with so many materials leading to many draw calls making the program slow. So I searched on a way to reduce the draw calls and I found a solution. Combine all the meshes into one big mesh and create a texture atlas by combining all the textures used. Combining the meshes works fine and combining the textures comes out great as well. Then I faced the problem of uv mapping. So I found a solution from the NVidia white paper to make a custom shader which uses the tex2d function to interpolate the texel from the texture using the uv positions with their derivatives. I think this would have worked, but my meshes have really weird triangles and I think they are ruining this solution. In the images below you can see the difference when the meshes are combined from when they are separate:
Combined Meshes with Changed UVs and Custom Shader
Separate Meshes with original UVs
This is the code I am using in the shader to set the color of the model:
o.Albedo = tex2D (_MainTex, IN.uv2_BlendTex, ddx(IN.uv_MainTex), ddy(IN.uv_MainTex)).rgb;
As you can see, I have added a second UV which is the non-tiled version of the original UV. I do that by using the frac() function, but in the C# code rather than in the shader. Since the textures can be different sizes, I had to calculate the UV before getting to the shader because I have access to the texture sizes at that time.
Here is the code I used to calculate the 2 UVs:
Rect surfaceTextureRect = uvReMappers[textureIndex];
Mesh surfaceMesh = allFaces[i].mesh;
Vector2[] atlasTiledUVs = new Vector2[surfaceMesh.uv.Length];
Vector2[] atlasClampedUVs = new Vector2[surfaceMesh.uv.Length];
for (int j = 0; j < atlasClampedUVs.Length; j++)
{
Vector2 clampedUV = new Vector2((surfaceMesh.uv[j].x - Mathf.Floor(surfaceMesh.uv[j].x)), (surfaceMesh.uv[j].y - Mathf.Floor(surfaceMesh.uv[j].y)));
float atlasClampedX = (clampedUV.x * surfaceTextureRect.width) + surfaceTextureRect.x;
float atlasClampedY = (clampedUV.y * surfaceTextureRect.height) + surfaceTextureRect.y;
atlasTiledUVs[j] = new Vector2((surfaceMesh.uv[j].x * surfaceTextureRect.width) + surfaceTextureRect.x, (surfaceMesh.uv[j].y * surfaceTextureRect.height) + surfaceTextureRect.y);
atlasClampedUVs[j] = new Vector2(atlasClampedX, atlasClampedY);
if (i < 10) { Debug.Log(i + " Original: " + surfaceMesh.uv[j] + " ClampedUV: " + clampedUV); }
}
surfaceMesh.uv = atlasTiledUVs;
surfaceMesh.uv2 = atlasClampedUVs;
The array uvReMappers is an array of Rect created when using the Texture2D function PackTextures().
Sorry for taking so long, but here is my question: Why do the textures come out contorted. Is it because the way the meshes are triangulated or is it because of the way I wrote the custom shader. And finally how can I fix it.
Thank you for your time. I am sorry for writing so much, but I have never posted a question before. I always find answers to almost all my problems online, but I have been searching for days on how to fix this problem. I feel it might be too specific to be able to find an answer for. I hope I have provided enough information.
I finally solved the problem! So it turns out I should not calculate the UVs before the shader. Instead I passed the information needed by the shader through the UVs so that it can calculate the new texel positions directly.
Here is the code before the shader:
Rect surfaceTextureRect = uvReMappers[textureIndex];
Mesh surfaceMesh = allFaces[i].mesh;
Vector2[] atlasTexturePosition = new Vector2[surfaceMesh.uv.Length];
Vector2[] atlasTextureSize = new Vector2[surfaceMesh.uv.Length];
for (int j = 0; j < atlasTexturePosition.Length; j++)
{
atlasTexturePosition[j] = new Vector2(surfaceTextureRect.x, surfaceTextureRect.y);
atlasTextureSize[j] = new Vector2(surfaceTextureRect.width, surfaceTextureRect.height);
}
surfaceMesh.uv2 = atlasTexturePosition;
surfaceMesh.uv3 = atlasTextureSize;
Here is the shader code:
tex2D(_MainTex, float2((frac(IN.uv.x) * IN.uv3.x) + IN.uv2.x, (frac(IN.uv.y) * IN.uv3.y) + IN.uv2.y));