Sorry, your browser cannot access this site
This page requires browser support (enable) JavaScript
Learn more >

凹凸纹理

一、凹凸纹理的作用和原理

  • 纹理除了可以用来进行颜色映射外,另外一种常见的应用就是进行凹凸映射,凹凸映射的目的是使用一张纹理来修改模型表面的法线,让我们不需要增加顶点,而让模型看起来有凹凸效果。
  • 原理:光照的计算都会利用法线参与计算,决定最终的颜色表现效果。那么在计算“假凹凸面时,使用“真”凹凸面的法线参与计算,呈现出来的效果可以以假乱真
  • 2024-09-04 093019.png
  • 2024-09-04 093206.png

二、主流实现的两种方式

1.高度纹理贴图

1.)基本概念

高度纹理贴图一般简称高度图 它存储了模型表面上每个点的高度信息。 通常它使用灰度图像,其中不同灰度值表示不同高度。较亮区域通常对应较高的点,较暗的区域对应较低的点。 它主要用于模拟物体表面的位移

2.)存储规则

图片中的某一个像素点的 RGB 值是相同的,都表示高度值,A 值一般高度值范围一般为 0~1,0 代表最低,1 代表最高情况下为 1。

3.)优缺点

  • 优点 可以通过高度图很明确的知道模型表面的凹凸情况
  • 缺点 无法在 Shader 中直接得到模型表面点的法线信息而是需要通过额外的计算得到,因此会增加性能消耗,所以我们几乎很少使用它。

我们在使用凹凸纹理时,一般都会使用法线纹理贴

2.法线纹理贴图

1.)基本概念

法线纹理贴图一般简称法线贴图 或 法线纹理它存储了模型表面上每个点的法线方向。

2.)存储规则

图片中的 RGB 值分别存储法线的 X、Y、Z 分量值,A 值可以用于存储其他信息比如材质光滑度等。

3.)优缺点

  • 优点:从法线贴图中取出的数据便是法线信息,可以直接简单处理后就参与光照计算,性能表现更好
  • 缺点:我们无法直观的看出模型表面的凹凸情况

4.)读取规则

  • 存储图片:像素分量 = (法线分量 + 1)/2
  • 读取数据时:法线的分量 = 像素分量 * 2 - 1

原因:法线的 XYZ 分量范围在[-1,1]之间 像素 RGB 的分量范围在[0,1]之间 因此我们需要进行映射运算

5.)两种不同的存储方式

A、基于模型空间下的法线纹理
2024-09-04 095625.png
a. 定义

模型数据中自带的法线数据,是定义在模型空间中的,因此最直接的存储法线贴图数据的方式就是存储基于模型空间下的法线信息。

(注意:模型数据中的法线数据是“真”数据,法线贴图中对的法线数据是“假”数据)

b. 模型空间的法线为五颜六色的原因

法线(0,1,0)映射到像素后(法线线量+1)/2 是(0.5,1.0.5)绿色

法线(0,-1,0)映射到像素后(法线线量+1)/2 是(0.5,0,0.5)紫色

因此基于模型空间的法线纹理一般是五颜六色的这种法线纹理贴图数据取出来直接参与 Shader 计算即可

B、基于切线空间下的法线纹理
2024-09-04 095628.png
a. 什么时切线空间

每个顶点都有自己的切线空间原点:顶点本身

X 轴:顶点切线

Z 轴:法线方向(顶点的原法线)

Y 轴:X 和 Z 的又乘结果,也被称为副切线

2024-09-04 101022.png
b. 大部分为蓝色的原因

在切线空间下,如果该顶点的法线不变化(不需要“凹凸感”)那么它的坐标是(0,0,1),因为在切线空间下,Z 轴就是原法线方向.

因此:

法线(0,0,1)映射到像素后(法线分量+1)/2 是(0.5,0.5,1)浅蓝色

这个浅蓝色就是 切线空间下法线贴图 存在大片蓝色的原因

因为大部分顶点的法线和模型本身法线是一致的只有凹凸部分的颜色才会有些许差异

这种法线纹理贴图数据取出来后需要进行坐标空间转换再参与 Shader 计算

C、建议使用切线空间下的法线纹理贴图的原因
  • 可以用于不同的模型处理——如果模型空间下的法线,不可以用于其他模型
  • 方便处理模型变形——同上
  • 可以复用——一个砖块,6 个面的贴图都一样,可以只用一张法线贴图,即可用于六个面的计算
  • 可以压缩——可以只存储两个轴的分量
  • 方便制作 UV 动画——UV 坐标改变可以实现凹凸移动效果,如果时模型下法线贴图会有问题等等

