使用多边形资产时,一次只能绘制一个对象(如果不考虑批处理和实例化等技术),但是如果您使用带符号的距离字段(带符号距离字段,SDF),那么我们不仅限于此。 如果两个位置的坐标相同,则带符号的距离函数将返回相同的值,并且在一次计算中,我们可以获得多个数字。 要了解如何转换用于生成带符号距离字段的空间,建议
您弄清楚如何
使用带符号距离函数创建形状并
组合sdf shape 。
构型
在本教程中,我修改了正方形和圆形之间的配对,但是您可以将其用于任何其他形状。 这类似于上
一教程的配置。
在此重要的是,可修改部分应在使用位置生成图形之前。
Shader "Tutorial/036_SDF_Space_Manpulation/Type"{ Properties{ _InsideColor("Inside Color", Color) = (.5, 0, 0, 1) _OutsideColor("Outside Color", Color) = (0, .5, 0, 1) _LineDistance("Mayor Line Distance", Range(0, 2)) = 1 _LineThickness("Mayor Line Thickness", Range(0, 0.1)) = 0.05 [IntRange]_SubLines("Lines between major lines", Range(1, 10)) = 4 _SubLineThickness("Thickness of inbetween lines", Range(0, 0.05)) = 0.01 } SubShader{
2D_SDF.cginc函数与着色器位于同一文件夹中,我们将对其进行扩展,首先看起来像这样:
#ifndef SDF_2D #define SDF_2D
重复空间
镜面反射
最简单的操作之一是围绕轴镜像世界。 为了使其绕y轴镜像,我们获取位置x分量的绝对值。 因此,轴线左右的坐标将相同。
(-1, 1)
变成
(1, 1)
,并以
(1, 1)
为坐标原点并且半径大于0变成圆内。
通常,使用此功能的代码看起来像
position = mirror(position);
所以我们可以简化一下。 我们将简单地将position参数声明为inout。 因此,在写入参数时,它还将更改我们传递给函数的变量。 然后,该返回值可以是void类型,因为我们仍然不使用该返回值。
结果已经很好,但是通过这种方式,我们只获得了一个用于镜像的轴。 我们可以像旋转图形时那样旋转空间来扩展功能。 首先,您需要旋转空间,然后对其进行镜像,然后再将其旋转回去。 这样,我们可以针对任何角度执行镜像。 转移空间并在镜像后执行反向转移时,也可以这样做。 (如果执行这两个操作,则在镜像之前,请不要忘记先进行传输,然后再转弯,然后先转弯。)
细胞
如果您知道
噪声产生的工作原理,那么您就会知道,对于程序生成,我们经常重复该位置并获得基本相同的小单元,只是参数无关紧要。 我们可以对距离场做同样的事情。
由于
fmod
函数(以及使用%除以余数)为我们提供了余数,而不是余数的定义,因此我们将不得不使用技巧。 首先,我们将其余的整数除以fmod函数。 对于正数,这正是我们需要的;对于负数,这是我们需要的结果减去周期。 您可以通过添加句点并再次使用除法的其余部分来解决此问题。 添加一个周期将为负输入值提供理想的结果,而对于正输入值,该值高一个周期。 除法的第二个余数不会对负输入值的值执行任何操作,因为它们已经在0到周期之间的范围内,而对于正输入值,我们将实质上减去一个周期。
单元的问题在于,我们失去了我们喜欢距离场的连续性。 如果形状仅在单元格的中间,这也不错,但是在上面显示的示例中,当将距离场用于通常可应用距离场的各种任务时,这可能会导致产生明显的伪影,应避免使用。
有一种解决方案并非在每种情况下都有效,但是当它可行时,这很妙:镜像所有其他单元。 为此,我们需要一个像素单元索引,但是函数中仍然没有返回值,因此我们可以使用它来返回单元索引。
为了计算单元格索引,我们将位置除以句点。 因此,0-1是第一个单元格,1-2是第二个单元格,依此类推...我们可以轻松地离散化它。 为了获得单元格的索引,我们然后简单地将值四舍五入并返回结果。 重要的是在除以其余部分以重复单元之前,我们先计算单元的索引; 否则,我们将在任何地方获得索引0,因为头寸不能超过句点。
有了这些信息,我们就可以翻转单元格。 要了解是否翻转,我们将单元索引取模2。此操作的结果是每隔2个单元交替显示0和1或-1。 为了使更改更持久,我们采用绝对值并获得一个介于0和1之间的值。
要使用此值在正常位置和翻转位置之间进行翻转,我们需要一个对值0不执行任何操作的函数,并从翻转为1的周期中减去该位置。即,我们使用flip变量从法向位置到翻转位置执行线性插值。 由于flip变量是2d向量,因此其分量会单独翻转。
细胞
另一个重要特征是径向模式中的空间重复。
为了获得这种效果,我们首先计算径向位置。 为此,我们对相对于x轴中心的角度以及与中心沿y轴的距离进行编码。
float2 radialPosition = float2(atan2(position.x, position.y), length(position));
然后,我们重复角落。 由于传输重复次数比每个片段的角度容易得多,因此我们首先计算每个片段的大小。 整个圆是2 * pi,因此要得到正确的部分,我们将2 * pi除以像元大小。
const float PI = 3.14159; float cellSize = PI * 2 / cells;
有了这些信息,我们可以在每个cellSize单位上重复径向位置的x分量。 我们用除数除法进行重复,因此,像以前一样,我们会遇到负数的问题,这可以通过除数除法的两个函数来消除。
radialPosition.x = fmod(fmod(radialPosition.x, cellSize) + cellSize, cellSize);
然后,您需要将新位置移回通常的xy坐标。 在这里,我们将sincos函数与径向位置的x分量用作角度,以将正弦写入位置的x坐标,并将余弦写入y坐标。 通过这一步,我们得到一个标准化的位置。 要从中心获得正确的方向,请将其乘以径向位置的分量y,即长度。
然后,我们也可以像常规单元格一样添加单元格索引和镜像。
有必要在计算径向位置之后但在从除法运算接收余数之前,计算像元索引。 我们通过除以径向位置的x分量并将结果四舍五入得到它。 在这种情况下,索引也可以为负数,如果单元格数为奇数,则这是一个问题。 例如,对于3个像元,我们得到1个像元的索引为0、1个像元的索引为-1和2个半像元的索引为1和-2。 为了解决这个问题,我们将单元格的数量加到变量的四舍五入后的变量中,然后除以剩下的单元格的大小。
为了反映这一点,我们需要以弧度指定坐标,为避免在函数外重新计算径向坐标,我们将使用bool参数为其添加一个选项。 通常在着色器中,不欢迎分支(如果构造),但是在这种情况下,屏幕上的所有像素都将沿着相同的路径前进,因此这是正常的。
镜像应该在径向坐标循环之后但在转换回其正常位置之前发生。 我们找出是否需要通过将单元格索引除以2来除以当前单元格,通常这应该给我们零和一,但是在我的情况下会出现几个二,这很奇怪,但是我们可以处理它。 为了消除推论,我们只需从flip变量中减去1,然后取绝对值即可。 因此,根据需要,零和减数将变为1,单位变为零,仅以相反的顺序。
由于零和一的顺序不正确,因此我们像以前一样执行从上向下版本到上向下版本的线性插值,反之亦然。 要翻转坐标,我们只需从像元大小中减去位置即可。
摇摆空间
但是改变空间是没有必要重复的。 例如,在基础教程中,我们对其进行了旋转,移动和缩放。 您还可以执行以下操作:使用正弦波在每个轴的基础上移动每个轴。 这会使带符号的距离函数的距离不太准确,但是除非它们摆动太大,否则一切都会好起来的。
首先,我们通过翻转x和y分量,然后将它们乘以摆频来计算位置变化的幅度。 然后,我们从该值中获取正弦值,然后将其乘以要添加的摆动量。 之后,我们只需将此摆动因子添加到位置,然后再次将结果应用于位置。
我们还可以对此挥舞进行动画处理,更改其位置,在偏移位置应用挥舞并返回该空间。 为了使浮点数不会太大,我用摆动频率除以pi * 2的余数,这与摆动相关(正弦波每pi * 2个单位重复一次),因此我们避免了跳跃和太大的偏移量。
源代码
2D SDF库
#ifndef SDF_2D #define SDF_2D
基本的演示着色器
Shader "Tutorial/036_SDF_Space_Manpulation/Mirror"{ Properties{ _InsideColor("Inside Color", Color) = (.5, 0, 0, 1) _OutsideColor("Outside Color", Color) = (0, .5, 0, 1) _LineDistance("Mayor Line Distance", Range(0, 2)) = 1 _LineThickness("Mayor Line Thickness", Range(0, 0.1)) = 0.05 [IntRange]_SubLines("Lines between major lines", Range(1, 10)) = 4 _SubLineThickness("Thickness of inbetween lines", Range(0, 0.05)) = 0.01 } SubShader{
现在您已经知道了记号距离功能的所有基础知识。 在下一个教程中,我将尝试对它们做一些有趣的事情。