0%

unityShader实现绕中心旋转

需求

实现一个Quad模型,在忽略任何角度旋转的billboard模式(公告板,永远面向摄像机)下,在shader中填入一个参数(旋转角度),使得最后的图像显示,在其本身平面上,绕中心点旋转。
要求效果如下:
image

实现

Amplify:
image

总体思路: 对模型的所有顶点绕Z轴,模型中心旋转。
大体解释一下这张图,主要是输出到VertexOffset之前的部分。
Amplify官方文档:http://wiki.amplify.pt/index.php?title=Unity_Products:Amplify_Shader_Editor/Rotate_About_Axis
RotateAboutAxis四个输入:

参数 注释
Normalized Rotation Axis 绕哪根轴旋转,输入值需要归一化
Rotation Angle 旋转角(弧度制)
Pivot Point 旋转中心点
Position 当前顶点值

翻译成代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
float3 RotateAroundAxis( float3 center, float3 original, float3 u, float angle )
{
original -= center;
float C = cos( angle );
float S = sin( angle );
float t = 1 - C;
float m00 = t * u.x * u.x + C;
float m01 = t * u.x * u.y - S * u.z;
float m02 = t * u.x * u.z + S * u.y;
float m10 = t * u.x * u.y + S * u.z;
float m11 = t * u.y * u.y + C;
float m12 = t * u.y * u.z - S * u.x;
float m20 = t * u.x * u.z - S * u.y;
float m21 = t * u.y * u.z + S * u.x;
float m22 = t * u.z * u.z + C;
float3x3 finalMatrix = float3x3( m00, m01, m02, m10, m11, m12, m20, m21, m22 );
return mul( finalMatrix, original ) + center;
}
1
2
3
4
float3 axis = fixed3(0,0,1);
fixed3 _rotatePoint = fixed3(0.25,0.25,0);
float3 rotatedValue = RotateAroundAxis( _rotatePoint, saturate( v.vertex.xyz ), axis, _RotateAngle );
v.vertex.xyz = ( rotatedValue - _rotatePoint );

问题

再放大一下amplify版仔细观察一下。
image

这里有一点很鬼畜我始终不得其解,主要就是RotateAboutAxis的旋转中心点_rotatePoint需要设成(0.25,0.25,0),旋转后再减去这个_rotatePoint就是正确结果。
用Amplify之所以要再减去vertexPosition是因为最终输出是vertex offset。

于是我作了个测试,测试shader我把顶点位置作为颜色值直接输出,得到这样的测试图:
image
根据测试图的颜色可知,Unity自带的Quad模型顶点范围为(-0.5.-0.5,0)~(0.5,0.5,0)。中心点怎么看都应该是(0,0,0)对吧?

但如果把_rotatePoint设成(0,0,0) 结果就会如下图这么旋转
image

image

体会到我的绝望了吗???

验证RotateAroundAxis

好,这时候就会想了,是不是amplify提供RotateAroundAxis方法有问题啊?shader单点调试有点烦。
于是我把这个函数放到unity里实现了一遍看输出有没有问题……
丢代码丢代码

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

private Vector3 RotateAroundAxis(Vector3 center, Vector3 original, Vector3 axis, float angle) {
original -= center;
Vector3 u = axis;
float C = Mathf.Cos(angle * Mathf.Deg2Rad);
float S = Mathf.Sin(angle * Mathf.Deg2Rad);
float t = 1 - C;
float m00 = t * u.x * u.x + C;
float m01 = t * u.x * u.y - S * u.z;
float m02 = t * u.x * u.z + S * u.y;
float m10 = t * u.x * u.y + S * u.z;
float m11 = t * u.y * u.y + C;
float m12 = t * u.y * u.z - S * u.x;
float m20 = t * u.x * u.z - S * u.y;
float m21 = t * u.y * u.z + S * u.x;
float m22 = t * u.z * u.z + C;

Matrix4x4 matrix4X4 = new Matrix4x4(
new Vector4(m00, m01, m02),
new Vector4(m10, m11, m12),
new Vector4(m20, m21, m22),
new Vector4(0, 0, 0)
);

Vector4 mul = matrix4X4 * original;
return new Vector3(mul.x,mul.y,mul.z) + center;
}

public Vector3 center = new Vector3(0, 0, 0);
public float angle = 90;

[ContextMenu("TestRotate")]
public void TestRotate() {
Vector3 origin = new Vector3(-0.5f, -0.5f, 0);
//float angle = 90;

Vector3 normal = new Vector3(0, 0, 1);

Vector3 result = RotateAroundAxis(center, origin, normal, angle);
LogMgr.LogDebug(result);

Vector3 expectResult = new Vector3(0.5f, -0.5f, 0);
}

事实证明,这个函数!
它!
木有问题!

GG,所以我还是不知道为什么。

image


现在是2021/2/3,上面的问题破案了……
朋友今天看到我的博客后问了灵魂拷问,你为什么要saturate。
所以其实我把(-0.5.-0.5,0)~(0.5,0.5,0) 这个区间给坍塌到了(0,0) ~ (0.5,0.5)……所以中点变为了(0.25,0.25)
再给个最终的简单的答案吧……无语子。
image