法线贴图的计算方法
- 在切线空间下进行光照计算,需要把光照方向、视角方向变换到切线空间下参与计算
- 在世界空间下进行光照计算,需要把法线方向变换到世界空间下参与计算
各自的优缺点-性能
在切线空间中计算,效率更高,因为可以在顶点着色器中就完成对光照、视角方向的矩 阵变换,计算量相对较小。( 矩阵变换在顶点着色器中计算)
在世界空间中计算,效率较低,由于需要对法线贴图进行采样,所以变换过程必须在片 元着色器中实现,我们需要在片元着色器中对法线进行矩阵变换。( 矩阵变换在片元着色器中计算)
各自的优缺点-效果
在切线空间中计算,对全局效果的表现可能会不够准确(在处理一些列如镜面反射、环境映射效果时表现效果可能不够准确)
在世界空间中计算,对全局效果的表现更准确(可以更容易的应用于全局效果的计算)
选择
若没有全局效果要求,我们优先使用在切线空间下进行光照计算,因为它效率较高
反之,我们选择在世界空间下计算
在切线空间下计算
在切线空间下进行光照计算,需要把光照方向、视角方向变换到切线空间下参与计算
关键点:
计算模型空间到切线空间的变换矩阵
计算方式:
由于变换矩阵进行变换为矢量而非点的变换,因此可以变为3x3矩阵。
变换矩阵为子到父的逆矩阵
$$
\begin{bmatrix}
|& |& |\\
Xs& Ys& Zs\\
|& |&|
\end{bmatrix}
$$
x、y、z轴分别为切线空间中顶点的切线、副切线、法线
切线和法线 可以从模型空间中获取
副切线为 切线和法线叉乘的结果
而3个轴为相互垂直的单位向量,因此可以推出 该矩阵为 正交矩阵
$$
\begin{bmatrix}
-& Xs& -\\
-& Ys& -\\
-& Zs&-
\end{bmatrix}
$$
他就是 模型空间到切线空间的变换矩阵
需要使用的内置函数
//得到模型空间光的方向
ObjSpaceLightDir(模型空间顶点坐标);
//得到模型空间视角方向
ObjSpaceViewDir(模型空间顶点坐标);
实现
Shader "Unlit/NormalMapLearn"
{
Properties
{
//主贴图
_MainTex("MainTex",2D) = ""{}
//法线
_BumpMap("BumpMap",2D) = ""{}
_BumpScale("BumpScale",Range(0,1)) = 1
//漫反射
_MainColor("MainColor",Color) = (1,1,1,1)
//高光反射
_SpecularColor("SpecularColor",Color) = (1,1,1,1)
_SpecNum("SpecNum",Range(0.1,20)) = 10
}
SubShader
{
Pass
{
Tags { "LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct v2f
{
float4 pos:SV_POSITION;
float4 uv:TEXCOORD0;
float3 lightDir:TEXCOORD1;//切线空间光照向量
float3 viewDir:TEXCOORD2;//切线空间视口向量
};
sampler2D _MainTex;//uv贴图
float4 _MainTex_ST;
sampler2D _BumpMap;//法线贴图
float4 _BumpMap_ST;
float _BumpScale;//凹凸度
fixed4 _MainColor;//漫反射颜色
fixed4 _SpecularColor;//高光反射颜色
float _SpecNum;//光泽度
v2f vert (appdata_full v)
{
v2f data;
//将模型坐标系下的顶点坐标 转换到裁剪空间下
data.pos = UnityObjectToClipPos(v.vertex);
//设置UV坐标(xy存贴图,zw存法线)
data.uv.xy = v.texcoord.xy * _MainTex_ST.xy + _MainTex_ST.zw;
data.uv.zw = v.texcoord.xy * _BumpMap_ST.xy + _BumpMap_ST.zw;
//计算模型空间到切线空间的变换矩阵
float3 binormal = cross(normalize(v.tangent),normalize(v.normal)) * v.tangent.w;//计算副切线向量
float3x3 rotation = {v.tangent.xyz , binormal, v.normal};
//计算切线空间下灯光和视角向量
data.lightDir = mul(rotation,ObjSpaceLightDir(v.vertex));
data.viewDir = mul(rotation,ObjSpaceLightDir(v.vertex));
return data;
}
fixed4 frag (v2f i) : SV_Target
{
//法线贴图取样
float4 packedNormal = tex2D(_BumpMap,i.uv.zw);
float3 tangentNormal = UnpackNormal(packedNormal);//切线空间下的法线信息
//凹凸度计算
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0 - saturate(dot(tangentNormal.xy,tangentNormal.xy)));
//计算光照(切线空间下)
//Lambert漫反射
fixed3 uvColor = tex2D(_MainTex,i.uv.xy) * _MainColor;//uv取样 乘漫反射颜色
fixed3 lambertColor = _LightColor0.rgb * uvColor * max(0,dot(tangentNormal,normalize(i.lightDir)));
//Blinn-Phong高光
float3 halfA = normalize(i.lightDir + i.viewDir);
fixed3 specularColor =_LightColor0.rgb * _SpecularColor.rgb * pow(max(0,dot(tangentNormal,halfA)),_SpecNum);
//Blinn-Phong光照模型
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * uvColor + lambertColor + specularColor;
return fixed4(color,1);
}
ENDCG
}
}
}
在世界空间下计算
在世界空间下进行光照计算,需要把法线方向变换到世界空间下参与计算