《Unity Shader入门精要》笔记06

news2024/9/24 3:27:28

基础纹理

  • 单张纹理
    • 纹理的属性
      • 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

在这里插入图片描述
Alpha from Grayscale勾选后透明通道的值将会由每个像素的灰度值生成

Wrap Mode

他决定了当纹理坐标超过[0,1]范围后将会如何被平铺
在这里插入图片描述

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

在这里插入图片描述
Point、Bilinear、Trilinear得到的图片滤波效果以此提升,但需要耗费的性能也依次增大。
纹理滤波会影响放大或缩小纹理时得到的图片质量

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

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

可以勾选Generate Mipmaps来开启多级渐远纹理技术
在这里插入图片描述

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



如果导入的纹理大小超过了Max Size中的设置值,那么Unity将会把纹理缩放为这个分辨率。

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


在这里插入图片描述
Format决定了Unity内部使用那种格式来存储纹理。
使用的纹理格式精度越高,占用的内存空间越大,但得到的效果也越好
在这里插入图片描述
对于一些不需要使用很高精度的纹理,例如用于漫反射颜色的纹理,应该尽量使用压缩格式。

凹凸映射

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

两种方法实现
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根据不同平台对纹理进行压缩,再通过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会根据高度图来生成一张切线空间下的法线纹理
在这里插入图片描述
Bumpiness用于控制凹凸程度
Filtering决定用哪种方式来计算凹凸程度。一种是Smooth,会让生成后的法线纹理比较平滑,一种是Sharp,会使用Sobel滤波(一种边缘检测时使用的滤波器)来生成法线。
在这里插入图片描述

渐变纹理

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模式,以防止对纹理进行采样时由于浮点精度而造成的问题。
在这里插入图片描述Reapeat
在这里插入图片描述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四个通道,用于存储不同的属性。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1079652.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

[SRT]1.协议简介

1.简介 ​ 安全可靠传输协议(Secure Reliable Transport)简称SRT,是一种基于UDT协议的开源互联网传输协议,Haivision和Wowza合作成立SRT联盟,管理和支持SRT协议开源应用的组织,这个组织致力于促进视频流解决方案的互通性&a…

041:mapboxGL移动到到某Layer上,更换鼠标形状

第041个 点击查看专栏目录 本示例的目的是介绍演示如何在vue+mapbox中通过鼠标hover的方式来更换鼠标形状。 通过mouseenter和mouseleave的方法,经过某个图层上的时候,更换鼠标的形状,从default到pointer。 离开后从pointer到default。 直接复制下面的 vue+mapbox源代码,操…

安信证券携手共议量化行业的赋能发展

9月22日,安信证券上海浦西分公司携手非凸科技、通联数据在上海共同开展“量化私募闭门交流会”,与资方管理人就如何赋能量化私募可持续发展,给出了精彩纷呈的见解与讨论。 安信证券以“打造数字化券商”为目标,加强科技创新与业务…

数据库常见面试题--MySQL

梳理面试过程中数据库相关的常见问题,需要说明的是,这篇文章主要是基于MySQL数据库,其他类型的数据库还请自行参考使用。 数据库概述 为什么使用数据库 1、数据库增删改查更方便 2、提供了事务的能力 本质是更好的管理数据。 数据库体系结…

Android rtmp 低延迟直播方案:简介

Android rtmp 低延迟直播方案:简介 Android RTMP 低延迟直播方案:使用 RTMP 推送至 ZLMediaKit,通过 WebRTC 进行拉流。

会展购票系统有哪些特点?如何选择好的会展购票系统开发公司

会展购票系统是会展行业的重要组成部分,它具有提高会展业务流程运转效率、业务操作方便快捷、降低人工成本等优势。如开利网络自主研发的会展购票系统就有以下几个特点: 1. 拥有多端开发能力。除了电脑端外,还可以开发微信小程序端、公众号端…

不说废话,推荐一款超实用免费配音软件~

短视频发展如火如荼,在制作编辑视频的同时,文字转语音的需求也越来越大,例如解说配音、旁白配音、vlog配音等,今天就给大家推荐一款超实用的免费配音软件,感兴趣的小伙伴请接着往下看! 一、悦音AI配音&…

语音芯片的“等级”之分