三、法线贴图的计算方式

1.两种主流的计算方式

实现凹凸效果---主要就是使用基于切线空间的法线贴图中的法线信息参与到光照计算中

因此在计算光照模型时,通常有两种选择

  • 切线空间下进行光照计算 需要将光照方向、视角方向变换到切线空间下参与计算
  • 世界空间下进行光照计算 需要将法线方向变换到世界空间下参与计算

2.两种主流的计算方式的优缺点

  • 效率
    • 切线空间中计算,效率高。 因为矩阵的变换在顶点着色器中进行
    • 世界空间中计算,效率低。 因为矩阵变换在片元着色器中进行
  • 全局效果
    • 切线空间中计算,全局效果表现可能会不够准确 例如:处理镜面反射、环境映射效果时表现效果可能不够准确
    • 世界空间中计算,全局效果表现更准确 可以更容易的应用于全局效果

注意: 根据需求不同的计算方式,没有全局效果要求时,多使用切线空间下计算

3.切线空间下的计算方式

1.)关键点

计算 模型空间->切线空间 的变换矩阵

  1. 变换矩阵为子到父的2024-09-04 160119.png逆矩阵
  2. 由于我们主要使用变换矩阵来进行矢量变换而非点的变换,因此可以变为 3X3 的矩阵2024-09-04 160502.png
  3. 而 x、y、z 轴分别为切线空间中顶点的切线、副切线、法线
  4. 其中切线、法线可以从模型数据中获取,副切线为切线,法线的叉乘,并且三个轴是互相垂直的单位向量,因此2024-09-04 160502.png是正交矩阵
  5. 因此改变换矩阵的逆矩阵为2024-09-04 160502.png的转置矩阵

2.)计算时的核心知识

  • 得到模型空间光的方向 :ObjectSpaceLightDir(模型空间顶点坐标)
  • 得到模型空间视角的方向 :ObjectSpaceViewDir(模型空间顶点坐标)
  • 得到俩方向后 与 变换矩阵 进行矩阵运算,再参与后续切线空间下的计算

4.世界空间下的计算方式

1.)关键点

计算 切线空间->世界空间 的变换矩阵

  1. 变换矩阵为子到父的变换2024-09-04 160119.png
  2. 由于我们主要使用变换矩阵来进行矢量变换而非点的变换,因此可以变为 3X3 的矩阵2024-09-04 160502.png
  3. 而 x、y、z 轴分别为切线空间中顶点的切线、副切线、法线
  4. 我们只需要得到三个轴相对世界空间下的向量表达,即可以得到该矩阵

2.)计算时的核心知识

  • 法线从模型空间到世界空间 :UnityObjectToWorldNormal(模型空间法线数据)
  • 切线从模型空间到世界空间 :UnityObjectToWorldDir(模型空间切线数据)
  • 世界空间的副切线 :上面计算结果叉乘即可
  • 由上面三个计算得到的向量 组成变换矩阵2024-09-04 160502.png

四、关键注意点

1.模型空间下的切线数据

模型数据中的切线数据为 float4 类型,其中的 W 表示副切线的方向

原因: 用法线和切线叉乘得到的副切线有两条,用切线数据中的 W 与之相乘确定副切线的方向

2.Unity 当中的法线纹理类型

当我们把纹理类型设置为 Normal map(法线贴图时),我们可以使用 Unity 提供的内置函数 UnpackNormal 来得到正确的法线方向。该函数不仅可以进行法线分量=像素分量 * 2 - 1的逆运算,还会进行解压运算(Unity 会根据不同平台对法线纹理进行压缩)

3.法线纹理的命名

法线纹理属性一般命名为_BumpMap(凸块贴图)

同时还会声明一个名为_BumpMap(凸块缩放)的 float 属性---主要是用于控制凹凸度

4.高度纹理的处理办法

  1. 将图片类型设置为 Normal Map(法线贴图)
  2. 勾选 Creat form Grayscale(从灰度创建)
  • 多出的 Bumpniess(颠簸值)---控制凹凸程度
  • Filtering(过滤模式)---决定凹凸程度的算法
    • Sharp 滤波生成法线
    • Smooth 光滑的生成法线

五、切线空间下计算法线贴图的实现

