法线贴图,对主纹理凹凸显示
建模原理
法线贴图:切线空间,存储xy切线,映射法线,法线信息存储在切线空间中。
模型是否凹凸,是由模型顶点决定的,现在实现的法线贴图,控制凹凸,实际上是配合
光照实现的,凹进去的部分,颜色偏暗,突出来的部分,颜色偏亮。
导入法线贴图

贴图类型转换为Normal Map,法线纹理类型。
法线贴图计算
法线贴图:存储有与法线垂直的切线信息,切线信息存储在切线空间中,使用内部
值(x切线,y切线)时,需要将法线转换到世界空间中,再进行光照运算才能得到
正确的结果。调整凹凸深度的参数:用户可配置,可以控制法线长短。
Shader实现
加载两张纹理:主纹理,光照法线纹理(切线空间存储数据)
顶点着色器:
主纹理的UV偏移计算
法线纹理的UV偏移计算
计算切线空间到世界空间的转换矩阵(可变),用于变换光照法线
片元着色器
解压主纹理
解压法线纹理,根据切线信息,转换光照法线信息,将光照法线从切线空间,
转到世界空间
拿法线纹理算出的光照法线,做光照运算。
相关实现代码示例如下所示:
cs
// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "CreateTest/PhongNormalTexture"
{
Properties
{
//用于显示材质纹理
_MainTex("主纹理",2D)="white"{}
//用于和主纹理混色
_Color("混色",Color)=(1,1,1,1)
//法线纹理
_BumpTex("法线纹理",2D)="bump"{}
//法线深度系数,可以控制法线高度
_BumpScale("法线深度系数",Float) = 1
_SpecularColor("高光反射材质颜色",Color) = (1,1,1,1)
_Gloss("光晕系数",Range(8,256)) = 10
}
SubShader
{
Tags{"LightMode" = "ForwardBase"}
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
//导入主纹理数据
sampler2D _MainTex;
float4 _MainTex_ST;
fixed4 _Color;
//导入法线信息
sampler2D _BumpTex;
float4 _BumpTex_ST;
float _BumpScale;
//导入高光信息
fixed4 _SpecularColor;
float _Gloss;
//从CPU过来的数据
struct c2v
{
float4 vertex:POSITION;//从CPU传递过来的模型空间下,需要渲染的点
float4 texcoord : TEXCOORD0; //因为两张贴图的像素,除颜色外完全重叠,所以纹理坐标点可以通用
float4 tangent:TANGENT; //光照法线纹理,因为要计算切线空间下的信息,所以需要渲染点的切线信息
float3 normal:NORMAL;
};
struct v2f
{
float4 pos:SV_POSITION; //模型空间到裁剪空间转换后的点
float4 uv:TEXCOORD1; //因为要算出两张纹理的uv坐标,所以做一个float4,xy存储主纹理UV,zw存储法线纹理UV
float4 MatrixRowOne:TEXCOORD2; //用于传递从顶点着色器计算好的转换矩阵
float4 MatrixRowTwo:TEXCOORD3;
float4 MatrixRowThree:TEXCOORD4;
};
v2f vert(c2v data)
{
v2f r;
r.pos = UnityObjectToClipPos(data.vertex);
//两张纹理的缩放和偏移可能不同,所以分别计算uv偏移信息,存储在v2f.uv
r.uv.xy = data.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
r.uv.zw = data.texcoord.xy * _BumpTex_ST.xy + _BumpTex_ST.zw;
//CPU传递过来的切线存储在模型空间下
//法线纹理中的,光照法线推算信息,是存储在切线空间下的
//CPU传递过来的切线信息与法线纹理中的切线信息,有转换关系,所以可以推算出一个转换矩阵用于切换法线
//计算出来的转换矩阵应该是从(切线空间到模型空间)的转换
//但是我们需要的是计算(从切线空间到世界空间)的转换矩阵(因为最终的光照运算,需要在世界空间中完成)
//所以应该先把CPU传递过来的切线信息,转换到世界空间下
//再计算切线的转换矩阵,这时就能得到从(切线空间,到世界空间)的转换矩阵
//拥有了转换矩阵,就可以将法线纹理中,求解的法线信息,从切线空间,转换到世界空间下,进而可以计算光照
//世界坐标系下的点的位置(片元着色器计算光照需求)
float4 worldPos = mul(unity_ObjectToWorld, data.vertex);
//世界空间下渲染点的法线信息
float3 worldNormal = mul((float3x3)unity_ObjectToWorld, data.normal);
//世界空间下切线的方向向量
float3 worldTangent = mul((float3x3)unity_ObjectToWorld, data.tangent.xyz);
//世界空间下计算与切线和法线垂直的线的方向向量(用于计算转换矩阵)
float3 worldBinormal=cross(worldNormal, worldTangent)* data.tangent.w;
//需要将转换矩阵,传递给片元着色器,用于转换切线空间下的法线到世界空间中
r.MatrixRowOne = float4(worldTangent.x, worldBinormal.x, worldNormal.x, worldPos.x);
r.MatrixRowTwo = float4(worldTangent.y, worldBinormal.y, worldNormal.y, worldPos.y);
r.MatrixRowThree = float4(worldTangent.z, worldBinormal.z, worldNormal.z, worldPos.z);
return r;
}
fixed4 frag(v2f data) :SV_Target
{
//世界空间下的点
float3 worldPos = float3(data.MatrixRowOne.w,data.MatrixRowTwo.w,data.MatrixRowThree.w);
//计算法线纹理中法线信息(重点),解出的法线在切线空间
//解法线前,需要先对法线纹理贴图进行采样
fixed3 bump = UnpackNormal(tex2D(_BumpTex, data.uv.zw));
//通过缩放值,影响凹凸感
bump.xy *= _BumpScale;
//计算法线高度(数学公式)
//法线还没有转换空间,所以计算出的法线,还在切线空间下
bump.z = sqrt(1 - max(0, dot(bump.xy, bump.xy)));
//通过顶点着色器传递过来的转换矩阵,转换法线,从切线空间到世界空间
bump = float3(dot(data.MatrixRowOne.xyz, bump), dot(data.MatrixRowTwo.xyz, bump), dot(data.MatrixRowThree.xyz, bump));
//解主纹理
fixed4 texColor = tex2D(_MainTex, data.uv.xy) * _Color;
//计算漫发射光照
fixed3 diffuse = _LightColor0.rgb * texColor.rgb * max(0, dot(normalize(bump), normalize(_WorldSpaceLightPos0.xyz)));
//计算高光反射光照
fixed3 viewDir = normalize(_WorldSpaceCameraPos.xyz - worldPos);
fixed3 refDir = normalize(reflect(-_WorldSpaceLightPos0.xyz, normalize(bump)));
fixed3 specular = _LightColor0.rgb * _SpecularColor.rgb * pow(max(0, dot(viewDir, refDir)), _Gloss);
//Phong光照运算
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.xyz * texColor.rgb + diffuse + specular;
return fixed4(color, 1);
}
ENDCG
}
}
Fallback "Diffuse"
}
其实现效果如下图:

