查看原文
其他

Unite 2018 | 使用自定义渲染纹理实现炫酷特效

鲍健运 Unity官方平台 2018-11-15

自定义渲染纹理(Custom Render Textures)是Unity 2017.1发布的一项新特性,它允许开发者使用Shader轻松更新纹理,然后在普通材质中使用它。 Unity的技术经理鲍健运在Unite 2018上的演讲《使用Unity自定义渲染纹理实现炫酷特效》将以自定义渲染纹理设计开发的工作流出发,结合实际案例,帮助开发者了解如何使用自定义渲染纹理。


下面为演讲内容:


大家好,我是Unity大中华区的技术经理鲍健运,各位可以叫我Jerry。这次我给你们带来Unity中一个很有意思的特性:Custom Render Texture,即自定义渲染纹理。


引子

 

正式开始今天内容之前我先起个引子,在座各位可以观察到这是一首诗。

 

 

此诗把现代数学和物理中前沿的概念纳入优美的意境中,讴歌数学的奇迹,毫无斧凿痕迹。特别是“拈花一笑”,极为传神。佛陀拈花一笑,是告诉诸佛门弟子,一切名利是非,皆伤本体,一切荣辱皆无。拈花一笑,传递的是禅意。这首诗当然不是我本人写的,它的作者是我一直以来非常敬仰的数学大师:整体微分几何之父陈省身先生。

 

微分几何研究微分流形的几何性质,是现代数学中一主流;是广义相对论的基础,与拓扑学、代数几何及理论物理关系密切。欧拉、蒙日和高斯被公认为古典微分几何的奠基人。近代微分几何的创始人是黎曼,他在1854年创立了黎曼几何(实际上黎曼提出的是芬斯勒几何),这成为近代微分几何的主要内容,并在相对论有极为重要的作用。他与我们今天讲的主题有什么关系呢?事实上大有关系,请待我后面细细道来。


 

数学真的是非常美妙的,我们生活中的种种都离不开它。当然作为Unity开发者我们更离不开它了,这张图是我在YouTube上找到的“数学图谱”,我们可以发现,在应用数学领域、计算机科学、密码学、甚至是现在时髦的机器学习事实上我们一直在接触它们。仔细的同学还会发现,图中间土黄色,一群数学的先贤开始向外根据不同数学的分支门类,其实还是以难度的进阶辐射出去。

 


微分几何(Differential geometry)隶属于纯数学领域(Pure mathematics)中的空间数学(Spaces)范畴,位于图片的最左边的位置,足见其高深程度,而且极具现代意义。


维数的增加 

古典微分几何起源于微积分,主要内容为曲线论和曲面论,从概念上就是维度增加的分析。古典微分几何中,曲线与曲面是被放在更高维度的欧几里得空间中来考虑的。比较典型的就是下图中右侧图片,一个三角形沉浸在一个鞍形面(一个双曲抛物面)上,以及二条发散的超平行线。



那么这个和我今天要讲的自定义渲染纹理有什么关系呢?首先从我后面对于Custom Render Texture的讲解展开,大家就会发现自定义渲染纹理到画面表现,其实就是一个升级维度的过程,自定义渲染纹理之于纹理,就如纹理之于材质。如果纹理是将材质从直线到二维平面升华的重要参数的话,那么自定义渲染纹理就是能让材质从单调线条到绚丽三维实体蜕变的元素了。

 

 

高速高效高保真度的实时渲染技术,只能仰仗功能强大的图形处理单元GPU。而更进一步,画面渲染的质量再很大程度上取决于像素着色器Pixel Shader(它一般也叫做片元着色器Fragment Shader)的纹理贴图功能(Texture Mapping)。无论是像素着色器还是纹理贴图技术都需要将面映射到平面区域,同时尽量减小映射带来的几何畸变。微分几何中曲面映射理论为这些技术提供了坚实的基础,指明了算法的发展方向。


在计算机中,曲面的信息被分解成二部分,分别用不同的数据结构来表示:曲面的几何信息被表示成三维的离散多面体网格,我们称之为三角网格;曲面表面的颜色纹理、局部的皱褶、物理光学反射特性都被表示成二维的图像,我们称之为纹理图像。


