简体   繁体   English

在Unity中使用自定义着色器正确平铺Atlas纹理

[英]Tiling atlas textures correctly with a custom shader in Unity

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. 事情就是这样:我正在使用Unity在运行时从bsp文件生成地图游戏对象,该文件包含一堆顶点,面,uv,纹理参考等。 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. 然后我遇到了uv映射的问题。 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. 因此,我从NVidia白皮书中找到了一种解决方案,以制作自定义着色器,该着色器使用tex2d函数通过uv位置及其派生值从纹理中插入纹理像素。 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 结合了具有更改的UV和自定义着色器的网格

Separate Meshes with original UVs 使用原始UV分离网格

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. 如您所见,我添加了第二个UV,它是原始UV的非平铺版本。 I do that by using the frac() function, but in the C# code rather than in the shader. 我通过使用frac()函数来完成此操作,但是使用C#代码而不是使用着色器。 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. 由于纹理可以是不同的大小,因此在进入着色器之前必须计算UV,因为那时我可以访问纹理大小。

Here is the code I used to calculate the 2 UVs: 这是我用来计算2个UV的代码:

                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(). 数组uvReMappers是使用Texture2D函数PackTextures()创建的Rect数组。

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 took a different approach and created a texture atlas on the cpu, from there UV mapping was just like normal UV mapping all I had to do was assign a texture to the vertex info from my atlas ... 我采用了另一种方法,并在cpu上创建了纹理图集,从那里进行UV映射就像正常的UV映射一样,我所要做的就是为我的图集的顶点信息分配纹理...

My scenario is a custom voxel engine that can handle anything from minecraft to rendering voxel based planets and I haven't found a scenario it can't handle yet. 我的场景是自定义体素引擎,可以处理从Minecraft到渲染基于体素的行星的任何事情,但我还没有找到它无法处理的场景。

Here's my code for the atlas ... 这是我的地图集代码...

using UnityEngine;
using Voxels.Objects;

namespace Engine.MeshGeneration.Texturing
{
    /// <summary>
    /// Packed texture set to be used for mapping texture info on 
    /// dynamically generated meshes.
    /// </summary>
    public class TextureAtlas
    {
        /// <summary>
        /// Texture definitions within the atlas.
        /// </summary>
        public TextureDef[] Textures { get; set; }

        public TextureAtlas()
        {
            SetupTextures();
        }

        protected virtual void SetupTextures()
        {
            // default for bas atlas is a material with a single texture in the atlas
            Textures = new TextureDef[]
            {
                new TextureDef 
                { 
                    VoxelType = 0, 
                    Faces =  new[] { Face.Top, Face.Bottom, Face.Left, Face.Right, Face.Front, Face.Back },
                    Bounds = new[] {
                        new Vector2(0,1), 
                        new Vector2(1, 1),
                        new Vector2(1,0),
                        new Vector2(0, 0)
                    }
                }
            };
        }


        public static TextureDef[] GenerateTextureSet(IntVector2 textureSizeInPixels, IntVector2 atlasSizeInPixels)
        {
            int x = atlasSizeInPixels.X / textureSizeInPixels.X;
            int z = atlasSizeInPixels.Z / textureSizeInPixels.Z;
            int i = 0;
            var result = new TextureDef[x * z];
            var uvSize = new Vector2(1f / ((float)x), 1f / ((float)z));

            for (int tx = 0; tx < x; tx++)
                for (int tz = 0; tz < z; tz++)
                {
                    // for perf, types are limited to 255 (1 byte)
                    if(i < 255)
                    {
                        result[i] = new TextureDef
                        {
                            VoxelType = (byte)i,
                            Faces = new[] { Face.Top, Face.Bottom, Face.Left, Face.Right, Face.Front, Face.Back },
                            Bounds = new[] {
                                new Vector2(tx * uvSize.x, (tz + 1f) * uvSize.y), 
                                new Vector2((tx + 1f) * uvSize.x, (tz + 1f) * uvSize.y),
                                new Vector2((tx + 1f) * uvSize.x, tz * uvSize.y),
                                new Vector2(tx * uvSize.x, tz * uvSize.y)
                            }
                        };

                        i++;
                    }
                    else
                        break;
                }

             return result;
        }
    }
}