1.书写步骤

  1. 属性相关

    • 漫反射颜色
    • 高光反射颜色
    • 光泽度
    • 单张纹理
    • 法线纹理
    • 凹凸程度
  2. 结构体相关

    • 顶点着色器中传入: 可以使用"UnityCG.cginc" 中的 appdata_full 其中包含了我们需要的顶点、法线、切线、纹理坐标相关数据
    • 片元着色器中传入: 自定义一个结构体 其中包含 裁剪空间下的顶点坐标、uv 坐标、光的方向、视角方向
  3. 顶点回调函数中

    1. 顶点模型空间坐标转裁剪空间

    2. 单张纹理和法线纹理 的 UV 坐标缩放偏移计算

    3. 副切线计算 切线(tangent) X 法线(normal)·切线中的 w

    4. 构建模型空间到切线空间的变换矩阵 —— 切线 ——

      —— 副切线 ——

      —— 法线 ——

    5. 将光照方向和视角方向转换到模型空间

      • ObjSpaceLightDir()
      • ObjSpaceView()
    6. 将光照方向和视角方向转换到切线空间 利用 4 中计算号的变换矩阵

  4. 片元着色器回调函数中

    1. 取出法线贴图中的法线信息(利用纹理采样 tex2D)
    2. 利用内置的 UnpackNormal 函数对法线信息进行逆运算以及可能的解压
    3. 得到的法线数据*凹凸程度 来控制凹凸度
    4. 用切线空间下的 光方向、视角方向、法线方向、进行 BlinnPhong 光照模型的计算

2.实例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
Shader "Unlit/TangentspaceBumpMap"
{
Properties
{
//光照相关属性
_MainColor("MainColor",Color) = (1,1,1,1)
_specularColor("specularColor",Color) = (1,1,1,1)
_specularNum("specularNum",Range(0,20)) = 15
//单张纹理
_MainTex("MainTex",2D) = ""{}
//法线纹理
_BumpMap("BumpMap",2D) = ""{}
_BumpMapScale("BumpMapScale",Float) = 1

}
SubShader
{
Pass
{
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag

#include "UnityCG.cginc"
#include "Lighting.cginc"

struct v2f
{
float4 pos:POSITION;
//我们可以单独申明两个float2成员 用于记录 颜色和法线的uv坐标
/* float2 uvTex:TEXCOORD0;
float2 uvBump:TEXCOORD1; */
//我们也可以直接申明一个float4成员
//xy表示颜色纹理的uv zw表示法线的纹理uv
float4 uv:TEXCOORD0;
//光的方向 相对于切线空间下
float3 lightDir:TEXCOORD1;
//视角的方向 相对于切线空间下
float3 viewDir:TEXCOORD2;
};
//---光照---//
float4 _MainColor;//漫反射颜色
float4 _specularColor;//高光反色颜色
float _specularNum;//光泽度
//---纹理---//
sampler2D _MainTex;//颜色纹理
float4 _MainTex_ST;//颜色纹理缩放和平移
sampler2D _BumpMap;//法线纹理缩放和平移
float4 _BumpMap_ST;//法线纹理缩放和平移
float _BumpMapScale;//凹凸程度
v2f vert (appdata_full v)
{
v2f data;
//将模型空间下的顶点转移到裁剪空间下
data.pos = UnityObjectToClipPos(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;

//在顶点着色器当中 得到 模型空间到切线空间的转换矩阵
//切线、副切线、法线
//计算副切线————注意乘以切线中的w确定方向
float3 binormal = cross(normalize(v.tangent),normalize(v.normal))*v.tangent.w;
//转换矩阵
float3x3 rotation = float3x3(v.tangent.xyz,
binormal,
v.normal);
//模型空间下的光的方向
data.lightDir = ObjSpaceLightDir(v.vertex);
//转换到切线空间下的光的方向
data.lightDir = mul(rotation,data.lightDir);
//模型空间下的视角方向
data.viewDir = ObjSpaceViewDir(v.vertex);
//转换到切线空间下的视角方向
data.viewDir = mul(rotation,data.viewDir);

return data;
}

fixed4 frag (v2f i) : SV_Target
{
//通过纹理采样函数 取出法线纹理贴图当中的数据
float4 packedNormal = tex2D(_BumpMap,i.uv.zw);
//将我们取出来的法线数据 进行逆运算并且可能会进行解压缩运算 得到切线空间下的法线信息
float3 tangentNormal = UnpackNormal(packedNormal);
//乘以凹凸程度的系数
tangentNormal *= _BumpMapScale;
//---处理带颜色纹理的 BlinnPhong光照模型---//

//颜色纹理和漫反射颜色的叠加
fixed3 albedo = tex2D(_MainTex,i.uv.xy) * _MainColor.rgb;
//计算兰伯特
fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0,dot(tangentNormal,normalize(i.lightDir)));
//计算半角向量
float3 halfA = normalize(normalize(i.viewDir) + normalize(i.lightDir));
//计算高光反射
float3 specularColor = _LightColor0.rgb * _specularColor * pow(max(0,dot(tangentNormal,halfA)) , _specularNum);
//计算BlinnPhong
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;


return fixed4(color.rgb , 1 );
}
ENDCG
}
}
}