参数化算法将三维网格展开到二维图像平面上,也可以被视作把二维图像粘贴到三维网格上面。Pixel Shader将三维网格曲面逐点画到屏幕上,每点的颜色由其几何位置和对应的纹理信息共同决定。参数化为Pixel Shader提供了寻址手段。传统的计算机图形学大量使用线性代数,特别是矩阵求逆和特征分解。但是参数化需要大量微分几何的知识。正因为Pixel Shader的发明,使得工业界对参数化技术提出要求,这使得微分几何开始在图形学领域粉墨登场。

 

纹理贴图技术

曲面参数化

 

在计算机图形学中,曲面参数化是指将曲面映射到平面区域,为曲面指定一个参数的过程。三维人脸曲面被映射到平面圆盘之上。纹理贴图是将平面纹理图像借助参数化贴到三维曲面之上的技术,渲染的视觉效果既依赖于曲面的几何也依赖于纹理图像。


纹理贴图技术也可以用于表示曲面的法向量信息。GPU的渲染速度反比于三角面片的个数,通常人们将曲面简化以降低三角面片个数(简称为低模曲面),提高渲染速度,同时将原始曲面的法向量存入法线贴图,用简化的网格加上法线贴图来渲染。如果GPU的Pixel Shader功能强大,那么这种技术解决方式可以达到高质量高速度的实时渲染效果。


微分几何之曲面映射理论

 

保持角度变换

 

曲面间的映射非常复杂,基于对这一问题不同层面的考察,微分几何领域建立了许多领域,彼此之间的关系也错综复杂。在计算机科学领域,已经被应用了的微分几何中曲面映射理论包括:调和映照、共形映射、拟共形映射、最优传输理论等。


上图中显示的是从女孩人脸曲面到平面单位圆盘间的保角变换。这种变换保持局部形状不变,把曲面上的无穷小圆映成平面上的无穷小圆。这种变换有无穷多种,并且构成一个群,但是这个群是有限维的,因此容易控制。这个是大多数游戏引擎会选择的这种来保真。


Render Texture – 渲染纹理

对于纹理贴图技术和微分几何的关系就暂时讲解到这里,我们现在回到Unity上,先了解一下什么是Render Texture,渲染纹理。


 

Render Texture本身而言,它是在运行时(Runtime)创建和更新的特殊纹理。使用是需要单独创建一个Render Texture 对象,指定一个摄像机对其进行渲染。即Render Texture是通过相机进行采样,采样过来之后映射到纹理上面,这个纹理本身就产生了一些变化,这就好比像我们维度的提升。因为在纹理本身就发生了变化,它不是完全静态的。这个等于说是一个维度的提升,但是这个提升还是比较有限的。


 

 

Render Texture 快速搭建

了解Render Texture开发的同学应该知道,搭建一个Render Texture是挺容易的:

  • 通过Asset → Create → Render Texture 创建

  • 为Camera指定Render Texture

  • 场景中创建Cube,直接拖拽Render Texture到Cube 上

  • 选择Camera查看

 

通过以上的操作,我们基本能得到如下图的渲染纹理的表现。

 

 

图中红色箭头指出的就是使用了渲染纹理的材质,显示了某个非主摄像机捕捉到的画面。

 

Render Texture 后期处理

另外一种使用方式在后期处理中比较多的,就是Graphis.Blit和Graphis.BlitMultiTap,它会对数据进行处理,然后影射到Render Texture,这样可以对Render Texture的画面做一些改造。例如:高斯或迭代锥模糊在多个不同位置采样源纹理。


但是这样的表现还是相当有限的,因为我们能控制的东西还是比较少,而且很多东西都是由摄像机告诉你该有多少改多少,对于其本身的内容操控还是比较有限。如何扩展更丰富的纹理渲染变化的世界呢?


Custom Render Texture – 自定义渲染纹理

这时候Custom Render Texture横空出世了。它应该也不是算一个新功能,诞生于Unity 2017.1版本。自定义渲染纹理的出现可以说是打开了渲染纹理处理的手脚,为我们创作带来了更多的可能性。现在就让我先来看看,Custom Render Texture到底是个什么样的东西呢?


  

