计算法线贴图
Contents
法线贴图的计算方法
- 在切线空间下进行光照计算,需要把光照方向、视角方向变换到切线空间下参与计算
- 在世界空间下进行光照计算,需要把法线方向变换到世界空间下参与计算
各自的优缺点-性能
在切线空间中计算,效率更高,因为可以在顶点着色器中就完成对光照、视角方向的矩 阵变换,计算量相对较小。( 矩阵变换在顶点着色器中计算)
在世界空间中计算,效率较低,由于需要对法线贴图进行采样,所以变换过程必须在片 元着色器中实现,我们需要在片元着色器中对法线进行矩阵变换。( 矩阵变换在片元着色器中计算)
各自的优缺点-效果
在切线空间中计算,对全局效果的表现可能会不够准确(在处理一些列如镜面反射、环境映射效果时表现效果可能不够准确)
在世界空间中计算,对全局效果的表现更准确(可以更容易的应用于全局效果的计算)
选择
若没有全局效果要求,我们优先使用在切线空间下进行光照计算,因为它效率较高
反之,我们选择在世界空间下计算
在切线空间下计算
在切线空间下进行光照计算,需要把光照方向、视角方向变换到切线空间下参与计算
关键点:
计算模型空间到切线空间的变换矩阵
计算方式:
由于变换矩阵进行变换为矢量而非点的变换,因此可以变为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
}
}
}在世界空间下计算
在世界空间下进行光照计算,需要把法线方向变换到世界空间下参与计算
关键点:
计算切线空间到世界空间的变换矩阵
计算方式:
$$ \begin{bmatrix} |& |& |\\ Xs& Ys& Zs\\ |& |&| \end{bmatrix} $$而x、y、z轴分别为切线空间中顶点的切线、副切线、法线
我们只需要得到3个轴相对于世界空间的向量表达,即可得到该变换矩阵
需要使用的内置函数
//法线从模型空间到世界空间:
UnityObjectToWorldNormal(模型空间法线数据);
//切线从模型空间到世界空间:
UnityObjectToWorldDir(模型空间切线数据)实现
Shader "Unlit/WNormalMapLearn"
{
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 noraml:NORMAL;
float3 wpos:TEXCOORD1;
float3x3 rotation: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);
data.noraml = UnityObjectToWorldNormal(v.normal);
data.wpos = mul(unity_ObjectToWorld,v.vertex);
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 wtangent = UnityObjectToWorldDir(v.tangent);
float3 wbinormal = cross(normalize(wtangent),normalize(data.noraml)) * v.tangent.w;
data.rotation = float3x3( wtangent.x,wbinormal.x,data.noraml.x,
wtangent.y,wbinormal.y,data.noraml.y,
wtangent.z,wbinormal.z,data.noraml.z );
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)));
//变换到 世界坐标系下
float3 normalDir = mul(i.rotation,tangentNormal);
//UV
fixed3 uvColor = tex2D(_MainTex,i.uv.xy);
uvColor *= _MainColor.rgb;//乘漫反射颜色
//Lambert
float3 lightDir = normalize(_WorldSpaceLightPos0);
fixed3 LambertColor = _LightColor0 * uvColor * max(0,dot(normalDir,lightDir));
//BlinnPhong高光
float3 viewDir = normalize(_WorldSpaceCameraPos - i.wpos);
float3 halfA = normalize(lightDir+viewDir);
fixed3 specularColor = _LightColor0 *_SpecularColor * pow(max(0,dot(normalDir,halfA)),_SpecNum);
//BlinnPhong光照模型
fixed3 blinnPhongColor = UNITY_LIGHTMODEL_AMBIENT * uvColor + LambertColor + specularColor;
return fixed4(blinnPhongColor,1);
}
ENDCG
}
}
}凹凸度系数计算
如果直接将法线*凹凸度系数
当凹凸系数趋近于0时,会影响光照模型的计算
所以只让其法线中的xy乘以凹凸系数
$$ z=\sqrt{1-(x^2+y^2)} $$