OpenGL学习-----实用技术:场景管理与视锥体剔除

目录

场景管理:

场景树建立:

视锥体剔除:

视锥体:

包围盒:

球:

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

这里就暂时不详细展开了

  • 3
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值