左侧为Standard的shader效果,右侧为上面代码下的shader效果,右侧较左边多了高光反射的相应数据,考虑到砖墙等一些粗糙表面现实情况下不会有如此明显的高光,应用此shader文件的同学可以将Phong光照模型的公式中的高光反射部分去除,在其他需要高光反射的情况下再进行视情况添加即可,法线贴图下第一个属性值为法线深度系数(数值0.6处,由于Shader文件未设置成UTF-8导致的中文乱码,读者可自行设置Shader文件的格式或采用英文命名),可通过调节系数对应加强法线纹理效果。

以下内容作者提供一个网址,方便读者下载Amplify Shader Editor,可找到该插件的网址如下:
amplify_shader_pack unity3D_游戏3d模型 免费下载 - 爱给网
插件中提供了很多较高质量动态Shader,有需要的可自行下载。
例:



导入Unity包后即可使用该可视化Shader编辑器,使用的Unity包例:

使用编辑器的过程:


若两边窗口未展开,点击左右上角的银灰色方框即可展开,部分使用示例如下:
更改Shader名称:

添加并编辑某一属性:(以Texture Sample [T]为例)

实现效果如图:


左侧黄色按钮,黄色为未保存,单击使其变为绿色,则成功保存期间的设置。
剩余属性可自行探索。
实现上面代码的功能对应在可视化编辑器中的操作结果如下所示:

其在检查器窗口的情况如下:

该系列专栏为网课课程笔记,仅用于学习参考。