是否有(一个)单调非递减噪声函数?


10

我想要一个函数,该函数可以使对象随时间从A点移动到B点,以使其在某个固定时间到达B点,但是它在任何时候的位置都会以连续的方式随机地受到扰动,但是永远不会向后退。对象沿直线移动,所以我只需要一个维度。

从数学上讲,这意味着我正在寻找一些连续的f(x),x∈[0,1],使得:

  • f(0)= 0
  • f(1)= 1
  • x <y→f(x)≤f(y)
  • 在“最”点,f(x + d)-f(x)与d没有明显关系。(该函数不是均匀增加的或不可预测的;我认为这也等同于说没有导数是常数。)

理想情况下,我实际上希望以某种方式拥有这些功能的族,提供某种种子状态。对于我当前的使用,我至少需要4位种子(16个可能的函数),但是由于没有太多的余地提供更多。

为了避免误差积累的各种问题,我宁愿功能并不需要任何的内部状态。也就是说,我希望它是一个真正的功能,而不是编程的“功能”。


3
您的第三个和第四个要求可以近似为f'(x)>0,因此任何噪声函数的绝对值的归一化积分将满足您的所有要求。不幸的是,我不知道有什么简单的方法可以计算出来,但是也许有人可以做到。:)
SkimFlux 2012年

会干扰您的函数的瞬时斜率的垂直方向吗?
kaoD 2012年

当您说“避免累积错误的各种问题”时,我以为您担心精度。根据您的许多评论,您似乎担心过多评估的性能成本。您应该确切说明我们要受到的性能和内存限制-无论如何,此要求无济于事,因为看似可以构造不带累积错误的状态的函数(无论如何意味着什么?)。另外,您的第四点是错误的。一个简单的例子:e ^ x的任何导数都不是常数,所以这并不等于说。
最佳

Answers:


4

对于此帖子,y = f(t),其中t是您要更改的参数(时间/进度),y是到目标的距离。因此,我将根据2D绘图上的点进行发言,其中水平轴是时间/进度,垂直轴是距离。

我认为您可以制作三次贝塞尔曲线,第一个点位于(0,1),第四个点(最后)位于(1,0)。可以将两个中间点随机放置在此1×1矩形内(x = rand,y = rand)。我无法通过分析来验证这一点,但是仅仅通过玩一个applet(是的,继续笑),似乎Bezier曲线将永远不会因这种限制而减小。

这将是您的基本函数b(p1,p2),它提供了从点p1到点p2的非递减路径。

现在您可以生成ab(p(1)=(0,1),p(n)=(1,0))并沿该曲线选取多个p(i)使得1

本质上,您将生成一个“通用”路径,然后将其分解为多个段并重新生成每个段。

由于您需要一个数学函数:假设上述过程打包为一个函数y = f(t,s),该函数为种子s的函数提供了t处的距离。你会需要:

  • 4个随机数,用于放置主Bezier样条曲线的2个中点(从(0,1)到(1,0))
  • 如果您有n个段,则n-1个数字代表每个段的边界(第一个段始终从(0,1)开始,即t = 0,最后一个段从(1,0)开始,即t = 1)
  • 如果要随机化段数,则为1个数字
  • 还有4个数字,用于放置t线段样条的中点

因此,每个种子必须提供以下之一:

  • 0到1之间的7 + n个实数(如果要控制段数)
  • 7个实数和一个大于1的整数(对于随机数的段)

我想您可以通过简单地提供一个数字数组作为种子s来完成上述任一操作。或者,您可以执行一些操作,例如提供一个数字s作为种子,然后使用rand(s),rand(s + 1),rand(s + 2)等调用内置的随机数生成器(或初始化为s,然后继续调用rand.NextNumber)。

请注意,即使整个函数f(t,s)由许多段组成,您也只需要为每个t评估一个段。您需要使用此方法重复计算线段的边界,因为您必须对它们进行排序以确保没有两个线段重叠。您可能可以优化并摆脱这些额外的工作,只为每个调用找到一个分段的端点,但是对我而言这现在并不明显。

同样,贝塞尔曲线不是必需的,任何适当的样条曲线都可以。

