为什么现代渲染器越来越像数据库
为什么现代渲染器越来越像数据库
很多开发者第一次接触图形学时。
脑海中的渲染器通常是这样的:
scene.draw();或者:
renderer.draw(mesh);从软件工程角度看。
这是一种典型的对象模型。
对象拥有:
Mesh Material Texture渲染器负责:
遍历对象 ↓ 提交 Draw Call看起来十分自然。
但随着现代引擎不断发展。
人们开始发现一个有趣现象。
越来越多渲染器内部已经不再围绕对象运行。
而开始围绕数据运行。
它们越来越像数据库。
最初的渲染器是对象驱动的
例如:
classMeshRenderer{public:Mesh*mesh;Material*material;};渲染时:
for(auto&renderer:renderers){renderer.draw();}逻辑简单。
容易理解。
对象本身就是系统中心。
许多早期引擎都是这种结构。
对象数量开始爆炸
问题出现在规模增长之后。
例如:
100 个物体时。
没有任何问题。
但当场景变成:
100000 个物体时。
情况开始改变。
此时渲染器真正关心的往往不是:
Object #3812而是:
哪些物体可见?或者:
哪些物体使用同一材质?或者:
哪些物体需要进入 Shadow Pass?注意。
这些已经不是对象问题。
而是查询问题。
查询开始成为核心操作
观察现代渲染器会发现。
每一帧都在执行类似操作:
查找可见对象 查找透明对象 查找阴影对象 查找相同材质对象 查找相同 Mesh 对象这些行为非常像:
SELECT*FROMRenderObjectsWHEREVisible=true;或者:
SELECT*FROMRenderObjectsWHEREMaterial=X;当然。
引擎不会真的运行 SQL。
但本质上。
已经开始执行:
查询 过滤 分组这样的操作。
而这正是数据库最擅长的事情。
ECS 本质上也是一种数据表
上一篇文章讨论 Archetype 时提到。
现代 ECS 经常采用:
Position[] Velocity[] MeshID[] MaterialID[]这样的布局。
如果换一个角度观察。
这其实非常像:
Entity Table例如:
| Entity | Mesh | Material |
|---|---|---|
| 0 | 12 | 3 |
| 1 | 4 | 8 |
| 2 | 12 | 3 |
渲染系统真正关心的是:
Material = 3的所有实体。
或者:
Mesh = 12的所有实体。
本质上已经变成:
数据查询问题。
而不是:
对象调用问题。
GPU Driven 更进一步
GPU Driven 出现后。
这种趋势更加明显。
过去:
object.draw();现在:
Cull LOD Generate Draw CommandGPU 并不关心:
Object是什么。
GPU 只关心:
数据在哪里。
例如:
Transform[]Bounds[]MaterialID[]然后执行:
过滤 排序 分组这与数据库执行查询十分相似。
Bindless 让资源变成记录
传统资源系统:
Texture*直接引用对象。
Bindless 出现后。
资源开始变成:
TextureIndex例如:
Material{uint32_talbedoTexture;uint32_tnormalTexture;}Shader 执行时:
textureTable[albedoTexture]访问资源。
此时:
资源更像数据库中的:
记录索引开始比指针更重要。
Render Graph 像查询规划器
Render Graph 的出现。
进一步强化了这种趋势。
过去:
ShadowPass();GBufferPass();LightingPass();执行顺序由程序员决定。
而 Render Graph 中:
Resource A ↓ Pass B ↓ Resource C系统根据依赖关系自动安排执行顺序。
这与数据库中的:
Query Planner其实有相似思想。
开发者描述:
需要什么系统决定:
如何执行关注点再次从过程转向数据。
为什么会发生这种变化
原因其实很简单。
对象模型更适合:
表达逻辑而现代渲染器更关心:
处理海量数据当规模达到:
10 万对象 100 万实例 数万资源时。
系统真正面临的问题变成:
如何过滤 如何查询 如何批处理这些问题与数据库面临的问题非常接近。
于是相似的设计开始出现。
现代渲染器越来越重视数据
观察过去十年的趋势会发现。
越来越多技术都在推动同一个方向:
ECS:
数据表Archetype:
数据分区GPU Driven:
数据查询Bindless:
资源索引Render Graph:
依赖规划它们看起来完全不同。
但背后都在推动一件事:
从对象驱动转向数据驱动。
这并不意味着 OOP 失败了
看到这里。
很容易产生误解:
现代引擎已经不需要对象了。
实际上并不是。
对象仍然非常重要。
例如:
EditorWindow AssetImporter Project这些系统天然适合对象模型。
变化发生在:
高频数据处理领域。
因为当数据规模足够大时。
数据组织方式开始比对象关系更重要。
结语
很多人第一次学习渲染器时。
看到的是:
mesh.draw();这样的代码。
但随着系统规模增长。
渲染器内部逐渐变成:
过滤 查询 排序 分组 批处理的世界。
对象仍然存在。
但它们越来越像用户界面。
而真正驱动渲染器运行的。
是大量连续的数据。
从这个角度看。
现代渲染器并没有变成数据库。
但它们正在解决越来越多与数据库相同的问题。
而这或许也是现代引擎架构不断演化后的一个共同方向:
不再围绕对象组织系统。
而开始围绕数据组织系统。
