纹理映射
纹理映射坐标定义了该顶点在纹理中对应的2D坐标,通常用一个二维变量(u,v)表示,顶点UV坐标通常都被归一化到[0,1)范围内
在OpenGL里,纹理空间的原点位于左下角, 而在DirectX中,原点位于左上角
效果图
【单张纹理】
【凹凸映射】
Bump Scale = 5
Bump Scale = -5
【渐变纹理】
【遮罩纹理】
Specular Scale = 2 VS Specular Scale = 0
1 单张纹理
在Blin-Phong的基础上加单张纹理
【代码解析】
Properties{
_Color(“Color Tint”, Color) = (1, 1, 1, 1)
_MainTex(“Main Tex”, 2D) = “white” {} // 纹理属性
_Specular(“Specular”, Color) = (1, 1, 1, 1)
_Gloss(“Gloss”,Range(8.0, 256)) = 20 }
2D 是纹理属性声明方式
fixed4 _Color;
sampler2D _MainTex;
float4 _MainTex_ST; // 加了个_MainTex_ST
fixed4 _Specular;
float _Gloss;
CG 代码片中声明和上述属性类型相匹配的变量,以便和材质面板中的属性建立联系
MainTex_ST声明该纹理的 _MainTex_ST.xy 缩放值和MainTex_ST.zw偏移值
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; // 顶点的纹理坐标
//以便在FS中使用该坐标进行纹理采样。
};
VS
o.uv = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
使用纹理的属性值_MainTex_ST 来对顶点纹理坐标进行变换,得到最终的纹理坐标
FS
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo;
fixed3 diffuse = _LightColor0.rgb* albedo * max(0,dot(worldNormal, worldLightDir));
使用tex2D 函数对纹理进行采样,的一个参数是被采样的纹理,第二个参数是一个 float2 类型的纹理坐标,返回得到的纹素值
使用 albedo 来计算环境光和漫反射光照
左repeat,右clamp
Point模式使用了最近邻(nearest neighbor)滤波
Bilinear滤波则使用了 线性滤波, 对于每个目标像素找到4个邻近像素, 然后对它们进行线性插值混合后得到最终像素
三线性插值基于mipmap,找到8个临近像素,会多占用33%的内存空间
尽量使用压缩格式的纹理
2 凹凸映射 bump mapping
【知识点】
使用一张纹理来修改模型表面的法线,以便为模型提供更多的细节,不会真的改变模型的顶点位置,可以从模型的轮廓处看出“破绽”。
两种主要的方法进行凹凸映射: 高度纹理 (height map) + 法线纹理 (normal map) ;
法线纹理直接存储表面法线。凹凸映射和法线映射当成是相同的技术
非常直观,在实时计算时不能直接得到表面法线,而是需要由像素的灰度值计算而得,法线方向的范围为[-1, l], 而像素的分
量的范围为 [0, 1]
pixel = (normal+1) / 2
normal = pixel × 2 - 1
切线空间下的法线纹理几乎全部是浅蓝色,因为存储了每个点在各自的切线空间中的法线扰动方向,这些蓝色实际上说明顶点的大部分法线是和模型本身法线一样 的,不需要改变
例子:基于Blinn-Phong, 使用切线空间下的法线,分别在切线空间下和世界空间下进行光照计算
在切线空间下计算
VS:存储两个纹理坐标,把光线和视角方向移到切线空间
FS:用切线空间下的法线方向进行光照计算
Properties{ _Color(“Color Tint”, Color) = (1, 1, 1, 1)
_MainTex(“Main Tex”, 2D) = “white” {} // 纹理属性
_BumpMap(“Normal Map”, 2D) = “bump” {} // 法线纹理的属性,当没有提供任何法线纹理时 "bump"就对应了模型自带法线信息
_BumpScale(“Bump Scale”, Float) = 2.0 // 控制凹凸程度的属性
_Specular(“Specular”, Color) = (1, 1, 1, 1)
_Gloss(“Gloss”,Range(8.0, 256)) = 20 }
// 和Properties 语义块中的属性建立联系
fixed4 _Color; // 材质颜色
sampler2D _MainTex; // 纹理
float4 _MainTex_ST; // 得到该纹理的属性(平铺和偏移系数)
sampler2D _BumpMap; // 法线纹理
float4 _BumpMap_ST; // 纹理属性
float _BumpScale; // 控制程度
fixed4 _Specular; // 高光
float _Gloss; // 高光的大小范围
struct a2v {
float4 vertex : POSITION;
loat3 normal : NORMAL;
float4 tangent : TANGENT; // 构建切线空间, 注意是float4类型,需要 tangent.w 决定副切线的方向性
float4 texcoord : TEXCOORD0;
};struct v2f {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0; // 单张纹理使用 float2 uv : TEXCOORD2;
float3 lightDir: TEXCOORD1; // 切线空间下的光照方向
float3 viewDir : TEXCOORD2; // 切线空间下的和视角方向
};
VS
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw; //
存储纹理映射坐标uv[0,1] // 使用同一组纹理坐标,减少插值寄存器的使用
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
TANGENT_SPACE_ROTATION; // 直接得到rotation变换矩阵
o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz; // from object space to tangent space
o.viewDir = mul(rotation, ObjSpaceViewDir(v.vertex)).xyz;
把 v2f 中的 UV 变量的类型定义为 float4 类型 其中 xy 分扯存储了_MainTex 的纹理坐标 zw 分量存 BumpMap纹理坐标
FS
采样得到切线空间下的法线方向,再在切线空间下进行光照计算即可
fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw); tangentNormal =
UnpackNormal(packedNormal); // 需要进行反映射 normal=pixel*2-1
tangentNormal.xy *= _BumpScale;
在世界空间下计算光照模型
VS:计算从切线空间到世界空间的变换矩阵,把它传递给片元着色器
FS:把法线纹理中的法线法线方向从切线空间变换到世界空间下
Properties{ _Color(“Color Tint”, Color) = (1, 1, 1, 1)
_MainTex(“Main Tex”, 2D) = “white” {}
_BumpMap(“Normal Map”, 2D) = “bump” {}
_BumpScale(“Bump Scale”, Float) = 2.0
_Specular(“Specular”, Color) = (1, 1, 1, 1)
_Gloss(“Gloss”,Range(8.0, 256)) = 20 }
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 大小的变量,所以把它拆成多个变量
float4 TtoW2 : TEXCOORD3; // 对方向矢量float3,但为了充分利用插值寄存器的存储空间,把世界空间下的顶点位置存储在W分量中。
};
VS
存储纹理映射坐标 + 切线空间到世界空间的变换矩阵
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw; //
存储纹理映射坐标uv[0,1] // 使用同一组纹理坐标,减少插值寄存器的使用
o.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
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);
FS
// Get the normal in tangent space
fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw)); // 用内置的 UnpackNormal 函数对法线纹理进行采样和解码(需要把法线纹理的格式标识成 Normal map
bump.xy *= _BumpScale;
bump.z = sqrt(1.0 - saturate(dot(bump.xy, bump.xy)));
// Transform the narmal from tangent space to world space
bump = normalize(half3(dot(i.TtoW0.xyz, bump), dot(i.TtoW1.xyz, bump), dot(i.TtoW2.xyz, bump)));
3 渐变纹理 Ramp texture
使用渐变纹理来控制漫反射光照的结果
之前我们都是使用表面法线和光照方向的点积结果与材质的反射率相乘来得到表面的漫反射光照
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 }
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;
};
VS
o.uv = TRANSFORM_TEX(v.texcoord, _RampTex);
使用内置的 TRANSFORM TEX 宏来计算经过平铺和偏移后的纹理坐标。
FS
// Use the texture to sample the diffuse color
fixed halfLambert = 0.5 * dot(worldNormal, worldLightDir) + 0.5;
fixed3 diffuseColor = tex2D(_RampTex, fixed2(halfLambert, halfLambert)).rgb * _Color.rgb;fixed3 diffuse = _LightColor0.rgb * diffuseColor;
RampTex 实际就是一个一维纹理(纵轴方向上颜色不变) 纹理坐标的方向我们都使用了 halfLambert 。然后,把从渐变纹理采到的颜色和材质颜色 Color 相乘 得到最终漫反射颜色
需要注意的是, 需要把渐变纹理的 Wrap Mode 设为 Clamp 模式,以防止对纹理进行采样由于浮点数精度而造成的问题
4 遮罩纹理 mask texture
作用:
遮罩纹理可以让美术人员更加精准(像素级别)地控模型表面各种性质。
流程:
一般是通过采样得到遮罩纹理纹素值,然后使用其中某(几)个通道 (一共有RGBA 四个通道)的值
(例如 texel.r 来与某种表面属性进行相乘,这样,当该通道的值为0时,可以保护表面不受该属性的影响
示例:在FS使用纹理遮罩,控制模型表面的高光反射强度
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
}
fixed4 _Color;
sampler2D _MainTex; // 主纹理
float4 _MainTex_ST; //定义了共同的纹理属性变量,节省插值寄存器
sampler2D _BumpMap; // 法线纹理
float _BumpScale;
sampler2D _SpecularMask; // 遮罩纹理
float _SpecularScale;
fixed4 _Specular;
float _Gloss;
为_MainTex,_BumpMap和_Spec ularMask定义了共同的纹理属性变量 MainTe _ST,在材质面板中修改主纹理的平铺系数和偏移系数会同时影响3个纹理的采样
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;
};
FS
// Get the mask value
fixed specularMask = tex2D(_SpecularMask, i.uv).r * _SpecularScale; // specularMask采样
// Compute specular term with the specular mask
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss) * specularMask; // 影响高光部分
源代码
单张纹理
Shader "Custom/SingleTextureMat"
{
Properties{
_Color("Color Tint", Color) = (1, 1, 1, 1)
_MainTex("Main 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 _MainTex;
float4 _MainTex_ST; // 加了个_MainTex_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 = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw; // 使用纹理的属性值_MainTex_ST 来对顶点纹理坐标进行变换,得到最终的纹理坐标
// Or just call the built-in function: 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));
// Use the texture to sample the diffuse color
// 使用CG的tex2D函数对纹理进行采样,第一个参数是需要被采样的纹理,第 个参数是一个 float2 类型的纹理坐标,它将返回计算得到的纹素值
// 材质反射率albedo = 纹理采样结果 * 颜色属性_Color(Phong里面的Diffuse)
fixed3 albedo = tex2D(_MainTex, i.uv).rgb * _Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz * albedo; // * albedo
fixed3 diffuse = _LightColor0.rgb * albedo * max(0, dot(worldNormal, worldLightDir)); // Phong的第二项_Diffuse.rgb改成了albedo
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"
}
凹凸映射 bump mapping
切线空间下
// 切线空间下计算光照,
// VS:存储两个纹理坐标,把光线和视角方向移到切线空间
// FS:用切线空间下的法线方向进行光照计算
Shader "Custom/C7_ NormalMapTangentSpace"
{
Properties{
_Color("Color Tint", Color) = (1, 1, 1, 1)
_MainTex("Main Tex", 2D) = "white" {} // 纹理属性
_BumpMap("Normal Map", 2D) = "bump" {} // 法线纹理的属性,当没有提供任何法线纹理时 "bump"就对应了模型自带法线信息
_BumpScale("Bump Scale", Float) = 2.0 // 控制凹凸程度的属性
_Specular("Specular", Color) = (1, 1, 1, 1)
_Gloss("Gloss", Range(8.0, 256)) = 20
}
SubShader{
Pass {
Tags { "LightMode" = "ForwardBase" } // 指明该Pass的光照模式
CGPROGRAM
#pragma vertex vert // 使用vert这个顶点着色器 和 frag这个片元着色器
#pragma fragment frag
#include "Lighting.cginc" // 内置文件,方便使用内置变量
fixed4 _Color; // 和Properties 语义块中的属性建立联系
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类型,需要 tangent.w 决定副切线的方向性
float4 texcoord : TEXCOORD0;
};
struct v2f {
float4 pos : SV_POSITION;
float4 uv : TEXCOORD0; // 单张纹理使用 float2 uv : TEXCOORD2;
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; // 存储纹理映射坐标uv[0,1] // 使用同一组纹理坐标,减少插值寄存器的使用
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
TANGENT_SPACE_ROTATION; // 直接得到rotation变换矩阵
o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)).xyz; // from object space to tangent space
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);
fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw); // 利用tex2D对法线纹理BumpMap进行采样,参数为:纹理和float2 类型的纹理坐标,返回纹素值
fixed3 tangentNormal;
// 第二张图是法线纹理,法线方向范围在[-1, l], 而像素范围为[O, 1],需要进行反映射 normal=pixel*2-1
// tangentNormal.xy = (packedNormal.xy * 2 - 1) * _BumpScale; // 先把packedNormal的xy分量映射回法线方向,然后乘以 BumpScale(控制凹凸程度)来得到 tangentNormal xy 分量
// Or mark the texture as "Normal map", and use the built-in funciton
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);
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss);
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}
模型空间下
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
// 在世界空间下计算光照模型
// VS:计算从切线空间到世界空间的变换矩阵 把它传递给片元着色器
// FS:把法线纹理中的法线法线方向从切线空间变换到世界空间下
Shader "Custom/C7_NormalMapWorldSpaceMat"
{
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
_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 大小的变量,所以把它拆成多个变量
float4 TtoW2 : TEXCOORD3; // 对方向矢量float3,但为了充分利用插值寄存器的存储空间,把世界空间下的顶点位置存储在W分量中。
};
v2f vert(a2v v) {
v2f o;
o.pos = UnityObjectToClipPos(v.vertex); // 裁剪空间
o.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw; // 存储纹理映射坐标uv[0,1] // 使用同一组纹理坐标,减少插值寄存器的使用
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;
// Compute the matrix that transform directions from tangent space to world space
// Put the world position in w component for optimization
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 {
// Get the position in world space
float3 worldPos = float3(i.TtoW0.w, i.TtoW1.w, i.TtoW2.w);
// Compute the light and view dir in world space
fixed3 lightDir = normalize(UnityWorldSpaceLightDir(worldPos));
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));
// Get the normal in tangent space
fixed3 bump = UnpackNormal(tex2D(_BumpMap, i.uv.zw)); // 用内置的 UnpackNormal 函数对法线纹理进行采样和解码(需要把法线纹理的格式标识成 Normal map
bump.xy *= _BumpScale;
bump.z = sqrt(1.0 - saturate(dot(bump.xy, bump.xy)));
// Transform the narmal from tangent space to world space
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"
}
渐变纹理 Ramp texture
Shader "Custom/Ramp Texture" {
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;
// Use the texture to sample the diffuse color
fixed halfLambert = 0.5 * dot(worldNormal, worldLightDir) + 0.5;
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"
}
遮罩纹理 mask texture
// 在FS使用纹理遮罩,控制模型表面的高光反射强度
Shader "Custom/NewSurfaceShader"
{
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; // 为_MainTex,_BumpMap和_Spec ularMask定义了共同的纹理属性变量 MainTe _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);
// Get the mask value
fixed specularMask = tex2D(_SpecularMask, i.uv).r * _SpecularScale; // specularMask采样
// Compute specular term with the specular mask
fixed3 specular = _LightColor0.rgb * _Specular.rgb * pow(max(0, dot(tangentNormal, halfDir)), _Gloss) * specularMask; // 影响高光部分
return fixed4(ambient + diffuse + specular, 1.0);
}
ENDCG
}
}
FallBack "Specular"
}