自定义渲染纹理是渲染纹理的扩展,允许用户使用着色器轻松更新所述纹理。 要使用它们,需要创建一个新的自定义渲染纹理资源并为其分配一个材质。 此材质将根据其各种参数更新纹理的内容。 自定义渲染纹理然后可以像任何常规纹理一样分配给任何类型的材质,甚至可以将其用于另一个自定义渲染纹理。


当然我们还可以根据实际需求将Custom Render Texture导出为PNG或EXR文件。各位设想一下,在Custom Render Texture中,Material材质成为了参数,这就意味着我们可以进行更深一层次的画面控制与处理,而从感官维度上,我们为玩家,为观众提供的就是二重积分,甚至是多重积分的炫目表现可能性。


 

适用情况

这里有一些预设好大家可以参考的适用场景,主要是对于实施各种复杂的仿真非常有用。例如:焦散、雨滴效果的波纹模拟、墙壁上的液体泼溅等。它还提供了脚本和着色器框架,以帮助实现更复杂的配置。例如:部分或多通道更新、多样化更新频率等。

 

主要部分  

Custom Render Textures的拥有比Render Texture更多的属性,主要分为三个部分:

  • Material材质:定义用于更新纹理的着色器。

  • Initialization初始化:控制着色器完成任何更新之前纹理的初始化方式。由于着色器撰写的某些模拟仿真可能不会有初始化的情况,这就需要通过这个选项事先做好重设以便展现所需的画面效果。

  • Update更新:控制着色器如何更新纹理。



材质选项下面的Shader Pass着色器通道,用于更新自定义纹理。 下拉框中会显示材料中可用的所有通道。

 

模式

初始化模式与更新模式各有三种方法,这个方法就是分别调用处理的时机和方式。

  • OnLoad载入时:就是材质载入进来的时候,它就直接执行。

  • Realtime实时:就是不但载入的时候,中间会不停的调用。但是是由我们这边材质本身调用。

  • OnDemand外部请求:这个就是通过外面的C#脚本来控制时机以及对应的操作。


 

另外在初始化模式中,它还有个Source资源选项,用于设置初始化所需的资源。

  • Texture and Color颜色加纹理的方式:这样的话比较简单的,像拿2D的纹理图片做一些简单的画面表现,当然这个不用播东西,就是根据实际需要开发的时候才放那些东西。

  • Material材质:这个材质可以是一个常规的材质文件,或者也可以是另一个Custom Render Texture制作出来的材质,即嵌套纹理。

 


更新区域

Update Zone 更新区域:定义更新区域的坐标系统,它有二种尺寸方式:

  • Normalized 归一化的:所有的坐标和大小都在0到1之间,左上角从(0,0)开始

  • Pixed像素:所有的坐标和大小都用纹理宽度和高度限制的像素表示,左上角从(0,0)开始



默认情况下,当自定义渲染纹理更新时,整个纹理由材质立即更新。 自定义纹理的一个重要功能是用户可以定义部分更新区域的功能。 借此用户可以根据需要,定义多个区域并处理它们的顺序。这可以用于几个不同的目的。 例如:可以有多个小区域来在纹理上溅出水滴,然后完整地传递以模拟波纹。 当你知道不需要更新完整纹理时,也可以将其用作优化。

 

如何制作Custom Render Texture? 

怎么样制作一个自定义渲染纹理,给大家看一个简单的小例子。我这边写了一个比较简单的Shader,里面写了一些数学方式,写了一些雨滴附着在地面的效果。这个左右二边二张图,一张是通过自定义渲染纹理做出来的,大家猜一下是哪张?看不出来是吧?看不出来就对了。


 

左边这张是普通的一个,我通过Diffuse方式创建的,就是直接拿一张2D的纹理,下面加了一个Noise,通过计算雨滴的方式实现模拟的这个效果。而右边大家可以看到,它只有一个元素,而这个元素调用的话也就是一个Diffuse,这边是我自己写的一个,直接使用了一个纹理。大家看到那张图片,这个是Render Texture的图片,而自定义渲染纹理也是这个icon。

 

 

