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。
我是 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。