欢迎来到NiceSpace!祝大家开心每一天!
  • C++
  • 图形学
3D图形学总结(二)—相机坐标转换(欧拉相机)

物体放置到世界中后,在欧拉相机模型下转换成相机坐标。

欧拉相机模型是使用位置和旋转角度(欧拉角度)定义的,其中旋转角度决定了相机的朝向。

相机位置在(camx, camy, camz),观察角度为(0,angy,0),即相机绕y轴顺时针旋转angy。如图:

如图所示,要想转换成相机坐标,首先将相机移动到原点处,这样物体的每个点都平移(-camx,-camy,-camz),如图:

物体的每个点都进行平移运算,可以通过矩阵运算完成,平移矩阵Tcam:

顶点v( world_x, world_y, world_z, 1 ) * Tcam = ( world_x - camx, world_y - camy, world_z - camz, 1)

完成平移运算后进行旋转操作,欧拉相机的指向方向是绕Y轴顺时针旋转angy,所以要想完成相机坐标变换,让相机的朝向与Z轴重合,相机和物体需要旋转 -angy,即逆时针旋转angy,如图:

这样相机位置(0,0,0),相机角度(0,0,0),就完成了欧拉相机的相机坐标变换,上面是针对相机朝向为(0,angy,0)的情况,一般情况下相机朝向为(angx,angy,angz),所以相机需要进行一系列的旋转转换,3个角度的旋转顺序有6种,我们取yxz的顺序,先绕Y轴旋转-angy,再绕x轴旋转-angx,最后绕Z轴旋转-angz。但是旋转操作需要如何计算呢,以相机绕X轴旋转为例,在相机绕X轴旋转的过程中,相机朝向的x分量是不变的,所以我们可以在二维平面上推导,如图:

如图所示是左手坐标系下,我们站在正X轴方向向负X轴看去得到的平面图,我们设当前欧拉相机的朝向在YOZ平面的投影为OA方向,OA与Z轴正向夹角为α,此时相机绕着X轴顺时针旋转β到OB,点A在Z轴投影为A´,点B再Z轴投影为B´,A点坐标为(z1,y1),B点坐标为(z2,y2),OA的长度为r,因此我们有以下两个推导:

y2 = r * sin( α - β ) = r * (sinα*cosβ - cosα*sinβ) = r * sinα*cosβ - r * cosα*sinβ =  z1 * (-sinβ ) + y1*cosβ

z2 = r * cos( α - β ) = r * (cosα*cosβ + sinα*sinβ)  = r * cosα*cosβ + r * sinα*sinβ = z1 * cosβ + y1 * sinβ

所以相机绕X轴顺时针旋转β时的计算公式为:

y2 = y1*cosβ + z1 * (-sinβ )

z2 = y1 * sinβ + z1 * cosβ 

同理我们可以t通过同样的方式算出相机绕其他两个轴的计算公式,上述是相机旋转β后的计算公式,我们要完成相机坐标变换只需要将物体顶点反方向旋转β,即 使用-β带入上面公式中就可以了。根据推导结果我们可以看出旋转操作可以通过矩阵运算来完成,物体顶点v (x, y, z, 1),经过绕X轴旋转 -β:

(x, y, z, 1) *    = ( x, y * cos(-β) - z * sin(-β), y * sin(-β) + z * cos(-β), 1)

所以,绕X轴旋转的相机坐标变换矩阵Rcamx为:

通过计算,绕Y轴旋转的相机坐标变换矩阵Rcamy:

绕Z轴旋转的相机坐标变换矩阵Rcamz:

这样我们得到了3个轴的旋转变换矩阵Rcamx,Rcamy,Rcamz,加上之前的相机平移矩阵Tcam,对于物体的顶点v( world_x, world_y, world_z)经过相机坐标转换后的结果为:

( world_x, world_y, world_z, 1) * Tcam * Rcamy * Rcamx * Rcamz

操作为点v先经过平移( -camx, -camy, -camz, 1),然后绕Y轴旋转-angy,在绕X轴旋转-angx,最后绕Z轴旋转-angz。而矩阵

Tres = Tcam * Rcamy * Rcamx * Rcamz 就是欧拉相机坐标变换矩阵。

构建欧拉相机坐标变换矩阵源码:

#include <math.h>

//点和向量四维
typedef struct VECTOR4D_TYP
{
	union
	{
		float M[4];
		struct
		{
			float x, y, z, w;
		};
	};
} VECTOR4D, POINT4D, *VECTOR4D_PTR, *POINT4D_PTR;

