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