2

はじめに


おい!

数週間前、JS チャレンジの小さなデモを行いました。このデモは、手続き的に生成されたheightmapに基づいて風景を表示していました。それを 3D サーフェスとして表示するために、ランダム ポイントの補間された高さを評価し (モンテカルロ レンダリング)、それらを投影しました。

その時、私は自分の方法にいくつかの不具合があることをすでに認識していましたが、助けを求めて挑戦が終わるのを待っていました. 私はあなたを頼りにしています。:)

問題


したがって、次のスクリーンショットで主なエラーを確認できます。

スクリーンショット - 補間エラー? http://code.aldream.net/img/interpolation-error.jpg

中央に見えるように、いくつかのポイントは半島の上に浮かんでいるように見え、密度の低いレリーフのように形成されています。問題はグローバルに見えますが、色の違いにより、背後にある海では特に明白です。

現在の方法


サーフェス補間

サーフェスの各ポイントの高さを評価するために、三角形分割 + 重心座標による線形補間を使用しています。

  1. A = (X,Y)、B = (X+1, Y)、C = (X, Y+1)、D = (X+1) で、私の点(x, y)である正方形 ABCD を見つけます, Y+1)XYはx, yの切り捨てられた値です。(各ポイントは私のハイトマップにマッピングされています)
  2. 条件を使用して、どの三角形 - ABD または ACD - 私の点がであるかを推定します: isInABD = dx > dy with dx, dy x, yの小数部。
  3. 線形補間を使用してポイントの高さを評価します
    • ABD の場合、高さ = h(B) + [h(A) - h(B)] * (1-dx) + [h(D) - h(B)] * dy
    • ACD の場合、高さ = h(C) + [h(A) - h(C)] * (1-dy) + [h(D) - h(C)] * dx、h(X) の高さ地図。

表示中

ポイントを表示するには、(x、y、高さ)をワールド座標に変換し、頂点を投影します (ヨー角とピッチ角を使用した単純な透視投影を使用)。取得したピクセルを描画するかどうかを確認するために、更新し続けるzBufferを使用します。

試み


私の印象では、いくつかのポイントで、補間された高さが間違っています。したがって、三角形分割+線形補間の実装で、いくつかのエラーまたはカバーされていない境界のケースを検索しようとしました。しかし、もしあれば、私はそれらを見つけることができません。

私は他のデモでプロジェクションを使用しているので、ここで問題が発生するとは思いません。zBuffering に関しては、それがどのように関連しているのかわかりません...

ここで運が尽きてきました...どんなヒントでも大歓迎です!

ご清聴ありがとうございました。よい一日を!



別館


JsFiddle-デモ

これは、微調整したい人のために、わずかに単純化されたデモ全体の jsFiddle http://jsfiddle.net/PWqDL/です...

JsFiddle - 補間の小さなテスト

この質問を書き留めていたとき、内挿の結果をよく見てみようという考えが浮かびました。いくつかの色相値を含む 2x2 マトリックスを使用する簡単なテストを実装し、キャンバスに表示する前に中間色を補間しました。

これが jsFiddle です: http://jsfiddle.net/y2K7n/

残念ながら、結果は、私が行っている「三角」補間の種類で予想される動作と一致しているように見えるので、間違いなくアイデアが不足しています。

コードサンプル

