You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WebGL递归八面体生成球体:索引生成异常问题求助

问题描述

我正在尝试基于传入的recursionDepth参数,通过递归八面体细分生成类球体模型。目前顶点生成工作正常,但递归过程中三角形与线的索引生成不完整,例如当recursionDepth为2时,模型呈现残缺状态。以下是我的实现代码,恳请帮忙分析解决:

var octahedron = (function () {

    function midpoint(v1, v2) {
        return [
            (v1[0] + v2[0]) / 2,
            (v1[1] + v2[1]) / 2,
            (v1[2] + v2[2]) / 2
        ];
    }

    function normalize(v) {
        var length = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
        return [v[0] / length, v[1] / length, v[2] / length];
    }

    function createVertexData(recursionDepth) {
        // Positions.
        const originVertices = new Float32Array([
            0.0, 1.0, 0.0,  // Vertex 0: Top
            1.0, 0.0, 0.0,  // Vertex 1: Front-right
            -1.0, 0.0, 0.0, // Vertex 2: Front-left
            0.0, 0.0, 1.0,  // Vertex 3: Front-bottom
            0.0, 0.0, -1.0, // Vertex 4: Back-bottom
            0.0, -1.0, 0.0  // Vertex 5: Bottom
        ]);

        // Index data for triangles.
        const originIndicesTris = new Uint16Array([
            0, 1, 3,  // Top-front-right triangle
            0, 3, 2,  // Top-front-left triangle
            0, 2, 4,  // Top-back-left triangle
            0, 4, 1,  // Top-back-right triangle
            5, 3, 1,  // Bottom-front-right triangle
            5, 2, 3,  // Bottom-front-left triangle
            5, 4, 2,  // Bottom-back-left triangle
            5, 1, 4   // Bottom-back-right triangle
        ]);

        // Normals.
        const originNormals = new Float32Array([
            // Top vertex
            0.0, 1.0, 0.0,
            // Bottom vertex
            0.0, -1.0, 0.0,
            // Front vertex
            0.0, 0.0, 1.0,
            // Back vertex
            0.0, 0.0, -1.0,
            // Left vertex
            -1.0, 0.0, 0.0,
            // Right vertex
            1.0, 0.0, 0.0,
        ]);

        // Index data for lines.
        const originIndicesLines = new Uint16Array([
            0, 1, 1, 2, 2, 0,  // Top face
            0, 3, 3, 1,        // Front face
            0, 4, 4, 2,        // Back face
            5, 1, 1, 3, 3, 5,  // Bottom face
            5, 2, 2, 4,        // Left face
            5, 4, 4, 1         // Right face
        ]);

        // Subdivide each triangle and move the new vertices to the surface of a sphere.
        this.vertices = originVertices;
        this.normals = originNormals;
        this.indicesLines = originIndicesLines;
        this.indicesTris = originIndicesTris;

        function subdivideGeometry(depth, indicesTris, vertices) {
            if (depth <= 0) {
                // Base case: Return current geometry
                return {
                    vertices: vertices,
                    normals: new Float32Array(vertices), // 球体模型法线等于顶点位置
                    indicesTris: indicesTris,
                    indicesLines: generateLinesFromTris(indicesTris)
                };
            }

            const midpointCache = new Map();
            const newVertices = Array.from(vertices);
            const newIndicesTris = [];

            // 生成唯一的中点键(确保v1索引小于v2,避免重复缓存)
            function getMidpointKey(i1, i2) {
                return i1 < i2 ? `${i1}-${i2}` : `${i2}-${i1}`;
            }

            // 从缓存获取或生成中点
            function getOrCreateMidpoint(i1, i2) {
                const key = getMidpointKey(i1, i2);
                if (midpointCache.has(key)) {
                    return midpointCache.get(key);
                }

                const v1 = vertices.subarray(i1 * 3, i1 * 3 + 3);
                const v2 = vertices.subarray(i2 * 3, i2 * 3 + 3);
                const m = normalize(midpoint(v1, v2));
                const index = newVertices.length / 3;
                newVertices.push(...m);
                midpointCache.set(key, index);
                return index;
            }

            // 处理每个三角形
            for (let i = 0; i < indicesTris.length; i += 3) {
                const v0 = indicesTris[i];
                const v1 = indicesTris[i + 1];
                const v2 = indicesTris[i + 2];

                // 生成三个中点
                const m01 = getOrCreateMidpoint(v0, v1);
                const m12 = getOrCreateMidpoint(v1, v2);
                const m20 = getOrCreateMidpoint(v2, v0);

                // 添加四个新三角形的索引
                newIndicesTris.push(
                    v0, m01, m20,
                    m01, v1, m12,
                    m20, m12, v2,
                    m01, m12, m20
                );
            }

            // 递归进行下一层细分
            return subdivideGeometry(depth - 1, new Uint16Array(newIndicesTris), new Float32Array(newVertices));
        }

        // 根据三角形索引生成线框索引(避免重复边)
        function generateLinesFromTris(tris) {
            const edgeCache = new Set();
            const lines = [];

            function addEdge(a, b) {
                const key = a < b ? `${a}-${b}` : `${b}-${a}`;
                if (!edgeCache.has(key)) {
                    edgeCache.add(key);
                    lines.push(a, b);
                }
            }

            for (let i = 0; i < tris.length; i += 3) {
                const a = tris[i];
                const b = tris[i + 1];
                const c = tris[i + 2];
                addEdge(a, b);
                addEdge(b, c);
                addEdge(c, a);
            }

            return new Uint16Array(lines);
        }

        if (recursionDepth > 0) {
            // Call the recursive function
            const updatedGeometryData = subdivideGeometry(recursionDepth, this.indicesTris, this.vertices);

            this.vertices = updatedGeometryData.vertices;
            this.normals = updatedGeometryData.normals;
            this.indicesTris = updatedGeometryData.indicesTris;
            this.indicesLines = updatedGeometryData.indicesLines;
        }
    }

    return {
        createVertexData: createVertexData
    };
}());
关键问题分析与修复
  1. 顶点重复冗余
    原代码在处理每个三角形时,重复添加原始顶点和中点,导致顶点数量呈指数级冗余,同时索引计算完全混乱。修复后通过midpointCache缓存已生成的中点,确保每个边的中点只生成一次,大幅减少顶点数量并保证索引正确性。

  2. 索引生成逻辑错误
    原代码的新三角形索引写法完全错误,修复后将每个原始三角形拆分为4个新三角形,索引正确对应原始顶点和新生成的中点:

  • 第一个三角形:原顶点v0、中点m01、中点m20
  • 第二个三角形:中点m01、原顶点v1、中点m12
  • 第三个三角形:中点m20、中点m12、原顶点v2
  • 第四个三角形:中点m01、中点m12、中点m20
  1. 递归逻辑错误
    原递归时错误地用深层递归结果覆盖当前层的细分结果,修复后递归是在当前层细分的基础上继续处理,保证每一层细分都正确累积。

  2. 线框索引生成优化
    新增generateLinesFromTris函数,通过缓存边避免重复生成线框索引,确保线框完整且无冗余边。

  3. 全局变量修复
    给所有原始数据变量添加const声明,避免污染全局作用域。

内容的提问来源于stack exchange,提问作者neverhalfnot

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 14:37:32