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

在很多情况下,欧拉相机是可行的,但是会存在一些问题,如万向头死锁。

UVN相机是使用注视目标和向量u,v,n定义的。它与欧拉相机模型的差别是如何定义相机朝向,欧拉相机使用角度,而UVN相机使用向量。定义相机的朝向:右向量u( ux, uy, uz),上向量v( vx, vy, vz),注视向量n( nx, ny, nz),所有的向量之间是相互正交的,彼此线性无关。此外UVN向量系统还有一个注视点,指定了相机的观察方向,如图:

实际上UVN相机指定了XYZ的新朝向,假设相机处于一个位置(camx, camy, camz),指向了一个目标p点 (objx, objy, objz),那么对于这个目标物体如何进行相机坐标变换呢。

首先这个目标是相对于XYZ坐标系下的坐标(objx, objy, objz),而UVN相机的u,v,n向量坐标也是相对于XYZ坐标系下的坐标,那么我们如果可以得到这个目标在UVN坐标系即以u,v,n为坐标轴(uvn本身就是线性无关的)下的相对坐标,然后只需要进行平移操作就可以完成相机坐标转换了(因为得到相对坐标后,可以调整相机uvn向量方向与xyz坐标轴重合,而此时目标点在UVN坐标系下的相对坐标不变,所以只需要平移就可以完成变换),如何得到UVN坐标系下的相对坐标:

设目标在UVN坐标系下的相对坐标为(obju, objv, objn),obju就是目标在坐标轴u下的分量,objv就是坐标轴v,objn就是坐标轴n下的分量。

这里我们插入下向量点积的意义,向量a点乘向量b等于向量a的长度乘以向量b在向量a上的投影长度,即:
向量a * 向量b = |a|*|b|*cosθ 

也可以说|a|乘以向量b在向量a方向上的投影长度,那么如果向量a是单位向量,a和b向量的乘积就是向量b在a向量方向的分量。

然后我们回来看目标点 p(objx, objy, objz)在UVN坐标系下u轴方向上的分量为向量p点乘u轴正轴方向的单位向量,所以有:

obju = (objx, objy, objz) * (ux, uy, uz)  //这里的ux,uy,uz表示向量需要是单位向量

同理

objv = (objx, objy, objz) * (vx, vy, vz)

objn = (objx, objy, objz) * (nx, ny, nz)

终上我们可以得到目标点p(objx, objy, objz)在UVN坐标系下的相对坐标为
(obju, objv, objn, 1) = 

 (objx, objy, objz, 1) *

= (objx*ux+objy*uy+objz*uz, objx*vx+objy*vy+objz*vz, objx*nx+objy*ny+objz*nz, 1)

我们称矩阵Muvn为旋转矩阵:

根据旋转矩阵我们求得相对坐标后再继续平移操作,将相机位置(camx, camy, camz)移动到原点处,这样需要相机和目标点同时移动(-camx, -camy, -camz),可以得到平移矩阵Tcam:

最后我们可以得到UVN相机下的坐标变换矩阵Tuvn = Muvn * Tcam

 * 

=

现在我们已经知道了如何计算UVN相机模型的相机坐标变换,我们在看下如何计算UVN相机的uvn向量:

第一步:向量n = 目标点位置 - 观察参考点  = (objx, objy, objz) - (camx, camy, camz)

第二步:向量v我们先假设为(0, 1, 0),即y轴方向(这种假设是相机没有绕自身n轴旋转情况,否则需要将假设进行一旋转操作)

第三步:向量n = 向量v x 向量n(这里为什么不受n x v呢,因为是在左手坐标系下)

第四步:反求v,向量v = 向量n x 向量u

第五步:将向量u,v,n向量归一化

上面就是计算uvn向量的方式,但是有个问题就是第一步中,目标点的位置,一般情况下正好在注视向量方向上的目标点的位置是不好确定的,我们只知道相机的大概方向。所以,一种更自然的基于球面坐标系的UVN相机模型,这种模型需要指定相机位置,然后指定仰角和方位角,如图:

