- 为啥要有切线空间和啥是切线空间
- 纹理坐标跟位置坐标是啥关系
- 切线坐标系的求法
1.为啥要有切线空间和啥是切线空间
- 为啥要有切线空间?
3D空间中不同的坐标系有不用的用处。如局部空间坐标系(模型空间坐标系)方便对3D模型进行建模,在这个空间中我们不需要考虑模型在场景中的位置、朝向等细节,可以专注于模型本身。在世界空间下我们关心的问题是场景装各个物体的位置、朝向,而不必关注相机的观察位置及其朝向。可见一个坐标系的根本用途,是让我们在处理不同的问题时,能以合适的参照系,抛开不想管的因素,从降低问题的复杂度。 - 啥是切线空间?
直白的说,模型顶点中的纹理坐标,就定义于切线空间。普通二维坐标系中包含U, V两项,其中U坐标增长的方向为切线空间的Tangent轴,V坐标增长的方向为切线空间的Bitangent轴。模型中不同的三角形都有对应的切线空间,其中Tangent轴和Bitangent轴位于三角形所在的平面上,结合三角形平面对应的法线,我们称Tangent(T),Bitangent(B), Normal(N)组成的坐标系为切线空间(TBN)
2.纹理坐标跟位置坐标是啥关系
通过切线空间可以将纹理坐标和位置坐标联系起来
一个三角形及其所在切线空间,已知这个三角形三个顶点在空间坐标下的位置为V0、V1、V2 , 以及对应的纹理坐标(u0,v0)、(u1,v1)、(u2,v2)。
定义三角形的两条边为:
E0 = V1 - V0
E1 = V2 - V0
对应的纹理坐标差值为:
(t1, b1) = (u1-u0, v1-v0)
(t2,b2) = (u2-u0, v2-v0)
我们可以用T,B的线性组合来表示E0和E1:
E0 = t1T + b1B
E1 = t2T + b2B
3. 切线坐标系的求法
根据上面得到的纹理坐标和切线空间关系可以将其表示为矩阵形式:
将E0, E1, T, B拆分成分量形式:
将
移到另一边得到:
求逆矩阵公式:
整理后得到:
切线空间包括TBN三个向量,大多数情况下,我们使用的切线空间这三个向量都是准备化且相互垂直的,因此,对于每个顶点,我们只要提供T、N两个向量即可,在运行时通过向量叉乘临时地计算B向量。
4. 法线贴图在Unity中的实际应用
Shader "NormalMap"
{
Properties
{
_MainTex ("_MainTex", 2D) = "white" {}
_NormalTex ("_NormalTex", 2D) = "white" {}
_BumpScale("Bump Scale", Float) = 1 //凹凸度系数
}
SubShader
{
Tags { "RenderType"="Opaque" }
LOD 100
Pass
{
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
// make fog work
#pragma multi_compile_fog
#include "UnityCG.cginc"
#include "Lighting.cginc"
struct appdata
{
float4 vertex : POSITION; // 告诉Unity把模型空间下的顶点坐标填充给vertex属性
float3 normal : NORMAL; // 不再使用模型自带的法线。保留该变量是因为切线空间是通过(模型里的)法线和(模型里的)切线确定的。
float4 tangent : TANGENT; // tangent.w用来确定切线空间中坐标轴的方向
float4 uv : TEXCOORD0;// UV0
};
struct v2f
{
float4 vertex : SV_POSITION; // 用来存储顶点在裁剪空间下的坐标
float3 lightDir : TEXCOORD0; // 切线空间下,平行光的方向
float4 uv : TEXCOORD2; // xy存储MainTex的纹理坐标,zw存储NormalMap的纹理坐标
};
sampler2D _MainTex;
float4 _MainTex_ST;
sampler2D _NormalTex;
float4 _NormalTex_ST;
float _BumpScale;
v2f vert (appdata v)
{
v2f o;
o.vertex = UnityObjectToClipPos(v.vertex); // 使用Unity内置函数将世界空间坐标转化为剪裁空间坐标
//f.uv = v.texcoord.xy; // 不使用缩放和偏移
o.uv.xy = v.uv.xy * _MainTex_ST.xy + _MainTex_ST.zw; // 贴图的纹理坐标
o.uv.zw = v.uv.xy * _NormalTex_ST.xy + _NormalTex_ST.zw; // 法线贴图的纹理坐标
TANGENT_SPACE_ROTATION; // 调用这个宏会得到一个矩阵rotation,该矩阵用来把模型空间下的方向转换为切线空间下
o.lightDir = mul(rotation, ObjSpaceLightDir(v.vertex)); // ObjSpaceLightDir(v.vertex); 得到模型空间下的平行光方向
return o;
}
fixed4 frag(v2f i) : SV_Target
{
float3 ambient = UNITY_LIGHTMODEL_AMBIENT.rgb; //环境光颜色
// 法线方向。从法线贴图中获取。法线贴图的颜色值 --> 法线方向
fixed4 normalColor = tex2D(_NormalTex, i.uv.zw); //法线贴图的颜色值
fixed3 tangentNormal = UnpackNormal(normalColor); // 使用Unity内置的方法,从颜色值得到法线在切线空间的方向
tangentNormal.xy = tangentNormal.xy * _BumpScale; // 控制凹凸程度
tangentNormal = normalize(tangentNormal);
// 光照方向
fixed3 lightDir = normalize(i.lightDir);
// 贴图上的颜色
fixed3 mainColor = tex2D(_MainTex, i.uv.xy);
// 漫反射Diffuse颜色 = 直射光颜色 * max(0, cos(光源方向和法线方向夹角)) * 材质自身色彩(纹理对应位置的点的颜色
fixed3 difussColor = _LightColor0 * max(0, dot(tangentNormal, lightDir)) * mainColor;
// 最终颜色 = 漫反射 + 环境光
fixed3 tempColor = difussColor + ambient * mainColor; // 让环境光也跟纹理颜色做融合,防止环境光使得纹理效果看起来朦胧
return fixed4(tempColor,1);
}
ENDCG
}
}
}
效果
原图
参考
https://blog.csdn.net/BonChoix/article/details/8619624
https://blog.csdn.net/sixdaycoder/article/details/79015765
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)