【问题标题】:Perlin noise interpolationPerlin 噪声插值
【发布时间】:2013-07-23 02:54:51
【问题描述】:

我一直在使用 thisthis 在 F# 中编写 Perlin 噪声生成器,并且在算法的插值部分之前一直是成功的。这是到目前为止的工作代码(您可能不需要阅读接下来的 2 个代码块,因为它们只是为了上下文,所以不要害怕):

// PerlinNoiseProvider3D.fs
open System
open System.Collections.Generic
open Axiom.Math
open Ops

// Instances return a pseudorandom noise value between -1 and 1. scale defines how far apart the grid points are spaced.
type PerlinNoiseProvider3D(scale) =
  let randGen = new Random()
  // Each point in the grid has a random gradient
  let grid = new Dictionary<Vector3, Vector3>()

  // I stole this handy algorithm from this SE question:
  // http://math.stackexchange.com/questions/44689/how-to-find-a-random-axis-or-unit-vector-in-3d
  let randomAngle() =
    // Random value between 0 and 2π
    let θ = randGen.NextDouble() * Math.PI * 2.0
    // Random value between -1 and 1
    let z = randGen.NextDouble() * 2.0 - 1.0
    // Compute the resulting point
    (sqrt(1.0 - (z**2.0)) * cos(θ)) @@ (sqrt(1.0 - (z**2.0)) * sin(θ)) @@ z

  // Returns the gradient (just a vector pointing in a particular direction) at the given GRID point (not world coordinates)
  let getGradientAt v =
    try
      grid.[v]
    with
      | :? KeyNotFoundException -> grid.[v] <- randomAngle(); grid.[v]

  // Calculates the influence of the gradient g at gp on p, which is the dot product of gp and a vector facing from gp to p. Note that gp is _not_ in grid coordinates!!
  let influence (p: Vector3) (gp: Vector3) (g: Vector3) =
    // Find the vector going from the gp to p 
    let f = p - gp
    // Dot product on the grandient and the vector facing p from gp
    float <| (g.x * f.x) + (g.y * f.y) + (g.z * f.y)

  member this.GetValue (point: Vector3) =
    let v: Vector3 = point / scale
    // There's gotta be a shorter way to do this...
    let extract (i: Vector3) (min: Vector3) (max: Vector3) =
      let x =
        match int i.x with
        | 0 -> min.x
        | 1 -> max.x
        | _ -> failwith "Bad x index (shouldn't happen!)"
      let y =
        match int i.y with
        | 0 -> min.y
        | 1 -> max.y
        | _ -> failwith "Bad y index (shouldn't happen!)"
      let z =
        match int i.z with
        | 0 -> min.z
        | 1 -> max.z
        | _ -> failwith "Bad z index (shouldn't happen!)"
      x @@ y @@ z
    let min = (floor <| float v.x) @@ (floor <| float v.y) @@ (floor <| float v.z)
    let max = (ceil <| float v.x) @@ (ceil <| float v.y) @@ (ceil <| float v.z)
    // Relative 3D array of neighboring points (fst in tuple) and data (snd in tuples)
    let neighbors = Array3D.init 2 2 2 (fun xi yi zi -> extract (xi @@ yi @@ zi) min max, 0 @@ 0 @@ 0)
    let pInfluence = influence v
    let influences = neighbors |> Array3D.map (fun (p, g) -> pInfluence p (getGradientAt p))

