我一直致力于为像素艺术打造理想的延迟渲染管线,灵感来源于《风来之国》和即将推出的《Kyora》等游戏。这不是一个插件或逐行教程,但我想分享所需的内容以及工作流程的样子。如果有人感兴趣,随着系统的改进,我会分享更多过程。提前说明,这不适合新手。

在我继续之前,这是最终产品的示例:

最终结果

https://i.redd.it/d7iyofdaiqoh1.gif

我觉得手绘一些阴影(十字)看起来仍然比纯平涂(宝石)更好

贴图与纹理

无论你使用的是Unreal还是Gamemaker,如果你想要基于物理的渲染,你需要为你的精灵创建贴图。这个管线灵感来源于RE引擎,但也类似于Unity或Unreal。有许多工具可以创建贴图,有些人使用laigter或blender,但我更喜欢在Aseprite中手绘。

这个管线目前使用以下贴图,尽管还有"空间"容纳更多数据:

*打哈欠*

漫反射纹理 "spr_shrine_alb"

RGBA:漫反射贴图 - 艺术/颜色/基础纹理

不是我最好的作品,但还行

法线纹理 "spr_shrine_nrm"

RGB:法线贴图 - 垂直于表面的方向

A:未使用

虽然丑但有用

表面纹理 "spr_shrine.sur"(分3层绘制,高光和环境光遮蔽设置为"叠加"混合模式,输出为一张图像)

R:粗糙度 - 高光的扩散范围

G:高光 - 表面反射的光量

B:环境光遮蔽 - 环境光和太阳光被遮挡的区域

A:目前未使用

绿色通道是次表面散射,本例中未重点展示

效果纹理 "spr_shrine_fxs"

R:亮度/发光 - 漫反射纹理的此区域会叠加到光照中

G:次表面散射

BA:目前未使用

在某些显示器上可能看起来纯黑……

扩展纹理 "spr_shrine_ext"

R:深度/高度图 - 红色值会从物体的Z位置中减去,以在光照计算中提供更准确的Z坐标

GBA:目前未使用

所以,是的,你必须在Aseprite中手绘8张贴图。如果这足以让你望而却步,我能理解。然而,这并不会让你的工作难度增加8倍。我认为一旦你做了10次左右,并设置好模板,其实也没那么糟。事实上,由于你的基础美术可能已经包含了高光、深阴影和发光部分,一半的贴图已经完成了。

既然所有在Gamemaker之外的工作都做完了,接下来呢?Gamemaker本身不支持任何延迟渲染。所以全靠我们自己。但一旦完成,就一劳永逸了。要导入新美术,我只需将5张图片拖入Gamemaker,全选它们并右键设置纹理组,就完成了。它就这么简单

绕过Gamemaker的限制

以下是在Gamemaker中实现此功能需要做的事情。这是所需工作量的示例,我不能把所有代码都贴在这里,所以很抱歉这不是真正的教程。

  • 为物体定义所有贴图精灵的自定义脚本
  • 我们不想绘制五个不同的精灵。我们想绘制一个精灵,但包含五套不同的UV坐标
  • 为了方便,每个精灵必须遵循严格的命名约定。你给物体分配漫反射精灵,然后脚本在创建时完成其余工作

​

//获取贴图精灵
var _name = sprite_get_name(sprite_index);
_name = string_copy(_name,0,string_length(_name) - 3);
alb = sprite_index;
nrm = asset_get_index(_name + "nrm");
sur = asset_get_index(_name + "sur");
fxs = asset_get_index(_name + "fxs");
ext = asset_get_index(_name + "ext");
  • 只要我们的文件命名正确,就不需要额外工作
  • 禁用应用表面
  • 我们不再需要这个了
  • 没有东西会自动绘制
  • 忘记你关于内置绘制函数的知识,我不会再用它们了
  • 创建obj_draw
  • 这个物体管理所有表面:
    • surf_alb
    • surf_nrm
    • surf_sur
    • surf_fxs
    • surf_lgt
    • surf_shd
  • 这个物体绘制所有瓦片、角色、装饰等。房间里的一切
  • 是的,我们会绘制许多全分辨率表面。加载时,这个管线在一台500美元的笔记本电脑上以超过3000帧运行。然而,屏幕上最多只能同时有4个光源投射阴影。这是主要限制。
  • 我们仍然可以添加非投影光源(如体积光),对性能影响很小或没有
  • 构建顶点缓冲区
  • 不再使用draw_sprite()之类的函数,我们将创建一个函数,将物体的精灵数据添加到全局顶点缓冲区中。
  • 我为角色、瓦片、植被、视差背景等设置了不同的缓冲区和着色器。
  • 例如,global.vb_actors会在每一步被管理。这使我们能够控制每个物体的Z位置,并添加贴图的纹理坐标。我们将不再使用深度排序。深度就是Z位置。
  • 这对CPU来说是更多的工作,但静态物体/装饰/瓦片/植被/粒子/云/雨可以被冻结,使它们的渲染比以往更快。即使每个顶点存储了更多的UV数据。
  • 在绘制阴影时,我不需要再次绘制角色/装饰。只需用不同的阴影着色器提交它们的顶点。所有这些数据已经在一个巨大的批次中坐等再次使用。非常高效。
  • 瓦片顶点缓冲区
  • 所有这一切中最糟糕的部分就是瓦片层。我花了数小时编写这些脚本。基本上,我们正常创建瓦片层,但也要遵循非常严格的命名约定。该瓦片层必须包含你的瓦片精灵的名称。然后我们构建一个瓦片集顶点缓冲区,在房间创建时冻结,然后销毁瓦片层。我们需要自己的缓冲区,以便瓦片数据可以包含法线贴图、表面纹理等。我们可以检查房间中的每个瓦片,获取其瓦片集索引,然后通过大量数学运算获取该索引在精灵纹理页上的位置。
  • 说实话,我绝对讨厌这个。
  • 使用MRT(多渲染目标)绘制
  • 这被称为G-Buffer(几何缓冲区)。我们正常绘制美术到surf_alb。同时,为了最高效率,相同的着色器输出另外3个片段到其他3个表面:法线贴图表面、表面纹理表面和视觉效果表面。顶点着色器无需做额外工作。然后我们的光照计算会采样所有这些"几何体"来计算其强度和反射(高光)。
  • 这是obj_draw在其draw_g_buffer()函数中所做的一个示例:

