天天看点

d3d12龙书学习之MiniEngine的最小化实现(十二) 龙书第16章 实例化与视锥体裁剪实例化视锥体裁剪

文章目录

  • 实例化
    • 通过实例化修改渲染流程
  • 视锥体裁剪
    • 修改MiniEngine中Frustum为左手坐标系
    • 通过一个常量缓冲区实现裁剪
    • release帧率对比

实例化

实例化实际上就是通过一套顶点和索引,绘制多个物体。

可以为物体设置不同的转换矩阵,纹理等所需属性。

简单流程如下:

  1. 缓冲区写入每个渲染目标的的矩阵参数
  2. 缓冲区写入每个渲染目标的纹理属性以及纹理ID
  3. 缓冲区写入所需的纹理

那么通过实例化只需要一次DrawCall就可以把这些目标全部渲染。

通过实例化修改渲染流程

在上一篇博客动态索引中,实际上已经把纹理属性和纹理都写入了缓冲区。

渲染流程如下:

  1. 每个渲染目标有自己独立的常量数据
  2. shader中根据顶点的常量数据去拿纹理数据以及对应纹理

在这里,采用实例化的方式,通过shader中的SV_InstanceID参数,我们可以确定当前绘制的是第几个实例化目标。

把每个渲染目标的数据也提前一次性写入缓冲区。

整体非常的简单,也没有什么坑,这里就不再细说了。

github:

https://github.com/mversace/DirectX12-MiniEngine-Dragon/tree/63d3490e0cccedd2a01fc5402c53ce9a208252b0

视锥体裁剪

这个的意思就是,利用CPU根据当前的摄像机计算出可以看到的视锥体,然后把与视锥体不相交的渲染目标剔除出渲染队列。

这可以有效的减少shader运算的负担。否则的话,渲染流水线依旧会提交这些顶点,直到经过几何着色阶段之后,开始裁剪时才会去除目标,而这会带来大量的计算开销。

CPU提前进行视锥体裁剪就是CPU多计算一点,来提高整体的运行效率。

修改MiniEngine中Frustum为左手坐标系

MiniEngine中在Camera类中本身有剔除类Frustum(被删掉了,这次加回来)

我们需要修改Frustum这个类中构造透视视锥体的代码,来匹配左手坐标系

void Frustum::ConstructPerspectiveFrustum( float HTan, float VTan, float NearClip, float FarClip )
{
    // 已改为左手坐标系
    const float NearX = HTan * NearClip;
    const float NearY = VTan * NearClip;
    const float FarX = HTan * FarClip;
    const float FarY = VTan * FarClip;

    // 视锥体,计算近远两个面的4个顶点
    m_FrustumCorners[ kNearLowerLeft  ] = Vector3(-NearX, -NearY, NearClip);    // Near lower left
    m_FrustumCorners[ kNearUpperLeft  ] = Vector3(-NearX,  NearY, NearClip);    // Near upper left
    m_FrustumCorners[ kNearLowerRight ] = Vector3( NearX, -NearY, NearClip);    // Near lower right
    m_FrustumCorners[ kNearUpperRight ] = Vector3( NearX,  NearY, NearClip);    // Near upper right
    m_FrustumCorners[ kFarLowerLeft   ] = Vector3( -FarX,  -FarY,  FarClip);    // Far lower left
    m_FrustumCorners[ kFarUpperLeft   ] = Vector3( -FarX,   FarY,  FarClip);    // Far upper left
    m_FrustumCorners[ kFarLowerRight  ] = Vector3(  FarX,  -FarY,  FarClip);    // Far lower right
    m_FrustumCorners[ kFarUpperRight  ] = Vector3(  FarX,   FarY,  FarClip);    // Far upper right

    const float NHx = RecipSqrt( 1.0f + HTan * HTan );
    const float NHz = NHx * HTan;
    const float NVy = RecipSqrt( 1.0f + VTan * VTan );
    const float NVz = NVy * VTan;

    // 计算视锥体的6个面,存储的是法向量以及面上的一个点
    m_FrustumPlanes[kNearPlane]        = BoundingPlane( 0.0f, 0.0f, 1.0f,  NearClip );
    m_FrustumPlanes[kFarPlane]        = BoundingPlane( 0.0f, 0.0f,  -1.0f,   FarClip );
    m_FrustumPlanes[kLeftPlane]        = BoundingPlane(  NHx, 0.0f,   NHz,      0.0f );
    m_FrustumPlanes[kRightPlane]    = BoundingPlane( -NHx, 0.0f,   NHz,      0.0f );
    m_FrustumPlanes[kTopPlane]        = BoundingPlane( 0.0f, -NVy,   NVz,      0.0f );
    m_FrustumPlanes[kBottomPlane]    = BoundingPlane( 0.0f,  NVy,   NVz,      0.0f );
}
           

还有点修改,详见log记录。

MiniEngine中设计的还是比较巧妙的,原版本是Camera中的矩阵存储了摄像机深度比例。我们后来修改后采用了dx的api,这个比例就没有了,所以给Frustum的构造函数额外传一个参数提供这个比例,以生成正确的视锥体。

通过一个常量缓冲区实现裁剪

龙书中是设置了一个上传缓冲区,动态的将渲染目标的一些参数拷贝进去。拷贝的量有点大,我这里采用另外一种方式来实现。

  1. 设置一个常量缓冲区,只记录可视目标的idx
  2. shader中根据这个idx来取值

这种方式,对于每次拷贝的数据量稍微小点。

这个稍微改下就可以了。

有个点需要注意,shader中的数组,数组中的每个值都会至少分配float4的空间(vs2019图像调试经常崩溃无法定位,研究了很久才找到问题)

所以我们传入的数据要适配这种格式。

cbuffer cbPass1 : register(b1)
{
    int gDrawObjs[128];     // 数组中的每个元素都会被封装为float4,d3d12龙书727页
};
           

相应的修改C++中渲染目标之类的结构体,稍微调整下。

release帧率对比

debug下不明显,release下效率差距极大

d3d12龙书学习之MiniEngine的最小化实现(十二) 龙书第16章 实例化与视锥体裁剪实例化视锥体裁剪
d3d12龙书学习之MiniEngine的最小化实现(十二) 龙书第16章 实例化与视锥体裁剪实例化视锥体裁剪

继续阅读