《Unity Shader入门精要》笔记06

基础纹理

  • 单张纹理
    • 纹理的属性
      • Alpha Source
      • Wrap Mode
      • Filter Mode
  • 凹凸映射
    • 高度纹理
    • 法线纹理
    • 实践
      • 在切线空间下计算
      • 在世界空间下计算
    • Unity中的法线纹理类型
      • Create from Grayscale
  • 渐变纹理
  • 遮罩纹理
    • 其他遮罩处理

单张纹理

我们通常会使用一张纹理来代替物体的漫反射颜色

Shader "Custom/SpecularFragShader"
{
    Properties
    {
        _Diffuse ("Diffuse", Color) = (1,1,1,1)
        _Specular ("Specular", Color) = (1,1,1,1)
        _Gloss ("Gloss", Range(8.0,256)) = 20
    }
    SubShader
    {
        //顶点 片元着色器的代码要写在Pass语义块 而非SubShader语义块
        Pass
        {
            Tags { "LightMode"="ForwardBase" }
            
            CGPROGRAM

            #pragma vertex vert;
            #pragma fragment frag;

            #include "Lighting.cginc"

            fixed4 _Diffuse; //颜色范围在0-1之间 可以用fixed来储存
            fixed4 _Specular;
            float _Gloss;//范围很大

            struct a2v
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                fixed3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
            };

            //计算世界空间下的法线方向和顶点坐标,传递给片元着色器
            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = mul(v.normal,(float3x3)unity_WorldToObject);
                o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
                return o;
            }
            fixed4 frag(v2f i) : SV_Target
            {
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(_WorldSpaceLightPos0.xyz);
                fixed3 diffuse = _LightColor0.rgb * _Diffuse.rgb * saturate(dot(worldNormal,worldLightDir));

                fixed3 reflectDir = normalize(reflect(-worldLightDir,worldNormal));
                fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - i.worldPos.xyz);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(saturate(dot(reflectDir,viewDir)),_Gloss);

                return fixed4(ambient + diffuse + specular,1.0);
            }
            
            ENDCG
        }
    }
    FallBack "Specular"
}

纹理的属性

Alpha Source

《Unity Shader入门精要》笔记06_第1张图片
Alpha from Grayscale勾选后透明通道的值将会由每个像素的灰度值生成

Wrap Mode

他决定了当纹理坐标超过[0,1]范围后将会如何被平铺
《Unity Shader入门精要》笔记06_第2张图片

Repeat:在这种模式下,如果纹理坐标超过了1,那么他的整数部分将会被舍弃,而直接使用小数部分进行采样,这样的结果是纹理将会不断重复
Clamp:在这种模式下,如果纹理坐标大于1,那么将会截取到1,如果小于0,那么将会截取到0

如果需要这样的效果,我们必须使用纹理的属性(例如上面的_MainTex_ST变量)在UnityShader中对顶点纹理坐标进行相应的变换 也就是说,代码中需要包含类似下面的代码

o.uv = v.texcoord.xy * _MainTex_ST.xy + MainTex_ST.zw;//TRANSFORM_TEX(v.texcoord,_MainTex);

Filter Mode

《Unity Shader入门精要》笔记06_第3张图片
Point、Bilinear、Trilinear得到的图片滤波效果以此提升,但需要耗费的性能也依次增大。
纹理滤波会影响放大或缩小纹理时得到的图片质量

纹理缩小比放大更加复杂,因为缩小时原纹理中的多个像素将会对应一个目标像素,并且需要处理抗锯齿问题。

多级渐远纹理技术
将原纹理提前用滤波处理来得到很多更小的图像,形成了一个图像金字塔,每一层都是对上一层图像降采样的结果。这样在实时运行时,就可以快速得到结果像素。但缺点是需要使用一定的空间用于储存这些多级渐远纹理,通常会多占用33%的内存空间。

可以勾选Generate Mipmaps来开启多级渐远纹理技术
《Unity Shader入门精要》笔记06_第4张图片

