在本教程中,我将讨论如何使用着色器在Unity中重新创建流行的精灵涂鸦效果。 如果您的样式需要这种样式,那么从本文开始,您将学习如何在不渲染其他图像的情况下实现它。
在过去的几年中,这种风格变得越来越流行,并在诸如
GoNNER和
Baba is You之类的游戏中得到积极使用。
本教程涵盖了您所需的一切,从着色器编码的基础到所使用的数学。 本文结尾处有一个链接,用于下载完整的Unity软件包。
Doodle Studio 95的成功启发了我创建本教程
! 。
引言
在我的博客中,我探讨了非常复杂的主题,从
逆运动学到 大气瑞利散射 。 我真的很想让如此困难的话题为广大读者所理解。 但是对它们感兴趣并具有足够技术水平的人数并不是很多。 因此,最受欢迎的文章是最简单的,您应该不会感到惊讶。 这也适用于
尼克·卡曼(Nick Caman)最近的一条
推文 ,其中他展示了如何在Unity中创建
涂鸦效果 。
在进行了1000次点赞和4000次转推后,很明显,对即使对于几乎不了解着色器知识的人也可以研究的简单教程有强烈的需求。
如果您正在寻找一种专业且有效的方式来动画化具有高度艺术性的2D精灵,那么我强烈建议
Doodle Studio 95! (请参见下面的GIF)。
在这里,您可以查看一些使用此工具的游戏。
涂鸦效果解剖
要重新创建Doodle效果,我们首先需要了解它的工作原理以及使用的技术。
着色效果。 首先,我们希望这种效果尽可能简单,并且不需要其他脚本。 这要归功于使用
着色器 ,该
着色器告诉Unity如何在屏幕上渲染3D模型(包括平面模型!)。 如果您不熟悉
着色器编码领域 ,那么您应该阅读我的文章
“着色器的柔和介绍” 。
精灵着色器。 Unity附带许多类型的着色器。 如果使用提供的Unity 2D工具,则很可能正在使用
sprites 。 如果是这样,则需要
Sprite SpriteRenderer
一种与
SpriteRenderer
Unity兼容的特殊类型的着色器。 或者,您可以从更传统的
Unlit shader开始 。
顶点偏移。 手动绘制精灵时,没有框架会与其他框架完全重合。 我们想以某种方式使精灵“摆动”以模拟这种效果。 着色器具有一种非常有效的方法,即使用
顶点偏移 。 这是一项允许您更改3D对象顶点位置的技术。 如果我们随机移动它们,则会获得理想的效果。
旅行时间。 徒手动画通常具有较低的帧频。 如果我们想每秒模拟五帧,那么我们需要每秒改变精灵顶点的位置五次。 但是,Unity可能以更高的刷新率运行游戏。 也许每秒30甚至60帧。 为了使Sprite每秒不改变60次,您需要处理动画计时组件。
步骤1:完成精灵着色器
如果要在Unity中创建新的着色器,则选择将非常有限。 我们可以开始使用的最接近的着色器是
Unlit Shader ,尽管它不一定最适合我们的目的。
如果您希望Doodle着色器与
SpriteRenderer
Unity完全兼容,那么我们需要对现有的
Sprite Shader进行补充。 不幸的是,我们无法直接从Unity本身访问它。
您可以转到
Unity下载档案页面,然后下载要使用的
Unity版本的“
在着色器中构建”包,以获取相关信息。 这是一个zip文件,其中包含Unity构建随附的所有着色器的源代码。
下载后,将其解压缩,然后在
builtin_shaders-2018.1.6f1\DefaultResourcesExtra
Sprites-Diffuse.shader
找到
Sprites-Diffuse.shader
文件。 这是我们将在本教程中使用的文件。
Sprites-Diffuse不是标准的精灵着色器!创建新的精灵时,其标准材质使用名为Sprites-Default.shader
的着色器,而不是Sprites-Diffuse.shader
。
两者之间的区别在于,第一个不使用照明,而第二个对场景中的照明做出响应。 由于Unity实现的性质,与没有光照的版本相比,漫反射版本更容易编辑。
在本教程的最后,有一个链接,可下载带有和不带有照明的涂鸦着色器。
步骤2:偏移顶点
在
Sprites-Diffuse.shader
内部,有一个叫做
vert
的函数,这是我们上面讨论的
顶点函数 。 它的名称并不重要,主要是它与
#pragma
的
vertex:
部分中指定的名称一致:
#pragma surface surf Lambert vertex:vert nofog nolightmap nodynlightmap keepalpha noinstancing
简而言之,将为3D模型的每个顶点调用顶点函数,并决定如何将其叠加在二维屏幕空间上。 在本教程中,我们仅对如何移动对象感兴趣。
appdata_full v
参数包含
vertex
字段,该字段包含
对象空间中每个顶点的3D位置。 值更改时,顶点移动。 也就是说,例如,下面显示的代码将使用其着色器将对象沿X轴传输一个单位。
void vert (inout appdata_full v, out Input o) { v.vertex = UnityFlipSprite(v.vertex, _Flip); v.vertex.x += 1; #if defined(PIXELSNAP_ON) v.vertex = UnityPixelSnap (v.vertex); #endif UNITY_INITIALIZE_OUTPUT(Input, o); o.color = v.color * _Color * _RendererColor; }
默认情况下,在Unity中创建的2D游戏只能在X和Y轴上运行,因此我们需要更改
v.vertex.xy
才能在二维平面上移动精灵。
什么是对象空间?appdata_full
结构的vertex
字段包含着色器在对象空间中处理的当前顶点的位置。 这是假设对象位于世界中心(0,0,0)且不改变比例和不旋转的情况下顶点的位置。
世界空间中表达的峰顶反映了其在Unity场景中的真实位置。
为什么物体不以每帧一米的速度移动?如果在C#脚本的Update
方法中将+1添加到transform.position
值的分量x
中,我们将看到对象如何以每帧1米的速度向右飞行,即每小时约216公里。
这是因为C#所做的更改正在更改位置本身。 在顶点函数中,这不会发生。 着色器仅更改模型的视觉表示,而不会更新或修改模型的存储顶点。 这就是为什么在v.vertex.x
添加+1 v.vertex.x
一个对象移动一米。
记住将精灵导入为“紧”!此效果会移动精灵的顶部。 传统上,精灵会以四边形的形式导入Unity(请参见左图)。 这意味着它们只有四个峰。 如果是这样,则只能移动这些点,从而降低了涂鸦效果的强度。
为了获得更强烈,更逼真的失真,您需要通过为“
网格类型 ”(
Mesh Type)参数选择“
紧缩”(Tight)来导入精灵,这会将它们变成凸形(请参见右图)。
这会增加顶点数量。 这并不总是可取的,但这正是我们现在所需要的。
随机偏移
涂鸦效果随机移动每个顶点的位置。 在着色器中对
随机数进行采样一直是一项艰巨的任务。 这主要是由分布式GPU体系结构引起的,该体系结构使大多数库(包括
Mathf.Random
)中使用的算法的复杂化和降低了效率。
尼克·卡曼(Nick Caman)的帖子使用了一种噪声纹理,该噪声纹理在进行采样时会给人以随机的感觉。 在您的项目环境中,这可能不是最有效的方法,因为在这种情况下,着色器执行的纹理搜索操作的数量增加了一倍。
因此,在大多数着色器中,使用了相当混乱和混乱的函数,尽管它们具有确定性,但在我们看来似乎没有规律性。 并且由于必须分配它们,因此每个随机数必须使用其自己的种子生成。 这对我们来说很棒,因为每个顶点的位置必须唯一。 我们可以使用它来将随机数绑定到每个顶点。 稍后我们将讨论该随机函数的实现。
random3
我们将其
random3
。
我们可以使用
random3
生成每个顶点的随机偏移。 在下面的示例中,使用
_NoiseScale
属性缩放随机数,使您可以控制位移力。
void vert (inout appdata_full v, out Input o) { ... float2 noise = random3(v.vertex.xyz).xy * _NoiseScale; v.vertex.xy += noise; ... }
现在我们需要自己编写
random3
代码。
着色器中的随机性
着色器中使用的最常见和标志性的伪随机函数之一摘自W. Rey在1998年发表的题为“
借助y = [(a + x)sin(bx)] mod 1生成随机数 ”)的文章。
float rand(float2 co) { return fract(sin(dot(co.xy ,float2(12.9898,78.233))) * 43758.5453); }
此函数是确定性的(也就是说,它不是
真正随机的),但是它的行为是如此随机,以至于看起来完全是随机的。 这种功能称为
伪随机 。 在本教程中,我选择了
Nikita Miropolsky发明的更为复杂的功能
。在着色器中生成伪随机数是一个非常复杂的主题。 如果您有兴趣了解有关她的更多信息,那么《
The Shader of Shaders 》对她有很好的介绍。 此外,
Patricio Gonzales Vivo还建立了一个大型存储库,称为
GLSL noise ,可以在着色器中使用。
步骤3:增加时间
多亏了我们编写的代码,每一帧中的每个点都偏移了相同的量。 因此,我们得到了扭曲的精灵,而不是涂鸦效果。 要解决此问题,您需要找到一种随时间改变效果的方法。 最简单的方法之一是使用顶点位置和当前时间来生成随机数。
在本例中,我只是将当前时间(以
_Time.y
秒为单位)
_Time.y
到了顶点位置。
float time = float3(_Time.y, 0, 0); float2 noise = random3(v.vertex.xyz + time).xy * _NoiseScale; v.vertex.xy += noise;
更复杂的效果可能需要更复杂的方法来增加方程式的时间。 但是因为我们只对间歇随机效应感兴趣,所以两个值绰绰有余。
时间切换
添加
_Time.y
的主要问题在于,它会导致精灵在每一帧中都被设置为动画。 这对于我们来说是不可取的,因为大多数手绘动画的帧频都较低。 时间分量不应是连续的,而是离散的。 这意味着,如果我们想每秒显示五帧,那么每秒应该只更改五次。 也就是说,时间应
限制在五分之一秒。 唯一有效的值应该是
,
,
,
,
,
与,等等...
我已经在“
如何捕捉到网格”的博客中介绍了
捕捉 。 在本文中,我提出了解决将对象的位置绑定到空间网格的问题的解决方案。 如果我们需要将时间绑定到时间网格,那么数学以及代码将是相同的。
下面显示的函数将数字
x
绑定到整数值,该整数值是
snap
倍数。
inline float snap (float x, float snap) { return snap * round(x / snap); }
也就是说,我们的代码如下所示:
float time = snap(_Time.y, _NoiseSnap); float2 noise = random3(v.vertex.xyz + float3(time, 0.0, 0.0) ).xy * _NoiseScale; v.vertex.xy += noise;
结论
可以
在Patreon上免费下载用于此效果的Unity软件包。
其他资源
在过去的几个月中,出现了许多涂鸦风格的游戏。 在我看来,这样做的原因是
Doodle Studio 95的成功
! -由
Fernando Ramallo开发的Unity工具。 如果这种风格适合您的游戏,那么我建议您购买此出色的工具。