Cocos Creator 3.6 新特性详解 2/3:渲染篇

这段是负责承上启下的

上一篇文章,麒麟子和各位一起体验了 Cocos Creator 3.6 版本中那些令人激动的编辑器新特性。不管从编辑器外观,还是从工作效率上都带来了不错的提升。

相信有不少朋友和麒麟子一样,每次的版本更新,都会分外关注引擎渲染相关的特性,有没有新增渲染技术,有没有画质方面的提升。

今天我们细数一下 3.6 版本中那些与渲染特性相关的内容。

一切都是为了颜值

3.6 中较为硬核的新渲染特性:

  • GGX 环境反射卷积(GGX Convolution)
  • 级联阴影(CSM, Cascaded Shadow Maps)
  • 粒子噪声模块(Noise Module)
  • 动态模型(Dynamic Mesh)
  • 各向异性光照(Anisotropic Lighting)
  • 表面着色器(Surface Shader)

如果朋友们在之前的版本中遇上过:

  • 粗糙表面油油的?
  • 阴影效果糊糊的?
  • 粒子轨迹傻傻的?

不用担心,这些问题在 3.6 中都将不复存在。

如果朋友们在之前的版本中问过:

  • 如何使用代码控制模型顶点运动?
  • 如何制作出如丝般的材质效果?
  • 如何更简单的自定义Shader?

不用着急,这些问题在 3.6 中都有有解决方案。

铺垫了这么多,就是想说:还不赶紧到碗里来!

GGX 环境反射卷积图

这是麒麟子一直想要的特性,它可以极大的提升非光滑表面的真实感,请看下图:

从上面的对比图中,我们可以明显感知到有卷积(GGX Convolution)和无卷积(Mipmaps)的效果差异。

那什么是 GGX 环境反射卷积图呢?

它是指采用 GGX 算法,使用卷积方式生成的用于环境反射计算的 Cubemap 贴图。

这东西性能如何?是我这800块的手机能跑的吗?

由于卷积图是预生成的,朋友们完全不用担心性能问题,在同样需要环境反射的情况下,使用卷积图和使用 Mipmap 图性能是一致的,只需在编辑器中烘焙好即可。

既然它这么棒,我们如何在 Cocos Creator 中生成和使用 GGX 环境反射卷积图呢?

大家请看下图:

这是 Cocos Creator 3.6 中最新的 Skybox 面板,我们重点关注:

  • 环境光照类型(Env Lighting Type)
  • 环境反射卷积图(Reflection Convolution)烘焙(Bake)

麒麟小贴士:PBR材质在处理环境光照时,会将环境贴图视作一个巨大的环绕光源,贴图上的每一个像素都各自代表了一个方向上的光亮度。这个处理方式被称为:IBL(Image Based Lighting),而IBL中主要由漫反射和环境反射两个部分的计算构成。

环境光照类型
引擎环境光照提供了三种组合选择:

  • HEMISPHERE_DIFFUSE(半球光照)
  • AUTOGEN_HEMISPHERE_DIFFUSE_WITH_REFLECTION(自动计算半球光照漫反射+环境反射)
  • DIFFUSEMAP_WITH_REFLECTION(环境辐照度卷积+环境反射)

选择 XXX_WITH_REFLECTION 表示我们希望环境反射,反之则没有环境反射。 如果把同样的测试场景切换到 HEMISPHERE_DIFFUSE ,我们只能得到下面这样的效果:

HEMISPHERE与AUTOGEN_HEMISPHERE的区别是,AUTOGEN_HEMIPSHERE会根据环境贴图自动计算天光和地光。

环境反射卷积图烘焙

环境反射卷积图后面的烘焙(Bake)按钮,用于生成环境反射卷积图,当烘焙成功,IBL运算会采用生成好的环境反射卷积图。

如果不想使用环境反射卷积图,可点击移除按钮,IBL运算会采用环境反射贴图的 Mipmaps 参与运算。

能详细说说环境反射卷积吗?

对于想要了解它背后原理的朋友,我们先来看看两个名词解释:

  • GGX:一种微表面分布函数,用GGX算法渲染的材质,过渡会更加自然,分布曲线更加拟合真实物体,最主要的是可以还原一部分真实BRDF的高光拖尾,对比效果如下图所示:

  • 环境反射卷积图:通过适合的算法,预先生成对应粗糙度的反射图并存到Cubemap的各个Mipmap等级中,如下图所示:



    可以明显的看到,Cubemap 中的 Mipmaps 每一级都比上一级更模糊,这是普通的 Mipmap 生成算法无法达到的效果。

