以下过程均为自己实践的过程,不能保证过程及结论的正确性。如果哪里有错误,还希望大家批评指正。
最近在学习shader,当学到法线贴图时,遇到了让我疑惑不解的地方。法线贴图有两种,一种是模型空间的贴图,也就是贴图中的法线信息是在模型空间下的,第二种是切线空间的贴图,也就是贴图中的法线信息是在切线空间下的,由于后一种要比前一种好用(绝大多数法线贴图都是这种,具体原因不在赘述),所以后面用的皆为切线空间中的法线贴图。在计算法线贴图时,有两种方式,一种是将光源方向、视线方向、法线(实际上法线不用转换,因为本来就是切线空间中的)转换到切线空间中,然后计算;第二种方式是将以上信息转换到世界空间中计算。然而为什么在切线空间的计算方式下,将光源向量与视线向量转换到切线空间,可以在Vertex Shader阶段完成;而在世界空间的计算方式下,将光源向量与视线向量转换到世界空间,必须要在Fragment Shader中完成,假如两者计算的位置颠倒,那么又会有什么样的变化,这个问题思考了很久,决定弄清楚再继续接下来的学习。
为了对比这四者之间的差异,我先把这四种Shader都写出来,然后对比一下效果。
限定条件:单一光源,方向光
首先是在切线空间下,在VertexShader中计算:
// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)'
Shader "Unlit/BlinnSpecular"
{
Properties{
m_mainTex("MainTex",2D)=""{}
m_normalMap("NormalMap",2D)=""{}
m_normalMapScale("NormalMapScale",Range(-10,10))=1
m_specular("Specular",Color)=(1,1,1,1)
m_gloss("Gloss",Range(-10,10))=1
}
SubShader{
Pass{
Tags{"LightMode"="ForwardBase"}
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
sampler2D m_mainTex; //主贴图
float4 m_mainTex_ST; //主贴图缩放偏移量
sampler2D m_normalMap; //法线贴图
float m_normalMapScale; //凹凸程度
fixed3 m_specular; //反光的颜色
float m_gloss; //反光强度
struct v2f{
float4 pos:SV_POSITION; //MVP变换后的顶点坐标
float2 uv:TEXCOORD1; //纹理采样
float3 vertex:TEXCOORD2; //顶点坐标
float3 tangentLight:TEXCOORD3; //切线空间下的光源方向
float3 tangentView:TEXCOORD4; //切线空间下的视线方向
};
v2f vert(appdata_full v){
v2f o;
o.pos = UnityObjectToClipPos(v.vertex);
o.uv = v.texcoord.xy*m_mainTex_ST.xy+m_mainTex_ST.zw;
TANGENT_SPACE_ROTATION; //生成将模型空间转为切线空间的矩阵
o.tangentLight = mul(rotation,(ObjSpaceLightDir(v.vertex))).xyz;
o.tangentView = mul(rotation,(ObjSpaceViewDir(v.vertex))).xyz;
return o;
}
fixed4 frag(v2f i):SV_TARGET0{
fixed3 tangentLight = normalize(i.tangentLight);
fixed3 tangentView = normalize(i.tangentView);