实现球体缩放时光标下点屏幕位置不变的技术方案问询
实现Google Earth式的相机缩放(保持光标球点屏幕位置不变)
核心约束分析
相机始终瞄准原点,光标对应的球上点P的屏幕坐标需全程保持不变。这意味着P在相机视锥中的相对位置(横向/纵向偏移与深度的比值)必须恒定,推导后可知相机的移动轨迹被限制在原点O、点P、初始相机位置C三点确定的平面内,可简化为2D平面几何问题求解。
数学推导与实现步骤
假设:
- 球半径R=1,球心O(0,0,0)
- 初始相机位置C,到原点距离
d = length(C) - 光标对应的球上点P,满足
length(P)=1
1. 计算关键恒定比值k
k是P在相机视野中的相对位置比值,直接决定屏幕坐标的恒定关系:
vec3 cross_PC = cross(P, C); float len_cross = length(cross_PC); float dot_CC = dot(C, C); float dot_PC = dot(P, C); float k = len_cross / (dot_CC - dot_PC);
2. 确定新的相机距离d'
根据滚轮方向调整相机到原点的距离,示例如下:
// 滚轮向前(拉近)用0.9,向后(拉远)用1.1,可根据需求调整缩放系数 float scale = wheel_direction > 0 ? 1.1 : 0.9; float d_prime = d * scale;
3. 求解相机新方向的夹角参数
通过一元二次方程求解相机新位置与P点的夹角余弦值cosθ',并筛选符合约束的解:
float a = 1.0 + k*k; float b = -2.0 * k*k * d_prime; float c = k*k * d_prime*d_prime - 1.0; float discriminant = b*b - 4.0*a*c; float sqrt_disc = sqrt(discriminant); // 计算两个候选根 float cos_theta1 = (-b + sqrt_disc) / (2.0*a); float cos_theta2 = (-b - sqrt_disc) / (2.0*a); // 选择正确的根:需保证P在相机前方(d' - cosθ' > 0),且与初始方向同侧 float cos_theta; float sin_theta_initial = k * (d - dot_PC / d); float sin_theta1 = k * (d_prime - cos_theta1); float sin_theta2 = k * (d_prime - cos_theta2); if ((d_prime - cos_theta1) > 0.0 && sign(sin_theta1) == sign(sin_theta_initial)) { cos_theta = cos_theta1; } else { cos_theta = cos_theta2; } float sin_theta = k * (d_prime - cos_theta);
4. 计算新的相机位置
构造平面内垂直于P的单位向量Q,生成最终相机位置:
// 生成O-P-C平面内垂直于P的单位向量Q vec3 Q = normalize(C - dot_PC * P); // 计算新相机位置 vec3 C_prime = d_prime * (cos_theta * P + sin_theta * Q);
5. 更新视图矩阵
用新相机位置生成视图矩阵:
mat4 view_matrix = lookAt(C_prime, vec3(0.0), vec3(0.0, 0.0, 1.0));
关键说明
- 相机实际沿O-P-C平面内的圆弧轨迹移动,严格满足P点屏幕位置不变的约束
- 所有计算基于向量运算,可直接在Shader或CPU端实现,最终通过
lookAt生成视图矩阵即可
内容的提问来源于stack exchange,提问作者Basti
相关产品推荐
相关产品推荐