3.演示效果

2024-09-05 165420.png 2024-09-05 165406.png

六、世界空间下计算法线贴图的实现

1.主要思路

  1. 在顶点着色器中 计算切线空间到世界空间的变换矩阵
  2. 在片元着色器中进行法线采样转化

2.书写步骤

  1. 属性相关
    • 漫反射颜色
    • 高光反射颜色
    • 光泽度
    • 单张纹理
    • 法线纹理
    • 凹凸程度
  2. 结构体相关
    • 顶点着色器中传入
      • 可以使用 "UnityCG.cginc" 中的 appdata_full
      • 其中包含了我们需要的顶点、法线、切线、纹理坐标相关数据
    • 片元着色器中传入
      • 自定义一个结构体
      • 其中包含 裁剪空间下的坐标、uv 坐标、世界空间下顶点位置、变换矩阵
  3. 顶点着色器回调函数中
    • 顶点坐标 模型空间转裁剪空间
    • 单张纹理和法线纹理 UV 坐标缩放偏移计算
    • 模型空间下顶点转换到世界空间下(之后再片元着色器中用于计算视角方向)
    • 将模型空间下的法线、切线转换到世界空间下
    • 副切线计算 用世界空间下的法线和切线进行叉乘 在乘以切线中的 w(确定副切线方向)
    • 构建模型空间到切线空间的变化矩阵 2024-09-06 154515.png
  4. 片元着色器回调函数中
    • 计算光的方向、视角方向
    • 取出法线贴图冲的法线信息(利用纹理采样函数 tex2D)
    • 利用内置函数的 UnpackNormal 函数堆法线信息进行逆运算以及可能的解压
    • 得到的切线空间的法线数据 乘以 BumpScale 来控制凹凸度
    • 将计算完成后的切线空间下的法线准换到世界空间下
    • 得到单张纹理的颜色和漫反射颜色的叠加颜色
    • 用切线空间下的 光方向、视角方向、法线方向 进行 BlinnPhong 光照模型计算

