Unity/HLSL:根据表面法线尝试使用纹理图集的不同部分时出现奇怪的结果

Unity/HLSL: Odd results when trying to use different parts of texture atlas depending on surface normal

我是 3D 代码和编写着色器的新手,所以我可能在这里做了一些愚蠢的事情。我正在研究一个简单的 Minecraft 克隆,只是为了学习 Unity,我认为对地面进行纹理处理的最简单方法是使用带有草地 + 泥土纹理的纹理图集,并使用着色器选择要获取图集的哪一部分纹理取决于表面法线。即,对于立方体朝上的面,将使用草地纹理,而对于所有其他面,将使用泥土纹理。纹理看起来像这样:

在我的着色器中,如果我执行以下操作,我会按预期在每个面上获得污垢纹理:

float y = (IN.uv_MainTex.y / 2.0f);
float2 uv = {IN.uv_MainTex.x, y};
fixed4 c = tex2D (_MainTex, uv) * _Color;
o.Albedo = c.rgb;

如果我使用相同的代码但始终将 0.5f 添加到 y 以获得上半部分,即 float y = (IN.uv_MainTex.y / 2.0f) + 0.5f; 我会按预期获得到处都是草的纹理:

但是当我尝试根据法线设置 y 时,即 float y = (IN.uv_MainTex.y / 2.0f) + floor(IN.worldNormal.y) * 0.5f; 我得到了这个奇怪的结果,其中顶面大部分是草,但部分污垢纹理显示在对角线中:

IN.worldNormal 是正确的正常使用,还是我需要将其转换为其他 space?还是我使用 floor() 的方式有问题?任何建议表示赞赏。

完整着色器代码:

Shader "Custom/GroundShader"
{
    Properties
    {
        _Color ("Color", Color) = (1,1,1,1)
        _MainTex ("Albedo (RGB)", 2D) = "white" {}
        _Glossiness ("Smoothness", Range(0,1)) = 0.5
        _Metallic ("Metallic", Range(0,1)) = 0.0
    }
    SubShader
    {
        Tags { "RenderType"="Opaque" }
        LOD 200

        CGPROGRAM
        // Physically based Standard lighting model, and enable shadows on all light types
        #pragma surface surf Standard fullforwardshadows

        // Use shader model 3.0 target, to get nicer looking lighting
        #pragma target 3.0

        sampler2D _MainTex;

        struct Input
        {
            float2 uv_MainTex;
            float3 worldNormal;
        };

        half _Glossiness;
        half _Metallic;
        fixed4 _Color;

        // Add instancing support for this shader. You need to check 'Enable Instancing' on materials that use the shader.
        // See https://docs.unity3d.com/Manual/GPUInstancing.html for more information about instancing.
        // #pragma instancing_options assumeuniformscaling
        UNITY_INSTANCING_BUFFER_START(Props)
            // put more per-instance properties here
        UNITY_INSTANCING_BUFFER_END(Props)

        void surf (Input IN, inout SurfaceOutputStandard o)
        {
            float y = (IN.uv_MainTex.y / 2.0f) + floor(IN.worldNormal.y) * 0.5f;

            float2 uv = {IN.uv_MainTex.x, y};

            // Albedo comes from a texture tinted by color
            fixed4 c = tex2D (_MainTex, uv) * _Color;

            o.Albedo = c.rgb;
            // Metallic and smoothness come from slider variables
            o.Metallic = _Metallic;
            o.Smoothness = _Glossiness;
            o.Alpha = c.a;
        }
        ENDCG
    }
    FallBack "Diffuse"
}

编辑: 原来的答案在下面,但我以错误的方式解决了这个问题。正确的做法是使用 Mesh.uv 属性 设置网格的 UV。在这种情况下不需要使用着色器。


复制我在 Unity 论坛上从 Namey5 得到的答案:

This looks to be a precision issue (potentially from vertex normal interpolation). There are probably smarter ways to do this, but as a simple fix you could just add a small bias to the normal, i.e;

float y = (IN.uv_MainTex.y / 2.0f) + floor (IN.worldNormal.y + 0.01) * 0.5f;

我稍微修改为 float y = (IN.uv_MainTex.y / 2.0f) + floor (IN.worldNormal.y * 1.1f) * 0.5f; 这确保底面也显示污垢纹理,因为法线将下降到 -2 而不是 -1。