由于本文重点关注它能带来什么效果,因此不会解释是什么GGX,什么是卷积。有兴趣了解这方面的朋友们可以自行搜索,或者私信交流。

高质量的动态阴影(CSM)

Cocos Creator 从 3.0 版本开始就支持了ShadowMap,这是一个在实时图形引擎中被广泛采用的阴影技术。

但开发者们在使用的时候,经常遇上下面两个问题困惑:

如果 Shadow Distance 设小了,离摄像机远一点的地方就没了阴影,如下图所示:

如果 Shadow Distance 设大了,阴影又会特别模糊,如下图所示:

以上问题,开启CSM就好啦,不管从远处看,还是从近处看,效果都妥妥的,如下图所示:

那什么是 CSM 呢?

CSM 是 Cascaded Shadow Maps 的缩写,中文一般叫此术语翻译为:级联阴影。

为什么叫级联呢,请看下图:

素材来源于网络

CSM 中,将视锥体内的场景从近到远分为了四个层级(在 Cocos Creator 3.6中,默认的 CSM 层级为 4 层),每一个层级对应一张 ShadowMap,在阴影采样阶段再根据物体位置计算出使用哪一张 ShadowMap 最适合。

这样的操作,使得近处的 ShadowMap 覆盖较少的内容,从而确保近处的阴影清晰,而越远的 ShadowMap 则需要覆盖较多的内容,从而确保较远处的阴影可见。

CSM 的 DrawCall 开销如何呢?

虽然 4 层 CSM 需要绘制四次场景,但由于引擎的裁剪机制和阴影 Pass 绘制时的优化,额外的 DrawCall 开销不超过普通 ShadowMap 的 40%。

CSM 的内存开销如何呢?
在 Cocos Creator 的场景面板中,ShadowMap Size有四个选项:

  • Low_256x256
  • Medium_512x512
  • High_1024x1024
  • Ultra_2048x2048

CSM 4 级共享一张 ShadowMap,且每一级的开销占ShadowMap Size的1/4,比如选择了 Ultra_2048x2048,则每一级的 ShadowMap 占 1024x1024 大小。

简单说来就是,开启 CSM 和不开 CSM 的 ShadowMap 内存开销是一样的。

CSM 区域可视化调试

在上一篇关于编辑器的介绍中,我们提到了渲染调试功能,通过此功能,可以查看 CSM 各级所占的比例,如下图所示:

这个功能可以使我们在调节 Shadow Distance 和 Shadow Invisble Occlusion Range 的时候,直观地看到各级变化,根据需求精确定位各级位置。

CSM 四级太多了,可以少些吗?

目前只开放了4级,后续规划中,可能会开放层级设置和层级的lambda分布设置,从而实现更精准的层级分布和性能节省。

粒子噪声模块(Noise Module)

Cocos Creator 3.6 为粒子系统新增了噪声模块(Noise Module),可以很方便地制作出一些粒子随机飘荡的效果,如下所示:

目前粒子噪声模块有以下参数:

  • Noise Preview:预览目前采样所使用的噪点图
  • Stength X:粒子在X方向的noise强度
  • Stength Y:粒子在Y方向的noise强度
  • Stength Z:粒子Z方向的noise强度
  • Noise Speed X:噪点图在X方向的滚动变化速度
  • Noise Speed Y:噪点图在Y方向的滚动变化速度
  • Noise Speed Z:噪点图在Z方向的滚动变化速度
  • Noise Frequency:生成噪点图所使用的噪点频率,数值越大,噪点越密集
  • Octaves:生成噪点图所使用的算法层数,数值越大变化越多,计算量也越大
  • Octave Multiplier:对新层数的强度乘数
  • Octave Scale:对新层数的频率乘数

那个噪声预览图(Noise Preview)是拿来干什么用的呢?

麒麟子猜到大家会有这个疑问,所以提前问了引擎组相关人员这个问题。由于他不愿意透露自己的身份,所以在这里我们就用安迪来替代。

安迪说,设计这个主要有三个原因:

  • 1.目前所有的噪声控制参数会生成到一张 Noise Map中,并传递给粒子系统,所以希望把这个图的样子呈现给开发者。
  • 2.有一些参数在拖动的时候,很难察觉,但会在 Noise Map 中表现出来,能够有效感知参数变化
  • 3.有助于粒子系统开发过程中的迭代调试,避免工程师嘴硬