这是在右手坐标系下的球坐标系,我们指定仰角φ为相机朝向与Z轴夹角(范围0-180度),与正Z轴重合时为0,偏移角θ为相机朝向在XOY平面上的投影与正X轴之间的夹角,与正X轴重合时为0,球坐标半径长度ρ,我们有:

x = ρ * sin(φ) * cos(θ)

y = ρ * sin(φ) * sin(θ)

z = ρ * cos(φ)

指定了仰角和方位角,一般取ρ为1,这样得到的x, y, z就是注视向量n。到此UVN相机模型下的相机坐标变换就基本解决了。

左手坐标系下UVN相机模型下的相机坐标变换源码:
 

#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 VECTOR4D_CROSS(VECTOR4D_PTR va, VECTOR4D_PTR vb, VECTOR4D_PTR vc)
{
	vc->x = va->y*vb->z - vb->y*va->z;
	vc->y = vb->x*va->z - va->x*vb->z;
	vc->z = va->x*vb->y - vb->x*va->y;
	vc->w = 1;
}
 
//向量归一化
void VECTOR4D_Normalize(VECTOR4D_PTR va)
{
	float length = sqrtf(va->x*va->x+va->y*va->y+va->z*va->z);
	float length_inv = 1.0 / length;
 
	va->x = va->x *length_inv;
	va->y = va->y *length_inv;
	va->z = va->z *length_inv;
}
 
//向量复制
void VECTOR4D_COPY(VECTOR4D_PTR res, VECTOR4D_PTR src)
{
	res->x = src->x;
	res->y = src->y;
	res->z = src->z;
	res->w = src->w;
}
 
//向量初始化
void VECTOR4D_INITXYZ(VECTOR4D_PTR vt, float x, float y, float z)
{
	vt->x = x;
	vt->y = y;
	vt->z = z;
	vt->w = 1;
}
//矩阵初始化
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;
		}
	}
}
 
//基于左手坐标系UVN相机变换矩阵
void BuildMatrixCamUVN(CAM4DV1_PTR cam)
{
	MATRIX4X4 mt_inv, mt_uvn, mt_tmp;
	//平移矩阵
	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);
	//目标点
	float phi = cam->dir.x; //仰角(绕x轴旋转的角度,与z轴重合时为0,范围是0-180)
	float theta = cam->dir.z; //方位角(绕z轴旋转,与正x轴重合时为0)
 
	cam->target.x = 1.0 * sin(phi) * cos(theta);
	cam->target.y = 1.0 * sin(phi) * sin(theta);
	cam->target.z = 1.0 * cos(phi);
 
	//n向量
	VECTOR4D_COPY(&cam->n, &cam->target);
	//初始化v向量
	VECTOR4D_INITXYZ(&cam->v, 0, 1, 0);
	//u向量
	VECTOR4D_CROSS(&cam->v, &cam->n, &cam->u);
	//反求v
	VECTOR4D_CROSS(&cam->n, &cam->u, &cam->v);
	//归一化
	VECTOR4D_Normalize(&cam->u);
	VECTOR4D_Normalize(&cam->v);
	VECTOR4D_Normalize(&cam->n);
	//uvn旋转矩阵
	Mat_Init_4X4(&mt_uvn, cam->u.x, cam->v.x, cam->n.x, 0,
		cam->u.y, cam->v.y, cam->n.y, 0,
		cam->u.z, cam->v.z, cam->n.z, 0,
		0, 0, 0, 1);
 
	Mat_Mul_4X4(&mt_inv, &mt_uvn, &cam->mcam);
}
随机文章
3D图形学总结(十)—纹理映射透视矫正 3D图形学总结(十一)—深度缓存 3D图形学总结(四)—透视坐标变换 3D图形学总结(十二)—纹理滤波 lua的sort排序