简体   繁体   English

使用Perlin噪音来制造闪电?

[英]Using Perlin noise to create lightning?

Actually I am having several questions related to the subject given in the topic title. 实际上我在主题标题中给出了与主题相关的几个问题。

I am already using Perlin functions to create lightning in my application, but I am not totally happy about my implementation. 我已经在我的应用程序中使用Perlin函数创建闪电,但我对我的实现并不完全满意。

The following questions are based on the initial and the improved Perlin noise implementations. 以下问题基于初始和改进的Perlin噪声实现。

To simplify the issue, let's assume I am creating a simple 2D lightning by modulating the height of a horizontal line consisting of N nodes at these nodes using a 1D Perlin function. 为了简化这个问题,让我们假设我通过使用1D Perlin函数调制由这些节点上的N个节点组成的水平线的高度来创建简单的2D闪电。

  1. As far as I have understood, two subsequent values passed to the Perlin function must differ by at least one, or the resulting two values will be identical. 据我所知,传递给Perlin函数的两个后续值必须相差至少一个,否则得到的两个值将是相同的。 That is because with the simple Perlin implementation, the Random function works with an int argument, and in the improved implementation values are mapped to [0..255] and are then used as index into an array containing the values [0..255] in a random distribution. 这是因为使用简单的Perlin实现,Random函数使用int参数,并且在改进的实现中,值被映射到[0..255],然后用作包含值[0..255]的数组的索引]随机分布。 Is that right? 是对的吗?

  2. How do I achieve that the first and the last offset value (ie for nodes 0 and N-1) returned by the Perlin function is always 0 (zero)? 如何实现Perlin函数返回的第一个和最后一个偏移值(即节点0和N-1)始终为0(零)? Right now I am modulation a sine function (0 .. Pi) with my Perlin function to achieve that, but that's not really what I want. 现在我用我的Perlin函数调制一个正弦函数(0 .. Pi)来实现这个目标,但这并不是我想要的。 Just setting them to zero is not what I want, since I want a nice lightning path w/o jaggies at its ends. 将它们设置为零并不是我想要的,因为我想要一条不错的闪电路径,而不是锯齿状的。

  3. How do I vary the Perlin function (so that I would get two different paths I could use as animation start and end frames for the lightning)? 如何改变Perlin功能(这样我可以使用两条不同的路径作为闪电的动画起点和终帧)? I could of course add a fixed random offset per path calculation to each node value, or use a differently setup permutation table for improved Perlin noise, but are there better options? 我当然可以为每个节点值添加固定的随机偏移量,或者使用不同的设置置换表来改善Perlin噪声,但是有更好的选择吗?

  1. That depends on how you implement it and sample from it. 这取决于您如何实现它并从中进行采样。 Using multiple octaves helps counter integers quite a bit. 使用多个八度音程可以帮助计算整数。

    The octaves and additional interpolation/sampling done for each provides much of the noise in perlin noise. 每个八度音程和额外的插值/采样都会在perlin噪声中提供大部分噪声。 In theory, you should not need to use different integer positions; 理论上,您不需要使用不同的整数位置; you should be able to sample at any point and it will be similar (but not always identical) to nearby values. 你应该能够在任何一点进行采样,它与附近的值相似(但并不总是相同)。

  2. I would suggest using the perlin as a multiplier instead of simply additive, and use a curve over the course of the lightning. 我建议使用perlin作为乘数而不是简单的加法,并在闪电过程中使用曲线。 For example, having perlin in the range [-1.5, 1.5] and a normal curve over the lightning (0 at both ends, 1 in the center), lightning + (perlin * curve) will keep your ends points still. 例如,perlin在范围[-1.5,1.5]和闪电上的正常曲线(两端为0,中心为1), lightning + (perlin * curve)将使您的终点保持不变。 Depending on how you've implemented your perlin noise generator, you may need something like: 根据您实现perlin噪声发生器的方式,您可能需要以下内容:

    lightning.x += ((perlin(lightning.y, octaves) * 2.0) - 0.5) * curve(lightning.y);

    if perlin returns [0,1] or 如果perlin返回[0,1]或

    lightning.x += (perlin(lightning.y, octaves) / 128.0) * curve(lightning.y);

    if it returns [0, 255]. 如果它返回[0,255]。 Assuming lightning.x started with a given value, perhaps 0, that would give a somewhat jagged line that still met the original start and end points. 假设lightning.x以给定值(可能为0)开始,这将产生仍然符合原始起点和终点的有点锯齿状线。

  3. Add a dimension to the noise for every dimension you add to the lightning. 为添加到闪电的每个维度添加一个维度。 If you're modifying the lightning in one dimension (horizontal jagged), you need 1D perlin noise. 如果您在一维中修改闪电(水平锯齿状),则需要1D perlin噪声。 If you want to animate it, you need 2D. 如果你想为它设置动画,你需要2D。 If you wanted lightning that was jagged on two axis and animated, you'd need 3D noise, and so on. 如果你想要在两轴和动画上锯齿状的闪电,你需要3D噪音,依此类推。

