Unity Shader入门精要学习笔记 - 第7章 基础纹理
本系列为UnityShader入门精要读书笔记总结,
原作者博客链接:http://blog.youkuaiyun.com/candycat1992/article/
书籍链接:http://product.dangdang.com/23972910.html
##第7章 基础纹理
纹理最初的目的就是使用一张图片来控制模型的外观。使用纹理映射技术,我们可以把一张图“黏”在模型表面,逐纹素地控制模型的颜色。
在美术人员建模的时候,通常会在建模软件中利用纹理展开技术把纹理映射坐标存储在每个顶点上。纹理映射坐标定义了该顶点在纹理中对应的2D坐标。通常,这些坐标使用一个二维坐标(u,v)来表示,其中u是横向坐标,而v是纵向坐标。因此,纹理映射坐标也被称为UV坐标。顶点中存储了这些uv坐标,着色的时候,根据顶点的这些坐标,去对应的贴图中去找像素。
###7.1 单张纹理
我们通常会使用一张纹理来代替物体的漫反射颜色。

实践-单张纹理+Blinn-Phong光照
Shader "Unlit/Chapter7-MySingleTexture"
{
Properties{
_Color ("Color Tint", Color) = (1,1,1,1)
_MainTex("MainTex", 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 _MainTex;
//我们需要为纹理类型的属性声明一个float4类型的变量_MainTex_ST。
//其中,_MainTex_ST的名字不是任意起的。
//在Unity中,我们需要使用纹理名_ST的方式来声明某个纹理的属性。其中ST是缩放和平移的缩写。
//_MainTex_ST可以让我们得到该纹理的缩放和平移值
//_MainTex_ST.xy存储的是缩放值,_MainTex_ST.zw存储的是偏移值
float4 _MainTex_ST;
fixed4 _Specular;
float _Gloss;
//顶点着色器的输入结构体
struct a2v {
float4 vertex : POSITION;
float3 normal : NORMAL;
//unity会将模型的第一组纹理坐标存储到该变量中。
float4 texcoord : TEXCOORD0;
};
//顶点着色器的输出结构体
struct v2f {
float4 pos : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
//用来存储纹理坐标
float2 uv : TEXCOORD2;
};
//顶点着色器
//在顶点着色器中,我们使用纹理的属性值_MainTex_ST来对顶点纹理坐标进行变换,
//得到最终的纹理坐标。计算过程是,首先使用缩放属性_MainTex_ST.xy对顶点纹理坐标
//进行缩放,然后再使用偏移属性MainTex_ST.zw对结果进行偏移
v2f vert(a2v v){
v2f o;
o.pos = mul(UNITY_MATRIX_MVP, v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal);
o.worldPos = mul(_Object2World, v.vertex).xyz;
o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
//可以使用内置函数来代替
//o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
fixed4 frag(v2f i) : SV_Target{
//计算了世界空间下的法线方向和光照方向
fixed3 worldNormal = normalize(i.worldNormal);
fixed3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos));
//使用tex2D函数对纹理进行采样,第一个参数是需要被采样的纹理,第二个是float2类型的纹理坐标
//我们使用采样结果和颜色属性_Color的乘积来作为材质的反射率albedo
fixed3 albedo = tex2D(_MainTex,i.uv).rgb * _Color.rgb;
//使用albedo来计算漫反射光照的结果
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb * albedo * max(0,dot(worldNormal, worldLightDir));
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"
}
在我们向Unity 中导入一张纹理资源后,可以在它的材质面板上调整属性,如下图所示:

纹理面板中的第一个属性是纹理类型。我们之所以要为导入的纹理选择合适的类型,是因为只有这样才能让Unity 知道我们的意图,为Unity Shader 传递正确的纹理,并在一些情况下可以让Unity 对该纹理进行优化。
Wrap Mode。它决定了当纹理坐标超过[0,1]范围后将会被平铺。Wrap Mode 有两种模式:一种是Repeat,在这种模式下,如果纹理坐标超过了1,那么它的整数部分将会被舍弃,而直接使用小数部分进行采样,这样的结果是纹理将会不断重复;另一种是Clamp,在这种模式下,如果纹理坐标大于1,那么将会截取到1,如果小于0,那么将会截取到0.下图给出了两种模式下平铺一张纹理的效果:

纹理导入面板中的下一个属性是Filter Mode 属性,它决定了当纹理由于变换而产生拉伸时将会采用哪种滤波模式。Fliter Mode 支持3种模式:Point,Bilinear 以及 Trilinear 。它们得到的图片滤波效果依次提升,但需要耗费的性能也依次增大。纹理滤波会影响放大或缩小纹理时得到的图片质量。
更多的具体信息,可以去官网的介绍查看。
###7.2 凹凸映射
纹理的另一种常见的应用就是凹凸映射。凹凸映射的目的是使用一张纹理来修改模型表面的法线,以便为模型提供更多的细节。这种方法不会真的改变模型的顶点位置,只是让模型看起来好像是“凹凸不平”的,但可以从模型的轮廓处看出“破绽”。
有两种主要的方法可以用来进行凹凸映射:一种方法是使用一张高度纹理来模拟表面位移,然后得到一个修改后的法线值,这种方法也被称为高度映射;另外一种方法则是使用一张法线纹理来直接存储表面法线,这种方法又被称为法线映射。
我们先来看第一种技术,即 使用一张高度图来实现凹凸映射。高度图中存储的是强度值,它用于表示模型表面局部的海拔高度。因此颜色越浅表明该位置的表面越向外凸起,而颜色越深表面该位置越向里凹。这种方法的好处是非常直观,我们可以从高度图中明确地知道一个模型表面的凹凸情况,但缺点是计算更加复杂,在实时计算时不能直接得到表面法线,而是需要像素的灰度值计算而得,因此需要消耗更多的性能。下图给出了一张高度图。

而法线纹理中存储的就是表面的法线方向。由于法线放的分量范围在[-1, 1],而像素的分量范围为[0,1],因此,我们需要做一个映射,通常使用的映射就是 pixel = (normal + 1) / 2
对于模型顶点自带的法线,它们是定义在模型空间中的,因此一种直接的想法就是将修改后的模型空间中的表面法线存储在一张纹理中,这种纹理被称为是模型空间的法线纹理。
在实际制作中,我们往往会采用另一种坐标空间,即模型顶点的切线空间来存储法线。对于模型的每个顶点,它都有一个属于自己的切线空间,这个切线空间的原点就是该顶点本身,而z轴是顶点的法线方向(n),x轴是顶点的切线方向(t),而y轴可由法线和切线叉积而得,也被称为是副切线或副法线,如下图所示。

这种纹理被称为是切线空间的法线纹理。
下图给出了模型空间和切线空间下的法线纹理。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aaEKm79i-1594733001017)(https://img-blog.youkuaiyun.com/20170531095551327)]
实际上,法线本身存储在哪个坐标系中都是可以的。但问题是,我们并不是单纯地想要得到法线,后续的光照计算才是我们的目的。而选择哪个坐标系意味着我们需要把不同信息转换到响应的坐标系中。例如,如果选择了切线空间,我们需要把从法线纹理中得到的法线方向从切线空间转换到世界空间中。
使用切线空间有更多的优点。
自由度很高。模型空间下的法线纹理记录的是绝对法线信息,仅可用于创建它的那个模型,而应用到其他模型上效果就完全错误了。而切线空间下的法线纹理记录的是相对法线信息,这意味着,即便把该纹理应用到一个完全不同的网格上,也可以得到一个合理的结果。
可进行UV动画。比如,我们可以移动一个纹理的UV坐标来实现一个凹凸移动的效果,但使用模型空间下的法线纹理会得到完全错误的结果。原因同上。这种UV动画在水或者火山熔岩这种类型的物体上会经常用到。
可以重用法线纹理。比如,一个砖块,我们仅用一张法线纹理就可以用到所有的6个面上。原因同上。
可压缩。由于切线空间下的法线纹理中法线的Z方向总是正方向,因此我们可以仅存储XY方向,而推导得到Z方向。而模型空间下的法线纹理由于每个方向都是可能的,因此必须存储3个方向的值,不可压缩。
切线空间下的法线纹理的前两个优点足以让很多人放弃模型空间下的法线纹理而选择它。从上面的优点可以看出,切线空间在很多情况下优于模型空间,而且可以节省美术人员的工作。
实践:在切线空间进行切线的相关计算
基本思路是:在片元着色器中通过纹理采样得到切线空间下的法线,然后再与切线空间下的视角方向、光照方向扥进行计算,得到最终的光照结果。
Shader "Unlit/Chapter7-MyNormalMapTangentSpace"
{
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
//法线纹理,使用"bump"作为默认值。
//"bump"是Unity内置的法线纹理,当没有提供任何法线纹理时,"bump"就对应了模型自带的法线信息。
_BumpMap ("Normal Map", 2D) = "bump" {}
//_BumpScale是用于控制凹凸程度
_BumpScale ("Bump 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;
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;
float3 lightDir: TEXCOORD1;
float3 viewDir : TEXCOORD2;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
//由于我们使用了两张纹理,因此需要存储两个纹理坐标。为此,我们把v2f中的uv变量
//的类型定义为float4,其中xy分量存储了_MainTex的纹理坐标,而zw分量存储了_BumpMap
//的纹理坐标。
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
// Compute the binormal
// float3 binormal = cross( normalize(v.normal), normalize(v.tangent.xyz) ) * v.tangent.w;
// // Construct a matrix which transform vectors from object space to tangent space
// float3x3 rotation = float3x3(v.tangent.xyz, binormal, v.normal);
// Or just use the built-in macro
//计算出从模型空间到切线空间的变换矩阵 rotation。
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);
//利用tex2D对法线纹理进行采样
fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw);
fixed3 tangentNormal;
// If the texture is not marked as "Normal map"
// tangentNormal.xy = (packedNormal.xy * 2 - 1) * _BumpScale;
// tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy, tangentNormal.xy)));
// Or mark the texture as "Normal map", and use the built-in funciton
//解压缩的法线信息,法线信息本身存储是压缩的
tangentNormal = UnpackNormal(packedNormal);
//利用_BumpScale 控制凹凸程度
tangentNormal.xy *= _BumpScale;
//由于法线都是单位矢量,因此tangentNormal.z 可以由tangentNormal.xy计算而得
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);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
图给出了不同的BumpScale 属性值下得到的结果

实践:在世界空间进行切线的相关计算
基本思路是:在顶点着色器中计算从切线空间到世界空间的变换矩阵,并把它传递给片元着色器。
Shader "Unlit/Chapter7-MyNormalMapWorldSpace"
{
Properties {
_Color ("Color Tint", Color) = (1, 1, 1, 1)
_MainTex ("Main Tex", 2D) = "white" {}
//法线纹理,使用"bump"作为默认值。
_BumpMap ("Normal Map", 2D) = "bump" {}
//_BumpScale是用于控制凹凸程度,0意味着该法线纹理不会对光照产生任何影响
_BumpScale ("Bump 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;
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);
//由于我们使用了两张纹理,因此需要存储两个纹理坐标。为此,我们把v2f中的uv变量
//的类型定义为float4,其中xy分量存储了_MainTex的纹理坐标,而zw分量存储了_BumpMap
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
//我们计算了世界空间下的顶点切线、副切线和法线的矢量表示,
//并把它们按列摆放得到从切线空间到世界空间的变换矩阵
float3 worldPos = mul(unity_ObjectToWorld, v.vertex).xyz;
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
fixed3 worldBinormal = cross(worldNormal, worldTangent) * v.tangent.w;
//然后把上述矩阵的每一行分别存储在TtoW0、TtoW1、TtoW2中,并把世界空间下
//的顶点位置的xyz分量分别存储再了这些变量的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));
bump.xy *= _BumpScale;
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);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(bump, halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
使用的法线纹理按上面的方式标识成Normal map 才能得到正确的结果(即便你忘了这么做,Unity 也会在材质面板中提醒你修正这个问题)
关于法线的更多问题,可以查看作者的博客。
http://blog.youkuaiyun.com/candycat1992/article/details/41605257
###7.3 渐变纹理
尽管在一开始,我们在渲染中使用纹理是为了定义一个物体的颜色,但后来人们发现,纹理
其实可以用于存储任何表面属性。一种常见的用法就是使用渐变纹理来控制漫反射光照的结果。
实践:渐变纹理
Shader "Unlit/Chapter7-MyRampTexture"
{
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;
//使用内置的TRANSFORM_TEX宏来计算经过平铺和偏移后的纹理坐标
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;
//使用halfLambert来构建一个纹理坐标,并用这个纹理坐标对渐变纹理_RampTex进行采样
//这里没有使用uv信息,而是自己构建了一个二维数据 [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 模式,以防止对纹理进行采样时由于浮点数精度而造成的问题。下图给出了Wrap Mode 分别为Repeat 和 Clamp 模式的效果对比。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZDGxLsYd-1594733001019)(https://img-blog.youkuaiyun.com/20170603113819721)]
###7.4 遮罩纹理
遮罩允许我们可以单独控制某些区域。遮罩一般是一张黑白图。
使用遮罩纹理的一般流程是:通过采用得到遮罩纹理的纹素值,然后使用其中某个通道的值来与某种表面属性进行相乘,这样,当该通道的值为0时,可以保护表面不受该属性的影响。总而言之,使用遮罩纹理可以让美术人员更加精准地控制模型表面的各种性质。
Shader "Unlit/Chapter7-MyMaskTexture"
{
Properties {
_Color ("Color Tint", 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;
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;
float2 uv : TEXCOORD0;
float3 lightDir: TEXCOORD1;
float3 viewDir : TEXCOORD2;
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
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);
//在计算高光反射时,我们首先对遮罩纹理_SpecularMark进行采样
//由于本例使用的遮罩纹理中每个纹素的rgb分量其实都是一样的,
//表明了该点对应的高光反射强度,在这里我们选择使用r分量来计算掩码值
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"
}
在真实的游戏制作过程中,遮罩纹理已经不止限于保护某些区域使它们免于某些修改,而是可以存储任何我们希望逐像素控制的表面属性。通常,我们会充分利用一张纹理的RGBA四个通道,用于存储不同的属性。例如,我们可以把高光反射的强度存储在R通道,把边缘光照的强度存储在G通道,把高光反射的指数部分存储在B通道,最后把自发光强度存储在A通道。

457

被折叠的 条评论
为什么被折叠?