快速搭建

第一步:把原有的Shader改到适用于自定义渲染纹理库,即include UnityCustomRenderTextue.cginc。还有需要调用的方法,主要针对自定义渲染纹理的。如果大家写起来比较熟悉,修改难度并不大。像常用的,我们默认的是V2f,主要用在更新情况下。这里就是使用V2f_customrendertexture作为中间数据结构 。



第二步:与制作普通的材质的方式一样,新建一个Material材质,使用刚才写好的Shader,而主纹理MainTex就是用这张雨滴背景的图片,并关联Noise噪音纹理作为SecondTex。

 

 

第三步:这个材质设置好了什么时候用?我就把它放回到Custom Render Texture里,做完之后这就完全可以生成Custom Render Texture,这个其实可以显示和左边这张完全一模一样表现出来。


 

第四步:指定Shader Pass。在Custom Render Texture实现的Shader中,添加分别实现CustomRenderTextureVertexShader和frag的Pass,并命名“Update”。而这个Update的名字就可以在Shader Pass的列表中找到。但是不写的话也可以给一个默认的ID符,但是可能是你不太明白的到底哪个对应哪个。



第五步:因为这里我更新模式选用OnDemand,即通过外部指令控制更新的方式。所以,我另外实现了一个控制脚本,CRTUpdate.cs。创建一个Custom Render Texture变量,用户更新的控制,也可以用于初始化的控制。

 

 

第六步:最后这个自定义渲染纹理就完成了,应该怎么使用呢?完全可以当普通纹理使用,我随便做一个材质挂到对应点,就当普通纹理使用。为什么这个点比较好?因为只要纹理能用的地方,自定义渲染纹理都可以用,不像渲染纹理只和摄像机相关的才能处理。

 


对大家来讲拓展了新的空间,所以我说这是维度的变化,现在就是从普通函数到了二重积分的,这样你的想象空间会更多,所以它可以模拟一些复杂的仿真。像这样的可能性,我拿了一个PBR的材质,可以放到任何通道里,右边的话可以看到放到Normal Map,甚至放到Height Map等,可以调出很多有趣的效果。


案例一:反应扩散系统

反应-扩散系统(reaction–diffusion system)是一类数学模型的统称。这类模型和许多物理现象相关:最常见的是一种或多种化学物质的浓度在空间分布上的变化。在这个过程中,局部的化学反应使化学物质相互转变;扩散作用使物质向四周分散运动。


反应-扩散系统模拟的是在反应和扩散这二种不同机制的竞争下,物质在空间分布上的具体行为。反应-扩散系统不仅被应用到化学研究中,还可以描述没有化学反应参与的动力学过程,在生物学、地质学、物理学(中子扩散理论)和生态学领域中都有相应的例子。反应-扩散方程的解行为五花八门,其中包括行波的形成、类波现象,以及自组装图案(条纹、六边形或是更复杂的结构,例如耗散孤子)。


 

下面是我在这个Demo中所使用到的反应-扩散系统的等式和化学反应公式,遵循Gray-Scott模型:

  1. U和V和P是化学物质。

  2. u和v代表它们的浓度。

  3. du和dv是它们的扩散速率。

  4. k表示V到P的转化率。(一般用kill表示,简写k,旧元素死亡的速度)

  5. f表示供给U并排出U,V和P的过程的速率。(一般用feed表示,简写f,生成新元素的速度)



这个部分就是在Shader中,将Gray-Scott模型的CG代码实现,大家可以到最后参考的Github地址获取到源码进行学习。


 

需要选用一个PRNG (伪随机数生成器)来生成随机数。理想的情况是CPU在每一帧提供随机的种子,PRNG则会生成前后无关的随机数。这里我选用的是烟雾模拟中的随机数生成算法,获得了0-1之间随机分布的随机数。因为反应扩散系统,是不断对空间侵蚀的反应,这里如果不对显示进行重设,无论你怎么运行,它都会停在之前结束的那一刻的样子。所以这个初始化最主要的作用是对于画面的重新设置,以便于每次展示的时候都是从零的开始。


 

