实验
素材:
在材质球中,我们经常可以看到像 Main Tex 这样的属性,里面有个 Tiling 和 Offset,那它是什么意思呢?Tiling可以理解为是缩放,Offset 是平移。
我们合理推测这里的点 A 的贴图坐标是 (0.75,0.875)。
我们现在把 Tiling 的 x 改成 2。结果如下图所示。
也就是 A 点变成了 (1.5,0.875)。因为这张贴图的 Wrap Mode 设置的是 Repeat,所以超出 [0,1] 的部分会进行重复,可以像下图一样理解,但是实际上超出部分直接去掉整数部分就可以了,比如 1.5 直接保留小数部分也就是 0.5。
将 Wrap Mode 改成了Clamp,也就是超出部分就按照 0 或者 1 来算。结果如下。
下面是其他几种 Wrap Mode,可以顾名思义。
还可以通过改成 Per-axis 来分别指定 uv 轴的 Wrap Mode。
Offset 同理,回到一开始的 A (0.75,0.875),然后将将 Offset 的 x,设置为 0.25。如下图所示,合理推测现在 A 的坐标为 (1,0.875)。
单张纹理
Shader "MyShader/Texture/SingleTextureMat"
{
Properties
{
_Spacular("Spacular",Color) = (1,1,1,1)
_Gloss("Gloss",Range(8.0,256)) = 20
_MainTex("Main Tex",2D) = "white"{}
_TexColor("TextureColor",Color) = (1,1,1,1)
}
SubShader
{
Tags { "LightMode"="ForwardBase" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "UnityShaderVariables.cginc"
#include "Lighting.cginc"
fixed4 _DiffuseColor;
fixed4 _Spacular;
float _Gloss;
sampler2D _MainTex;
float4 _MainTex_ST; // 存放同名贴图的缩放和平移信息,xy 是缩放值,面板上对应的是 Tilling;zw 是平移值,面板上对应的是 Offset。
fixed4 _TexColor;
struct appdata
{
float4 vertex : POSITION;
fixed3 normal : NORMAL;
float2 texcoord:TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 worldNormal : TEXCOORD0;
float3 worldPos : TEXCOORD1;
float2 uv:TEXCOORD2;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.worldNormal = UnityObjectToWorldNormal(v.normal); // 使用 UnityObjectToWorldNormal 计算法线
o.worldPos = mul(unity_ObjectToWorld, v.vertex);
o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
// 纹理采样
fixed3 albedo = tex2D(_MainTex,i.uv) * _TexColor; // 漫反射系数
// 环境光
fixed3 ambientColor = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo; // 环境光也要 * 贴图的颜色
// 漫反射光
float3 normal = i.worldNormal;
fixed3 lightColor = _LightColor0.rgb;
float3 worldLightDir = normalize(UnityWorldSpaceLightDir(i.worldPos)); // 使用 WorldSpaceLightDir 获取光照的方向
fixed3 diffuseColor = albedo * lightColor * saturate(dot(normal,worldLightDir));
// 使用 BlinnPhone 模型,因为计算简单很多
float3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos)); // 使用 WorldSpaceViewDir 获取观察的方向
float3 h_normal = normalize(viewDir.xyz + worldLightDir.xyz);
fixed3 spacularColor = _Spacular * lightColor * pow(max(0,dot(normal,h_normal)),_Gloss);
fixed3 color = ambientColor + diffuseColor + spacularColor;
return fixed4(color,1);
}
ENDCG
}
}
}
凹凸纹理
切线空间下
Shader "MyShader/Texture/NormalMapTangentSpaceMat"
{
Properties
{
_Spacular("Spacular",Color) = (1,1,1,1)
_Gloss("Gloss",Range(8.0,256)) = 20
_MainTex("Main Tex",2D) = "white"{}
_TexColor("TextureColor",Color) = (1,1,1,1)
_NormalTex("NormalTexture",2D) = "bump"{}
_BumpScale("BumpScale",float)=0.8
}
SubShader
{
Tags { "LightMode"="ForwardBase" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "UnityShaderVariables.cginc"
#include "Lighting.cginc"
fixed4 _DiffuseColor;
fixed4 _Spacular;
float _Gloss;
sampler2D _MainTex;
float4 _MainTex_ST; // 存放同名贴图的缩放和平移信息,xy 是缩放值,面板上对应的是 Tilling;zw 是平移值,面板上对应的是 Offset。
fixed4 _TexColor;
sampler2D _NormalTex;
float4 _NormalTex_ST; //存放法线贴图的缩放和位移
float _BumpScale;
struct appdata
{
float4 vertex : POSITION;
fixed3 normal : NORMAL;
float4 tangent:TANGENT; // 这里是 float4 ,用 w 来确定副切线朝向
float2 texcoord:TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 tangentSpaceLightDir : TEXCOORD0; // 切线空间下光照的方向
float3 tangentSpaceViewDir : TEXCOORD1; // 切线空间下的观察方向
float4 uv:TEXCOORD2; // uv 信息,xy 存放贴图的 uv,zw 存放法线贴图的 uv
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
// 设置 uv 信息
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv.zw = v.texcoord.xy * _NormalTex_ST.xy + _NormalTex_ST.zw;
// 设置模型空间转切线空间的矩阵
float3 y_axis = cross(normalize(v.normal),normalize(v.tangent))*v.tangent.w;
float3x3 modle2tangent = float3x3(v.tangent.xyz,y_axis,v.normal);
// 设置切线空间的光照方向
o.tangentSpaceLightDir = mul(modle2tangent, ObjSpaceLightDir(v.vertex)).xyz;
// 设置切线空间的观察方向
o.tangentSpaceViewDir = mul(modle2tangent,ObjSpaceViewDir(v.vertex)).xyz;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float4 packedNormal = tex2D(_NormalTex,i.uv.zw);
float3 tangentNormal = UnpackNormal(packedNormal); // 这里的法线是单位向量
tangentNormal *= _BumpScale; // 但是乘了系数之后就不是单位向量了
//tangentNormal = normalize(tangentNormal); // 你可以让他直接归一化,但这样就像等于上一步没有做,依然是乘了系数 1
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy,tangentNormal.xy))); // 所以我们把它弥补到 z 轴,这样就可以实现系数越大,凹凸越明显。
tangentNormal = normalize(tangentNormal);
float3 tangentSpaceLightDir = normalize(i.tangentSpaceLightDir);
float3 tangentSpaceViewDir = normalize(i.tangentSpaceViewDir);
// 纹理采样
fixed3 albedo = tex2D(_MainTex,i.uv.xy) * _TexColor; // 漫反射系数
// 环境光
fixed3 ambientColor = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo; // 环境光也要 * 贴图的颜色
// 漫反射光
fixed3 lightColor = _LightColor0.rgb;
fixed3 diffuseColor = albedo * lightColor * max(0,(dot(tangentNormal,tangentSpaceLightDir)));
// 高光反射
float3 half_normal = normalize(tangentSpaceViewDir.xyz + tangentSpaceLightDir.xyz);
fixed3 spacularColor = _Spacular * lightColor * pow(max(0,dot(tangentNormal,half_normal)),_Gloss);
fixed3 color = ambientColor + diffuseColor + spacularColor;
return fixed4(color,1);
}
ENDCG
}
}
}
解释一下为什么可以用一个 _BumpScale 的系数就可以控制凹凸。为了方便区分,把切线空间中的 Z 轴叫做无凹凸的法线。真实的法线就叫做法线。
- 首先如果 _BumpScale = 0,那么经过
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy,tangentNormal.xy)));
之后法线为 (0,0,1) ,也就是跟无凹凸的法线保持一致,即没有凹凸性。 - 当 0<_BumpScale<1 的时候,比如说是 0.8,我们假设一开始的法线为 (1/2,0,√3/2) ,也就是跟无凹凸的法线夹角为 30°。然后乘了 0.8 之后,不再是单位向量,我们把损失全部弥补到 Z 轴,也就是将向量的尽头顺着切线空间内 x=0.4,y=0 所能组成的直线移动,直到向量重新变成长度为 1,就是新的法线了。新的法线应该是有两条,我们舍去 Z 为负的那一条。可以想象到现在新的法线与无凹凸法线的夹角是要小于 30° 的。也就是表面的倾斜程度要更加的平缓。
- 当 _BumpScale=1 的时候,就是真实的切线贴图想要表现的凹凸程度,如果能一直保证这就是想要的效果,那么
tangentNormal *= _BumpScale;
和tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy,tangentNormal.xy)));
都是不需要的。 - 当 _BumpScale>1 的时候,我们想象这个系数是无限大的,那么结果就是 x=+∞,y=+∞,z=0。也就是说这个点所在的平面已经是垂直得了,角度再大就到背面去了。所以我们可以发现一个问题,也就是这个时候法线又不是一个单位向量了。所以我个人认为应该在算出 z 的值之后再加一句
tangentNormal = normalize(tangentNormal);
进行归一化。对比效果如下图:
-
当 _BumpScale<0 的时候,还是以刚刚法线为 (1/2,0,√3/2) 的点为例,当我们运行结束之后,结果其实是 (-1/2,0,√3/2),而不是 (-1/2,0,-√3/2)。所以这个法线依然是与无凹凸法线成三十度夹角,而不是变成了 150°,想象一下光的方向(这里是从光源到点的方向,跟代码中相反) 是 (-1/2,0,-√3/2),也就是正好直射(光线和平面垂直)在这个点上,那对于法线为 (-1/2,0,√3/2) 所在的平面,光线就是倾斜着照射(具体来说是和平面成 30° 夹角),不用脑阔想就知道斜着照进来肯定光线要暗一点。
世界空间下
Shader "MyShader/Texture/NormalMapWorldSpace"
{
Properties
{
_Spacular("Spacular",Color) = (1,1,1,1)
_Gloss("Gloss",Range(8.0,256)) = 20
_MainTex("Main Tex",2D) = "white"{}
_TexColor("TextureColor",Color) = (1,1,1,1)
_NormalTex("NormalTexture",2D) = "bump"{}
_BumpScale("BumpScale",float)=0.8
}
SubShader
{
Tags { "LightMode"="ForwardBase" }
Pass
{
CGPROGRAM
#pragma enable_d3d11_debug_symbols
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "UnityShaderVariables.cginc"
#include "Lighting.cginc"
fixed4 _DiffuseColor;
fixed4 _Spacular;
float _Gloss;
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _TexColor;
sampler2D _NormalTex;
float4 _NormalTex_ST;
float _BumpScale;
struct appdata
{
float4 vertex : POSITION;
fixed3 normal : NORMAL;
float4 tangent:TANGENT;
float2 texcoord:TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float4 T2W_1stLine : TEXCOORD0; // 存储切线空间到世界空间变换矩阵的第一行,为了充分利用差值寄存器的四位,w 用来存顶点世界坐标的 x
float4 T2W_2ndLine : TEXCOORD1; // 第二行和顶点世界坐标的 y
float4 T2W_3rdLine : TEXCOORD2; // 第三行和顶点世界坐标的 z
float4 uv : TEXCOORD3;
float3 Test:TEXCOORD4;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
// 设置 uv 信息
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv.zw = v.texcoord.xy * _NormalTex_ST.xy + _NormalTex_ST.zw;
// 存储切线空间到世界空间的变换矩阵和世界顶点坐标
float3 worldPosition = mul((float3x3)unity_ObjectToWorld,v.vertex);
float3 wSpaceTangent = normalize(UnityObjectToWorldDir(v.tangent));
float3 wSpaceNormal = normalize(UnityObjectToWorldNormal(v.normal));
float3 biTangent = cross(v.normal,v.tangent)*v.tangent.w; // 计算副切线,单位向量叉乘依然是单位向量
o.T2W_1stLine = float4(wSpaceTangent.x,biTangent.x,wSpaceNormal.x,worldPosition.x);
o.T2W_2ndLine = float4(wSpaceTangent.y,biTangent.y,wSpaceNormal.y,worldPosition.y);
o.T2W_3rdLine = float4(wSpaceTangent.z,biTangent.z,wSpaceNormal.z,worldPosition.z);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float3 tSpaceNormal = UnpackNormal(tex2D(_NormalTex,i.uv.zw));
tSpaceNormal *= _BumpScale;
tSpaceNormal.z = sqrt(1.0 - saturate(dot(tSpaceNormal.xy,tSpaceNormal.xy)));
tSpaceNormal = normalize(tSpaceNormal);
float3 WSpaceNormal = normalize(half3(dot(i.T2W_1stLine.xyz,tSpaceNormal),dot(i.T2W_2ndLine.xyz,tSpaceNormal),dot(i.T2W_3rdLine.xyz,tSpaceNormal)));
float3 WSpaceLightDir = normalize(UnityWorldSpaceLightDir(float3(i.T2W_1stLine.w,i.T2W_2ndLine.w,i.T2W_3rdLine.w)));
float3 WSpaceViewDir = normalize(UnityWorldSpaceViewDir(float3(i.T2W_1stLine.w,i.T2W_2ndLine.w,i.T2W_3rdLine.w)));
i.Test = WSpaceLightDir;
// 纹理采样
fixed3 albedo = tex2D(_MainTex,i.uv.xy) * _TexColor;
// 环境光
fixed3 ambientColor = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
// 漫反射光
fixed3 lightColor = _LightColor0.rgb;
fixed3 diffuseColor = albedo * lightColor * max(0,(dot(WSpaceNormal,WSpaceLightDir)));
// 高光反射
float3 half_normal = normalize(WSpaceViewDir.xyz + WSpaceLightDir.xyz);
fixed3 spacularColor = _Spacular * lightColor * pow(max(0,dot(WSpaceNormal,half_normal)),_Gloss);
fixed3 color = diffuseColor;
return fixed4(color,1);
}
ENDCG
}
}
}
对比
这两个空间下的计算虽然基本思路是一样的,但实际上的运行效果是不同的,先拿高光反射单独来看,下面是将 BlinnPhone 的高光反射模型最后要乘的中间向量和法线方向的结果作为颜色画在了物体表面。可以看到在同一个位置和角度,两个物体并不是完全相同的。
二者的代码代码部分如下:
// 高光反射
float3 half_normal = normalize(WSpaceViewDir.xyz + WSpaceLightDir.xyz);
fixed3 spacularColor = _Spacular * lightColor * pow(max(0,dot(WSpaceNormal,half_normal)),_Gloss);
fixed3 color = pow(max(0,dot(WSpaceNormal,half_normal)),_Gloss);
return fixed4(color,1);
//return fixed4(float3(i.T2W_1stLine.w,i.T2W_2ndLine.w,i.T2W_3rdLine.w),1);
// 高光反射
float3 half_normal = normalize(tangentSpaceViewDir.xyz + tangentSpaceLightDir.xyz);
fixed3 spacularColor = _Spacular * lightColor * pow(max(0,dot(tangentNormal,half_normal)),_Gloss);
fixed3 color = pow(max(0,dot(tangentNormal,half_normal)),_Gloss);
return fixed4(color,1);
再来比较一下漫反射的部分,也是不一样的,切线空间整体要亮一些,但最亮和最暗的部分貌似没有区别,代码方便就是最后只保留了漫反射的那一部分。
最后是整体的效果:
总结:这两种方式最后的效果基本一样,但切线空间的好像要更亮一些,但在最亮和最暗的地方几乎是一样的。然后我怀疑过是因为顶点上携带的数据到片元着色器之后采样导致的区别,于是将之前切线空间的代码改成下面这样,也就是插值模型坐标的顶点,而不是切线空间的光线方向和观察方向进行插值,但是结果跟原来的是一样的。
Shader "MyShader/Texture/01Test"
{
Properties
{
_Spacular("Spacular",Color) = (1,1,1,1)
_Gloss("Gloss",Range(8.0,256)) = 20
_MainTex("Main Tex",2D) = "white"{}
_TexColor("TextureColor",Color) = (1,1,1,1)
_NormalTex("NormalTexture",2D) = "bump"{}
_BumpScale("BumpScale",float) = 0.8
}
SubShader
{
Tags { "LightMode" = "ForwardBase" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "UnityShaderVariables.cginc"
#include "Lighting.cginc"
fixed4 _DiffuseColor;
fixed4 _Spacular;
float _Gloss;
sampler2D _MainTex;
float4 _MainTex_ST; // 存放同名贴图的缩放和平移信息,xy 是缩放值,面板上对应的是 Tilling;zw 是平移值,面板上对应的是 Offset。
fixed4 _TexColor;
sampler2D _NormalTex;
float4 _NormalTex_ST; //存放法线贴图的缩放和位移
float _BumpScale;
struct appdata
{
float4 vertex : POSITION;
fixed3 normal : NORMAL;
float4 tangent:TANGENT; // 这里是 float4 ,用 w 来确定副切线朝向
float2 texcoord:TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float4 Obj2Tan_1stLine : TEXCOORD0;
float4 Obj2Tan_2ndLine : TEXCOORD1;
float4 Obj2Tan_3rdLine : TEXCOORD2;
float4 uv:TEXCOORD3; // uv 信息,xy 存放贴图的 uv,zw 存放法线贴图的 uv
};
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
// 设置 uv 信息
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
o.uv.zw = v.texcoord.xy * _NormalTex_ST.xy + _NormalTex_ST.zw;
// 设置模型空间转切线空间的矩阵
float3 y_axis = cross(normalize(v.normal),normalize(v.tangent)) * v.tangent.w;
o.Obj2Tan_1stLine = float4(v.tangent.xyz,v.vertex.x);
o.Obj2Tan_2ndLine = float4(y_axis,v.vertex.y);
o.Obj2Tan_3rdLine = float4(v.normal,v.vertex.z);
return o;
}
fixed4 frag(v2f i) : SV_Target
{
float4 packedNormal = tex2D(_NormalTex,i.uv.zw);
float3 tangentNormal = UnpackNormal(packedNormal); // 这里的法线是单位向量
tangentNormal *= _BumpScale; // 但是乘了系数之后就不是单位向量了
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy,tangentNormal.xy))); // 所以我们把它弥补到 z 轴,这样就可以实现系数越大,凹凸越明显。
tangentNormal = normalize(tangentNormal);
float3x3 Obj2Tan = float3x3(i.Obj2Tan_1stLine.xyz,i.Obj2Tan_2ndLine.xyz,i.Obj2Tan_3rdLine.xyz);
float4 origin_vertex = float4(i.Obj2Tan_1stLine.w,i.Obj2Tan_2ndLine.w,i.Obj2Tan_3rdLine.w,0);
float3 tangentSpaceLightDir = normalize(mul(Obj2Tan,ObjSpaceLightDir(origin_vertex)));
float3 tangentSpaceViewDir = normalize(mul(Obj2Tan,ObjSpaceViewDir(origin_vertex)));
// 纹理采样
fixed3 albedo = tex2D(_MainTex,i.uv.xy) * _TexColor; // 漫反射系数
// 环境光
fixed3 ambientColor = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo; // 环境光也要 * 贴图的颜色
// 漫反射光
fixed3 lightColor = _LightColor0.rgb;
fixed3 diffuseColor = albedo * lightColor * max(0,(dot(tangentNormal,tangentSpaceLightDir)));
// 高光反射
float3 half_normal = normalize(tangentSpaceViewDir.xyz + tangentSpaceLightDir.xyz);
fixed3 spacularColor = _Spacular * lightColor * pow(max(0,dot(tangentNormal,half_normal)),_Gloss);
fixed3 color = ambientColor + diffuseColor + spacularColor;
return fixed4(color,1);
}
ENDCG
}
}
}
渐变纹理
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "MyShader/Texture/Ramp Texture"
{
Properties
{
_RampTexure("RampTexture",2D) = "white"{}
_Gloss("Gloss",Range(0.8,256)) = 20
_Spacular("Spacular",Color) = (1,1,1,1)
_MainColor("MainColor",Color) = (1,1,1,1)
}
SubShader
{
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
sampler2D _RampTexure;
float _Gloss;
fixed3 _Spacular;
fixed3 _MainColor;
float4 _RampTexure_ST;
struct appdata
{
float4 vertex : POSITION;
float3 normal :NORMAL;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 WS_normal: TEXCOORD1; // 世界空间的法线
float4 WS_position:TEXCOORD2; // 世界空间的顶点坐标
};
v2f vert(appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
o.WS_normal = normalize(mul(v.normal,(float3x3)unity_WorldToObject));
o.WS_position = mul(unity_ObjectToWorld,v.vertex);
return o;
}
fixed4 frag(v2f i):SV_TARGET
{
float3 WS_lightDir = normalize(UnityWorldSpaceLightDir(i.WS_position));
//float ramp_position = saturate(dot(i.WS_normal,WS_lightDir)); // 让这个范围在 0-1 之间,但这样效果很丑
float ramp_position = 0.5*dot(i.WS_normal,WS_lightDir)+0.5; // 让这个范围在 0-1 之间
fixed3 ramp_color = tex2D(_RampTexure,fixed2(ramp_position.x*_RampTexure_ST.x+_RampTexure_ST.z,0.1)); // 这里默认渐变图片的 y 是不变的。
// 计算环境光
fixed3 ambient_color = UNITY_LIGHTMODEL_AMBIENT.xyz;
// 计算漫反射光
fixed3 light_color = _LightColor0.rgb;
fixed3 diffuse_color = light_color * _MainColor * ramp_color;
// 计算高光反射
float3 WS_viewDir = normalize(UnityWorldSpaceViewDir(i.WS_position));
float3 half_normal = normalize(WS_viewDir.xyz + WS_lightDir.xyz);
fixed3 spacularColor = _Spacular * light_color * pow(max(0,dot(i.WS_normal,half_normal)),_Gloss);
return fixed4(ambient_color+diffuse_color+spacularColor,1);
}
ENDCG
}
}
}
运行结果:
渐变纹理就是通过法线和光照方向的角度来控制采样渐变图片上的哪一点来作为片元的颜色,一般来说,角度越小,采样的位置做靠左,也就是颜色越深。
书中提到了,这种情况下最好将图片的 Wrap Mode 设置为 Clamp 模式,如果是 Repeat 模式的话,由于浮点数的误差,比如理想中有一个采样点的 x 是 1.000,但实际上他可能是 1.00001。整数部分被去掉之后就变成了 0.00001,就会变成最暗的部分。
遮罩纹理
Shader "MyShader/Texture/Mask Texture"
{
Properties
{
_Spacular("Spacular",Color) = (1,1,1,1)
_Gloss("Gloss",Range(8.0,256)) = 20
_MainTex("Main Tex",2D) = "white"{}
_TexColor("TextureColor",Color) = (1,1,1,1)
_BumpTex("NormalTexture",2D) = "bump"{}
_BumpScale("BumpScale",float)=0.8
_MaskTex("MaskTexture",2D) = "white"{}
_R_MaskScale("MaskScale",float) = 1
_G_MaskScale("MaskScale",float) = 1
_B_MaskScale("MaskScale",float) = 1
}
SubShader
{
Tags { "LightMode"="ForwardBase" }
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "UnityShaderVariables.cginc"
#include "Lighting.cginc"
fixed4 _DiffuseColor;
fixed4 _Spacular;
float _Gloss;
sampler2D _MainTex;
float4 _MainTex_ST; //这里因为各种贴图太多了,所以就直接用一个 ST 来存储所有的缩放和位移。
fixed4 _TexColor;
sampler2D _BumpTex;
float4 _BumpTex_ST; // 法线贴图还是单独存吧。。
float _BumpScale;
sampler2D _MaskTex;
float _R_MaskScale;
float _G_MaskScale;
float _B_MaskScale;
struct appdata
{
float4 vertex : POSITION;
fixed3 normal : NORMAL;
float4 tangent:TANGENT;
float2 uv:TEXCOORD0;
};
struct v2f
{
float4 vertex : SV_POSITION;
float3 tangentSpaceLightDir : TEXCOORD0; // 切线空间下光照的方向
float3 tangentSpaceViewDir : TEXCOORD1; // 切线空间下的观察方向
float4 uv:TEXCOORD2;
};
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex);
// 设置 uv 信息
o.uv.xy = v.uv.xy * _MainTex_ST.xy + _MainTex_ST.zw; // 用 xy 控制主贴图和遮罩贴图的缩放和偏移
o.uv.zw = v.uv.xy * _BumpTex_ST.xy + _BumpTex_ST.zw; // 用 zw 控制法线贴图的缩放和偏移
// 设置模型空间转切线空间的矩阵
float3 y_axis = cross(normalize(v.normal),normalize(v.tangent))*v.tangent.w;
float3x3 modle2tangent = float3x3(v.tangent.xyz,y_axis,v.normal);
// 设置切线空间的光照方向
o.tangentSpaceLightDir = mul(modle2tangent, ObjSpaceLightDir(v.vertex)).xyz;
// 设置切线空间的观察方向
o.tangentSpaceViewDir = mul(modle2tangent,ObjSpaceViewDir(v.vertex)).xyz;
return o;
}
fixed4 frag (v2f i) : SV_Target
{
float4 packedNormal = tex2D(_BumpTex,i.uv.zw);
float3 tangentNormal = UnpackNormal(packedNormal);
tangentNormal *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy,tangentNormal.xy)));
tangentNormal = normalize(tangentNormal);
float3 tangentSpaceLightDir = normalize(i.tangentSpaceLightDir);
float3 tangentSpaceViewDir = normalize(i.tangentSpaceViewDir);
// 计算遮罩,因为这张图的三个通道都是一样的,所以其实只用到了一个。
float r_mask = tex2D(_MaskTex,i.uv.xy).r*_R_MaskScale;
float g_mask = tex2D(_MaskTex,i.uv.xy).g*_G_MaskScale;
float b_mask = tex2D(_MaskTex,i.uv.xy).b*_B_MaskScale;
// 纹理采样
fixed3 albedo = tex2D(_MainTex,i.uv.xy) * _TexColor; // 漫反射系数
// 环境光
fixed3 ambientColor = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo; // 环境光也要 * 贴图的颜色
// 漫反射光
fixed3 lightColor = _LightColor0.rgb;
fixed3 diffuseColor = albedo * lightColor * max(0,(dot(tangentNormal,tangentSpaceLightDir)));
// 高光反射
float3 half_normal = normalize(tangentSpaceViewDir.xyz + tangentSpaceLightDir.xyz);
fixed3 spacularColor = _Spacular * lightColor * pow(max(0,dot(tangentNormal,half_normal)),_Gloss)*r_mask;
fixed3 color = ambientColor + diffuseColor + spacularColor;
return fixed4(color,1);
}
ENDCG
}
}
}
可以看到在遮罩纹理黑色地方,是没有高光的,如果把遮罩用在漫反射上就会出现这种效果。
书上说遮罩纹理一般会在 RGBA 4 个通道保存不同的属性,比如在 《DOTA 2》中,每个模型有 4 个贴图,一个是模型本身的颜色,一个是法线贴图,另外两个,也就是八个通道,分别用来存储高光反射强度,边缘光照强度,高光反射指数,自发光强度等。
还了解到遮罩纹理可以用制作 “土、草、石” 效果,也就是最里面是土,然后是草,草这一层用遮罩纹理挖去几个洞,就能露出里面的土,最外面是石头,同样的,挖去几个洞,就能漏出里面的土和草。(但这种方式我还不知道怎么搞 QAQ),下面是假装做一个墙纸破掉漏出里面的墙的样子,一定是没有合适的遮罩纹理,所以才看起来不像!
// 纹理采样
fixed3 albedo = tex2D(_MainTex,i.uv.xy) * _TexColor; // 漫反射系数
if(r_mask < 0.3)
{
albedo = tex2D(_BiTexture,i.uv.xy);
}