语音芯片,你或许不晓得这个“芯”也是有高低之分,你可能听说过手机“发烧级”高性能芯片,同样在语音芯片中存在着性能不同等级的语音芯片。一般我们将普通芯片主要分为3个等级:商业级(又称民用级)、工业级和…

在Scrum敏捷开发中,开发人员(Developers)的职责

在Scrum敏捷开发中,开发人员(Developers)是Scrum团队中最重要的角色之一,负责产品的开发和交付,其重要性不言而喻。 那开发人员的职责和需要参加的活动是什么呢? Developers核心职责: 承诺并完…

【外汇天眼】不工作只做交易?探索不平凡的生活

金融界曾有一句广为流传的名言——"富人做债券,中产做股票,穷人做期货外汇"。这并非因为穷人特别热爱期货和外汇交易,而是因为他们更渴望通过杠杆交易在短期内实现财富梦想。如果一个人不愿意或没有技能去工作,但精通交…

Lambda 表达式使用详解,一篇文章手把手教会你

目录 1. Lambda 表达式有什么用? 2. 匿名内部类举例 3. Lambda 表达式的标准格式与使用 4. Lambda 表达式使用注意点 5. 什么是函数式接口? 6. Lambda 表达式的省略写法 7. Lambda 表达式省略写法简单展示 1. Lambda 表达式有什么用? …

LeetCode【394】字符串解码

题目&#xff1a; 思路&#xff1a; 参考&#xff1a;https://blog.csdn.net/xushiyu1996818/article/details/107973300 代码&#xff1a; public String decodeString(String s){Deque<Character> stack new ArrayDeque<>();for(char c :s.toCharArray()){if…

第一章:Android开发技能入门指南

Android 从入门到出门第一章&#xff1a;Android开发技能入门指南第二章&#xff1a;使用声明式UI创建屏幕并探索组合原则第三章&#xff1a;使用Hilt处理Jetpack Compose UI状态第四章&#xff1a;现代Android开发中的导航第五章&#xff1a;使用DataStore存储数据和测试第六章…

软考之系统架构师-01什么是系统架构师?什么是系统架构?

第一章 绪论 1. 什么是系统架构师&#xff1f; 系统架构设计师(System Architecture Designer&#xff09;是项目开发活动中的关键角色之一。系统架构是系统的一种整体的高层次的结构表示&#xff0c;是系统的骨架和根基&#xff0c;其决定了系统的健壮性和生命周期的长短。 …

系统架构师--面向对象选择题

系统架构师--面向对象选择题 面向对象考题&#xff08;将设计模式删减了&#xff0c;主要考察uml&#xff09;

IDEA使用模板创建webapp时,web.xml文件版本过低的一种解决方法

创建完成后的web.xml 文件&#xff0c;版本太低 <!DOCTYPE web-app PUBLIC"-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN""http://java.sun.com/dtd/web-app_2_3.dtd" ><web-app><display-name>Archetype Created Web Appl…

关于优先队列的一点细节

在使用优先队列PriorityQueue时&#xff0c;默认的是升序排列&#xff0c;自己可以指定比较器改为降序排列&#xff0c;例如Collections.reverseOrder()等。 但是在我做力扣的过程中&#xff0c;简单的用一个list的addAll方法添加了优先队列里边所有元素&#xff0c;结果发现添…

浅谈智能照明控制系统在地铁照明中的应用

摘要&#xff1a;随着我国经济建设的加速发展&#xff0c;城市轨道交通越来越获得社会的青睐。车站照明关系到轨道交通的服务质量、运营成本、运营成本等多个方面&#xff0c;在既要保证运营成本又要满足国家“节能”要求的背景下&#xff0c;智能照明控制系统应运而生。 轨道…

uniapp:幸运大转盘demo

<template><view class"index"><image src"../../static/img/158.png" mode"" class"banner"></image><view class"title">绿色积分加倍卡拿到手软</view><almost-lottery :lottery…

Element组件案例 Vue路由 前端打包部署步骤

目录 Element组件案例案例需求与分析环境搭建整体布局顶部标题左侧导航栏核心-右侧导航栏表格编写表单编写分页工具栏编写 异步数据加载异步加载数据性别展示修复图片展示修复 Vue路由Vue路由简介Vue路由入门 打包部署前端工程打包部署前端工程nginx介绍部署 Element组件案例 …