Point模式使用了最近邻(nearest neighbor)滤波,再放大或缩小时,它的采样像素数通常只有一个,因此图像会看起来有种像素风的效果
Bilinear滤波使用了线性滤波,对于每个目标像素它会找到4个临近像素,对他们进行线性插值混合后得到最终像素,因此看起来是模糊的
Trilinear滤波和Bilinear几乎是一样的,只是它还会在多级渐远纹理之间进行混合。如果没开多级渐远纹理技术的话那么结果就是和Bilinear一样


《Unity Shader入门精要》笔记06_第5张图片
如果导入的纹理大小超过了Max Size中的设置值,那么Unity将会把纹理缩放为这个分辨率。

处于性能和空间的考虑,我们应该尽量使用2的幂大小的纹理。如果使用了非2幂大小的纹理,那么这些纹理往往会占用更多的内存空间,而且GPU读取该纹理的速度也会有所下降。


《Unity Shader入门精要》笔记06_第6张图片
Format决定了Unity内部使用那种格式来存储纹理。
使用的纹理格式精度越高,占用的内存空间越大,但得到的效果也越好
《Unity Shader入门精要》笔记06_第7张图片
对于一些不需要使用很高精度的纹理,例如用于漫反射颜色的纹理,应该尽量使用压缩格式。

凹凸映射

目的:使用一张纹理来修改模型表面的法线,为模型提供更多细节。

两种方法实现
1.高度映射:使用高度纹理来模拟表面位移,然后得到一个修改后的法线值
2.法线映射:使用法线纹理来直接存储表面法线(通常使用)

高度纹理

使用一张高度图来实现凹凸映射,因为高度图中存储的是强度值(intensity),他用于表示模型表面局部的海拔高度。
优点:直观,可以从颜色深浅看出表面凹凸情况
缺点:计算更加复杂,实时计算不能直接得到表面法线,而是需要从像素的灰度值计算,因此需要消耗更多性能

通常和法线映射一起使用,用于给出表面凹凸的额外信息。通常会使用法线映射来修改光照

法线纹理

法线纹理中存储的是表面法线方向。由于法线方向的分量范围在[-1,1]而像素分量范围为[0,1],因此需要做一个映射
pixel = (normal + 1) / 2
因此在shader中对法线纹理进行纹理采样后,需要对结果进行一次反射的过程,以得到原先的法线方向
normal = pixel * 2 + 1

将修改后的模型空间中的表面法线存储在一张纹理中,这种纹理被称为是模型空间的法线纹理(object-space normal map)

对于模型的每个顶点,他都有一个属于自己的切线空间,这个切线空间的原点就是该顶点的本身,而z轴是顶点的法线方向(n),x轴是顶点的切线方向(t),而y轴可由法线和切线叉积而得,也就是副切线或副法线,这种纹理被称为是切线空间的法线纹理(tangent-space normal map)

模型空间下的法线纹理是五颜六色的。因为模型空间下各个点存储的法线方向是各异的,经过了映射之后会有不同颜色。
切线空间下的法线纹理是浅蓝色。因为每个法线所在的坐标空间不同,这种法线纹理存储的是各个点在各自切线空间中的法线扰动方向,因此如果法线不变,name在他的切线空间中,新法线就是z轴(0,0,1),映射后就对应了RGB(0.5 , 0.5 , 1)

在平面情况下法线扰动的原理是:原本的法线是(0,1),通过改变高度,然后计算切线,dp=c*[h(p+1)-h(p)] 旋转90°得到法线,即由(1,dp)->(-dp,1),再做归一化即刻得到扰动后的法线。
在3D情况下法线扰动的原理是:原本的法线是(0,0,1),求导dp/du=c1*[h(u+1)-h(u)]dp/dv=c2*[h(v+1)-h(v)],同样旋转90°,可以得到新的法线为(-dp/du,-dp/dv,1),再做归一化。

引用:GKF快去做饭啊 https://www.jianshu.com/p/d7be0220b65c