看这毫无规律、翩翩起舞的蝴蝶,只需要一个粒子发射器就搞定,是不是很棒?

除此之外,我们还可以利用噪声模块为粒子效果添加细节,如下所示:

动态模型(Dynamic Mesh)

如果你想实现一条弯弯扭扭,由你掌控的3D小蛇。

如果你想在3D空间中生成根据指示方向动态生成的提示弧线。

如果你想发挥你才华横溢的优势,自己写一套3D特效、云雾、天气系统。

你最需要的特性,就是动态更新模型数据

代码创建静态模型

在 3.6 版本之前,Cocos Creator 引擎只提供了一个用于创建静态模型的函数: utils.MeshUtils.createMesh 函数。 但它每次都会构建模型相关对象,只适合在更新频率不高的情况下使用。

代码创建动态模型

3.6 提供了专门用于处理动态模型的 utils.MeshUtils.createDynamicMesh 函数,如下图所示:

三步搞定动态模型

1、options 定义容量

动态模型创建时,需要通过 options 参数指定 3 个数据:

  • maxSubMeshes:子网格数量
  • maxSubMeshVertiecs:每一个子网格最大顶点数
  • maxSubMeshIndices:每一个子网格最大索引数

2、调用 createDynamicMesh 创建动态模型

大部分情况下,我们都只有一个 SubMesh,那在创建的时候顺便填充数据就是最方便的。这个函数的原型像这样就够了: createDynamicMesh(geometry,options):Mesh

但设计者觉得这样不够灵活,所以添加了 primitiveIndex。这个参数用于指定 geometry 属于哪个 SubMesh。

注意:记得调用 meshRender.onGeometryChanged 函数

3、调用 updateSubMesh 更新动态模型

函数原型为:updateSubMesh(primitiveIndex, geometry): void

用途是:用 geometry 的数据更新 primitiveIndex 指定的 SubMesh。

示例如下:

注意:更新完数据后,别忘了调用 meshRender.onGeometryChanged 函数

别走:关于动态模型顶点数量

回顾一下 options 中的两个属性:

  • maxSubMeshVertiecs:每一个子网格最大顶点数
  • maxSubMeshIndices:每一个子网格最大索引数

这里之所以叫 “最大” ,是因为这两个值主要决定了分配的模型数据大小,而决定真正使用多少空间的是 geometry 数据, 只要确保 geometry 数据的顶点和索引数据不超过 options 指定的值,均可正常工作。

对于顶点和索引固定的情况,建议 maxSubMeshVertiecsmaxSubMeshIndices 的值与 geometry 数据保持一致,以避免不必要的内存浪费。

但对于顶点和索引会更改的情况,则需要确保这两个最大值总是能满足 geometry 的需要。

举个简单的例子:

假如我们要实现一个由 10x10x10 个方块组成的物体,当用户点中某个方块的时候,方块会消失。

我们创建一个只有一个 SubMesh 的动态模型,maxSubMeshVertiecs 为 8000,maxSubMeshIndices 为 36000。

当点中的方块消失时,我们只需要移除 geometry 中对应的顶点数据,并调用 updateSubMesh 刷新模型即可。

类似的情况还有自制粒子、植被系统、自定义特效等场合。

官方示例

引擎组提供了一个龙的示例,点击 Update 按钮,龙会从无到有显示出来,如下所示:

有需要研究和学习的朋友,可以查看引擎官方Github仓库。

地址:https://github.com/cocos/cocos-test-projects/tree/v3.6

场景:assets/cases/dynamic-mesh

各向异性光照(Anisotropic Lighting)

在自然界中,有一些物体的表面,有大量细小齐整的沟槽。普通的图形学光照模型无法很好的重现这类物体表面的光照效果,于是产生了专门模拟此类光照效果的技术:各向异性光照。

常见的难以表达的表面:

虽然实现原理很复杂,但在 Cocos Creator 3.6 中使用各向异性光照只需下面三步:

  • 1、在 Cocos Creator 中新建一个材质
  • 2、将材质的 Effect 切换为 surfaces/standard
  • 3、开启 IS ANISOTROPY

相关的参数解释:

  • USE ANISOTROPY ROTATION MAP:是否启用各向异性旋转图
  • Anisotropy Shape:各向异性形状, 0为完全同性,1为完全异性。 这个参数也可以理解为强度(Intensity),但由于它会决定此效果的最终形状,所以用了 Shape 这个词。
  • Anisotropy Rotation:控制各向异性的条纹方向
  • Anisotropy Rotation Map:各向异性旋转图,用于精确控制条纹方向,仅在USE ANISOTROPY ROTATION MAP宏开启时有效

