事实上,前面编写的渲染器 Renderer 非常简陋,虽然能够进行一些简单的渲染,但是它并不能满足我们的要求。
当渲染粒子系统时,需要开启混合模式,但渲染其他顶点时却不需要开启混合模式。所以同时渲染粒子系统和其他纹理时会得不到想要的结果,渲染器还存在许多的不足:
1、当渲染许多透明图形时,没有对其进行排序,使得本应透明的图形没有透明。
2、不能对不同的顶点使用不同的状态进行渲染。
渲染器要做的东西很简单,就是
1、传递数据到 GPU
2、设置 OpenGL 状态信息(Alpha测试、模板测试、深度测试、混合,裁剪测试等等)
3、设置着色程序,绑定 Uniform 数据
4、绘制顶点
下面给出两种方式实现上面的流程。
第一种实现方式:
DrawList 根据绘制的图形填充顶点数据,VertexData 和 IndexData 分别指向一块内存,分别储存顶点数据和索引数据(传递数据到 GPU 时递交这两块内存的数据)。
CmdList 命令列表,储存一些绘制的信息(Alpha测试、模板测试、深度测试、混合,裁剪测试等等)。值得注意的是,绘制命令有 BeginIndex 和 IndexCount 这两个属性数据,调用函数 glDrawElements 进行绘制时使用。
glDrawElements (GLenum mode, GLsizei count, GLenum type, const void *indices);
即所有的绘制命令 Cmd 都对应着其唯一的顶点数据,只不过它们的数据都储存在一起。这样每次绘制只需要提交一次数据就可以了,提高的效率。
这种渲染方式是在 ImGui 中看到的,但这次重写渲染器用的不是这种方式。
第二种实现方式:
这次渲染器要实现的功能:
1、对于不同的顶点可以设置不同的 OpenGL 状态进行绘制。
2、对半透明图形进行排序渲染。
这次重写渲染器用的就是这种方式,渲染器构成的主要类:
这里把着色器给做成一个单独的类,绘制顶点时 OpenGL 状态信息储存在 Pass 中,对象分配器(来自于物理引擎 Box2D 的小型对象分配器源码)分配内存空间储存顶点数据。
当绘制图形并显示在窗口上时,需要进行的流程:
首先设置渲染器当前 Pass,然后设置顶点数据到渲染器中。在渲染时
1、渲染器会迭代每一个 Pass
2、对顶点数据进行排序
3、设置 OpenGL 状态
4、合并顶点数据
5、设置着色器
6、绑定 Uniform 数据
7、最后进行渲染。
渲染器具体要做的就是如何管理好 Pass 和 VertexIndexData,又不失效率。下面是渲染器管理 Pass 和顶点数据的图片介绍:
半透明渲染是这次写渲染器中遇到的一个难题,单两个不透明图形重叠时,进行深度测试时被遮挡部分的像素会被剔除或者先绘制被遮挡的图形,然后绘制顶层的图形。但是渲染半透明的图形时,半透明的图形能够看到被遮挡的图形,直接抛弃就露馅了,不顾深度测试也依然得不到正确的结果,因为 Alpha
混合颠倒顺序会得到不一样的结果。
一个解决方法就是把透明的和半透明的图形分开,先渲染不透明的图形,然后对半透明的图形进行排序(被遮挡的半透明的图形先渲染),排序后进行渲染。但是这样代价较高,这次渲染器用的是这种方式。
渲染器中有两个渲染列表,不透明的 Pass 列表和透明的 Pass 列表。Pass 和 RenderOperation 作为键值对储存在 Map 中,顶点数据储存在RenderOperation 中。VertexIndexData 数组用于对顶点数据进行排序,主要是解决半透明图形的渲染问题。
接下来将对完成渲染器的各个部分。