切线空间在很多情况下都优于模型空间
模型空间优点:实现简单,更加直观,可提供平滑的边界
切线空间优点:自由度高(模型空间下的法线纹理是绝对法线信息,仅可用于创建它的那个模型,而切线空间下的法线信息是相对的),可进行UV动画(移动纹理来达到凹凸移动的效果),可重用法线纹理,可压缩(切线空间下的z总是正方向,因此可以仅储存xy,推导出z,而模型空间下每个方向都是可能的,必须储存三个方向的值)

附:什么是法线贴图及它们是如何工作的


实践

在切线空间下计算

把光照方向、视角方向变换到切线空间下

【效率更高】 可以在顶点着色器中就完成对光照方向和视角方向的变换。而世界空间下需要先对法线纹理进行采样,变换过程必须在片元着色器中实现,因此需要在片元着色器中进行一次矩阵操作

模型空间到切线空间的变换矩阵的逆矩阵为,切线(x轴),副切线(y轴),法线(z轴)的顺序按列排序。
【定义】如果一个变换中仅存在平移和旋转变换,那么这个变换的逆矩阵就为它的转置矩阵。
因此从模型空间到切线空间的变换矩阵就是从切线空间到模型空间的变换矩阵的转置矩阵。因此切线(x轴),副切线(y轴),法线(z轴)的顺序按行排序则可得到模型空间到切线空间的变换矩阵。

Shader "Custom/TangentSpaceShader"
{
    Properties
    {
        _Color("Color Tint",Color) = (1,1,1,1)
        _MainTex("Main Tex",2D) = "white" {}
        _BumpMap("Normal Map",2D) = "bump" {} //法线纹理 "bump"是Unity内置的法线纹理
        _BumpScale("Bump Scale",Float) = 1.0 //凹凸程度 为0时法线纹理不会对光照产生影响
        _Specular("Specular",Color) = (1,1,1,1)
        _Gloss("Gloss",Range(8.0,256)) = 20
    }
    SubShader
    {
        Pass
        {
            Tags{"LightMode" = "ForwardBase"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"

            fixed4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _BumpMap;
            float4 _BumpMap_ST;
            float _BumpScale;
            fixed4 _Specular;
            float _Gloss;

            //切线空间是由顶点法线和切线构建出的坐标空间
            struct a2v
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 tangent : TANGENT; //同为切线 但tangent是float4
                //因为我们需要使用tangent.w分量来决定切线空间中的第三个坐标轴——副切线的方向性
                float4 texcoord : TEXCOORD0;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float4 uv : TEXCOORD0;//需要储存两个纹理坐标 所以为float4 xy存储MainTex zw存储BumpMap
                float3 lightDir : TEXCOORD1; //变换后的光照方向
                float3 viewDir : TEXCOORD2; //变换后的视角方向
            };
            
            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv.xy = TRANSFORM_TEX(v.texcoord,_MainTex);//v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
                o.uv.zw = TRANSFORM_TEX(v.texcoord,_BumpMap);//v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;

                //计算副法线 因为同时垂直于切线和法线的方向有两个,而w决定了我们选择那一个方向
                //float3 binormal = cross(normalize(v.normal),normalize(v.tangent.xyz))*v.tangent.w;
                //构造一个矩阵,将向量从模型空间变换到切线空间
                //float3x3 rotation = float3x3(v.tangent.xyz,binormal,v.normal);

                //或直接使用内置宏
                TANGENT_SPACE_ROTATION;

                //获得模型空间下的光照和视角方向 利用矩阵变换到切线空间中
                o.lightDir = mul(rotation,ObjSpaceLightDir(v.vertex).xyz);
                o.viewDir = mul(rotation,ObjSpaceViewDir(v.vertex).xyz);
                return o;
            }

            //采样得到切线空间下的发现方向,再在切线空间下进行光照计算
            fixed4 frag(v2f i) : SV_Target
            {
                float3 tangentLightDir = normalize(i.lightDir);
                float3 tangentViewDir = normalize(i.viewDir);

                //获得法线贴图中的纹理
                fixed4 packedNormal = tex2D(_BumpMap,i.uv.zw);//法线纹理中存储的是把法线经过映射后的到的像素值 此处为pixel
                fixed3 tangentNormal;//真的法线

                //如果纹理未被标记为"Normal map" 套用公式
                //tangentNormal.xy = (packedNormal.xy * 2 - 1) * _BumpScale;
                //tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy,tangentNormal.xy)));

                //如果纹理被标记为"Normal map" 使用内置方法UnpackNormal
                tangentNormal = UnpackNormal(packedNormal);
                tangentNormal.xy *= _BumpScale;
                tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy,tangentNormal.xy)));

                fixed3 albedo = tex2D(_MainTex,i.uv).rgb * _Color.rgb;// 材质的反射率
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;// 环境光部分
                fixed3 diffuse = _LightColor0.rgb * albedo * max(0,dot(tangentNormal,tangentLightDir));//套用公式

                fixed3 halfDir = normalize(tangentLightDir + tangentViewDir); //Blinn-Phong光照模型的新矢量
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(tangentNormal,halfDir)),_Gloss);//套用公式

                return fixed4(ambient + diffuse + specular,1.0);
                
            }
            ENDCG
        }
    }
    FallBack "Specular"
}