我创建了一个示例Matlab实现。

Bezier函数(矢量化):

function p = bezier(t, points)
% p = bezier(t, points) takes 4 2-dimensional points defined by 2-by-4 matrix
% points and gives the value of the Bezier curve between these points at t.
% 
% t can be a number or 1-by-n vector. p will be an n-by-2 matrix.
    coeffs = [
        (1-t').^3, ...
        3*(1-t').^2.*t', ...
        3*(1-t').*t'.^2, ...
        t'.^3
    ];

    p = coeffs * points;
end

上面描述的复合Bezier函数(故意不进行矢量化处理,以使每个调用都需要进行多少评估):

function p = bezier_compound(t, ends, s)
% p = bezier(t, points) takes 2 2-dimensional endpoints defined by a 2-by-2
% matrix ends and gives the value of a "compound" Bezier curve between
% these points at t.
% 
% t can be a number or 1-by-n vector. s must be a 1-by-7+m vector of random
% numbers from 0 to 1. p will be an n-by-2 matrix. 
    %% Generate a list of segment boundaries
    seg_bounds = [0, sort(s(9:end)), 1];

    %% Find which segment t falls on
    seg = find(seg_bounds(1:end-1)<=t, 1, 'last');

    %% Find the points that segment boundaries evaluate to
    points(1, :) = ends(1, :);
    points(2, :) = [s(1), s(2)];
    points(3, :) = [s(3), s(4)];
    points(4, :) = ends(2, :);

    p1 = bezier(seg_bounds(seg), points);
    p4 = bezier(seg_bounds(seg+1), points);

    %% Random middle points
    p2 = [s(5), s(6)] .* (p4-p1) + p1;
    p3 = [s(7), s(8)] .* (p4-p1) + p1;

    %% Gather together these points
    p_seg = [p1; p2; p3; p4];

    %% Find what part of this segment t falls on
    t_seg = (t-seg_bounds(seg))/(seg_bounds(seg+1)-seg_bounds(seg));

    %% Evaluate
    p = bezier(t_seg, p_seg);    
end

绘制随机种子函数的脚本(请注意,这是唯一一个调用随机函数的地方,所有其他代码的随机变量都是从该随机数组传播的):

clear
clc

% How many samples of the function to plot (higher = higher resolution)
points = 1000;

ends = [
    0, 0;
    1, 1;
    ];

% a row vector of 12 random points
r = rand(1, 12);

p = zeros(points, 2);

for i=0:points-1
    t = i/points;
    p(i+1, :) = bezier_compound(t, ends, r);
end

% We take a 1-p to invert along y-axis here because it was easier to
% implement a function for slowly moving away from a point towards another.
scatter(p(:, 1), 1-p(:, 2), '.');
xlabel('Time');
ylabel('Distance to target');

这是一个示例输出:

在此处输入图片说明

它似乎符合您的大多数标准。然而:

  • 有“角”。通过更适当地使用贝塞尔曲线可以做到这一点。
  • 它“显然”看起来像样条曲线,尽管除非您知道种子,否则您无法真正猜测它在不平凡的一段时间后会做什么。
  • 它很少会向拐角偏移太多(可以通过调节种子生成器的分布来解决)。
  • 给定这些约束,三次贝塞尔函数不能到达拐角附近的区域。

1

我猜想,您可以混合几个以f(0)= 0开始的单调函数,如f(x)= x或2x,或x ^ 2等。事实上,由于您的域限制为0 => 1,因此您还可以混合适合该域内账单的触发函数,例如cos(90 * x + 270)。要将您的方法标准化为以1结尾,您可以简单地将这些从f(0)= 0开始的单调方法的加权和除以f(1)。这样的事情也应该很容易反转(我从无状态实函数与编程函数的角度收集了您想要的东西)。

希望这可以帮助。


1

可以分析这张原始图片在此处输入图片说明 您可以得到一个函数,该函数可以通过使用统一的rand函数即时执行动画。我知道这不是确切的数学公式,但实际上没有随机函数的数学公式,即使有一个数学函数,您也需要编写很多代码才能实现。考虑到您未指定任何平滑度条件,因此速度曲线是连续的$ C ^ 0 $(但由于您不使用机器人,因此无需担心不连续的加速曲线)。


“实际上对于随机函数没有数学公式”我想要一个噪声函数,而不是随机函数。有充分证据证明存在噪声功能。像这样的分段定义也可能会导致效率低下(评估变为O(件),当您具有较长的标度时,这会成为问题),不纯函数(在O(1)中评估但需要保持先前的位置)或过度限制可能的功能(例如,所有拐点均处于固定间隔)。

嗯,对不起,我认为噪声函数也使用随机数生成器过程,并且还依赖于离散的引导/关键点集来产生形状(我看到提到了Perlin Noise。很难生成的数字生成器,因此没有解析解)。可以分析集成噪声功能吗?我想知道其中之一是否可以作为候选链接
teodron'3

例如,Perlin噪声采用255个8位数字的种子状态,但是由此产生了无限远的三维三维噪声。将它们描述为“指导点”并不是很准确,在数学上它们更像是您不想继续提供的另外256个参数。正如您所说,它本质上是不可集成的,但它是一个纯函数。您链接到的页面对Perlin噪声的解释很糟糕(他解释的并不是Perlin噪声)。至于是否可能为某些类型的噪声功能......嗯,那就是问题,是不是?

1

从[0,1]生成N个随机数递增序列的通常方法是生成任意范围内的N个随机数,然后将它们全部除以它们的总和,然后一次求和以得到序列。

生成序列2、2、5、8、6。
他们的总和为23,所以我们的总和为2 / 23、2 / 23、5 / 23、8 / 23和6/23。
我们的最终顺序是2 / 23、4 / 23、9 / 23、17 / 23、23 / 23

通过为X和Y生成这些值,可以将其扩展为2D。您可以增加N以获得所需的任何粒度。


在@teodron的类似答案中,您引用了大范围的效率问题。在不知道您面临的实际问题的情况下,我无法确定这种担忧是否成立。但是另一种选择是针对较小的 N 生成并简单地平滑结果。根据应用程序,这实际上可能会产生更好的结果。

在此处输入图片说明
N = 100,不平滑

在此处输入图片说明
N = 15,具有平滑


无论您做什么平滑处理,似乎都无法获得结果(x = 0.95左右)。我不确定这是您的制图程序的人工产物还是错误。在0.7左右也似乎违反了单调性。无论如何,我熟悉“通常的方式”-之所以问这个问题,是因为我怀疑通常的方式是cr脚的。毕竟,在佩林之前的噪声中,没有人遇到过巨大的价值噪声LUT问题,这只是“通常的方式”。今天,我们有了一种更加灵活高效的方法。

3
我同意BlueRaja的观点:无论采用哪种示例,都有许多众所周知的易于实现的平滑方式而不会违反单调性。例如,移动平均线或绘图样条线。但是,@ JoeWreschnig的关注并非无关紧要。游戏规则和机制可能取决于永不退缩的对象-假设提问者确实不需要他说的东西是很少的好主意。
最佳

1
@BlueRaja:我对teodrone的回复描述了我对这种分段方法的基本抱怨。这并不是要找到“最严格,最精确的数学结果”,而是要用我们以前未知的数学工具开辟新的可能性。再次,考虑巨值噪声LUT和Perlin噪声之间的类比。并非网站上的每个问题都需要即时可用的“足够好”的答案,任何中途的CS本科生都可能会在讲座之间碰到麻烦-有时,让我们来做点原创和专业的事情,好吗?

1
或者,我们可以继续让该站点陷入有关转换矩阵的90%的基本困惑中,而让10%的“帮助我停止玩游戏!” 这将使每个专业人士都喜欢来到一个很棒的问答网站。

2
@Joe:那是不对的。您要求一种适合您条件的解决方案,我给了您一个。仅仅因为它简单并不会使它变坏。
BlueRaja-Danny Pflughoeft 2012年

1

我建议此实现的灵感来自分形噪声中发现的八度音的总和,并在此到处散布一些廉价的屁股。我相信它的速度相当快,可以通过请求比存储在参数中的参数更少的八度来进行调整,而精度损失约为1/2^octave

您可以将其视为仅需要O(log(pieces))时间的分段实现。参数数组既用于分治制枢轴位置,也用于到达枢轴时的行进距离。

template<int N> struct Trajectory
{
    Trajectory(int seed = 0)
    {
        /* The behaviour can be tuned by changing 0.2 and 0.6 below. */
        if (seed)
            srand(seed);
        for (int i = 0; i < N; i++)
            m_params[i] = 0.2 + 0.6 * (double)(rand() % 4096) / 4096;
    }

    double Get(double t, int depth = N)
    {
        double min = 0.0, max = 1.0;
        for (int i = 0, dir = 0; i < N && i < depth; i++)
        {
            int j = (dir + 1 + i) % N;
            double mid = min + (max - min) * m_params[j];
            if (t < m_params[i])
            {
                dir += 1;
                t = t / m_params[i];
                max = mid;
            }
            else
            {
                dir ^= i;
                t = (t - m_params[i]) / (1.0 - m_params[i]);
                min = mid;
            }
        }
        t = (3.0 - 2.0 * t) * t * t; // Optional smoothing
        return min + (max - min) * t;
    }

    double m_params[N];
};

通过预先计算浮点除法,可以更快地进行存储,但需要存储三倍的信息。

这是一个简单的示例:

五个不同的轨迹

该示例是使用以下代码获得的:

for (int run = 0; run < 5; run++)
{
    /* Create a new shuffled trajectory */
    Trajectory<12> traj;

    /* Print dots */
    for (double t = 0; t <= 1.0; t += 0.0001)
        printf("%g %g\n", t, traj.Get(t));
}

0

大声思考,承认微积分不是我的强项……这也许不可能吗?为了避免出现任何明显的模式,x上任何变化的噪声函数的平均值必须接近零,并且为了确保单调性,x上该变化的噪声幅度必须小于x的变化,因为任何较大的幅度都可能导致x'处的值相对于x较低。但这意味着当您将dx减小到0时,该函数还必须将dA(其中A是幅度)减小到零,这意味着您不会从任何兼容的噪声函数中受益。

我可以想象有可能制定出一个函数,该函数可以在x接近1时逐渐降低噪声的贡献,但是会给您一个曲线函数,当x接近1时它会减速,这不是我想的。


1
我可以绘制数百万个此类函数的图,并且正如SkimFlux所说,如果对它进行归一化,则噪声函数的集成将提供几乎等效的函数。因此功能是否存在,仅取决于它们是否可行编码。因此,在这里询问而不是math.se。

例如,作为减速x接近1的任何功能的当量“反向”的功能g(x) = 1 - f(1 - x),它代替加速为x 0出发

当然,这些功能存在-您可以像teodron一样画一个-但是它们是“噪音”功能吗?噪声表示基于伪随机输入的连续函数,该函数具有相对于基线的隐式幅度。而且,如果该幅度太高,那么您将无法保证各步之间的差异足够小,无法保持输出单调。但是我的确想到,噪声的密度和内插步骤可以精心设计以满足您的要求,我将对此进行更多考虑。
Kylotan 2012年

噪波只是意味着它“不可预测”,它没有说明生成方法(甚至从技术上讲,也没有连续性,尽管对于动画来说,您几乎总是需要连贯的噪波)。的确,固定端点在某种程度上但并非完全限制了此功能的可能幅度。其他噪声函数具有相似的属性,例如,对于任何整数x,Perlin(x)= 0。单调性是比这更强的保证,但是我不认为单调性太强使得它不可能。

@JoeWreschnig我确定您知道Perlin噪声函数公然违反了您的一些标准。首先,它在网格节点处通过0,因此f(x + d)-f(x)对于某些x(规则间隔)是d的常数倍。此外,由于这种巧妙的缓存技巧,它将在大型网格中重复。对于经典噪声,我认为参考实现应假定网格图块(x,y)与图块(x + 256,y + 256)相同。您应该说明这是否可以接受,以及达到什么程度。
最佳
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.