【OpenGL ES】MVP矩阵变换

【OpenGL ES】MVP矩阵变换,第1张

【OpenGL ES】MVP矩阵变换 1 前言

        本文主要介绍 MVP 矩阵变换,其本质是线性变换,应用见→绘制立方体。

  • Model:模型变换,施加在模型上的空间变换,包含平移变换(translateM)、旋转变换(rotateM)、对称变换(transposeM)、缩放变换(scaleM);
  • View:观测变换,施加在观测点上的变换,用于调整观测点位置、观测朝向、观测正方向;
  • Projection:透视变换,施加在视觉上的变换,用于调整模型的透视效果(如:矩形的透视效果是梯形)。

        上述变换依次叠加,得到一个总的变换矩阵,即 MVP 变换矩阵,mvpMatrix = projectionMatrix * viewMatrix * modelMatrix,MVP 变换作用到模型的原始坐标矩阵上,得到的最终坐标矩阵即为用户观测到的模型状态。

        空间坐标系建立后,空间中任何一个点都对应一个向量,设空间中一向量为 [a, b, c]',为方便采用统一的格式描述线性变换,将三维空间中的向量 [a, b, c]' 映射到四维空间中的向量 [a, b, c, 1]',同时,线性变换采用 4x4 的矩阵描述。所有向量 [a, b, c, 1]' 生成的空间仍然是一个三维空间,它是四维空间中的一个三维子空间。

        对与某些线性变换(如平移变换),若使用 4x4 矩阵描述,此矩阵可以固定,不必依赖待变换的向量;若使用 3x3 矩阵描述,此矩阵不能固定,随着待变换的向量而变化。由此说明, 4x4 矩阵具有更强的描述性。

        设三维空间中的任意向量按照以上规则映射到四维空间中的向量为 v = [a, b, c, 1]',变换矩阵为 A 。OpenGL 为通用化接口,在获取变换矩阵时,会左乘一个初始矩阵 M,即将 MA 作为最终的变换矩阵,通常情况 M 为单位矩阵(E),即 M = E。(v 为列向量,A、M、E 都是 4x4 矩阵)

        说明:本文涉及的矩阵变换的方法都是 android.opengl.Matrix 类中的静态方法。

2 模型变换

        在介绍矩阵变换前,先介绍下单位矩阵,opengl 中初始化单位矩阵的方法如下:

public static void setIdentityM(float[] sm, int smOffset) {
	for (int i = 0 ; i < 16 ; i++) {
		sm[smOffset + i] = 0;
	}
	for(int i = 0; i < 16; i += 5) {
		sm[smOffset + i] = 1.0f;
	}
}

         说明:smOffset = sm.length - 16,sm 数组中 smOffset 之前的数,不参入接下来的线性变换。

2.1 平移变换

         设平移向量为 [x, y, z, 0]',则 A * v 如下:

        若用 3x3 矩阵描述平移变换,即 A 为 3x3 矩阵,则 A 中的元素必须依赖于 a, b, c,这样就会降低变换的一般性(或通用性),这也解释了为什么平移变换要用 4x4 矩阵描述的问题。

        M * A 如下: 

        源码如下,m 为初始矩阵,通常取单位矩阵;moffset 为索引偏移,表示 m 中 moffset 之前的数不参与变换;x , y , z 为向量 v 在 x轴、y 轴、z轴上的平移值。

public static void translateM(
		float[] m, int mOffset,
		float x, float y, float z) {
	for (int i = 0 ; i < 4 ; i++) {
		int mi = mOffset + i;
		m[12 + mi] += m[mi] * x + m[4 + mi] * y + m[8 + mi] * z;
	}
}
2.2 旋转变换

         本节仅列出沿着 x 轴、y 轴、z 轴旋转的变换矩阵,对于沿任意轴的旋转,公式比较复杂,本文不作讨论。设旋转角度为 (角度制),其对应的弧度为。

        1)沿 x 轴旋转

        2)沿 y 轴旋转

         3)沿 z 轴旋转

        源码如下,rm 初始矩阵,通常取单位矩阵;rmoffset 为索引偏移,表示 rm 中 rmoffset 之前的数不参与变换;a 为旋转角度(角度制);x , y , z 为旋转轴。旋转时,按照右手原则逆时针旋转 a 度。