在世界空间下计算

采样得到的法线方向变换到世界空间下,再和世界空间下的光照方向和视角方向进行计算

【更通用】 有时需要在世界空间下进行计算。比如使用Cubemap进行环境映射是,需要使用世界空间下的反射方向对Cubemap进行采样。如果同时需要进行发现映射,则需要把发现方向变换到世界空间下。

在顶点着色器中计算从切线空间到世界空间的变换矩阵,并把它传给片元着色器。变换矩阵的计算可以由顶点的切线、副切线和法线在世界空间下的表示来得到。最后,我们只需要在片元着色器中把法线纹理中的法线方向从切线空间变换到世界空间下。

Shader "Custom/WorldSpaceShader"
{
    Properties
    {
        _Color("Color Tint",Color) = (1,1,1,1)
        _MainTex("Main Tex",2D) = "white" {}
        _BumpMap("Normal Map",2D) = "bump" {} //法线纹理 "bump"是Unity内置的法线纹理
        _BumpScale("Bump Scale",Float) = 1.0 //凹凸程度 为0时法线纹理不会对光照产生影响
        _Specular("Specular",Color) = (1,1,1,1)
        _Gloss("Gloss",Range(8.0,256)) = 20
    }
    SubShader
    {
        Pass
        {
            Tags{"LightMode" = "ForwardBase"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"

            fixed4 _Color;
            sampler2D _MainTex;
            float4 _MainTex_ST;
            sampler2D _BumpMap;
            float4 _BumpMap_ST;
            float _BumpScale;
            fixed4 _Specular;
            float _Gloss;
            
            struct a2v
            {
               	float4 vertex : POSITION;
				float3 normal : NORMAL;
				float4 tangent : TANGENT;
				float4 texcoord : TEXCOORD0;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float4 uv : TEXCOORD0;
                float4 TtoW0 : TEXCOORD1; //按行拆分矩阵每一行
                float4 TtoW1 : TEXCOORD2;
                float4 TtoW2 : TEXCOORD3;
            };

             //计算从切线空间到世界空间的变换矩阵
            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv.xy = TRANSFORM_TEX(v.texcoord,_MainTex);
                o.uv.zw = TRANSFORM_TEX(v.texcoord,_BumpMap);

                float3 worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
                fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
                fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
                //计算副法线 因为同时垂直于切线和法线的方向有两个,而w决定了我们选择那一个方向
                fixed3 worldBinormal = cross(worldNormal,worldTangent) * v.tangent.w;

                //充分利用插值寄存器的存储空间
                o.TtoW0 = float4(worldTangent.x,worldBinormal.x,worldNormal.x,worldPos.x);
                o.TtoW1 = float4(worldTangent.y,worldBinormal.y,worldNormal.y,worldPos.y);
                o.TtoW2 = float4(worldTangent.z,worldBinormal.z,worldNormal.z,worldPos.z);
                
                return o;
            }
            
            fixed4 frag(v2f i) : SV_Target
            {
                float3 worldPos = float3(i.TtoW0.w,i.TtoW1.w,i.TtoW2.w);
                //使用内置函数得到世界空间下的光照和视角方向
                fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
                
                fixed3 bump = UnpackNormal(tex2D(_BumpMap,i.uv.zw));
                //乘以_BumpScale控制凹凸程度
                //只需要控制xy轴,这是因为z轴就是模型自带的法线方向,所以如果xy为0了,就是原法线,如果xy值变化越大,则越偏离原法线,凹凸效果就越强
                bump.xy *= _BumpScale;
                //z分量可以由xy计算得到。由于使用的是切线空间下的法线纹理,因此可以保证法线方向的z分量为正
                //用平方根计算z分量 sqrt(1 - x² - y²)
                bump.z = sqrt(1.0 - saturate(dot(bump.xy,bump.xy)));
                //矩阵的每一行和法线相乘 把法线转换到世界空间下
                bump = normalize(half3(dot(i.TtoW0.xyz,bump),dot(i.TtoW1.xyz,bump),dot(i.TtoW2.xyz,bump)));

                fixed3 albedo = tex2D(_MainTex,i.uv).rgb * _Color.rgb;// 材质的反射率
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;// 环境光部分
                fixed3 diffuse = _LightColor0.rgb * albedo * max(0,dot(bump,lightDir));//套用公式

                fixed3 halfDir = normalize(lightDir + viewDir); //Blinn-Phong光照模型的新矢量
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(bump,halfDir)),_Gloss);//套用公式

                return fixed4(ambient + diffuse + specular,1.0);
                
            }
            ENDCG
        }
    }
    FallBack "Specular"
}

