《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

在这里插入图片描述
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存储BumpMapfloat3 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);//法线纹理中存储的是把法线经过映射后的到的像素值 此处为pixelfixed3 tangentNormal;//真的法线//如果纹理未被标记为"Normal map" 套用公式//tangentNormal.xy = (packedNormal.xy * 2 - 1) * _BumpScale;//tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy,tangentNormal.xy)));//如果纹理被标记为"Normal map" 使用内置方法UnpackNormaltangentNormal = 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.cgincinline 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);
#elsereturn 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.mzph.cn/news/104012.shtml

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

相关文章

K8s Kubernetes Namespave Pod Label Deployment Service 实战

本章节将介绍如何在kubernetes集群中部署一个nginx服务,并且能够对其进行访问。 Namespace Namespace是kubernetes系统中的一种非常重要资源,它的主要作用是用来实现多套环境的资源隔离或者多租户的资源隔离。 默认情况下,kubernetes集群中…

MySQL之双主双从读写分离

一个主机 Master1 用于处理所有写请求,它的从机 Slave1 和另一台主机 Master2 还有它的从 机 Slave2 负责所有读请求。当 Master1 主机宕机后, Master2 主机负责写请求, Master1 、 Master2 互为备机。架构图如下 : 准备 我们…

升级教育技术软件的多合一解决方案

当今时代技术和教育联系越来越紧密,教育机构对强大、安全、灵活的 IT 解决方案的探索至关重要。 全球事件、技术进步以及学生和教职员工不断变化的需求影响着不断变化的教育格局,我们要采取变革性的方法来确保教育的连续性和质量提升。 Splashtop Ente…

力扣刷题 day43:10-13

1.完全平方数 给你一个整数 n ,返回 和为 n 的完全平方数的最少数量 。 完全平方数 是一个整数,其值等于另一个整数的平方;换句话说,其值等于一个整数自乘的积。例如,1、4、9 和 16 都是完全平方数,而 3 …

20基于MATLAB的车牌识别算法,在环境较差的情景下,夜间识别度很差的车牌号码可以精确识别出具体结果,程序已调通,可直接替换自己的数据跑。

基于MATLAB的车牌识别算法,在环境较差的情景下,夜间识别度很差的车牌号码可以精确识别出具体结果,程序已调通,可直接替换自己的数据跑。 20matlab车牌识别 (xiaohongshu.com)

【融合ChatGPT等AI模型】Python-GEE遥感云大数据分析、管理与可视化及多领域案例实践应用

目录 第一章 理论基础 第二章 开发环境搭建 第三章 遥感大数据处理基础与ChatGPT等AI模型交互 第四章 典型案例操作实践 第五章 输入输出及数据资产高效管理 第六章 云端数据论文出版级可视化 更多应用 随着航空、航天、近地空间等多个遥感平台的不断发展,近…

如何在 PyTorch 中冻结模型权重以进行迁移学习:分步教程

一、说明 迁移学习是一种机器学习技术,其中预先训练的模型适用于新的但类似的问题。迁移学习的关键步骤之一是能够冻结预训练模型的层,以便在训练期间仅更新网络的某些部分。当您想要保留预训练模型已经学习的特征时,冻结至关重要。在本教程中…

4年软件测试,突破不了20K,太卷了。。。

先说一个插曲:上个月我有同学在深圳被裁员了,和我一样都是软件测试,不过他是平安外包,所以整个组都撤了,他工资和我差不多都是14K。 现在IT互联网已经比较寒冬,特别是软件测试,裁员先裁测试&am…

【Monorepo实战】pnpm+turbo+vitepress构建公共组件库文档系统

Monorepo架构可以把多个独立的系统放到一起联调,本文记录基于pnpm > workspace功能,如何构建将vitepress和组件库进行联调,并且使用turbo进行任务顺序编排。 技术栈清单: pnpm 、vitepress 、turbo 一、需求分析 1、最终目标…

Node.js 新特性 SEA/单文件可执行应用尝鲜

#1 关于 SEA 单文件可执行应用(SEA,Singe Executable Applications),是 Node.js 新版本的特性,最初在 v19.7.0、v18.16.0 加入,并在 v20.x 得到扩展。而上个月发布的全家桶 Bun.js,就自带了 SEA…

正点原子嵌入式linux驱动开发——Busybox根文件系统构建

前面已经移植了TF-A、Uboot和Linux kernel,就剩最后一个 rootfs(根文件系统)了,本章就来学习一下根文件系统的组成以及如何构建根文件系统。这是Linux系统移植的最后一步,根文件系统构建好以后就意味着拥有了一个完整的、可以运行的最小系统 …

大数据Doris(十):添加BE步骤

文章目录 添加BE步骤 一、使用mysql连接 二、​​​​​​​添加be

PySpark 概述

文章最前: 我是Octopus,这个名字来源于我的中文名--章鱼;我热爱编程、热爱算法、热爱开源。所有源码在我的个人github ;这博客是记录我学习的点点滴滴,如果您对 Python、Java、AI、算法有兴趣,可以关注我的…

十六、 代码校验(3)

本章概要 测试驱动开发 测试驱动 vs 测试优先 日志 日志信息日志等级 测试驱动开发 之所以可以有测试驱动开发(TDD)这种开发方式,是因为如果你在设计和编写代码时考虑到了测试,那么你不仅可以写出可测试性更好的代码&#xff…

分享一下开发回收废品小程序的步骤

随着人们环保意识的不断提高,回收利用已成为日常生活中不可或缺的一部分。回收小程序作为一种便捷、高效的回收方式,越来越受到人们的关注和喜爱。本文将探讨回收小程序的意义和作用,设计理念、功能特点、使用流程以及推广策略,并…

【奇葩问题】微信小程序 We分析 访问来源Top10的总比例为什么不止100%

今天有朋友在小程序后台开访问来源数据的时候发现三个渠道来源的比例超过了100% 搜了很多文章最终在官方社区找到了官方回复: 超过100%,是因为可能有用户,在当日通过多个场景,打开过你的小程序 比如用户A,上午通过【…

Navicat For MySQL使用指南

勾选填充零后的效果,就是不够的位数用零来补齐!

leetcode oj

150. 逆波兰表达式求值 - 力扣(LeetCode) 思路:定义一个名为 Solution 的类,并在其中定义了一个名为 evalRPN 的公共函数。这个函数接受一个由字符串组成的向量 tokens 作为输入,并返回一个整数。 在代码中&#xff0…

sentinel的启动与运行

首先我们github下载sentinel Releases alibaba/Sentinel (github.com) 下载好了后输入命令让它运行即可,使用cmd窗口输入一下命令即可 java -Dserver.port8089 -jar sentinel-dashboard-1.8.6.jar 账号密码默认都是sentinel 启动成功后登录进去效果如下

Nacos集群搭建

Nacos集群搭建 1.集群结构图 Nacos集群图: 其中包含3个nacos节点,然后一个负载均衡器代理3个Nacos。这里负载均衡器可以使用nginx。 三个nacos节点的地址: 节点ipportnacos1192.168.150.18845nacos2192.168.150.18846nacos3192.168.150…