这里说的视图旋转不是FPS游戏里面那种第一人称的视图旋转,那个处理起来比较自然。这里说的视图旋转是类似3ds Max, Maya这种3D设计软件里面的第三人称的视图旋转。有的系统里面叫做tumble,有的人习惯叫orbit,有的系统里面干脆就叫rotate。不管怎样,这个看似非常基础的算法,网上居然几乎找不到理想的方法说明,我简直震惊了。好吧,经过一些实验,自己写了一个算法,经测试各种角度的旋转都是完美的,共享出来大家鉴定下。如果有更好的算法,还请留言告诉我。我们先来看看实现:
eiBool need_init;
eiNode *cam_inst = ei_edit_node(cam_inst_name, &need_init);
eiIndex xform_pid = ei_node_find_param(cam_inst, "transform");
eiIndex motion_xform_pid = ei_node_find_param(cam_inst, "motion_transform");
eiMatrix xform_val = *ei_node_get_matrix(cam_inst, xform_pid);
eiVector up_vector = ei_vector(0.0f, 0.0f, 1.0f);
eiVector camera_pos = point_transform(ei_vector(0.0f, 0.0f, 0.0f), xform_val);
eiVector cam_up = normalize(vector_transform(ei_vector(0.0f, 1.0f, 0.0f), xform_val));
eiVector cam_right = normalize(vector_transform(ei_vector(1.0f, 0.0f, 0.0f), xform_val));
eiVector cam_dir = normalize(vector_transform(ei_vector(0.0f, 0.0f, -1.0f), xform_val));
if (!target_set) /* initialize camera to align with original view */
{
eiVector obj_center = ei_vector(0.0f, 0.0f, 0.0f);
point_on_plane(camera_target, camera_pos, -cam_dir, obj_center);
target_set = EI_TRUE;
}
eiVector target_vec = camera_pos - camera_target;
eiScalar target_dist = normalize_len(target_vec, target_vec);
/* make horizontal speed slower when approaching up vector */
eiScalar horiz_speed = 1.0f - 0.7f * absf(dot(up_vector, target_vec));
target_vec = vector_transform(target_vec, rotate(radians(offset[0] * -0.2f * horiz_speed), cam_up));
target_vec = vector_transform(target_vec, rotate(radians(offset[1] * -0.2f), cam_right));
camera_pos = camera_target + target_vec * target_dist;
eiVector camera_dir_z = target_vec;
eiVector camera_dir_x = cross(up_vector, camera_dir_z);
if (absf(dot(up_vector, camera_dir_z)) > 0.99f) /* fix up vector precision issue */
{
eiVector fixed_up = cross(camera_dir_z, cam_right);
camera_dir_x = cross(fixed_up, camera_dir_z);
}
if (dot(cam_right, camera_dir_x) < 0.0f) /* fix sudden flip when approaching up vector */
{
camera_dir_x = - camera_dir_x;
}
eiVector camera_dir_y = cross(camera_dir_z, camera_dir_x);
xform_val = ei_matrix(
camera_dir_x.x, camera_dir_x.y, camera_dir_x.z, 0.0f,
camera_dir_y.x, camera_dir_y.y, camera_dir_y.z, 0.0f,
camera_dir_z.x, camera_dir_z.y, camera_dir_z.z, 0.0f,
camera_pos.x, camera_pos.y, camera_pos.z, 1.0f);
ei_node_set_matrix(cam_inst, xform_pid, &xform_val);
ei_node_set_matrix(cam_inst, motion_xform_pid, &xform_val);
ei_end_edit_node(cam_inst);
实现是基于Elara SDK写的(Elara是一个离线渲染器,详见:www.rendease.com),不过都是基本的数学函数,看起来应该不复杂,我就不改写了。
说一下比较核心的几个地方:
1. 一定要基于up vector构造新的摄像机坐标系,否则我发现总会产生转多了视图倾斜的问题。up vector的方向也是不同的系统中使用这个算法需要注意的地方,这里用的是跟3ds Max兼容的up vector即Z轴朝上方向。
2. 初始化的时候,一定要计算好摄像机target点的位置,否则第一次旋转会产生跳变。
3. 因为基于up vector构造坐标系,一定要处理下摄像机朝向旋转到与up vector很接近的方向时候的精度问题。
4. 当旋转过顶部的时候,由于与up vector叉乘产生的向量的方向突变,所以会产生跳变,这里要特殊处理一下。
5. 摄像机朝向越接up vector的方向,绕up vector旋转的速度应该越慢,这样看起来不会产生跳跃感。
6. point_on_plane算法中,要取绝对值,不能用signed distance,以保证camera target总在摄像机朝向的前方。