目录
场景管理:
场景树建立:
视锥体剔除:
视锥体:
包围盒:
球:
AABB:
判断是否在视锥体内:
包围盒为球体:
包围盒为AABB:
空间加速:
场景管理:
场景树建立:
场景树的建立,这里讲一下建树,树里面保存根据自己的model矩阵,这个矩阵需要根据父物体的model矩阵做出相对变化。出于学习目的:代码会以欧拉角的形式展示,也是只是用oop,而不是odp
属性的定义:
class Transform
{
protected:
glm::vec3 m_pos ={0.0f,0.0f,0.0f};
glm::vec3 m_eulerRot ={0.0f,0.0f,0.0f};
glm::vec3 m_scale ={1.0f,1.0f,1.0f};
glm::mat4 m_modelMatrix = glm::mat4(1.0f);
protected:
glm::mat4 getLocalModelMatrix()
{
const glm::mat4 transformX = glm::rotate(glm::mat4(1.0f), glm::radians(m_eulerRot.x), glm::vec3(1.0f,0.0f,0.0f));
const glm::mat4 transformY = glm::rotate(glm::mat4(1.0f), glm::radians(m_eulerRot.y), glm::vec3(0.0f,1.0f,0.0f));
const glm::mat4 transformZ = glm::rotate(glm::mat4(1.0f), glm::radians(m_eulerRot.z), glm::vec3(0.0f,0.0f,1.0f));
const glm::mat4 roationMatrix = transformY * transformX * transformZ;
return glm::translate(glm::mat4(1.0f), m_pos)* roationMatrix * glm::scale(glm::mat4(1.0f), m_scale);
}
public:
void computeModelMatrix()
{
m_modelMatrix =getLocalModelMatrix();
}
void computeModelMatrix(const glm::mat4&parentGlobalModelMatrix)
{
m_modelMatrix = parentGlobalModelMatrix *getLocalModelMatrix();
}
[...]
}
那具体怎么更新模型矩阵呢?如果一个物体他有父物体那么他需要把父物体的旋转也应用上
void forceUpdateSelfAndChild()
{
if(parent)
transform.computeModelMatrix(parent->transform.getModelMatrix());
else
transform.computeModelMatrix();
for(auto&& child : children)
{
child->forceUpdateSelfAndChild();
}
}
一个树节点的定义(简化版):
class Entity
{
public:
std::list<std::unique_ptr<Entity>> children;
Entity* parent =nullptr;
Transform transform;
Model* pModel =nullptr;
[...]
void addChild(TArgs&...args)
{
children.emplace_back(std::make_unique<Entity>(args...));
children.back()->parent=this;
}
[...]
};
视锥体剔除:
在大量物体渲染的时候,我们需要限制GPU的使用,这里有项技术,就是视锥体剔除。
视锥体:
在之前csm的文章里,我怎么表示一个视锥体的呢?我主要是通过视锥体的八个点来表示一个视锥体(这些点可以根据相机的位置和方向进行求解或者根据NDC空间坐标反推世界空间的坐标),但是这里我们采用一个更加合适视锥体剔除的新的方法。
我们可以使用法线和平面到原点的距离来表示一个平面:
struct Plan
{
glm::vec3 normal ={0.f,1.f,0.f};
float distance =0.f;
Plan()=default;
Plan(const glm::vec3&p1,const glm::vec3&norm)
:normal(glm::normalize(norm)),
distance(glm::dot(normal, p1))
{}
};
六个面组成一个视锥体:
struct Frustum
{
Plan topFace;
Plan bottomFace;
Plan rightFace;
Plan leftFace;
Plan farFace;
Plan nearFace;
};
创建一个视锥体:
Frustum createFrustumFromCamera(const Camera&cam,floataspect,floatfovY,floatzNear,floatzFar)
{
Frustum frustum;
constfloat halfVSide = zFar *tanf(fovY *.5f);
constfloat halfHSide = halfVSide * aspect;
const glm::vec3 frontMultFar = zFar *cam.Front;
frustum.nearFace={cam.Position+ zNear *cam.Front,cam.Front};
frustum.farFace={cam.Position+ frontMultFar,-cam.Front};
frustum.rightFace={cam.Position, glm::cross(cam.Up, frontMultFar +cam.Right* halfHSide)};
frustum.leftFace={cam.Position, glm::cross(frontMultFar -cam.Right* halfHSide,cam.Up)};
frustum.topFace={cam.Position, glm::cross(cam.Right, frontMultFar -cam.Up* halfVSide)};
frustum.bottomFace={cam.Position, glm::cross(frontMultFar +cam.Up* halfVSide,cam.Right)};
return frustum;
}
包围盒:
最简单的包围盒就是球体,以及稍稍进阶一点的AABB,接下来都会讲到
球:
球的表示就比较简单了,圆心和半径
AABB:
AABB的表示有两种:可以保存最大点和最小点(参考我的光追文章),也可以保存中心点和任意一个点
(需要注意,aabb需要在旋转缩放之后更新最大点/最小点/Extent)
然后排列组合就能求出AABB的八个顶点了。
struct AABB :public BoundingVolume
{
glm::vec3 center{0.f,0.f,0.f};
glm::vec3 extents{0.f,0.f,0.f};
AABB(const glm::vec3&min,const glm::vec3&max)
:BoundingVolume{},
center{(max + min)*0.5f},
extents{max.x-center.x,max.y-center.y,max.z-center.z}
{}
AABB(const glm::vec3&inCenter,floatiI,floatiJ,floatiK)
:BoundingVolume{},center{ inCenter },extents{ iI, iJ, iK }
{}
[......]
};
判断是否在视锥体内:
核心是我们怎么判断他是否在视锥体内?
包围盒为球体:
这里采用的方法是:前面不是保存了每个面的法线吗,可以用法线投影求出球心到平面的距离,从而判断出是否在平面外。六个平面都判断一下就知道是不是在视锥体内了。
因为平面是无限延申的,而且点的位置的vec3其实也可以表示从原点指向点的一个向量,那我们点乘一下,求出其在法线上的投影,然后减去平面距离原点的距离就能得出结果。
glm::dot(normal, point) - distance;
判断球是否在视锥体内完整代码:
float getSignedDistanceToPlan(const glm::vec3&point)const
{
return glm::dot(normal, point)- distance;
}
bool isOnOrForwardPlan(const Plan&plan)const
{
returnplan.getSignedDistanceToPlan(center)>-radius;
}
bool isOnFrustum(const Frustum&camFrustum,const Transform&transform)constfinal
{
[......]
return (globalSphere.isOnOrForwardPlan(camFrustum.leftFace) &&
globalSphere.isOnOrForwardPlan(camFrustum.rightFace) &&
globalSphere.isOnOrForwardPlan(camFrustum.farFace) &&
globalSphere.isOnOrForwardPlan(camFrustum.nearFace) &&
globalSphere.isOnOrForwardPlan(camFrustum.topFace) &&
globalSphere.isOnOrForwardPlan(camFrustum.bottomFace));
};
包围盒为AABB:
这里采用的方法是:计算 AABB( c) 的中心和它的范围 ( e)。然后我们将范围折叠e到平面法线上。在这一点上,我们有一条在两个方向上都有中心点c和范围的线r。然后我们计算线到平面的距离s,如果线的距离超过其长度的一半,则它不会相交。然后我们再重复算六个面就行
判断AABB是否在视锥体内完整代码:
bool isOnOrForwardPlan(const Plan&plan)const
{
const float r =extents.x* std::abs(plan.normal.x)+
extents.y* std::abs(plan.normal.y)+extents.z* std::abs(plan.normal.z);
return-r <=plan.getSignedDistanceToPlan(center);
}
bool isOnFrustum(const Frustum&camFrustum,const Transform&transform)constfinal
{
[...]
return (globalAABB.isOnOrForwardPlan(camFrustum.leftFace) &&
globalAABB.isOnOrForwardPlan(camFrustum.rightFace) &&
globalAABB.isOnOrForwardPlan(camFrustum.topFace) &&
globalAABB.isOnOrForwardPlan(camFrustum.bottomFace) &&
globalAABB.isOnOrForwardPlan(camFrustum.nearFace) &&
globalAABB.isOnOrForwardPlan(camFrustum.farFace));
};
(注意:上述的所有算法的计算如果在相机空间进行更好理解也会更少错误)
空间加速:
如果我们每个物体都算一边其实也有点消耗的,一般得进行加速。这个其实可以做bvh或者八叉树等等。
(注意:空间加速树和场景管理树不一样,场景管理树一般根据逻辑划分父子节点,空间加速树根据位置划分父子节点)
BVH的话可以看我之前光照的文章光追渲染器开发记录:BVH加速结构构建与射线求交_This is MX的博客-CSDN博客
八叉树的简历其实需要找到所有物体的最大包围盒bound,然后xyz各进行一次划分,不断进行下去,直到子空间没有物体了或者只有一个物体之后就不划分了。可以参考这篇点云的文章:
Open3d之八叉树(Octree)_ancy_i_cv的博客-CSDN博客_octree python文章来源:https://www.toymoban.com/news/detail-422769.html
这里就暂时不详细展开了文章来源地址https://www.toymoban.com/news/detail-422769.html
到了这里,关于OpenGL学习-----实用技术:场景管理与视锥体剔除的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!