在傳統相機模擬中,地面通常被建模為一個平面(Plane),這在低空場景下是合理的。但在更大視場范圍或遠距觀察時,地球的曲率不可忽視。因此,我們需要將地面模型從平面升級為球體,并基于球面與光線的幾何關系判斷每個像素是否看到地面或天空,并進行地面貼圖映射。
本文將介紹完整的數學推導與 C++ 實現邏輯,適用于任何高度、視角和分辨率的相機建模。
一、模型定義
1.1 地球模型
我們將地球建模為一個完美球體:
地球半徑(單位:米):
R=6371000地球球心坐標(世界坐標系原點):
O=(0,0,0)地面貼圖采用等距圓柱投影(Equirectangular projection)
1.2 相機定義
相機位置在球心正上方,高度為H:
相機方向向量(單位向量):
水平方向X、垂直方向Y、視軸方向Z構成相機坐標系:
相機分辨率為 (W, H),視場角為fov。
二、光線生成與交點判斷
2.1 像素對應的光線生成
對于每個像素 (i, j),我們將其映射為歸一化視場坐標:
水平范圍 [-1, 1],垂直范圍 [-1, 1]
假設視場角為fov,焦距為:
則該像素對應的相機坐標為:
再通過相機基向量轉換為世界方向:
得到從相機發出的世界坐標系下的光線:
2.2 光線與球體求交
地球表面滿足球面方程:
將光線代入球面方程,得到一元二次方程:
展開并整理為:
若
:光線未擊中地球,像素顯示天空
若
:光線擊中地球,交點為:
(若 則交點在背后,也認為看向天空)
三、紋理映射(球坐標轉二維紋理)
3.1 世界坐標 → 球坐標
交點為:
轉換為球坐標(經緯度):
經度(longitude):
緯度(latitude):
3.2 球坐標 → 貼圖坐標 (u, v)
將經緯度映射為貼圖坐標(歸一化到[0, 1]):
四、C++ 實現核心邏輯
cv::Vec3b tracePixelRay(int px, int py, int W, int H, double fov, const cv::Vec3d& camPos, const cv::Vec3d& X, const cv::Vec3d& Y, const cv::Vec3d& Z, const cv::Mat& texture)
{double aspect = double(W) / H;double fx = (W / 2.0) / tan(fov / 2.0);double fy = fx;double x = (px - W / 2.0) / fx;double y = (H / 2.0 - py) / fy;cv::Vec3d dir_cam = cv::normalize(cv::Vec3d(x, y, -1));cv::Vec3d dir_world = cv::normalize(X * dir_cam[0] + Y * dir_cam[1] + Z * dir_cam[2]);// 光線-球面求交double R = 6371000.0;double a = 1.0;double b = 2.0 * camPos.dot(dir_world);double c = camPos.dot(camPos) - R * R;double delta = b * b - 4 * a * c;if (delta < 0) return cv::Vec3b(0, 0, 0); // 天空double t = (-b - sqrt(delta)) / (2 * a);if (t <= 0) return cv::Vec3b(0, 0, 0); // 相機后方// 計算交點cv::Vec3d P = camPos + t * dir_world;double theta = atan2(P[1], P[0]);double phi = acos(P[2] / R);double u = (theta + CV_PI) / (2 * CV_PI);double v = phi / CV_PI;int tex_u = u * texture.cols;int tex_v = v * texture.rows;tex_u = std::clamp(tex_u, 0, texture.cols - 1);tex_v = std::clamp(tex_v, 0, texture.rows - 1);return texture.at<cv::Vec3b>(tex_v, tex_u);
}
五、主循環調用
cv::Mat render(int W, int H, double fov_rad, double height, const cv::Vec3d& D_view, const cv::Mat& texture)
{double R = 6371000.0;cv::Vec3d camPos = cv::Vec3d(0, 0, R + height);cv::Vec3d up = cv::Vec3d(0, 1, 0);cv::Vec3d Z = -cv::normalize(D_view);cv::Vec3d X = cv::normalize(up.cross(Z));cv::Vec3d Y = Z.cross(X);cv::Mat img(H, W, CV_8UC3);for (int j = 0; j < H; ++j){for (int i = 0; i < W; ++i){img.at<cv::Vec3b>(j, i) = tracePixelRay(i, j, W, H, fov_rad, camPos, X, Y, Z, texture);}}return img;
}
總結
通過將地面建模為球體并結合光線-球面交點分析,我們能夠精確判斷每一條視線是否擊中地球,實現真實地面與天空的分界。同時,利用等距圓柱投影將地球紋理映射到圖像,完成完整可視化。這種方法可廣泛應用于衛星成像模擬、導引頭視場渲染、地球可視仿真等場景。