【问题标题】:Calculating normals on terrain mesh计算地形网格上的法线
【发布时间】:2017-05-22 18:57:10
【问题描述】:

我正在尝试通过减少三角形数量来优化我的地形,同时保留尽可能多的细节。减少效果很好,我将顶点数量减少了五分之一,而没有太多的视觉损失。这个新的不对称网格的法线计算存在问题。

我每个顶点都有法线,这里是计算法线的 sn-p:

private void calcNormal(Vector<Triangle_dt> triangles, Point_dt point) {

    Vec3 normal = new Vec3(0, 0, 0);
    for (Triangle_dt triangle : triangles) {
        Vec3 a = getPos(triangle.p1());
        Vec3 b = getPos(triangle.p2());
        Vec3 c = getPos(triangle.p3());

        Vec3 AB = b.subtract(a);
        Vec3 AC = c.subtract(a);

        normal = normal.add(AB.cross(AC));
    }

    setNormal(point, normal.getUnitVector());
}

其中三角形是连接到顶点(点)的三角形。我将所有三角形法线加在一起(不进行归一化以使最终矢量按三角形面积加权),然后最后对最终结果进行归一化。

我相信计算是正确的,但结果中有令人讨厌的伪影(它被定向光照亮):

如您所见,在顶点稀疏的地方存在不需要的线。这是由于小的点簇靠近但远离下一组点(见下图)。知道如何防止这种情况吗?这是与点渲染相同的视图:

【问题讨论】:

  • 你有原始的高度图吗?那么最好从地形法线贴图纹理中对每个片段的法线进行采样。
  • 我有,可能值得一试,恐怕加载起来有点重(一维高度图以纯文本格式保存为 166 MB)
  • (1) 不要以文本形式存储!? (2) 使用 4 字节/顶点纹理可能比您现在使用的 3 个浮点数/顶点便宜。 (3) 通常,如果您有一个巨大的地形模型,那么您最好根据与相机的距离将其加载到瓦片中。 (4) 使用具有作为相机距离函数的分辨率的统一曲面细分以及高度和法线的平铺纹理可能比现在更容易实现并为您提供更好的结果。
  • @ybungalobill (1) 这只是为了调试目的,只是为了让您了解高度图的大小。非常感谢您的帮助和建议,我最终按照您所说的做了,从原始高度图渲染了法线贴图(有关详细信息,请参阅下面的答案)。 Spektre:这有点像我已经做过的,它在你的例子中效果很好,顶点是规则间隔的,但在我的例子中由于不规则的三角形大小,它没有使它足够平滑。

标签: java opengl 3d normals


【解决方案1】:

感谢 ybungalobill,我做了以下工作:

  1. 使用以下代码从原始高度图(对称网格)创建法线贴图:

根据高度图计算法线

// Calculating normals from height map
public void calcNormals() {
    Vec3 up = new Vec3(0, 1, 0);
    float sizeFactor = 1.0f / (8.0f * cellSize);
    normals = new Vec3[rows * cols];

    for (int row = 0; row < rows; row++) {
        for (int col = 0; col < cols; col++) {
            Vec3 normal = up;

            if (col > 0 && row > 0 && col < cols - 1 && row < rows - 1) {
                float nw = getValue(row - 1, col - 1);
                float n = getValue(row - 1, col);
                float ne = getValue(row - 1, col + 1);
                float e = getValue(row, col + 1);
                float se = getValue(row + 1, col + 1);
                float s = getValue(row + 1, col);
                float sw = getValue(row + 1, col - 1);
                float w = getValue(row, col - 1);

                float dydx = ((ne + 2 * e + se) - (nw + 2 * w + sw)) * sizeFactor;
                float dydz = ((sw + 2 * s + se) - (nw + 2 * n + ne)) * sizeFactor;

                normal = new Vec3(-dydx, 1.0f, -dydz).getUnitVector();
            }

            normals[row * cols + col] = normal;
        }
    }
}

从法线创建图像

public static BufferedImage getNormalMap(Terrain terrain) {
    Vec3[] normals = terrain.getNormals();
    float[] pixels = new float[normals.length * 3];

    for (int i = 0; i < normals.length; i++) {
        Vec3 normal = normals[i];
        float x = (1.0f + normal.x) * 0.5f;
        float y = (1.0f + normal.y) * 0.5f;
        float z = (1.0f + normal.z) * 0.5f;
        pixels[i * 3] = x * MAX;
        pixels[i * 3 + 1] = y * MAX;
        pixels[i * 3 + 2] = z * MAX;
    }

    BufferedImage img = new BufferedImage(cols, rows, BufferedImage.TYPE_INT_RGB);
    WritableRaster imgRaster = img.getRaster();
    imgRaster.setPixels(0, 0, cols, rows, pixels);
    return img;
}
  1. 在片段着色器中应用图像并使用顶点着色器中的顶点位置计算纹理坐标:

片段着色器的一部分:

void main() {
    vec3 newNormal = texture(normalMap, vec2(worldPos0.x / maxX, worldPos0.z / maxZ)).xyz;
    newNormal = (2.0 * newNormal) - 1.0;
    outputColor = calcColor(normalize(newNormal));
}

结果如下:

与点渲染相同的视图:

换句话说:顶点少但视觉上细节丰富的地形

【讨论】:

    猜你喜欢
    • 2014-11-07
    • 2012-11-10
    • 2012-12-08
    • 2011-10-03
    • 2021-01-02
    • 2011-05-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多