そして、これはレンダリング方法を説明する私の JS コードの最もおそらく間違っている部分を簡略化したものです (ただし、ここでは言語はあまり重要ではないと思います)。サイズ(SIZE x SIZE) :

    for (k = 0; k < nbMonteCarloPointsByFrame; k++) {
        // Random float indices:
        var i = Math.random() * (dim-1),
            j = Math.random() * (dim-1),
        // Integer part (troncated):
            iTronc = i|0,
            jTronc = j|0,
            indTronc = iTronc*dim + jTronc,
        // Decimal part:
            iDec = i%1,
            jDec = j%1,
        // Now we want to intrapolate the value of the float point from the surrounding points of our map. So we want to find in which triangle is our point to evaluate the weighted average of the 3 corresponding points.
        // We already know that our point is in the square defined by the map points (iTronc, jTronc), (iTronc+1, jTronc), (iTronc, jTronc+1), (iTronc+1, jTronc+1).
        // If we split this square into two rectangle using the diagonale [(iTronc, jTronc), (iTronc+1, jTronc+1)], we can deduce in which triangle is our point with the following condition:
            whichTriangle = iDec < jDec, // ie "are we above or under the line j = jTronc + distanceBetweenLandscapePoints - (i-iTronc)"
            indThirdPointOfTriangle = indTronc +dim*whichTriangle +1-whichTriangle, // Top-right point of the square or bottm left, depending on which triangle we are in.
        // Intrapolating the point's height:
            deltaHeight1 = (displayHeightMap[indTronc] - displayHeightMap[indThirdPointOfTriangle]),
            deltaHeight2 = (displayHeightMap[indTronc+dim+1] - displayHeightMap[indThirdPointOfTriangle]),
            height = displayHeightMap[indThirdPointOfTriangle] + deltaHeight1 * (1-(whichTriangle? jDec:iDec)) + deltaHeight2 * (!whichTriangle? jDec:iDec),

            posX = i*distanceBetweenLandscapePoints - SIZE/2,
            posY = j*distanceBetweenLandscapePoints - SIZE/2,
            posZ = height - WATER_LVL;

        // 3D Projection:
        var temp1 = cosYaw*(posY - camPosY) - sinYaw*(posX - camPosX),
            temp2 = posZ - camPosZ,
            dX = (sinYaw*(posY - camPosY) + cosYaw*(posX - camPosX)),
            dY = sinPitch*temp2 + cosPitch*temp1,
            dZ = cosPitch*temp2 - sinPitch*temp1,
            pixelY = dY / dZ * minDim + canvasHeight,
            pixelX = dX / dZ * minDim + canvasWidth,
            canvasInd = pixelY * canvasWidth*2 + pixelX;

        if (!zBuffer[canvasInd] || (dZ < zBuffer[canvasInd])) { // We check if what we want to draw will be visible or behind another element. If it will be visible (for now), we draw it and update the zBuffer:
            zBuffer[canvasInd] = dZ;

            // Color:
            a.fillStyle = a.strokeStyle = EvaluateColor(displayHeightMap, indTronc); // Personal tweaking.

            a.fillRect(pixelX, pixelY, 1, 1);
        }
    }
4

1 に答える 1

2

とった。そして、それは予想通り愚かな間違いでした:フレームごとに zBuffer を再初期化していました...

通常、これはあなたがすべきことですが、私の場合、各フレーム(つまり、私のPainting()関数の呼び出し) は同じフレームに詳細を追加します(つまり、一定の視点から静的シーンを描画します)。

Painting()を呼び出すたびに zBuffer をリセットすると、前回の呼び出しで描画されたポイントの深度情報が失われます。したがって、対応するピクセルは空白と見なされ、深度に関係なく、投影されたポイントに対して再描画されます。

注: 再初期化を行わないと、zBuffer は非常に大きくなります。したがって、以前に行うべきだった別の修正は、投影されたポイントのピクセルの位置 (したがって zBuffer のインデックス) を整数値に変換することでした。

pixelY = dY / dZ * minDim + canvasHeight +.5|0,
pixelX = dX / dZ * minDim + canvasWidth +.5|0,
canvasInd = pixelY * canvasWidth*2 + pixelX;
if (dZ > 0 && (!zBuffer[canvasInd]  || (dZ < zBuffer[canvasInd]))) {
    // We draw the point and update the zBuffer.
}

楽しい事実

背後に海があるレリーフのグリッチがより明白に見える場合、それは色の違いだけではなく、風景の丘陵部分が平らな部分 (海など) よりも多くのポイントをレンダリングする必要があるためです。表面

ポイントの単純化したモンテカルロ サンプリングでは、この特性を考慮していません。つまり、Painting()を呼び出すたびに、海は統計的に陸地よりも密度が高くなります。

フレームごとに zBuffer が再初期化されるため、海は、山が覆われているはずの写真の領域で「戦いに勝った」ようになりました (そこでの「幽霊のような山」効果を説明しています)。

修正された JsFiddle

興味のある方のための修正版: http://jsfiddle.net/W997s/1/

于 2013-05-09T16:41:36.683 に答える