After reading peachykeen's answer and doing some (more) own research in the internet, I have found the following solution to work for me. 在阅读了peachykeen的回答并在互联网上做了一些(更多)自己的研究后,我发现以下解决方案对我有用。

  1. With my implementation of Perlin noise, using a value range of [0.0 .. 1.0] for the lightning path nodes work best, passing the value (double) M / (double) N for node M to the Perlin noise function. 通过实现Perlin噪声,对闪电路径节点使用[0.0 .. 1.0]的值范围最佳,将节点M的值(双)M /(双)N传递给Perlin噪声函数。

  2. To have a noise function F' return the same value for node 0 and node N-1, the following formula can be applied: F'(M) = ((M - N) * F(N) + N * F (N - M)) / M. In order to have the lightning path offsets begin and end with 0, you simply need to subtract F'(0) from all lightning path offsets after having computed the path. 为了使噪声函数F'为节点0和节点N-1返回相同的值,可以应用以下公式:F'(M)=((M-N)* F(N)+ N * F(N - M))/ M.为了使闪电路径偏移以0开始和结束,您只需在计算路径后从所有闪电路径偏移中减去F'(0)。

  3. To randomize the lightning path, before computing the offsets for each path node, a random offset R can be computed and added to the values passed to the noise function, so that a node's offset O = F'(N+R). 为了使闪电路径随机化,在计算每个路径节点的偏移之前,可以计算随机偏移R并将其添加到传递给噪声函数的值,使得节点的偏移O = F'(N + R)。 To animate a lightning, two lightning paths need to be computed (start and end frame), and then each path vertex has to be lerped between its start and end position. 要为闪电设置动画,需要计算两条闪电路径(起始帧和结束帧),然后必须在其起始位置和结束位置之间搜索每个路径顶点。 Once the end frame has been reached, the end frame becomes the start frame and a new end frame is computed. 一旦到达结束帧,结束帧就成为起始帧并计算新的结束帧。 For a 3D path, for each path node N two offset vectors can be computed that are perpendicular to the path at node N and each other, and can be scaled with two 1D Perlin noise values to lerp the node position from start to end frame position. 对于3D路径,对于每个路径节点N,可以计算两个偏移矢量,其垂直于节点N处的路径并且彼此相邻,并且可以使用两个1D Perlin噪声值来缩放以从开始到结束帧位置来缩放节点位置。 。 That may be cheaper than doing 3D Perlin noise and works quite well in my application. 这可能比做3D Perlin噪音便宜,并且在我的应用程序中运行得很好。

Here is my implementation of standard 1D Perlin noise as a reference (some stuff is virtual because I am using this as base for improved Perlin noise, allowing to use standard or improved Perlin noise in a strategy pattern application. The code has been simplified somewhat as well to make it more concise for publishing it here): 以下是我对标准1D Perlin噪声的实现作为参考(有些东西是虚拟的,因为我使用它作为改进Perlin噪声的基础,允许在策略模式应用中使用标准或改进的Perlin噪声。代码已经有所简化了以便在这里发布它更简洁):

Header file: 头文件:

#ifndef __PERLIN_H
#define __PERLIN_H

class CPerlin {
  private:
    int m_randomize;

  protected:  
    double m_amplitude;
    double m_persistence;
    int m_octaves;

  public:
    virtual void Setup (double amplitude, double persistence, int octaves, int randomize = -1);
    double ComputeNoise (double x);

  protected:  
    double LinearInterpolate (double a, double b, double x);
    double CosineInterpolate (double a, double b, double x);
    double CubicInterpolate (double v0, double v1, double v2, double v3, double x);
    double Noise (int v);       
    double SmoothedNoise (int x);
    virtual double InterpolatedNoise (double x);
  };

#endif //__PERLIN_H

Implementation: 执行:

#include <math.h>
#include <stdlib.h>
#include "perlin.h"

#define INTERPOLATION_METHOD 1

#ifndef Pi
#  define  Pi 3.141592653589793240
#endif

inline double CPerlin::Noise (int n) {
  n = (n << 13) ^ n;
  return 1.0 - ((n * (n * n * 15731 + 789221) + 1376312589) & 0x7fffffff) / 1073741824.0;    
  }

double CPerlin::LinearInterpolate (double a, double b, double x) {
  return a * (1.0 - x) + b * x;
  }

double CPerlin::CosineInterpolate (double a, double b, double x) {
  double f = (1.0 - cos (x * Pi)) * 0.5;
  return  a * (1.0 - f) + b * f;
  }

double CPerlin::CubicInterpolate (double v0, double v1, double v2, double v3, double x) {
  double p = (v3 - v2) - (v0 - v1);
  double x2 = x * x;
  return v1 + (v2 - v0) * x + (v0 - v1 - p) * x2 + p * x2 * x;
  }

double CPerlin::SmoothedNoise (int v) {
  return Noise (v) / 2  +  Noise (v-1) / 4  +  Noise (v+1) / 4;
  }

int FastFloor (double v) { return (int) ((v < 0) ? v - 1 : v; }

double CPerlin::InterpolatedNoise (double v) {
  int i = FastFloor (v);
  double v1 = SmoothedNoise (i);
  double v2 = SmoothedNoise (i + 1);
#if INTERPOLATION_METHOD == 2
  double v0 = SmoothedNoise (i - 1);
  double v3 = SmoothedNoise (i + 2);
  return CubicInterpolate (v0, v1, v2, v3, v - i);
#elif INTERPOLATION_METHOD == 1
  return CosineInterpolate (v1, v2, v - i);
#else
  return LinearInterpolate (v1, v2, v - i);
#endif
  }

double CPerlin::ComputeNoise (double v) {
  double total = 0, amplitude = m_amplitude, frequency = 1.0;
  v += m_randomize;
  for (int i = 0; i < m_octaves; i++) {
    total += InterpolatedNoise (v * frequency) * amplitude;
    frequency *= 2.0;
    amplitude *= m_persistence;
    }
  return total;
  }

void CPerlin::Setup (double amplitude, double persistence, int octaves, int randomize) {
  m_amplitude = (amplitude > 0.0) ? amplitude : 1.0;
  m_persistence = (persistence > 0.0) ? persistence : 2.0 / 3.0;
  m_octaves = (octaves > 0) ? octaves : 6;
  m_randomize = (randomize < 0) ? (rand () * rand ()) & 0xFFFF : randomize;
  }

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM