Renderer
简单说一下该引擎的渲染流程 ——
主要分块
- 用户层:
renderer全局单例给用户调用,用户传入需要渲染的数据就可以不用在意底层如何实现而直接开始跑; - 渲染逻辑层:
render device把从renderer得到的数据经过必须的逻辑组装和数学运算交付给硬件层 - 硬件层:
RHI接收来自render device的数据并且给 GPU 开始运算
单独抽离出 renderdevice 和 RHI 的目的是把硬件和逻辑解耦,这样既可以使用 OpenGL 作为图形后端,在之后迁移的时候也可以方便的切换成 Vulkan 等其他图形后端
渲染原理
首先 ——
- CPU: 人少, 但是脑子强
- GPU: 人多, 但是擅长做简单的并行运算
所以为了提升渲染效率,我们会喜欢把逻辑运算等放到 CPU 中, 然后在 GPU 处 做一些矩阵乘法和光栅化a之类的并行运算;
但是有一个问题,就是 GPU 不可能直接读取 CPU 中的内存数据 ( 原料 ), 然后进行流水线加工 ( Graphics Pipeline ), 所以需要在 CPU 进行运算之后进行一步搬运数据的步骤 ( 现代渲染速度的一大瓶颈 )
最后数据流向 GPU 后, 按照 Graphics Pipeline 开始渲染整个世界 🌍
Graphics Pipeline
(感觉好像以前写过) 当 GPU 的指令处理器读取到 DrawCall 命令时 ( CPU 发出 ), GPU 就会启动 Graphics Pipeline 来进行渲染
基本上, 分为几何阶段和光栅化阶段
几何阶段
- 从显存中读取顶点数据,硬件根据顶点和索引缓冲 ( VBO / IBO) 组装成独立的三角形图元
- 应用顶点着色器
- 进行图元设置与裁剪, 把x, y, z 压缩到
的标准区间中, 然后把 NDC 之外的三角形部分全部切除 - 屏幕映射
光栅化阶段
- 三角形设置, 硬件拿到三个屏幕坐标点, 计算出这个三角形的边界方程
- 三角形遍历, 把三角形内等像素激活,形成一个 Fragment
- 重心坐标插值, 其实就是线性插值,混合法线、uv 等内容
- 运用片元着色器
- 比如通过 uv 进行贴图
- 比如运用 BRDF 等
- 进行深度测试和混合等
进过上述两个步骤, 就会生成一个可以输出出来的图像——帧缓存,我们之需要读取帧缓存内的数据就可以丢到屏幕上进行 display了.
当然, 这只是最简单的一条渲染管线,这不算太强悍,也有很多的浪费运算——
光追
简述一下光追走的 pipeline
- 从屏幕上的每一个 pixel 向游戏世界发出一条虚拟的射线 ( 模拟光线 )
- 模拟光线在世界中反射折射等 ( 通过 BVH 等结构加速运算 )
- 对于光线在虚拟世界中的碰撞等,写一个碰撞处理方法——比如返回颜色,或者做递归追踪等.
GPU Driven Rendering
为了消灭 CPU 提交十万次 Draw Call 导致的性能瘫痪, 于是提出 GPU Driven ——
即 CPU 只需要调用一次 Compute Shader, 接着 GPU 内部会物体进行视锥剔除(Culling),并将幸存物体的绘制参数写入 Indirect Buffer。最后由 GPU 自己读取这个 Buffer 生成绘制命令(DrawIndirect)