手把手教你架构3D引擎高级篇系列六

前面给读者介绍了哈希表的封装,其实我们实现的是数据结构算法,只是把基本的数据结构算法进行了一个适合引擎的封装。引擎中使用了大量的数学知识,下面我们开始封装向量,矩阵运算。C++为我们提供了Vector3和Matrix的封装,但是引擎在这块为了扩展方便还是需要自己封装的,比如Ogre,Unity,UE4以及一些大公司自研引擎都是自己封装。

向量

向量在游戏中用的非常多,计算两个物体之间的距离,一个物体朝另一个物体移动等等,都会用用到向量的运算,向量运算包括:向量相加,向量相减,向量点乘,向量叉乘,向量长度,向量单位化等等。向量有二维,三维,四维向量之分,引擎都需要逐一封装,向量的封装没有什么特殊的地方,随便在网上一搜索就是一大堆,这里给读者把代码分享一二。

void Vec3::normalize()
{
	float x = this->x;
	float y = this->y;
	float z = this->z;
	float inv_len = 1 / sqrt(x * x + y * y + z * z);
	x *= inv_len;
	y *= inv_len;
	z *= inv_len;
	this->x = x;
	this->y = y;
	this->z = z;
}


Vec3 Vec3::normalized() const
{
	float x = this->x;
	float y = this->y;
	float z = this->z;
	float inv_len = 1 / sqrt(x * x + y * y + z * z);
	x *= inv_len;
	y *= inv_len;
	z *= inv_len;
	return Vec3(x, y, z);
}


float Vec3::length() const
{
	float x = this->x;
	float y = this->y;
	float z = this->z;
	return sqrt(x * x + y * y + z * z);
}

以上是计算向量的单位化以及向量长度,再看看矩阵的封装:

矩阵

矩阵运算用的最多,模型的位移,旋转,缩放等都与矩阵的变换相关,矩阵有相乘,单位化,旋转,转置以及与欧拉角的转换等运算。

void Matrix::fromEuler(float yaw, float pitch, float roll)
{
	float sroll = sinf(roll);
	float croll = cosf(roll);
	float spitch = sinf(pitch);
	float cpitch = cosf(pitch);
	float syaw = sinf(yaw);
	float cyaw = cosf(yaw);

	m11 = sroll * spitch * syaw + croll * cyaw;
	m12 = sroll * cpitch;
	m13 = sroll * spitch * cyaw - croll * syaw;
	m14 = 0.0f;
	m21 = croll * spitch * syaw - sroll * cyaw;
	m22 = croll * cpitch;
	m23 = croll * spitch * cyaw + sroll * syaw;
	m24 = 0.0f;
	m31 = cpitch * syaw;
	m32 = -spitch;
	m33 = cpitch * cyaw;
	m34 = 0.0f;
	m41 = 0.0f;
	m42 = 0.0f;
	m43 = 0.0f;
	m44 = 1.0f;
}


Matrix Matrix::rotationX(float angle)
{
	Matrix m = IDENTITY;
	float c = cosf(angle);
	float s = sinf(angle);

	m.m22 = m.m33 = c;
	m.m32 = -s;
	m.m23 = s;

	return m;
}


Matrix Matrix::rotationY(float angle)
{
	Matrix m = IDENTITY;
	float c = cosf(angle);
	float s = sinf(angle);

	m.m11 = m.m33 = c;
	m.m31 = s;
	m.m13 = -s;

	return m;
}

Matrix Matrix::rotationZ(float angle)
{
	Matrix m = IDENTITY;
	float c = cosf(angle);
	float s = sinf(angle);

	m.m11 = m.m22 = c;
	m.m21 = -s;
	m.m12 = s;
	
	return m;
}

除了向量和矩阵的封装,我们还会封装一些常用数学公式如下所示:

Math

数学常用的算法很多,比如射判断线与平面相交,射线与球体相交,射线与立方体相交等等,角度与弧度的相互转化等等。封装部分代码如下所示:

Vec3 degreesToRadians(const Vec3& v)
{
	return Vec3(degreesToRadians(v.x), degreesToRadians(v.y), degreesToRadians(v.z));
}

Vec3 radiansToDegrees(const Vec3& v)
{
	return Vec3(radiansToDegrees(v.x), radiansToDegrees(v.y), radiansToDegrees(v.z));
}

float angleDiff(float a, float b)
{
	float delta = a - b;
	delta = fmodf(delta, PI * 2);
	if (delta > PI) return -PI * 2 + delta;
	if (delta < -PI) return PI * 2 + delta;
	return delta;
}

bool getRayPlaneIntersecion(const Vec3& origin,
	const Vec3& dir,
	const Vec3& plane_point,
	const Vec3& normal,
	float& out)
{
	float d = dotProduct(dir, normal);
	if (d == 0)
	{
		return false;
	}
	d = dotProduct(plane_point - origin, normal) / d;
	out = d;
	return true;
}

