2

介绍


嘿!

几周前,我为一个 JS 挑战做了一个小演示。该演示显示基于程序生成的高度图的景观。为了将其显示为 3D 表面,我正在评估随机点的插值高度(蒙特卡罗渲染),然后投影它们。

那时,我已经意识到自己的方法有一些小问题,但我正在等待挑战结束寻求帮助。我指望你。:)

问题


所以我得到的主要错误可以在下面的截图中看到:

屏幕截图 - 插值错误?http://code.aldream.net/img/interpolation-error.jpg

正如您在中心看到的那样,一些点似乎漂浮在半岛上方,形成了一个密度较低的浮雕。由于颜色差异,后面的海尤其明显,尽管问题似乎是全球性的。

当前方法


曲面插值

为了评估表面每个点的高度,我使用重心坐标的三角测量+线性插值,即:

  1. 我发现我的点(x, y)在哪个正方形 ABCD中,其中A = (X,Y), B = (X+1, Y), C = (X, Y+1) 和 D = (X+1 , Y+1) , XYx, 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, height)转换为世界坐标,投影顶点(使用带有偏航角和俯仰角的简单透视投影)。我使用一个我不断更新的zBuffer来检查我是否绘制了获得的像素。

尝试


我的印象是,对于某些点,我得到了错误的插值高度。因此,我尝试在三角测量+线性插值的实现中搜索一些错误或一些未覆盖的边界情况。但如果有的话,我看不到它们。

我在其他演示中使用投影,所以我认为问题不是来自这里。至于zBuffering,我看不出它是如何相关的......

我在这里运气不好...欢迎任何提示!

感谢您的关注,祝您有美好的一天!



附件


JsFiddle - 演示

这是整个稍微简化的演示的 jsFiddle http://jsfiddle.net/PWqDL/,适合那些想要调整的人......

JsFiddle - 插值的小测试

当我写下这个问题时,我有了一个更好地了解插值结果的想法。我实现了一个简单的测试,其中我使用了一个包含一些色调值的 2x2 矩阵,并在将它们显示在画布中之前插入了中间颜色。

这是 jsFiddle:http: //jsfiddle.net/y2K7n/

,结果似乎与我正在做的那种“三角形”插值的预期行为相匹配,所以我肯定没有想法了。

代码示例

这是我的 JS 代码中描述我的渲染方法的最有可能错误的简化部分(但我认为语言在这里并不重要),给定一个正方形高度图“ displayHeightMap ”,其大小为(dim x dim)用于风景大小(大小 x 大小)

    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 回答