一个插值寄存器最多只能存储float4大小的值,对于矩阵我们可以把它们按行拆成多个变量存储
TtoW0,TtoW1,TtoW2是依次存储了从切线空间到世界空间的每一行
但实际上对方向矢量变换只需要用到3x3的矩阵
因此为了充分利用插值寄存器的存储空间,也把世界空间下的顶点位置存储在变量的w分量中

Unity中的法线纹理类型

当把法线纹理的纹理类型标志为Normal map时,可以使用Unity的内置函数UnpackNormal来得到正确的法线方向。
《Unity Shader入门精要》笔记06_第8张图片
《Unity Shader入门精要》笔记06_第9张图片

这么做可以让Unity根据不同平台对纹理进行压缩,再通过UnpackNormal函数来针对不同的压缩格式对法线纹理进行正确的采样

//UnityCG.cginc

inline fixed3 UnpackNormalDXT5nm (fixed4 packednormal)
{
    fixed3 normal;
    normal.xy = packednormal.wy * 2 - 1;//在DXT5nm格式中 x对应a通道(w分量) y对应g通道 ,纹理的r b通道被舍弃
    normal.z = sqrt(1 - saturate(dot(normal.xy, normal.xy)));//可推导
    return normal;
}

//普通纹理不能按这种方式压缩,因为法线纹理中法线是单位向量,并且切线空间下法线方向的z分量始终为正,可以通过另外两个通道推导出来。

inline fixed3 UnpackNormal(fixed4 packednormal)
{
#if defined(UNITY_NO_DXT5nm)
    return packednormal.xyz * 2 - 1;
#elif defined(UNITY_ASTC_NORMALMAP_ENCODING)
    return UnpackNormalDXT5nm(packednormal);
#else
    return UnpackNormalmapRGorAG(packednormal);
#endif
}

Create from Grayscale

