法线贴图可以让低模看起来像是高模一样,具体的实现原理,是改变了光照,呈现出凹凸度:
下图中为法线贴图
根据光照模型来进行更改:
首先我们需要声明法线贴图纹理和法线的凹凸度:
_BumpMap("bumpMap",2D) = "bump"{}
_BumpScale("BumpScale",Float) = 1.0
sampler2D _BumpMap;
float4 _BumpMap_ST;
float _BumpScale;
输入我们需要添加一个切线和法线:
struct Input
{
float4 Position : POSITION0;
float2 Texcoord : TEXCOORD0;
float3 Normal : NORMAL;
float4 tangent : TANGENT;
};
在顶点着色器中:我们需要将uv坐标存一下,传到片元阶段:
......
//uv用的float4类型的
float4 uv : TEXCOORD0;
......
//xy通道存储主纹理,zw通道存储法线贴图
o.uv.xy = TRANSFORM_TEX(i.Texcoord, _MainTex);
o.uv.zw = TRANSFORM_TEX(i.Texcoord, _BumpMap);
还有一个问题:
法线是需要转换到TBN坐标系(切线空间)进行计算的(其实将其全部转换到世界坐标是一样的,只要在同一坐标系下,没试过)。
TBN坐标系是:切线,副法线,法线,我们有了法线和副法线,那么切线就可以利用叉乘简单算出来了:worldToTangent :切线空间转换矩阵
//求出世界空间下的法线向量
fixed3 worldNormal = UnityObjectToWorldNormal(v.normal);
//求出世界空间下的切线向量
fixed3 worldTangent = UnityObjectToWorldDir(v.tangent.xyz);
//副法线向量由法线和切线的叉乘得到,切线的w分量决定副法线的方向性
fixed3 worldBinormal = cross(worldNormal, worldTangent) * (v.tangent.w*unity_WorldTransformParams.w);
// 切线空间转换矩阵
float3x3 worldToTangent = float3x3(worldTangent, worldBinormal, worldNormal);
//调用
//将其转换到切线空间
o.LightDir = mul(worldToTangent, o.LightDir);
o.ViewDir = mul(worldToTangent, o.ViewDir);
但unity给了我们一个宏定义:,需要: #include “UnityCG.cginc”
TANGENT_SPACE_ROTATION;
该宏定义之后,会有一个rotation值,利用这个rotation值进行与光线和视线进行相乘就可以得到切线空间下的光线方向和视线方向了:
o.lightDir = mul(rotation, ObjSpaceLightDir(i.Position)).xyz;
o.viewDir = mul(rotation, ObjSpaceViewDir(i.Position)).xyz;
不过有一个问题:
下面是这个宏定义的等价代码:
也就是说,我们输入的定义对象必须是v,而且在Input结构体里面的对象也必须是normal和tangent,否则报错,我们可以直接复制下面代码,根据需求进行修改:
float3 binormal = cross( v.normal, v.tangent.xyz ) * v.tangent.w;
float3x3 rotation = float3x3( v.tangent.xyz, binormal, v.normal );
然后就是像素(片元)阶段:
我们需要做的是对法线贴图进行采样:法线uv存储的是zw通道
fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw);
然后我们根据UnpackNormal方法,将其对应的法线纹理需要设置为Normal map的格式。
==UnpackNormal()==函数在UnityCG.cginc中,它是对法线纹理的采样结果的一个反映射操作,其对应的法线纹理需要设置为Normal map的格式
_BumpMap法线贴图,tangentNormal转换为了Normal Map格式
法线是一个单位向量,也就是它的长度是1,所以只需要知道x,y的数值,是可以计算得到z的数值的,z=1-(x+y)的平方
为什么需要转换成NormalMap模式呢?
这样做可以让unity根据不同平台纹理进行压缩,再通过UnpackNormal函数针对不同的压缩格式对法线纹理进行正确的采样
fixed3 tangentNormal;
tangentNormal = UnpackNormal(packedNormal);
tangentNormal.xy *= _BumpScale;
//z=1-(x+y)的平方
tangentNormal.z = sqrt(1 - tangentNormal.x*tangentNormal.x - tangentNormal.y * tangentNormal.y);
然后将tangentNormal 替换掉光照模型里面的法线,将自发光+镜面反射+漫反射+环境光得到的就是添加了法线贴图的模型了:
纹理遮罩
对于图形编程来说,大部分信息都是图片纹理获取的,而纹理遮罩使用也是如此,
如下代码:遮罩纹理当前像素信息里面r存的是对高光的遮罩
在实际应用中,我们会充分利用遮罩纹理中的每一个颜色通道进行存储不同的表面属性
需要在原先法线的shader上面添加相对应的遮罩代码,下面代码只是遮罩了高光部分的
....
_SpecularMask("SpecularMask",2D) = "white"{}
_SpecularScale("SpecularScale",Float) = 1.0
....
//声明:
//遮罩纹理
sampler2D _SpecularMask;
//遮罩影响的系数大小
float _SpecularScale;
....
//使用就直接对纹理进行采样*系数
fixed3 spmask = tex2D(_SpecularMask, i.uv.xy).r*_SpecularScale;
....
//比如说,我的遮罩纹理当前像素信息里面r存的是对高光的遮罩:
float3 Specul = _LightColor0.xyz* _Specular.xyz* pow(saturate(dot(tangentNormal, halfdiff)), _Gloss);
Specul = Specul * spmask;
....
完整的shader代码:
可以对比一下光照模型有什么改动和区别:效果一样
Properties
{
_Color("Color Tint",Color) = (1,1,1,1)
_MainTex("MainTex",2D) = "white"{}
_BumpMap("bumpMap",2D) = "bump"{}
_BumpScale("BumpScale",Float) = 1.0
_Specular("Specular",Color) = (1,1,1,1)
_Gloss("Gloss",Range(8.0,256)) = 20
_SpecularMask("SpecularMask",2D) = "white"{}
_SpecularScale("SpecularScale",Float) = 1.0
}
SubShader
{
Tags{ "RenderType" = "Opaque" "LightMode" = "ForwardBase"}
LOD 100
pass {
//cg语言开始编写
CGPROGRAM
//在逻辑中申明顶点和片段://编译申明函数
//顶点程序
#pragma vertex vert
//片段程序
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _BumpMap;
float4 _BumpMap_ST;
sampler2D _SpecularMask;
float _SpecularScale;
float4 _Color;
float _BumpScale;
float4 _Specular;
float _Gloss;
struct v2f {
float4 pos : POSITION0;
float4 uv : TEXCOORD0;
float3 lightDir: TEXCOORD1;
float3 viewDir:TEXCOORD4;
};
struct Input
{
float4 Position : POSITION0;
float2 Texcoord : TEXCOORD0;
float3 Normal : NORMAL;
float4 tangent : TANGENT;
};
v2f vert(Input i)
{
v2f o;
o.pos = UnityObjectToClipPos(i.Position);
o.uv.xy = TRANSFORM_TEX(i.Texcoord, _MainTex);
o.uv.zw = TRANSFORM_TEX(i.Texcoord, _BumpMap);
float3 binormal = cross(i.Normal, i.tangent.xyz) * i.tangent.w;
float3x3 rotation = float3x3(i.tangent.xyz, binormal, i.Normal);
o.lightDir = mul(rotation, ObjSpaceLightDir(i.Position)).xyz;
o.viewDir = mul(rotation, ObjSpaceViewDir(i.Position)).xyz;
return o;
}
float4 frag(v2f i) :COLOR
{
float3 L = normalize(i.lightDir);
//float3 N = normalize(i.Normal);
float3 viewdir = normalize(i.viewDir); //视线
fixed4 packedNormal = tex2D(_BumpMap, i.uv.zw);
fixed3 tangentNormal;
tangentNormal = UnpackNormal(packedNormal);
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1 - tangentNormal.x*tangentNormal.x - tangentNormal.y * tangentNormal.y);
fixed3 albedo = tex2D(_MainTex, i.uv.xy).xyz*_Color.xyz;
fixed3 spmask = tex2D(_SpecularMask, i.uv.xy).r*_SpecularScale;
//漫反射=漫反射颜色 * 漫反射系数 * max(N,L); saturate:这样是为了约束平滑到0-1之间
float3 diff = _LightColor0.xyz* saturate(dot(L, tangentNormal))*albedo;
//计算光线和法线相加得到的向量,单位化,再与法线夹角进行对比,入射光线离法线越近,夹角越大
float3 halfdiff = normalize(L + tangentNormal);
//环境光=环境光颜色 * 环境光系数
float3 Ambiel = UNITY_LIGHTMODEL_AMBIENT.rgb*albedo;
//反射光线计算=两倍的水平投影减去入射光线
//float3 refl = normalize(reflect(-L,tangentNormal));
//高光
float3 Specul = _LightColor0.xyz* _Specular.xyz* pow(saturate(dot(tangentNormal, halfdiff)), _Gloss);
Specul = Specul * spmask;
//光照模型:自发光+镜面反射+漫反射+环境光
fixed4 col = fixed4(Ambiel + diff + Specul,1);
return col;
}
ENDCG
}
}
计算高光时,将反射光线refl改成了halfdiff,可以试一下改回来有什么区别。也可以去掉spmask看看有什么区别@.@
效果:
就酱,如果有错误,请提出来,我也是刚学没多久,233
最后求一波三连QAQ