Cocos Creator 3.6 中渲染的效果如下:

表面着色器(Surface Shader)

什么是 Surface Shader?

Surface Shader 并不是一个新的 Shader 品种,它是引擎为了方便 Shader 编写提供的流程和框架。它对底层的 Vertex/Fragment Shader 做了封装,省去了一些重复代码编写的工作量,从而降低了 Shader 编写复杂度,提升 Shader 编写效率和兼容性。

为什么需要 Surface Shader?

随着 Cocos Creator 的 3D 特性越来越好,工作流越来越完善。越来越多的 3D 开发者,美术,TA,图形学爱好者加入到了 Cocos 生态行列。

社区 KOL 们,以及 Cocos 官方布道师们也输出了不少关于 Cocos 3D 编程Cocos Shader 学习的教程。

关于这方面的问题,麒麟子收到的反馈集中在以下几个方面:

  • Cocos Effect 内容太多了,新手看着就打退堂鼓
  • 有一些 PBR 运算相关参数和过程暴露得不够多,需要去改 trunks 里的内置 Shader 文件才能实现想要的效果
  • surf 函数里暴露了太多不需要改动的内容,比如法线、roughness、metallic 等基础运算。
  • 没有模块化,实现功能更像是在默认的Shader上打补丁,导致新版本兼容和跨项目复用困难

Surface Shader 流程的出现,极大的改善了以上情况,使自定义 Shader 更便捷。由于写法上更加简洁,使得Shader编写更容易上手,兼容性更高。

到底有多简洁?

如下图所示,一个完整的 standard-fs 满打满算 10 行。

一个完整的 Vertex Shader 或者 Fragment Shader 需要包含宏定义公共文件UBO定义光照模型Shader Stage输出目标六个部分。开发者可以根据需要,分别对这六个部分做自定义,不需要自定义的部分保持默认即可。

如何自定义 Surface Shader?

官方文档中,有完整的如何编写 Surface Shader 的步骤,其中最重要的一个就是宏定义机制。

Internal/chunks/surfaces/default-functions 目录下有 common-vsstandard-fstoon-fs 三个文件,其中定义了所有可以用宏替换的内置函数。如下图所示:

#ifndef CC_SURFACES_XXXX的意思是指,假如未定义 CC_SURFACES_XXXX,则使用下面的函数。

以较为简单的边缘光(RimLight)为例,只需要在自己的 Surface Shader 的 CCProgram surface-fragment %{ ... }% 片段中,定义 #define CC_SURFACES_FRAGMENT_MODIFY_BASECOLOR_AND_TRANSPARENCY 宏,并实现 vec4 SurfacesFragmentModifyBaseColorAndTransparency 函数即可。

以上就是关于Surface Shader编写中,麒麟子认为最核心的机制部分。

由于篇幅有限,其余内容就不展开细说,大家可结合官方文档和 surfaces/standard.effect 研究。

补充说明

1、编辑器中的渲染调试(Rendering Debug View)仅支持使用 Surface Shader 的材质。

2、目前各向异性光照仅在 Surface Shader 中有选项。

3、引擎规划中,后期的可视化 Shader 编辑器(Shader Graph)生成的 Shader 代码为 Surface Shader。

4、为了迁移性、兼容性和可读性,麒麟子建议在新项目中优先使用 Surface Shader

简单总结一下

此次的渲染相关的更新主要集中在环境反射卷积级联阴影粒子噪声模块动态模型 几个部分,当然也有许多渲染相关的提升由于太过细微,就没有在本文中出现,但并不意味着它们不重要。

这些细节部分的价值挖掘就交给朋友们自行解决咯。还等什么,打开 Cocos Creator 3.6,行动起来!

下一篇文章,麒麟子将为大家呈现 3.6 版本中,关于性能部分的提升,敬请期待!

想第一时间获得内容更新的朋友,可以关注麒麟子公众号:
qrcode_for_qilinzisuibi copy

或者 知乎主页

4赞

Cocos Creator 3.6 新特性详解 1/3:编辑器篇
Cocos Creator 3.6 新特性详解 3/3:性能篇

随后就到!

1赞

我是第一个吧,哈哈哈哈

高级,效果很棒

:laughing:
就知道麒麟子老大的文章不简单, 果然又是一篇将近万字的长文呀

又要开始学习了!

Rendering Debug View 有没有相应的教程

上班啦,催更,催更

Mark!!!

??? 1/3呢!!!!