shader入门基础-凹凸映射
shader入门基础-凹凸映射
本文内容主要参考 《Unity shader 入门精要》一书,旨在总结所学知识与加深个人理解
凹凸映射
使用一张纹理来修改模型表面的法线,为模型提供更多细节。
使用法线纹理进行凹凸映射
法线方向的分量范围在[-1,1],像素分量范围为[0,1],法线到像素的映射 $pixel = \frac{normal+1}{2}$. 我们在 shader 中对法线纹理采样后,需要进行反映射 $normal=pixel\ast 2-1$。
模型空间的法线纹理(object-space normal map)
定义:模型空间中的表面法线存储在一张纹理中。 优点:
- 实现简单,直观。
- 纹理坐标缝合处和尖锐的边角部分,课件突变较少,可以提供平滑的边界。
切线空间的法线纹理(tangent-space normal map)
定义: 模型定点的切线空间来存储法线,对于模型的每个定点,它都有一个属于自己的切线空间,这个切线空间的原点就是该顶点本身, 而 z 轴是顶点的法线方向(n),x 轴是顶点的切线方向(t),而 y 轴可由法线和切线叉积而得,也被称为副切线。
优点:
- 自由度高。模型空间下的法线纹理记录的是绝对法线信息,仅可用于创建它时的那个模型。切线空间下的纹理记录的是相对法线信息, 意味着,把纹理应用到完全不同的网格上,也可以得到合理结果。
- 可进行 UV 动画。我们可以移动一个纹理的 UV 坐标来实现凹凸移动的效果,但使用模型空间下的法线纹理得到完全错误的结果。这种 UV 动画在水 或者火山熔岩。
- 可以重用法线纹理。
- 可压缩。
在切线空间下计算光照模型
实现:在片元着色器中通过纹理采样得到切线空间的法线,然后在于切线空间下的视角方向,光照方向等进行计算。 需要在顶点着色器中把视角方向,光照方向从来模型空间变换到切线空间中。即我们需要知道的从模型空间到切线空间的变换 矩阵。这个变换矩阵的逆矩阵,在顶点着色器中按切线(x 轴),副切线(y 轴),法线(z 轴)的顺序按列排列。在 UnityCG.cginc 中 定义了 TANGENT_SPACE_ROTATION
实现
_BumpMap,使用”bump”作为它的默认值。”bump”是 Unity 内置法线纹理,当没有提供任何法线纹理时,bump 对应模型自带法线信息。 _BmupScale 控制凹凸程度,0 则意味该法线纹理不会对光照产生任何影响。 tangent.w 分量决定切线空间中的第三个坐标轴-副切线的方向性。 法线纹理中存储的是把法线经过映射后得到的像素值,因此需要把他们反映射出来。
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
Shader "Practice/NormalMapTagSpace"
{
Properties
{
_Color("Color Tint",Color) = (1,1,1,1)// 漫反射颜色
_MainTex ("Main Tex", 2D) = "white" {}//贴图
_BumpMap("Bump Map",2D) = "bump" {}//法线贴图
_BumpScale("Bump Scale",float) = 1.0//凹凸程度
_Specular("Specular",Color)= (1,1,1,1)//高光颜色
_Gloss("Gloss",Range(8,256)) = 20 //光泽度
}
SubShader
{
Pass{
Tags { "LightModel" = "ForwardBase" }
CGPROGRAM
#pragma vertex vert
#pragma fragment frag
#include "Lighting.cginc"
fixed4 _Color;
sampler2D _MainTex;
sampler2D _BumpMap;//法线纹理
float4 _MainTex_ST;
float4 _BumpMap_ST;//法线纹理属性
fixed4 _Specular;
float _Gloss;
float _BumpScale;//凹凸程度
struct a2v{
float4 vertex:POSITION; //模型顶点信息
float3 normal:NORMAL;//法线
float4 tangent:TANGENT;//切线
float4 texcoord:TEXCOORD0;//纹理
};
struct v2f {
float4 pos:SV_POSITION;
float4 uv:TEXCOORD0;//纹理映射坐标
float3 lightDir:TEXCOORD1;//光线
float3 viewDir:TEXCOORD2;//视线
};
v2f vert(a2v i){
v2f o;
o.pos = UnityObjectToClipPos(i.vertex);
//减少插值寄存器的 xy存储_MainTex的纹理坐标 ,zw存储_BumpMap坐标
o.uv.xy =i.texcoord.xy*_MainTex_ST.xy+_MainTex_ST.zw;
o.uv.zw = i.texcoord.xy*_BumpMap_ST.xy+_BumpMap_ST.zw;
//计算副切线
float3 binormal = cross(normalize(i.normal),normalize(i.tangent.xyz))*i.tangent.w;
float3x3 rotation = float3x3(i.tangent.xyz,binormal,i.normal);
//获取到顶点坐标的光线防线后,把光线方向从模型空间转换到切线空间
o.lightDir = mul(rotation,ObjSpaceLightDir(i.vertex)).xyz;
o.lightDir = mul(rotation,ObjSpaceViewDir(i.vertex)).xyz;
return o;
}
fixed4 frag(v2f i):SV_TARGET
{
fixed3 tangentLightDir = normalize(i.lightDir);
fixed3 tangentViewDir = normalize(i.viewDir);
fixed4 packedNormal = tex2D(_BumpMap,i.uv.zw);
fixed3 tangentNormal;
tangentNormal = UnpackNormal(packedNormal);
tangentNormal.xy *= _BumpScale;
tangentNormal.z = sqrt(1.0-saturate(dot(tangentNormal,tangentViewDir)));
fixed3 albedo = tex2D(_MainTex,i.uv).rgb*_Color.rgb;
fixed3 ambient = UNITY_LIGHTMODEL_AMBIENT.xyz*albedo;
fixed3 diffuse = _LightColor0.rgb*albedo*max(0,dot(tangentNormal,tangentLightDir));
fixed3 halfDir = normalize(tangentLightDir+tangentViewDir);
fixed3 specular = _LightColor0.rgb*_Specular.rgb*pow(max(0,dot(tangentNormal,halfDir)),_Gloss);
return fixed4(ambient+diffuse+specular,1.0);
}
ENDCG
}
}
Fallback "Diffuse"
}
本文由作者按照 CC BY 4.0 进行授权