ejoy2d shader 模块改进计划
由于有公司很多同事参与 ejoy2d 的开发,所以 ejoy2d 这个项目已经转移到 ejoy 的 github 名下。
有更多项目的参与的情况下,原来 ejoy2d 的简单构架慢慢显出一些局限性。主要是不同的项目会根据项目的需要(通常是针对某些特定需求的优化,以及特别的效果需求)修改底层 shader 的部分。最早设计的时候,因为考虑到只是用于 2d 游戏的开发,所以把 shader 模块实现的比较简单。特别是 attribute layout 是固定的,而 uniform 管理也没有留下太多扩展性。
在现代手机的 GPU 架构下,从渲染层渲染层 API 看,其实 2d 和 3d 其实没有本质上的区别。都是基于三角片渲染的。需要把顶点上传到 GPU 中由 vs 处理,在最后对像素做 fs 渲染出来。
而 2d engine 和 3d engine 的区别通常在于 2d engine 的顶点变换很简单。不需要用 projection matrix 和 view matrix 做变换。2d engine 中的对象多半是四边形,数量很多,常见的优化手法是将大量的四边型合并到同一个渲染批次中;所以 world matrix (以平移变换为主)在 CPU 中和顶点计算再提交更常见一些。
2d engine 从应用上说,就是在处理一张张图片。所以对图片(四边型)的处理的变化要多一些。这使得 fs 要多变一点,需要引擎提供一定的可定制性。但很少去处理 3d engine 常见的光照投影这些东西。更多的是为了优化贴图用量等目的而技巧性的去使用一些图片。
突出 2d engine 的专门面对的业务的特性,而简化 GPU 提供的模型,用简短的代码构建 engine 框架,是 ejoy2d 设计的初衷。而且我也相信,简单可以带来更好的性能。所以一开始设计 ejoy2d 的时候,shader 模块的很多东西都被写死了,以最简单的方式达到目的。仅暴露了很少的外部接口,再在这些有限的接口上设计数据结构,做性能优化。
事情总要做两遍以上,才能做对。目前 ejoy2d 完成了三个项目,由十多个同学亲身用过。让我看到了局限性:前段时间我个人精力主要放在 skynet 上,而客户端在紧急的情况下,由不同的同学给 ejoy2d 打了好些补丁,来解决实际面临的问题。
我想,还是需要有一个更灵活一些的 shader 模块底层才能更好的发展。而且希望这个模块可以用于未来引入 3d engine 的元素,而又不会增加太多的复杂度包袱。
为了做这件事情,我在最近两个月阅读了 Unreal4 的渲染层代码、horde3d 、还有之前读过的 pixellight 。
为什么要阅读其他成熟引擎的代码,再自己实现;而不是直接使用这些引擎?在我看来原因是这样几点:
最重要的一点,编写代码解构问题并解决的过程非常有趣。
大多数引擎历史悠久,带有或多或少的历史包袱:从计算机图形的发展历史看,有许多东西是曾经被提出来后来又被否定掉的。历史越久的引擎就越多的支持了这些可能不再被使用的可能性。支持多种不同的底层 API 在移动设备上也是没有必要的(虽然提取出不同 API 的共性这件事情本身很有价值,可以提炼出相对正确的模型)。
移动设备的显卡特性相对统一。许多 PC/Console 高端显卡上才支持的特性,在三年内都不太可能在移动设备上广泛使用。为这些东西增加的结构复杂性非常不值。同时一些为了低端设备考虑的东西,又不太需要兼容,去掉它们年可以简化引擎底层。
只是实现渲染的基础部分,而不考虑工具的话。无论 2d engine 还是 3d engine 都没有太多的工作要做,并不是一件高成本的事情(控制在一个人半个月到一个月左右的工作量)。关键是正确的去做。
受够了正在使用的 Unity3D 的闭源策略(尤其是资源管理部分),如果未来有新的项目需要一些 3d 元素,可以多一个选择。
上面提到的几个 3d engine ,从阅读角度讲,我最喜欢 horde3d 。它的渲染底层最简单,只需要读 RendererBase 这一个模块就能理解。
它只封装了 opengl 的可编程管线(但其实扩展到 DirectX10/11 也很简单),没有做那些华而不实的抽象层。在 RendererBase 之上,又根据 3d engine 的需要设计了 Renderer 一层。
所谓封装,就是根据业务的需要裁减你不需要的可能性,简化和合并 API 。(根据你可以预计的使用场合)规划资源的管理,并加入合理的限制(限制可以防止错误的使用,并简化实现)。
在 RendererBase 的层次上,主要是封装显卡驱动级的各种渲染状态切换,创建 shader ,把 attribute layout 抽象出来,和 vertex buffer 一起加以管理。uniform 的管理可以裸露出来,到上一层 Renderer 再说。texture 和 render target 也是在这一层次做封装的。horde3d 很好的把这些东西用自己的 id 管理起来,屏蔽了 opengl 层的 id 。
对于 render target ,在移动设备上其实暂时还不需要对 MRT (multiple render target) 做太多支持。因为短期内还看不到在移动设备上使用 Deferred Shading 的实用性。
周末动手按 horde3d 的思路重新封装了 shader 模块,大约写了不到 2000 行代码。包括了 C 模块和 lua 封装。C 接口定义了不到 30 个 API ,比 hord3d 的 RendererBase 要简单一些(它有 70+ 个 public api ,而 UE4 的 RHI 有 150+ 个);而 Lua 封装可以方便直接在 lua 里使用它做测试。
由于尚未完工,暂时放在了 github 的私有仓库里。
Comments
Posted by: xhd | (9) February 6, 2017 06:01 PM
Posted by: jiahuafu | (8) January 21, 2015 03:35 PM
Posted by: lichking | (7) October 19, 2014 10:42 AM
Posted by: lichking | (6) October 19, 2014 10:42 AM
Posted by: lichking | (5) October 19, 2014 10:42 AM
Posted by: lichking | (4) October 19, 2014 10:42 AM
Posted by: 叶起涟漪 | (3) September 25, 2014 10:32 AM
Posted by: Cloud | (2) September 21, 2014 08:54 PM
Posted by: ephay | (1) September 21, 2014 06:25 PM