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 ほぼパーリン ノイズ画像 (スケール 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))

これにより、さらに悪いように見える次の結果が得られます。

動作するはずの本当に悪いパーリン ノイズ アルゴリズム

基本的には、各軸を補間するだけだと思っていました.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 です。この方法は、「c​​heap Phong」補間と呼ばれることもあります。

方程式を理解するのに十分なコンテキストを得るには、おそらく記事全体を読む必要があります。

于 2013-07-23T20:43:08.907 に答える