public static void setRotateM(float[] rm, int rmOffset,
		float a, float x, float y, float z) {
	rm[rmOffset + 3] = 0;
	rm[rmOffset + 7] = 0;
	rm[rmOffset + 11]= 0;
	rm[rmOffset + 12]= 0;
	rm[rmOffset + 13]= 0;
	rm[rmOffset + 14]= 0;
	rm[rmOffset + 15]= 1;
	a *= (float) (Math.PI / 180.0f);
	float s = (float) Math.sin(a);
	float c = (float) Math.cos(a);
	if (1.0f == x && 0.0f == y && 0.0f == z) { //沿x轴旋转
		rm[rmOffset + 5] = c;   rm[rmOffset + 10]= c;
		rm[rmOffset + 6] = s;   rm[rmOffset + 9] = -s;
		rm[rmOffset + 1] = 0;   rm[rmOffset + 2] = 0;
		rm[rmOffset + 4] = 0;   rm[rmOffset + 8] = 0;
		rm[rmOffset + 0] = 1;
	} else if (0.0f == x && 1.0f == y && 0.0f == z) { //沿y轴旋转
		rm[rmOffset + 0] = c;   rm[rmOffset + 10]= c;
		rm[rmOffset + 8] = s;   rm[rmOffset + 2] = -s;
		rm[rmOffset + 1] = 0;   rm[rmOffset + 4] = 0;
		rm[rmOffset + 6] = 0;   rm[rmOffset + 9] = 0;
		rm[rmOffset + 5] = 1;
	} else if (0.0f == x && 0.0f == y && 1.0f == z) { //沿z轴旋转
		rm[rmOffset + 0] = c;   rm[rmOffset + 5] = c;
		rm[rmOffset + 1] = s;   rm[rmOffset + 4] = -s;
		rm[rmOffset + 2] = 0;   rm[rmOffset + 6] = 0;
		rm[rmOffset + 8] = 0;   rm[rmOffset + 9] = 0;
		rm[rmOffset + 10]= 1;
	} else { //沿任意轴旋转
		float len = length(x, y, z);
		if (1.0f != len) {
			float recipLen = 1.0f / len;
			x *= recipLen;
			y *= recipLen;
			z *= recipLen;
		}
		float nc = 1.0f - c;
		float xy = x * y;
		float yz = y * z;
		float zx = z * x;
		float xs = x * s;
		float ys = y * s;
		float zs = z * s;
		rm[rmOffset +  0] = x*x*nc +  c;
		rm[rmOffset +  4] =  xy*nc - zs;
		rm[rmOffset +  8] =  zx*nc + ys;
		rm[rmOffset +  1] =  xy*nc + zs;
		rm[rmOffset +  5] = y*y*nc +  c;
		rm[rmOffset +  9] =  yz*nc - xs;
		rm[rmOffset +  2] =  zx*nc - ys;
		rm[rmOffset +  6] =  yz*nc + xs;
		rm[rmOffset + 10] = z*z*nc +  c;
	}
}
2.3 对称变换

         设对称轴 u = [x, y, z, 1]',则 A * v + v = 2 * u,即

        若用 3x3 矩阵描述对称变换,即 A 为 3x3 矩阵,则 A 中的元素必须依赖于 a, b, c,这样就会降低变换的一般性(或通用性),这也解释了为什么对称变换要用 4x4 矩阵描述的问题。 

         M * A 如下: 

         代码如下(Matrix 类中没有对称变换,此方法是笔者写的),m 为初始矩阵,通常取单位矩阵;moffset 为索引偏移,表示 m 中 moffset 之前的数不参与变换; [x, y, z, 1]' 为对称轴。

public static void symmetryM(float[] m, int mOffset,
		float x, float y, float z) {
	for (int i = 0 ; i < 12 ; i++) {
		int mi = mOffset + i;
		m[mi] = -m[mi];
	}
	for (int i = 0 ; i < 4 ; i++) {
		int mi = mOffset + i;
		m[12 + mi] += 2 * (m[mi] * x + m[4 + mi] * y + m[8 + mi] * z);
	}
}
 2.4 缩放变换

        设向量 v 在 x 轴、y 轴、z 轴上的缩放分量分别为 x, y, z,则 A * v 如下:

        缩放变换可以用 3x3 矩阵描述,保证 A 中的元素不依赖于 a, b, c,但是为了提高接口的通用性, 线性变换统一使用 4x4 矩阵描述。

         M * A 如下: 

        源码如下,m 为初始矩阵,通常取单位矩阵;moffset 为索引偏移,表示 m 中 moffset 之前的数不参与变换; x, y, z 为向量 v 在 x 轴、y 轴、z 轴上的缩放分量。 

public static void scaleM(float[] m, int mOffset,
		float x, float y, float z) {
	for (int i=0 ; i<4 ; i++) {
		int mi = mOffset + i;
		m[mi] *= x;
		m[4 + mi] *= y;
		m[8 + mi] *= z;
	}
}
3 观测变换

         设观察点坐标为 eye = [eyeX, eyeY, eyeZ, 1]',观察方向为 f = [fx, fy, fz, 0]',观察向上的正方向为 u = [ux, uy, uz, 0]',向量 s = [sx, sy, sz, 0]' 为 u x f 的方向向量(x 为向量叉乘)。说明:假设 f、u、s 都是单位向量(可以通过归一化做到),并且它们相互正交(若 f 与 u 不正交,可以通过 f x s 反向求出 u),向量 u 可以理解为相机的摆向,如:可以横着拍照,也可以竖着或斜着拍照。令 e = [0, 0, 0, 1]',则矩阵 A' = [s, u, -f, e] 为 正规矩阵,所以 ,。

        观测变换的本质是:建立一个新的坐标系,该坐标系以 eye 为坐标原点,以 s, u, -f 为 x 轴、 y 轴、z 轴,将原向量 v 映射到新坐标系下的向量 u。

public static void setLookAtM(float[] rm, int rmOffset, float eyeX, float eyeY, float eyeZ, float centerX, float centerY, float centerZ, float upX, float upY, float upZ)

4 透视变换
public static void frustumM(float[] m, int offset, float left, float right, float bottom, float top, float near, float far)

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/4684159.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-11-07
下一篇 2022-11-07

发表评论

登录后才能评论

评论列表(0条)

保存