And for a texture definition within the atlas ... 对于地图集内的纹理定义...

using UnityEngine;
using Voxels.Objects;

namespace Engine.MeshGeneration.Texturing
{
    /// <summary>
    /// Represents an area within the atlas texture 
    /// from which a single texture can be pulled.
    /// </summary>
    public class TextureDef
    {
        /// <summary>
        /// The voxel block type to use this texture for.
        /// </summary>
        public byte VoxelType { get; set; }

        /// <summary>
        /// Faces this texture should be applied to on voxels of the above type.
        /// </summary>
        public Face[] Faces { get; set; }

        /// <summary>
        /// Atlas start ref
        /// </summary>
        public Vector2[] Bounds { get; set; }
    }
}

For custom scenarios where I need direct control of the UV mappings I inherit texture atlas and then override the SetupTextures() method but in pretty much all cases for me I create atlases where the textures are all the same size so simply calling GenerateTextureSet will do the uv mapping calculations I believe you need. 对于需要直接控制UV贴图的自定义场景,我继承了纹理图集,然后覆盖SetupTextures()方法,但是在我看来,在几乎所有情况下,我都会创建纹理都相同大小的图集,因此只需调用GenerateTextureSet即可uv映射计算,我相信您需要。

The UV coords for a given face of a given voxel type are then ... 然后,将给定体素类型的给定面的UV坐标...

IEnumerable<Vector2> UVCoords(byte voxelType, Face face, TextureAtlas atlas)
        {
            return atlas.Textures
                .Where(a => a.VoxelType == voxelType && a.Faces.Contains(face))
                .First()
                .Bounds;
        }

In your case you probably have a different way to map to the texture of choice from your pack but essentially the combination of a face and type in my case are what determine the uv mapping set I want. 在您的情况下,从包装中映射到所选纹理的方式可能有所不同,但从本质上来说,面和类型的组合决定了我想要的uv映射集。

This then allows you to use your mesh with any standard shader instead of relying on custom shader logic. 然后,这使您可以将网格与任何标准着色器一起使用,而不必依赖自定义着色器逻辑。

I finally solved the problem! 我终于解决了问题! So it turns out I should not calculate the UVs before the shader. 因此,事实证明我不应该在着色器之前计算UV。 Instead I passed the information needed by the shader through the UVs so that it can calculate the new texel positions directly. 相反,我通过UV传递了着色器所需的信息,以便它可以直接计算新的纹理像素位置。

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));

You have to turn the passed in TEXCOORD0 from a percentage of the image space to a pixel value, use the modulus to figure out which pixel it is on the tiled texture, and then turn it back into a percentage of the image. 您必须将传入的TEXCOORD0从图像空间的百分比转换为像素值,使用模数找出平铺纹理上的哪个像素,然后将其转换回图像的百分比。

Here's the code: You need the 2D variables _MainTex and _PatternTex to be defined. 这是代码:需要定义2D变量_MainTex_PatternTex

        struct v2f
        {
            float2 uv : TEXCOORD0;
            float4 vertex : SV_POSITION;
        };            


        float modFunction(float number, float divisor){
            //2018-05-24: copied from an answer by Nicol Bolas: https://stackoverflow.com/questions/35155598/unable-to-use-in-glsl
            return (number - (divisor * floor(number/divisor)));
        }


        fixed4 frag (v2f i) : SV_Target
        {
            fixed4 curColor = tex2D(_MainTex, i.uv);                
            fixed4 pattern = tex2D(_PatternTex, 
                float2(
                    modFunction(i.uv.x*_MainTex_TexelSize.z,_PatternTex_TexelSize.z) *_PatternTex_TexelSize.x,
                    modFunction(i.uv.y*_MainTex_TexelSize.w,_PatternTex_TexelSize.w) *_PatternTex_TexelSize.y
                )
            );
            fixed4 col = curColor * pattern;                
            col.rgb *= col.a;
            return col;
        }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM