【Overload游戏引擎分析】从视图投影矩阵提取视锥体及overload对视锥体的封装

overoad代码中包含一段有意思的代码,可以从视图投影矩阵逆推出摄像机的视锥体,本文来分析一下原理

一、平面的方程

视锥体是用平面来表示的,所以先看看平面的数学表达。

平面方程可以由其法线N=(A, B, C)和一个点Q=(x0,y0,z0)定义,其形式为:
A ( x − x 0 ) + B ( y − y 0 ) + C ( z − z 0 ) = 0 A(x-x_{0})+B(y-y_{0})+C(z-z_{0})=0 A(x−x0)+B(y−y0)+C(z−z0)=0 整理变为: A x + B y + C z + D = 0 Ax+By+Cz+D=0 Ax+By+Cz+D=0, 其中 D = − A x 0 − B y 0 − C z 0 D=−Ax_{0}−By_{0}−Cz_{0} D=−Ax0−By0−Cz0

方程进一步可以将方程归一化:
A A 2 + B 2 + C 2 x + B A 2 + B 2 + C 2 y + C A 2 + B 2 + C 2 z + D A 2 + B 2 + C 2 = 0 \frac{A}{\sqrt{A^{2}+B^{2}+C^{2} } } x + \frac{B}{\sqrt{A^{2}+B^{2}+C^{2} } }y+\frac{C}{\sqrt{A^{2}+B^{2}+C^{2} } }z+\frac{D}{\sqrt{A^{2}+B^{2}+C^{2} } } = 0 A2+B2+C2 Ax+A2+B2+C2 By+A2+B2+C2 Cz+A2+B2+C2 D=0 写成通用格式 a x + b y + c z + d = 0 ax+by+cz+d=0 ax+by+cz+d=0

那么点 p = ( x 1 , y 1 , z 1 ) p=(x_{1}, y_{1}, z_{1}) p=(x1,y1,z1)到平面的距离为:
D = a x 1 + b y 1 + c z 1 + d D=ax_{1}+by_{1}+cz_{1}+d D=ax1+by1+cz1+d

一个平面会将空间分成两个半空间(halfspace),进一步法线的朝向的空间称为正半空间(positive halfspace),法线背离的空间称为反半空间(negative halfspace)。根据D的符号可以判断点的相对位置:

  • D < 0, 点位于反半空间
  • D = 0, 点位于平面上
  • D > 0, 点位于正半空间

这种特性可用于判断点是否在视锥体内部。

二、OpenGL视锥体

视锥体是摄像机能看到的区域,只有在视锥体内的物体才能被看到。其由近平面、远平面与周围四个面组成,形成一个平截头体区域。

三、Overload对视锥体的封装

Overload对视锥体的封装在文件Frustum.h、Frustum.cpp中。先看其定义:

cpp 复制代码
class Frustum
{
public:
	/**
	* 根据视图投影矩阵提取视锥体
	* @param p_viewProjection
	*/ 
	void CalculateFrustum(const OvMaths::FMatrix4& _viewProjection);
	/**
	* 判断点是不是在视锥体内
	* @param p_x
	* @param p_y
	* @param p_z
	*/
	bool PointInFrustum(float p_x, float p_y, float _z) const;
	/**
	* 判断球是不是在视锥体内
	* @param p_x
	* @param p_y
	* @param p_z
	* @param p_radius
	*/
	bool SphereInFrustum(float p_x, float p_y, loat p_z, float p_radius) const;
	/**
	* 判断立方体是不是在视锥体内
	* @param p_x
	* @param p_y
	* @param p_z
	* @param p_size
	*/
	bool CubeInFrustum(float p_x, float p_y, float _z, float p_size) const;
	/**
	* 判断包围球是不是在视锥体内
	* @param p_boundingSphere
	* @param p_transform
	*/
	bool BoundingSphereInFrustum(const vRendering::Geometry::BoundingSphere& _boundingSphere, const OvMaths::FTransform& _transform) const;
	/**
	* 返回近平面
	*/
	std::array<float, 4> GetNearPlane() const;
	/**
	* 返回远平面
	*/
	std::array<float, 4> GetFarPlane() const;
private:
	float m_frustum[6][4];  // 6个平面的方程参数
};

m_frustum保存着6个平面的方程参数,为了提升操作便利性,其定义了两个枚举作为索引:

cpp 复制代码
enum FrustumSide
{
	RIGHT = 0,		// The RIGHT side of the frustum
	LEFT = 1,		// The LEFT	 side of the frustum
	BOTTOM = 2,		// The BOTTOM side of the frustum
	TOP = 3,		// The TOP side of the frustum
	BACK = 4,		// The BACK	side of the frustum
	FRONT = 5		// The FRONT side of the frustum
};

// 平面方程的参数索引
enum PlaneData
{
	A = 0,				// The X value of the plane's normal
	B = 1,				// The Y value of the plane's normal
	C = 2,				// The Z value of the plane's normal
	D = 3				// The distance the plane is from the origin
};

函数的具体实现在文件Frustum.cpp中,我们先看最基础的判断点是否在视锥体内:

cpp 复制代码
bool OvRendering::Data::Frustum::PointInFrustum(float x, float y, float z) const
{
	for (int i = 0; i < 6; i++)
	{
		if (m_frustum[i][A] * x + m_frustum[i][B] * y + m_frustum[i][C] * z + m_frustum[i][D] <= 0)
		{
			return false;
		}
	}
	return true;
}

定义视锥体的面法线都是朝外的,如果点在视锥体内,点到6个面的距离必须全部小于0。进一步判断球体是否完全在视锥体内,距离必须小于半径的负数。

最后分析一下CalculateFrustum,它是根据一个视图投影矩阵反向构建一个视锥体,具体公式怎么来的可以参考这篇文章,里面将的特别详细:
Fast Extraction of Viewing Frustum Planes from the World View-Projection Matrix

其本身的代码没啥好说的,无非就是公式的翻译。

相关推荐
郑寿昌9 小时前
UE5与UE6在Lumen和Nanite的差异解析
游戏引擎·图形渲染·着色器
郑寿昌19 小时前
UE6 AI加速Lumen光线追踪降噪技术解析
人工智能·游戏引擎
晴夏。19 小时前
GAS下的网络同步的全面分析【超级全面】
游戏引擎·ue·gas·网络同步
田鸡_19 小时前
Unity新输入系统(Input System)教学篇
unity·游戏引擎·游戏程序
EQ-雪梨蛋花汤20 小时前
【Unity笔记】Unity 音游模板与免费资源:高效构建节奏游戏开发全指南
笔记·unity·游戏引擎
微莱羽墨20 小时前
零、0基础入门Unity 安装详细教程(2026最新版教程,安装Unity看这一篇就够了!)
unity·游戏引擎·unity安装
nnsix21 小时前
Unity 刚体的 默认力、瞬时力 区别
unity·游戏引擎
nnsix21 小时前
Unity Sprite的 Generate Physics Shape 参数解释
unity·游戏引擎
魔士于安21 小时前
Unity完整小球迷宫项目
前端·unity·游戏引擎·贴图·模型
め.21 小时前
Unity协程的原理
unity·游戏引擎