4

我一直在使用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]
    ]

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

这就是我想出的插值(只是其他代码+这个,结果删除了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))

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

应该可以工作的非常糟糕的 Perlin 噪声算法

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

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

4

1 回答 1

0

这里

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。这种方法有时被称为“便宜的 Phong”插值。

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

于 2013-07-23T20:43:08.907 回答