高度图中生成法线纹理
白色表示相对更高 黑色表示相对更低
在Texture Type为Normal map下勾选了Create from GrayScale后,Unity会根据高度图来生成一张切线空间下的法线纹理
《Unity Shader入门精要》笔记06_第10张图片
Bumpiness用于控制凹凸程度
Filtering决定用哪种方式来计算凹凸程度。一种是Smooth,会让生成后的法线纹理比较平滑,一种是Sharp,会使用Sobel滤波(一种边缘检测时使用的滤波器)来生成法线。
《Unity Shader入门精要》笔记06_第11张图片

渐变纹理

Shader "Custom/RampShader"
{
    Properties
    {
        _Color("Color Tint",Color) = (1,1,1,1)
        _RampTex("Ramp Tex",2D) = "white" {}
        _Specular("Specular",Color) = (1,1,1,1)
        _Gloss("Gloss",Range(8.0,256)) = 20
    }
    SubShader
    {
        Pass
        {
            Tags{"LightMode" = "ForwardBase"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"

            fixed4 _Color;
            sampler2D _RampTex;
            float4 _RampTex_ST;
            fixed4 _Specular;
            float _Gloss;

            struct a2v
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 texcoord : TEXCOORD0;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float3 worldNormal : TEXCOORD0;
                float3 worldPos : TEXCOORD1;
                float2 uv : TEXCOORD2;
            };

            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.worldNormal = UnityObjectToWorldNormal(v.normal);
                o.worldPos = mul(unity_ObjectToWorld,v.vertex).xyz;
                o.uv = TRANSFORM_TEX(v.texcoord,_RampTex);
                return o;
            }

            //关键
            fixed4 frag(v2f i):SV_Target
            {
                fixed3 worldNormal = normalize(i.worldNormal);
                fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));

                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz;

                //半兰伯特模型
                fixed halfLambert = 0.5 * dot(worldNormal,worldLightDir) + 0.5;
                //使用0-1之间的半兰伯特来构建纹理坐标 并对渐变纹理进行采样 再和材质颜色相乘 得到最终的漫反射颜色
                fixed3 diffuseColor = tex2D(_RampTex,fixed2(halfLambert,halfLambert)).rgb * _Color.rgb;//纹理的漫反射颜色

                fixed3 diffuse = _LightColor0.rgb * diffuseColor;//环境光的漫反射颜色

                fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));
                fixed3 halfDir = normalize(worldLightDir + viewDir);
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(worldNormal,halfDir)),_Gloss);

                return fixed4(ambient + diffuse + specular, 1.0);
            }
            
            ENDCG
        }
    }
    FallBack "Specular"
}

注意: 我们需要把渐变纹理的Wrap Mode设为Clamp模式,以防止对纹理进行采样时由于浮点精度而造成的问题。
《Unity Shader入门精要》笔记06_第12张图片Reapeat
《Unity Shader入门精要》笔记06_第13张图片Clamp
由于半兰伯特虽然是在0-1之间 但是可能会有1.00001的出现,如果使用Reapeat则会舍弃整数只保留小数,所以会出现黑点。

遮罩纹理

常见应用在只做地形材质时需要混合多张图片,例如表现草地、石子、裸露土地的纹理等,使用遮罩纹理可以控制如何混合这些效果。

流程:通过采样得到遮罩纹理的纹素值,使用其中某个或某几个通道的值来与某种表面属性进行相乘,这样当该通道的值为0时,可以保护表面不受该属性的影响。

可以让美术同学更加精准(像素级别)的控制模型表面的各种性质。

