这里说的视图旋转不是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总在摄像机朝向的前方。