🚀 优质资源分享 🚀
学习路线指引(点击解锁) | 知识定位 | 人群定位 |
---|---|---|
🧡 Python实战微信订餐小程序 🧡 | 进阶级 | 本课程是python flask+微信小程序的完美结合,从项目搭建到腾讯云部署上线,打造一个全栈订餐系统。 |
💛Python量化交易实战💛 | 入门级 | 手把手带你打造一个易扩展、更安全、效率更高的量化交易系统 |
大家好~本文使用WebGPU和光线追踪算法,从0开始实现和逐步优化Demo,展示了从渲染500个2D物体都吃力到流畅渲染4百万个2D物体的优化过程和思路
目录* 需求
- 成果
- 1、选择渲染的算法
- 2、实现内存需求
- 3、渲染1个圆环
- 4、测试渲染极限
- 5、尝试设置workgroup_size
- 6、实现BVH
- 7、测试渲染极限
- 8、设置workgroup_size
- 9、测试内存占用
- 10、使用LBVH算法来构造BVH
- 11、实现剔除
- 12、改进遍历BVH
- 13、测试渲染极限
- 总结
- 后续的改进方向
- 参考资料
需求
我们对Demo提出下面的要求:
- 渲染1百万个以上的2D物体时达到30FPS以上
暂时只渲染一种2D物体:圆环 - 内存占用小
- 剔除被遮挡的物体
- 放大物体时无锯齿,达到矢量渲染的效果
成果
我们最终能够流畅渲染4百万个圆环
性能指标:
- 45 FPS左右,也就是每帧花费21毫秒
- 内存占用211MB
下面让我们从0开始,介绍实现和优化的步骤:
1、选择渲染的算法
思考如何实现与渲染相关的需求
现在,我们来回顾下与渲染相关的需求,从而确定渲染算法:
放大物体时无锯齿,达到矢量渲染的效果
要实现该需求,可以用参数化的方法来表示物体,如用圆心坐标、半径、圆环宽度 来表示一个圆环;
然后可以根据下面的公式判断一个点是否在圆环上:
let distanceSquare = Math.pow((点-圆心坐标), 2)
let isPointInRing = distanceSquare >= Math.pow(半径, 2) && distanceSquare <= Math.pow(半径 + 圆环宽度, 2)
因为圆环是2D的,是直接绘制在屏幕上的,所以这里的一个点就是屏幕上的一个像素
从上面的分析可知,通过“如果一个像素在圆环上则渲染”的逻辑,就可以实现需求
剔除被遮挡的物体
要实现该需求,首先圆环加上“层”的数据;
然后遍历所有圆环,判断像素在哪些圆环上;
最后取出最大“层”的圆环,将它的颜色作为像素的颜色
选择渲染的算法
有两种渲染算法可选择:光栅化渲染、光追渲染
根据我们之前对渲染需求的思考,我们应该选择光追渲染作为渲染算法
典型的光追渲染会依次执行下面两个pass:
1、光追pass
从相机向每个像素发射射线,经过多次射线弹射,计算每个像素的颜色
2、屏幕pass
将屏幕光栅化,渲染每个像素的颜色
其中光追pass是在CS(计算着色器)中进行,屏幕pass是在VS(顶点着色器)和FS(片元着色器)中进行
因为这里是2D物体,所以需要对光追算法做一些简化:
射线不需要弹射,因为不需要计算间接光照;
因为物体直接就在屏幕上,所以不需要计算射线与物体相交,而是按照参数化公式计算像素是否在2D物体上
2、实现内存需求
现在来考虑如何实现“内存占用小”的需求
这里只考虑CPU端内存,它主要存放了场景数据,包括圆环的transform、geometry、material的数据
我们使用ECS架构,将物体建模为gameObject+components
这里的components具体就是transform、geometry、material这些组件
按照Data Oriented的思想,每种组件就是一个ArrayBuffer,连续地存放组件数据(如transform buffer连续地存放圆环的localPositionX, localPostionY, layer(层)数据)
component为对应buffer中数据的索引
通过ECS的设计,我们就将场景数据都放在buffer中了。在初始化时,只根据最大的组件数量,创建一次对应的组件buffer。这样就实现了内存占用最小的目标
3、渲染1个圆环
现在来渲染出一个圆环,也不考虑剔除
我们依次实现下面两个pass:
光追pass
我们需要传入下面的场景数据到CS中,CS中对应的数据结构如下:
struct AccelerationStructure {
worldMin : vec2,
worldMax : vec2,
instanceIndex: f32,
pad\_0: f32,
pad\_1: f32,
pad\_2: f32,
}
struct Instance {
geometryIndex: f32,
materialIndex: f32,
localPosition: vec2,
}
struct Geometry {
c: vec2,
w: f32,
r: f32,
}
struct Material {
color: vec3,
pad\_0: f32,
}
struct AccelerationStructures {
accelerationStructures : array,
}
struct Instances {
instances : array,
}
struct Geometrys {
geometrys : array,
}
struct Materials {
materials : array,
}
struct Pixels {
pixels : array>
}
struct ScreenDimension {
resolution : vec2
}
@binding(0) @group(0) var sceneAccelerationStructure : AccelerationStructures;
@binding(1) @group(0) var sceneInstanceData : Instances;
@binding(2) @group(0) var sceneGeometryData : Geometrys;
@binding(3) @group(0) var sceneMaterialData : Materials;
@binding(4) @group(0) var pixelBuffer : Pixels;
@binding(5) @group(0) var screenDimension : ScreenDimension;
折叠
struct中的pad用于对齐
sceneAccelerationStructure包含圆环的包围盒数据,作为加速相交检测的加速结构。其中worldMin、wor