//4x4矩阵
typedef struct MATRIX4X4_TYP
{
	union
	{
		float M[4][4];
		struct
		{
			float M00, M01, M02, M03;
			float M10, M11, M12, M13;
			float M20, M21, M22, M23;
			float M30, M31, M32, M33;
		};
	};
} MATRIX4X4, *MATRIX4X4_PTR;

//相机结构
typedef struct CAM4DV1_TYP
{
	int state;
	int attr;

	POINT4D pos; //相机在世界坐标中的位置
	VECTOR4D dir; //欧拉角度或者UVN相机模型的注视方向

	VECTOR4D u;
	VECTOR4D v;
	VECTOR4D n;
	POINT4D target;

	float view_dist;//视距
	float fov; //水平方向和垂直方向视野
	float near_clip_z;//近裁剪面
	float far_clip_z;//远裁剪面

	//上下左右裁剪面 略

	float viewplane_width;//视平面宽度
	float viewplane_height;//视平面高度

	float viewport_width;//视口宽度
	float viewport_heght;//视口高度
	float viewport_center_x;//视口中心x
	float viewport_center_y;//视口中心y

	float aspect_radio; //宽高比

	MATRIX4X4 mcam; //相机变换矩阵
	MATRIX4X4 mper; //透视变换矩阵
	MATRIX4X4 mscr; //屏幕变换矩阵

}CAM4DV1, *CAM4DV1_PTR;

//矩阵初始化
void Mat_Init_4X4(MATRIX4X4_PTR ma,
	float m00, float m01, float m02, float m03,
	float m10, float m11, float m12, float m13,
	float m20, float m21, float m22, float m23,
	float m30, float m31, float m32, float m33)

{
	ma->M00 = m00; ma->M01 = m01; ma->M02 = m02; ma->M03 = m03;
	ma->M10 = m10; ma->M11 = m11; ma->M12 = m12; ma->M13 = m13;
	ma->M20 = m20; ma->M21 = m21; ma->M22 = m22; ma->M23 = m23;
	ma->M30 = m30; ma->M31 = m31; ma->M32 = m32; ma->M33 = m33;

}

//矩阵和矩阵变换函数
void Mat_Mul_4X4(MATRIX4X4_PTR ma, MATRIX4X4_PTR mb, MATRIX4X4_PTR mc)
{
	for (int row = 0; row < 4; row++)
	{
		for (int col = 0; col < 4; col++)
		{
			float tmp = 0;
			for (int i = 0; i < 4; i++)
			{
				tmp += ma->M[row][i] * mb->M[i][col];
			}
			mc->M[row][col] = tmp;
		}
	}
}

//构建欧拉相机相机转换矩阵
void BuildMatrixCamEuler(CAM4DV1_PTR cam)
{
	MATRIX4X4 mt_inv, mx_inv, my_inv, mz_inv, mrot, mtmp;
	//相机平移矩阵
	Mat_Init_4X4(&mt_inv, 1, 0, 0, 0,
		0, 1, 0, 0,
		0, 0, 1, 0,
		-cam->pos.x, -cam->pos.y,-cam->pos.z, 1);
	//旋转矩阵
	double theta_x = cam->dir.x;
	double theta_y = cam->dir.y;
	double theta_z = cam->dir.z;
	//x的正弦和余弦
	double cos_theta = cos(theta_x);
	double sin_theta = -sin(theta_x);
	Mat_Init_4X4(&mx_inv, 1, 0, 0, 0,
		0, cos_theta, sin_theta, 0,
		0, -sin_theta, cos_theta, 0,
		0, 0, 0, 1);
	//y的正弦和余弦
	cos_theta = cos(theta_y);
	sin_theta = -sin(theta_y);
	Mat_Init_4X4(&my_inv, cos_theta, 0, -sin_theta, 0,
		0, 1, 0, 0,
		sin_theta, 0, cos_theta, 0,
		0, 0, 0, 1);
	//z的正弦和余弦
	cos_theta = cos(theta_z);
	sin_theta = -sin(theta_z);
	Mat_Init_4X4(&mz_inv, cos_theta, sin_theta, 0, 0,
		-sin_theta, cos_theta, 0, 0,
		0, 0, 1, 0,
		0, 0, 0, 1);
	//逆矩阵积yxz顺序
	Mat_Mul_4X4(&my_inv, &mx_inv, &mtmp);
	Mat_Mul_4X4(&mtmp, &mz_inv, &mrot);
	//相机变换矩阵
	Mat_Mul_4X4(&mt_inv, &mrot, &cam->mcam);
}

 

随机文章
3D图形学总结(九)—3D裁剪 Django本地配置ckeditor(windows系统) 一个简单的CSS加载动画 python爬虫之爬取捧腹网段子 3D图形学总结—总览
推荐文章