Shader "Custom/MaskShader"
{
    //高光遮罩纹理 逐像素的控制模型表面高光反射强度
    Properties
    {
        _Color("Color Tine",Color) = (1,1,1,1)
        _MainTex("Main Tex",2D) = "white" {}
        _BumpMap("Normal Map",2D) = "bump" {}
        _BumpScale("Bump Scale",Float) = 1.0
        _SpecularMask("Specular Mask",2D) = "white" {} //高光反射遮罩纹理
        _SpecularScale("Specular Scale",Float) = 1.0 // 控制遮罩影响度的系数
        _Specular("Specular",Color) = (1,1,1,1)
        _Gloss("Gloss",Range(8.0,256)) = 20
        
    }
    SubShader
    {
        Pass
        {
            Tags{"LightMode" = "ForwardBase"}
            CGPROGRAM
            #pragma vertex vert
            #pragma fragment frag

            #include "Lighting.cginc"

            fixed4 _Color;
            sampler2D _MainTex;
            
            float4 _MainTex_ST;//_MainTex _BumpMap _SpecularMask共同使用
            //因此在材质面板中修改主纹理系数和偏移系数会同时影响三个纹理采样
            //好处是可以节省需要存储的纹理坐标数,否则随着使用的纹理数目增加,会迅速沾满顶点着色器中可使用的插值寄存器
            
            sampler2D _BumpMap;
            float _BumpScale;
            sampler2D _SpecularMask;
            float _SpecularScale;
            fixed4 _Specular;
            float _Gloss;

            struct a2v
            {
                float4 vertex : POSITION;
                float3 normal : NORMAL;
                float4 tangent : TANGENT;
                float4 texcoord : TEXCOORD0;
            };

            struct v2f
            {
                float4 pos : SV_POSITION;
                float4 uv : TEXCOORD0;
                float3 lightDir : TEXCOORD1;
                float3 viewDir : TEXCOORD2;
            };

            v2f vert(a2v v)
            {
                v2f o;
                o.pos = UnityObjectToClipPos(v.vertex);
                o.uv.xy = TRANSFORM_TEX(v.texcoord,_MainTex);
                
                TANGENT_SPACE_ROTATION;

                //获得模型空间下的光照和视角方向 利用矩阵变换到切线空间中
                o.lightDir = mul(rotation,ObjSpaceLightDir(v.vertex)).xyz;
                o.viewDir = mul(rotation,ObjSpaceViewDir(v.vertex)).xyz;
                
                return o;
            }

            //使用遮罩纹理的地方是片元着色器。我们使用它来控制模型表面的高光反射强度
            fixed4 frag(v2f i) : SV_Target
            {
                fixed3 tangentLightDir = normalize(i.lightDir);
                fixed3 tangentViewDir = normalize(i.viewDir);

                fixed3 tangentNormal = UnpackNormal(tex2D(_BumpMap,i.uv));
                tangentNormal.xy *= _BumpScale;
                tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy,tangentNormal.xy)));

                fixed3 albedo = tex2D(_MainTex,i.uv).rgb * _Color.rgb;
                fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
                fixed3 diffuse = _LightColor0.rgb * albedo * max(0,dot(tangentNormal,tangentLightDir));
                fixed3 halfDir = normalize(tangentLightDir + tangentViewDir);

                //对遮罩纹理进行采样
                //由于使用的遮罩纹理中每个纹素的rgb分量是相同的,表示了该点对应的高光反射强度 在此选择使用r分量来计算掩码值
                //计算结果和scale相乘 一起控制高光反射强度
                fixed specularMask = tex2D(_SpecularMask,i.uv).r * _SpecularScale;
                fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0,dot(tangentNormal,halfDir)),_Gloss) * specularMask;

                return fixed4(ambient + diffuse +specular,1.0);
            }

            ENDCG
        }
    }
    FallBack "Specular"
}

在对遮罩纹理进行采样时,由于使用的遮罩纹理中每个纹素的rgb分量是相同的,实际上有很多空间被浪费了。
在实际的游戏制作时,会充分利用遮罩纹理中每一个颜色通道来存储不同的表面属性。

其他遮罩处理

在真实的游戏制作过程中,遮罩纹理已经不止限于保护某些区域使它们免于某些修改,而是可以存储任何我们希望逐像素控制的表面属性。通常会充分利用一张纹理的RGBA四个通道,用于存储不同的属性。

你可能感兴趣的:(读书笔记,unity,笔记)