3.实例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
Shader "Unlit/WorldSpaceBumpMap"
{
Properties
{
//光照相关属性
_MainColor("MainColor",Color) = (1,1,1,1)
_specularColor("specularColor",Color) = (1,1,1,1)
_specularNum("specularNum",Range(0,20)) = 15
//单张纹理
_MainTex("MainTex",2D) = ""{}
//法线纹理
_BumpMap("BumpMap",2D) = ""{}
_BumpMapScale("BumpMapScale",Float) = 1
}
SubShader
{
Pass
{
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
//---光照---//
float4 _MainColor;//漫反射颜色
float4 _specularColor;//高光反色颜色
float _specularNum;//光泽度
//---纹理---//
sampler2D _MainTex;//颜色纹理
float4 _MainTex_ST;//颜色纹理缩放和平移
sampler2D _BumpMap;//法线纹理缩放和平移
float4 _BumpMap_ST;//法线纹理缩放和平移
float _BumpMapScale;//凹凸程度

struct v2f
{
//裁剪空间下的顶点坐标
float4 pos:SV_POSITION;
//颜色和法线的uv坐标
//xy表示颜色纹理的uv zw表示法线的纹理uv
float4 uv:TEXCOORD0;
//顶点相对于世界坐标的位置 主要用于 之后的 视角方向的计算
float3 worldPos:TEXCOORD1;
//切线 到 世界坐标 变换矩阵
float3x3 rotation:TEXCOORD2;


};
v2f vert (appdata_full v)
{
v2f data;
//将模型空间下的顶点转到裁剪空间下
data.pos = UnityObjectToClipPos(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;

//得到世界空间下的 顶点位置 用于在片元中的视角方向(世界空间下)
data.worldPos = mul(unity_ObjectToWorld,v.vertex);
//将模型空间下的法线转换到世界空间下
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
//将模型空间下的切线转换到世界空间下
float3 worldTangent = UnityObjectToWorldDir(v.tangent);
//计算副切线
float3 worldBinormal = cross(normalize(worldTangent),normalize(worldNormal)) * v.tangent.w;
//构建 模型空间到切线空间 的变换矩阵
data.rotation =
float3x3(
worldTangent.x,worldBinormal.x,worldNormal.x,
worldTangent.y,worldBinormal.y,worldNormal.y,
worldTangent.z,worldBinormal.z,worldNormal.z
);
return data;

}

fixed4 frag (v2f i) : SV_Target
{
//世界空间下光的方向
fixed3 lightDir = normalize(_WorldSpaceLightPos0);
//世界空间下的视角方向
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(i.worldPos));

//通过纹理采样函数
//取出法线纹理贴图当中的数据
float4 packedNormal = tex2D(_BumpMap,i.uv.zw);
//将我们取出来的法线数据 进行逆运算并且可能会进行解压缩的运算 最终得到切线空间下的法线数据
float3 tangentNormal = UnpackNormal(packedNormal);

//---控制凹凸程度---//
//乘以凹凸系数 控制凹凸程度
//tangentNormal *= _BumpMapScale;

//优化控制凹凸程度计算方式
//原因:见光照的知识补充
tangentNormal.xy *= _BumpMapScale;
//saturate [0,1]的特殊夹紧函数
tangentNormal.z = sqrt(1-saturate(dot(tangentNormal.xy,tangentNormal.xy)));
//---控制凹凸程度---//

//把计算完成后的切线空间下的法线数据 转换到 世界空间下
float3 worldNormal = mul(i.rotation,tangentNormal);

//---处理带颜色纹理的 BlinnPhong光照模型---//

//颜色纹理和漫反射颜色的叠加
fixed3 albedo = tex2D(_MainTex,i.uv.xy) * _MainColor.rgb;
//计算兰伯特
fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0,dot(worldNormal,normalize(lightDir)));
//计算半角向量
float3 halfA = normalize(normalize(viewDir) + normalize(lightDir));
//计算高光反射
float3 specularColor = _LightColor0.rgb * _specularColor.rgb * pow(max(0,dot(worldNormal,halfA)) , _specularNum);
//计算BlinnPhong
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;


return fixed4(color.rgb , 1 );
}
ENDCG
}
}
}

4.实现效果

2024-09-07 130810.png

七、提高性能优化的写法

  • 我们在 v2f 结构体中 世界坐标顶点位置和变换矩阵使用了 float3 和 float3x3 两个变量存储 但是在很多世界空间下计算 法线贴图的 Shader 中 往往会使用 3 个 float4 类型的变量来存储
  • 原因 这种写法在很多情况下可以提高性能,因为它更好地与 GPU 的硬件架构匹配 float4 类型的寄存器是非常高效的 因为现代的 GUP 通常是会以 4 分量的向量为基本单位进行并行计算 float3x3 矩阵相对来需要更多的寄存器和指令来表示和计算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