// Ops.fs
module Ops
  let average (numbers: float list) =
    let rec average' (numbers': float list) =
      if numbers'.Length > 1 then
        average' [
          for i in 0..2..(numbers'.Length - 1) ->
            let a = numbers'.[i]
            try
              (a + numbers'.[i + 1]) / 2.0
            with
            | :? System.ArgumentException -> a
        ]
      else
        numbers'
    (average' numbers).[0]

  // Temporary 3D array average
  let average3D numbers =
    // Simply flatten the list and use the average function defined earlier
    average [
      for x in 0..(Array3D.length1 numbers - 1) do
        for y in 0..(Array3D.length2 numbers - 1) do
          for z in 0..(Array3D.length3 numbers - 1) ->
            numbers.[x, y, z]
    ]

我知道有很多东西需要考虑,但是上面给出的代码都没有被破坏;它按预期工作会产生这样的不完整图像(只是缺少一些):

这是我为插值想出的(只是其他代码+这个,结果删除了average3D调用):

// PerlinNoiseProvider3D.fs
// ...
let interp((a: float, b), axis) = a + (axis * (b - a))
let Sx, Sy, Sz = S <| float v.x, S <| float v.y, S <| float v.z
let zE1, zE2, zE3, zE4 =
  (influences.[0, 0, 0], influences.[0, 0, 1]),
  (influences.[0, 1, 0], influences.[0, 1, 1]),
  (influences.[1, 0, 0], influences.[1, 0, 1]),
  (influences.[1, 1, 0], influences.[1, 1, 1])
// Interpolate the points along the z-axis
let xE1, xE2 =
  (interp(zE1, Sz), interp(zE2, Sz)),
  (interp(zE3, Sz), interp(zE4, Sz))
// Interpolate the edges along the x-axis
let yE = (interp(xE1, Sx), interp(xE2, Sx))
// And finally, interpolate the y edge to yield our final value
interp(yE, Sy)

// Ops.fs
// ..
// Ease curve
let ease p = (3.0 * (p ** 2.0)) - (2.0 * (p ** 3.0))
let S x = ease (x - (floor x))

这给出了这个结果,看起来更糟:

我认为您基本上应该对每个轴进行插值 - 在 3D 的情况下,所有 4 个 z 轴,然后是生成的 2 个 x 轴,最后是生成的 y 轴得到最终值。它似乎不起作用。我一定是在这里误解了某事;它只是行不通!也许我的插值函数是错误的。也许我的应用是错误的。任何帮助表示赞赏。甚至是对您应该如何执行此操作的确切解释 - 最后一步的大多数其他来源都说,“然后将这些点积插值在一起以获得最终值。”

PS:我正在使用游戏库 Axiom(Ogre 的 C# 端口)中的结构,主要是 Vector3,因此我定义了一个 @@ 运算符,该运算符在整个代码中使用,用于像这样轻松创建 Vector3 :1 @@ 2 @@ 3

【问题讨论】:

  • 糟糕,愚蠢的错误。改变
  • 我会更改它,但我必须更改至少 5 个字符才能更改帖子。
  • 等等,你在说什么?这实际上是在 F# 中,而不是在 C# 中。 (我是倒着读的,所以删除了我使用 C# 标签的合理错误)
  • 我在想你的意思是它是用 C# 编程的,并且不小心输入了 F# 错字,但后来我意识到代码不是 C#。没有伤害!
  • let yE = (interp(xE1, Sy), interp(xE2, Sx)) 是错字吗?看起来应该是:let yE = (interp(xE1, Sx), interp(xE2, Sx)).

标签: .net f# perlin-noise noise-generator


【解决方案1】:

来自here

11.1.3 法线向量插值 此技术使用类似的插值方法,不同之处在于跨 多边形是表面法线向量,而不是计算的 强度本身。 (此方法也称为 Phong 插值。) 顶点法线的计算与强度插值一样 方法。再次参考图 11.2,表示正常 向量点 P 为 NP ,我们有

NQ =  u*NB+(1-u)NA,  where  u=  AQ/AB 
NR =  w*NB+(1-w)NC,  where  w=  CR/CB 
NP =  v*NR+(1-v)NQ,  where  v=  QP/QR 

法向量也可以增量确定。

结果比强度插值更真实 方法,尤其是在考虑镜面反射时 - 高光渲染更忠实,马赫条纹大大增加 减少(但球体和圆柱体仍然容易出现马赫带 效果)。

这种方法的缺点是需要更多的计算 处理多边形中的每个插值点;在每个点 必须使用插值执行完整的着色计算 那个时候正常。

11.1.4 点积插值 该方法是强度插值和法线向量插值之间的一种折衷。在这里, 从扫描线的一端到另一端内插的量是 点积 N·L 和 (R·V)n 。这种方法有时被称为 'cheap Phong' 插值。

您可能必须阅读整篇文章才能获得足够的上下文来理解这些方程式。

【讨论】:

  • 我真的不需要插值算法;我已经在尝试使用我阅读的两篇文章中建议的线性插值。我真的只需要知道我是否应用错了,如果是,如何解决它。
  • SO 不是特别适合这类问题的地方。
  • 嗯,那会是什么?有没有其他适合这个的 SE 网站?
  • 我不确定您可以从哪里获得免费的调试帮助。
  • 这不是一个错误,我想我只是在这里使用了错误的插值(好吧,我想这在技术上是一个错误:P)。这是理解的问题;我什至不确定为什么您在 Perlin 噪声中使用插值 - 我知道插值是什么以及它通常用于什么。
猜你喜欢
  • 2014-04-18
  • 2013-06-29
  • 2014-02-15
  • 2020-06-06
  • 2011-09-20
  • 2021-06-30
  • 2020-06-22
  • 2011-03-19
  • 1970-01-01
相关资源
最近更新 更多