线性代数:矩阵变换图形(三维绕任意轴向量旋转)
上图就是我们需要求解的子弹动画,我来仔细解释一下,如下图:
假如存在一颗子弹b1,沿着仿射坐标系XYZO的x轴运动,那么我们建立Z轴旋转矩阵T_z变换仿射坐标系XYZO为仿射坐标系X1Y1ZO,此时仿射空间中子弹b1被变换成子弹b2,然后以新的仿射坐标系X1Y1ZO为标准,建立新的Y1轴旋转矩阵T_y1变换仿射坐标系X1Y1ZO为新的仿射坐标系X2Y1Z1O,那么此时子弹b2被变换为最终的子弹b3,也就是我们射击方向的子弹,然后我们通过X2轴旋转矩阵T_x2的持续作用,使子弹B3持续高速旋转。这就是任意轴的旋转方法,通过仿射坐标系的旋转组合得到。但是我想说这很复杂有木有,变换次数太多,我们肯定想建立一个通用矩阵一次性变换成功。
首先让我们看下绕任意轴旋转是什么样子的,如下图:
在仿射坐标系XYZO中存在任意向量γ,同时向量OA绕着向量γ旋转成向量OA',这种绕轴旋转可以理解为,仿射坐标系XYZO绕着向量γ进行旋转,使仿射坐标系内的向量OA被牵引旋转成新向量OA'。
那么我们怎么计算这个特殊的旋转矩阵呢?我们建立向量组合计算,如下图:
这里我来解释一下,首先我们要计算图形饶任意轴旋转,必须知道图形绕的轴向量,然后规范化单位向量ON,其次我们要构建图形中任意点或者任意向量绕ON旋转后的新任意点和新任意向量(ps:求向量其实也是求点,因为前面我们了解到图形变换也就是变换图形所在原点的仿射坐标系,假设原点为O,那么A和A'的坐标值等于向量值),同时我们建立图中下部的辅助截面,建立PB垂直辅助向量,通过计算出向量OP,PA,PB,PA'就能推出PA’,也就是向量PA变换后的向量PA'。
到这里我们已经计算建立好饶任意轴变换的已知量了,接下来我们就来建立变换矩阵和线性方程组了,如下图:
这里我们依旧使用已知量,比如OA单位轴向量,辅助单位向量ON等和θ角度去代换计算出矩阵T,大家最好手动推算一下,比较稳妥。
接下来就是程序验证了,最后得出的这么复杂的公式,如果不验证一下根本就不知道到底推算错误了没,如下图:
上面我们随意构建了一个任意轴线,然后用矩阵控制一个图形绕着任意轴旋转,从图中看得出效果是正常的,下面上cgshader代码。Shader "Unlit/AnyAxisUnlitShader" { Properties { _MainTex("Texture", 2D) = "white" {} _AnyAxis("AnyAxis",vector) = (0,0,0,1) _AnyAngle("AnyAngle",float) = 0 } SubShader { Tags { "RenderType"="Opaque" } LOD 100 Pass { CGPROGRAM #pragma vertex vert #pragma fragment frag
#include "UnityCG.cginc"
struct appdata
{
float4 vertex : POSITION;
float2 uv : TEXCOORD0;
};
struct v2f
{
float2 uv : TEXCOORD0;
float4 vertex : SV_POSITION;
};
sampler2D _MainTex;
float4 _MainTex_ST;
vector _AnyAxis; //任意轴
float _AnyAngle; //旋转角度
v2f vert (appdata v)
{
v2f o;
//旋转角度值随着时间增加
_AnyAngle = _Time*20.0;
//建立比较易于观察的值
float n1 = _AnyAxis.x;
float n2 = _AnyAxis.y;
float n3 = _AnyAxis.z;
float cosAngle = cos(_AnyAngle);
float sinAngle = sin(_AnyAngle);
//构建任意轴旋转矩阵,扩展平移维度
float4x4 _Mat_Anyaxis = float4x4(n1*n1 * (1 - cosAngle) + cosAngle, n1*n2*(1 - cosAngle) - n3*sinAngle, n1*n3*(1 - cosAngle) + n2*sinAngle, 0,
n1*n2*(1 - cosAngle) + n3*sinAngle, n2*n2 * (1 - cosAngle) + cosAngle, n2*n3*(1 - cosAngle) - n1*sinAngle, 0,
n1*n3*(1 - cosAngle) - n2*sinAngle, n2*n3*(1 - cosAngle) + n1*sinAngle, n3*n3 * (1 - cosAngle) + cosAngle, 0,
0, 0, 0, 1);
float4 vx = mul(_Mat_Anyaxis, v.vertex); //绕任意轴旋转子弹
o.vertex = UnityObjectToClipPos(vx);
o.uv = TRANSFORM_TEX(v.uv, _MainTex);
return o;
}
fixed4 frag (v2f i) : SV_Target
{
fixed4 col = tex2D(_MainTex, i.uv);
return col;
}
ENDCG
}
}
}
和以前的代码类似,其实也就是处理vertex顶点函数中的矩阵变换顶点,下面则是c#控制类代码。
控制类代码作用只是简单的给cgshader提供已知参数用于计算。如果小伙伴们是依次循序渐进的看到这里,那么应该很容易理解上面的代码。