Shader "Unlit/WorldSpaceBumpMap"
{
Properties
{
//光照相关属性
_MainColor("MainColor",Color) = (1,1,1,1)
_specularColor("specularColor",Color) = (1,1,1,1)
_specularNum("specularNum",Range(0,20)) = 15
//单张纹理
_MainTex("MainTex",2D) = ""{}
//法线纹理
_BumpMap("BumpMap",2D) = ""{}
_BumpMapScale("BumpMapScale",Float) = 1
}
SubShader
{
Pass
{
Tags { "LightMode"="ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "UnityCG.cginc"
#include "Lighting.cginc"
//---光照---//
float4 _MainColor;//漫反射颜色
float4 _specularColor;//高光反色颜色
float _specularNum;//光泽度
//---纹理---//
sampler2D _MainTex;//颜色纹理
float4 _MainTex_ST;//颜色纹理缩放和平移
sampler2D _BumpMap;//法线纹理缩放和平移
float4 _BumpMap_ST;//法线纹理缩放和平移
float _BumpMapScale;//凹凸程度

struct v2f
{
//裁剪空间下的顶点坐标
float4 pos:SV_POSITION;
//颜色和法线的uv坐标
//xy表示颜色纹理的uv zw表示法线的纹理uv
float4 uv:TEXCOORD0;
//顶点相对于世界坐标的位置 主要用于 之后的 视角方向的计算
//float3 worldPos:TEXCOORD1;
//切线 到 世界坐标 变换矩阵
//float3x3 rotation:TEXCOORD2;
//---优化性能的写法---//
//代表我们切线空间到世界空间的 变换矩阵的三行
//其中 w 代表顶点相对于世界坐标位置
float4 TtoW0:TEXCOORD1;
float4 TtoW1:TEXCOORD2;
float4 TtoW2:TEXCOORD3;



};
v2f vert (appdata_full v)
{
v2f data;
//将模型空间下的顶点转到裁剪空间下
data.pos = UnityObjectToClipPos(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;

//得到世界空间下的 顶点位置 用于在片元中的视角方向(世界空间下)
//data.worldPos = mul(unity_ObjectToWorld,v.vertex);
float3 worldPos = mul(unity_ObjectToWorld,v.vertex);
//将模型空间下的法线转换到世界空间下
float3 worldNormal = UnityObjectToWorldNormal(v.normal);
//将模型空间下的切线转换到世界空间下
float3 worldTangent = UnityObjectToWorldDir(v.tangent);
//计算副切线
float3 worldBinormal = cross(normalize(worldTangent),normalize(worldNormal)) * v.tangent.w;
//构建 模型空间到切线空间 的变换矩阵
/* data.rotation =
float3x3(
worldTangent.x,worldBinormal.x,worldNormal.x,
worldTangent.y,worldBinormal.y,worldNormal.y,
worldTangent.z,worldBinormal.z,worldNormal.z
); */
data.TtoW0 = float4(worldTangent.x,worldBinormal.x,worldNormal.x,worldPos.x);
data.TtoW1 = float4(worldTangent.y,worldBinormal.y,worldNormal.y,worldPos.y);
data.TtoW2 = float4(worldTangent.z,worldBinormal.z,worldNormal.z,worldPos.z);

return data;

}

fixed4 frag (v2f i) : SV_Target
{
//世界空间下光的方向
fixed3 lightDir = normalize(_WorldSpaceLightPos0.xyz);
//世界空间下的视角方向
float3 worldPos = float3(i.TtoW0.w,i.TtoW1.w,i.TtoW2.w);
fixed3 viewDir = normalize(UnityWorldSpaceViewDir(worldPos));

//通过纹理采样函数
//取出法线纹理贴图当中的数据
float4 packedNormal = tex2D(_BumpMap,i.uv.zw);
//将我们取出来的法线数据 进行逆运算并且可能会进行解压缩的运算 最终得到切线空间下的法线数据
float3 tangentNormal = UnpackNormal(packedNormal);

//---控制凹凸程度---//
//乘以凹凸系数 控制凹凸程度
//tangentNormal *= _BumpMapScale;

//优化控制凹凸程度计算方式
//原因:见光照的知识补充
tangentNormal.xy *= _BumpMapScale;
//saturate [0,1]的特殊夹紧函数
tangentNormal.z = sqrt(1.0-saturate(dot(tangentNormal.xy,tangentNormal.xy)));
//---控制凹凸程度---//

//把计算完成后的切线空间下的法线数据 转换到 世界空间下
//float3 worldNormal = mul(i.rotation,tangentNormal);
float3 worldNormal = float3(dot(i.TtoW0.xyz,tangentNormal),dot(i.TtoW1.xyz,tangentNormal),dot(i.TtoW2.xyz,tangentNormal));

//---处理带颜色纹理的 BlinnPhong光照模型---//

//颜色纹理和漫反射颜色的叠加
fixed3 albedo = tex2D(_MainTex,i.uv.xy) * _MainColor.rgb;
//计算兰伯特
fixed3 lambertColor = _LightColor0.rgb * albedo.rgb * max(0,dot(worldNormal,normalize(lightDir)));
//计算半角向量
float3 halfA = normalize(normalize(viewDir) + normalize(lightDir));
//计算高光反射
float3 specularColor = _LightColor0.rgb * _specularColor.rgb * pow(max(0,dot(worldNormal,halfA)) , _specularNum);
//计算BlinnPhong
fixed3 color = UNITY_LIGHTMODEL_AMBIENT.rgb * albedo + lambertColor + specularColor;


return fixed4(color.rgb , 1 );
}
ENDCG
}
}
}

评论