完成了二个材质,分别是RD Initializer和RD Updater,可以搭建出所需的Custom Render Texture。


 

这个RDSystemUpdater.cs脚本就是用于Custom Render Texture初始化和更新,而且更新时隙也是有脚本来控制的。


 

最后,怎么表现液体的金属感呢?这里写了表面着色器Surface Shader,输出对于Standard Shader标准着色器参数的处理,对数据,像平滑度,光泽等等进行一些数据的调整,这样能表现出所需要的像液滴抛起来的效果。

 


现在这个实现的RD Texture就可以像普通的纹理文件一样应用到刚才建好的Surface Material中去使用。


案例二:Mirror催眠之眼

下面的案例大家可能比较感兴趣了,也许你已经观看过《ADAM 2》中的Mirror催眠之眼,这里主要用到了Custom Render Texture。


Mirror的眼睛特别明显,就像波纹移动的效果。虽然她出现的次数不多,但是那双眼睛的确很惊艳。她的这双眼睛用到什么技术了呢?


首先就是Custom Render Texture,没有这个东西,制作团队的技术指导说过,没有这个东西的话我们想出这样的东西都出不来,想催眠你都催眠不了。还用了Alembic流,以及GPU曲面细分和GPU程序化方式顶点动画。但是这都不是我要讲的重点。

 


大家可以看到工程中间这些代码的位置,大家可以看到Mask Texture有二块白色的区域,这个就是从0-1的值,只有这样才能在这个区域实现那个效果,否则的话就变成麻子脸。这里影响波纹效果的参数有哪些,第一个,是噪音。如果没有噪音的话就太假。Peak,就是对有效区域内设置峰值,让它进行有效变化。Wave是波形控制。


 

我们这个Displacement效果怎么做出来的呢?它就是用到了Custom Render Texture材质。大家看到最下面制造的眼睛会呈现一些红色,这个就是Custom Render Texture所呈现的一些效果。在实现本身的CustomRenderTexture_Mirror_Displacement着色器中,使用了一些波形相关的方法比如Sin、Peak和Wave,从而可以实现Mirror眼部波纹状的奇幻效果。

 


当然还有一个不可或缺的就是Normal,即法线部分实现。没有它与Displacement的结合,就无法表现出精湛的凹凸感的画面效果。

 


Mirror Head Skin这个材质其实是基于Unity Standard Shader(PBR)进行二次开发的着色器。这里将Mirror的皮肤应用为主纹理,以自定义渲染纹理CustomRenderTexture_Mirror_Normal作为Normal Map,然后将自定义渲染纹理CustomRenderTexture_Mirror_Displacement作为曲面细分的Displacement,来进行画面表现。

 


以上就是Mirror催眠之眼的Custom Render Texture实现。

 

正如我前面为大家所说的,Mirro催眠之眼其实很早Unity就公布给大家了。各位可以在Asset Store查找资源“ADAM EP2/EP3 – Chararcter Pack”,这里我们三个重要的角色:机器人,Mirror和先知都有,并且每个都有单独的Scene场景文件用于查看。


更多参考资料


主要API及实现方法:

  • docs.unity3d.com/Manual/CustomRenderTextures.html

  • docs.unity3d.com/ScriptReference/CustomRenderTexture.html

 

Render Texture后期处理

  • https://docs.unity3d.com/ScriptReference/Graphics.Blit.html

  • https://docs.unity3d.com/ScriptReference/Graphics.BlitMultiTap.html


反应扩散系统项目:

github.com/keijiro/RDSystem

 

快速搭建Custom Render Texture来源:

www.shadertoy.com

 

小结

最后,非常感谢各位倾听我的演讲!更多Unity技术文章请访问Unity官方中文论坛(Unitychina.cn)!


 

Unite Beijing 2018 精品演讲

技术直播

5月30日本周三晚 0点,新一期的Unity官方技术直播课程即将开课,Unity技术经理成亮将带你了解Unity 2018中的渐进光照贴图Progressive Lightmappe。[了解详情]


直播地址:

https://connect.unity.com/events/unitychina-progressivelightmapper

 

点击“阅读原文”访问Unity官方中文论坛

    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存