Billboarding

原理

Billboarding通常会根据需求来旋转一个被纹理着色的多边形,本质上是构建一个旋转矩阵,通常是基于法线或者基于向上的方向,还需要设定锚点来确定多边形在空间中的位置。

难点在于法线和向上的方向常常不是垂直的,需要构建的矩阵由法线方向(normal),向上的方向(up)和向右的方向(right)组成

  1. right = normal x up
  2. up’ = normal x right

如图,通过视线作为一个轴,获取设定好的向上方向之后,获取向右的方向,因为向上的向量恒定不变,因此叉乘出来的向右向量由视线决定,在更新了向右向量之后再反过来影响向上的向量。(图片来自shader入门精要)

实现

参考《Unity Shader入门精要》

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
v2f vert (appdata v){
v2f o;
// 设置模型空间中原点
float3 center = float3(0,0,0);
// 模型空间中的视线
half3 view_model = mul(unity_WorldToObject, float4(_WorldSpaceCameraPos, 1));
// 确定法线
half3 normal_dir = view_model - center;
// 加上法线和向上向量权重
normal_dir.y = normal_dir.y * _VerticalBillboarding;
normal_dir = normalize(normal_dir);
// 如果法线已经是向上的了,那么up就设置为向右, 防止叉乘中的平行
half3 up_dir = abs(normal_dir.y)>0.999?half3(0,0,1):half3(0,1,0);
// 构建向右和新的向上向量
half3 right_dir = normalize(cross(up_dir, normal_dir));
up_dir = normalize(cross(normal_dir, right_dir));

// 计算偏移,重新得到新的顶点位置
half3 centerOffs = v.vertex.xyz - center;
float3 localPos = center + right_dir * centerOffs.x + up_dir * centerOffs.y + normal_dir * centerOffs.z;
o.pos = UnityObjectToClipPos(float4(localPos, 1));
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}

fixed4 frag (v2f i) : SV_Target{
fixed4 col = tex2D(_MainTex, i.uv);
col.rgb = _Color.rgb * col.rgb;
return col;
}

图中是向上向量和视线向量分别来作为恒定轴时候的效果(图片来自shader入门精要)

优化

如果单纯只是想实现一直朝向的效果,那么可以进行如下优化,因为本质上是通过构建旋转矩阵追踪视角变换的朝向,并将朝向的变换应用到物体上,那么反过来想,抵消掉物体朝向的变换也是可以的,因此通过mv矩阵的逆可以实现同样的保存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
v2f vert (appdata v)
{
v2f o;
// 计算偏移,重新得到新的顶点位置
float3 localPos = mul(UNITY_MATRIX_T_MV, v.vertex.xyz);
o.pos = UnityObjectToClipPos(float4(localPos, 1));
o.uv = TRANSFORM_TEX(v.texcoord, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
col.rgb = _Color.rgb * col.rgb * _Intensity;
return col;
}     

效果