bool getRaySphereIntersection(const Vec3& origin,
	const Vec3& dir,
	const Vec3& center,
	float radius,
	Vec3& out)
{
	ASSERT(dir.length() < 1.01f && dir.length() > 0.99f);
	Vec3 L = center - origin;
	float tca = dotProduct(L, dir);
	if (tca < 0) return false;
	float d2 = dotProduct(L, L) - tca * tca;
	if (d2 > radius * radius) return false;
	float thc = sqrt(radius * radius - d2);
	float t0 = tca - thc;
	out = origin + dir * t0;
	return true;
}

bool getRayAABBIntersection(const Vec3& origin,
	const Vec3& dir,
	const Vec3& min,
	const Vec3& size,
	Vec3& out)
{
	Vec3 dirfrac;

	dirfrac.x = 1.0f / (dir.x == 0 ? 0.00000001f : dir.x);
	dirfrac.y = 1.0f / (dir.y == 0 ? 0.00000001f : dir.y);
	dirfrac.z = 1.0f / (dir.z == 0 ? 0.00000001f : dir.z);

	Vec3 max = min + size;
	float t1 = (min.x - origin.x) * dirfrac.x;
	float t2 = (max.x - origin.x) * dirfrac.x;
	float t3 = (min.y - origin.y) * dirfrac.y;
	float t4 = (max.y - origin.y) * dirfrac.y;
	float t5 = (min.z - origin.z) * dirfrac.z;
	float t6 = (max.z - origin.z) * dirfrac.z;

	float tmin = Math::maximum(
		Math::maximum(Math::minimum(t1, t2), Math::minimum(t3, t4)), Math::minimum(t5, t6));
	float tmax = Math::minimum(
		Math::minimum(Math::maximum(t1, t2), Math::maximum(t3, t4)), Math::maximum(t5, t6));

	if (tmax < 0)
	{
		return false;
	}

	if (tmin > tmax)
	{
		return false;
	}

	out = tmin < 0 ? origin : origin + dir * tmin;
	return true;
}

以上代码封装了射线与球体,AABB相交的判断,球体和AABB我们自己也要定义的,这也是我们说的几何封装。

geometry

Geometry主要的功能包括:碰撞体之间的检测,设置视锥体,透视视锥体,正交视锥体的计算,每个视锥体有六个面,比如我们说的透视视锥体,前,后,左,右,上下;其中前后就是我们通常说的原裁剪面和近裁剪面等等。核心代码如下所示:

void Frustum::computePerspective(const Vec3& position,
	const Vec3& direction,
	const Vec3& up,
	float fov,
	float ratio,
	float near_distance,
	float far_distance,
	const Vec2& viewport_min,
	const Vec2& viewport_max)
{
	ASSERT(near_distance > 0);
	ASSERT(far_distance > 0);
	ASSERT(near_distance < far_distance);
	ASSERT(fov > 0);
	ASSERT(ratio > 0);
	float scale = (float)tan(fov * 0.5f);
	Vec3 right = crossProduct(direction, up);
	Vec3 up_near = up * near_distance * scale;
	Vec3 right_near = right * (near_distance * scale * ratio);
	Vec3 up_far = up * far_distance * scale;
	Vec3 right_far = right * (far_distance * scale * ratio);

	Vec3 z = direction.normalized();

	Vec3 near_center = position + z * near_distance;
	Vec3 far_center = position + z * far_distance;

	setPoints(*this, near_center, far_center, right_near, up_near, right_far, up_far, viewport_min, viewport_max);
}


void Frustum::computePerspective(const Vec3& position,
	const Vec3& direction,
	const Vec3& up,
	float fov,
	float ratio,
	float near_distance,
	float far_distance)
{
	computePerspective(position, direction, up, fov, ratio, near_distance, far_distance, {-1, -1}, {1, 1});
}

void Frustum::computeOrtho(const Vec3& position,
	const Vec3& direction,
	const Vec3& up,
	float width,
	float height,
	float near_distance,
	float far_distance,
	const Vec2& viewport_min,
	const Vec2& viewport_max)
{
	Vec3 z = direction;
	z.normalize();
	Vec3 near_center = position - z * near_distance;
	Vec3 far_center = position - z * far_distance;

	Vec3 x = crossProduct(up, z).normalized() * width;
	Vec3 y = crossProduct(z, x).normalized() * height;

	setPoints(*this, near_center, far_center, x, y, x, y, viewport_min, viewport_max);
}

我们以前都是直接使用引擎为我们提供的摄像机,通过编辑器可以直接拖拽到场景中,其实它在引擎内部也是这么实现的,只是对开发者来说是黑盒子而已。以上是我们引擎封装的功能,这些都是底层的数学运算。

发布了327 篇原创文章 · 获赞 582 · 访问量 88万+
展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览