一、什么是视锥体?——形象比喻
想象你用手做了一个“望远镜”或者“漏斗”,
你眼睛看到的空间,就是一个锥形的区域,
这个区域叫做视锥体(View Frustum)。
- 摄像机就像你的眼睛
- 视锥体就是你能看到的“空间范围”
视锥体有6个面:上、下、左、右、前、后,
就像一个被切掉头尾的金字塔。
二、视锥体的6个平面和法向量朝向
1. 6个平面
- 左平面(Left)
- 右平面(Right)
- 上平面(Top)
- 下平面(Bottom)
- 近裁剪面(Near)(靠近摄像机的那一面)
- 远裁剪面(Far)(远离摄像机的那一面)
2. 法向量的朝向
法向量就像每个平面上竖着的一根“箭头”,
都指向视锥体的内部,也就是摄像机能看到的空间。
- 左、右、上、下平面的法向量都“斜着”指向视锥体内部
- 近裁剪面法向量指向视锥体内部(远离摄像机)
- 远裁剪面法向量指向视锥体内部(朝向摄像机)
3. 形象图示
/\
/ \
/ \
/------\
/ | | \
/ | | \
/ | | \
/----|----|----\
Near Far
(俯视图,摄像机在左,视锥体向右展开)
- 每个斜面(左、右、上、下)都像一面“墙”,法向量都朝向视锥体内部
- Near面法向量指向视锥体内部(远离摄像机)
- Far面法向量指向视锥体内部(朝向摄像机)
三、计算6个平面的法向量和方程
1. 已知条件
2. 计算视锥体8个顶点
- 先计算Near面和Far面中心点
- 再根据FOV和宽高比,算出Near面和Far面的宽高
- 得到8个顶点(Near面4个,Far面4个)
3. 计算每个平面的法向量
以左平面为例:
- 取摄像机位置、Near面左上角、Far面左上角三个点
- 用向量叉乘得到法向量
具体步骤:
其他平面同理:
4. 平面方程
四、方向图形(示意)
摄像机
|
v
Near面
/ \
/ \
左平面/ \右平面
/ \
/ \
/ \
/ \
上平面 下平面
\ /
\ /
\ /
\ /
\ /
\ /
Far面
每个平面的法向量都指向视锥体内部
五、总结口诀
“六面包围视野锥,法向量都朝里推;
叉乘三点得法线,判断可见靠它归。”
六、实际应用
- 视锥体裁剪:判断物体是否在摄像机视野内
- 碰撞检测:判断射线/物体是否穿过视锥体
- 光照/阴影:判断光源照射范围
七、简要伪代码
# 以左平面为例
left_normal = cross(NearTopLeft - CameraPos, FarTopLeft - CameraPos)
left_normal = normalize(left_normal)
left_plane = (left_normal, NearTopLeft) # 法向量+平面上一点
# 其他平面同理
继续研究用视锥体的6个平面做物体可见性判断,在游戏开发和3D渲染中叫做“视锥体裁剪(Frustum Culling)”。它的作用是:只渲染玩家摄像机能看到的物体,节省大量计算资源。
下面用生动形象的方式详细讲解:
一、形象比喻
想象你拿着一个漏斗(视锥体),眼睛贴在漏斗小口(摄像机)往外看。
只有在漏斗范围内的东西你才能看到,漏斗外的东西你看不到。
我们要做的,就是判断一个物体(比如一个箱子)是不是在漏斗里面。
二、基本原理
- 视锥体由6个平面包围(上、下、左、右、近、远)。
- 每个平面都有一个“朝向视锥体内部”的法向量和方程 (Ax+By+Cz+D=0)。
- 物体通常用“包围盒”(AABB)或“包围球”来近似表示。
三、判断流程
1. 以包围盒为例
- 包围盒有8个顶点(立方体的8个角)。
2. 对每个平面做判断
- 对于每个平面,把包围盒的8个顶点都代入平面方程。
- 计算 (Ax+By+Cz+D) 的值。
3. 判断结果
- 如果8个点都在平面“外侧”(即 (Ax+By+Cz+D < 0)),说明物体完全在视锥体外部,可以直接丢弃,不渲染。
- 如果有点在平面“内侧”(即 (Ax+By+Cz+D \geq 0)),说明物体有一部分在视锥体内,需要渲染。
4. 对6个平面都做一次
- 只要有一个平面“全部在外”,就可以丢弃。
- 只有所有平面都“不是全部在外”,物体才可能被看到。
四、图示说明
视锥体(漏斗) 包围盒(箱子)
/------\ +------+
/ \ / /|
/ \ +------+ |
/------------\ | | +
| | | |/
\------------/ +------+
- 箱子在漏斗内:可见
- 箱子在漏斗外:不可见
- 箱子部分在漏斗内:可见
五、伪代码示例
def is_box_in_frustum(box_vertices, frustum_planes):
for plane in frustum_planes:
# 检查包围盒的8个顶点是否都在平面外侧
if all(plane.A * v.x + plane.B * v.y + plane.C * v.z + plane.D < 0 for v in box_vertices):
return False # 物体完全在视锥体外
return True # 物体有一部分在视锥体内
六、包围球的判断(更快)
- 计算球心到平面的距离 (d = A x_0 + B y_0 + C z_0 + D)
- 如果 (d < -r)(r为半径),球完全在平面外侧,可丢弃
- 如果 (d \geq -r),球有一部分在视锥体内
七、口诀总结
“六面包围视野锥,八点代入看内外;
全外丢弃节性能,部分可见才渲染。”
八、实际应用
- 游戏场景中,成千上万个物体,只有视锥体内的才会被渲染
- 大大提升帧率和性能