​

//启用Z
gpu_set_ztestenable(1);
gpu_set_zwriteenable(1);
gpu_set_alphatestenable(1);

surface_set_target_ext(0,surf_alb);
surface_set_target_ext(1,surf_nrm);
surface_set_target_ext(2,surf_sur);
surface_set_target_ext(3,surf_fxs);

//清除表面
draw_clear_alpha(c_black,0.0);

//透明度不改变颜色值
gpu_set_blendmode_ext_sepalpha(bm_one,bm_zero,bm_one,bm_zero);

//应用相机
camera_apply(global.view);

//绘制角色
shader_set(shd_actors);
vertex_submit(global.vb_actors,pr_trianglelist,tex_actors);
shader_reset();

//然后我会绘制植被、静态物体、绳索/藤蔓、瓦片、背景、天空等

//结束G-Buffer
surface_reset_target();
gpu_set_blendmode(bm_normal);

//禁用深度
gpu_set_ztestenable(0);
gpu_set_zwriteenable(0);
gpu_set_alphatestenable(0);

//引用G-Buffer
tex_alb = surface_get_texture(surf_alb);
tex_nrm = surface_get_texture(surf_nrm);
tex_sur = surface_get_texture(surf_sur);
tex_fxs = surface_get_texture(surf_fxs);
  • 我们总是从前往后绘制,因为我们不想浪费时间绘制背景或天空,如果它们有70%会被物体、瓦片和装饰遮挡。Gamemaker会在背景的片段着色器因Z测试失败(被瓦片等遮挡)时将其丢弃。
  • 光照计算
  • 我有3个点光源、太阳/月亮光和环境光。
  • 它们都以加法模式绘制到光照表面上。
  • 首先,点光源和太阳将阴影绘制到阴影表面,每个光源使用不同的颜色通道。这减少了每帧的表面切换,是巨大的性能提升。
  • 光源采样阴影表面和G-Buffer表面来计算其强度/高光。
  • 这意味着你必须自己编写所有法线贴图/Blinn-Phong/SSS/AO着色器计算。
  • 我的环境光很简单,根据一天中的时间变化,但也可以尽可能复杂。
  • 后绘制事件
  • 我绘制我的surf_alb表面,采样光照表面。
  • 光照颜色的亮度决定强度 - 将漫反射颜色乘以光照颜色。
  • 光照的Alpha存储高光值 - 高光将光照颜色加到漫反射上。
  • 最终我会在这个阶段添加雾/薄雾。注意,部分透明度在G-Buffer中无法工作,必须在之后添加。
  • 渲染完成。

结论

这就是大概情况。如果你读到这里,你可能会觉得我这样做很蠢,而不是直接改用Unity或Unreal等。你可能说得对,但听我说。我绝对讨厌Unity和Unreal。没有不尊重使用这些引擎的人的意思,但我宁愿做这项工作1000次也不会切换到它们。Gamemaker是我的引擎。

那么,在Gamemaker中实现基于物理的渲染有多难?嗯,我觉得有点难,但非常有趣。编写代码花了一些时间,但学习着色器和PBR需要数月时间。如果有人想了解更多,我会尽力回答任何问题。Acerola也有很多关于计算机图形的精彩视频,启发了我自己在Gamemaker中尝试。